Rails - nested model: Can't mass-assign protected attributes - ruby-on-rails

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.

Related

belongs_to with accepts_the_nested_attributes for is not saving foreign key in object

I have a model named Profile which has belongs_to relation with Address
class Profile < ActiveRecord::Base
belongs_to :address, dependent: :destroy
accepts_nested_attributes_for :address, allow_destroy: true
here is the code in controller
def create
#profile = Profile.new(profile_signup_params)
#profile.save
respond_to
..... etc.....
end
for params
def profile_signup_params
params.require(:profile).permit( { address_attributes: [:country]
end
but #profile.save
i get this object
#<MemberProfile:0x0000000af135b0
id: 28,
address_id: nil,
birth_date: nil,
country_code: nil,
phone: nil,
stripe_customer_id: "123",
created_at: some time,
updated_at: some time>
as you cane see this address_id is nil
Profile is created
Address is created
but Address is not assigned to Profile
Please help me, what thing i am doing wrong
I think You made a Wrong association between Address and Profile
Profile which has belongs_to relation with Address instead It should be Profile has has_one association with respect to Address.
As mention in official Documentation Active Record Nested Attributes
class Profile < ActiveRecord::Base
has_one :address, dependent: :destroy
accepts_nested_attributes_for :address, allow_destroy: true
...
end
class Address < ActiveRecord::Base
belongs_to :profile
end
Rest Controller and model code would be same in your case except you need to a primary-foreign key relation between Address and Profile; need to create profile_id column in address table.
Note: Make sure if there is a uniqueness you need to follow Uniqueness Gotcha!!! in ActiveRecord NestedAttributes.
Original Blog Uniqueness Gotcha!!! Problem and Solution
Hope this This Help you!!!
To add to Vinay's answer (which is correct IMO), you'd want to make sure you're passing the right data through your controller.
Whilst the belongs_to association should allow you to set the nested parameters you require, it would be prudent to mention what Vinay said -- if you're creating an Address for a Profile, surely it would be the address that would belong to the Profile?
You can see about the has_one association here:
You'd handle it in a very similar way:
#app/controllers/profiles_controller.rb
class ProfilesController < ApplicationController
def new
#profile = Profile.new
#profile.build_address
end
def create
#profile = Profile.new profile_params
#profile.save
end
private
def profile_params
params.require(:profile).permit(address_attributes: [:country])
end
end
#app/views/profiles/new.html.erb
<%= form_for #profile do |f| %>
<%= f.fields_for :address do |a| %>
<%= a.text_field :country %>
<% end %>
<% end %>
This, with the model code from Vinay should get it working properly.

Ruby: How do I assign values in the controller when using nested forms?

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.

Create parent model from child controller

I'm working on on a web app that takes in information about companies. Information can be taken for a PreferredOffering (of stock) or an Incorporation. So in other words, when I create a new entry to either one of those models, a new company is formed.
It works out that my database is cleaner if PreferredOffering and Incorporation are children of Company, even though I'm trying to go through the preferred_offerings_controller or theincorporations_controller to create a new Company. Here lies my question; I'm trying to figure out how to configure my view and controllers to create a parent model from a child controller. I've done some research and have seen two other S/O posts on how to accomplish this with Rails 3, however it would seem that the addition of strong params adds another layer of complexity to the endeavor.
So I have my models set up like this
class Company < ActiveRecord::Base
belongs_to :user
has_one :incorporation, dependent: :destroy
has_many :preferred_offerings, dependent: :destroy
accepts_nested_attributes_for :preferred_offerings, allow_destroy: true
accepts_nested_attributes_for :incorporation, allow_destroy: true
end
.
class Incorporation < ActiveRecord::Base
belongs_to :company
end
.
class PreferredOffering < ActiveRecord::Base
belongs_to :company
end
The controller and view are what I'm iffy on.
Let's just take a look at the incorporation view/controller. If I were to configure it so that Incorporation has_one :company, I would set it up as follows:
class IncorporationsController < ApplicationController
def index
end
def new
#user=current_user
#incorporation = #user.incorporations.build
#company = #incorporation.build_company
end
def create
#incorporation = current_user.incorporations.build(incorporation_params)
end
private
def incorporation_params
params.require(:incorporation).permit(:title, :trademark_search, :user_id, :employee_stock_options, :submit, :_destroy,
company_attributes: [:id, :name, :employee_stock_options, :options_pool, :state_corp, :street, :city, :state, :zip, :issued_common_stock, :outstanding_common_stock, :fiscal_year_end_month, :fiscal_year_end_day, :user_id, :_destroy]
)
end
end
And the view would be:
<%= simple_form_for #incorporation, html: {id:"incorporationform"}, remote: false, update: { success: "response", failure: "error"} do |f| %>
(incorporation-specific fields)
<%= f.simple_fields_for :company do |company| %>
(Company-specific fields)
<% end %>
<% end %>
So my question is:
How do I need to modify my controller and view to create a Company from the incorporations_controller IF Company has_one :incorporation
Any suggestions would be much appreciated.
While it isn't the "Rails Way", there is nothing really wrong with having #company being the parent in your form, even though it is in the incorporations#new action. Your view would change to this:
<%= simple_form_for #company, html: {id:"companyform"}, remote: false, update: { success: "response", failure: "error"} do |f| %>
(company-specific fields)
<%= f.simple_fields_for :incorporation do |incorporation| %>
(incorporation-specific fields)
<% end %>
<% end %>
And your strong params would change so that Company is the parent and Incorporation is the child.
Another option would be to simply go through the Company controller. You could create two new actions: new_preferred_offering and new_incorporation. You would then create the objects in those actions. Or you could pass in some kind of :type param so that the normal new action renders one of two forms based on which one you want.

Can't mass-assign protected attributes: students

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.

Backwards nested form?

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"}
}
}

Resources