Rails的accept_nested_attributes_for与f.fields_for和AJAX一起使用

时间:2013-12-07 01:42:20

标签: jquery ruby-on-rails ruby ajax ruby-on-rails-3

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

视图/命令/ 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 %>

视图/ 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

模型/ 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>

视图/命令/ 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 partial

<%= 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 %>

1 个答案:

答案 0 :(得分:20)

可能

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

我们最近还在其中一个开发应用上实现了此类表单。如果你转到&amp; http://emailsystem.herokuapp.com,注册(免费)并点击“新消息”。 “订户”部分使用此技术

BTW我们手动完成了这项工作。 Cocoon看起来真的很好,似乎使用与我们相同的原则。还有一个RailsCast,但这仅适用于单个添加(我认为)


<强> f.fields_for

你这样做的方法是使用一系列部分动态构建你需要的字段。从您的代码中,看起来您已经掌握了基础知识(表单正在运行),所以现在需要构建几个组件来处理AJAX请求:

  1. 您需要在控制器上处理AJAX(路由+控制器操作)
  2. 您需要将f.fields_for放入partials(因此可以使用Ajax调用它们)
  3. 您需要处理模型中的build功能

  4. 使用控制器处理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!"