Rails 使用 f.fields_for 和 AJAX 接受_nested_attributes_for

Posted

技术标签:

【中文标题】Rails 使用 f.fields_for 和 AJAX 接受_nested_attributes_for【英文标题】:Rails accepts_nested_attributes_for with f.fields_for and AJAX 【发布时间】:2013-12-24 13:11:30 【问题描述】:

我很好奇如何正确使用accepts_nested_attributes_forf.fields_for

views/orders/new.html.erb

<%= form_for @order, html:role: "form" do |f| %>

  <%= f.submit "Don't push...", remote: true %>
  <%= f.text_field :invoice %>
  <%= f.text_field :ordered_on %>
  <%= f.text_field :delivered_on %>

  <table  id='order_form'>
    <h3>Details</h3>
    <tbody>
      <%= render 'order_details/details', f: f %>
    </tbody>
    <%= link_to 'add line', new_order_detail_path(company_id: params[:company_id]), remote: true %>
    <%= link_to 'new box', new_box_path, remote: true %>
  </table>

<% end %>

views/order_details/_details.html.erb

<tr class='row0'>
  <%= f.fields_for :order_details, child_index: child_index do |d| %>
    <td><%= d.collection_select :box_id, @boxes, :id, :uid, ,
                                 name: "box_id", class: 'form-control' %></td>
    <td><%= d.text_field :quantity, class: 'form-control' %></td>
    <td><%= d.text_field :box_price, class: 'form-control' %></td>
    <td><%= d.text_field :cb_price, class: 'form-control' %></td>
    <td><%= d.text_field :mould_fees, class: 'form-control' %></td>
    <td>$$$</td>
  <% end %>
</tr>
<tr class='box0'>
  <td colspan="6">&#8594; <b><%= @b.uid %></b> | length: <%= @b.length %> | width: <%= @b.width %> | height: <%= @b.height %> | weight: <%= @b.weight %></td>
</tr>

controllers/orders_controller.rb(我很确定这是错误的......这里的任何帮助将不胜感激)

def create
  @order = Order.create(params[:order])
  if @order.save
    flash[:success] = "Order #@order.invoice added!"
    redirect_to current_user      
  else
    render 'orders/new'
  end
end

models/order.rb

class Order < ActiveRecord::Base
  attr_accessible ..., :order_details_attributes
  has_many :order_details
  accepts_nested_attributes_for :order_details
end

我能够让局部发挥得很好的唯一方法是如果我实际上将fields_for 称为fields_for Order.new.order_details.build。但这根本不会构建嵌套对象。我需要使用f.fields_for 命名法来构建Order 和OrderDetail。不过,我只能建造一个。这是我的下一个问题。

看看里面怎么有按钮?它将 AJAX 行插入表单。如果你点击add line,我会得到

NameError in Order_details#new
Showing D:/Dropbox/Apps/rails_projects/erbv2/app/views/order_details/new.js.erb where line #3 raised:
undefined local variable or method `f' for #<#<Class:0x5cf0a18>:0x5cbd718>

views/orders/add_detail.js.erb

$('#order_form tr.total').before("<%= j render partial: 'orders/details', locals: f: @f, child_index: @ci %>")

我不知道如何定义f...我查看了Rails AJAX: My partial needs a FormBuilder instance 和其他几个。

关于我应该如何处理这个问题有什么建议吗?使用我在这里的代码...我能够创建一个新订单,并带有关联的 order_details,但 box_id 没有保存,company_id 没有保存。我知道这有点讨厌,但我不知道还能去哪里。

更新

路线:

resources :orders do
  collection  get :add_detail 
end

this is way better than having a separate resource for the details.  I didn't think of this before!

HTML 表单:

<%= form_for @order, company_id: params[:company_id], html:role: "form" do |f| %>
  f. ...
  <%= render partial: 'details', locals:  f: f  %> #first child
  <%= link_to 'add line', add_detail_orders_path(company_id: params[:company_id]), remote: true %> #add subsequent children
<% end %>

订单控制器:

def add_detail
  @order = Order.build
  @boxes = Company.find(params[:company_id]).boxes
  @b = @boxes.first
  @ci = Time.now.to_i
  respond_with(@order, @boxes, @b, @ci)
end

_details 部分

<%= form_for @order do |f| %>
  <%= f.fields_for :order_details, child_index: @ci do |d| %>
    <td><%= d.collection_select :box_id, @boxes, :id, :uid, ,
                                class: 'form-control' %></td>
    <td><%= d.text_field :quantity, class: 'form-control' %></td>
    <td><%= d.text_field :box_price, class: 'form-control' %></td>
    <td><%= d.text_field :cb_price, class: 'form-control' %></td>
    <td><%= d.text_field :mould_fees, class: 'form-control' %></td>
    <td>$$$</td>
  <% end %>
<% end %>

【问题讨论】:

我不完全清楚出了什么问题,但如果可能的话,您可能想查看 Cocoon gem:github.com/nathanvda/cocoon 如果您可以更清楚地知道错误发生的地点和时间,它可以帮助澄清。有很多代码可以筛选=) Cocoon 看起来不错,但我不太确定它对我有什么作用。不过,第一个问题...将我的_details partial AJAXing 到表单中失败。 (我链接到的站点中有损坏的代码,允许部分进入,但在提交表单时不会创建任何内容)。使用f.fields_for 给我&lt;select id="order_order_details_attributes_0_box_id" name="order[order_details_attributes][0][box_id]"&gt; ... &lt;/select&gt;,如果有2,下一个应该是1,然后是2,等等。我有意义吗?哈哈。我整天都在为此绞尽脑汁,我一直在坚持,哈哈! 请在您的控制器中向我提供新操作,您是否在新操作中编写了构建以获取订单详细信息,例如@order.order_details.build 控制器中只有你看到的东西。我应该打什么电话? 【参考方案1】:

有可能

这里有一个非常非常好的教程:http://pikender.in/2013/04/20/child-forms-using-fields_for-through-ajax-rails-way/

我们最近还在我们的一个开发应用程序中实现了这种类型的表单。如果您转到 &http://emailsystem.herokuapp.com,请注册(免费)并单击“新消息”。 “订阅者”部分使用此技术

顺便说一句,我们是手动完成的。 Cocoon 实际上看起来非常好,并且似乎使用与我们相同的原理。还有一个RailsCast,但这仅适用于单个添加(我认为)


f.fields_for

您这样做的方式是使用一系列动态构建您需要的字段的部分。从您的代码看来,您已经具备了基础知识(表单正在运行),因此现在需要构建多个组件来处理 AJAX 请求:

    你需要在控制器上处理AJAX(路由+控制器动作) 您需要将 f.fields_for 放入 partials(以便使用 Ajax 调用它们) 您需要处理模型中的build 功能

使用控制器处理 AJAX

首先,您需要在控制器中处理 Ajax 请求

为此,您需要向路由添加一个新的“端点”。这是我们的:

 resources :messages, :except => [:index, :destroy] do
    collection do
       get :add_subscriber
    end
 end

然后控制器动作转化为:

#app/controllers/messages_controller.rb
#Ajax Add Subscriber
def add_subscriber
    @message = Message.build
    render "add_subscriber", :layout => false
end

将您的 f.fields_for 添加到部分

要处理这个问题,您需要将您的f.fields_for 放入部分中。这是我们表单的代码:

#app/views/resources/_message_subscriber_fields.html.erb
<%= f.fields_for :message_subscribers, :child_index => child_index do |subscriber| %>
    <%= subscriber.collection_select(:subscriber_id, Subscriber.where(:user_id => current_user.id), :id, :name_with_email, include_blank: 'Subscribers') %>
<% end %>

#app/views/messages/add_subscriber.html.erb
<%= form_for @message, :url => messages_path, :authenticity_token => false do |f| %>
        <%= render :partial => "resources/message_subscriber_fields", locals: f: f, child_index: Time.now.to_i %>
<% end %>

#app/views/messages/new.html.erb
<% child_index = Time.now.to_i %>
<div id="subscribers">
    <div class="title">Subscribers</div>
    <%= render :partial => "message_subscriber_fields", locals: f: f, child_index: child_index  %>
</div>

将构建功能扩展到您的模型

为了保持干货,我们只是在模型中创建了一个build 函数,我们可以每次调用它:

   #Build
    def self.build
       message = self.new
       message.message_subscribers.build
       message
    end

Child_Index

你最好的朋友是child_index

如果您要添加多个字段,您将遇到的大问题是增加字段的 [id](这是我们在 Ryan Bates 的教程中发现的缺陷)

我发布的第一个教程解决这个问题的方法是将新字段的child_index 设置为Time.now.to_i。这会设置一个唯一的 id,并且由于新字段的实际 ID 无关紧要,因此您可以使用它添加任意数量的字段


JQuery

    #Add Subscriber
    $ ->
      $(document).on "click", "#add_subscriber", (e) ->
         e.preventDefault();

         #Ajax
         $.ajax
           url: '/messages/add_subscriber'
           success: (data) ->
               el_to_add = $(data).html()
               $('#subscribers').append(el_to_add)
           error: (data) ->
               alert "Sorry, There Was An Error!"

【讨论】:

感谢您的帮助,Rich!很好的解释。我有部分到位,我正在使用rails''内置'ajaxing。所以,我点击一个按钮来添加孩子&lt;%= link_to 'add line', add_detail_orders_path(company_id: params[:company_id], f: f, child_index: Time.now.to_i), remote: true %&gt;。然后我得到undefined method 'fields_for' for "#&lt;ActionView::Helpers::FormBuilder:0x665e170&gt;":String...params = "child_index"=&gt;"1386466882", "company_id"=&gt;"1", "f"=&gt;"#&lt;ActionView::Helpers::FormBuilder:0x665e170&gt;" 好的,您的问题是您将f 变量从视图传递给控制器​​。问题是 f 是一个 FormBuilder 对象,并且只在它被调用时才起作用。通过路径发送它不会保留其状态,这就是您遇到此错误的原因 解决这个问题的方法是在您用来创建新字段的部分中构建一个新表单(如我们上面的#app/views/messages/add_subscriber.html.erb 所示——这会创建一个新的表单对象,它将允许您使用原始部分创建所需的字段(从您创建原始表单时开始)。这将起作用,因为您使用 timestamp 作为child_index,确保每个字段都是唯一的并且顺序更大比上一个 #app/views/messages/add_subscriber.html.erb 部分吗?我习惯他们以_下划线开头 感谢您指出这一点 - 我打算写一个教程,因为我没有看到任何其他类似质量的教程。此外,该教程是针对 Rails 3 的。您希望我这样做并发布链接吗?

以上是关于Rails 使用 f.fields_for 和 AJAX 接受_nested_attributes_for的主要内容,如果未能解决你的问题,请参考以下文章

Rails jQuery 自动完成和动态嵌套字段

Rails视图中的嵌套模型

将自定义javascript添加到nested_form(Rails)

rails wicked gem has_one 关联和嵌套形式

rails使用bootstrap

用 Rails 3 和 Foundation 4 覆盖 a:visited 的正确方法是啥?