9

我有一个rails应用程序,它有三种不同类型的用户,我需要它们共享相同的常用配置文件信息。但是,每个不同的用户本身也具有独特的属性。我不确定如何区分不同的领域。Rails Active Record - 如何建模用户/配置文件场景

  • 管理员(网站管理员宽)(/商店的等)
  • 所有者
  • 会员(如合作社的成员)

我使用色器件认证和cancan授权。因此,我有一个用户模型,其中包含可应用于用户的一组角色。这个类看起来这样:

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 

每个用户都拥有包括个人资料:

  • 首先&姓
  • 地址信息(城市/ ST/ZIP /等)
  • 电话

我不想用这个信息污染User模型,所以我把它扔到Profile模型中。这部分相当简单。这会把用户模型弄成这个样子:

class User < ActiveRecord::Base 
    # ... devise stuff omitted for brevity ... 
    # ... cancan stuff omitted for brevity ... 
    has_one :profile 
end 

的附加字段是我对如何模型有一些忐忑不安的心情......

如果用户是管理员,他们将有独特的领域,如:

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

如果用户是所有者,他们将有独特的领域...

  • stripe_api_key:字符串
  • stripe_test_api_key:字符串
  • stripe_account_number:字符串
  • HAS_ONE:商店#AR Refence到管理员和成员没有的另一种模式。

如果用户是一个成员,他们将有一些额外的领域,例如:

  • stripe_account_number:字符串
  • belongs_to的:商店#商店,他们是
  • 成员has_many:note

...

并且Store模型将在成员中包含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 

这种方法的好处是用户模型是根,可以访问所有内容。如果用户是“所有者”,那么“管理员”和“成员”引用将为零(以及其他可能性的管理员 - 管理员而不是所有者或成员等)。

我在想的另一种选择是让从用户模型,这样用户继承的每种类型:

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 

问题的,这是我正在污染User对象有各类零年代在一个类型不需要另一个类型/等的值的表中。看起来很混乱,但我不确定。

另一种选择是将每个帐户类型创建为根,但将用户作为子对象,如下所示。

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 

与上面的是我不知道如何加载,一旦用户登录正确的类中的问题。我有自己的USER_ID,我就可以告诉他们是哪个角色(由于用户模型中的角色关联),但我不确定如何从用户UP转到根对象。方法?其他?

结论 我有一些不同的方法可以做到这一点,但我不知道正确的“轨”的方法是什么。在rails AR中对此进行建模的正确方法是什么? (MySQL后端)。如果没有一个“正确”的方法,上面最好的是什么(我也接受其他想法)。

谢谢!

回答

6

我的回答假定一个给定的用户只能是一种类型的用户 - 例如只有管​​理员或只有会员。如果是这样,这对ActiveRecord's Polymorphic association来说似乎是一个完美的工作。

class User < ActiveRecord::Base 
    # ... 

    belongs_to :privilege, :polymorphic => true 
end 

这种关联给出用户称为“特权”的存取(由于缺乏一个更好的术语和避免命名混乱后来将变得显而易见)。由于它是多态的,它可以返回各种类。多态关系需要在相应的表上有两列 - 一个(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 

现在你可以做这样的事情:

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 

我会建议你做数据库中的ENUM的(accessor)_type场如果你期望它是一组已知的值。 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 

第一索引允许的ActiveRecord迅速找到反向关联(如发现有联系#2特权用户),第二个索引,可以找到所有管理员或所有成员。

这个RailsCast有点过时,但仍然是一个关于多态关系的好教程。

最后一个注释 - 在您的问题中,您表示管理员,所有者或成员是用户的类型,这足够合适,但正如您可能看到的,我必须解释您的用户表将会有一个user_type_type字段。

+0

这就是我的答案“标记为”这工作“,因为当我测试它时,它工作得很好。谢谢你非常深思熟虑和详细的回应Jeffrey。:)然而,经过与Michael Hartl(RailsTutorial.org)咨询了一些MVP可行的产品)的东西,我决定把所有的配置文件数据放入用户模型,以简化与模型的交互。如果MVP证明是成功的是有利可图的,那我稍后可以解决这个问题。 – 2012-04-04 16:26:44

1

我可能不会给你批准建议一个轨道,但..

分离您的个人资料是一个良好的通话。考虑使用Decorator pattern作为角色。您可以拥有AdminUserDecorator,OwnerUserDecorator或MemberOwnerDecorator。您还可以动态增加直接在实例(这是红宝石毕竟)的附加字段,但我认为会变得丑陋和复杂。 (如果你真的想做坏事,使用访客对象给你一个装饰器的实例从用户类的方法。)

此外,为什么把所有者的条纹或付款配置,而不是商店信息的一部分?除非业主可以拥有多个商店,并为每家商店使用相同的付款信息?

UPDATE:我也应该建议使用TDD来清除有效的东西。

+0

呀,支付配置的所有者,因为车主可以有很多的商店。 – 2012-03-22 21:31:50

1

您已经接受了一个答案,但是对于它的价值,我有类似的情况,并选择以“用户模型作为聚合根”的方式。我的用户模型包含所有“个人资料”信息和用户,以使用虚构示例has_one:buyer和has_one:seller。我使用一个简单的tinyint字段作为用户所持角色的位标记,因为用户既可以是买方也可以是卖方(或将来需要其他角色)。如果设置了一个位,则可以假定相应的关联不为零(这对我来说不是问题,因为在使用关联引用之前,我总是检查位标记)。我实际上并不在我的实际下属车型太多独特的领域,但它让事情decluttered当每个下属模型具有额外的关联非常有用的,就像如果卖家HAS_ONE:merchant_account”和买家HAS_ONE:purchase_history,等我的天堂“T去住呢,但是当我做,我会按照这个帖子了我遇到的任何问题。