Updating Strong Params Only - ruby-on-rails

First of all, I believe there must be some people, who already asked this question before but I don't know how can I google this problem. So, if it is duplicate I am sorry.
I am working on a social media site. I have user model, which I use to register users to the site. It validates, name, email, and password when registering.
I use the same model to make users edit their informations, like username.
This is what I have in my update controller:
def update
# Find an existing object using form parameters
#profile = User.find_by_id(current_user.id)
# Update the object
if #profile.update_attributes!(settings_profile_params)
# If save succeeds, redirect to itself
redirect_to request.referrer
else
# If save fails, redisplay the form so user can fix the problems
render('edit')
end
end
private # user_params is not an action, that is why it is private.
def settings_profile_params
params.require(:user).permit(:first_name, :last_name, :username, :school, :program, :website, :information)
end
The problem is, I only want to update strong parameters that I defined there. But I am getting an exception because of password validation. I don't know why am I getting this exception. How can I set up system to update the values in strong parameter only.
Thank you.

You can achieve this by changing you password validation. You need to add a condition on password validation.
# Password
validates :password,
:presence => {:message => 'Password cannot be blank'},
:length => {:within => 8..99, :message => 'Password length should be within 8 and 99 characters'}
:if => Proc.new { new_record? || !password.nil? }

By calling update_attributes you are implicitly invoking the same range of validations as an other update and save. You need to update on the specific params you're targeting (e.g. omitting :password).
Here, we can store that list of permitted keys in a variable that is reusable. Then we call update_attribute on each of those keys — doing so within a reduce that gives the same true/false for the switch to edit or display.
def update
# Find an existing object using form parameters
#profile = User.find_by_id(current_user.id)
# Update the object
if PERMITTED_KEYS.reduce(true) {|bool, key| bool &&= #profile.update_attribute(key, #profile.send(key)) }
# If save succeeds, redirect to itself
redirect_to request.referrer
else
# If save fails, redisplay the form so user can fix the problems
render('edit')
end
end
private
PERMITTED_KEYS = [:first_name, :last_name, :username, :school, :program, :website, :information]
# user_params is not an action, that is why it is private.
def settings_profile_params
params.require(:user).permit(PERMITTED_KEYS)
end
Having not used strong_parameters gem before, I think this would be more idiomatic to the use of the gem:
def update
# Find an existing object using form parameters
#profile = User.find_by_id(current_user.id)
# Update the object
if settings_profile_params.keys.reduce(true) {|bool, key| bool &&= #profile.update_attribute(key, #profile.send(key)) }
# If save succeeds, redirect to itself
redirect_to request.referrer
else
# If save fails, redisplay the form so user can fix the problems
render('edit')
end
end
private
# user_params is not an action, that is why it is private.
def settings_profile_params
params.require(:user).permit(
:first_name, :last_name, :username,
:school, :program,
:website, :information
)
end
Though, I still think this is a duplicate question, since it regard how to update model data without all of the defined validation. I've answered in case the update_attributes loop is felt to be a sufficiently unique solution to warrant non-duplication.

Okay, now I found the problem. First of all, #Muntasim figured out a way to solve this problem. But I actually don't need to use this solution, because there is another easy way to fix this.
In this situation, when I let users to update their profiles, rails should not validate my password or any other column in user model, if I don't ask it to. But why was it validating? Because I have validates :password in user model. Instead it has to be validates :digest_password. Because I am using bcrypt.
I don't know why :password was working fine when I register even though I used bcrypt.

Related

Proper user of strong parameters, assignments, and validators in Rails 4?

What is the proper way to set strong parameters in a Rails controller and using validators with them? I've seen several examples of doing this several different ways.
This is what a typical controller def looks like for me:
# User model
class User < ActiveRecord::Base
validates :first_name, length: { in: 2..50 }, format: { with: /[a-zA-Z0-9\s\-\']*/ }
validates :last_name, length: { in: 2..50 }, format: { with: /[a-zA-Z0-9\s\-\']*/ }
validates :email, presence: true, length: { 5..100 }, format: { with: /**email regex**/ }, uniqueness: { case_sensitive: false }
end
# Controller def
def valid_email
# Strong parameters defined here? When is an error thrown for unmatched requires/permits?
params.require(:user)
params[:user].permit(:email)
# How to prevent blank params[:user][:email] from making unnecessary database call if it's blank?
#user = User.find_by(email: params[:user][:email])
unless #user.nil?
# Should work because in permit whitelist?
#user.assign_attributes(email: params[:user][:email])
# Should not work because not in permit whitelist?
#user.assign_attributes(first_name: params[:user][:first_name])
# Should work just sending to private def
#user.assign_attributes(savable_params)
# Validate entire model
if #user.valid?
#user.save
end
end
rescue => e
log_error(e)
render text: "Something bad happened. Contact support.", status: :unprocessed_entity
end
private def savable_params
params.require(:user).permit(:email)
end
How I understand it, is the params.require and params.permit allow for whitelisting of data passed to the controller/action, but doesn't keep "other" data from being seen in the params list (if it's passed).
So, leads into a few questions:
Proper way of whitelisting in a similar scenario a top?
How would you, say for instance, make sure params[:user][:email] isn't blank ("") before trying to do a User.find_by since we know that a blank email isn't valid anyway and do not want to make an unnecessary database call?
User.valid? will validate the entire model, even though I'm interested in only the :email attribute be valid before doing a User.find_by?
If you have the uniqueness validator on email, for instance, how would you keep uniqueness from hitting the database, if params[:user][:email] is blank and we're only interested in that email isn't blank, fits within a certain length, and matches the format?
Thanks again.
So this is updating an existing user?
Just do - assuming for some reason you are not sending a user id as part of your submission:
not_found if params[:user].blank? or params[:user][:email].blank?
#user = User.find_by email: params[:user][:email] or not_found
#user.update savable_params
if #user.valid?
flash.notice = "All good"
redirect_to #user
else
# this has not been saved
flash.now.alert = #user.errors.full_messages.to_sentence
render :(whatever you call to display the form in the first place
end
and add to your application_controller:
def not_found
raise ActionController::RoutingError.new('Not Found')
end
to your questions:
as per above. Validators run on your model, they have the logic, keep your controllers simple.
I added a line for that - should work, but normally you're sending up and id of the user, so it's not something I've done. I don't bother testing it.
The validation happens on the update,. it won't hit the db unless it passes, it's a few cycles, not something I've ever worried about. You could put another pre-validation in there, to save on the db roundtrip, but if your going to do that - why not put it on the html field, and save a server round trip.
Put uniqueness constraint on the model and the db (the model should get it, but for race conditions, the db is your backstop). Nothing will hit the db unless it passes your validations first, so it should be rare.
oh, and I think you'll need:
params.require(:user).permit(:email, :first_name)
to get the other field to update.

user.save returns false in console, but true when done via the UI

I'm a programming/rails beginner, and have encountered a bug I cannot wrap my head around.
I'm using/learning about the "has_secure_password" method. When I try and create a user in my console with a mismatched password/confirm_password, the console returns false and the error is "Password confirmation doesn't match Password". But, when I try and do the same thing within the UI given the below code (+ a view), it saves just fine! Now, notice that in my "user_params" method, I accidentally forgot to permit ":password_confirmation" which is how I noticed this issue in the first place. With that ":password_confirmation" added, the view throws an error but that's not the point. Why even without this is the new User record being successfully created with a mismatched password and password confirmation, even though it doesn't save in the console?
Here is my User model:
class User < ActiveRecord::Base
has_secure_password
validates :name, presence: true
validates :email, presence: true, format: /\A\S+#\S+\z/, uniqueness: {case_sensitive: false}
validates :password, length: {minimum: 4, allow_blank: true}
end
And my User controller:
class UsersController < ApplicationController
def index
#users = User.all
end
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
redirect_to #user, notice: "Thanks for signing up!"
else
render :new
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password)
end
end
This is happening because the password_confirmation attribute is optional. When it isn't supplied to the model that has_secure_password, the model simply accepts the password.
When your password confirmation attribute isn't whitelisted in your controller via user_params, it isn't being passed to the model at all, which is why mismatches appears not to throw an error. In truth the validation isn't taking place at all.
This works in your console because it creates a user without involving a controller or strong parameter whitelisting.

Form values not returning after failed validation in Rails 4

I'm using strong parameters in my User controller in my Rails 4 application.
def new
#user = User.new
end
def create
#user = User.new(user_params)
if !#user.save
render :new
end
end
private
def user_params
params.require(:user).permit(:first_name, :last_name, :address_1, :address_2, :city, :state, :zip, :phone, :email, :password, :ssn)
end
I also have validation in the User model.
validates_presence_of :first_name, :last_name
If the user fills out my form to create their account and don't pass the validation, when the controller renders the new action, none of the previously fields the user filled out are populated, the entire form is blank. Am I missing something?
It seems you made new instance variable when validation fails.
You have to use failed instance variable in new form.
if you can't understand my answer please add your code of create action and form view.
I was using Single Table Inheritance and was pre-designating the type of user it would be. Once I removed STI, my fields were appearing in the form no problem.

A missing attribute when using { render json: }

I have an app that uses devise for login. I have a login form in a web app that authenticates a user to the database and simply returns the #user hash as a json string.
The goal is to get the users authenticate the user and retrieve their authentication_token for future use in the app to prevent the user from having to continually log in.
The problem is that I can't get authentication_token to be included in the returned json.
My user.rb Model
attr_accessible :authentication_token, :email, :password, :password_confirmation, :remember_me, :bio, :confirmed, :deposit, :email, :fri25, :mon21, :name, :paid, :picture, :sat26, :sun20, :sun27, :thur24, :tue22, :wed23
clearly includes the authentication_token symbol.
Then in the session controller I have a custom action called newApi which runs a simple authentication method and responds with the #user hash as json.
def newapi
#user = User.authenticate(params[:email],params[:password])
respond_to do |format|
format.json { render json: #user }
end
eend
Yet no matter what I do the authentication token is not included with the json response. Am I missing something obvious? Why is it not included?
Devise > 2.x seems to strip out attributes from xml and json responses. This is likely a security feature, but I'm not sure where it got introduced. The list of "protected attributes" is here:
https://github.com/plataformatec/devise/blob/master/lib/devise/models/authenticatable.rb
line 54:
BLACKLIST_FOR_SERIALIZATION = [:encrypted_password, :reset_password_token, :reset_password_sent_at,
:remember_created_at, :sign_in_count, :current_sign_in_at, :last_sign_in_at, :current_sign_in_ip,
:last_sign_in_ip, :password_salt, :confirmation_token, :confirmed_at, :confirmation_sent_at,
:remember_token, :unconfirmed_email, :failed_attempts, :unlock_token, :locked_at, :authentication_token]
and the code that initializes it is on line 122-135
The comments say you can provide your own list of blacklisted attributes or append to the existing list by using :force_except and :except, respectively.
My hacky solution would be this:
def newapi
#user = User.authenticate(params[:email],params[:password])
respond_to do |format|
format.json { render json: #user.as_json(:force_except => Devise::Models::Authenticatable::BLACKLIST_FOR_SERIALIZATION.delete_if{|x| x == :authentication_token} }
end
end
But I'm thinking there would be a way to override this list somewhere more elegantly like in an initializer. Mine works, but is likely not the best answer.
Though this is old, all I did was use with its "methods" option
to_json(methods: :arbitrary_method)
since all attributes technically become accessible methods on the model. This seemed most hassle free :D
On this page: http://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html
My workaround:
class User < ActiveRecord::Base
# include devise token_authenticatable and others
devise :token_authenticatable
# after devise finds our user (such as when logging in)
after_find :write_auth_token
# write our pseudo attribute to the model
def write_auth_token
write_attribute(:auth_token, self.authentication_token)
end
# make our pseudo attribute attr_accessible
attr_accessible :auth_token
end
I ended up solving the problem myself by using jbuilder.
Essentially you just remove the render json: #user call and use a jbuilder template.
in newapi.json.jbuilder
json.(
#user,
:authentication_token,
:email,
:password,
:password_confirmation,
:remember_me,
:bio,
:confirmed,
:deposit,
:email,
:fri25,
:mon21,
:name,
:paid,
:picture,
:sat26,
:sun20,
:sun27,
:thur24,
:tue22,
:wed23
)
Review their docs if you got this route as there are plenty of It does everything I needed and more and is the only way we do api returns now. If you've not used it before go check it out! it make API endpoints amazingly simple.
Nick Knudson's answer is also valid if you want to roll your own setup.
#Travis Todd's answer totally does the work, and yes, there might be a more elegant solution. In addition, if you want to building a private or temporary api for db migration or other similar purposes where you need 2 or more of these blacklist attributes, you might want to do something like:
def newapi
#devise_attrs = [:encrypted_password, :remember_created_at, :sign_in_count, :current_sign_in_at] #etc..
#user = User.authenticate(params[:email],params[:password]).includes(:identity)
respond_to do |format|
# I added a few other options and updated ruby syntax so that you can see how can this be customized if in need of specific user attributes or relations.
format.json { render json: #user.as_json(force_except: Devise::Models::Authenticatable::BLACKLIST_FOR_SERIALIZATION.delete_if{|x| #devise_attrs.include?(x)}, include: [:identity]), status: 200 }
end
end
Still waiting for another finger-licking elegant solution for this.

Rails 3.2, Mass Assignment, Dynamic Roles?

I have a Rails app with a user model that contains an admin attribute. It's locked down using attr_accessible. My model looks like this:
attr_accessible :name, :email, :other_email, :plant_id, :password, :password_confirmation
attr_accessible :name, :email, :other_email, :plant_id, :password, :password_confirmation, :admin, :as => :admin
And here's what my update method in my users controller looks like:
def update
#user = User.find(params[:id])
if #user.update_attributes(params[:user], :as => current_user_role.to_sym)
flash[:notice] = "Profile updated"
redirect_to edit_user_url(#user)
else
render 'edit'
end
end
I have a helper method in my application controller that passes back the role as a string:
def current_user_role
#current_user_role ||= current_user.admin? ? "admin" : "default"
end
helper_method :current_user_role
I've also set config.active_record.whitelist_attributes = true in config/application.rb.
I've verified that the current_user_role method is returning the proper value based on the current user's admin status. Rails isn't throwing a mass-assignment error. But when I try to update a user's admin status while logged in as an admin, Rails performs the update and silently ignores the admin attribute. Pulling up the user's record in the Rails console shows that the record hasn't been modified.
I have a feeling there's a Ruby- or Rails-specific issue at play that I'm not aware of. I can't locate any info on making the role dynamic. The best I could find was this.
There was an errant attr_accessor :admin in my model that was left in from a prior attempt at getting this to work. I overlooked it. Removing it fixed it.
So, the upshot is that this is a pretty simple way to get dynamic roles working in Rails 3.2.
Looks like it could be a bug in Rails 3.2
https://github.com/stffn/declarative_authorization/issues/127

Resources