How to use strong parameters on ruby on rails? - ruby-on-rails

I am new to ruby on rails 4, and I am trying to use strong parameters to require that a "project" exists before adding a "role" to a project. The "role" itself requires a "project" to be associated to.
The issue that I am having is that with my current code, I am getting the error
"undefined methodpermit' for "1":String"` - how can I resolve this???
The error is identified in my "roles" controller -->
private
def role_params
params.require(:project_id).permit(:role)
end
def project
#project ||= Project.find(params[:project_id])
end`
My create method in the controller is
def create
#role = project.roles.create(role_params)
new_was_successful = #role.save
end
the model is:
class Role < ActiveRecord::Base
belongs_to :project
validates :project_id , :presence => true
end
What am I doing wrong??

Update
def role_params
params.require(:project_id).permit(:role)
end
to
def role_params
params.require(:role).permit(:project_id) ## if more fields are present in role model then add them as arguments to permit
end
You are getting error because you have set the strong parameters incorrectly. In the params hash, you would get something like this:
Example :
"role"=>{"project_id"=>1,...} ### ... refers to other fields in role model, if present
EDIT
Update your create action as below
def create
#role = project.roles.create(role_params)
if #role.save
redirect_to #role, notice: 'Role was successfully created.'
else
render action: 'new'
end
end

Related

How to add default values to nested items in a Rails controller?

In the Sign up form of my Rails 6 application an Account with a nested User can be created.
class AccountsController < ApplicationController
def new
#account = Account.new
#account.users.build(
:owner => true,
:language => "FR"
)
end
def create
#account = Account.new(account_params)
if #account.save
redirect_to root_url, :notice => "Account created."
else
render :new
end
end
private
def account_params
safe_attributes = [
:name,
:users_attributes => [:first_name, :last_name, :email, :password, :owner, :language]
]
params.require(:account).permit(*safe_attributes)
end
end
What is the best way to define default values on the new user here?
Right now, I use hidden_fields for the default values in my sign up form thus making them publicly available. This is of course not what I want because it's very insecure.
Is there a better way to deal with this?
I know that there's Rails' with_defaults method but I couldn't get it to work on nested items so far.
try with:
account_params[:users_attributes] = account_params[:users_attributes].with_defaults({ first_name: 'John', last_name: 'Smith'})
in first line of create action

How to make a model callback conditional based upon controller actions [:create, :update]?

I wanted to do something like this in goal.rb
before_save :set_tag_owner,:if => [:create, :update]
def set_tag_owner
# Set the owner of some tags based on the current tag_list
set_owner_tag_list_on(self.user, :tags, self.tag_list)
self.tag_list = nil
end
I want this method to work before save for only the create and update actions of the goals_controller.
Otherwise I run into the problem with the tagging that when a goal is marked as accomplished the tag then disappears because set_tag_owner is setting its tags to nil.
def mark_accomplished
#goal.update(accomplished: true)
end
def create
#goal = current_user.goals.build(goal_params)
#goal.save
respond_modal_with #goal, location: root_path, notice: 'Goal was successfully created! Go chase those dreams!'
end
def update
#goal.update(goal_params)
respond_modal_with #goal, location: root_path
end
I need this line though self.tag_list = nil because without it a tag is double rendered
I also tried applying that goal model logic inside the controller via a before_action callback, but I was getting an undefined error even if I changed self to #goal.
Another way to do this is to add an attr_accessor to your model and use that to stop the before_save
An example
class Goal < ActiveRecord::Base
attr_accessor :dont_set_tag_owner
before_save :set_tag_owner, :unless => dont_set_tag_owner
def set_tag_owner
# Set the owner of some tags based on the current tag_list
set_owner_tag_list_on(self.user, :tags, self.tag_list)
self.tag_list = nil
end
end
Then, in the controller
def mark_accomplished
#goal.update(accomplished: true, :dont_set_tag_owner => true)
end
And, just to give you one more option - depending on your needs for updated_at, you can also do this
def mark_accomplished
#goal.update_column(accomplished: true)
end

forbidden attribute error rails 4 Carrierwave

Have a issue with a controller file which i have narrowed down to a method
Controller
def create
#gallery = Gallery.new(params[:gallery])
if #gallery.save
flash[:notice] = "Successfully created gallery."
redirect_to #gallery
else
render :action => 'new'
end
end
private
def gallery_params
params.require(:gallery).permit(:name, :gallery, :gamepic)
end
end
the problem is that there is no attr_accessible in the controller or the model
model
class Gallery < ActiveRecord::Base
has_many :gamepics
private
def gallery_params
params.require(:gallery).permit(:name, :gallery, :gamepic)
end
end
Try passing the strong parameters method into Gallery.new instead of params[:gallery].
My understanding of this is that, the hash returned from that method is what should be used anyways. So you'd have:
#gallery = Gallery.new(gallery_params)
If you only need certain params from your :permit call, just make a new strong params method and use that one.

How to validate foreign keys in Rails validations?

In my Rails app I have users who can have many projects which in turn can have many invoices.
How can I make sure that a user can only create an invoice for one of his projects and not for another user's projects?
class Invoice < ActiveRecord::Base
attr_accessible :number, :date, :project_id
validates :project_id, :presence => true,
:inclusion => { :in => ????????? }
end
Thanks for any help.
class InvoicesController < ApplicationController
def new
#invoice = current_user.invoices.build(:project_id => params[:project_id])
end
def create
#invoice = current_user.invoices.build(params[:invoice])
if #invoice.save
flash[:success] = "Invoice saved."
redirect_to edit_invoice_path(#invoice)
else
render :new
end
end
end
I think that shouldn't be on a validation. You should ensure the project the user selected is one his projects.
You could do something on your controller like:
project = current_user.projects.find params[:project_id]
#invoice = Invoice.new(project: project)
# ...
Your create action could look something like this.
def create
#invoice = current_user.invoices.build(params[:invoice])
#invoice.project = current_user.projects.find params[:invoice][:project_id]
if #invoice.save
flash[:success] = "Invoice saved."
redirect_to edit_invoice_path(#invoice)
else
render :new
end
end
project_id is "sensitive" attribute - so remove it from attr_accessible. You are right that you should not believe params from the form and you must check it.
def create
#invoice = current_user.invoices.build(params[:invoice])
# #invoice.project_id is nil now because this attr not in attr_accessible list
#invoice.project_id = params[:invoice][:project_id] if current_user.project_ids.include?(params[:invoice][:project_id])
if #invoice.save
flash[:success] = "Invoice saved."
redirect_to edit_invoice_path(#invoice)
else
render :new
end
end
If user tries to hack your app and change project_id to not owned value then method create render partial new with invalid #invoice. Do not forget to leave the validation of project_id on presence.
If you get exception Can't mass-assign protected attributes... there are several ways what to do. The simplest ways are:
1. remove line from environment configs (development, test, production)
# Raise exception on mass assignment protection for Active Record models
config.active_record.mass_assignment_sanitizer = :strict
2. Reject sensitive parameters from params before assigning.
# changes in method create
def create
project_id = params[:invoice].delete(:project_id)
#invoice = current_user.invoices.build(params[:invoice])
#invoice.project_id = project_id if current_user.project_ids.include?(project_id)
...
end
OK, luckily I managed to come up with a solution of my own this time.
I didn't make any changes to my controller ("let's keep 'em skinny"), but added a validation method to my model instead:
class Invoice < ActiveRecord::Base
attr_accessible :number, :date, :project_id
validates :project_id, :presence => true,
:numericality => { :only_integer => true },
:inclusion => { :in => proc { |record| record.available_project_ids } }
def available_project_ids
user.project_ids
end
end
I am not sure if this is good or bad coding practice. Maybe someone can shed some light on this. But for the moment it seems pretty safe to me and I haven't been able to hack it in any way so far.

How to add current_user to user_id to form_for in rails?

I have a form for creating materials (title, description and content - all basic). The form saves these details just fine but it doesn't save the user_id, which should be the user_id of the current_user. How do I do this? It must be easy but nothing has worked so far.
def create
#material = Material.new(params[:material])
if #material.save
flash[:success] = "Content Successfully Created"
redirect_to #material
else
render 'new'
end
end
def create
#material = Material.new(params[:material])
#material.user_id = current_user.id if current_user
if #material.save
flash[:success] = "Content Successfully Created"
redirect_to #material
else
render 'new'
end
end
There are a few different ways to do it depending on how you have your application setup. If there is a relationship between the user and materials (User has many materials), you could use that in your controller:
def create
#material = current_user.materials.new(params[:material])
# ...
end
If you don't have that relationship, I would still recommend setting it in the controller as opposed to a hidden field in the form. This will be more secure because it won't let someone tamper with the user id value:
def create
#material = Material.new(params[:material].merge(user_id: current_user))
# ...
end
Assuming you are saving the login users's object in the current_user following will work for you
#material = Material.new(params[:material])
#material.user_id = current_user.id
if #material.save
With Rails 5 and parameters needing to be permitted before objects are created, this is the simplest way to merge the current_user into the params, kudos to #Peter Brown in his answer:
def create
#discussion = current_user.materials.new(new_material_params)
# ...
end
private
def new_material_params
params.require(:material).permit(:title, :description,: content)
end
If you have nested object creation using accepts_nested_attributes_for, you need to manually merge deep into the association parameters:
class User < ApplicationRecord
has_many :discussions # Used to associate User with Discussion later
end
class Comment < ApplicationRecord
belongs_to :user
end
class Discussion < ApplicationRecord
belongs_to :user
has_many :comments
accepts_nested_attributes_for :comments
end
class DiscussionsController < ApplicationController
def create
# Merge the params[:discussion][:user_id] by using the relationship's #new
#discussion = current_user.discussion.new(new_discussion_params)
end
private
# Sanitized params for creation, not editing
def new_discussion_params
params.require(:discussion)
.permit(:title, :user_id,
comments_attributes: [:id, :content, :discussion_id, :user_id])
.tap do |discussion_params|
# Require the association parameters, and if they exist,
# set :user_id for each.
discussion_params.require(:comments_attributes).each do |i, comment|
comment.merge!(user_id: current_user.id)
end
end
end
end
Heads up: Setting (or overwriting!) what will be params[:discussion][:comments_attributes]["0"][:user_id] works fine for creation. But if you allow editing deep hierarchies in addition to creation, make sure you don't accidentally overwrite all the :user_ids with the current user.

Resources