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去住呢,但是當我做,我會按照這個帖子了我遇到的任何問題。