拥有会员资格的用户可以读取所有数据

User with a membership can read all data

设置

我正在使用 Rails 5.2 和 cancancan gem。

rails g scaffold User first_name email:uniq
rails g scaffold Organization name:uniq
rails g scaffold Role name
rails g scaffold Membership user:references organization:references role:refences

user.rb

class User < ApplicationRecord
  has_many :memberships
  has_many :roles, through: :memberships
  has_many :organizations, through: :memberships
end

membership.rb

class Membership < ApplicationRecord
  belongs_to :role
  belongs_to :organization
  belongs_to :user
end

organization.rb

class Organization < ApplicationRecord
  has_many :memberships
  has_many :users, through: :memberships
end

role.rb

class Role < ApplicationRecord
  has_many :memberships
  has_many :users, through: :memberships
end

seeds.rb

admin = Role.create(name: 'Admin')
user = Role.create(name: 'User')

abc = Organization.create(name: 'Abc Inc.')

bob = User.create(first_name: 'Bob')
alice = User.create(first_name: 'Alice')

Membership.create(role: user, company: abc, role: user)
Membership.create(role: admin, company: abc, role: admin)

任务

管理员应该能够管理公司的所有用户和会员资格 he/she 是管理员。一个用户只能读取该公司的所有用户和会员。

这是我对 cancancan 配置的看法:

ability.rb

class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.new

    user_role = Role.find_by_name('User')
    admin_role = Role.find_by_name('Admin')

    organizations_with_user_role = Organization.includes(:memberships).
      where(memberships: {user_id: user.id, role_id: user_role.id})
    organizations_with_admin_role = Organization.includes(:memberships).
      where(memberships: {user_id: user.id, role_id: admin_role.id})

    can :read, Organization, organizations_with_user_role
    can :manage, Organization, organizations_with_admin_role
  end
end

然后我尝试 运行 视图中的这段代码:

<% if can? :read, organization %><%= link_to 'Show', organization %><% end %>

这会导致错误页面显示:

可以吗?不能? call 不能与原始 sql 'can' 定义一起使用。无法确定校验码:read #

我想我是从一个完全错误的角度来解决问题的。我必须如何设置 ability.rb 才能解决这个问题?

来自documentation

Almost anything that you can pass to a hash of conditions in Active Record will work here. The only exception is working with model ids. You can't pass in the model objects directly, you must pass in the ids.

can :manage, Project, group: { id: user.group_ids }

所以尝试这样的事情:

can :read, Organization, id: organizations_with_user_role.pluck(:id)

另外,您为什么使用 includes 而不是 joins?您的查询可以简化为(不需要 user_role = Role.find_by_name('User')):

organizations_with_user_role = Organization.joins(memberships: :role).
  where(memberships: {user_id: user.id}).where(roles: {name: 'User'})

我会在这里使用一个简单的条件散列:

can :read, Organization, memberships: { user_id: user.id, role: { name: 'User' } }
can :manage, Organization, memberships: { user_id: user.id, role: { name: 'Admin' } }

这应该足够了,您的优势在于,使用这种语法,您还可以调用 Organization.accessible_by(ability, :read) 并检索用户可以阅读的所有组织。