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

时间:2015-11-10 08:57:37

标签: ruby-on-rails activerecord models

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

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

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

Genymotion

关联如下(仅针对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

有什么方法吗?

2 个答案:

答案 0 :(得分:3)

我会这样做 - 我已经将身份和其他人之间的关系名称更改为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

编辑 - 而不是为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 =方法用作&#34;虚拟属性&#34;,例如,如果您有一个类似"user[linkedin_identity_id]"的表单字段,如果是LinkedinIdentity的id,则可以通常的方式在控制器中执行@user.update_attributes(params[:user])

答案 1 :(得分: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