Suppose I have a form like below
<%= form_for #uni, :html => {:multipart => true, :honeypot => true} do |uni_form| %>
<% 3.times { #uni.app.build } %>
<%= uni_form.fields_for :apps do |builder| %>
<%= render 'app', uni_form: builder %>
<% end %>
<% end %>
and my app partial is
<div>
<%= uni_form.label :uni_id, "University" %>
<%= uni_form.collection_select :uni_id, #unis, :id, :name, {:include_blank => true} %>
</div>
Now I want the first form code without the loop. Something like this
<%= form_for #uni, :html => {:multipart => true, :honeypot => true} do |uni_form| %>
<% 3.times { #uni.app.build } %>
<%= render 'app', uni_form: builder %>
<%= render 'app', uni_form: builder %>
<%= render 'app', uni_form: builder %>
<% end %>
How can I do this?
Firstly, don't build your associated objects in your view - do it in your controller:
#app/controllers/unis_controller.rb
class UnisConstroller < ApplicationController
def new
#uni = Uni.new
3.times do
#uni.apps.build
end
end
end
Secondly, the fields_for method is your friend here.
You'll gain what you need by using the following:
#app/views/unis/new.html.erb
<%= form_for #uni, :html => {:multipart => true, :honeypot => true} do |uni_form| %>
<%= uni_form.fields_for :apps do |builder| %>
<%= builder.label :uni_id, "University" %>
<%= builder.collection_select :uni_id, #unis, :id, :name, {:include_blank => true} %>
<% end %>
<% end %>
fields_for takes your model's associated objects and automatically creates the fields you need. There is literally no need to "loop" - fields_for does it for you... if you set it up correctly.
The problem you have is you're building your associated objects at runtime, which is not only inefficient & against convention, but I think it will prevent the form_for from recognizing them (which is what allows fields_for to loop through them).
The above code should fix this for you.
Related
I'm creating a form where a user can input their favorite food. I want the form to work for both an existing favorite food, and a new favorite food.
I thought I could just switch what object the form is being created for like this:
<% if #user.favorite_food %>
<%= form_for #user.favorite_food, :html => { class: :form } do |f| %>
<% else %>
<%= form_for :favorite_food, :url => :favorite_food, :html => { class: :form } do |f| %>
<% end %>
However I get an error syntax error, unexpected keyword_else, expecting keyword_end.
I agree with Mayank comment your form should be like this
<% if #user.favorite_food %>
<%= form_for #user.favorite_food, :html => { class: :form } do |f| %>
<% end %>
<% else %>
<%= form_for :favorite_food, :url => :favorite_food, :html => { class: :form } do |f| %>
<% end %>
<% end %>
You would better use a nested form for this and let the UsersController take care of the update:
<%= form_for #user, :html => { class: :form } do |f| %>
<%= f.fields_for :favourite_food do |food_form| %>
<% # your favorite_food inputs go here %>
<% end %>
<% end %>
In your User class you have to have accepts_nested_attributes_for:
class User
accepts_nested_attributes_for :favorite_food
...
end
In your UserController you need to make sure to permit the favorite_food_attribures on your strong parametes:
params.require(:person).permit(favorite_food_attribures: []) #do not delete the existing parameters that are already there.
You could check this answer for more info.
Hope this can help you.
I am using cocoon for dynamic/nested field/forms.
However, I cannot pass the index variable inside the partial file.
Here is what i have in my _form.html.erb:
<% #project_procurement_management_plan.items.each_with_index do |item, index | %>
<%= f.fields_for :items, item, :child_index => index do |builder| %>
<%= render 'item_fields', :f => builder, :g=>index %>
<% end %>
<% end %>
<div>
<%=link_to_add_association 'Add Item', f, :items, class:"btn btn-success totheleft" %>
</div>
And in my _item_fields.html.erb:
<%= f.select :category_id, Category.all.map{ |c| [c.code, c.id] }, {:prompt=>""},{class:"cat-code #{g}",required:true} %>
It says:
undefined local variable or method `g' for #<#:0x007f82dadeacd8>
It is pretty clear that the variable g as index cannot be read in my partial.
Are there any workarounds to properly pass the index variable to fields_for and into my rendering.
Thank you.
You don't have to iterate over the items yourself! fields_for will do that for you.
Remove the loop <% #project_procurement_management_plan.items.each_with_index do |item, index | %>.
Do you still need the index now?
Please consider using "locals"
<%= render :partial => 'item_fields', :locals => { :g => index, :f => builder } %>
I solved something like this (in haml) by setting a #parameter in mine like so:
questions.html.haml
- #answer_index = 0
.answers
= f.fields_for :answers
= f.link_to_add "Add an answer", :answers
answers.html.haml
.answer
.field
= f.label :answer, "Answer #{('A'..'Z').to_a[#answer_index]}"
= f.radio_button :correct , 1, {onclick: "check_answer(this);"}
= f.text_field :answer
%span.remove
= f.link_to_remove "remove"
- #answer_index += 1
Not sure if you can pass an # variable like #answer_index
So for you it might look something like (but not exactly, I'm sure):
_form.html.erb
<%= #item_index = 0 %>
<%= f.fields_for :items do |builder| %>
<%= render 'item_fields', :f => builder %>
<%= #item_index += 1 %>
<% end %>
<% end %>
<div>
<%=link_to_add_association 'Add Item', f, :items, class:"btn btn-success totheleft" %>
</div>
_item_fields.html.erb
<%= f.select :category_id, Category.all.map{ |c| [c.code, c.id] }, {:prompt=>""},{class:"cat-code #{#item_index}",required:true} %>
I am creating list of forms say
Questions form
Answers form
Hints form
All these have different controller and view, question_controller , answers_controller, hints_controller.
Now I need to fetch all these views in tabbed UI in home page (say home_controller , home#index)
I tried render : partial ,render :template also with locals , I can't achieve.
It can be easily done by moving all the object to same controller ( home_controller , but i am not sure about this approach , since it will make home controller too complicated to manage ) , but I need to keep this in separate controllers (question_controller , answers_controller, hints_controller) and render it to same page. I am using client side validation, simple form gems.
Below is my question controller
class QuestionsController < ApplicationController
def index
#question = Question.new
#question_status = []
#question_mode = []
#question_type = []
#question_lookups = Lookup.where({:lookup_for => "question"})
#question_lookups.each do |lk|
case lk.lookup_type
when 'mode'
#question_mode << lk
when 'status'
#question_status << lk
else
#question_type << lk
end
end
#caa = Questioncaa.new
end
end
Question View ( with Simple form )
<%= simple_form_for #question, :validate => true do |q| %>
<%= q.input :question_info, :as => :ckeditor, :input_html => { :toolbar => 'Easy', :width => 750 } %>
<%= q.input :question_source %>
<%= q.input :is_mobile %>
<%= q.input :is_similar_question %>
<%= q.input :is_boss_question %>
<%= q.input :is_racing_question %>
<%= q.input :is_speed_question %>
<%= q.input :difficulty_level %>
<%= q.input :ideal_time %>
<%= q.input :lookups, :collection => #question_mode, :value_method => :id, :label_method => :lookup_value,:prompt => "Choose Mode", :label => :QuestionMode %>
<%= q.input :lookups, :collection => #question_status, :value_method => :id, :label_method => :lookup_value,:prompt => "Choose Status", :label => :QuestionStatus %>
<%= q.input :lookups, :collection => #question_type, :value_method => :id, :label_method => :lookup_value,:prompt => "Choose Type", :label => :QuestionType %>
<%= simple_fields_for #caa do |c| %>
<%= c.input :needs_hints %>
<%= c.input :needs_video_solution %>
<%= c.input :needs_tips_tricks %>
<%= c.input :needs_formulae %>
<%= c.input :needs_key_concepts %>
<% end %>
<%= q.button :submit %>
<% end %>
Home View
<div class="tab-content">
<div class="tab-pane active" id="learning_map">
<!-- I need to acheive this -->
<%= render :template => "learning_map/index" %>
</div>
<div class="tab-pane" id="questions">
<!-- I need to acheive this -->
<%= render :template => "questions/index", :collection => #question_mode %>
</div>
<div class="tab-pane" id="answers">.
<!-- I need to acheive this -->
<%= render :templates => "answers/index" %>
</div>
</div>
Pls advice me , it will be very helpful. Thanks for reading this.
You should change logic of your templates a little (here is example for 'question' view):
1) split your question template into 2 files:
- header with form declaration of simple_form_for
- _form.html.erb file with <%= fields_for #question do |q| %> and list of your questions fields like <%= q.input %>
2) add <%= render :partial => 'form' %> in your header file
3) use <%= render :partial => 'question\form' %> in your home view template
4) dont forget to initialize #question variable in home_controller.
This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
Passing only two variables between controller and view - best practice?
There is my action:
def list
#codes = Code.order("created_at")
#languages = Language.order('name').collect {|l| [l.name, l.coderay]}
end
There is my view(I removed some lines):
<% #codes.each do |code| %>
<div class="code">
<%= link_to code.title, :action => 'show', :id => code.id %>
<% if code.author %>
#<%= code.author %>
<% end %>
</div>
<% end %>
<%= render :partial => 'shared/error_messages', :locals => {:object => #code} %>
<%= form_for :code, :url => {:action => 'create' }, :html => {:multipart => true} do |f| %>
<%= f.text_field :title %><br />
<%= f.text_area :content %><br>
<%= f.select(:language, #languages, {:selected => 'text'}) %>
<%= f.text_field :author %><br>
<%= f.submit "Submit code" %>
<% end %>
There are 3 variables in it: #codes(list of posts), #code(current post, used in another action) and #languages.
My IDE writes:
At most two instance variables should be shared between controller and
view
This inspection warns if there are more than two instance
variables shared between a controller and a view. A controller should
only manage one instance variable, plus a second one for the
current_user variable.
Usually I share more variables between Controller and View(in PHP), sometimes 10+.
How it's done in Rails?
You can save an instance var by making languages a helper:
def languages
Language.order('name').collect {|l| [l.name, l.coderay]}
end
Its a guideline some developers follow some of the time.
But I would read up on Rails Routing a bit more. Understanding how Rails routing works should give you a better idea on how to structure your code.
http://guides.rubyonrails.org/routing.html
I modified your code a bit, not tested. But hopefully gives you some good ideas.
Controller:
def new
#code = Code.new
#codes = Code.order("created_at")
end
def create
#code = Code.new(params[:code])
if #code.save?
# Do your thing.
else
# render your :new action passing your #code variable
end
end
View:
<% #codes.each do |code| %>
<div class="code">
# Use Rails Routing - In console, type rake routes to get list of routes.
<%= link_to code.title, code_path(code.id) %> # example.
<% if code.author %>
<%= code.author %>
<% end %>
</div>
<% end %>
<%= render 'shared/error_messages', :object => #code %>
<%= form_for #code, :html => {:multipart => true} do |f| %>
<%= f.text_field :title %><br />
<%= f.text_area :content %><br>
# language_list = helper method.
<%= f.select(:language, language_list, {:selected => 'text'}) %>
<%= f.text_field :author %><br>
<%= f.submit "Submit code" %>
<% end %>
'shared/subscription' %>
To call this partial view:
<% form_for(:subscription, :url => city_subscriptions_path(#city)) do |form| %>
<%= form.hidden_field :city_id, :value => #city.id %>
<%= form.text_field :email, :size => 30 %>
<%= form.submit "Email Me" %>
<% end %>
Since I am using this partial view on different places, how do I alter the caller so it will pass a hash for the form_for helper? So it would be like this when the helper is called:
<% form_for(:subscription, :url => city_subscriptions_path(#city), :html => {:id => 'main_input' }) do |form| %>
<%= form.hidden_field :city_id, :value => #city.id %>
<%= form.text_field :email, :size => 30 %>
<%= form.submit "Email Me" %>
<% end %>
<%= render :partial => "shared/subscription", :locals => {:foo => "bar", :foofoo => ["bar", "bar"]}
In your partial view, use them:
<%= foo #this outputs "bar" %>
<%= foofoo.to_s %>