Question

I have a Sezzion model:

attr_accessible  :description
has_many :session_instructors, :dependent => :destroy
has_many :instructors, :through => :session_instructors
accepts_nested_attributes_for :session_instructors
accepts_nested_attributes_for :instructors

Instructor model:

attr_accessible :bio
has_many :sezzions, :through => :session_instructors
has_many :session_instructors, :dependent => :destroy

SessionInstructor model:

attr_accessible :instructor_id, :sezzion_id
belongs_to :sezzion
belongs_to :instructor

Lastly, User model:

has_many :sezzions
has_many :instructors

I'm trying to create a form for Sezzion with nested form for SessionInstructor which has multiple select option for Instructors.

How can I do the following:

  1. nested form for SessionInstructor
  2. use multiple select option to get all the selected Instructor's instructor_id
  3. hidden field to pass in the created/updated session_id with each select instructor

I have the following code as of now:

<%= form_for(@sezzion) do |f| %>

    <%= f.label :description %>
    <%= f.text_area :description %>


    <%= f.label :instructors %>
    <%= fields_for :session_instructors do |f| %>
      <select multiple>
        <% current_user.instructors.each do |instructor| %>
          <option><%= instructor.name %></option>
        <% end %>
      </select>
    <% end %>

    <%= f.submit %>

<% end %>

Thank you so much!

Was it helpful?

Solution

This is something that seems ridiculously hard in Rails.

I think something like this might work:

<%= f.fields_for :session_instructors do |si| %>
  <%= si.collection_select :instructor_ids, current_user.instructors, :id, :name, multiple: true>
<% end %>

This should create a form element which will set sezzion[session_instructors_attributes][instructor_ids].

Although I'm not sure if that's actually what you want. I've never tried this using a multi select. If it doesn't work, you could also try getting rid of the fields_for and just using f.collection_select. If you're willing to use a checkbox, I can show you how to do that for sure.

I hope that helps.

Edit:

Here's how I would usually do it with a check_box:

<%= f.fields_for :session_instructors do |si| %>
  <%= si.hidden_field "instructor_ids[]" %>
  <% current_user.instructors.each do |i| %>
    <%= si.check_box "instructor_ids[]", i.id, i.sezzions.include?(@sezzion), id: "instructor_ids_#{i.id}" %>
    <%= label_tag "instructor_ids_#{i.id}", i.name %>
  <% end %>
<% end%>

There are a couple "gotchas!" with this method. When editing a model, if you deselect all checkboxes then it won't send the parameter at all. That's why the hidden_field is necessary. Also, you need to make sure each form element has a unique id field. Otherwise only the last entry is sent. That's why I manually set the value myself.

I copy pasted and then edited. Hopefully I got the syntax close enough where you can get it to work.

FINAL EDIT:

Per Sayanee's comment below, the answer was a bit simpler than I thought:

<%= f.collection_select :instructor_ids, current_user.instructors, :id, :name, {}, {:multiple => true} %>

OTHER TIPS

@Sayanee, can you post how your instructors, sezzions table look like. Also for note, you can not get instructor_ids from Instructor object, hence you are getting "undefined method" error. With the current association that you shared, you can get instructor_ids from a Sezzion object. So you need to loop through current_user.sezzions in stead of current_user.instructors.

This is a way to implement fields_for nested form with html multiple_select in case of a has_many :through association. Solved it by doing something like this. The form view:

<%= form_for(@sezzion) do |f| %>
...

    <%= fields_for :session_instructors do |g| %>
        <%= g.label :instructor, "Instructees List (Ctrl+Click to select multiple)" %>:
        <%= g.select(:instructor_id, Instructor.all.collect { |m| [m.name, m.id] }, {}, { :multiple => true, :size => 5 }) %>
        <%= g.hidden_field :your_chosen_variable_id, value: your_chosen.id %>
    <% end %>
...
<%= f.submit %>

<% end %>

Note:Since the @sezzion would not be saved at the time of generating the form you cannot pass that id (@sezzion.id) in place of your_chosen.id through the form. You could handle that save in the controller.

Make sure that your controller Initializes the Variables while generating the form: Your def new could look something like this:

def new
  @sezzion = Sezzion.new
  @sezzion.session_instructor.build
  @sezzion.instructors.build
end

Now the create controller has to be able to accept the strong params required for the multiple select, so the sezzion_params method may look something like this:

def sezzion_params
    params.require(:sezzion).permit(:description, :any_other_fields,
                                 :session_instructors_attributes  => 
                                     [:instructor_id => [], :your_chosen_id => Integer])
end

In the create function, the first session_instructor variable is linked to the @sezzion instance variable through our new function. The other session_instructors in our multiple select must be built after the Sezzion instance is saved, if you want to pass in the created @sezzion.id with each select instructor. .

def create
    @sezzion = Sezzion.new(sezzion_params)
    @startcount=1 #The first element of the array passed back from the form with multiple select is blank
    @sezzion.session_instructors.each do |m|
      m.instructor_id = sezzion_params["session_instructors_attributes"]["0"]["instructor_id"][@startcount]
      m.your_chosen_variable_id = your_chosen.id
      @startcount +=1
    end

    if @sezzion.save
        sezzion_params["session_instructors_attributes"]["0"]["instructor_id"].drop(@startcount).each do |m|
        @sezzion.session_instructors.build(:instructor_id => sezzion_params["session_instructors_attributes"]["0"]["instructor_id"][@startcount],
                                 :your_chosen_variable_id => your_chosen.id).save
        @startcount += 1
      end
      flash[:success] = "Sezzion created!"
      redirect_to root_url
    else
      flash[:danger] = "There were errors in your submission"
      redirect_to new_sezzion_path
    end
end
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top