Search code examples
ruby-on-rails-4nested-attributesstrong-parameters

Rails 4 nested form with double association


I have an Order model like this:

class Order < ActiveRecord::Base
  belongs_to :service_address, class_name: 'Address'
  belongs_to :billing_address, class_name: 'Address'

  accepts_nested_attributes_for :service_address, allow_destroy: true
  accepts_nested_attributes_for :billing_address, allow_destroy: true
end

class Address < ActiveRecord::Base

end

I am struggling to make a nested form work to allow me to add both addresses in the same form. The params section of the Order is like this:

params.require(:order).permit(:contract_id, :billing_address_id, :service_address_id, :valid_from, :valid_to, :contact_person_id, :billing_mode,
                                service_address: [:city_id, :street_id, :number, :block, :entrance, :floor, :apartment, :_destroy],
                                billing_address: [:city_id, :street_id, :number, :block, :entrance, :floor, :apartment, :_destroy])

My create action is like this:

@order = Order.new(order_params)
@order.service_address = Address.new(params[:order][:service_address])
@order.billing_address = Address.new(params[:order][:billing_address])

When submitting this way, the form does not validate, all fields for billing_address being highlighted as incomplete.

If I use service_address_params and billing_adress_params I get an error of undefined local variable or method 'service_address_params'

I am stuck here for two days, so any help would be appreciated.

EDIT: new

def new
  @order = Order.new
  @order.service_address = Address.new
  @order.billing_address = Address.new
end

EDIT: form

<%= simple_form_for @order, data: {validate: 'parsley'} do |f| %>
  <%= f.input :contract_id, as: :hidden, :input_html => { :value => @contract_id } %>
  <%= f.input :same_billing_address, as: :hidden, :input_html => { :value => 0 } %>

  <%= f.error_notification %>

  <%= render :partial => 'order_data', :locals => { f: f } %>
  <%= render :partial => 'service_address' %>
  <%= render :partial => 'billing_address' %>

  <div class="form-actions">
    <%= f.button :submit %>
  </div>
<% end %>

One of the partials for addresses:

<%= simple_fields_for :service_address do |sa| %>
  <div class="form-inputs" id="inputs-step2">
    <%= sa.input :city_id, label: 'Oras', collection: @cities, input_html: { 'data-parsley-group' => "block2", :required => true, id: 'service_address_city_id' } %>
    <%= sa.input :street_id, label: 'Strada', collection: @streets, input_html: { 'data-parsley-group' => "block2", :required => true, id: 'service_address_street_id' } %>
    <%= sa.input :number, label: 'Numar', input_html: { 'data-parsley-group' => "block2", :required => true } %>
    <%= sa.input :block, label: 'Bloc' %>
    <%= sa.input :entrance, label: 'Scara' %>
    <%= sa.input :floor, label: 'Etaj' %>
    <%= sa.input :apartment, label: 'Apartament' %>
  </div>
<% end %>

Billing address partial:

<%= simple_fields_for :billing_address do |ba| %>
  <div class="form-inputs" id="inputs-step3">
    <%= ba.input :city_id, label: 'Oras', collection: @cities, input_html: { 'data-parsley-group' => "block3", :required => true, id: 'billing_address_city_id' } %>
    <%= ba.input :street_id, label: 'Strada', collection: @streets, input_html: { 'data-parsley-group' => "block3", :required => true, id: 'billing_address_street_id' } %>
    <%= ba.input :number, label: 'Numar', input_html: { 'data-parsley-group' => "block3", :required => true } %>
    <%= ba.input :block, label: 'Bloc' %>
    <%= ba.input :entrance, label: 'Scara' %>
    <%= ba.input :floor, label: 'Etaj' %>
    <%= ba.input :apartment, label: 'Apartament' %>
  </div>
<% end %>

The params hash:

--- !ruby/hash:ActionController::Parameters
utf8: ✓
authenticity_token: nZvowtR+Iwo/Rt420w55ZSzNtiZCrjga57dSimDPdg0=
order: !ruby/hash:ActionController::Parameters
  contract_id: ''
  same_billing_address: '0'
  valid_from: '2013-08-04'
  months: '12'
  billing_mode: '1'
service_address: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
  city_id: '1'
  street_id: '19'
  number: '1'
  block: '1'
  entrance: '1'
  floor: '1'
  apartment: '1'
billing_address: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
  city_id: '1'
  street_id: '19'
  number: '2'
  block: '2'
  entrance: '2'
  floor: '2'
  apartment: '2'
commit: Create Order
action: create
controller: orders

Solution

  • Firstly,using accepts_nested_attributes_for with a belongs_to is a pain and quite tricky as well.And secondly there are lot of mistakes in the code.I will be explaining one by one.

    Mistake #1

    Your new method should be like this

    def new
      @order = Order.new
      @order.build_service_address 
      @order.build_billing_address
    end
    

    In your create method,you don't need these lines,remove them.

    @order.service_address = Address.new(params[:order][:service_address])
    @order.billing_address = Address.new(params[:order][:billing_address])
    

    Mistake #2

    Your order_params method should be like this

    def order_params
     params.require(:order).permit(:contract_id, :billing_address_id, :service_address_id,    :valid_from, :valid_to, :contact_person_id, :billing_mode,service_address_attributes: [:city_id, :street_id, :number, :block, :entrance, :floor, :apartment, :_destroy],billing_address_attributes: [:city_id, :street_id, :number, :block, :entrance, :floor, :apartment, :_destroy])
    end
    

    Notice the changes service_address_attributes and billing_address_attributes

    Mistake #3

    You are not passing locals to your service_address and billing_address partials.

    These lines

    <%= render :partial => 'service_address' %>
    <%= render :partial => 'billing_address' %>
    

    should be like this

    <%= render :partial => 'service_address',:locals => { f: sa }%>
    <%= render :partial => 'billing_address',:locals => { f: ba } %>
    

    Update

    Try calling the partials like this in the main form

    <%= f.simple_fields_for :service_address do |sa| %>
      <%= render :partial => 'service_address',:locals => { f: sa }%>
    <% end %>
    
    <%= f.simple_fields_for :billing_address do |ba| %>
      <%= render :partial => 'billing_address',:locals => { f: ba } %>
    <% end %>
    

    And change the code in your service_address and billing_address partials like this

    #_service_address.html.erb
    <div class="form-inputs" id="inputs-step2">
        <%= f.input :city_id, label: 'Oras', collection: @cities, input_html: { 'data-parsley-group' => "block2", :required => true, id: 'service_address_city_id' } %>
        <%= f.input :street_id, label: 'Strada', collection: @streets, input_html: { 'data-parsley-group' => "block2", :required => true, id: 'service_address_street_id' } %>
        <%= f.input :number, label: 'Numar', input_html: { 'data-parsley-group' => "block2", :required => true } %>
        <%= f.input :block, label: 'Bloc' %>
        <%= f.input :entrance, label: 'Scara' %>
        <%= f.input :floor, label: 'Etaj' %>
        <%= f.input :apartment, label: 'Apartament' %>
      </div>
    
    #_billing_address.html.erb
    <div class="form-inputs" id="inputs-step3">
        <%= f.input :city_id, label: 'Oras', collection: @cities, input_html: { 'data-parsley-group' => "block3", :required => true, id: 'billing_address_city_id' } %>
        <%= f.input :street_id, label: 'Strada', collection: @streets, input_html: { 'data-parsley-group' => "block3", :required => true, id: 'billing_address_street_id' } %>
        <%= f.input :number, label: 'Numar', input_html: { 'data-parsley-group' => "block3", :required => true } %>
        <%= f.input :block, label: 'Bloc' %>
        <%= f.input :entrance, label: 'Scara' %>
        <%= f.input :floor, label: 'Etaj' %>
        <%= f.input :apartment, label: 'Apartament' %>
      </div>