0

是否可以在 has_many through: 关联中使用 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

这是列表视图_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
4

0 回答 0