Rails 具有嵌套字段的模型中 updating/deleting 字段的 4 个问题
Rails 4 problems with updating/deleting fields in a model with nested fields
在 Railscast 第 196 集之后,我一直致力于在我的应用程序中引入嵌套表单 http://railscasts.com/episodes/196-nested-model-form-revised and the remastered version for rails 4 https://github.com/dnewkerk/nested-model-form。
假设我们在 receipts 和 articles 之间有一个一对多的关联。
他们的模型是这样的:
receipt.rb:
class Receipt < ActiveRecord::Base
has_many :articles, dependent: :destroy
accepts_nested_attributes_for :articles, allow_destroy: true, reject_if: :all_blank
belongs_to :shop
belongs_to :user
def display_name
self.name
end
end
article.rb:
class Article < ActiveRecord::Base
belongs_to :receipt
def name_with_brand
"#{name} #{brand}"
end
end
下面是 receipts_controller.rb 的样子:
class ReceiptsController < ApplicationController
before_action :set_shop, only: [:show, :edit, :update, :destroy]
respond_to :html, :xml, :json
def index
@receipts = current_user.receipts
respond_with(@receipts)
end
def show
respond_with(@receipt)
end
def new
@receipt = Receipt.new
2.times do
@receipt.articles.build
end
respond_with(@receipt)
end
def edit
end
def create
@receipt = Receipt.new(receipt_params)
user_id = current_user.id
@receipt.articles.each do |article|
warranty_time = article.warranty_time
article.warranty_expires = @receipt.shopping_date.advance(months: warranty_time)
end
@receipt.user_id = user_id
@receipt.save
respond_with(@receipt)
end
def update
if @receipt.update(receipt_params)
redirect_to @receipt, notice: "Successfully updated receipt."
else
render :edit
end
end
def destroy
@receipt.destroy
respond_with(@receipt)
end
private
def set_shop
@receipt = Receipt.find(params[:id])
end
def receipt_params
params.require(:receipt).permit(:name, :shopping_date, :shop_id, :file,
articles_attributes: [:id, :name, :brand, :warranty_time, :warranty_expires,
:receipt_id, :_destroy])
end
end
这是我的 receipts.js.coffee 的样子:
jQuery ->
$('#receipt_shopping_date').datepicker(dateFormat: 'yy-mm-dd')
$.datepicker.setDefaults($.datepicker.regional['PL']);
$('form').on 'click', '.remove_fields', (event) ->
$(this).prev('input[type=hidden]').val('1')
$(this).closest('fieldset').hide()
event.preventDefault()
$('form').on 'click', '.add_fields', (event) ->
time = new Date().getTime()
regexp = new RegExp($(this).data('id'), 'g')
$(this).before($(this).data('fields').replace(regexp, time))
event.preventDefault()
$(document).ready(jQuery)
$(document).on('page:load', jQuery)
最后,这是我对添加新收据和向其添加文章的看法:
(other fields...)
<div class="large-12 columns">
<p>Add articles on the receipt:</p>
</div>
<div class="field">
<div class="large-12 columns">
<%= f.fields_for :articles do |builder| %>
<div class="article_fields">
<%= render "article_fields", :f => builder %>
</div>
<% end %>
<%= link_to_add_fields "Add another article", f, :articles %>
</div>
</div>
<div class="actions">
<div class="large-12 columns">
<%= f.submit "Sumbit Receipt" %>
</div>
</div>
<% end %>
如您所见,我正在使用 link_to_add_fields 辅助方法,如下所示:
def link_to_add_fields(name, f, association)
new_object = f.object.send(association).klass.new
id = new_object.object_id
fields = f.fields_for(association, new_object, child_index: id) do |builder|
render(association.to_s.singularize + "_fields", f: builder)
end
link_to(name, '#', class: "add_fields small button", data: {id: id, fields: fields.gsub("\n", "")})
end
最后,如您所见,我正在生成一个名为 _article_fields.html.erb 的部分,它的外观如下:
<fieldset style="width:1400px">
<legend>new article</legend>
<div class="large-2 columns">
<%= f.text_field :name%>
</div>
<div class="large-2 columns">
<%= f.text_field :brand%>
</div>
<div class="large-2 columns">
<%= f.text_field :warranty_time, class: "warranty" %>
</div>
<div class="large-12 columns">
<%= link_to "delete article", '#', class: "remove_fields button small alert" %>
</div>
</fieldset>
现在让我们开始解决我的问题。第一次创建收据时,一切都很好 - 我在我的显示视图中看到了收据中的文章数量,并且在每篇文章中看到了 warranty_expires。
当我通过 receipts/edit 更新或删除 article_fields 时,事情变得一团糟:
1) 当我编辑收据并想删除任何文章时(尽管在我的编辑视图中它们在视觉上消失了 - JS 似乎有效),这些字段没有从我的数据库中删除,因此显示视图和以前完全一样。
简单示例:
编辑前:我的收据有6篇文章
在编辑过程中:按了 3 次 'delete article' 按钮,所以收据应该有 3 篇文章
编辑后:收据还有6篇文章
2) 当我编辑收据并想添加另一个文章字段时,值 warranty_expires 始终为 nil - 如何使其与收据控制器中的更新操作一起使用?我尝试使用与创建操作中相同的代码:
@receipt.articles.each do |article|
warranty_time = article.warranty_time
article.warranty_expires = @receipt.shopping_date.advance(months: warranty_time)
end
但这行不通。知道为什么吗?
简单示例:
一个收据已经有2篇文章了。当我添加第三个时,我得到以下结果:
3 篇文章 - 所有文章都有名称和 warranty_time 字段,但其中只有 2 篇文章有 warranty_expires 值。
非常感谢您的所有帮助。提前谢谢你。
更新:我设法解决了第一个问题。
第一个解决方案的修复如下:
删除文章时缺少隐藏字段 :_destroy。
所以我需要更改以下代码:
<div class="large-12 columns">
<%= link_to "delete article", '#', class: "remove_fields button small alert" %>
</div>
至:
<div class="large-12 columns">
<%= f.hidden_field :_destroy %>
<%= link_to "delete article", '#', class: "remove_fields button small alert" %>
</div>
仍然不知道如何解决第二个问题。
首先我注意到,您的 reciepts_controller 新操作中有一个循环
2.times do
@receipt.articles.build
end
这意味着,文章只会为该收据创建 2 次。
最好去掉循环,这样您就可以添加任意多的文章。
对于第二个问题,添加以下行以编辑控制器的操作
@receipt.articles.build
我想这对你有帮助。
另外 nested_form 非常适合 gem 来管理此类任务。
https://github.com/ryanb/nested_form
检查一下。
我认为您可以在 Article 模型中使用一些回调来解决您的第二个问题,
开始删除它,尝试让您的控制器尽可能简单并处理模型中的操作。
@receipt.articles.each do |article|
warranty_time = article.warranty_time
article.warranty_expires = @receipt.shopping_date.advance(months: warranty_time)
end
在您的文章模型中添加一些回调
class Article < ActiveRecord::Base
belongs_to :receipt
def name_with_brand
"#{name} #{brand}"
end
before_update :set_warranty_expires
before_create :set_warranty_expires
def set_warranty_expires
self.warranty_expires = self.receipt.shopping_date.advance(months: self.warranty_time)
end
end
代码未经测试,但它的想法。希望对你有帮助。
检查这两个 gem simple_form and nested_form 这在编写大型表单时很有帮助,并且它们可以很好地相互配合。
这是在 receipts.js.coffee
中调用 .hide()
的问题。我能想到的解决这个问题的最简单方法是简单地将 .hide()
替换为 .remove()
在 Railscast 第 196 集之后,我一直致力于在我的应用程序中引入嵌套表单 http://railscasts.com/episodes/196-nested-model-form-revised and the remastered version for rails 4 https://github.com/dnewkerk/nested-model-form。
假设我们在 receipts 和 articles 之间有一个一对多的关联。
他们的模型是这样的:
receipt.rb:
class Receipt < ActiveRecord::Base
has_many :articles, dependent: :destroy
accepts_nested_attributes_for :articles, allow_destroy: true, reject_if: :all_blank
belongs_to :shop
belongs_to :user
def display_name
self.name
end
end
article.rb:
class Article < ActiveRecord::Base
belongs_to :receipt
def name_with_brand
"#{name} #{brand}"
end
end
下面是 receipts_controller.rb 的样子:
class ReceiptsController < ApplicationController
before_action :set_shop, only: [:show, :edit, :update, :destroy]
respond_to :html, :xml, :json
def index
@receipts = current_user.receipts
respond_with(@receipts)
end
def show
respond_with(@receipt)
end
def new
@receipt = Receipt.new
2.times do
@receipt.articles.build
end
respond_with(@receipt)
end
def edit
end
def create
@receipt = Receipt.new(receipt_params)
user_id = current_user.id
@receipt.articles.each do |article|
warranty_time = article.warranty_time
article.warranty_expires = @receipt.shopping_date.advance(months: warranty_time)
end
@receipt.user_id = user_id
@receipt.save
respond_with(@receipt)
end
def update
if @receipt.update(receipt_params)
redirect_to @receipt, notice: "Successfully updated receipt."
else
render :edit
end
end
def destroy
@receipt.destroy
respond_with(@receipt)
end
private
def set_shop
@receipt = Receipt.find(params[:id])
end
def receipt_params
params.require(:receipt).permit(:name, :shopping_date, :shop_id, :file,
articles_attributes: [:id, :name, :brand, :warranty_time, :warranty_expires,
:receipt_id, :_destroy])
end
end
这是我的 receipts.js.coffee 的样子:
jQuery ->
$('#receipt_shopping_date').datepicker(dateFormat: 'yy-mm-dd')
$.datepicker.setDefaults($.datepicker.regional['PL']);
$('form').on 'click', '.remove_fields', (event) ->
$(this).prev('input[type=hidden]').val('1')
$(this).closest('fieldset').hide()
event.preventDefault()
$('form').on 'click', '.add_fields', (event) ->
time = new Date().getTime()
regexp = new RegExp($(this).data('id'), 'g')
$(this).before($(this).data('fields').replace(regexp, time))
event.preventDefault()
$(document).ready(jQuery)
$(document).on('page:load', jQuery)
最后,这是我对添加新收据和向其添加文章的看法:
(other fields...)
<div class="large-12 columns">
<p>Add articles on the receipt:</p>
</div>
<div class="field">
<div class="large-12 columns">
<%= f.fields_for :articles do |builder| %>
<div class="article_fields">
<%= render "article_fields", :f => builder %>
</div>
<% end %>
<%= link_to_add_fields "Add another article", f, :articles %>
</div>
</div>
<div class="actions">
<div class="large-12 columns">
<%= f.submit "Sumbit Receipt" %>
</div>
</div>
<% end %>
如您所见,我正在使用 link_to_add_fields 辅助方法,如下所示:
def link_to_add_fields(name, f, association)
new_object = f.object.send(association).klass.new
id = new_object.object_id
fields = f.fields_for(association, new_object, child_index: id) do |builder|
render(association.to_s.singularize + "_fields", f: builder)
end
link_to(name, '#', class: "add_fields small button", data: {id: id, fields: fields.gsub("\n", "")})
end
最后,如您所见,我正在生成一个名为 _article_fields.html.erb 的部分,它的外观如下:
<fieldset style="width:1400px">
<legend>new article</legend>
<div class="large-2 columns">
<%= f.text_field :name%>
</div>
<div class="large-2 columns">
<%= f.text_field :brand%>
</div>
<div class="large-2 columns">
<%= f.text_field :warranty_time, class: "warranty" %>
</div>
<div class="large-12 columns">
<%= link_to "delete article", '#', class: "remove_fields button small alert" %>
</div>
</fieldset>
现在让我们开始解决我的问题。第一次创建收据时,一切都很好 - 我在我的显示视图中看到了收据中的文章数量,并且在每篇文章中看到了 warranty_expires。
当我通过 receipts/edit 更新或删除 article_fields 时,事情变得一团糟:
1) 当我编辑收据并想删除任何文章时(尽管在我的编辑视图中它们在视觉上消失了 - JS 似乎有效),这些字段没有从我的数据库中删除,因此显示视图和以前完全一样。
简单示例:
编辑前:我的收据有6篇文章
在编辑过程中:按了 3 次 'delete article' 按钮,所以收据应该有 3 篇文章
编辑后:收据还有6篇文章
2) 当我编辑收据并想添加另一个文章字段时,值 warranty_expires 始终为 nil - 如何使其与收据控制器中的更新操作一起使用?我尝试使用与创建操作中相同的代码:
@receipt.articles.each do |article|
warranty_time = article.warranty_time
article.warranty_expires = @receipt.shopping_date.advance(months: warranty_time)
end
但这行不通。知道为什么吗?
简单示例:
一个收据已经有2篇文章了。当我添加第三个时,我得到以下结果:
3 篇文章 - 所有文章都有名称和 warranty_time 字段,但其中只有 2 篇文章有 warranty_expires 值。
非常感谢您的所有帮助。提前谢谢你。
更新:我设法解决了第一个问题。
第一个解决方案的修复如下:
删除文章时缺少隐藏字段 :_destroy。
所以我需要更改以下代码:
<div class="large-12 columns">
<%= link_to "delete article", '#', class: "remove_fields button small alert" %>
</div>
至:
<div class="large-12 columns">
<%= f.hidden_field :_destroy %>
<%= link_to "delete article", '#', class: "remove_fields button small alert" %>
</div>
仍然不知道如何解决第二个问题。
首先我注意到,您的 reciepts_controller 新操作中有一个循环
2.times do
@receipt.articles.build
end
这意味着,文章只会为该收据创建 2 次。
最好去掉循环,这样您就可以添加任意多的文章。 对于第二个问题,添加以下行以编辑控制器的操作
@receipt.articles.build
我想这对你有帮助。
另外 nested_form 非常适合 gem 来管理此类任务。
https://github.com/ryanb/nested_form
检查一下。
我认为您可以在 Article 模型中使用一些回调来解决您的第二个问题,
开始删除它,尝试让您的控制器尽可能简单并处理模型中的操作。
@receipt.articles.each do |article|
warranty_time = article.warranty_time
article.warranty_expires = @receipt.shopping_date.advance(months: warranty_time)
end
在您的文章模型中添加一些回调
class Article < ActiveRecord::Base
belongs_to :receipt
def name_with_brand
"#{name} #{brand}"
end
before_update :set_warranty_expires
before_create :set_warranty_expires
def set_warranty_expires
self.warranty_expires = self.receipt.shopping_date.advance(months: self.warranty_time)
end
end
代码未经测试,但它的想法。希望对你有帮助。
检查这两个 gem simple_form and nested_form 这在编写大型表单时很有帮助,并且它们可以很好地相互配合。
这是在 receipts.js.coffee
中调用 .hide()
的问题。我能想到的解决这个问题的最简单方法是简单地将 .hide()
替换为 .remove()