+1

Gem Pundit là gì ? Xây dựng một ứng dụng Rails áp dụng gem Pundit

I. Mở đầu

Bài viết hôm nay sẽ nói về việc sử dụng gem Pundit , là một thư viện được sử dụng trong các project của Rails, ta sẽ cùng nhau phân tích về gem này và áp dụng thực tế nó vào một project của Rails nhé.

II. Gem Pundit

1. Gem Pundit là gì ?

Nói đơn giản, pundit là một thư viện để phân quyền cho ứng dụng của bạn. Nếu bạn đang có ý định xây ứng một ứng dụng với nhiều loại User, thì gem pundit là một thư viện cực kì phù hợp để có thể hạn chế những tài nguyên mà User có thể truy cập được.

2 . Tại sao nên sử dụng

Hãy tưởng tượng rằng bạn đang xây dựng một với hàng loạt các loại User và cơ chế quyền khác nhau, và cách bạn thường làm là sẽ xử lí các logic này ở controller, dần dần nó sẽ khiến cho controller của bạn trở nên phình to ra, càng ngày nó sẽ càng phức tạp và khó kiểm soát để bảo trì code. Vậy lợi thế của gem pundit là gì, nó sẽ xử lí giúp cho controller ứng dụng của bạn trở nên nhẹ nhàng, thanh mảnh, loại bỏ độ phức tạp của code. Thay vào đó sẽ bằng những Policy khiến bạn có cái nhìn trực quan hơn, và dễ dàng nắm bắt được quyền hạn của từng loại User.

III. Xây dựng ứng dụng Rails áp dụng gem pundit

1. Cài đặt

Việc cài đặt vô cùng đơn giản như bao loại gem khác : Thêmgem "pundit" vào Gemfile của bạn và chạy lệnh bundle install

2. Xây dựng project

Ở đây mình sẽ xậy dụng ra một app nho nhỏ với 2 role Admin, User và có nhiều Post , chức năng chính sẽ là phân quyền cho User không thể create post, chỉ có admin mới có quyền create

  • Mình sẽ bỏ qua các bước tạo model cho User, Post và chỉ tập trung vào phần Pundit

Đầu tiên để sử dụng gem pundit chúng ta phải thêm Pundit vào application controller

class ApplicationController < ActionController::API
  include Pundit
end
  • Sau đó ta sẽ chạy lệnh:
rails g pundit:install

Lệnh này sẽ tạo ra 1 file application policy nằm ở app/policies/application_policy.rb, nó sẽ có những giá trị default như này :

class ApplicationPolicy
  attr_reader :user, :record

  def initialize(user, record)
    @user = user
    @record = record
  end

  def create?
    false
  end
  
   def create?
    false
  end

  class Scope
    attr_reader :user, :scope

    def initialize(user, scope)
      @user = user
      @scope = scope
    end

    def resolve
      scope.all
    end
  end
end

Giải thích sơ qua một chút về những thành phần trong Application Policy:

  • Ta thấy rằng tham số đầu tiền trong hàm initializer luôn là user, Pundit sẽ sử dụng phương thức current_user để lấy nó ra
  • Ở trên là file application, ngoài ra nếu muốn xây dựng từng Policy riêng ứng với từng model, tên class nên là tên của model sau đó đến từ Policy. Ví dụ: PostPolicy sẽ là policy ứng với model Post.
  • Policy sẽ có các phương thức như create? hoặc new? để kiểm tra quyền truy cập

Vào phần chính, mình sẽ tạo một policy là PostPolicy nằm ở thư mục, app/policies/post_policy.rb

class PostPolicy
  attr_reader :user, :post

  def initialize(user, post)
    @user = user
    @post = post
  end

  def create?
    user.admin?
  end
end

Và ở trong posts_controller, nó kế thừa từ ApplicationController , ta sẽ viết như sau

class PostsController < ApplicationController
  def create
    @post = Post.new
    authorize @post
    render json: {success: "true"}
  end
end

Ở controller, ta chỉ việc thêm phương thức authorize , phương thức này sẽ có khi ta include pundit vào trong application controller ở trên. Ở hàm initializer của PostPolicy ta đã khởi tạo tham số là @post, cho nên khi authorize @post ở controller , pundit sẽ tự động tìm kiếm PostPolicy dựa trên @post ta đã khai báo Và nó sẽ dựa vào action name của controller để chạy vào hàm action tương ứng ở PostPolicy và chạy phần code ta đã viết trong đó

Vậy thì @user ở đâu, ở controller ta đâu có truyền vào, như ở trên mình nói ở phần application policy, thì pundit sẽ sử dụng hàm current_user để lấy dữ liệu về user, điều này có nghĩa là mình phải tạo một hàm current_user ở trong ApplicationController

class ApplicationController < ActionController::API
  include Pundit

//Ví dụ mình sẽ tạo user có quyền admin 
  def current_user
   User.create email:"example@gamil.com", role: :admin
  end
end

Ở trong User model, mình sẽ tạo role, khai báo theo kiểu enum nhé

class User < ApplicationRecord
  has_many :post

  enum role: {admin:0 , user: 1}
end

Ok, như vậy là ta đã có current_user với quyền admin , ở hàm create? của PostPolicy ta sẽ kiểm trả user có phải admin hay không, nếu không nó sẽ raise ra lỗi

Pundit::NotAuthorizedError

III. Kết luận

Trên đây là bài viết tìm hiểu về gem Pundit, mình chỉ áp dụng một phần nhỏ để áp dụng vào project, tập trung vào việc pundit xử lý flow phân quyền như thế nào. Ngoài ra, còn có nhiều kiểu phân quyền khác nữa, ở trên chỉ là kiểu phân quyền cơ bản nhất. Bạn có thể tìm hiểu thêm tại đây.

Bài viết còn nhiều thiếu sót, hy vọng nhận được nhiều đóng góp ý kiến của mọi người.

Xin chân thành cảm ơn


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.