Форма с вложенными fields_for, которая сохраняется в несколько записей

У меня есть три модели: companies, transactions и subtransactions. Каждый имеет_много следующего. Мне нужно создать форму, которая сохраняется как в transactions, так и в несколько записей subtransactions.

Я борюсь с лучшим подходом к форме и подходящим способом сохранения в БД. В идеале записи сохраняются сразу, если все проходят проверку.

Моя упрощенная форма:

<%= form_for(@transaction) do |f| %>
  <div class="field" id="transaction_amount">
    <%= f.label :total %><br>
    <%= f.text_field :total %>
  </div>

    <% 1.upto(5) do |i| %>
          <%= fields_for("subtransaction[#{i}]") do |s| %>
              <div class="field" id="subtotal_amount">
                <%= s.label :subtotal, "Subtotal #{i}" %><br>
                <%= s.text_field :subtotal %>
              </div>
          <% end %>
    <% end %>  
<% end %>

Мой Контроллер транзакций:

def new
    @transaction = company.transactions.build if logged_in?
end

def create
    @transaction = company.transactions.build(transaction_params)

    params[:subtransaction].each do |i|
        # build and save
    end

    if @transaction.save ## @subtransaction.save
        flash[:success] = "Success!"
        redirect_to success_url
      else
         render :new
      end
end

person Matthias    schedule 30.01.2016    source источник


Ответы (2)


Способ сделать это — передать атрибуты nested через различные модели:

#app/models/company.rb
class Company < ActiveRecord::Base
   has_many :transactions
   accepts_nested_attributes_for :transactions
end

#app/models/transaction.rb
class Transaction < ActiveRecord::Base
   has_many :sub_transactions
   accepts_nested_attributes_for :sub_transactions
end

Это даст вам возможность использовать следующее:

#app/controllers/transactions_controller.rb
class TransactionsController < ApplicationController
   def new
      @transaction = company.transactions.new
      5.times do
        @transaction.sub_transactions.build
      end
   end

   def create
      @transaction = company.transactions.new transaction_params
      @transaction.save
   end

   private

   def transaction_params
      params.require(:transaction).permit(sub_transactions_attributes: [:subtotal])
   end
end

Затем...

#app/views/transactions/new.html.erb
<%= form_for @transaction do |f| %>
   <%= f.fields_for :sub_transactions do |s| %>
      <%= s.text_field :subtotal %>
   <% end %>
   <%= f.submit %>
<% end %>

Это позволит вам сохранить различные вложенные атрибуты через основной объект. Это правильный, традиционный способ сделать это.

person Richard Peck    schedule 31.01.2016
comment
Спасибо за Ваш ответ. Очень полезный. И последнее замечание: означает ли это, что я также должен выполнять проверки атрибутов субтранзакций в модели транзакции? - person Matthias; 01.02.2016
comment
Нет - вы должны сохранить проверки в соответствующих моделях. Однако вам также может понадобиться inverse_of, чтобы сделать данные доступными в каждой связанной модели. - person Richard Peck; 01.02.2016
comment
Это не работает для меня, оно отправляет только одну sub_transactions вместо их массива, чего, по-видимому, ожидают рельсы, поэтому в итоге возникает ошибка - person Jeff Davenport; 10.05.2017

модель

class Transaction < ActiveRecord::Base
  has_many :subtransations
  accepts_nested_attributes_for :subtransactions
end

контроллер

def new
  @transaction = Transaction.new
  @transaction.subtransactions.build
end

def create
  company = Company.first # Or whatever you need.

  # We merge the company id to the transaction params since 
  # a transaction belongs_to a company.
  @transaction = Transaction.create!(create_params.merge(company_id: company.id))
end

private

def create_params
  params.require(:transaction).permit(
    :total,
    subtransactions_attributes: [:id, :subtotal]
  )
end

Посмотреть

<%= form_for(@transaction) do |f| %>
  <div class="field">
    <%= f.label :total %><br>
    <%= f.text_field :total %>
  </div>

  <%= f.fields_for :subtransaction do |s| %>
    <div class="field" id="subtotal_amount">
      <%= s.label :subtotal %><br>
      <%= s.text_field :subtotal %>
    </div>
  <% end %>
<% end %>

Это позволит создать только одну субтранзакцию с записью транзакции

Если вы хотите создать более одной записи subtransaction, вам следует использовать cocoon gem, это легко настроить и сэкономит много драгоценного времени.

person DevMarwen    schedule 30.01.2016
comment
Мне пришлось проголосовать за неэффективность в create_params - person Richard Peck; 31.01.2016
comment
Спасибо Марвен за ваш ответ. Очень полезный. Я также проверю драгоценные камни, которые вы предложили. Привет! - person Matthias; 01.02.2016
comment
@RichardPeck, почему create_params неэффективен? Может быть, немного уточнить? это было бы полезно. - person walidvb; 04.06.2020