I'm kinda new to ruby on rails, I've been reading documentation on assosiations and I've been having an easy time (and usually a quick google search solves most of my doubts) however recently I'm having problems with a seemingly easy thing to do.
What I'm trying to do is to create an Event, linked to an existing Category.
Event model
class Event < ApplicationRecord
has_many :categorizations
has_many :categories, through: :categorizations
accepts_nested_attributes_for :categorizations
.
.
.
end
Category model
class Category < ApplicationRecord
has_many :categorizations
has_many :events, through: :categorizations
end
Categorization model
class Categorization < ApplicationRecord
belongs_to :event
belongs_to :category
end
Event controller
class EventsController < ApplicationController
def new
#event = Event.new
end
def create
#user = User.find(current_user.id)
#event = #user.events.create(event_params)
if #event.save
redirect_to root_path
else
redirect_to root_path
end
end
private
def event_params
params.require(:event).permit(:name, category_ids:[])
end
Here is the form, which is where I think the problem lies:
<%= form_for #event, :html => {:multipart => true} do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.fields_for :categorizations do |categories_fields|%>
<% categories = [] %>
<% Category.all.each do |category| %>
<% categories << category.name %>
<% end %>
<%= categories_fields.label :category_id, "Category" %>
<%= categories_fields.select ( :category_id, categories) %>
<% end %>
.
.
.
<%= f.submit "Create"%>
<% end %>
I previously populate the Category db with some categories, so what's left to do is to while creating an event, also create a categorization that is linked both to the new event and the chosen Categorization. but the things I've tried don't seem to be working.
Other things seem to be working ok, whenever I try to submit the event all things are populated as expected except the categorization.
As you mentioned that you are new to rails, you'll find this cocoon gem very interesting. You can achieve what you wanted. And the code will cleaner.
I don't have the points to comment, that's why I am giving this as an answer.
Related
Setup
I have a simple many to many relationship between a Submit and an Answer through SubmitAnswer.
Answers are grouped by a Question (in my case each question has three answers) - think of it as a multiple choice quiz.
I have been trying to use SimpleFormFor to make a form which renders a predetermined set of questions, where each question has a predetermined set of answers.
Something like this:
#form
<%= simple_form_for Submit.new, url: "/questionnaire" do |f| %>
<% #questions.each do |question| %>
<%= f.association :answers, collection: question.answers %>
<% end %>
<%= f.submit :done %>
<% end %>
#controller
def create
#submit = Submit.new(submit_params)
#submit.user = current_user
if #submit.save
redirect_to root_path
else
render :new
end
end
def submit_params
params.require(:submit).permit(answer_ids: [])
end
When I submit the form, Rails creates the join table, SubmitAnswers, automatically.
So here is the crux of the matter: Whats the easiest way to re-render the form, errors and all, if not all questions have been answered, ie if #submit.answers.length != #question.length ?
I can add a custom error with errors.add(:answers, 'error here'), but when I re-render, the correctly selected answers arent repopulated, which is suboptimal.
For completions sacke, here are my models:
class Submit < ApplicationRecord
belongs_to :user
has_many :submit_answers
has_many :answers, through: :submit_answers
end
class SubmitAnswer < ApplicationRecord
belongs_to :submit
belongs_to :answer
end
class Answer < ApplicationRecord
has_many :submit_answers
has_many :submits, through: :submit_answers
end
Alright, after some digging we did find the answer to make the form work, albeit with more pain that we anticipated a simple many-to-many should take.
#model
class Submit < ApplicationRecord
belongs_to :user
has_many :submit_answers
has_many :answers, through: :submit_answers
accepts_nested_attributes_for :submit_answers
end
#controller
def new
#submit = Submit.new
#questions.count.times { #submit.submit_answers.build }
end
def create
#submit = Submit.new(submit_params)
#submit.user = current_user
if #submit.save
redirect_to root_path
else
render :home
end
end
def submit_params
params.require(:submit).permit(submit_answers_attributes:[:answer_id])
end
#form
<%= simple_form_for #submit do |f| %>
<%= f.simple_fields_for :submit_answers do |sa| %>
<%= sa.input :answer_id, collection: #answers[sa.options[:child_index]], input_html: { class: "#{'is-invalid' if sa.object.errors.any?}"}, label: #questions[sa.options[:child_index]].name %>
<div class="invalid-feedback d-block">
<ul>
<% sa.object.errors.full_messages.each do |msg| %>
<li> <%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= f.submit :done %>
<% end %>
The solution is to use simple_fields_for/fields_for. Note that <%= sa.input :answer_id %> must be :answer_id, not :answer, which is something I had tried before.
Also one must allow accepts_nested_attributes_for :submit_answers, where :submit_answers is the join_table.
I prebuild my SubmitAnswers like so: #questions.count.times { #submit.submit_answers.build } which generates an input field for each question, all of which get saved on the form submit, a la build.
For the strong_params one needs to permit the incoming ids:
params.require(:submit).permit(submit_answers_attributes:[:answer_id]), so in this case submit_answers_attributes:[:answer_id].
For anyone wondering what the params look like:
{"authenticity_token"=>"[FILTERED]",
"submit"=>
{"submit_answers_attributes"=>
{"0"=>{"answer_id"=>""}, "1"=>{"answer_id"=>""}, "2"=>{"answer_id"=>""}, "3"=>{"answer_id"=>""}, "4"=>{"answer_id"=>""}, "5"=>{"answer_id"=>""}, "6"=>{"answer_id"=>""}}},
"commit"=>"done"}
As for the errors, im sure there might be a better way, but for now I have just manually added them with input_html: { class: "#{'is-invalid' if sa.object.errors.any?}"}.
On a final note, the sa.object # => SubmitAnswer allows me to retrieve the Model, the errors of that Model or whatever else one might want.
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've been working on a simple scenario : users can join one group of each type. I am tring to build a form that will show all types, and under each type's name- the chosen group for that type, or a select box to choose a group of that type, if the user is not a member of one.
So far, I only could come up with a seperate form for each type - rather unconvinient. I'v Been tring to solve this for several days. I found explanations for uniqness of instances, collection_select and has_many through forms but I can't find a way to a combine solution.
Models:
class User < ActiveRecord::Base
has_many :memberships
has_many : groups, through: :memberships
end
class Group < ActiveRecord::Base
has_many : memberships
has_many :users, through: :memberships
belongs_to :group_type
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :group
validates :user_id, uniqueness: { scope: [:group_id, :group_type] }
end
class GroupType < ActiveRecord::Base
has_many :groups
end
View:
<% #types = GroupTypes.all %>
<% #types.each do |type| %>
<%= '#{#type.name}' %>
<% #active_group = user.groups.where(type :type) %>
<% if #active_group.exist? %>
<%= '#{#active_group}' %>
<%= link_to 'Leave', [group.user], method: :delete, data: { confirm: 'Are you sure?' } %>
<% else %>
<%= form_for (Membership.new) do |f| %>
<%= f.hidden_field :user_id, value: #user.id %>
<%= f.collection_select :group_id, Groups.all.where(type :type), :id, :name
<%= f.submit %>
<%end>
<%end>
<%end>
controlller:
Class MembershipController < ApplicationController
def create
#user = User.find(params[:user_id])
#group = Group.find(params[:group_id])
#membership = user.membership.create(group :#group )
#user. memberships << membership
redirect_to user_register_path
end
def destroy
#user = User.find(params[:user_id])
#user.groups.find_by(group : params[:group_id]).delete
redirect_to user_register_path
end
private
def membership_params
params.require(:membership).permit(:user_id, :group_id)
end
end
Not sure if it is working properly, but as I wrote I am not happy with the idea of a form for each cathegory. was wondering if anyone could advise on a solution for that basic problem.
Thanks!
not a complete answer but I thought of posting
the whole idea is by DRYING up your code you can easily see solution to your problems
1) DROP the TypeGroup model
class Group < ActiveRecord::Base
has_many : memberships
has_many :users, through: :memberships
has_many :types, class_name: "Group",
foreign_key: "type_id"
belongs_to :type, class_name: "Group"
end
migration
class CreateTypes < ActiveRecord::Migration[5.0]
def change
create_table :groups do |t|
t.references :type, index: true
t.timestamps
end
end
end
2) your controller#new
def new
#active_groups = current_user.groups.map{ |group| group.types}
#types = Type.all
end
3) use form helpers
def user_group?
type.group.user == current_user
end
4) DRY your form
<% #types.each do |type| %>
<%= '#{#type.name}' %>
<% if user_group? %>
// show your form
<%end>
// etc etc
<%end>
also I never use this architecture, of showing the child form and using it to query for the parent, but usually I always start from the parent and build a nested form
I am making a model where users can belong to multiple teams and teams have multiple people.
I have checkboxes but they don't pass the value onto the object.
class User < ActiveRecord::Base
attr_accessible :email, :name
has_many :teams
accepts_nested_attributes_for :teams
end
class Team < ActiveRecord::Base
has_many :users
attr_accessible :name
end
Here is the code in my controller
def create
#users = User.all
#user = User.new
#teams = Team.all
#user.attributes = {:teams => []}.merge(params[:user] || {})
end
Here is the code in my view file
<%= form_for #user, url: {action: "create"} do |f| %>
<%= f.label :teams%>
<% for team in #teams %>
<%= check_box_tag team.name, team.name, false, :teams => team.name%>
<%= team.name -%>
<% end %>
<%= submit_tag "Create User" %>
I am trying to show it into
<%= user.teams.name %>
But the only output is "Team"
Can someone tell me what I am doing wrong?
Actually, you can't do a many-to-many relationship that way... you need to do has_many :through or alternatively has_and_belongs_to_many Nice explanation here...
http://guides.rubyonrails.org/association_basics.html