Matthias
Matthias

Reputation: 1953

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

Upvotes: 2

Views: 5360

Answers (2)

Richard Peck
Richard Peck

Reputation: 76774

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.

Upvotes: 5

mtkcs
mtkcs

Reputation: 1716

model

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

controller

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

view

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

This will allow to create only one subtransaction with the transaction record

If you want to create more than one subtransaction record you should use cocoon gem, it's easy to setup and will save much precious time.

Upvotes: 2

Related Questions