1

我在Rails中使用父项父项的嵌套属性进行范围唯一性验证有问题。Rails-验证嵌套属性父项的范围父项的唯一性

背景

我有一个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 

问题

我希望做一个范围,以确保属性 :模型EmailAddress 的电子邮件在帐户级别是唯一的 (帐户是联系人的父母,它本身是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不存在” 我应该怎么办?

感谢您的帮助!

+0

可能是这样work.Add给你的'EmailAddress'模型'验证:电子邮件,:唯一性=> {:scope =>:contact_id}' – Pavan

+0

实际上,我希望为该帐户有一个范围,而不是为联系人 – Nobigie

+0

您应该添加'belongs_to:account'到'EmailAddress'模型来做到这一点。 – Pavan

回答

8

在性能方面更好的选择如下所述。它经过测试,工作得很好。

为什么?

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

如何?

在EmailAddress模型中兑现account_id并执行before验证方法。

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 
+0

这确实是一个很好的解决方案。我的解决方案的地址范围很小,所以这里唯一的实际区别就是索引。尽管如此,仍然是更好的解决方Upvoting。 –

+0

谢谢:)红宝石赛车 – Nobigie

3

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

在你Account型号:

has_many :email_addresses, through: :contacts 

在你EmailAddress型号:

validate :uniqueness_of_mail 

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

感谢您的回答。它工作得很好。只是一个问题:如果在EmailAddress中我们不添加belongs_to:帐户,是否存在问题? – Nobigie

+0

我不认为你可以拥有'belongs_to:account',因为它是派生关联。但是你可以通过'def account \ n contact.account \ n end'来拥有'@ emailaddress.account'。否则,它现在就是'@ emailaddress.contact.account'。 –