Search code examples
ruby-on-railsnested-attributesruby-on-rails-5formtastic

Create multiple Addresses for one User with Formtastic


I currently have the following setup in my Rails 5 Application:

class User < ApplicationRecord
  has_many :addresses
  accepts_nested_attributes_for :addresses
end

class Address < ApplicationRecord
  belongs_to :user
end

The corresponding controller looks like this:

class UsersController < ApplicationController
  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)

    if @user.save
      redirect_to users_path
    else
      render 'new', notice: "User could not be created"
    end
  end

  private

  def user_params
    params.require(:user).permit(
      :first_name, :last_name, :email, ...
    )
  end
end

Each User is supposed to have multiple Addresses (e.g. for billing and shipping) that I need to set during user creation. The form (made with formtastic) looks like this (outtake):

= semantic_form_for @user do |user|
  = user.input :first_name
  = user.semantic_fields_for Address.new do |shipping_address|
    = shipping_address.input :city
    = shipping_address.input :usage,
      as: :hidden,
      input_html: { value: 'shipping' }
  = user.semantic_fields_for Address.new do |billing_address|
    = billing_address.input :city
    = billing_address.input :usage,
      as: :hidden,
      input_html: { value: 'billing' }

The problem is ofc that only the latter address is present in the sites parameters, so only one will be created.

How can I create multiple address for the same user?


Solution

  • To create nested records you should and seed the record and pass the association to fields_for.

    For example you would do:

    class UsersController < ApplicationController
      # ...
      def new
        @user = User.new
        # if we skipped this step there would be no address inputs on the form.
        @user.addresses.new(value: 'shipping')
        @user.addresses.new(value: 'billing')
      end
    end
    

    <%= semantic_form_for(@user) do |f| %>
      # ...
      # fields_for iterates through @user.addresses creating 
      # inputs for each
      <% f.semantic_fields_for :addresses do |a| %>
        <%= a.inputs :street %>
        <%= a.input :value,  as: :hidden %>
      <% end %>
    <% end %>
    

    Which would give the following params hash:

    {
      user: {
        addresses_attributes: [
          {
            street: 'Wall Street',
            value: 'shipping'
          },
          {
            street: 'Broadway',
            value: 'billing'
          },
        ]
      }
    }
    

    Note that proper pluralization is extremely important here! You can then pass this to your user model and accepts_nested_attributes will do the rest of the work.

    class UsersController < ApplicationController
      # ...
      def create
        @user = User.new(user_params)
        if @user.save
          #...
        else
          # ...
        end
      end
    
      private
        def user_params
          params.require(:user)
                .permit(:email, :password, addresses_attributes: [:value, :street, :city]) # etc
        end
    end