ruby-on-railsnested-attributesfields-for

Rails 5 - form_with - fields_for assign nested attributes upon creation of parent items.


I'm new to rails or coding in general, trying to learn by creating a simple app, I'm not sure if it's silly to ask, but I tried really finding a concrete answer to the problem and failed each time for days, I would appreciate if someone can help me on this;

I have a "Plane" model which has "PlaneModel", "Xroutes" and "Staffs" attributes. Each 3 attrs, are pre-created, have their own tables, and upon a new instance of plane creation you just select through available items via "collection_select". Code for Plane.rb;

class Plane < ApplicationRecord
  belongs_to :plane_model
  belongs_to :xroute    
  has_many :staffs
  accepts_nested_attributes_for :staffs
end

Now, because of the db relations, Planes table have a Planemodel_id, Xroute_id but not Staff_id. There lies the problem, because when I try to create an instance of a plane, "collection_select" easily works for the first 2 attrs, assigning Planemodel_id and Xroute_id respectively. For staffs though what I want to achieve is, after creation of each plane, assigning that plane_id on the corresponding staff row in Staffs table. Code for Staff.rb;

class Staff < ApplicationRecord
  belongs_to :hub, optional: true
  belongs_to :plane, optional: true
end

Below is the _form for planes;

<%= form_with(model: plane, local: true) do |form| %>
  <div class="control-group">
    <%= form.label :plane_model_id %>
    <div class="controls">
      <%= collection_select( :plane, :plane_model_id, PlaneModel.all, :id, :name, {}, { :multiple => false } ) %>
    </div>
  </div>

  <div class="control-group">
    <%= form.label :xroute_id %>
    <div class="controls">
      <%= collection_select( :plane, :xroute_id, Xroute.all, :id, :name, {}, { :multiple => false } ) %>
    </div>
  </div>

  <%= form.fields_for :staffs do |staff| %>
    <div class="control-group">
      <%= staff.label :staff_id %>
      <div class="controls">
        <%= staff.collection_select( :plane_id, Staff.all, :id, :name, { :multiple => false } ) %>
      </div>
    </div>
  <% end %>

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

Inside the plane controller new & create functions and params function to whitelist attributes;

def new
  @plane = Plane.new
  3.times { @plane.staffs.build }
end

def create
  @plane = Plane.new(plane_params)    
  respond_to do |format|
    if @plane.save
      format.html { redirect_to @plane, notice: 'Plane was successfully created.' }
      format.json { render :show, status: :created, location: @plane }
    else
      format.html { render :new }
      format.json { render json: @plane.errors, status: :unprocessable_entity }
    end
  end
end

def plane_params
  params.require(:plane).permit(:plane_model_id, :xroute_id, staffs_attributes: [:id, :staff, :plane_id])
end

Here is the visual representation of what's happening;

Form for creating new plane instance, PlaneModel, Xroute and 3 staff members are selected from dropdown lists.

When submitted, a new instance is successfully created.

Here is the index page for Planes, as you see PlaneModel and Xroute are there.

When I go to staffs page though, instead of putting created plane's id in the selected staffs "plane_id" field, it just throws additional 3 staff instances without any more information on it.

Staff index page showing, newly created empty staff instances.

Like I said maybe it's a simple thing but I'm missing something for sure, appreciate all the help & suggestions.

Thanks,


Solution

  • accepts_nested_attributes_for :staffs allows you to create a staffs instances together with a plane, inside the same form. But as I can see you want only to select existing staff, am I right? You need to change you form:

    <div class="control-group">
      <%= form.label :staff_ids %>
      <div class="controls">
        <%= form.collection_select( :staff_ids, Staff.all, :id, :name, {}, { multiple: true } ) %>
      </div>
    </div>
    

    params:

    def plane_params
      params.require(:plane).permit(:plane_model_id, :xroute_id, staff_ids: [])
    end
    

    and remove 3.times { @plane.staffs.build } from new action