Hiểu rõ hơn về gem CanCanCan trong Ruby On Rails- Part 1
CanCanCan là một thư viện phân quyền cho Ruby và Ruby on Rails, nó hạn chế những tài nguyên mà một người dùng nhất định được phép truy cập. Tất cả các quyền có thể được xác định trong một hoặc nhiều tệp khả năng và không bị trùng lặp trên controller, view và query DB, giữ logic phân quyền ở một ...
- CanCanCan là một thư viện phân quyền cho Ruby và Ruby on Rails, nó hạn chế những tài nguyên mà một người dùng nhất định được phép truy cập.
- Tất cả các quyền có thể được xác định trong một hoặc nhiều tệp khả năng và không bị trùng lặp trên controller, view và query DB, giữ logic phân quyền ở một nơi để dễ dàng bảo trì và kiểm tra.
Điều gì sẽ sảy nếu bạn hoặc khách hàng muốn thay đổi quyền mà không phải triển khai lại Ứng dụng? Trong trường hợp đó, tốt nhất có thể lưu trữ logic quyền trong cơ sở dữ liệu: rất dễ sử dụng các bản ghi cơ sở dữ liệu khi xác định các khả năng.
class Ability include CanCan::Ability def initialize user if user.admin? can :manage, :all cannot :create, Borrowing, user_id: user.id cannot :read, Borrowing else can :create, Borrowing can :read, Borrowing, user_id: user.id can :read, Borrowing, ["user_id = ?", user.id] do |borrowing| borrowing.created_at.year >= 2021 end end end end
Trong CanCanCan các action được thể hiện như sau:
def eval_cancan_action(action) case action.to_s when "index", "show", "search" cancan_action = "read" action_desc = I18n.t :read when "create", "new" cancan_action = "create" action_desc = I18n.t :create when "edit", "update" cancan_action = "update" action_desc = I18n.t :edit when "delete", "destroy" cancan_action = "delete" action_desc = I18n.t :delete else cancan_action = action.to_s action_desc = "Other: " << cancan_action end return action_desc, cancan_action end
Điều gì xảy ra nếu bạn muốn xác định quyền của Người dùng không phải là current_user ? Giả sử mình muốn xem liệu một user khác có quyền xem được phiếu mượn hay không
some_user.ability.can? :update, @borrowing
Thêm phương thức ability vào model User:
def ability @ability ||= Ability.new(self) end
Sử sụng delegate để có thể gọi trực tiếp từ User:
class User < ActiveRecord::Base delegate :can?, :cannot?, to: :ability end
Kết quả sẽ được như sau:
User.last.can? :read, Borrowing.first (0.7ms) User Load (0.7ms) SELECT `users`.* FROM `users` WHERE `users`.`deleted_at` IS NULL ORDER BY `users`.`id` DESC LIMIT 1 Borrowing Load (1.3ms) SELECT `borrowings`.* FROM `borrowings` WHERE `borrowings`.`deleted_at` IS NULL ORDER BY `borrowings`.`id` ASC LIMIT 1 => false
Cuối cùng, nếu bạn muốn xem current_user có quyền truy cập vào object nào không thì tốt nhất bạn nên ghi đè mothod current_ability trong ApplicationController.
def current_ability @current_ability ||= current_user.ability end
Kết quả sẽ được test như sau:
current_ability.can? :create, Borrowing => false
Một ability rule sẽ ghi đè một ability rule trước đó. Giả sử Admin có toàn quyền với Borrowing, nhưng không được xoá chúng.
can :manage, Borrowing cannot :destroy, Borrowing
Điều quan trọng là cannot :destroy, Borrowing sau dòng can :manage, Borrowing . Như vậy,cannot :destroy sẽ bị overridden bởi can :manage.
Việc thêm can không ghi đè rule trước đó.
can :read, Borrowing, user_id: user.id can :read, Borrowing do |borrowing| borrowing.created_at.year > 2021 end
can? :read sẽ luôn trả về true nếu user_id = user.id kể cả nếu Borrowing có thời gian tạo nhỏ hơn 2021
Điều gì sẽ xảy ra nếu bạn cần sửa đổi các quyền dựa trên thứ gì đó bên ngoài đối tượng User? Giả sử bạn muốn đưa các địa chỉ IP nhất định vào danh sách đen khỏi việc tạo comment. Địa chỉ IP có thể truy cập thông qua request.remote_ip nhưng class Ability không có quyền truy cập vào địa chỉ này. Cách đơn giản đề sửa đổi những gì mà bạn truyền vào cho đối tượng Ability bằng việc ghi đè phương thức current_ability trong ApplicationController.
class ApplicationController < ActionController::Base private def current_ability @current_ability ||= Ability.new(current_user, request.remote_ip) end end
class Ability include CanCan::Ability def initialize(user, ip_address=nil) can :create, Comment unless BLACKLIST_IPS.include? ip_address end end
Concept này cũng có thể áp dụng cho Session hay Cookie.
Mặc định trong gem CanCanCan là quyền dựa trên user và object được xác định trong load_resource. Nhưng nếu bạn có 1 SearchController và Admin::SearchController, bạn có thể sử dụng một số tiếp cận khác.
Trong trường hợp này, chỉ cần ghi đè lại method current_ability trong ApplicationController để include controller namespace, và tạo một class Ability biết phải làm gì với nó
class ApplicationController < ActionController::Base private def current_ability controller_name_segments = params[:controller].split('/') controller_name_segments.pop controller_namespace = controller_name_segments.join('/').camelize @current_ability ||= Ability.new(current_user, controller_namespace) end end
class Ability include CanCan::Ability def initialize(user, controller_namespace) case controller_namespace when "Admin" can :manage, :all if user.admin? else # rules for non-admin controllers here end end end
Còn một cách khác là sử dụng một Ability class khác trong controller này:
class Admin::BaseController < ActionController::Base #... private def current_ability @current_ability ||= AdminAbility.new(current_user) end end
Kết luận
Thông qua bài viết này, mình mong giúp các bạn hiểu hơn về gem CanCanCan.