【问题标题】:Rails 4 problems with updating/deleting fields in a model with nested fields在具有嵌套字段的模型中更新/删除字段的 Rails 4 问题
【发布时间】:2015-01-09 06:53:30
【问题描述】:

在 Railscast 第 196 集 http://railscasts.com/episodes/196-nested-model-form-revised 和 rails 4 https://github.com/dnewkerk/nested-model-form 的重新制作版本之后,我一直致力于在我的应用中引入嵌套表单。

假设我们在 receiptsarticles 之间存在一对多关联。

他们的模型如下所示:

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>

现在让我们开始解决我的问题。第一次创建收据时一切正常 - 我在显示视图中看到收据中的文章数量以及每篇文章中的保修期。

当我通过收据/编辑更新或删除 article_fields 时,事情变得一团糟:

1) 当我编辑收据并想要删除任何文章时(尽管在我的编辑视图中它们在视觉上消失了 - JS 似乎工作),这些字段没有从我的数据库中删除,因此显示视图保持不变和以前一样。

简单示例:

编辑前:我的收据有 6 篇文章

编辑期间:按 3 次“删除文章”按钮,因此收据应该有 3 篇文章

编辑后:收据仍有6篇文章

2) 当我编辑收据并想要添加另一个文章字段时,warranty_expires 的值始终为零 - 如何使其与收据控制器中的更新操作一起使用?我尝试使用与创建操作相同的代码:

@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 值。

您的所有帮助将不胜感激。提前谢谢你。

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-4 associations crud nested-forms


    【解决方案1】:

    我认为你可以在你的文章模型中使用一些回调来解决你的第二个问题,

    开始删除这个,尽量让你的控制器尽可能简单并处理模型中的操作。

     @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
    

    代码未经测试,但它的想法。希望对您有所帮助。

    检查这两个宝石simple_formnested_form 这在编写大型表单时很有帮助,并且它们相互配合得很好。

    【讨论】:

    • 谢谢你:)。上面的解决方案奏效了。所以总的来说,控制器的代码应该尽可能简单,而更复杂的方法应该在模型中实现?以后我一定会记住的:)。
    • 你说得对。一个容易记住的方法是胖模型和瘦控制器。
    【解决方案2】:

    更新:我设法解决了第一个问题。

    第一个解决方案的修复如下:

    删除文章时缺少隐藏字段 :_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>
    

    仍然不知道如何解决第二个问题。

    【讨论】:

      【解决方案3】:

      首先我注意到,您的 reciepts_controller 新操作中有一个循环

      2.times do 
        @receipt.articles.build 
      end
      

      这意味着,文章将只为该收据创建 2 次。

      最好删除循环,以便您可以添加任意数量的文章。 对于第二个问题,添加以下行以编辑控制器的操作

      @receipt.articles.build
      

      我想这会对你有所帮助。

      nested_form 也是管理此类任务的绝佳选择。

       https://github.com/ryanb/nested_form
      

      检查一下。

      【讨论】:

        【解决方案4】:

        这是在receipts.js.coffee 中调用.hide() 的问题。我能想到的最简单的解决方法是将.hide() 替换为.remove()

        【讨论】:

          猜你喜欢
          • 2018-01-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-05-26
          • 1970-01-01
          • 1970-01-01
          • 2021-09-19
          相关资源
          最近更新 更多