Search code examples
ruby-on-railsrubydeviseruby-on-rails-5nested-forms

Rails 5 - Devise - User has_one association - nested form fields are not shown at sign up form


As I searched, this is a common issue, but none of the answers I found work for my case. I have set up a User model with devise and it has two related models, it has one Contact Detail and many Addresses. The nested form works well with addresses, but my contact detail fields are not shown.

My User model is the following:

  validates_presence_of :contact_detail, :addresses
  # Include default devise modules. Others available are:
  # :lockable, :timeoutable, :trackable and :omniauthable
  devise :invitable, :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :confirmable

  has_one :contact_detail, dependent: :destroy
  has_many :addresses, dependent: :destroy

  accepts_nested_attributes_for :addresses, 
                                allow_destroy: true

  accepts_nested_attributes_for :contact_detail,
                                allow_destroy: true

The contact details model only has belongs_to :user

I made the changes mentioned at devise gem at my application controller:

class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [addresses_attributes: [:street_name, :street_number, :city, :country, :postal_code, :name],
                                                      contact_detail_attributes: [:first_name, :last_name, :description, :telephone, :mobile ]])
  end
end

and my app/views/devise/registrations/new.html.erb file looks like this:

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
...
  <div>
    <% f.fields_for :contact_detail do |contact_detail|%>
      <div class="field">
        <%= contact_detail.label :first_name %>
        <%= contact_detail.text_field :first_name %>
      </div>

      <div class="field">
        <%= contact_detail.label :last_name %>
        <%= contact_detail.text_field :last_name %>
      </div>

      <div class="field">
        <%= contact_detail.label :description %>
        <%= contact_detail.text_area :description %>
      </div>

      <div class="field">
        <%= contact_detail.label :telephone %>
        <%= contact_detail.number_field :telephone %>
      </div>

      <div class="field">
        <%= contact_detail.label :mobile %>
        <%= contact_detail.number_field :mobile %>
      </div>
    <% end %>
  </div>
...

But my contact detail fields are not shown. Any ideas?


Solution

  • You have to "seed" the relation in order for the inputs for an association to appear. fields_for works like a loop. If the association is empty or nil the block runs 0 times.

    Normally you would do this in the new action of your controller:

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

    In Devise the new action is Devise::RegistrationsController#new which you can customize by subclassing the controller:

    class MyRegistrationsController < Devise::RegistrationsController
      def new
        super do |user|
          user.build_contact_detail
        end
      end
    end
    

    super do |user| ... end uses the fact that all the Devise controller actions take a block and yield the resource. This makes it really easy to customize them without copy-pasting the entire method.

    You then have to alter the routes so that your custom controller is used:

    Rails.application.routes.draw do
      devise_for :users, controllers: {
        registrations: 'my_registrations'
      }
    end