4

我在 Rails 中对具有父级父级的嵌套属性进行范围唯一性验证时遇到问题。

背景

我有一个带有 3 个模型的 rails 4 应用程序:

#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

问题

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

正如http://guides.rubyonrails.org/active_record_validations.html所建议的,我尝试了:

 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

这会引发错误“列 email_addresses.account 不存在”我该怎么办?

谢谢你的帮助!

4

2 回答 2

12

下面描述了性能方面的更好选择。它经过测试并且工作正常。

为什么?

当大量电子邮件受到威胁时,映射电子邮件会消耗大量资源,因此最好直接使用数据库执行范围。

如何?

在 EmailAddress 模型中兑现 account_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) 更新 EmailAddress 模型

#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
于 2014-06-27T07:27:13.203 回答
5

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

在您的Account模型中:

has_many :email_addresses, through: :contacts

在您的EmailAddress模型中:

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
于 2014-06-25T19:42:19.693 回答