Search code examples
ruby-on-railspolymorphic-associations

Rails create button does not work with render partial


Following the DRY rule, I've inserted the render partial command inside my officers\_form.html.erb view:

<%= form_for(officer) do |f| %>
  <% if officer.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(officer.errors.count, "error") %> prohibited this officer from being saved:</h2>

      <ul>
        <% officer.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <%= render :partial => 'users/form', :locals => {:user => @officer.user} %>
  <%= render :partial => 'addresses/form', :locals => {:address => @officer.address} %>

  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

This is my users\_form.html.erb file:

<%= form_for(user) do |f| %>
  <% if user.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(user.errors.count, "error") %> prohibited this user from being saved:</h2>

      <ul>
      <% user.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <%= f.fields_for :user do |user_fields| %>
    <div class="field">
      <%= user_fields.label :last_name %>
      <%= user_fields.text_field :last_name %>
    </div>

    <div class="field">
      <%= user_fields.label :first_name %>
      <%= user_fields.text_field :first_name %>
    </div>

    <div class="field">
      <%= user_fields.label :middle_name %>
      <%= user_fields.text_field :middle_name %>
    </div>

    <div class="field">
      <%= user_fields.label :gender %>
      <%= user_fields.select(:gender, User.genders.keys) %>
    </div>
  <% end %>

  <!--div class="actions"-->
    <!--%= f.submit %-->
  <!--/div-->
<% end %>

Same reasoning as for User code applies to Addresses code, so I'll omit here for shortness.

This is my officers_controller file:

class OfficersController < BaseController
  before_action :set_officer, only: [:show, :edit, :update, :destroy]

  # GET /officers
  # GET /officers.json
  def index
    @officers = Officer.all
  end

  # GET /officers/1
  # GET /officers/1.json
  def show
  end

  # GET /officers/new
  def new
    @officer = Officer.new
  end

  # GET /officers/1/edit
  def edit
  end

  # POST /officers
  # POST /officers.json
  def create
    @officer = Officer.new(officer_params)

    respond_to do |format|
      if @officer.save
        format.html { redirect_to @officer, notice: 'Officer was successfully created.' }
        format.json { render :show, status: :created, location: @officer }
      else
        format.html { render :new }
        format.json { render json: @officer.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /officers/1
  # PATCH/PUT /officers/1.json
  def update
    respond_to do |format|
      if @officer.update(officer_params)
        format.html { redirect_to @officer, notice: 'Officer was successfully updated.' }
        format.json { render :show, status: :ok, location: @officer }
      else
        format.html { render :edit }
        format.json { render json: @officer.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /officers/1
  # DELETE /officers/1.json
  def destroy
    @officer.destroy
    respond_to do |format|
      format.html { redirect_to officers_url, notice: 'Officer was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_officer
      @officer = Officer.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def officer_params
      #params.fetch(:officer, {})
      params.require(:officer).permit!
    end
end

Now if I go to http://localhost:3000/officers/new, the parts included in both the users and addresses forms are shown, but when I press the Create officer button nothing happens. Where is the error?

class Officer < ApplicationRecord
  belongs_to :manager#, inverse_of: :officer
  has_many :customers#, inverse_of: :officer

  has_one :user, as: :userable, dependent: :destroy
  has_one :address, as: :addressable, dependent: :destroy

  accepts_nested_attributes_for :user, :address
end

class Manager < ApplicationRecord
  has_many :officers#, inverse_of: :manager

  has_one :user, as: :userable, dependent: :destroy
  has_one :address, as: :addressable, dependent: :destroy

  accepts_nested_attributes_for :user, :address
end

class User < ApplicationRecord
  enum gender: { female: 0, male: 1, undefined: 2 }

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

  belongs_to :userable, polymorphic: true
end

Thanks, FZ


Solution

  • You have not set user_attributes in your officer_params, do this:

    def officer_params
      #params.fetch(:officer, {})
      params.require(:officer).permit(:id, user_attributes: [:id, :last_name, :middle_name, :first_name, :gender, :_destroy])
    end
    

    And also change accepts_nested_attributes_for :user, :address to

     'accepts_nested_attributes_for :user, reject_if: :all_blank, allow_destroy: true
      accepts_nested_attributes_for :address, reject_if: :all_blank, allow_destroy: true'
    

    And you need to address_attributes to your officer params aswell but since i don't know your database field i can't do that part for you but it's pretty much the same as the user_attributes but with different fields(except :id and :_destroy which are the same for all).

    EDIT:

    This is a nested form:

    <%= form_for(officer) do |f %>
    
     <%= f.fields_for :user do |user| %> 
      <%= user.text_field :last_name %>
      <%= user.text_field :middle_name %>
      <%= user.text_field :first_name %>
     <% end %>
    
     <%= f.fields_for :address do |address| %>
      <%= address.text_field :street_name %>
      <%= address.text_field :zip_code %>
     <% end %>
    
    <%= f.submit 'submit' %>
    

    This way one submit button supplies for all the nested forms aswell.

    What you have is this:

    <%= form_for(officer) do |f %>
     <%= form_for(user) do |f|
      <%= f.fields_for :user do |user| %> // this (f) now stands for the user form instead of the officer form 
       <%= user.text_field :last_name %>
       <%= user.text_field :middle_name %>
       <%= user.text_field :first_name %>
      <% end %>
     <% end %>
    
    <%= form_for(address) do |f| %>
     <%= f.fields_for :address do |address| %> // same for this one
      <%= address.text_field :street_name %>
      <%= address.text_field :zip_code %>
     <% end %>
    <% end %>
    
    <%= f.submit 'submit' %> 
    

    Now you don't have a nested form, you just have 3 different full forms and you can't submit multiple forms with one submit button this way.