"Can't mass-assign protected attributes" with nested protected models - ruby-on-rails

I'm having a time trying to get this nested model working. I've tried all manner of pluralization/singular, removing the attr_accessible altogether, and who knows what else.
restaurant.rb:
# == RESTAURANT MODEL
#
# Table name: restaurants
#
# id :integer not null, primary key
# name :string(255)
# created_at :datetime not null
# updated_at :datetime not null
#
class Restaurant < ActiveRecord::Base
attr_accessible :name, :job_attributes
has_many :jobs
has_many :users, :through => :jobs
has_many :positions
accepts_nested_attributes_for :jobs, :allow_destroy => true
validates :name, presence: true
end
job.rb:
# == JOB MODEL
#
# Table name: jobs
#
# id :integer not null, primary key
# restaurant_id :integer
# shortname :string(255)
# user_id :integer
# created_at :datetime not null
# updated_at :datetime not null
#
class Job < ActiveRecord::Base
attr_accessible :restaurant_id, :shortname, :user_id
belongs_to :user
belongs_to :restaurant
has_many :shifts
validates :name, presence: false
end
restaurants_controller.rb:
class RestaurantsController < ApplicationController
before_filter :logged_in, only: [:new_restaurant]
def new
#restaurant = Restaurant.new
#user = current_user
end
def create
#restaurant = Restaurant.new(params[:restaurant])
if #restaurant.save
flash[:success] = "Restaurant created."
redirect_to welcome_path
end
end
end
new.html.erb:
<% provide(:title, 'Restaurant') %>
<%= form_for #restaurant do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label "Restaurant Name" %>
<%= f.text_field :name %>
<%= f.fields_for :job do |child_f| %>
<%= child_f.label "Nickname" %>
<%= child_f.text_field :shortname %>
<% end %>
<%= f.submit "Done", class: "btn btn-large btn-primary" %>
<% end %>
Output Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"DjYvwkJeUhO06ds7bqshHsctS1M/Dth08rLlP2yQ7O0=",
"restaurant"=>{"name"=>"The Pink Door",
"job"=>{"shortname"=>"PD"}},
"commit"=>"Done"}
The error i'm receiving is:
ActiveModel::MassAssignmentSecurity::Error in RestaurantsController#create
Cant mass-assign protected attributes: job
Rails.root: /home/johnnyfive/Dropbox/Projects/sa
Application Trace | Framework Trace | Full Trace
app/controllers/restaurants_controller.rb:11:in `new'
app/controllers/restaurants_controller.rb:11:in `create'
Anyone have ANY clue how to get this to work? Thanks!

in restaurant.rb:
it should be
attr_accessible :name, :jobs_attributes
instead of
attr_accessible :name, :job_attributes

regarding your last comment:
you can submit the user_id within your job form, that should submit the user_id to the model

Related

Using a form_for to create a nested model row

Each "User" has one "Move".
Each "Move" has many "Neighborhood Preferences".
I want to create a form on the "/user/(:id)/edit" page that lets a person edit their "move", and "neighborhood_preferences".
Currently, the form displays correctly. But when I submit the form, I get this error Move(#2168853040) expected, got ActionController::Parameters(#2158484920).
How can I save both a new move, and the neighborhood_preferences?
View for Users#edit
<%= form_for(#user) do |f| %>
<%= hidden_field_tag "user[move][neighborhood_ids][]", nil %>
<% #neighborhoods.each do |n| %>
<%= check_box_tag "user[move][neighborhood_ids][]",
n.id, # the value to submit
#user.move.neighborhood_ids.include?(n.id), # Check the box on load
id: dom_id(n) # add a unique category based on the object
%>
<%= label_tag dom_id(n), n.name %>
<br/>
<% end %>
<%= f.submit "Save Changes" %>
<% end %>
Controller for Users#edit
def edit
#user = User.find(params[:id])
#neighborhoods = Neighborhood.all
end
private
def user_params
params.require(:user).permit(
:email,
:age,
:gender,
:university,
:grad_year,
:occupation,
:company,
# Allow form to submit neighborhood_ids for a user's move.
move: [neighborhood_ids: []]
)
end
Models
class User < ActiveRecord::Base
# attr_accessible :provider, :uid, :name, :email
validates_presence_of :name
has_one :move
accepts_nested_attributes_for :move, allow_destroy: true
class Move < ActiveRecord::Base
belongs_to :user
has_many :neighborhood_preferences
has_many :neighborhoods, through: :neighborhood_preferences
accepts_nested_attributes_for :neighborhood_preferences, allow_destroy: true
end
class NeighbhoodPreference < ActiveRecord::Base
belongs_to :neighborhood
belongs_to :move
end

Nested form update action producing duplicate results

During the update action of a nested form, instead of updating the current nested records, it seems to create new nested records.
This is what my controller looks like :
class ApplicationsController < ApplicationController
before_filter :set_user_and_job
def new
job = params[:job_id]
#application = Application.build(job)
end
def create
#application = Application.new(application_params)
#application.save
redirect_to root_url, :notice => "You have now applied!"
end
def edit
#application = Application.find(params[:id])
#answers = []
#job.questions.each do |question|
#application.answers.each do |answer|
#answers << answer if answer.question_id == question.id
end
end
end
def update
#application = Application.find(params[:id])
#application.update_attributes(application_params)
redirect_to root_url, :notice => "You have updated your application!"
end
def destroy
Application.find(params[:id]).destroy
flash[:success] = "Application Deleted."
redirect_to root_url
end
def show
#application = Application.find(params[:id])
#answers = []
#job.questions.each do |question|
#application.answers.each do |answer|
#answers << answer if answer.question_id == question.id
end
end
end
private
def set_user_and_job
#user = current_user
#job = Job.find(params[:job_id])
end
def application_params
params.require(:application).permit(:job_id, :user_id, answers_attributes:[:question_id, :content]).merge(user_id: current_user.id, job_id: params[:job_id])
end
end
This is what my edit view looks like :
<% provide(:title, " Edit this application") %>
<div class="row">
<div class="span6">
<h2> Job: <%= #job.job_title %></h2>
<p> <%= #job.job_summary %> </p>
</div>
<div class="span6">
<h2> Applicant: <%= #user.name %></h2>
</div>
<div class="span12">
<h3>Edit your job application below.</h3>
</div>
</div>
<%= form_for [#job, #application] do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<% #job.questions.each_with_index do |question| %>
<%= f.fields_for :answers, question do |question_field| %>
<%= question_field.label :content, question.content %>
<%= question_field.text_area :content, :value => "" %>
<%= question_field.hidden_field :question_id, :value => question.id %>
<% end %>
<% end %>
<%= f.submit "Submit the application", class: "button" %>
<% end %>
The Application Model itself:
# == Schema Information
#
# Table name: applications
#
# id :integer not null, primary key
# user_id :integer
# job_id :integer
# created_at :datetime
# updated_at :datetime
#
class Application < ActiveRecord::Base
belongs_to :job
belongs_to :user
validates :job_id, presence: true
validates :user_id, presence: true
has_many :answers
accepts_nested_attributes_for :answers, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true
def self.build(job_id)
application = self.new
job = Job.find(job_id)
job.questions.count.times do
application.answers.build
end
application
end
end
And the Answer Model :
# == Schema Information
#
# Table name: answers
#
# id :integer not null, primary key
# application_id :integer
# question_id :integer
# created_at :datetime
# updated_at :datetime
# content :string(255)
#
class Answer < ActiveRecord::Base
belongs_to :question
belongs_to :application
validates :content, presence: true
end
From searching, I found this link, RoR nested attributes produces duplicates when edit , which suggests that I add the :id to application_params, however when I do that, I get the error
ActiveRecord::RecordNotFound in ApplicationsController#update
Couldn't find Answer with ID=5 for Application with ID=17
(That's also a bit weird, because the actual id of the answer is 39. 5 is actually the ID of the question :S )
What are your thoughts on this? Mentors of SO, help much appreciated :)
update_only does not work for has_many relationships. You need to add the nested attribute :id field to your strong parameters:
def application_params
params.require(:application).permit(:job_id, :user_id,
answers_attributes:[:id, :question_id, :content]).merge(user_id: current_user.id,
job_id: params[:job_id])
end
Try adding update_only to your call to accepts_nested_attributes_for.
accepts_nested_attributes_for :nested_attribute, update_only: true

parent model id not passing, using ryan bates nested forms

I'm trying to use Ryan Bates' excellent 'nested forms' to create the following->
When creating a new 'Job', a user will have the option to write in multiple bullet points about it, and multiple 'Roles'
Here's how the models are set up :
Job Model
# == Schema Information
#
# Table name: jobs
#
# id :integer not null, primary key
# job_title :string(255)
# job_summary :string(255)
# qualifications :string(255)
# created_at :datetime
# updated_at :datetime
#
class Job < ActiveRecord::Base
validates :job_title, presence: true
validates :job_summary, presence: true
validates :qualifications, presence: true
has_many :bullets, dependent: :destroy
has_many :roles, dependent: :destroy
accepts_nested_attributes_for :bullets, :reject_if => lambda { |a| a[:bullet].blank? }, :allow_destroy => true
accepts_nested_attributes_for :roles, :reject_if => lambda { |a| a[:role_title].blank? }, :allow_destroy => true
#lambda { |a| a[:bullet].blank? } makes sure that on edit, if a value is left blank, then it doesn't save it
end
Bullet Model
# == Schema Information
#
# Table name: bullets
#
# id :integer not null, primary key
# job_id :integer
# bullet :string(255)
# created_at :datetime
# updated_at :datetime
#
class Bullet < ActiveRecord::Base
belongs_to :job
validates :job_id, presence: true
validates :bullet, presence: true
end
Role Model
# == Schema Information
#
# Table name: roles
#
# id :integer not null, primary key
# job_id :integer
# role_title :string(255)
# role_desc :string(255)
# created_at :datetime
# updated_at :datetime
#
class Role < ActiveRecord::Base
belongs_to :job
validates :job_id, presence: true
validates :role_title, presence: true
validates :role_desc, presence: true
end
Jobs Controller :
class JobsController < ApplicationController
before_action :signed_in_user, only: [:new, :create, :update, :show, :edit, :destroy] # index, at least partially is available to all viewers
before_action :admin_user, only: [:new, :edit, :update, :create, :destroy] # only admins can make jobs
def new
#job = Job.new
3.times {
#job.bullets.build
#job.roles.build
}
end
def create
#job = Job.new(job_params)
if #job.save
redirect_to root_path, :flash => { :success => "Job created!" }
else
render 'new'
end
end
def job_params
params.require(:job).permit(:job_title, :job_summary, :qualifications,
bullets_attributes: [:id, :bullet, :_destroy],
roles_attributes: [:id, :role_title,:role_desc, :_destroy])
end
end
Jobs/New View
<% provide(:title, "Publish a new job") %>
<%= nested_form_for #job do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :job_title %>
<%= f.text_field :job_title %>
<div style="margin-left:30px">
<%= f.fields_for :bullets do |bullet_form| %>
<%= bullet_form.label :bullet %>
<%= bullet_form.text_field :bullet %>
<%= bullet_form.link_to_remove "Remove this bullet" %>
<% end %>
<p><%= f.link_to_add "Add a Bullet", :bullets %></p>
</div>
<%= f.label :job_summary %>
<%= f.text_area :job_summary %>
<%= f.label :qualifications %>
<%= f.text_area :qualifications %>
<div style="margin-left:30px">
<%= f.fields_for :roles do |role_form| %>
<%= role_form.label :role_title %>
<%= role_form.text_field :role_title %>
<%= role_form.label :role_desc %>
<%= role_form.text_field :role_desc %>
<%= role_form.link_to_remove "Remove this Role" %>
<% end %>
<p><%= f.link_to_add "Add a Role", :roles %></p>
</div>
<%= f.submit "Publish the Job", class: "button" %>
<% end %>
Alas, it looks like I'm doing it incorrectly. When I try to create a new job, I get the error :
* Bullets job can't be blank
* Roles job can't be blank
It seems to me like the info for job_id is not passing through the nested forms.
What you think is happening, and ideas about making this work would be greatly appreciated :)
Logs
Here are some logs to make it a bit clearer:
1) When I submit the form, choosing only 1 bullet I get the following parameters in development:
--- !ruby/hash:ActionController::Parameters
utf8: ✓
authenticity_token: ezgktBZjcrdI6nMTro8Aqd0Djs2k3M+HFAdACrxajS8=
job: !ruby/hash:ActionController::Parameters
job_title: Job Title example
bullets_attributes: !ruby/hash:ActionController::Parameters
'0': !ruby/hash:ActiveSupport::HashWithIndifferentAccess
bullet: Example bullet
_destroy: 'false'
job_summary: Job Summary Example
qualifications: Example Qualifications here
roles_attributes: !ruby/hash:ActionController::Parameters
'1387448871560': !ruby/hash:ActiveSupport::HashWithIndifferentAccess
role_title: Example Role Name
role_desc: Example Role Description
_destroy: 'false'
commit: Publish the Job
action: create
controller: jobs
On my Rails Server, this is what it's saying ->
Started POST "/jobs" for 127.0.0.1 at 2013-12-19 14:28:01 +0400
Processing by JobsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"ezgktBZjcrdI6nMTro8Aqd0Djs2k3M+HFAdACrxajS8=", "job"=>{"job_title"=>"Job Title example", "bullets_attributes"=>{"0"=>{"bullet"=>"Example bullet", "_destroy"=>"false"}}, "job_summary"=>"Job Summary Example", "qualifications"=>"Example Qualifications here", "roles_attributes"=>{"1387448871560"=>{"role_title"=>"Example Role Name", "role_desc"=>"Example Role Description", "_destroy"=>"false"}}}, "commit"=>"Publish the Job"}
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."remember_token" = '449addc616f3035c031b3220404a4d5eb5351c74' LIMIT 1
(0.2ms) begin transaction
(0.2ms) rollback transaction
Rendered shared/_error_messages.html.erb (2.4ms)
Rendered jobs/new.html.erb within layouts/application (15.7ms)
Rendered layouts/_shim.html.erb (0.1ms)
Rendered layouts/_header.html.erb (1.3ms)
Completed 200 OK in 101ms (Views: 32.0ms | ActiveRecord: 0.7ms | Solr: 0.0ms)
From what you've posted, there are several issues which could be causing the problem
Strong Params
def job_params
params.require(:job).permit(:job_title, :job_summary, :qualifications,
bullets_attributes: [:bullet],
roles_attributes: [:role_title,:role_desc])
end
I don't know why you've called your columns role_title and role_desc, but it's better to call the columns only by their actual name (title and desc)
It's basically because if you load #role, it's much more efficient to write #role.title rather than #role.role_title. The same for your other tables
You may wish to update your schemas to reflect that
ActiveRecord Build
Second problem is you're building your new ActiveRecord objects 3 times:
def new
#job = Job.new
3.times {
#job.bullets.build
#job.roles.build
}
end
This would be okay if you wanted to send 3 objects, but you only have 1. And I know you've done it so you can add extra fields (I'll explain that in a second)
You should change to this:
def new
#job = Job.new
#job.bullets.build
#job.roles.build
end
I believe this is your main issue actually - if you build the objects & they don't get populated, they are blank, no?
I would only declare the objects I wanted to show up front, and add the extras when required
Adding Fields
If you wish to add fields on the fly (with Ajax), I would highly recommend this tutorial. It uses the same principles as Ryan Bates' method, but is much cleaner & adds the objects when required. I can talk about this via chat if you wanted

Cannot submit Checkbox through nested form

Im trying to create an application were teachers a able to select students each day who aren't in school. I created the models through nifty-generators gem. The problem is that it wont submit to the notpresents table. Please help.
# == Schema Information
#
# Table name: students
#
# id :integer not null, primary key
# name :string(255)
# group_id :integer
# created_at :datetime not null
# updated_at :datetime not null
#
class Student < ActiveRecord::Base
attr_accessible :name, :group_id
belongs_to :days
end
# == Schema Information
#
# Table name: notpresents
#
# id :integer not null, primary key
# student_id :integer
# day_id :integer
# created_at :datetime not null
# updated_at :datetime not null
#
class Notpresent < ActiveRecord::Base
attr_accessible :student_id, :day_id
belongs_to :days
end
# == Schema Information
#
# Table name: days
#
# id :integer not null, primary key
# title :string(255)
# created_at :datetime not null
# updated_at :datetime not null
#
class Day < ActiveRecord::Base
attr_accessible :title, :presents
has_many :notpresents
accepts_nested_attributes_for :notpresents
end
And view _form.html.erb
<%= form_for #day do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :title %><br />
<%= f.text_field :title %>
</p>
<% for student in Student.find(:all) %>
<div>
<%= check_box_tag :notpresents, student.id%>
<%= student.name %>
</div>
<% end %>
<p><%= f.submit %></p>
<% end %>
I've never used the nifty-generators gem, but if a student can be absent in many days, and a day can have many students absent, shouldn't you have a many-to-many relationship?
class Student < ActiveRecord::Base
attr_accessible :name
has_many :days, through: :notpresents
has_many :notpresent
end
class Days < ActiveRecord::Base
attr_accessible :date
has_many :students, through: :notpresents
has_many :notpresent
end
class :Notpresents < ActiveRecord::Base
attr_accessible :student_id, :day_id
belongs_to :students
belongs_to :days
end
It could also be a has_and_belongs_to_many association, but with a has_many :through you could have a string or text attribute to make a note about the absence or something like that.
I recommend using simple_form for the form, it makes it so easy:
app/controllers/days_controller.rb:
def edit
#day = Day.find(params[:id])
end
app/views/days/_form.html.erb :
<%= simple_form_for #day do |f| %>
<%= f.association :students, as: :check_boxes %>
<% end %>

Paperclip has_one association

I have a User model with a has_one :image relationship to an Image model. I want each user to have a photo but I want the photos to be stored in a model separate from the User model, namely the Image model.
I am using paperclip to help with adding an image.
However when I try to create a new user through my view I get the following error:
ActiveModel::MassAssignmentSecurity::Error in UsersController#create
Can't mass-assign protected attributes: image_attributes
I don't have a image_attributes variable. Why is this happening? Is my implementation flawed? I want a separate table to hold the images, because in the future I wish users to have many images.
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>
<%= form_for #user do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label :first_name %>
<%= f.text_field :first_name %>
<br>
<%= f.label :last_name %>
<%= f.text_field :last_name %>
<br>
<%= f.label :email %>
<%= f.text_field :email %>
<br>
<%= f.fields_for :image, :html => {:multipart => true} do |asset| %>
<%= asset.label :photo %>
<%= asset.file_field :photo %>
<% end %>
<br>
<%= f.label :password %>
<%= f.password_field :password %>
<br>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation %>
<br>
<%= f.submit "Create my account"%>
<% end %>
User Controller
class UsersController < ApplicationController
#calls method signed_in_users.
before_filter :signed_in_user, only: [:index, :edit, :update, :show]
#ensures only the correct user can modify their own data
before_filter :correct_user, only: [:edit, :update]
def new
#user = User.new
##user.image.build
#user.build_image
end
def show # personal profile page
#Method to make sure only the signed in user can edit their information
if User.id_equals_cookie(params[:id], cookies[:remember_token])
#user = User.find(params[:id])
else
redirect_to root_url
end
end
def create
#user = User.new(params[:user])
if #user.save
sign_in #user #method defined in sessions_helper
flash[:notice] = "Thank you for signing up." #the view template must be configured to be seen.
redirect_to #user #does user_path work???
else
render 'new' #flash[:warning]
end
end
def edit
end
def update
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
flash[:success] = "Profile updated"
sign_in #user
redirect_to #user
else
render 'edit'
end
end
def index
end
private
#should this be in the users_helper.rb file
def signed_in_user
redirect_to signin_url, notice: "Please sign in." unless signed_in?
end
def correct_user
#user = User.find(params[:id])
redirect_to(root_path) unless current_user?(#user) #current_user is defined in the sessions_helper.rb
end
end
User Model
# == Schema Information
#
# Table name: users
#
# id :integer not null, primary key
# email :string(255)
# created_at :datetime not null
# updated_at :datetime not null
# password_digest :string(255)
# remember_token :string(255)
# first_name :string(255)
# last_name :string(255)
# full_name :string(255)
# birthdate :date
#
class User < ActiveRecord::Base
has_one :image, :dependent => :destroy #images?
accepts_nested_attributes_for :image #images?
attr_accessible :email, :first_name, :last_name, :full_name, :birthdate, :password, :password_confirmation
#magic to require a password, make sure passwords match, authenticate
has_secure_password
before_save { |user| user.email = email.downcase } #help ensure uniqueness
before_save :create_remember_token
#validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, uniqueness: {case_sensitive: false}
validates :password, presence: true, length: { minimum: 6 }
validates :password_confirmation, presence: true
def self.id_equals_cookie(id, cookie)
#user_of_cookie = find_by_remember_token(cookie)
#user_of_id = find(id)
if #user_of_cookie == nil
false
elsif #user_of_id == #user_of_cookie
true
else
false
end
end
private
def create_remember_token
self.remember_token = SecureRandom.urlsafe_base64
end
end
Image Model
# == Schema Information
#
# Table name: images
#
# id :integer not null, primary key
# created_at :datetime not null
# updated_at :datetime not null
# photo_file_name :string(255)
# photo_content_type :string(255)
# photo_file_size :integer
# photo_updated_at :datetime
# user_id :integer
#
require 'paperclip'
class Image < ActiveRecord::Base
belongs_to :user
has_attached_file :photo
attr_accessible :photo, :photo_file_name, :photo_content_type, :photo_file_size, :photo_updated_at
end
write this in your model
attr_accessible :image_attributes
just do attr_accessible :photo_attributes

Resources