Rails Active Record - 如何为用户/配置文件场景建模

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)

每个用户都有一个配置文件包括:

  • 名和姓
  • 地址信息(city/st/zip/etc)
  • 电话

我不想用这个信息污染用户模型,所以我把它扔进了一个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)

额外的领域是我对如何模型有一些不安的感觉......

如果用户是管理员,他们将拥有唯一的字段,例如:

  • admin_field_a:字符串
  • admin_field_b:字符串
  • 等等

如果用户是所有者,他们将拥有唯一的字段......

  • stripe_api_key:字符串
  • stripe_test_api_key:字符串
  • stripe_account_number:字符串
  • has_one:store#AR Refence到Admin和Member没有的另一个模型.

如果用户是会员,他们将有一些额外的字段:

  • stripe_account_number:字符串
  • belongs_to:store#他们所属的商店
  • has_many:注意

...

并且商店模型将在成员上包含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后端).如果没有"正确"的方法,那么上述最好的方法(我也对其他想法持开放态度).

谢谢!

Jef*_*man 6

我的回答假设给定用户只能是一种类型的用户 - 例如,只有管理员或只有会员.如果是这样,这似乎是ActiveRecord的Polymorphic协会的完美工作.

class User < ActiveRecord::Base
  # ...

  belongs_to :privilege, :polymorphic => true
end
Run Code Online (Sandbox Code Playgroud)

这种关联为用户提供了一个称为"特权"的访问者(缺少一个更好的术语,并避免命名混淆,这将在以后变得明显).因为它是多态的,所以它可以返回各种类.多态关系需要在相应的表上有两列 - 一个(accessor)_type(accessor)_id.在我的例子中,用户表将获得两个领域:privilege_typeprivilege_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字段.