是否可以在 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