Search code examples
ruby-on-railsformsassociationsparametersfields-for

Form with nested fields_for which saves to multiple records


I have three models, companies, transactions and subtransactions. Each has_many of the next. I need to build a form that saves to both transactions and multiple records of subtransactions.

I'm struggling with the best approach to the form and the appropriate way to save to the DB. Ideally, the records get saved at once if all pass validation.

My simplified form:

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

My TransactionsController:

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

Solution

  • The way to do this is to pass the nested attributes through the various models:

    #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
    

    This will give you the ability to use the following:

    #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
    

    Then...

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

    This will allow you to save the various nested attributes through the main object. It is the correct, conventional, way to do it.