I'm following along with railscast 196. I've got two levels of associations. App -> Form -> Question. This is the new action in the form controller.
def new
#app = App.find(params[:app_id])
#form = Form.new
3.times {#form.questions.build }
end
the view is displaying all 3 questions fine and I can submit the form... but nothing is inserted into the database for the questions. Here is my create action
def create
#app = App.find(params[:app_id])
#form = #app.forms.create(params[:form])
respond_to do |format|
if #form.save
format.html { redirect_to(:show => session[:current_app], :notice => 'Form was successfully created.') }
format.xml { render :xml => #form, :status => :created, :location => #form }
else
format.html { render :action => "new" }
format.xml { render :xml => #form.errors, :status => :unprocessable_entity }
end
end
end
Here are the params that are sent to my create method:
{"commit"=>"Create Form",
"authenticity_token"=>"Zue27vqDL8KuNutzdEKfza3pBz6VyyKqvso19dgi3Iw=",
"utf8"=>"✓",
"app_id"=>"3",
"form"=>{"questions_attributes"=>{"0"=>{"content"=>"question 1 text"},
"1"=>{"content"=>"question 2 text"},
"2"=>{"content"=>"question 3 text"}},
"title"=>"title of form"}}`
This shows that the params are being sent correctly... I think. The question model just has a "content" text column.
Any help appreciated :)
Assuming:
You have your form set up properly,
Your server shows your data is being sent to the new action, and
Your model doesn't contain callbacks that are blocking the save,
try changing:
#form = #app.forms.create(params[:form])
to
#form = #app.forms.build(params[:form])
Ok figured it out. Turns out I should have been looking at my console a little more. The error it was hanging up on when trying to insert questions into the db was "Warning: can't mass assign protected attributes :questions_attributes". Adding this into the accessible attributes did the trick.
class Form < ActiveRecord::Base
belongs_to :app
has_many :questions, :dependent => :destroy
accepts_nested_attributes_for :questions
attr_accessible :title, :questions_attributes
end
Related
I have two models Employee and Documents which are as follows:
Employee.rb
class Employee < ActiveRecord::Base
has_one :document #,dependent: :destroy
attr_accessible :age, :dob, :empNo, :first_name, :gender, :last_name, :middle_name, :document_attributes
accepts_nested_attributes_for :document
validates :first_name, presence: true , length: { maximum: 50 }
validates :empNo, presence: true, uniqueness:{ case_sensitive: false }
validates_length_of :empNo, :minimum => 5, :maximum => 5
#before save { |user| user.email = email.downcase }
end
Document.rb
class Document < ActiveRecord::Base
belongs_to :employee,foreign_key: "empno"
attr_accessible :idate, :iedate, :insuranceno, :iqamano, :iqedate, :iqidate, :passportno, :pedate, :pidate, :vedate, :vidate, :visano
end
and the controller file is employees_controller.rb(I have only shown new,create,show funcs)
def show
#employee = Employee.find(params[:id])
#document=Document.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #employee }
end
end
# GET /employees/new
# GET /employees/new.json
def new
#employee = Employee.new
#document= Document.new
respond_to do |format|
format.html # new.html.erb
format.json { render :json=>{:employee=> #employee,:document=>#document}, status: :created, :location=>{:employee=> #employee,:document=>#document} }
end
end
# POST /employees
# POST /employees.json
def create
#employee = Employee.create!(params[:employee])
#document = Document.create!(params[:document])
respond_to do |format|
if #employee.save and #document.save
format.html { redirect_to #employee, notice: 'Employee was successfully created.' }
format.json { render :json=>{:employee=> #employee,:document=>#document}, status: :created, location: #employee }
else
format.html { render action: "new" }
format.json { render json: #employee.errors, status: :unprocessable_entity }
end
end
end
When I create a new employee I get the following error
ActiveModel::MassAssignmentSecurity::Error in EmployeesController#create
Can't mass-assign protected attributes: document
The requsts parameters are fine as seen below
{"utf8"=>"✓",
"authenticity_token"=>"vXSnbdi+wlAhR5p8xXvTWhi85+AVZgOZufClx73gc8Q=",
"employee"=>{"empNo"=>"11111",
"first_name"=>"Thaha",
"middle_name"=>"Muhammed",
"last_name"=>"Hashim",
"age"=>"25",
"gender"=>"M",
"dob(1i)"=>"2014",
"dob(2i)"=>"7",
"dob(3i)"=>"18",
"document"=>{"passportno"=>"bycucde63"}},
"commit"=>"Create Employee"}
I have gone through nearly all posts on stackoverflow dealing with this issue and mostly the issue is related to
not using attr_accessible
not using accepts_nested_attributes_for
not using :document_attributes
If I change the value of config.active_record.whitelist_attributes to false then the error goes away(There is a warning in developer log about the same) and both the models are created but only attributes of employee model is filled with passed values wheras the attributes of document model is nil.
EDIT #1
If I tried to add :document to attr_accessible then I get the following error
ActiveRecord::AssociationTypeMismatch in EmployeesController#create
What is that I am doing wrong here?
Understanding Mass Assignment
Mass Assignment is the name Rails gives to the act of constructing your object with a parameters hash. It is "mass assignment" in that you are assigning multiple values to attributes via a single assignment operator.
The following snippets perform mass assignment of the name and topic attribute of the Post model:
Post.new(:name => "John", :topic => "Something")
Post.create(:name => "John", :topic => "Something")
Post.update_attributes(:name => "John", :topic => "Something")
In order for this to work, your model must allow mass assignments for each attribute in the hash you're passing in.
There are two situations in which this will fail:
You have an attr_accessible declaration which does not include :name
You have an attr_protected which does include :name
It recently became the default that attributes had to be manually white-listed via a attr_accessible in order for mass assignment to succeed. Prior to this, the default was for attributes to be assignable unless they were explicitly black-listed attr_protected or any other attribute was white-listed with attr_acessible.
The easiest way here would be, i suppose, to add :document to attr_accessible.
If an attribute is not listed there, ActiveRecord will not allow you to "mass-assign" them.
Relating to my comment, you'd end up with something like this:
...
"dob(3i)"=>"18",
"document"=><#Document ...>}, # {"passportno"=>"bycucde63"} is just a Hash, not a Document, that's why it raises "ActiveRecord::AssociationTypeMismatch"
"commit"=>"Create Employee"}
In the code, like:
def create
#document = Document.create!(params[:document])
#employee = Employee.create!(params[:employee].merge(:document => #document))
I have a Page Model that has a :name attribute. I have a specific route for the Page Model with the name "home", because I want this specific Page record to be found at the root_url. This works.. but because I'm hard coding the route... I only want users with the role "super_admin" to be able to change the :name attribute, on the Page model, where the name == "home". For example, users with the "admin" role should not be able to change the :name attribute on the "home" Page.
Can I get that fine grained with CanCan?
Should I put this logic in the PageControllers update action?
Should I set the "page#show" route differently (not hard code it)?
Not sure how to do any of these.
Thanks in advance for any advice!
ability.rb
elsif user.role == "admin"
can :manage, :all
cannot :update, Page, ["name == ?", "home"] do |page|
page.name == "home"
end
end
routes.rb (I'm using friendly_id to generate a slug from the :name attribute)
match '/:slug', :to => "pages#show", :as => :slug, :via => :get
root :to => 'pages', :controllers => "pages", :action => "show", :slug => "home"
pages_controller.rb (standard)
def update
#page = Page.find(params[:id])
respond_to do |format|
if #page.update_attributes(params[:page])
format.html { redirect_to #page, notice: 'Page was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #page.errors, status: :unprocessable_entity }
end
end
end
I must admit, I've read your question three times, and I think I have answers for you...
1 - Yes, I believe so. However, I'm not convinced your ability.rb code is correct. I'd aim for something closer to this:
cannot :update, Page do |page|
page.name == "home"
end
2 - If you do load_and_authorize_resource in your controller, that should be all you need, because that will load #page for you.
class PagesController < ApplicationController
load_and_authorize_resource
def update
respond_to do |format|
if #page.update_attributes(params[:page])
format.html { redirect_to #page, notice: 'Page was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #page.errors, status: :unprocessable_entity }
end
end
end
end
3 - To me, your route looks fine. That's likely the way I'd approach it.
Hi i am creating an action to copy an existing record in a new one in rails3 which is correctly populates the values from previous record to new one but at the time i submit the form it gives the error. Here is my code sample
class SalaryStructuresController < ApplicationController
def new
#salary_structure = SalaryStructure.new
#salary_structure.salary_structure_line_items.build
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #salary_structure }
end
end
def create
#salary_structure = SalaryStructure.new(params[:salary_structure])
#salary_structure.company_id = current_company.id
#salary_structure.created_by = current_user.id
respond_to do |format|
if #salary_structure.valid?
#salary_structure.save_with_variable_payheads
flash[:success]= "Salary structure successfully created."
format.html { redirect_to(#salary_structure) }
format.xml { render :xml => #salary_structure, :status => :created, :location => #salary_structure }
else
format.html { render :action => "new" }
format.xml { render :xml => #salary_structure.errors, :status => :unprocessable_entity }
end
end
end
#action to clone the salary structure
def copy
#payheads = current_company.payheads
#users = current_company.users
#source_salary_structure = SalaryStructure.find(params[:id])
#salary_structure = SalaryStructure.new(#source_salary_structure.attributes)
#source_salary_structure.salary_structure_line_items.each do |line_item|
salary_item = SalaryStructureLineItem.new(line_item.attributes)
#salary_structure.salary_structure_line_items << salary_item
end
render :action => "new"
end
end
My model:
class SalaryStructure < ActiveRecord::Base
has_many :salary_structure_line_items
belongs_to :user
# has_many :payheads
accepts_nested_attributes_for :salary_structure_line_items, :reject_if => lambda {|p| p[:payhead_id].blank? && p[:amount].blank? }, :allow_destroy => true
#validation
validates_presence_of :effective_from_date, :for_employee
validates_presence_of :salary_structure_line_items
validates_associated :salary_structure_line_items
attr_accessible :id, :effective_from_date, :salary_structure_line_items_attributes, :amount, :total, :pay_head_type, :for_employee, :pay_head, :created_by, :updated_at, :created_at, :company_id,
:salary_structure_line_items_attributes
end
When i submit the form (press save) i got the error on salary_structure_id:
ActiveRecord::RecordNotFound in SalaryStructuresController#create
Couldn't find SalaryStructureLineItem with ID=11 for SalaryStructure with ID=
Even in parameters salary_structure_id is present :
"commit"=>"Save",
"salary_structure"=>{"salary_structure_line_items_attributes"=>{"0"=>{"amount"=>"3000.0",
"_destroy"=>"",
"salary_structure_id"=>"4",
"id"=>"11",
"payhead_id"=>"1"},
"1"=>{"amount"=>"500.0",
"_destroy"=>"",
"salary_structure_id"=>"4",
"id"=>"12",
"payhead_id"=>"2"}
i am unable to trace where i am missing something, please help me.
I have created clone file in very easy way here i have created a new action in my controller
def copy_salary_structure
#users = current_company.users.without_salary_structure
#payheads = current_company.payheads.where(:optional => false)
#old_salary_structure = SalaryStructure.find_by_id(params[:id])
#salary_structure = SalaryStructure.new
#salary_structure.company_id = #old_salary_structure.company_id
#salary_structure.created_by = current_user.id
#old_salary_structure.salary_structure_line_items.each do |line_item|
salary_structure_line_item = SalaryStructureLineItem.new(
:payhead_id => line_item.payhead_id,
:amount => line_item.amount
)
#salary_structure.salary_structure_line_items << salary_structure_line_item
end
end
and i have create a view form with same name from which i can review the record and then save it easily
I have the following code in my view:
<%= f.select :user_id, user_all_select_options, :include_blank => '--Select a name-----' %>
It displays a list of users with --Select a name----- at the top. When a user doesn't select a name from the list and leaves --Select a name----- selected I get errors because the user_id is blank.
For reference the helper method code looks like this:
def user_all_select_options
User.all.map{ |user| [user.name, user.id] }
end
My model is as follows:
class Parcel < ActiveRecord::Base
attr_accessible :parcel, :received_at, :received_by, :chilled, :courier, :location, :passed_to, :user_id, :user, :content, :localip
validates :user_id, :presence => true
belongs_to :user
end
For some reason the validation doesn't appear to be running, however if I select a user from the drop down and add a validation for an other field input the application correctly shows the user a message stating which field is incorrectly empty.
Interestingly if I leave the select drop down as --Select a name----- and keep the additional validation, an exception is thrown. It doesn't prompt for the missing fields it just errors.
Here is the record during an error (this record was from when I had a validates presence check on the location field:
{"utf8"=>"✓", "authenticity_token"=>"wM4KPtoswp3xdv8uU4UasdadNsZi9yFZmk=", "parcel"=>{"user_id"=>"", "received_by"=>"dan", "content"=>"", "chilled"=>"0", "courier"=>"", "location"=>"", "passed_to"=>"", "received_at(3i)"=>"9", "received_at(2i)"=>"2", "received_at(1i)"=>"2013", "received_at(4i)"=>"22", "received_at(5i)"=>"59"}, "commit"=>"Receive this Parcel", "action"=>"create", "controller"=>"parcels"}
Where should I start looking? The errors that show are when the controller does an unless check against the user.
The parcel controller create method looks like this:
def create
#parcel = Parcel.new(params[:parcel])
#parcel.localip = request.env['REMOTE_ADDR']
#parcel.received_by = #parcel.received_by.upcase
unless #parcel.user.mobilenumber.blank?
UserMailer.parcel_notification(#parcel).deliver
end
respond_to do |format|
if #parcel.save
format.html { redirect_to #parcel, notice: 'Parcel was successfully created.' }
format.json { render json: #parcel, status: :created, location: #parcel }
else
format.html { render action: "new" }
format.json { render json: #parcel.errors, status: :unprocessable_entity }
end
end
end
the reason why you're getting an exception when you don't select a user is this line
unless #parcel.user.mobilenumber.blank?
since the user_id is not set, #parcel.user is nil which causes the exception.
I suggest you move that inside the #parcel.save block.
def create
#parcel = Parcel.new(params[:parcel])
#parcel.localip = request.env['REMOTE_ADDR']
#parcel.received_by = #parcel.received_by.upcase
respond_to do |format|
if #parcel.save
unless #parcel.user.mobilenumber.blank?
UserMailer.parcel_notification(#parcel).deliver
end
format.html { redirect_to #parcel, notice: 'Parcel was successfully created.' }
format.json { render json: #parcel, status: :created, location: #parcel }
else
format.html { render action: "new" }
format.json { render json: #parcel.errors, status: :unprocessable_entity }
end
end
end
In my app there is an association problem, which I'm unable to fix.
My app is quite simple: There's an Article model; each article has_many comments, and each of those comments has_many votes, in my case 'upvotes'.
To explain the way I designed it, I did a comments scaffold, edited the comment models and routes to a nested resource, everything works fine. Now, I basically did the same process again for 'upvotes' and again edited model and routes to make this a nested resource within the comment nested resource. But this fails at the following point:
NoMethodError in Articles#show
Showing .../app/views/upvotes/_form.html.erb where line #1 raised:
undefined method `upvotes' for nil:NilClass
My _form.html.erb file looks like this:
<%= form_for([#comment, #comment.upvotes.build]) do |f| %>
<%= f.hidden_field "comment_id", :value => :comment_id %>
<%= image_submit_tag "buttons/upvote.png" %>
<% end %>
Why is 'upvotes' undefined in this case, whereas here:
<%= form_for([#article, #article.comments.build]) do |form| %>
rest of code
everything works totally fine? I copied the same mechanism but with #comment.upvotes it doesn't work.
My upvotes_controller:
class UpvotesController < ApplicationController
def new
#upvote = Upvote.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #upvote }
end
end
def create
#article = Article.find(params[:id])
#comment = #article.comments.find(params[:id])
#upvote = #comment.upvotes.build(params[:upvote])
respond_to do |format|
if #upvote.save
format.html { redirect_to(#article, :notice => 'Voted successfully.') }
format.xml { render :xml => #article, :status => :created, :location => #article }
else
format.html { redirect_to(#article, :notice =>
'Vote failed.')}
format.xml { render :xml => #upvote.errors, :status => :unprocessable_entity }
end
end
end
end
I'm sorry for this much code.., my articles_controller: (extract)
def show
#upvote = Upvote.new(params[:vote])
#article = Article.find(params[:id])
#comments = #article.comments.paginate(page: params[:page])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #article }
end
end
And my 3 models:
class Article < ActiveRecord::Base
attr_accessible :body, :title
has_many :comments
end
class Comment < ActiveRecord::Base
attr_accessible :content
belongs_to :user
belongs_to :article
has_many :upvotes
end
class Upvote < ActiveRecord::Base
attr_accessible :article_id, :comment_id, :user_id
belongs_to :comment, counter_cache: true
end
Upvote migration file:
class CreateUpvotes < ActiveRecord::Migration
def change
create_table :upvotes do |t|
t.integer :comment_id
t.integer :user_id
t.timestamps
end
end
end
My routes:
resources :articles do
resources :comments, only: [:create, :destroy] do
resources :upvotes, only: [:new, :create]
end
end
Sorry for that much code. If anyone might answer this, they would be so incredibly awesome!
Thank you in advance!
Why is 'upvotes' undefined in this case, whereas here:
This is because you're calling upvotes on a nil object, the comment doesn't exist yet.
Best thing to do would be looking into nested attributes:
http://guides.rubyonrails.org/2_3_release_notes.html#nested-attributes
http://guides.rubyonrails.org/2_3_release_notes.html#nested-object-forms
Your error message, says that you try call upvotes on nil. Specifically it is a part of code #comment.upvotes.build in your /app/views/upvotes/_form.html.erb view.
You have to fix show action in you ArticlesController, by adding #comment (with contents) variable.
def show
#upvote = Upvote.new(params[:vote])
#article = Article.find(params[:id])
#comments = #article.comments.paginate(page: params[:page])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #article }
end
end
Also strange things are happening in UpvotesController, in create action.
#article = Article.find(params[:id])
#comment = #article.comments.find(params[:id])
#upvote = #comment.upvotes.build(params[:upvote])
Firstly you had fetched one #article using params[:id], then you had fetched all comments of that #article (throught association), where comments id is the same as #article id. Please review your code, it is inconsistent and will not work correctly.
Everything fixed and works fine now. Took a different approach and simply used Upvote.new instead of nesting it into the comments and building associations, edited my routes as well. Implemented Matthew Ford's idea
I would suspect you have many comments on the article page, the comment variable should be local e.g #article.comments.each do |comment| and then use the comment variable to build your upvote forms.
Thanks everybody for your help!