class Student < ActiveRecord::Base
attr_accessible :dob, :grade_status, :school_id
belongs_to :school
end
class School < ActiveRecord::Base
attr_accessible :location, :name
has_many :students
end
class HomeController < ApplicationController
def index
#school = School.new
#student = #school.students.build(params[:student])
School.create(params[:school])
end
end
add accepts_nested_attributes_for :students in the School model and add :students_attributes to the attr_accesible
Add :students to your list of attr_accessible. And buy on book about Rails.
It looks like your params hash is including the 'students' key inside of the 'school' key. Is this accurate? It looks something like this:
{ school: { name: 'Foo', location: 'Bar', students: [...] } }
I will assume this is the case. You should use nested attributes, add:
accepts_nested_attributes_for :students
to your School model. Also add students_attributes to your attr_accessible line in your School model.
In your view, you will need to use the fields_for helper so that Rails can build the students_attributes key in your params. It would look something like this:
form_for #school do |f|
f.text_field :name
f.text_field :location
f.fields_for :students do |builder|
builder.text_field :dob
...
(this should all be in ERB, Haml, or whatever you're using)
Here is the Railscast on nested forms: http://railscasts.com/episodes/196-nested-model-form-part-1 if you are still having trouble.
Related
I'm running into an error when nesting parameters in Rails 5: Unpermitted parameter: specialties
I have an Expertise model:
class Expertise < ApplicationRecord
has_many :buckets, through: :specialties
has_many :specialties
end
A Bucket model:
class Bucket < ApplicationRecord
has_many :expertises, through: :specialties
has_many :specialties
end
And a Specialty model:
class Specialty < ApplicationRecord
belongs_to :expertise
belongs_to :bucket
end
I'm trying to allow the User to edit his or her Expertises and adjust the Specialties associated with them. The #buckets are passed in from the controller, and the form currently looks like this:
<%= form_for(expertise) do |f| %>
<%= f.fields_for :specialties do |s| %>
<%= s.collection_select :bucket_ids, #buckets, :id, :name, {}, { multiple: true, class: "input" } %>
<% end %>
<% end %>
I based the form on this answer.
Here's the relevant snippet from the ExpertisesController:
def expertise_params
params.require(:expertise).permit(:user_id, :name, :rating, :description, specialties_attributes: [:id, :expertise_id, :bucket_id, :_destroy, bucket_ids: []])
end
And here are the parameters that are being passed in:
Parameters: {"expertise"=>{"specialties"=>{"bucket_ids"=>["", "1"]}, "description"=>""}, "id"=>"97"}
Specialties should be an array, right? I'm not sure how to do that.
The aim is to easily enable the User to select from the available Buckets (#buckets) to toggle his or her Expertise Specialties on or off. So let's say there are 5 Buckets available, the User would only be able to toggle on/off 5 possible Specialties for that Expertise.
Unpermitted parameter: specialties
You didn't set up accept_nested_attributes_for which spits out with that error
class Expertise < ApplicationRecord
has_many :specialties
has_many :buckets, through: :specialties
accepts_nested_attributes_for :specialties
end
When I try that, the nested fields_for form doesn't return any
specialties and so the HTML element is empty. Then, when I try to use
#expertise.specialties.build, I get undefined method bucket_ids for
Specialty because bucket_ids isn't actually an attribute, but
bucket_id is. Worth keeping in mind that the User needs to be able to
toggle multiple Specialties, each of which is tied to a Bucket (via a
bucket_id), and from what I've ready I'm supposed to use bucket_ids
(the plural) there
You don't need to have plural form(_ids) just because to accept multiple values. Just keep bucket_id to accept multiple values. And don't forget to build the associated model in the controller
def new
#expertise = Expertise.new
#expertise.specialties.build
end
Change bucket_ids to bucket_id in the form
<%= s.collection_select :bucket_id, #buckets, :id, :name, {}, { multiple: true, class: "input" } %>
And finally, expertise_params should be
def expertise_params
params.require(:expertise).permit(:user_id, :name, :rating, :description, specialties_attributes: [:id, :expertise_id, :_destroy, bucket_id: []])
end
Update:
Ok after some research, it looks like it should be bucket_ids, but the bucket_ids should be allowed as attribute for expertise. Check this post and tweak your form and expertise_params accordingly. You won't be needing accept_nested_attributes_for too!
The situation: Expertise has_many Buckets through Specialties and you want to update some bucket status of a specific expertise. So you can do this:
class ExpertisesController < ApplicationController
def your_action
#expertise = Expertise.find params[:id]
bucket_ids = params[:expertise][:specialties][:bucket_ids]
#expertise.specialties.where(id: bucket_ids).update(status: :on)
end
end
I have 3 models: Employers, Partners and Collaborations.
As an Employer, I want to add a record to my Partner model and to my Collaboration model to be able to indicate a collaboration between a Partner and a Employer. I therefore have the following columns in my database/tabels.
Models
class Employer < ActiveRecord::Base
has_many :collaborations
has_many :partners, :through => :collaborations
end
class Partner < ActiveRecord::Base
has_many :collaborations
has_many :employers, :through => :collaborations
accepts_nested_attributes_for :collaborations
end
class Collaboration < ActiveRecord::Base
belongs_to :employer
belongs_to :partner
end
Tables
Collaborations
employer_id:integer
partner_id:integer
tarive:string
Partners
added_by:integer
name:string
Because I want to be able to add a Partner/Collaboration within 1 form, I use nested forms. So I can add a partner (name, etc) and a collaboration (tarive, etc) in one go.
My (simple_form) form looks like this (I have named_space resource).
Te reduce clutter, I removed as much HTML mark_up as I could, this is not the issue.
Form
/views/employer/partners/_form
= simple_form_for [:employer, #partner], html: { multipart: true } do |f|
Partner
= f.input :name, input_html: { class: 'form-control' }
= f.simple_fields_for :collaborations do |ff|
Tarive
= ff.input :tarive, input_html: { class: 'form-control' }
= f.button :submit, "Save"
My controller looks like
class Employer::PartnersController < ActionController::Base
def new
#partner = Partner.new
#partner.collaborations.build
end
def create
#partner = Partner.new(partner_params)
#partner.collaborations.build
#partner.added_by = current_employer.id
#partner.collaborations.employer_id = current_employer.employer_id
#partner.collaborations.partner_id = #partner.id
#partner.collaborations.added_by = current_employer.id
if #partner.save
redirect_to employer_partner_path(#partner), notice: "Succes!"
else
render 'new'
end
end
def partner_params
params.require(:partner).permit(:id, :name, collaborations_attributes: [:id, :employer_id, :partner_id, :tarive])
end
end
Problem
The problem/question I have is this. The attributes are assigned nicely and added in the model. But I want to add a employer_id as well, which I have in current_employer.employer.id (Devise). I do not want to work with hidden forms, just to avoid this issue.
I assigned 'parent' models always like #partner.added_by = current_employer.id and that works beautifully.
When I use:
#partner.collaborations.employer_id = current_employer.employer_id
I get an error, saying #partner.collaborations.employer_id is empty.
Question
How can I assign a variable to the nested_form (Collaboration) in my controller#create?
Or more specifically: how can I assign current_employer.employer_id to #partner.collaborations.employer_id?
There are several ways:
Merge the params
Deal with objects, not foreign keys
Personally, I feel your create method looks really inefficient. Indeed, you should know about fat model skinny controller - most of your associative logic should be kept in the model.
It could be improved using the following:
#app/controllers/employers/partners_controller.rb
class Employers::PartnersController < ApplicationController
def new
#partner = current_employer.partners.new #-> this *should* build the associated collaborations object
end
def create
#partner = current_employer.partners.new partner_params
#partner.save ? redirect_to(employer_partner_path(#partner), notice: "Succes!") : render('new')
end
private
def partner_params
params.require(:partner).permit(:id, :name, collaborations_attributes: [:tarive]) #when dealing with objects, foreign keys are set automatically
end
end
This would allow you to use:
#app/views/employers/partners/new.html.erb
= simple_form_for #partner do |f| #-> #partner is built off the current_employer object
= f.input :name
= f.simple_fields_for :collaborations do |ff|
= ff.input :tarive
= f.submit
... and the models:
#app/models/partner.rb
class Partner < ActiveRecord::Base
belongs_to :employer, foreign_key: :added_by
has_many :collaborations
has_many :employers, through: :collaborations
accepts_nested_attributes_for :collaborations
end
#app/models/collaboration.rb
class Collaboration < ActiveRecord::Base
belongs_to :employer
belongs_to :partner
belongs_to :creator, foreign_key: :added_by
before_create :set_creator
private
def set_creator
self.creator = self.employer_id #-> will probably need to change
end
end
#app/models/employer.rb
class Employer < ActiveRecord::Base
has_many :collaborations
has_many :employers, through: :collaborations
end
This may not give you the ability to set tarive, however if you cut down the manual declarations in your model, we should be able to look at getting that sorted.
The main thing you need to do is slim down your code in the controller. You're being very specific, and as a consequence, you're encountering problems like that which you mentioned.
I have two models, Car and Manufacturer. These models are pretty simple:
class Car < ActiveRecord::Base
attr_accessible :manufacturer_id, :car_name, :descr, ...
belongs_to :manufacturer
...
end
and
class Manufacturer < ActiveRecord::Base
attr_accessible :name, :url
has_many :cars
...
end
The view (views/cars/_form.html.haml) with form for entering data:
= form_for #car do |f|
.field
= f.label :car_name
= f.text_field :car_name
...
= f.fields_for #manufacturer do |m|
.field
= m.label :name
= m.text_field :name
...
When I send the form for saving entered information (it goes to CarsController), I get this error:
Can't mass-assign protected attributes: manufacturer
I've tried to add the
accepts_nested_attributes_for :manufacturer
to the Car model, but it didn't help me...
Where is the problem?
EDIT:
How I am saving data in controller:
#manufacturer = Manufacturer.new(params[:car][:manufacturer])
#car = #manufacturer.cars.build(params[:car])
EDIT2:
Data from log:
{"utf8"=>"✓",
"_method"=>"put",
"authenticity_token"=>"4vcF5NV8D91DkxpCsqCzfbf05sOYsm7ssxZvPa3+kXo=",
"car"=>{"car_name"=>"...",
"descr"=>"...",
"categroy_ids"=>["2",
"3",
"4"],
"manufacturer"=>{"name"=>"Company",
"url"=>"..."}},
"commit"=>"Save",
"id"=>"..."}
Thank you
Can you save manufacturer through car?
Add to Car model:
accepts_nested_attributes_for :manufacturer
Add manufacturer_attributes among other Car attributes to attr_accessible call in Car model:
attr_accessible :manufacturer_attributes, :car_name, :descr, ...
Save it in your controller action(standard way) something like this:
def create
#car = Car.new(params[:car])
if #car.save
redirect_to #car
else
render :new
end
end
Make sure that everything you are sending in manufacturer_attributes hash is white listed with attr_accessible call in Manufacturer model(:name, :url etc..).
Your params[:car] contains manufacturer attributes.. Try this:
#manufacturer = Manufacturer.new(params[:car].delete(:manufacturer))
#car = #manufacturer.cars.build(params[:car])
You are not making use of has_many relation by doing this way. You can go through this
You need to add
attr_accessible :manufacturer_id, :car_name, :descr, :manufacturer_attributtes
in car the model. Don't bother with #manufacturer in your saving method in the car controller it is taken care of.
You should read this : Active Record Nested Attributes
I hope it helped.
I have read pretty much every question here about the nested forms with has_many through associations, but I can't get my model to work. Can someone please help?
There are 2 models: archetypes and skirtpreferences, linked through a skirtpreferencing model.
Here are the models:
class Archetype < ActiveRecord::Base attr_accessible :occasion,
:skirt_partworth, :title, :skirtpreferencings_attributes
has_many :skirtpreferencings has_many :SkirtPreferences, :through
=> :skirtpreferencings accepts_nested_attributes_for :SkirtPreferences accepts_nested_attributes_for :skirtpreferencings
end
Blockquote
class Skirtpreferencing < ActiveRecord::Base attr_accessible
:archetype_id, :skirt_preference_id, :skirtpreferencing_attributes
belongs_to :archetype belongs_to :SkirtPreferences
accepts_nested_attributes_for :SkirtPreferences
end
class SkirtPreference < ActiveRecord::Base attr_accessible
:archetype_id, ....
has_many :skirtpreferencings has_many :archetypes, :through =>
:skirtpreferencings
end
The form looks like this and that is displaying just fine:
<%= form_for(#archetype) do |f| %> ...
<%= f.fields_for :skirtpreferencing do |preference_builder| %>
<%= preference_builder.fields_for :SkirtPreferences do |builder| %>
<%= render "skirt_preferences_field", :f => builder %>
<% end %> <% end %> ...
I imagine I hav to do something in the controllers, but I am not sure exactly what.
Thanks!
Adding the Controllers:
class ArchetypesController < ApplicationController
def new
#archetype = Archetype.new
#archetype.skirtpreferencings.build
end
# GET /archetypes/1/edit
def edit
#archetype = Archetype.find(params[:id])
end
def create
#archetype = Archetype.new(params[:archetype])
end
class SkirtPreferencesController < ApplicationController
def new
#skirt_preference = SkirtPreference.new
end
def edit
#skirt_preference = SkirtPreference.find(params[:id])
end
def create
#skirt_preference = SkirtPreference.new(params[:skirt_preference])
end
I am guessing this is many-to-many relationship between SkirtPreference and Archetype, while SkirtPreferencing is the association between SkirtPreference and Archetype?
Try changing from skirtpreferencing_attributes to skirtpreferences_attributes. That's my hunch. Because you are trying to add data to skritpreferences, not the skirtpreferencings which are just there to associate between skirtpreferences and archetypes.
I also think this is unusual.
has_many :SkirtPreference, :through => :skirtpreferencings
accepts_nested_attributes_for :SkirtPreference
...
belongs_to :SkirtPreference
accepts_nested_attributes_for :SkirtPreference
All these normally should be :skirtpreferences.
** EDIT 1 **
Assuming the forms are generated in new action in ArchetypesController...
You seem to be missing the part where you build a skirtpreferencing attribute out of archetype.
So in your new action in ArchetypesController
def new
#archetype = Archetype.new
#archetype.skirtpreferencings.build
...
end
** Edit 2 **
SkirtPreferences should be changed to skirtpreferences except for class name.
Can you try changing f.fields_for :skirtpreferencing do to f.fields_for :skirtpreferencings do
Normally people make nested forms where nested attributes are accepted for objects that 'belong to' the main object...
For example:
class Brand < ActiveRecord::Base
has_many :models
end
class Model < ActiveRecord::Base
belongs_to :brand
end
So, given the above, one might expect to make a nested form for brand which accepts nested attributes for model.
This may sound ridiculous, but what I would like to do is create a form for model which accepts nested attributes for brand... is this possible?
Yes, you can.
class Brand < ActiveRecord::Base
has_many :models
end
class Model < ActiveRecord::Base
belongs_to :brand
accepts_nested_attributes_for :brands
end
After in view make
= form_for Model.new do |f|
%p
= f.label :name
= f.text_field :name
%p
Brands:
- 4.times do
= f.fields_for :brand, Brand.new do |bf|
= br.text_field :name
...
This form will generate params for brands like this:
model: {
name: "Shiny Ann",
brands_attributes: {
"0": {name: "Brand1"},
"1": {name: "Brand2"},
"2": {name: "Brand3"}
}
}