This question already has answers here:
ActiveModel::ForbiddenAttributesError when creating new user
(8 answers)
Closed 4 years ago.
I'm Working on upgrading a Rails 3v project to 5v. Everything seems to be working but I get this error:
ActiveModel::ForbiddenAttributesError in ContactsController#create
ActiveModel::ForbiddenAttributesError
#contact = Contact.new params[:contact]
Here is My Model:
class Contact < ApplicationRecord
belongs_to :contactable, :polymorphic => true
attribute :contact
validates :email, :presence => true
validates :name, :presence => true
validates :body, :presence => true
validates :email, email: true, allow_blank: true
scope :are_read, -> { where("state = ? ","read") }
scope :are_unread, -> { where("state = ?","unread") }
state_machine :state, :initial => :unread do
event :reading do
transition :unread => :read
end
end
end
and
Here is my Controller:
# -*- encoding : utf-8 -*-
class ContactsController < ApplicationController
def new
#contact = Contact.new
if params[:product].present?
#contact.contactable = Product.find params[:product]
end
if params[:rental].present?
#contact.contactable = Rental.find params[:rental]
end
end
def create
# #user = User.new(user_params)
#contact = Contact.new params[:contact]
if #contact.save
UserMailer.contact_notification(#contact).deliver
redirect_to thanks_contact_path
else
render :new
end
end
def thanks
end
end
Try to the following to create a private method with Rails strong parameters under the controller
private
def contact_params
params.require(:contact).permit(:name, :email, :body)
end
Change like this
def create
#contact = Contact.new(contact_params)
....
end
instead of this
#contact = Contact.new params[:contact]
Rails 4 onwards you cannot use params directly for mass assignment.
You need to use Strong Params to whitelist the parameters.
#contact = Contact.new params[:contact]
Use
def create
#contact = Contact.new(contact_params)
...
end
private
def contact_params
params.require(:contact).permit(:contact, :params, :attributes_here) # <= Change this
end
Related
One thing that confuses me the most is when doing validation in one model with two controllers. I have a login system which register and logs users in. There both use the same model but both does not use the same amount of HTML widgets. One controller contains password, retype password, user name, first & second name and so on. The second controller uses only the user name and password fields. How would you do validation in the same model for this situation?
Thank you
here is the controller that register new users:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
session[:user_id] = #user.id
redirect_to '/cool'
else
#user = Newuser.new
#user.valid?
#user.errors.messages
render 'new'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password, :salt, :firstname, :secondname, :address, :postcode)
end
end
second controller:
class LoginsController < ApplicationController
before_filter :authorize
def index
#rentals = Rental.where(user_id: current_user.id).limit(5)
#buys = Buy.where(user_id: current_user.id).limit(5)
#users = User.where(id: current_user.id)
#buyGames = BuyGame.where(user_id: current_user.id).limit(5)
end
def destroy
#user = User.find(params[:id])
#user.destroy
redirect_to '/logout'
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update(account_params)
redirect_to '/cool'
else
render 'edit'
end
end
private
def account_params
params.require(:user).permit(:name, :email, :password, :salt, :firstname, :secondname, :address, :postcode)
end
end
Here is my model:
class User < ApplicationRecord
has_secure_password
end
One way to go is to remove validations from the model and put them in form objects. For this case, you'll have two form objects, each with its own set of validations. And you use the appropriate one in respective controllers. Something along these lines:
# logins_controller
def update
login_form = FormObjects::LoginForm.new(login_params)
if login_form.valid?
redirect_to '/cool'
else
render 'edit'
end
end
# users controller
def create
signup_form = FormObjects::SignupForm.new(user_params)
if signup_form.save
redirect_to '/cool'
else
render 'new'
end
end
# signup_form
module FormObjects
class SignupForm
include ::ActiveMode::Model
validate_presense_of :email, :password, :password_confirmation, :address, :whatever_else
def save
# create user here
end
end
end
# login_form
module FormObjects
class LoginForm
include ::ActiveMode::Model
validate_presense_of :email, :password
end
end
You can simply specify validations on actions, that is:
validates :first_name, presence: true, on: :create # which won't validate presence of first name on update or any other action
I believe the trick you are looking for is to define validation actions on create/update of the model. Something roughly along these lines:
class User < ActiveRecord::Base
# These are example validations only; replace with your actual rules.
validates :password, confirmation: true
validates_presence_of :username
validates :first_name, presence: true, format: {with: /.../}, on: create
validates :last_name, presence: true, format: {with: /.../}, on: create
end
...However, I am unclear why you would want to do this in your specific example. It would be advisable to always run all validation checks on fields like first_name, to help maintain data integrity.
I have few issues about rails app I've made:
1. I've tried to use paperclip and carrierwave, but none of them save the file I want to attach to a specified folder. Attachment should be joined to user model:
class UsersController < ApplicationController
def new
#users = User.new
end
def create
#user = User.new(user_params)
if #user.save
session[:user_id] = #user.id
redirect_to '/'
else
redirect_to '/signup'
end
end
private
def user_params
params.require(:user).permit(:first_name, :last_name, :email, :PESEL, :phone, :cv, :password)
end
end
user controller:
class UsersController < ApplicationController
def new
#users = User.new
end
def create
#user = User.new(user_params)
if #user.save
session[:user_id] = #user.id
redirect_to '/'
else
redirect_to '/signup'
end
end
private
def user_params
params.require(:user).permit(:first_name, :last_name, :email, :PESEL, :phone, :cv, :password)
end
end
paperclip:
class AddAttachmentCvToUsers < ActiveRecord::Migration
def self.up
change_table :users do |t|
t.attachment :cv
end
end
def self.down
remove_attachment :users, :cv
end
end
What is the task - I want to have users 'joined' with the attachment, and then joined with jobapps model - so it will create new job app id, where the empoyee can decide if it is accepted or not. And I have a trouble with attaching a CV file.
I have a users_controller and a user_steps_controller which has three steps :business, :payment and :login
In the user.rb model
class User < ActiveRecord::Base
validates_presence_of :fname, :lname, :email, :mob, :country, :state, :suburb, :postal ,:add
end
while checking validation if i put some random values then also it is giving errors
Fname can't be blank
Lname can't be blank
Email can't be blank
Mob can't be blank
Country can't be blank
State can't be blank
Suburb can't be blank
Postal can't be blank
Add can't be blank
Please help me out
This is my users_controller
def new
#user = User.new
end
def create
#user = User.new(params[:id])
if #user.save
session[:user_id]= #user.id
#user.update_attributes(user_params )
redirect_to user_steps_path
else
render :new
end
end
private
def user_params
params.require(:user).permit( :fname, :lname, :email, :mob, :gender_male, :gender_female, :country, :state, :suburb, :postal ,:add, :cmpyname, :abnacn, :cmpyadd, :cmpydet,:cash, :paypal,:bsb,:usrname,:password_hash, :password_salt, :selcat, :protit, :prodes)
end
user.rb
class User < ActiveRecord::Base
validates :fname, :lname, :email, :mob, :country, :state, :suburb, :postal ,:add, :presence => true
attr_accessor :current_step
validates_presence_of :cmpyname, :abnacn, :cmpyadd, :cmpydet, if: -> { current_step?(:business) }
validates_presence_of :usrname,:password_hash, :password_salt, :selcat, :protit, :prodes, if: -> { current_step?(:login) }
def current_step?(step_key)
current_step == step_key
end
end
user_steps_controller
class UserStepsController < ApplicationController
include Wicked::Wizard
steps :business, :login, :payment
def show
#user = current_user
render_wizard
end
def update
#user = current_user
params[:user][:current_step] = step
#user.update_attributes(user_params )
render_wizard #user
end
private
def user_params
params.require(:user).permit( :cmpyname, :abnacn, :cmpyadd, :cmpydet,:cash, :paypal,:bsb,:usrname,:password_hash, :password_salt, :selcat, :protit, :prodes)
end
end
user.rb
def current_step?(step_key)
current_step == step_key
end
user_steps_controller.rb
class UserStepsController < ApplicationController
include Wicked::Wizard
steps :personal, :social
def show
#user = current_user
render_wizard
end
def update
#user = current_user
params[:user][:current_step] = step
#user.attributes = user_params
render_wizard #user
end
private
def redirect_to_finish_wizard(options = nil)
redirect_to root_url, notice: "Thank you for signing up."
end
def user_params
params.require(:user).permit(:name, :current_step, :date_of_birth, :bio, :twitter_username, :github_username, :website)
end
end
This method will work for you.
Your real problem is here:
#user = User.new(params[:id])
I'm assuming params[:id] is nil, because otherwise that would fail. Basically, your instantiating a User with no data supplied and trying to save it. So clearly those validations you have supplied will fail. If you're actually submitting a form with user data you need to pass user_params you've already defined as follows:
#user = User.new(user_params)
If you need to have validations occur on your User model in different steps, you'll need to have the validations run conditionally dependent on the state of the form:
class User
attr_accessor :current_step
validates_presence_of :business_related_attr, if: -> { current_step?(:business) }
def current_step?(step_key)
current_step.blank? || current_step == step_key
end
end
I have a "generic" controller in charge of managing all public pages and actions
class PublicController < ApplicationController
def index
end
def contact
#contact = Contact.new(contact_params)
end
private
def contact_params
params.require(:contact).permit(:name, :email, :question, :subject)
end
end
But when I want to access the "contact us" link I have the following error
param is missing or the value is empty: contact
Is it possible to operate strong parameters inside a "generic" controller or should I only use them as part of a controller named "Contact" ?
That looks like the error is because you've not got a contact parameter in the parameters hash. You want something more like the following:
def contact
#contact = Contact.new
end
def send_contact
#contact = Contact.new(contact_params)
end
private
def contact_params
params.require(:contact).permit(:name, :email, :question, :subject)
end
Or
def index
#contact = Contact.new
end
def contact
#contact = Contact.new(contact_params)
end
private
def contact_params
params.require(:contact).permit(:name, :email, :question, :subject)
end
Essentially you should only be calling contact_params on the action you're posting to.
I've got a problem with validators. I have a "contact" model which contains two fields firstname and lastname and I want both required on CREATE and UPDATE method. When I create a record with no data, the server return me a 422 and do the rollback. This is ok. But when I update a record the server don't return the error 422 although the server does the rollback. And I need the return of the error to manage it on the client side.
So I use validators like this :
class Contact < ActiveRecord::Base
validates :lastname, presence: true
validates :firstname, presence: true
end
and my controller is:
class ContactsController < ApplicationController
respond_to :json
def index
respond_with Contact.all
end
def create
respond_with Contact.create(contact_params)
end
def show
respond_with Contact.find(params[:id])
end
def edit
respond_with Contact.find(params[:id])
end
def update
respond_with Contact.find(params[:id]).update(contact_params)
end
def destroy
respond_with Contact.find(params[:id]).destroy
end
private
def contact_params
params.require(:contact).permit(:lastname, :firstname, :position)
end
end
I have a serializer:
class ContactSerializer < ActiveModel::Serializer
attributes :id, :lastname, :firstname, :created_at, :updated_at
end
Someone could help me, please ?
Thanks by advance.
Contact.find(params[:id]).update(contact_params)
returns a Boolean, hence you are telling Rails to render a boolean (which will render a 200 with the boolean serialized as JSON).
Same for destroy. You need to pass the instance.
def update
contact = Contact.find(params[:id])
contact.update(contact_params)
respond_with contact
end
def destroy
contact = Contact.find(params[:id])
contact.destroy
respond_with contact
end
It's also a good habit to extract the finder in a before_action.
before_action :find_contact
def update
#contact.update(contact_params)
respond_with #contact
end
def destroy
#contact.destroy
respond_with #contact
end
protected
def find_contact
#contact = Contact.find(params[:id])
end
You can refactor the other actions to remove the duplicate finder.
you can try the following code, it could be tempo fix
def update
#contact = Contact.find params[:id]
#contact.update contact_params
if #contact.errors.count > 0
# do you like
else
respond_with #contact
end
end