0

I have an Exam and an ExamBattery that is just a collection of Exams. They have a has_and_belong_to_many declaration for each other, and ExamBattery accepts nested attributes for Exam, like so:

class Exam < ApplicationRecord
  has_and_belongs_to_many :exam_batteries
  validates_presence_of :name
end

class ExamBattery < ApplicationRecord
  has_and_belongs_to_many :exams
  accepts_nested_attributes_for :exams, reject_if: lambda { |attrs| attrs['name'].blank? }
  validates_presence_of :name
end

When I create a new Exam, I want to be able to assign it to one or many ExamBatteries, so in ExamsController I whitelisted the array exam_battery_ids to accept multiple ExamBatteries to assign them to the current Exam (no other change was made, the controller is just from the scaffold):

def exam_params
  params.require(:exam).permit(:name, :description, :order, :price, exam_battery_ids: [])
end

Also, in the view exams/new I added a multiple select to send the desired exam_battery_ids as params:

 <%= form_with(model: exam, local: true) do |form| %>
   # ... typical scaffold code
   <div class="field">
     <% selected = exam.exam_batteries.collect { |eb| eb.id } %>
     <%= form.label :exam_battery_ids, 'Add batteries:' %>
     <%= form.select :exam_battery_ids,
                options_from_collection_for_select(ExamBattery.all, :id, :name, selected),
                { prompt: 'None' },
                multiple: true %>
   </div>
 <% end %>

The idea is to be able to create a new ExamBattery with new Exams in it, in the same form (I haven't wrote that part yet, I can only edit for now). Also, when I edit an ExamBattery I want to be able to edit its Exams and even assign them to other ExamBatteries (if I select 'None', or JUST another exam battery, it would stop being assigned to the current ExamBattery), so in exam_batteries/edit (actually, the form partial in it) I have this code:

<%= form_with(model: exam_battery, local: true) do |form| %>
  # ... normal scaffold code
  <div class="field">
    <!-- it should be exam_battery[exams_attributes][#_of_field][order] -->
    <!-- it is exam_battery[exam_battery_ids][] -->
    <% selected = exam_battery.exams.map { |exam| exam.id } %>
    <%= form.label :exam_battery_ids, 'Edit batteries:' %>
    <%= form.select :exam_battery_ids,
                    options_from_collection_for_select(ExamBattery.all, :id, :name, selected),
                    { prompt: 'None' },
                    multiple: true %>
  </div>
<% end %>

And in ExamBatteriesController I whitelisted the exam_batteries_attributes, with exam_battery_ids: [] as a param:

params.require(:exam_battery).permit(:name, :certification, exams_attributes: [:name, :description, :order, :price, exam_battery_ids: []])

But when in the ExamBattery form I try to edit the Exam's exam_batteries, the info doesn't update, because the params are like this:

Parameters: {"utf8"=>"✓", "authenticity_token"=>"blah", "exam_battery"=>{"name"=>"Battery1", "certification"=>"test1", "exams_attributes"=>{"0"=>{"name"=>"Exam1", "description"=>"", "order"=>"", "id"=>"3"}, "1"=>{"name"=>"Exam2", "description"=>"", "order"=>"", "id"=>"4"}, "2"=>{"name"=>"Exam3", "description"=>"", "order"=>"", "id"=>"5"}}, "exam_battery_ids"=>["", "", "", "", "", "3"]}, "commit"=>"Update Exam battery", "id"=>"3"}

The exam_battery_ids are sent as a different param because the select name is exam_battery[exam_battery_ids][] instead of something like exam_battery[exams_attributes][0][name], as it happens with the other fields. How can I fix that?

Thanks.

1 Answer 1

0

I had an error in the form. In exam_batteries/edit I didn't notice I was using the form_with variable (form) and not the fields_for variable (builder), so it should be like this:

<div class="field">
  <!-- it should be exam_battery[exams_attributes][0][order] -->
  <!-- it is exam_battery[exam_battery_ids][] -->
  <% selected = exam_battery.exams.map { |exam| exam.id } %>
  <%= builder.label :exam_battery_ids, 'Escoge una batería' %>
  <%= builder.select :exam_battery_ids,
                  options_from_collection_for_select(ExamBattery.all, :id, :name, selected),
                  {
                    include_hidden: false,
                    prompt: 'Ninguna'
                  },
                  multiple: true %>
</div>

With that it should work. The only issue now is that I can't get the selected batteries when I show them in the fields_for, but I'm working on it.

UPDATE: I can show the current exam_batteries of the exam in the nested form by replacing the selected variable in the view with this:

<% selected = exam_battery.exams[builder.options[:child_index]].exam_batteries.map { |eb| eb.id } %>

If you know about a cleaner method, please let me know.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.