Rails принимает_nested_attributes_for с f.fields_for и AJAX

Мне любопытно как правильно пользоваться accepts_nested_attributes_for а также f.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 номенклатура для построения Ордена и OrderDetail. Я могу только построить один, хотя. Что является моей следующей проблемой.

Видите, как там есть кнопки? Это AJAXs строки в форме. Если вы нажмете 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: Моему частичному нужен экземпляр FormBuilder и несколько других.

Любые предложения о том, как я должен справиться с этим? Используя код, который у меня есть здесь... Я смог создать новый заказ с соответствующими 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

_Детали частично

<%= 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 ответ

Решение

Это возможно

Здесь есть очень, очень хороший учебник: http://pikender.in/2013/04/20/child-forms-using-fields_for-through-ajax-rails-way/

Мы также недавно внедрили этот тип формы в одном из наших приложений для разработки. Если вы зашли на сайт & http://emailsystem.herokuapp.com/, зарегистрируйтесь (бесплатно) и нажмите "Новое сообщение". Часть "Подписчики" использует эту технологию

Кстати, мы сделали это вручную. Кокон на самом деле выглядит очень хорошо и, похоже, использует те же принципы, что и мы. Существует также RailsCast, но это работает только для отдельных дополнений (я думаю)


f.fields_for

То, как вы это делаете, - это использование ряда частичных элементов, которые динамически формируют нужные вам поля. Исходя из вашего кода, похоже, что у вас есть основные принципы (форма работает), так что теперь это случай создания нескольких компонентов для обработки запроса AJAX:

  1. Вам нужно обработать AJAX на контроллере (маршрут + действие контроллера)
  2. Вы должны поместить свой f.fields_for в партиалы (чтобы их можно было вызывать с помощью Ajax)
  3. Вы должны справиться с 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] поля (это был недостаток, который мы нашли с помощью учебника Райана Бейтса)

Первый урок, который я написал, решил, что нужно просто установить child_index из новых полей с Time.now.to_i, Это устанавливает уникальный идентификатор, и поскольку фактический идентификатор нового поля не имеет значения, вы сможете добавить столько полей, сколько хотите с ним


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!"
Другие вопросы по тегам