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'。 –