Rails: fields_for with index? - ruby-on-rails

Is there a method (or way to pull off similar functionality) to do a fields_for_with_index?
Example:
<% f.fields_for_with_index :questions do |builder, index| %>
<%= render 'some_form', :f => builder, :i => index %>
<% end %>
That partial being rendered needs to know what the current index is in the fields_for loop.

The answer is quite simple as the solution is provided within Rails. You can use f.options params. So, inside your rendered _some_form.html.erb,
Index can be accessed by:
<%= f.options[:child_index] %>
You don't need to do anything else.
Update: It seems that my answer wasn't clear enough...
Original HTML File:
<!-- Main ERB File -->
<% f.fields_for :questions do |builder| %>
<%= render 'some_form', :f => builder %>
<% end %>
Rendered Sub-Form:
<!-- _some_form.html.erb -->
<%= f.options[:child_index] %>

As of Rails 4.0.2, an index is now included in the FormBuilder object:
https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-fields_for
For example:
<%= form_for #person do |person_form| %>
...
<%= person_form.fields_for :projects do |project_fields| %>
Project #<%= project_fields.index %>
...
<% end %>
...
<% end %>

The answer below was posted many years ago, for a modern approach see:
https://stackoverflow.com/a/22640703/105403
This would actually be a better approach, following Rails documentation more closely:
<% #questions.each.with_index do |question,index| %>
<% f.fields_for :questions, question do |fq| %>
# here you have both the 'question' object and the current 'index'
<% end %>
<% end %>
From:
http://railsapi.com/doc/rails-v3.0.4/classes/ActionView/Helpers/FormHelper.html#M006456
It’s also possible to specify the
instance to be used:
<%= form_for #person do |person_form| %>
...
<% #person.projects.each do |project| %>
<% if project.active? %>
<%= person_form.fields_for :projects, project do |project_fields| %>
Name: <%= project_fields.text_field :name %>
<% end %>
<% end %>
<% end %>
<% end %>

For Rails 4+
<%= form_for #person do |person_form| %>
<%= person_form.fields_for :projects do |project_fields| %>
<%= project_fields.index %>
<% end %>
<% end %>
Monkey Patch For Rails 3 Support
To get f.index to work in Rails 3 you need to add an monkey patch to your projects initializers to add this functionality to fields_for
# config/initializers/fields_for_index_patch.rb
module ActionView
module Helpers
class FormBuilder
def index
#options[:index] || #options[:child_index]
end
def fields_for(record_name, record_object = nil, fields_options = {}, &block)
fields_options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options?
fields_options[:builder] ||= options[:builder]
fields_options[:parent_builder] = self
fields_options[:namespace] = options[:namespace]
case record_name
when String, Symbol
if nested_attributes_association?(record_name)
return fields_for_with_nested_attributes(record_name, record_object, fields_options, block)
end
else
record_object = record_name.is_a?(Array) ? record_name.last : record_name
record_name = ActiveModel::Naming.param_key(record_object)
end
index = if options.has_key?(:index)
options[:index]
elsif defined?(#auto_index)
self.object_name = #object_name.to_s.sub(/\[\]$/,"")
#auto_index
end
record_name = index ? "#{object_name}[#{index}][#{record_name}]" : "#{object_name}[#{record_name}]"
fields_options[:child_index] = index
#template.fields_for(record_name, record_object, fields_options, &block)
end
def fields_for_with_nested_attributes(association_name, association, options, block)
name = "#{object_name}[#{association_name}_attributes]"
association = convert_to_model(association)
if association.respond_to?(:persisted?)
association = [association] if #object.send(association_name).is_a?(Array)
elsif !association.respond_to?(:to_ary)
association = #object.send(association_name)
end
if association.respond_to?(:to_ary)
explicit_child_index = options[:child_index]
output = ActiveSupport::SafeBuffer.new
association.each do |child|
options[:child_index] = nested_child_index(name) unless explicit_child_index
output << fields_for_nested_model("#{name}[#{options[:child_index]}]", child, options, block)
end
output
elsif association
fields_for_nested_model(name, association, options, block)
end
end
end
end
end

Checkout Rendering a collection of partials. If your requirement is that a template needs to iterate over an array and render a sub template for each of the elements.
<%= f.fields_for #parent.children do |children_form| %>
<%= render :partial => 'children', :collection => #parent.children,
:locals => { :f => children_form } %>
<% end %>
This will render “_children.erb“ and pass the local variable 'children' to the template for display. An iteration counter will automatically be made available to the template with a name of the form partial_name_counter. In the case of the example above, the template would be fed children_counter.
Hope this helps.

I can't see a decent way to do this through the ways provided by Rails, at least not in -v3.2.14
#Sheharyar Naseer makes reference to the options hash which can be used to solve the problem but not as far as I can see in the way he seems to suggest.
I did this =>
<%= f.fields_for :blog_posts, {:index => 0} do |g| %>
<%= g.label :gallery_sets_id, "Position #{g.options[:index]}" %>
<%= g.select :gallery_sets_id, #posts.collect { |p| [p.title, p.id] } %>
<%# g.options[:index] += 1 %>
<% end %>
or
<%= f.fields_for :blog_posts do |g| %>
<%= g.label :gallery_sets_id, "Position #{g.object_name.match(/(\d+)]/)[1]}" %>
<%= g.select :gallery_sets_id, #posts.collect { |p| [p.title, p.id] } %>
<% end %>
In my case g.object_name returns a string like this "gallery_set[blog_posts_attributes][2]" for the third field rendered so I just match the index in that string and use it.
Actually a cooler (and maybe cleaner?) way to do it is to pass a lambda and call it to increment.
# /controller.rb
index = 0
#incrementer = -> { index += 1}
And the in the view
<%= f.fields_for :blog_posts do |g| %>
<%= g.label :gallery_sets_id, "Position #{#incrementer.call}" %>
<%= g.select :gallery_sets_id, #posts.collect { |p| [p.title, p.id] } %>
<% end %>

Added to fields_for child_index: 0
<%= form_for #person do |person_form| %>
<%= person_form.fields_for :projects, child_index: 0 do |project_fields| %>
<%= project_fields.index %>
<% end %>
<% end %>

I know that this is a bit late but I recently had to do this you can get the index of the fields_for like this
<% f.fields_for :questions do |builder| %>
<%= render 'some_form', :f => builder, :i => builder.options[:child_index] %>
<% end %>
I hope that this helps :)

If you want to have control over the indexes check out the index option
<%= f.fields_for :other_things_attributes, #thing.other_things.build do |ff| %>
<%= ff.select :days, ['Mon', 'Tues', 'Wed'], index: 2 %>
<%= ff.hidden_field :special_attribute, 24, index: "boi" %>
<%= end =>
This will produce
<select name="thing[other_things_attributes][2][days]" id="thing_other_things_attributes_7_days">
<option value="Mon">Mon</option>
<option value="Tues">Tues</option>
<option value="Wed">Wed</option>
</select>
<input type="hidden" value="24" name="thing[other_things_attributes][boi][special_attribute]" id="thing_other_things_attributes_boi_special_attribute">
If the form is submitted, params will include something like
{
"thing" => {
"other_things_attributes" => {
"2" => {
"days" => "Mon"
},
"boi" => {
"special_attribute" => "24"
}
}
}
I had to use the index option to get my multi-dropdowns to work. Good luck.

Related

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

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!
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
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,
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 %>
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.

Error with instance variable in Index view

So in my tutors_controller.rb this is my index action
def index
#tutor = Tutor.all
#tutor = #tutor.fees_search(params[:fees_search]) if params[:fees_search].present?
end
and in my index.html.erb this is the view
<div class='container'>
<%= form_tag(tutors_path, method: :get) do %>
<%= label_tag 'fees_search', 'Max Fees' %>
<%= select_tag 'fees_search', options_for_select((10..50).step(10)) %>
<%= submit_tag 'Filter' %>
<% end %>
<% #tutor.each do |tutor| %>
<% unless tutor.admin? %>
<div class='row' id='tutor-listing'>
<div class='col-xs-4'>
<%= image_tag(tutor.profile.avatar.url, :class => "img-rounded" ) if tutor.profile.avatar? %>
</div>
<div class='col-xs-8'>
<h3><%= link_to tutor.full_name, tutor_path(tutor) %></h3>
<% unless tutor.subjects.nil? %>
<% tutor.subjects.each do |subs| %>
<span class='badge'id='tutor-listing-badge'>
<%= link_to subs.name, subject_path(subs) %>
</span>
<% end %>
<% end %>
<% unless current_tutor %>
<%= button_to "Shortlist Tutor", add_to_cart_path(tutor.id), :method => :post %>
<% end %>
</div>
</div>
<% end %>
<% end %>
</div>
So i understand that when the index view first renders, #tutor would simply be Tutor.all so it renders each individual tutor perfectly.
After trying to filter it though, i start receiving errors. The exact error is NoMethodError in Tutors#indexand the highlighted line is <% unless tutor.admin? %>
profile.rb model
class Profile < ActiveRecord::Base
belongs_to :tutor
scope :fees_to, -> (fees_to) { where("fees_to <= ?", "#{fees_to}") }
end
tutor.rb model
class Tutor < ActiveRecord::Base
has_one :profile, dependent: :destroy
def self.fees_search(n)
#profile = Profile.fees_to(n)
if #profile.empty?
return Tutor.none
else
#profile.each do |y|
y.tutor
end
end
end
end
I get that now my #tutor instance variable has obviously changed. But how do i go about resolving this problem? Should i be rendering a partial instead? Obviously my index action in my controller could be "better" also but i'm quite confused now as to what i should be doing.
Would appreciate any advice! Thank you!
#profile.each do |y|
y.tutor
end
Seems to be a problem. All the other outcomes are a Tutor.something scope, whereas this will return the last tutor only. Change each to map to get an array of Tutors instead.

RoR - helper method that calls fields_for a variable number of times

In my Rails app, I have a form which uses fields_for many times, sometimes nesting calls to fields_for.
Unfortunately, the graphic designer insists that the input fields be ordered in such a way that they are not grouped according to the model to which they pertain. Therefore, I would like to make a helper that would build the fields_for blocks easily for me, but I can't see how to get the helper method to nest a variable number of blocks.
To illustrate, I envision code looking something like the following:
<%= form_for #object do |f| %>
<!-- The helper method should have a similar effect to the following... -->
<%= f.fields_for :assoc_a do |assoc_a_builder| %>
<%= assoc_a_builder.fields_for :assoc_b do |assoc_b_builder| %>
<%= assoc_b_builder.text_field :field_name %>
<% end %>
<% end %>
<!-- ...when given the following data... -->
<%= my_helper [f, :assoc_a, :assoc_b], :text_field, :field_name %>
<% end %>
Any idea how I can accomplish this?
I implemented the following helper method (probably not the best thing around).
Example function call in view:
<%= form_for #object do |builder| %>
<%= nested_form_field 'My label text', [builder, :association_name, :field_name], :collection_select, :my_model_id, MyModel.all, :id, :name %>
<% end %>
Function definition:
# Outputs a label and input, nesting calls to fields_for if 'form' arg is an Array.
# e.g. <%= nested_form_field nil, [builder, :approved_details], :collection_select, :my_model_id, MyModel.all, :id, :name %>
# Make label_text nil for just the input tag, no wrapping elements.
def nested_form_field label_text, form, helper_method, field, *args_for_helper_method
if form.is_a? Array
if form.length > 1
form.shift.fields_for form.first do |builder|
form[0] = builder
nested_form_field label_text, form, helper_method, field, *args_for_helper_method
end
else
nested_form_field label_text, form.first, helper_method, field, *args_for_helper_method
end
else
if label_text.nil?
form.send helper_method, field, *args_for_helper_method
else
%Q(<div class=row>
<div class=span4>#{label_text}</div>
#{ form.send helper_method, field, *args_for_helper_method }
</div>).html_safe
end
end
end
<%= simple_form_for(#primer3) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<% #primer3.attributes.each_pair do |name, value| %>
<%= f.input name if value %>
<% end %>
</div>
<div class="form-actions">
<%= f.button :submit %>
</div>
<% end %>

Rails: Update only when checkbox is checked

I want to update a model - only lines where the checkox is clicked and insert a remark
View:
<%= form_tag update_fb_instruction_users_path, :method => :put do %>
<% #user_wishes.each do |u| %>
<%= u.user.name %>
<%= fields_for "instruction[]", u do |f| %>
<%= f.text_field :remark_tl %>
<% end %>
<%= check_box_tag "instruction_user_ids[]", u.id %>
<% end %>
Controller:
def update_fb
params[:instruction_user_ids].each do
#check = InstructionUser.update(params[:instruction].keys, params[:instruction].values).reject { |p| p.errors.empty? }
end
The issue there is that they all have the same name. So whatever value the last one is, that's what it will be in the request params.
It's a bit old, but you might want to check out the railscast here: http://railscasts.com/episodes/73-complex-forms-part-1. The basic idea is to use fields_for on top of each user object. I haven't done it myself before, otherwise i'd write a full solution :).

Edit existing object and add new dependent object in rails 3 (Nested Forms)

Usually I just use something similar to
http://railscasts.com/episodes/196-nested-model-form-part-1
Now i have an existing User which i am trying to add new cars and annimals. The User has_many cars and annimals and accepts_nested_attributes_for cars and annimals
If i do
= form_for(#user) do |f|
= f.fields_for #user.cars.build do |c|
I get error:
unknown attribute: car
And if i do
= form_for(#user) do |f|
= f.fields_for :cars do |c|
I get a list of all existing cars for that user (when I want to make a new one)
Thannks!
ps: i guess i should add, i'm using simple_form_for and simple_fields_for, it might be a bug with that...
Deep inside the rails api I found:
It’s also possible to specify the instance to be used:
<%= form_for #person do |person_form| %>
...
<% #person.projects.each do |project| %>
<% if project.active? %>
<%= person_form.fields_for :projects, project do |project_fields| %>
Name: <%= project_fields.text_field :name %>
<% end %>
<% end %>
<% end %>
<% end %>
So in my case, i guess the syntax would be
= form_for(#user) do |f|
= f.fields_for :cars, #user.cars.build do |c|

Resources