我的数据库中有一个用户表。用户可以是'admin'或'manager'类型的用户。
鉴于下面的模型和架构,我希望对于“经理”用户的每个实例,“管理员”用户可以选择经理所属的租户的一个,部分或全部位置,以便选择经理可以控制的位置。
class User < ActiveRecord::Base
belongs_to :tenant
class Tenant < ActiveRecord::Base
has_many :users, dependent: :destroy
has_many :locations, dependent: :destroy
class Location < ActiveRecord::Base
belongs_to :tenant, inverse_of: :locations
首先,尝试在用户模型和位置模型之间建立范围限定的has_many关联。但是,我不能全力以赴地设计这种范围,以使“管理员”用户可以选择“经理”用户可以控制的位置。
第二,在users表中设置了受控位置属性。然后,我设置了一些代码,以便“管理员”用户可以选择“经理”可以控制的位置,并填充其“ managed_locations”属性。但是,保存在数据库中(受控位置数组内部)的是字符串而不是位置实例。
def change
add_column :users, :controlled_locations, :string, array: true, default: []
end
= f.input :controlled_locations, label: 'Select', collection: @tenant_locations, include_blank: "Anything", wrapper_html: { class: 'form-group' }, as: :check_boxes, include_hidden: false, input_html: {multiple: true}
if params["user"]["controlled_locations"]
params["user"]["controlled_locations"].each do |l|
resource.controlled_locations << Location.find(l.to_i)
end
resource.save!
end
首先,我不确定我尝试的第二条路径是否是一种好方法(在db中存储数组)。因此,我最好的选择是建立一个可能的范围关联。
在第二条路径可行的情况下,我想得到的是这样的东西。假设登录管理员,我选择了ID为1的用户(管理员)可以控制一个位置(波士顿体育场):
user = User.find(1)
user.controlled_locations = [#<Location id: 55, name: "Boston Stadium", created_at: "2018-10-03 12:45:58", updated_at: "2018-10-03 12:45:58", tenant_id: 5>]
相反,尝试后得到的是:
user = User.find(1)
user.controlled_locations = ["#<Location:0x007fd2be0717a8>"]
保存在数组中的不是简单的位置实例,而是纯字符串。
答案 0 :(得分:0)
首先,您的代码缺少locations
类中的Tenant
关联。
class Tenant < ActiveRecord::Base
has_many :users, dependent: :destroy
has_many :locations
让我们说变量manager
有一条User
记录。那么它可以控制的位置是:
manager.tenant.locations
如果需要,可以使用委托语句来缩短它。
class User < ActiveRecord::Base
belongs_to :tenant
delegate :locations, to: :tenant
然后您可以使用
manager.locations
答案 1 :(得分:0)
用于授权的常见模式是角色:
class User < ApplicationRecord
has_many :user_roles
has_many :roles, through: :user_roles
def add_role(name, location)
self.roles << Role.find_or_create_by(name: name, location: location)
end
def has_role?(name, location)
self.roles.exists?(name: name, location: location)
end
end
# rails g model role name:string
# make sure you add a unique index on name and location
class Role < ApplicationRecord
belongs_to :location
has_many :user_roles
has_many :users, through: :user_roles
validates_uniqueness_of :name, scope: :location_id
end
# rails g model user_role user:references role:references
# make sure you add a unique compound index on role_id and user_id
class UserRole < ApplicationRecord
belongs_to :role
belongs_to :user
validates_uniqueness_of :user_id, scope: :role_id
end
class Location < ApplicationRecord
has_many :roles
has_many :users, through: :roles
end
通过使系统比“受控位置”关联更通用,可以在不同情况下重新使用它。
比方说,登录管理员后,我选择了ID为1的用户 (经理)可以控制一个地点(波士顿体育场)
User.find(1)
.add_role(:manager, Location.find_by(name: "Boston Stadium"))
在实际的MVC术语中,您可以通过将角色设置为可像其他任何资源一样进行CRUD的嵌套资源来实现。可以使用accepts_nested_attributes
或AJAX完成单一表单中的多个角色的编辑。
如果要通过角色的存在来限定查询的范围,则可以将角色和用户角色表结合起来:
Location.joins(roles: :user_roles)
.where(roles: { name: :manager })
.where(user_roles: { user_id: 1 })
要对单个资源进行身份验证,请执行以下操作:
class ApplicationController < ActionController::Base
protected
def deny_access
redirect_to "your/sign_in/path", error: 'You are not authorized.'
end
end
class LocationsController < ApplicationController
# ...
def update
@location = Location.find(params[:location_id])
deny_access and return unless current_user.has_role?(:manger, @location)
# ...
end
end