2015-11-10 109 views
1

我有一個網站,允許用戶通過多種服務(LinkedIn,電子郵件,Twitter等)登錄。ActiveRecord多態關聯與唯一約束

我有以下結構設置爲模型User及其多個身份。用戶基本上可以有多個身份,但只有一個給定類型(例如不能有2個Twitter身份)。

我決定將它設置爲多態關係,如下圖所示。基本上有一箇中間表identities,它將User條目映射到多個*_identity表。

schema

的關聯如下(僅用於LinkedInIdentity示出,但可推知)

# /app/models/user.rb 
class User < ActiveRecord::Base 
    has_many :identities 
    has_one :linkedin_identity, through: :identity, source: :identity, source_type: "LinkedinIdentity" 

    ... 
end 


# /app/models/identity 
class Identity < ActiveRecord::Base 
    belongs_to :user 
    belongs_to :identity, polymorphic: true 

    ... 
end 


# /app/models/linkedin_identity.rb 
class LinkedinIdentity < ActiveRecord::Base 
    has_one :identity, as: :identity 
    has_one :user, through: :identity 

    ... 
end 

我運行到的問題是與User模型。由於它可以有多個身份,我使用has_many :identities。但是,對於給定的身份類型(例如LinkedIn),我使用了has_one :linkedin_identity ...

問題是has_one聲明是through: :identity,並且沒有稱爲:identity的單一關聯。這裏只有一個複數:identities

> User.first.linkedin_identity 
ActiveRecord::HasManyThroughAssociationNotFoundError: Could not find the association :identity in model User 

什麼辦法解決此問題?

回答

3

我會這麼做 - 我已將Identity和其他人之間的關係名稱更改爲​​,因爲identity.identity只是令人困惑,尤其是當您沒有獲取身份記錄時。我還會對Identity進行唯一性驗證,這將阻止爲任何用戶創建同一類型的第二個身份。

class User < ActiveRecord::Base 
    has_many :identities 
    has_one :linkedin_identity, through: :identity, source: :identity, source_type: "LinkedinIdentity" 
end 


# /app/models/identity 
class Identity < ActiveRecord::Base 
    #fields: user_id, external_identity_id 
    belongs_to :user 
    belongs_to :external_identity, polymorphic: true 
    validates_uniqueness_of :external_identity_type, :scope => :user_id 
    ... 
end 


# /app/models/linkedin_identity.rb 
class LinkedinIdentity < ActiveRecord::Base 
    # Force the table name to be singular 
    self.table_name = "linkedin_identity" 

    has_one :identity 
    has_one :user, through: :identity 

    ... 
end 

編輯 - 而不是作出linkedin_identity的關聯,你總是可以只是有一個getter和setter方法。

#User 
def linkedin_identity 
    (identity = self.identities.where(external_identity_type: "LinkedinIdentity").includes(:external_identity)) && identity.external_identity 
end  

def linkedin_identity_id 
    (li = self.linkedin_identity) && li.id 
end  

def linkedin_identity=(linkedin_identity) 
    self.identities.build(external_identity: linkedin_identity) 
end 

def linkedin_identity_id=(li_id) 
    self.identities.build(external_identity_id: li_id) 
end 

EDIT2 - 重構以上更加形式友好:可以使用linkedin_identity_id =方法作爲「虛擬屬性」,例如,如果你有一個形式字段像"user[linkedin_identity_id]",具有LinkedinIdentity的id,然後您可以按照通常的方式在控制器中執行@user.update_attributes(params[:user])

+1

你爲什麼要明確設置'linkedin_identity'的表名?我問,因爲離開它作爲'linkedin_identities'是有道理的,因爲它對我來說。 –

+0

以「identity.identity」命名的混淆本質的好處。我喜歡「身份」表上的唯一性約束,以防止給定的'user_id'和'_type'組合創建多個記錄。但是,原來的問題仍然存在 - 我不能稱之爲用戶。linkedin_identity「,因爲該關聯是通過」:identity「(單數),不存在的。謝謝! – user2490003

+1

如果你改變'through'選項是'通過:標識符'嗎?> –

1

這裏有一個想法,在這裏奇妙地工作,例如案件。 (我的情況有點不同,因爲所有標識符都在同一個表中,同一基類型的子類)。

class EmailIdentity < ActiveRecord::Base 
    def self.unique_for_user 
    false 
    end 

    def self.to_relation 
    'emails' 
    end 
end 

class LinkedinIdentity < ActiveRecord::Base 
    def self.unique_for_user 
    true 
    end 

    def self.to_relation 
    'linkedin' 
    end 
end 

class User < ActiveRecord::Base 
    has_many :identities do 
     [LinkedinIdentity EmailIdentity].each do |klass| 
     define_method klass.to_relation do 
      res = proxy_association.select{ |identity| identity.is_a? klass } 
      res = res.first if klass.unique_for_user 
      res 
     end 
     end 
    end 
end 

然後,您可以

@user.identities.emails 
@user.identities.linkedin 
+0

有趣!我希望有一個更原生的Rails方式來解決這個問題,但從你的解決方案看,沒有:(但我喜歡這種方法,所以如果沒有本地方式彈出,我可能會使用它,謝謝! – user2490003

+0

它實際上是本地的因爲ActiveRecord添加了關係擴展,請檢查文檔,爲了獲得最佳效率,您可以爲關係添加更多接口方法! – muichkine