具有唯一约束的 ActiveRecord 多态关联

use*_*003 1 activerecord ruby-on-rails models

我有一个网站,允许用户通过多种服务(LinkedIn、电子邮件、Twitter 等)登录。

我设置了以下结构来建模 aUser及其多重身份。基本上,一个用户可以有多个身份,但只能是给定类型的一个(例如不能有 2 个 Twitter 身份)。

我决定将其设置为多态关系,如下所示。基本上有一个中间表identities将一个条目映射User到多个*_identity表。

图式

关联如下(仅显示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
Run Code Online (Sandbox Code Playgroud)

我遇到的问题与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
Run Code Online (Sandbox Code Playgroud)

有什么办法解决这个问题吗?

Max*_*ams 5

我会这样做 - 我已将 Identity 和其他人之间的关系名称更改为external_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
Run Code Online (Sandbox Code Playgroud)

编辑- 您始终可以只拥有一个 getter 和 setter 方法,而不是与 linkedin_identity 建立关联。

#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
Run Code Online (Sandbox Code Playgroud)

编辑2 - 重构上述内容以使其更加表单友好:您可以使用 linkedin_identity_id= 方法作为“虚拟属性”,例如,如果您有一个类似于 的表单字段"user[linkedin_identity_id]",带有 LinkedinIdentity 的 id,那么您可以@user.update_attributes(params[:user])在控制器中执行以下操作通常的方式。