I'm trying to associate several dinners to a meal with a has_many: through relationship when the user hits "save". My question is not with the mechanics of has_many: through. I know how to set that up and I have it working in the Rails console, but I just don't know how to set up the view to associate several records at once.
I have models set up like this:
class Dinner < ApplicationRecord
has_one :user
has_many :meals
has_many :meal_plans, through: :meals
end
class MealPlan < ApplicationRecord
belongs_to :user
has_many :meals
has_many :dinners, through: :meals
end
class Meal < ApplicationRecord
belongs_to :dinner
belongs_to :meal_plan
end
With a meal plan controller:
def create
#meal_plan = current_user.meal_plans.build(meal_plan_params)
respond_to do |format|
if #meal_plan.save
format.html { redirect_to root_path, notice: 'Dinner was successfully created.' }
end
end
end
private
def meal_plan_params
params.require(:meal_plan).permit(dinners: [])
end
My question is about the view, in the new view, I create a #meal_plan and I want to pass several different dinners into the meal plan. Below the value: #dinners is just 7 random dinners pulled from the Dinners table.
<%= form_with model: #meal_plan do |f| %>
<%= f.hidden_field(:dinners, value: #dinners)%>
<%= f.submit 'Save'%>
<% end %>
Again, I've gotten this to work by running something like `usr.meal_plans.create(dinners: [d1, d2])`` in the Rails console but I don't
You can use the form option helpers to generate selects or checkboxes:
<%= form_with model: #meal_plan do |f| %>
<%= f.collection_select :dinner_ids, Dinner.all, :id, :name, multiple: true %>
<%= f.collection_checkboxes :dinner_ids, Dinner.all, :id, :name %>
<%= f.submit 'Save'%>
<% end %>
_ids is a special setter / getter generated by ActiveRecord for has many assocations. You pass an array of ids and AR will take care of inserting/removing the join table rows (meals).
You also need to change the name in your params whitelist:
def meal_plan_params
params.require(:meal_plan).permit(dinner_ids: [])
end
If you want to to pass an array through hidden inputs you can do it like so:
<% #dinners.each do |dinner| >
<%= hidden_field_tag "meal_plans[dinner_ids][]", dinner.id %>
<% end %>
See Pass arrays & objects via querystring the Rack/Rails way for an explaination of how this works.
Related
any help would be most appreciated, I am rather new to Rails.
I have two models a Shopping List and a Product. I'd like to save/update multiple products to a shopping list at a time.
The suggested changes are not updating the models. I've been googling and is "attr_accessor" or find_or_create_by the answer(s)?
Attempt 1 - Existing code
Error
> unknown attribute 'products_attributes' for Product.
Request
Parameters:
{"_method"=>"patch",
"authenticity_token"=>"3BgTQth38d5ykd3EHiuV1hkUqBZaTmedaJai3p9AR1N2bPlHraVANaxxe5lQYaVcWNoydA3Hb3ooMZxx15YnOQ==",
"list"=>
{"products_attributes"=>
{"0"=>{"title"=>"ten", "id"=>"12"},
"1"=>{"title"=>"two", "id"=>"13"},
"2"=>{"title"=>"three", "id"=>"14"},
"3"=>{"title"=>"four", "id"=>"15"},
"4"=>{"title"=>"five", "id"=>"16"},
"5"=>{"title"=>""},
"6"=>{"title"=>""},
"7"=>{"title"=>""},
"8"=>{"title"=>""},
"9"=>{"title"=>""},
"10"=>{"title"=>""}}},
"commit"=>"Save Products",
"id"=>"7"}
Attempt 2 - no errors the page reloads and none of the expected fields are updated. In earnest, I am Googling around and copying and pasting code snippets in the vain hope of unlocking the right combo.
Added to Products mode
class Product < ApplicationRecord
attr_accessor :products_attributes
belongs_to :list, optional: true
end
<%= content_tag(:h1, 'Add Products To This List') %>
<%= form_for(#list) do |f| %>
<%= f.fields_for :products do |pf| %>
<%= pf.text_field :title %><br>
<% end %>
<p>
<%= submit_tag "Save Products" %>
</p>
<% end %>
<%= link_to "Back To List", lists_path %>
list controller
def update
#render plain: params[:list].inspect
#list = List.find(params[:id])
if #list.products.update(params.require(:list).permit(:id, products_attributes: [:id, :title]))
redirect_to list_path(#list)
else
render 'show'
end
list model
class List < ApplicationRecord
has_many :products
accepts_nested_attributes_for :products
end
original do nothing - product model
class Product < ApplicationRecord
belongs_to :list, optional: true
end
If you just want a user to be able to select products and place them on a list you want a many to many association:
class List < ApplicationRecord
has_many :list_items
has_many :products, through: :list_products
end
class ListItem < ApplicationRecord
belongs_to :list
belongs_to :product
end
class Product < ApplicationRecord
has_many :list_items
has_many :lists, through: :list_products
end
This avoids creating vast numbers of duplicates on the products table and is known as normalization.
You can then select existing products by simply using a select:
<%= form_for(#list) do |f| %>
<%= f.label :product_ids %>
<%= f.collection_select(:product_ids, Product.all, :name, :id) %>
# ...
<% end %>
Note that this has nothing to with nested routes or nested attributes. Its just a select that uses the product_ids setter that's created by the association. This form will still submit to /lists or /lists/:id
You can whitelist an array of ids by:
def list_params
params.require(:list)
.permit(:foo, :bar, product_ids: [])
end
To add create/update/delete a bunch of nested records in one form you can use accepts_nested_attributes_for together with fields_for:
class List < ApplicationRecord
has_many :list_items
has_many :products, through: :list_products
accepts_nested_attributes_for :products
end
<%= form_for(#list) do |f| %>
<%= form.fields_for :products do |pf| %>
<%= pf.label :title %><br>
<%= pf.text_field :title %>
<% end %>
# ...
<% end %>
Of course fields_for won't show anything if you don't seed the association with records. That's where that loop that you completely misplaced comes in.
class ListsController < ApplicationController
# ...
def new
#list = List.new
5.times { #list.products.new } # seeds the form
end
def edit
#list = List.find(params[:id])
5.times { #list.products.new } # seeds the form
end
# ...
def update
#list = List.find(params[:id])
if #list.update(list_params)
redirect_to #list
else
render :new
end
end
private
def list_params
params.require(:list)
.permit(
:foo, :bar,
product_ids: [],
products_attrbutes: [ :title ]
)
end
end
Required reading:
Rails Guides: Nested forms
ActiveRecord::NestedAttributes
fields_for
I'm trying to do a nested form for a has_many :through association using Simple Form, and I can't figure out how to get around this error: ArgumentError in Variants#edit -- Association cannot be used in forms not associated with an object.
Here's what I'm trying to accomplish. I have a "Product Variant" model (called Variant). Each variant can have many parts (Part model) through a "Parts List Item" (PartsListItem) join model. Each variant should be able to have parts assigned to it in different quantities.
For instance, a guitar strap might have a part called "Backing Fabric" that has a quantity of 1. Meaning that the Guitar Strap variant needs 1 of the "Backing Fabric" part to be assembled. But the same variant might also have another part such as "Rivet" that has a quantity of 4. (As in 4 rivets are required to make this product variant.) After using the Variant form to add all the parts in various quantities to the variant, I'd like to show all of the parts with quantities on the variants#show page.
Here is the relevant code from my models:
class Variant < ApplicationRecord
has_many :parts_list_items, dependent: :destroy
has_many :parts, through: :parts_list_items, dependent: :nullify
accepts_nested_attributes_for :parts
end
class PartsListItem < ApplicationRecord
belongs_to :variant
belongs_to :part
end
class Part < ApplicationRecord
has_many :parts_list_items, dependent: :destroy
has_many :variants, through: :parts_list_items, dependent: :nullify
end
And my VariantsController:
class VariantsController < ApplicationController
def update
respond_to do |format|
if #variant.update(variant_params)
format.html { redirect_to #variant, notice: 'Variant was successfully updated.' }
else
format.html { render :edit }
end
end
end
private
def variant_params
params.require(:variant).permit(:account_id, :product_id, :sku,
:choice_ids => [], :part_ids => [])
end
end
And my form (views/variants/_edit_form.html.erb):
<%= simple_form_for #variant do |f| %>
<%= f.simple_fields_for :parts_list_items do |item| %>
<%= item.input_field :quantity %>
<%= item.association :parts %>
<% end %>
<% end %>
Note that this works just fine:
<%= simple_form_for #variant do |f| %>
<%= f.association :parts, as: :check_boxes %>
<% end %>
So, it works to associate parts directly to the variant through the PartsListItem join model. The trouble begins when I start trying to add the quantity for each associated part.
What am I doing wrong with this nested form? Is there a problem with my controllers or associations?
Do I need to create an additional model called PartsList that has_many :parts_list_items with additional associations? That seems like an extra step and that there should be a way to put the :quantity on the PartsListItem model.
I think you need to change parts to part
<%= simple_form_for #variant do |f| %>
<%= f.simple_fields_for :parts_list_items do |item| %>
<%= item.input_field :quantity %>
<%= item.association :parts %> <!-- HERE -->
<% end %>
<% end %>
I have two models, Event and Vip, each associated with HABTM relationship.
I have a events_vips table with columns for vip_id, and event_id
My new event form:
<%= form_for [#organization, #event] do |f| %>
<p>
<%= f.label :when %>
<%= f.date_select :when %>
</p>
<p>
<%= f.label :name %>
<%= f.text_field :name %>
</p>
<p>
<%= f.label :vip %>
<%= f.select :vip_id, options_for_select(#organization.vips.all.map {|v| [v.name, v.id]}) %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
My events controller:
def new
#organization = Organization.find(params[:organization_id])
#event = #organization.events.new
end
def create
#organization = Organization.find(params[:organization_id])
#event = #organization.events.build(event_params)
if #event.save
redirect_to organization_path(#organization)
else
render 'new'
end
end
I want my events_vips table to get populated with a new event/vip relationship when I create an event. Or is that the wrong way to do it?
Edit:
My models looks like:
class Vip < ActiveRecord::Base
belongs_to :organization
has_and_belongs_to_many :events
end
class Event < ActiveRecord::Base
belongs_to :organization
has_and_belongs_to_many :vips
end
I would use has_many :events, :through => :some_table_name and has_many :organizations, :through => :some_table_name rather than a habtm. The reason for this is that, with the habtm, you are limited in several ways. First, the join table name is restricted to events_organizations because of the alphabetical nature of the habtm construct, and with has_many :through you can use an arbitrary table name that can be more descriptive.
More importantly, you cannot store any other information about the relationship in a habtm join table. In the has_many :through, for example, you could store the table number, arrival time, dock scheduling, or other information that applies to a specific organization for a specific event in the join table, where it belongs. This way, the join table stores not only the relationship, but information about the nature of that relationship.
In either case, the answer to your question is to use accepts_nested_attributes_for :your_join_table_model in the model that you will be manipulating the data from (which could be both).
Here is part 1 of a railscast dedicated to the subject.
Here is a link to the HABTM checkboxes railscast, if you wanted to go that route.
You need to find the vip from your params and it to the event#vips.
def create
#organization = Organization.find(params[:organization_id])
#event = #organization.events.build(event_params)
if #event.save
vip = Vip.find(event_params[:vip_id])
#event.vips << vip # this will create the association
redirect_to organization_path(#organization)
else
render 'new'
end
end
now its always a good idea to add validation checks, for instance insuring that a vip param came in the form, but the above is how to actually forge the association. once the event is saved, its vip association can be made.
The problem that I have here is that I have a nested form that won't save to the database and I'm suspecting it's because the proper attributes aren't being passed into the form prior to being saved. Right now I'm trying to pass these values through hidden fields but I'm thinking there's probably a more "Railsy" way to do this. This is the form that I have created to do this:
<%= form_for #topic do |f| %>
<%= render "shared/error_messages", object: f.object %>
<%= f.fields_for :subject do |s| %>
<%= s.label :name, "Subject" %>
<%= collection_select :subject, :id, Subject.all, :id, :name, {prompt:"Select a subject"} %>
<% end %>
<%= f.label :name, "Topic" %>
<%= f.text_field :name %>
<div class="text-center"><%= f.submit class: "button radius" %></div>
<% end %>
This form generates a params hash that looks like this:
{"utf8"=>"✓", "authenticity_token"=>"PdxVyZa3X7Sc6mjjQy1at/Ri7NpR4IPUzW09Fs8I710=", "subject"=>{"id"=>"5"}, "topic"=>{"name"=>"Ruby"}, "commit"=>"Create Topic", "action"=>"create", "controller"=>"topics"}
This my model for user.rb:
class User < ActiveRecord::Base
has_many :topics
has_many :subjects, through: :topics
end
In my subject.rb file:
class Subject < ActiveRecord::Base
has_many :topics
has_many :users, through: :topics, dependent: :destroy
validates :name, presence: true
end
In my topic.rb file:
class Topic < ActiveRecord::Base
belongs_to :subject
belongs_to :user
accepts_nested_attributes_for :subject
validates :name, presence: true
end
class TopicsController < ApplicationController
before_filter :require_login
def new
#topic = Topic.new
#topic.build_subject
end
def create
#topic = Topic.new(topic_params)
#topic.user_id = current_user.id
#topic.subject_id = params[:subject][:id]
if #topic.save
flash[:success] = "Success!"
render :new
else
flash[:error] = "Error!"
render :new
end
end
private
def topic_params
params.require(:topic).permit(:name,:subject_id,:user_id, subjects_attributes: [:name])
end
end
So I'm getting closer to having a successful form submission! I placed the method accepts_nested_attributes_for in the join model, which in this case is in topic.rb. I don't really know why this works but I'm thinking it allows Rails to properly set the ":user_id" and ":subject_id" compared to placing accepts_nested_attributes_for on a model containing the "has_many through" relationship. I saw it on this post btw: http://makandracards.com/makandra/1346-popular-mistakes-when-using-nested-forms
NOW, I still have a problem where the ":subject_id" isn't being properly saved into the database. Would I have to pass in a hidden field to do this or would I have to do something else like nest my routes?
Wow that took forever to figure this one out. Since I have a has_many through relationship and I'm trying to created a nested form involving one of these models the problem I was having was I was placing the "accepts_nested_attributes_for" in the wrong model I was placing it in the has_many through model in the subject.rb file when it should have been placed in the model responsible for the join between two tables.
Also I made a super idiotic mistake on this line when I was trying to save the ":subject_id". I was writing this: #topic.subject_id = params[:subject_id][:id] instead of something like this:
#topic.subject_id = params[:subject][:id]
It was a really dumb mistake (probably because I was copying a pasting code from another controller haha)
Anyways I hope others can learn from my mistake if they ever want to do a nested form on models with a "has_many through" relationship, in certain cases the "accepts_nested_attributes_for" method will go on the JOIN table and NOT on the model with the "has_many through" relationship
I'm trying to build a rather complex nested form in rails and am stuck.
Basically, I have three models - Applicant, DataPoint, ApplicantDataPointValue .
The user can create a new DataPoint, give it a name ("gender" etc.) select it's type ("string","integer" etc.). The type determines what column the data will eventually be saved in in the ApplicantDataPointValue table.
I then want the user, when they're creating a new Applicant, to be able to add a value for each DataPoint into the ApplicantDataPointValue table
My models look like the following:
Applicant:
class Applicant < ActiveRecord::Base
has_many :applicant_data_point_values, dependent: :destroy
has_many :data_points, :through => :applicant_data_point_values
accepts_nested_attributes_for :data_points
accepts_nested_attributes_for :applicant_data_point_values
attr_accessible :data_points_attributes, :applicant_data_point_values_attributes
end
DataPoint:
class DataPoint < ActiveRecord::Base
has_many :applicant_data_point_values
has_many :applicants, :through => :applicant_data_point_values
accepts_nested_attributes_for :applicant_data_point_values
end
ApplicantDataPointValue:
class ApplicantDataPointValue < ActiveRecord::Base
belongs_to :data_point
belongs_to :applicant
end
But I'm at a loss to what to do in the 'new' and 'create' sections of my controller or how to construct the form.
Any insight would be greatly appreciated.
From what I understand, the form for the User will also have multiple ApplicantDataPointValue fields. (but that form won't allow creating of new DataPoint fields, right?)
In the controller new action, you'll want to set up your model with associated data point values:
def new
#user = User.new
DataPoint.all.each do |data_point|
applicant_data_point_value = #user.applicant_data_point_values.build
applicant_data_point_value.data_point = data_point
end
end
And then, display a text box for each data point value.
<%= form_for #user do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<% #user.applicant_data_point_values.each do |data_point_value| %>
<%= f.fields_for :applicant_data_point_values, data_point_value do |fields| %>
<%= fields.label :value, data_point_value.data_point.type %>
<%= fields.text_field :value %>
<% end %>
<% end %>
Reference: http://railscasts.com/episodes/196-nested-model-form-part-1