I've been hacking around with Rails 3.2.11 for a while, and am trying to do this the 'right' way.
I have three models (Reflection, Skill, Utilization) that relate to each other through has_many: through:
Utilization.rb
class Utilization < ActiveRecord::Base
attr_accessible :reflection, :skill, :used_skill #used_skill is a boolean
belongs_to :reflection
belongs_to :skill
end
Reflection.rb
class Reflection < ActiveRecord::Base
## attributes here ##
has_many :utilizations
has_many :skills, through: :utilizations
accepts_nested_attributes_for :utilizations
accepts_nested_attributes_for :skills
end
Skill.rb
class Skill < ActiveRecord::Base
## attributes here ##
has_many :utilizations
has_many :reflections, through: :utilizations
end
Within the app, skills are already defined. The user action I am trying to support is:
User gets form for new Reflection.
User sees a list of Skills and checks off which ones they have used (Utilization).
User posts to create new Reflection and create the associated Utilization objects.
Here is the new method reflection_controller.rb:
class ReflectionsController < ApplicationController
def new
#reflection = Reflection.new
Skill.all.each do |skill|
#reflection.utilizations.build(skill_id: skill.id, used_skill: false)
end
end
end
And an abbreviated _form.html.erb for Reflections
<%= form_for(#reflection) do |f| %>
<% f.fields_for :utilizations do |builder| %>
<%= builder.label :used_skill %>
<%= builder.check_box :used_skill %>
<%= builder.fields_for :skill do |skill| %>
<%= skill.label :description %>
<%= skill.text_field :description %>
<% end %>
<% end %>
<% end %>
So the problem is that even though there are multiple Skills and I .new the Utilization objects and associate them with the #reflection, they don't show up in the form. I've played with the data structures a little bit, and I can reach the point where in ReflectionController.new #reflection.utilizations contains Utilization objects, it still won't work; when I run #reflection.utilizations.count it returns 0. It looks like the problem is that since none of the objects have an id at that time, it simply will not render out in the form. But my understanding is that one should not create objects during the new method…
Is there something obvious I'm missing? Is there a better way to do this? I've seen examples, include Ryan Bates' Railscast where people just use code like:
def new
#survey = Survey.new
3.times do
question = #survey.questions.build
4.times { question.answers.build }
end
end
and supposedly this works fine.
I really appreciate the help. Trying to figure this out has been driving me crazy. This is my first question on SO, and I'm happy to add any clarifying data or additional code if you think it would help.
You forgot to use =:
<%#### Here ####%>
<%= f.fields_for :utilizations do |builder| %>
<%= builder.label :used_skill %>
<%= builder.check_box :used_skill %>
<%#### and here ####%>
<%= builder.fields_for :skill do |skill| %>
Related
So im working through the Odin Project's "Flight Booker" project. https://www.theodinproject.com/courses/ruby-on-rails/lessons/building-advanced-forms. Which essentially is what it sounds like and im running into a problem with passing nested attributes.
First and foremost the Relevant Models:
class Booking < ApplicationRecord
belongs_to :passenger
belongs_to :flight
accepts_nested_attributes_for :passenger
end
class Flight < ApplicationRecord
has_many :bookings, dependent: :destroy
has_many :passengers, through: :bookings
belongs_to :to_airport, class_name: 'Airport', foreign_key: 'origin_id'
belongs_to :from_airport, class_name: 'Airport', foreign_key: 'destination_id'
end
class Passenger < ApplicationRecord
has_many :bookings, dependent: :destroy
has_many :flights, through: :bookings
end
The passenger schema just contains an email and name for right now. But the problem is when I pass the information to the "booking" controller. Here is my "New" form for booking.
<%= form_for #booking do |f| %>
<%= f.hidden_field :flight_id, value: params[:booking][:flight_num] %>
<%= f.hidden_field :passengers_num, value: params[:booking][:passengers_num] %>
<% params[:booking][:passengers_num].to_i.times do |passenger| %>
<%= fields_for :passenger do |passenger| %>
<%= passenger.label :name, 'Name', class: "Label" %>
<%= passenger.text_field :name %>
<%= passenger.label :email, 'email', class: "Label" %>
<%= passenger.email_field :email %>
<% end %>
<% end %>
<%= f.submit "Book Flight" %>
<% end %>
(Ignore the hidden fields for now, they are passed from the "Flights" search page and Im getting those just fine.)
So I am getting the multiple forms (name and email fields) but when I "Submit" I am only getting parameters for the last field sets. (So if there are 3 sets of name/email fields, I only get parameters for the last one).
It's possible im not understanding the fields_for however as I can't find a ton of good examples.
Thanks!
There could be many issues with your implementation...I'll layout a few...
Move <% params[:booking][:passengers_num].to_i.times do |passenger| %> logic into the new action of your bookings controller...ie
def new
#booking = Booking.new
3.times { #booking.passengers.new } # or whatever your logic is to display x amount of passenger fields
end
Make sure that in your bookings controller you are permitting the nested attributes like this...
params.require(:booking).permit(passengers_attributes: [:name, :email])
As far as the form, you'll need to treat it like a form within a form (makes sense...nested attributes created from a nested form!) and use the block variable...like this
<ul>
<%= f.fields_for :passengers do |passenger_form| %>
<li>
<%= passenger_form.label :name
<%= passenger_form.text_field :name %>
</li>
<!-- other permitted fields -->
<% end %>
</ul>
What I try to do is a simple form for Plate where you can choose which Ingredients you want. The number and name of Ingredients may vary by day.
Every time a new Plate is created, it created 4 new Choices with plate_id and ingredient_id and a boolean chosen.
plate_id is from the new plate create and ingredient_id should be the id of the already existing ingredient in my database, and chosen it's if the ingredient should be in the plate.
Here is my classes Plate, Choice and Ingredient :
class Plate < ActiveRecord::Base
has_many :choices
has_many :ingredients, through: :choices
accepts_nested_attributes_for :choices
end
class Choice < ActiveRecord::Base
belongs_to :plate
belongs_to :ingredient
accepts_nested_attributes_for :ingredient
end
class Ingredient < ActiveRecord::Base
has_many :choices
has_many :plates, through: :choices
end
And my Plate nested form looks like this :
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<%= f.fields_for :choices do |fc| %>
<%= fc.check_box :chosen %>
<%= fc.fields_for :ingredient do |fi| %>
<%= fi.text_field(:name)%> <br />
<% end %>
<% end %>
And finally my Plate controller :
def new
#plate = Plate.new
#choice1 = #plate.choices.build
#choice2 = #plate.choices.build
#choice3 = #plate.choices.build
#choice4 = #plate.choices.build
#ingredient1 = Ingredient.find_by_name('peach')
#ingredient2 = Ingredient.find_by_name('banana')
#ingredient3 = Ingredient.find_by_name('pear')
#ingredient4 = Ingredient.find_by_name('apple')
#choice1.ingredient_id = #ingredient1.id
#choice2.ingredient_id = #ingredient2.id
#choice3.ingredient_id = #ingredient3.id
#choice4.ingredient_id = #ingredient4.id
end
def plate_params
params.require(:plate).permit(:name, choices_attributes: [:chosen, ingredient_attributes: [ :name]])
end
My problem is that when I create a new plate, it create new ingredients with same name as the chosen one (but with different id of course) and Choices had the ingredient_id of the new ingredients created.
I tried to add the :id in the nested attributes : params.require(:plate).permit(:name, choices_attributes: [:chosen, ingredient_attributes: [:id, :name]])
Bu when I do that, I go an error :
Couldn't find Ingredient with ID=1 for Choice with ID=
I searched for answers but couldn't find any, I guess I don't know Rails params, form and nested attributes enough to understand where is the problem.
Thanks for your help !
ps : It's my first question on Stack Overflow, please let my know if anything is wrong with my question
You don't need accepted_nested_parameters_for :ingredient since the form isn't designed to let you create ingredients or edit ingredients.
Better that you just use collection_selectto select an existing ingredient as part of the choice record (that is, just save the ingredient_id).
<%= f.fields_for :choices do |fc| %>
<%= fc.check_box :chosen %>
<%= fc.collection_select(:ingredient_id, Ingredient.all, :id, :name, prompt: true) %>
<% end %>
And your attributes should then be...
params.require(:plate).permit(:name, choices_attributes: [:chosen, :ingredient_id])
If you just want to show the ingredient but not allow the user to change which ingredient, you can do
<%= f.fields_for :choices do |fc| %>
<%= fc.check_box :chosen %>
<%= fc.hidden_field :ingredient_id %>
<%= Ingredient.find(fc.object.ingredient_id).name %>
<% end %>
You're finding the ingredient_id in the object encompassed by the form object fc and using that id to access the Ingredient, and retrieving the name attribute.
Also notice the hidden field for the :ingredient_id ... to make sure it's returned in the attribute hash.
I have a User, a Wiki, and a Collaborator model:
class User < ActiveRecord::Base
has_many :wikis
has_many :collaborators
end
class Wiki < ActiveRecord::Base
has_many :wikis
has_many :collaborators
end
class Collaborator < ActiveRecord::Base
belongs_to :user
belongs_to :wiki
end
When I am editing a Wiki's collaborators I would like the form to look something like this:
My problem is that I cannot figure out how to construct the form. I thought the following would work but <% form_for :collaborator do |f|%> doesn't result in anything being included in the resulting page.
<% form_for :collaborator do |f|%>
<% possible_collaborators.each do |user| %>
<%= check_box_tag 'wiki[collaborator_ids][]', user.id, wiki.collaborators.include?(user) %>
<%= user.name %>
<br />
<% end %>
<%= f.submit %>
<% end %>
As you didn't post your controller code I can't be entierly sure what you are trying to accomplish, but I think you are missing out on the accepts_nested_attributes_for (docs, tutorial with controller code).
Furthermore I can only recommend you the use simple_forms or formtastic as those gems do a good job when it comes to complex forms and help you a lot with the basic use cases.
I am making a goal tracking app. Right now outcome, purpose, action, priority, resources, and direction are all things which are part of Outcome in the database. However, I want to make purpose and action their own model objects. What I am confused about is how do I submit Outcome, Purpose, and Action, which will be 3 separate model objects, in a single HTTP request?
Should I just use multiple strong params in my controller?
app/view/outcomes/new.html.erb
You need to have model associations of outcomes with purpose and action.
Then you will need to create nested form. So that outform form can wrap purpose and action model attributes.
As you want to have different models for actions and purposes, I'm assuming outcome can has_many purposes and has_many actions. As per this type of association, below is the code you should have.
Your form will become something like:
<%= form_for #outcome do |f| %>
<%= f.label :outcome, "Outcome" %>
<%= f.text_area :outcome %>
<%= f.fields_for :purpose, #outcome.purpose.build do |p| %>
<%= p.text_area :desc, label: "Purpose" %>
<% end %>
<%= f.fields_for :action, #outcome.action.build do |p| %>
<%= p.text_area :desc, label: "Action" %>
<% end %>
<%= f.submit "submit" %>
<% end %>
Models:
# outcome.rb
has_many :purposes, :dependent => :destroy
has_many :actions, :dependent => :destroy
accepts_nested_attributes_of :purposes, :actions
-----------------------------------------
# purpose.rb
belongs_to :outcome
-----------------------------------------
# action.rb
belongs_to :outcome
Controller:
# outcomes_controller.rb
def outcome_params
params.require(:outcome).permit(:outcome, purpose_attributes:[:desc], action_attributes: [:desc])
end
SUGGESTION: You should rename your action model name to avoid unwanted conflicts with rails keyword action.
This may help you
Nestd Attributes
If the objects are associated (as below), you'll be best using the accepts_nested_attributes_for method:
#app/models/outcome.rb
Class Outcome < ActiveRecord::Base
has_many :purposes
has_many :actions
accepts_nested_attributes_for :purposes, :actions
end
#app/models/purpose.rb
Class Purpose < ActiveRecord::Base
belongs_to :outcome
end
#app/models/action.rb
Class Action < ActiveRecord::Base
belongs_to :outcome
end
accepts_nested_attributes_for means you'll be able to send the associated objects through the Outcome model - meaning you can send them all in a single HTTP request
You have to remember the way Rails is set up (MVC pattern), meaning if you send a single request; any further model objects you have will be able to be stored too.
Here's how you can set it up:
#app/controllers/outcomes_controller.rb
Class OutcomesController < ApplicationController
def new
#outcome = Outcome.new
#outcome.purposes.build
#outcoe.actions.build
end
def create
#outcome = Outcome.new(outcome_params)
#outcome.save
end
private
def outcome_params
params.require(:outcome).permit(:outcome, purpose_attributes:[:purpose], action_attributes: [:action])
end
end
Which will give you the ability to use this form:
#app/views/outcomes/new.html.erb
<%= form_for #outcome do |f| %>
<%= f.label :outcome %>
<%= f.text_area :outcome %>
<%= f.fields_for :purposes do |p| %>
<%= p.text_area :purpose %>
<% end %>
<%= f.fields_for :actions do |a| %>
<%= a.text_area :action %>
<% end %>
<%= f.submit %>
<% end %>
--
Recommendation
From the looks of it, I'd recommend you'll be able to keep all of these details in a single model - storing in multiple models seems overkill
When I use accepts_nested_attributes_for the corresponding fields no longer show in my view.
class Survey < ActiveRecord::Base
has_many :questions
accepts_nested_attributes_for :questions
end
class Question < ActiveRecord::Base
belongs_to :survey
end
Then in my view:
<%= form_for #survey do |f| %>
<%= f.fields_for :questions do |question_fields| %>
<%= question_fields.text_area :text %>
<% end %>
<% end %>
If I remove accepts_nested_attributes_for then the text_area shows, but if I keep it...nothing gets renders.
I'm running Rails 3.0.3
Did you build the questions , in the controller ?
Something like
#survey.questions.build
This builds one related question, so only one text area will show up. run it in a loop like
2.times { #survey.questions.build }
It will appear 2 times.
Do you want to create new questions or are you editing them? You might want to try something like this if you are creating a new question for this survey:
<= f.fields_for #survey.questions.build do |question_fields| %>