能力:条件依赖对象(allow :create only for team_members)

Ability: conditions dependent on the object (allow :create only for team_members)

设置

一个基本的酒店设置,其中 usershotel 的团队成员。它使用 cancancan and devise 和 Rails 5.2

rails g scaffold User name
rails g scaffold Hotel name
rails g scaffold TeamMembership user:references hotel:references
rails g scaffold Reservation starts_on:date ends_on:date hotel:references
rails g scaffold CheckIn hotel:references reservation:references

hotels 通过 has_many :users, through: :team_memberships 连接到 usersusershotels.

反之亦然

config/routes.rb

resources :hotels do
  resources :reservations
  resources :check_ins
end

app/controllers/check_ins_controller.rb

class CheckInsController < ApplicationController
  before_action :authenticate_user!
  load_and_authorize_resource :hotel
  load_and_authorize_resource :check_in, :through => :hotel
[...]

app/models/ability.rb

[...]
can [:read, :destroy], CheckIn, hotel_id: user.hotel_ids
can [:create], CheckIn
[...]

Problem/Question

在某个视图中我有这段代码:

<% if can? :create, CheckIn %>
  <%= link_to 'Create Check-In', new_hotel_check_in_path(@hotel) %>
<% end %>

它应该只对 @hotel 的团队成员可见。

ability.rb 的第一行工作正常但第二行不起作用,因为任何人都可以创建新的 check_in 但只有 team_memberships 应该能够创建他们酒店的新 check_in

解决这个问题的最佳方法是什么?显然 link 不应该显示,而且 /hotels/:hotel_id/check_ins/new URL 应该 可以被任何不是团队成员的人访问。

试试这个:

can [:create], CheckIn if user.team_memberships.present?

can [:create], CheckIn if user.hotels.present?

希望对您有所帮助。

这是一个常见问题,这是 business 逻辑与 authorization 逻辑相交的地方。

关于这件事,众说纷纭。

1) 很多人认为这种交叉路口是不可接受的。他们会建议你这样做(分离业务和授权逻辑)

<% if can?(:create, CheckIn) && current_user.member_of?(@hotel) %>
  <%= link_to 'Create Check-In', new_hotel_check_in_path(@hotel) %>
<% end %>

2) 如果你确定你需要这个,你可以这样做:

Hotel 模型添加新权限:

can [:check_in], Hotel do |hotel|
  user.member_of?(hotel)
end

然后在视图中:

<% if can?(:create, CheckIn) && can?(:check_in, @hotel) %>
  <%= link_to 'Create Check-In', new_hotel_check_in_path(@hotel) %>
<% end %>

在控制器中:

class CheckInsController < ApplicationController
  # ...
  def new
    authorize! :check_in, @hotel
    # ...
  end
end