I have this controller:
class UsersController < ApplicationController
load_and_authorize_resource
def create
#user.save
respond_with #user
end
def update
#user.update user_params
respond_with #user
end
def destroy
#user.destroy
respond_with #user
end
private
def user_params
permitted_keys = [:name,
:email,
:password,
:password_confirmation,
:lock_version]
permitted_keys << :role if can? :edit_role, #user
params.require(:user).permit(permitted_keys)
end
end
In my ability model:
can :edit_role, User do |user|
user.new_record?
end
The problem is, that when creating a user (#new or #create action), #user is nil. So the role can't be set, as it will not be added to the permitted_keys on #create.
How can this be solved? I could change my controller to:
permitted_keys << :role if action_name == 'create'
But I don't like this, as the Ability can be tested much more easily than this.
Another workaround would be this:
permitted_keys << :role if can? :edit_role, #user || User.new
But this feels redundant.
Anybody has a better idea? And by the way - I'm quite surprised that passing nil as object to can? is allowed and doesn't raise an error. It seems that it doesn't even propagate to the actual configuration in Ability, as otherwise user.new_record? would raise a NoMethod error or similar.
The CanCan docs prescribe defining abilities with blocks a little differently than how you've done it. They suggest using a guard instead of a block for the case you described (when the instance is nil):
# don't do this
can :edit_role, User do |user|
user.new_record? # this won't be called for User.accessible_by(current_ability, :edit_role)
end
# do this
can :edit_role, User if user.new_record?
I'd try this. For other information and to see where I got this example, check these docs
It seems that using CanCanCan gem, the user_params method is called before the #user variable is created; this kind of makes sense, as user_params is needed to create the #user variable, or at least it is needed to assign the values from the form.
If CanCanCan would assign a new #user variable (of type User, of course) before assigning the values from the form, the following code would work on #create:
private
def user_params
permitted_keys = [:name,
:email,
:password,
:password_confirmation]
permitted_keys << :role if can? :edit_role, #user
permitted_keys << :disabled if can? :disable_user, #user
params.require(:user).permit permitted_keys
end
But as it doesn't do that apparently, the following workaround works for me:
private
def user_params
permitted_keys = [:name,
:email,
:password,
:password_confirmation]
auth_object = #user || User.new
permitted_keys << :role if can? :edit_role, auth_object
permitted_keys << :disabled if can? :disable_user, auth_object
params.require(:user).permit permitted_keys
end
Related
I'm upgrading from Rails 4 to 4.2. I have a users controller that looks something like this:
user_controller.rb
class UsersController < ApplicationController
def new
end
def edit
end
protected
def create_with_token
#user.attributes = params[:user]
#user.validate_password = true
if #user.save
else
render 'new'
end
end
private
def user_params
params.require(:user).permit(
:email, :first_name, :last_name, :password, :more_attributes
)
end
end
I'm not sure yet if this has something to do with strong_params. But as far as I know params[:user] returns a hash. So why do I get ArgumentError: When assigning attributes, you must pass a hash as an argument. on #user.attributes = params[:user]?
I checked the rails code and found:
def assign_attributes(new_attributes)
if !new_attributes.respond_to?(:stringify_keys)
raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
end
return if new_attributes.empty?
attributes = new_attributes.stringify_keys
_assign_attributes(sanitize_for_mass_assignment(attributes))
end
From here, assuming #assign_attributes replaced #attributes (It's actually an alias for it. I can infer that params[:user] is not responding to #stringify_keys. But why? How? What's the work around?
Your params[:user] is an instance of ActionController::Parameters, I guess... so you just need to switch:
#user.attributes = params[:user]
To
#user.attributes = user_params
I do not want to allow the user to update their :name. I found this question for Rails version 3 but the answer does not work for me. I am using Rails 4. I might just have the wrong syntax. I couldn't get #user.update_attributes(params[:user]) to work. Only: #user.update_attributes(user_params). I am not including :name on my edit form, but my understanding (??) is that a user could still pass it through the browser themselves and with my current code they could change :name.
class UsersController < ApplicationController
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
#user.update_attributes(user_params)
if #user.save
flash[:success] = "Profile updated"
redirect_to #user
else
flash[:danger] = "Profile update Unsuccessful"
redirect_to 'edit'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :email_confirmation, :password, :password_confirmation)
end
There's more than one way to assure users won't tamper with your business rules.
As you're already using Rails 4, you can take advantage of strong parameters to deny access to the :name attribute.
You can have different sets of rules for each controller action:
def create_user_params
params.require(:user).permit(:name,:email, :email_confirmation, :password, :password_confirmation)
end
def update_user_params
params.require(:user).permit(:email, :email_confirmation, :password, :password_confirmation)
end
#user.create(create_user_params)
#user.update_attributes(update_user_params)
drying up:
def allowed_update_attrs
[:email, :email_confirmation, :password, :password_confirmation]
end
def allowed_create_attrs
allowed_update_attrs + [:name]
end
def user_params
params.require(:user)
end
#user.create user_params.permit(*allowed_create_attrs)
#user.update_attributes user_params.permit(*allowed_update_attrs)
There are other ways of accomplishing the same thing like tapping into already permitted attributes, but this way seemed simpler.
I am making a basic account setup and to try to learn how the database stuff works. I have been running into this error constantly, and I have no idea how to make it disappear. I have my stuff named as U, so the URL will be easier to type a username like Reddit has it example.com/u/username
The Error is uninitialized constant UController::User_param
It highlights this code: #user = U.new(User_param)
Controller:
class UController < ApplicationController
def index
#users = U.all
end
def show
end
def create
#user = U.new(User_param)
if #user.save
redirect_to :action => 'list'
else
#user = U.all
render :action => 'new'
end
end
def User_param
params.require(:Us).permit(:id, :email, :password, :created_at, :updated_at)
end
def new
#user = U.new
end
def edit
end
end
Routes:
resources :u
U Model:
class U < ActiveRecord::Base
end
In Rails you don't capitalize methods, only constants and classes. change User_param to user_params along with the method and that should work. I made params plural since it is clearer and easier to understand
Also, change the user_param method to this:
def user_params
params.require(:u).permit(:id, :email, :password, :created_at, :updated_at)
end
The .require(:u) doesn't need to be plural as you had it.
users_controller.rb
class UsersController < ApplicationController
include UsersHelper
def new
#user = User.new
end
def create
if isUsernameTaken?
render 'new'
elsif isEmailTaken?
render 'new'
else
#user = User.new(user_params)
if #user.save
else
render 'new'
end
end
end
private
def user_params
params.require(:user).permit(:username,:email,:password,:password_confirmation)
end
end
users_helper.rb
module UsersHelper
def isUsernameTaken?
!(User.find_by(username: params[:username]).nil?)
end
def isEmailTaken?
!(User.find_by(email: params[:email]).nil?)
end
end
The problem is isUsernameTaken? or isEmailTaken? never gets executed and always the else part gets executed even if I give same username again. Why is this happening?
This is because params[:username] and params[:email] are always blank in your controller. They should be params[:user][:username] or user_params[:username].
Anyway, those kind of checks belongs to the model, not the controller and there already are validators to do exactly what you want:
class User < ActiveRecord::Base
validates :email, :username, uniqueness: true
end
I am trying to generate and save a random string of characters to a user field called railsid. I have overridden the registrations_controller.rb for the devise user. I would like this randomly generated string of characters to be saved into the user object when the object is created. When I go into the rails console to see if the user has a railsid saved, it returns nil.
registrations_controller.rb
class Lenders::RegistrationsController < Devise::RegistrationsController
before_filter :update_sanitized_params
def new
super
end
def create
#user = User.new(params[:user])
#user.railsid = '%010d' % rand(10 ** 10)
super
end
def edit
super
end
def update
super
end
def authenticate_scope!
send(:"authenticate_user!", :force => true)
self.resource = send(:"current_user")
end
private
def update_sanitized_params
devise_parameter_sanitizer.for(:sign_up) {|u| u.permit(:email, :password, :password_confirmation, :type, :confirmed_at, :first_name, :last_name, :railsid)}
end
def after_sign_up_path_for(resource)
root_path
end
end
Shortly after posting this I tried the update_attribute method and it worked perfectly.
def create
super
resource.update_attribute(:railsid, '%010d' % rand(10 ** 10))
end