Don*_*ker 9 activerecord ruby-on-rails ruby-on-rails-3 ruby-on-rails-3.1
我有一个rails应用程序,它有三种不同类型的用户,我需要它们共享相同的公共配置文件信息.但是,每个不同的用户本身也具有唯一的属性.我不确定如何分离出不同的领域.
我正在使用设计进行身份验证,可以使用cancan进行授权.因此,我有一个User模型,其中包含一组可应用于用户的角色.这个类看起来像这样:
class User < ActiveRecord::Base
# ... devise stuff omitted for brevity ...
# Roles association
has_many :assignments
has_many :roles, :through => :assignments
# For cancan: https://github.com/ryanb/cancan/wiki/Separate-Role-Model
def has_role?(role_sym)
roles.any? { |r| r.name.underscore.to_sym == role_sym }
end
end
Run Code Online (Sandbox Code Playgroud)
每个用户都有一个配置文件包括:
我不想用这个信息污染用户模型,所以我把它扔进了一个Profile模型.这部分很简单.这会将User模型转换为如下所示:
class User < ActiveRecord::Base
# ... devise stuff omitted for brevity ...
# ... cancan stuff omitted for brevity ...
has_one :profile
end
Run Code Online (Sandbox Code Playgroud)
额外的领域是我对如何模型有一些不安的感觉......
如果用户是管理员,他们将拥有唯一的字段,例如:
如果用户是所有者,他们将拥有唯一的字段......
如果用户是会员,他们将有一些额外的字段:
...
并且商店模型将在成员上包含has_many,因此我们处理商店的成员.
问题在于其他领域.我将它们设置为不同的类吗?把它们放到不同的我现在尝试了几种不同的方法来设置它:
一种方法是将用户模型设置为聚合根
class User < ActiveRecord::Base
# ...
# Roles association
has_many :assignments
has_many :roles, :through => :assignments
# Profile and other object types
has_one :profile
has_one :admin
has_one :owner
has_one :member
# ...
end
Run Code Online (Sandbox Code Playgroud)
这种方法的好处是用户模型是根,可以访问所有内容.垮台是如果用户是"所有者",那么"admin"和"member"引用将是nil(和其他可能性的笛卡尔 - 管理员但不是所有者或成员等).
我想到的另一个选择是让每种类型的用户都继承自User模型:
class User < ActiveRecord::Base
# ... other code removed for brevity
has_one :profile
end
class Admin < User
# admin fields
end
class Owner < User
# owner fields
end
class Member < User
# member fields
end
Run Code Online (Sandbox Code Playgroud)
问题在于我在表中使用各种类型的nil来污染User对象,其中一种类型不需要来自另一种类型/ etc的值.看起来有点乱,但我不确定.
另一种选择是将每个帐户类型创建为根,但将用户作为子对象,如下所示.
class Admin
has_one :user
# admin fields go here.
end
class Owner
has_one :user
# owner fields go here.
end
class Member
has_one :user
# member fields go here.
end
Run Code Online (Sandbox Code Playgroud)
上面的问题是我不确定如何在用户登录后加载正确的类.我将拥有他们的user_id,我将能够告诉他们是哪个角色(因为角色关联在用户模型),但我不确定如何从用户UP转到根对象.方法?其他?
结论 我有几种不同的方法可以做到这一点,但我不确定正确的"rails"方法是什么.在rails AR中对此进行建模的正确方法是什么?(MySQL后端).如果没有"正确"的方法,那么上述最好的方法(我也对其他想法持开放态度).
谢谢!
我的回答假设给定用户只能是一种类型的用户 - 例如,只有管理员或只有会员.如果是这样,这似乎是ActiveRecord的Polymorphic协会的完美工作.
class User < ActiveRecord::Base
# ...
belongs_to :privilege, :polymorphic => true
end
Run Code Online (Sandbox Code Playgroud)
这种关联为用户提供了一个称为"特权"的访问者(缺少一个更好的术语,并避免命名混淆,这将在以后变得明显).因为它是多态的,所以它可以返回各种类.多态关系需要在相应的表上有两列 - 一个(accessor)_type
和(accessor)_id
.在我的例子中,用户表将获得两个领域:privilege_type
与privilege_id
这ActiveRecord的结合,找到在查找相关条目.
您的管理员,所有者和成员类看起来像这样:
class Admin
has_one :user, :as => :privilege
# admin fields go here.
end
class Owner
has_one :user, :as => :privilege
# owner fields go here.
end
class Member
has_one :user, :as => :privilege
# member fields go here.
end
Run Code Online (Sandbox Code Playgroud)
现在你可以做这样的事情:
u = User.new(:attribute1 => user_val1, ...)
u.privilege = Admin.new(:admin_att1 => :admin_val1, ...)
u.save!
# Saves a new user (#3, for example) and a new
# admin entry (#2 in my pretend world).
u.privilege_type # Returns 'Admin'
u.privilege_id # Returns 2
u.privilege # returns the Admin#2 instance.
# ActiveRecord's SQL behind the scenes:
# SELECT * FROM admin WHERE id=2
u.privilege.is_a? Admin # returns true
u.privilege.is_a? Member # returns false
Admin.find(2).user # returns User#3
# ActiveRecord's SQL behind the scenes:
# SELECT * FROM user WHERE privilege_type='Admin'
# AND privilege_id=2
Run Code Online (Sandbox Code Playgroud)
(accessor)_type
如果您希望它是一组已知值,我建议您将数据库中的字段设为ENUM.一个ENUM,恕我直言,比Rails通常默认的VARCHAR255更好的选择,更容易/更快/更小的索引,但当你有数百万用户时,更难以/耗时更改.此外,正确索引关联:
add_column :privilege_type, "ENUM('Admin','Owner','Member')", :null => false
add_column :privilege_id, :integer, :null => false
add_index :user, [:privilege_type, :privilege_id], :unique => true
add_index :user, :privilege_type
Run Code Online (Sandbox Code Playgroud)
第一个索引允许ActiveRecord快速查找反向关联(例如,查找具有Admin#2权限的用户),第二个索引允许您查找所有管理员或所有成员.
这个RailsCast有点陈旧,但是关于多态关系的一个很好的教程.
最后一个注意事项 - 在你的问题中,你表示管理员,所有者或成员是用户的类型,这是足够合适的,但正如你可能看到的,我必须解释你的用户表将有一个user_type_type
字段.