Ruby on rails Rails-使用父对象的作用域验证嵌套属性的唯一性

Ruby on rails Rails-使用父对象的作用域验证嵌套属性的唯一性,ruby-on-rails,scope,nested-attributes,validates-uniqueness-of,Ruby On Rails,Scope,Nested Attributes,Validates Uniqueness Of,我对Rails中父项为parent的嵌套属性的作用域唯一性验证有问题 背景 我有一个rails 4应用程序,有3种型号: #app/models/account.rb class Account < ActiveRecord::Base has_many :contacts, dependent: :destroy end #app/models/contact.rb class Contact < ActiveRecord::Base belongs_to :accoun

我对Rails中父项为parent的嵌套属性的作用域唯一性验证有问题

背景

我有一个rails 4应用程序,有3种型号:

#app/models/account.rb
class Account < ActiveRecord::Base
  has_many :contacts, dependent: :destroy
end

#app/models/contact.rb
class Contact < ActiveRecord::Base
  belongs_to :account
  has_many :email_addresses, dependent: :destroy, validate: :true, inverse_of: :contact
  accepts_nested_attributes_for :email_addresses,allow_destroy: true
  validates :email_addresses, presence: true
end

#app/models/email_address.rb
class EmailAddress  < ActiveRecord::Base
  belongs_to :contact, inverse_of: :email_addresses

  validates :label, presence: true
  validates :contact, presence: true
  validates :email, uniqueness: true, presence: true
  validates_email_format_of :email
end
#app/models/account.rb
类帐户
问题

我想创建一个范围,,以确保属性:型号EmailAddress的电子邮件在帐户级别是唯一的(帐户是联系人的父级,而联系人本身就是EmailAddress的父级)

正如在会议上建议的那样,我尝试:

 class EmailAddress  < ActiveRecord::Base
  belongs_to :contact, inverse_of: :email_addresses

  validates :label, presence: true
  validates :contact, presence: true
  validates :email, presence: true, uniqueness: { scope: :account, 
                    message: "This contact email is already taken" }
  validates_email_format_of :email
 end
class EmailAddress
这会引发错误“列email_addresses.account不存在” 我该怎么办


谢谢你的帮助

我将提供一个可能的解决方案。未测试,但它应该可以使用自定义验证和额外关联。

在您的
帐户中
型号:

has_many :email_addresses, through: :contacts
validate :uniqueness_of_mail

private
def uniqueness_of_mail
    account = contact.account
    if account.email_addresses.map(&:email).includes? email
        errors.add(email, 'Contact already has this email address')
        false
    else
        true
    end
end
在您的
电子邮件地址中
型号:

has_many :email_addresses, through: :contacts
validate :uniqueness_of_mail

private
def uniqueness_of_mail
    account = contact.account
    if account.email_addresses.map(&:email).includes? email
        errors.add(email, 'Contact already has this email address')
        false
    else
        true
    end
end

下文介绍了性能方面更好的选择。它经过测试,工作正常

为什么?

当大量电子邮件处于危险中时,映射电子邮件可能会消耗大量资源,因此最好直接使用数据库执行范围

怎么做?

在EmailAddress模型中兑现帐户\u id并执行验证前方法

1) 创建迁移:

change_table :email_addresses do |t|
  t.references :account, index: true
end
add_index :email_addresses, [:account_id, :email], unique: true
2) 迁移

3) 更新电子邮件地址模型

#app/models/email_address.rb

class EmailAddress < ActiveRecord::Base
  belongs_to :contact, inverse_of: :email_addresses
  belongs_to :account

  validates :label, presence: true
  validates :contact, presence: true
  validates_email_format_of :email
  validates_uniqueness_of :email, allow_blank: false, scope: :account

  before_validation do
    self.account = contact.account if contact
  end

end
#app/models/email_address.rb
类EmailAddress
可能是这项工作。将其添加到您的
电子邮件地址
模型
验证:电子邮件,:唯一性=>{:scope=>:contact\u id}
事实上,我希望为帐户而不是联系人设置一个范围。您应该将
所属的\u账户
添加到
电子邮件地址
模型以实现这一点。这将使我的数据库架构复杂化。有没有更好的方法在不改变db模式的情况下实现这个范围?我建议您在account.rb中添加一个嵌套的关联。更具体地说,
有很多:电子邮件地址,通过::contacts
开始。感谢您的回答。它很好用。只是一个问题:如果在电子邮件地址中我们没有添加“属于”\u to:account,是否会出现问题?我认为您不能有一个
属于”\u to:account
,因为它是派生关联。但是您可以执行
def account\n contact.account\n end
来拥有
@emailaddress.account
。否则,它就是
@emailaddress.contact.account
现在的状态。
在account.email\u addresses.map(&:email)
在我最新版本的ruby/rails上失败。尝试
account.email\u addresses.map(&:email)。包括?改为发电子邮件。这确实是一个很好的解决方案。然而,我的解决方案的地址范围非常小,所以这里唯一的实际区别是索引。不过,这是一个更好的解决方案。向上投票。谢谢:)Ruby Racer