Monday, October 9, 2017

Rails, edit action does not populate the form correctly with many instances of the same model

Leave a Comment

I'm new to Rails and I'm doing my first project. Also, English is not my native language so bear with me, please.

The problem I'm having is that I have a form with multiple instances of the same model, the data is being created correctly but when I try to edit it the form is populated in the wrong way.

I'm making an app to check if everything goes according to the rules. The items to be checked are in a nested association Chapters->Subchapters->Checks

Every time the checks are submitted a CheckRound is created and the information of every check is stored separately in CheckResults.

CheckRounds

has_many :check_results, inverse_of: :check_round, dependent: :destroy accepts_nested_attributes_for :check_results, reject_if: proc { |att| att['observation'].blank? } 

CheckResults

belongs_to :check_round, optional: true, inverse_of: :check_results belongs_to :check 

Chapters

has_many :subchapters 

Subchapters

belongs_to: chapter has_many: checks 

Checks

belongs_to :subchapter has_many :check_results 

The form displays all the Chapters and the nested Subchapters and Checks. Every Check displays its name and has a text_area as an input.

The user can fill none or many Checks.

<%= form_for(@check_round, :url => {:action => 'update', :client_id => @client.id, :project_id => @project.id}) do |f| %>   <% @chapters.each do |chapter| %>     <%= chapter.name %>     <% chapter.subchapters.each do |subchapter| %>       <%= subchapter.name %>       <% subchapter.checks.each do |check| %>          <%= f.fields_for :check_results do |result| %>             <%= check.name %>             <%= result.hidden_field(:check_id, :value => check.id) %>             <%= result.text_area(:observation, rows: 4, :id =>'obs' + check.id.to_s) %>         <% end %>       <% end %>    <% end %>   <% end %> <% end %> 

The controller is

def edit   @check_round = CheckRound.includes(:check_results).find(params[:id])   @chapters = Chapter.includes(subchapters: :checks).where("segment_id = ?", @project.segment_id).sorted end 

If for example, I submit that check.id = 3 has the observation = "bad" when I go to edit every check has "bad" in its observation regardless of its id.

I want to know how can I show in edit all the checks with a blank observation but the ones that were created.

Thanks in advance for your time!

4 Answers

Answers 1

I believe it works like you want with this (code with some simplifications):

Check

class Check < ApplicationRecord   belongs_to :subchapter   has_many :check_results    def check_results_for_form check_round_id     results = check_results.where(check_round_id: check_round_id)     results.any? ? results : check_results.build   end end 

CheckRoundsController

def edit   @check_round = CheckRound.find(params[:id])   @chapters = Chapter.includes(subchapters: :checks).all end 

edit.html.erb

<%= form_for(@check_round, :url => {:action => 'update'}) do |f| %>   <ul>     <% @chapters.each do |chapter| %>       <li>         <%= chapter.name %>         chapter         <ul>           <% chapter.subchapters.each do |subchapter| %>             <li>               <%= subchapter.name %>               subchapter               <ul>                 <% subchapter.checks.each do |check| %>                   <li>                   <%= check.name %>                   check                   <br>                   <%= f.fields_for :check_results, check.check_results_for_form(@check_round.id) do |result| %>                     <%= result.hidden_field(:check_id, :value => check.id) %>                     <%= result.text_area(:observation, rows: 4, :id =>'obs' + check.id.to_s) %>                   <% end %>                   </li>                 <% end %>               </ul>             </li>           <% end %>         </ul>       </li>     <% end %>   <ul>   <%= f.submit %> <% end %> 

Answers 2

Ok, From what i see 2 things that needs to fixed.

1st, your f.fields_for :check_results do |result| needs an extra parameter to specify which check_results it exactly has to modify... somethings like this:

f.fields_for :check_results, @check_round.check_results.where(check_id: check.id) do |result| in the exact same place so the check variable is specify the right way.

2de, you need to permit your nested parameters in your controller so they can be saved when u submit. Normally you should see a method called check_round_params in your check_round controller. this one have to like this for everything to work:

  def check_round_params    params.require(:check_round_params).permit(    /*your needed params*/,     check_results_attributes: [:id, :check_id, :observation, /*all your nested params*/]    )   end 

In short, your update and your create actions work according to those permitted params, so you need define them there. check_results_attributes: is the way that rails understands those params are for nested models.

Here is some documentation you might find interesting:Nested attributes example

Answers 3

Your problem is that you are repeating the display of the form fields for check_results. Look at line 7 of your view code:

<%= f.fields_for :check_results do |result| %> 

This is displaying the fields for each check result on f.object (which is @check_round). However, this code gets repeated for each check in subchapter. That surrounding block gets repeated for each subchapter in chapter, and the block surrounding that gets repeated for each chapter in @chapters.

When the form is submitted, the params for check_results all have the same names, they are not distinguished by chapter, subchapter, or check. As a result, the only value that gets saved for observation is the last one submitted.

I think a solution for your case would be to only show the check_result form fields associated with the current check in the loop. One way to do that is to put a conditional in the loop starting on line 7 of your view code:

<%= f.fields_for :check_results do |result| %>   <% if result.object.check == check %>     <%= result.hidden_field(:check_id, :value => check.id) %>       <%= result.text_area(:observation, rows: 4, :id =>'obs' + check.id.to_s) %>     <% end %>   <% end %> <% end %> 

You could also just loop through the check_results independently of the loops for checks, subchapters, and chapters, but I'm assuming that you want to keep that order and context for the UI.

Answers 4

Here is the solution i've promised.

Sinds you have already defined that check results with blank observations had to be rejected and there will to much logic involved in your erb for its own sake, i would put it all in an helper method so your erb will be cleaner. Something like this:

#helpers/check_rounds_helper.rb  def edit_or_instantiate_nested_check_results(f, check_round, check, new_check_result)   if check.check_results     f.fields_for :check_results, check_round.check_results.where(check_id: check.id) do |result|       result.hidden_field(:check_id, :value => check.id)       result.text_area(:observation, rows: 4, :id =>'obs' + check.id.to_s)     end #end for the already present check results     # if u want to add a new check result event if the check is populated     f.fields_for :check_results, new_check_result do |new|       new.hidden_field(:check_id, :value => check.id)       new.text_area(:observation, rows: 4, :id =>'obs' + check.id.to_s)     end #end for the new check result   else #if there is no existing check result nest a form for a new one     f.fields_for :check_results, new_check_result do |new|       new.hidden_field(:check_id, :value => check.id)       new.text_area(:observation, rows: 4, :id =>'obs' + check.id.to_s)     end #end for the new check result   end #end if statement end 

Then in your view:

<%= form_for(@check_round, :url => {:action => 'update', :client_id => @client.id, :project_id => @project.id}) do |f| %>   <% @chapters.each do |chapter| %>     <%= chapter.name %>     <% chapter.subchapters.each do |subchapter| %>       <%= subchapter.name %>       <% subchapter.checks.each do |check| %>          <%= check.name %>          <% new_check_result = CheckResult.new(check_round_id: @check_round.id, check_id = check.id) %>          <%= edit_or_instantiate_nested_check_results(f, @check_round, check, new_check_result) %>       <% end %>    <% end %>   <% end %> <% end %> 

And that shoud be it ;). Let me know if it did the trick :D!

KR,

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment