使用has_many:通过关联和accepted_nested_attributes_for进行唯一性验证

时间:2015-05-20 06:04:05

标签: ruby-on-rails has-many-through validates-uniqueness-of

是否可以在has_many through:association中使用accepts_nested_attributes_for,仅在不重复时才创建子项。否则只在连接表中创建关系?

我有一个可以有很多电子邮件的列表。反过来也是可能的。请参阅以下模型。

# models/list.rb
class List < ActiveRecord::Base
  has_many :emails_lists
  has_many :emails, through: :emails_lists

  accepts_nested_attributes_for :emails, allow_destroy: true
end

# models/email.rb
class Email < ActiveRecord::Base
  has_many :emails_lists
  has_many :lists, through: :emails_lists

  validates :name, uniqueness: true

  accepts_nested_attributes_for :lists, allow_destroy: true
end

# models/emails_list.rb
class EmailsList < ActiveRecord::Base
  belongs_to :email
  belongs_to :list

  validates :email_id, uniqueness: {scope: :list_id}
end

这是List视图_form。

# views/lists/_form.html.haml
= nested_form_for @list do |f|
  - if @list.errors.any?
    #error_explanation
      %h2= "#{pluralize(@list.errors.count, "error")} prohibited this list from being saved:"
      %ul
        - @list.errors.full_messages.each do |msg|
          %li= msg

  .field
    = f.label :name
    = f.text_field :name
  .field
    = f.label :description
    = f.text_area :description
  %fieldset
    %legend Email
    = f.fields_for :emails do |email|
      .field.email
        = email.label :name
        %p
          = email.text_field :name
          = email.link_to_remove "Remove this email"
    %p
      %i.fa.fa-plus
        = f.link_to_add "Add an email", :emails
  .actions
    = f.submit 'Save'

以下是有关'更新'和list_params方法的操作。

# PATCH/PUT /lists/1
# PATCH/PUT /lists/1.json
def update
  # binding.pry
  respond_to do |format|
    if @list.update(list_params)
      format.html { redirect_to @list, notice: 'List was successfully updated.' }
      format.json { render :show, status: :ok, location: @list }
    else
      format.html { render :edit }
      format.json { render json: @list.errors, status: :unprocessable_entity }
    end
  end
end

# Never trust parameters from the scary internet, only allow the white list through.
def list_params
  params.require(:list).permit(:name, :description, emails_attributes: [:id, :name, :_destroy])
end

在视图表单中,我可以将新电子邮件添加到列表中。问题是当我尝试添加重复的电子邮件时。验证和保存被拒绝。

这里我通过Rails控制台插入副本。

2.1.6 :001 > l = List.create
   (0.1ms)  begin transaction
  SQL (0.5ms)  INSERT INTO "lists" ("created_at", "updated_at") VALUES (?, ?)  [["created_at", "2015-05-20 05:52:38.567388"], ["updated_at", "2015-05-20 05:52:38.567388"]]
   (1.1ms)  commit transaction
 => #<List id: 7, name: nil, description: nil, created_at: "2015-05-20 05:52:38", updated_at: "2015-05-20 05:52:38"> 
2.1.6 :002 > l.emails.build({name: 'foo@test.com'})
 => #<Email id: nil, name: "foo@test.com", company_id: nil, created_at: nil, updated_at: nil> 
2.1.6 :003 > l.save
   (0.1ms)  begin transaction
  Email Exists (0.1ms)  SELECT  1 AS one FROM "emails" WHERE "emails"."name" = 'foo@test.com' LIMIT 1
  SQL (0.3ms)  INSERT INTO "emails" ("name", "created_at", "updated_at") VALUES (?, ?, ?)  [["name", "foo@test.com"], ["created_at", "2015-05-20 05:54:59.228313"], ["updated_at", "2015-05-20 05:54:59.228313"]]
  EmailsList Exists (0.1ms)  SELECT  1 AS one FROM "emails_lists" WHERE ("emails_lists"."email_id" = 18 AND "emails_lists"."list_id" = 7) LIMIT 1
  SQL (0.1ms)  INSERT INTO "emails_lists" ("list_id", "email_id") VALUES (?, ?)  [["list_id", 7], ["email_id", 18]]
   (1.3ms)  commit transaction
 => true 
2.1.6 :004 > l.emails.build({name: 'foo@test.com'})
 => #<Email id: nil, name: "foo@test.com", company_id: nil, created_at: nil, updated_at: nil> 
2.1.6 :005 > l.save
   (0.1ms)  begin transaction
  Email Exists (0.2ms)  SELECT  1 AS one FROM "emails" WHERE "emails"."name" = 'foo@test.com' LIMIT 1
   (0.1ms)  rollback transaction
 => false 

错误:

  

已经采取了电子邮件名称

我的目标是:

  • 如果“电子邮件”中尚未显示电子邮件名称,请在中创建电子邮件 '电子邮件'表。然后在中创建电子邮件和列表之间的关系 'emails_lists'。
  • 如果电子邮件名称是重复的,请找到重复的和 仅创建电子邮件和列表之间的关系 'emails_lists'。

修改

我发现这篇帖子描述了一个类似的问题: rails validate nested attributes

哈克

我找到了一个自定义黑客来解决我的问题。我认为该解决方案适用于其他情况。我不认为这是最好的做法,但它似乎适用于添加/删除案例。

def emails_attributes=(hash)
  hash.each do |sequence,email_values|
    if !email_values.include?(:id) && email = Email.find_by_name(email_values[:name])
      EmailsList.create(email_id: email.id, list_id: self.id)
      email_values['id'] = email.id
    end
  end
  super
end

0 个答案:

没有答案