Validating all attributes except one in Rails - ruby-on-rails

I'm trying to do a custom validation in my model using valid? method. I need to run all validations of this model, except the password.
Something like this:
#resource = SalesPartner.new(permitted_params.merge(parent: current_sales_partner))
respond_to do |format|
if #resource.valid?(except: :password)
#resource.generate_authentication_token
SalesPartnerMailer.mail_new_sales_partner(#resource.email,
{ auth_token: #resource.authentication_token, id: #resource.id, level: #resource.level }
).deliver
flash[:success] = "#{#resource.level} criado com sucesso."
format.html { render "#{#path}/index" }
else
logger.log_and_alert_save_error(#resource, flash, "Ocorreu um erro ao salvar.")
format.html { render "#{#path}/new" }
end
end
Is that possible?
Thanks everyone!

Any way here is a way that you can do with the help of context. Say you have a User model and you want to validates some fields.
validates_presence_of :name,: email, on: :special_context
validates_presence_of :name,:email, :password
With the above 2 lines, you can now do like below
#user.valid?(:special_context)
The above will validates name and email fields. And if you write now,
#user.valid?
This will perform the presence validations on the name, email and password fields as you wrote.
Read valid?(context = nil) to understand the context.
Runs all the validations within the specified context. If the argument is false (default is nil), the context is set to :create if new_record? is true, and to :update if it is not.
Validations with no :on option will run no matter the context. Validations with some :on option will only run in the specified context.
Check this documentation also as an official example.

Context could really help you, but following maybe not.
validates_presence_of :name,: email, on: :special_context
validates_presence_of :name,:email, :password
This is because when you use validates_presence_of without specifying its context, it will be applied to all context(nil). Please see ref here So, maybe you could try as follow code:
validates_presence_of :name, :email, on: :special_context
validates_presence_of :name, :email, :password, on: [ :create, :update]

Just found one more mechanism to skip some validation on specified context, because specify all other context is not always suitable:
validates_presence_of :name, :email, on: :special_context
validates_presence_of :name, :email, :password, if: -> {Array(validation_context).exclude?(:special_context)}
note: validation_context can be array.

Related

Show information after record creation on ActiveAdmin

I want to let the admin user see a particular message after or before or during he creats a new record.
I need either an alert box after he created the new record, or to change the current confirmation message just for the user model, or to add a small text in the form specifying this.
I can't seem to find any of the ways.
Thank you
You need to use "notice:". In my case, after saving new "admin_user", I am checking for "resource". If it is "valid", then "redirect_to" with a "message". ... This always works for me.
ActiveAdmin.register AdminUser do
....
....
permit_params :first_name, :last_name, :email, :password
def create
#admin_user = AdminUser.new( admin_user_params )
#admin_user.save
if resource.valid?
redirect_to collection_url, notice: 'Creation Success'
else
flash[:alert] = 'Creation Failed'
render :new
end
end
private
def admin_user_params
params.require(:admin_user).permit(:id, :first_name, :last_name, :email, :password)
end
end
you can modify the flash message with an after_create callback for that case, something like this
ActiveAdmin.register User do
permit_params :name, :email, :password
after_create do |user|
flash[:notice] = "User has been created with the default password" if user.valid?
end
end

Updating Strong Params Only

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.

Rails: update user but password is invalid

I have a payment api that takes bank account info and user info. I catch the api response and use ajax to send the infomation into my controller where I try to save the information to my user. When I save I get the error Validation failed: Password can't be blank, Password is invalid: Any ideas?
Bank Controller:
def addbank
#user = current_user
#user.phone_number = params[:phone_number]
#user.birth_year = params[:year]
#user.bank_uri = (params['bank_acct_uri'])
#user.save! # <------- ERROR here!
# Code was removed to be more clear
end
User Controller:
def update
# update user controller has been commented out but the error is still there
end
User Model:
class User < ActiveRecord::Base
attr_accessible :email,:password,:password_confirmation,:phone_number,:birth_year
attr_accessor :password
before_save :encrypt_password
before_save { |user| user.email = email.downcase }
VALID_PASSWORD_REGEX = # some reg-expression
VALID_PHONE = # some reg-expression
validates_confirmation_of :password
validates :password, presence: true, format:{ with: VALID_PASSWORD_REGEX }
validates :phone_number, format: { with: VALID_PHONE }, if: :phone_number
end
Edit: Why is saving user not hitting my update user controller?
If you want to avoid the validation of one particular field (password in your case), but you want to do all the other validations (for example phone number), you can do something like this:
attr_accessor :skip_password
validates :password, presence: true, format:{ with: VALID_PASSWORD_REGEX }, unless: :skip_password
Then, in your controller, you could do:
def addbank
#user = current_user
#user.skip_password = true # We don't want to validate this..
#user.phone_number = params[:phone_number]
#user.birth_year = params[:year]
#user.bank_uri = (params['bank_acct_uri'])
#user.save! # <------- ERROR here!
# Code was removed to be more clear
end
Do this at your own risk ~~
Where are you storing the encrypted password?
If you store it in password then it will fail validation every save because it doesn't equal password_confirmation.
I'd recommend putting the password in a separate field.
#from the User Model
attr_accessor :password, :password_confirmation
validates_presence_of :password, :on => :create
validates_confirmation_of :password
def password=(password)
#password = password
self.password_digest = BCrypt::Password.create(#password, :cost => 14)
end
def authenticate(password)
BCrypt::Password.new(self.password_digest) == password
end
This way the password gets hashed and saved to password_digest and won't trigger a validation error on save.
You can try to save without validation:
#user.save(:validate => false)
UPDATE:
if !#user.valid? && #user.errors[:phone_number].any?
#do not save
else
#user.save(:validate => false)
end
I'll post this as I am about 95% certain this is the cause, but I apologize if I'm off.
I believe the cause of this is because the user's password is indeed blank. If you look at your database, you'll see a column probably called encrypted_password, which is never directly accessed via your model, nor is it ever de-crypted into your accessible password and password_confirmation attributes.
In order to update the user, you will have to enter re-enter the password, or use the save(false) method to (potentially dangerously) bypass validations.

Rails Password Change

I'll start by telling you how I want my settings page set up.
I want users to be able to change their settings without requiring a password, and that's how it is set up now with this as the user model
validates :password, presence: true, length: { minimum: 6 }, :on => :create
validates :password_confirmation, presence: true, :on => :update, :unless => lambda{ |user| user.password.blank? }
This makes it so user's can change all of their settings without requiring a password (I know some might frown on this). But i want users to be able to change their passwords on the page like so...User clicks Change Password, a modal pops up, and users have to give their current password and then the new password and a confirmation of the new one. (I know how to do modal, i just want to know how do password reset).
Does this make sense?? I believe the way Pinterest does it is a good example (although they use Python I think)
My suggestion is to use a form object:
app/forms/change_password_form.rb
class ChangePasswordForm
extend ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::Validations
# Add all validations you need
validates_presence_of :old_password, :password, :password_confirmation
validates_confirmation_of :password
validate :verify_old_password
attr_accessor :old_password, :password, :password_confirmation
def initialize(user)
#user = user
end
def submit(params)
self.old_password = params[:old_pasword]
self.password = params[:password]
self.password_confirmation = params[:password_confirmation]
if valid?
#user.password = password
#user.password_confirmation = password_confirmation
#user.save!
true
else
false
end
end
def verify_old_password
self.errors << "Not valid" if #user.password != password
end
# This method is required
def persisted?
false
end
end
In controller initialize the form object #pass_form = ChangePasswordForm.new(current_user) and use the object in your modal: form_for #pass_form... and add the old_password, password and password_confirmation fields.
And finally, for example, in the update action:
#pass_form = ChangePasswordForm.new(current_user)
if #pass_form.submit(params[:change_password_form])
redirect_to some_path
else
render 'new'
end
I haven't tested this code, but you get the idea. Take a look to this Railscasts.

Rails 4.0 email addrese issues

Well I decided to try a different approach. Right now I have pages that are only accessible by number ids instead of usernames or emails. I would like it to be accessible by email addreses instead. I tried to override them but it failed in rails 4.0 and I tried the find_by_email command which also failed. This is the error message that I get: ActiveRecord::RecordNotFound in AccountsController#show.
The only method that did work is find(params[:id]) which only works for accounts that have id's attached them and fails completly if it is null.
Is there any other method to solve this?
Rails Controller
def show
#puts "****************************************"
#puts params
if #account.nil?
render "shared/404"
else
#if !current_account.nil?
respond_with #account
#else
# render "shared/403"
#end
end
def load_findaccount
#params[:id] remains fixed but find_by_id changes to username
#account = Account.find(params[:id])
#user_path(user)
Account.rb
class Account < ActiveRecord::Base
#def to_param # overridden
# email
#end
validates :first_name, :presence => true
validates :last_name, :presence => true
validates :email, :presence => true, :uniqueness =>{:case_sensitive => false}
end
You can use this Account.where(:email => "example#pop.com"). This will retrieve the records for that email id which I think will be at max one record in your code as email id is unique in your model.
I decided to resolve this on my own and use just Rails 3.2. So this will end it for this issue.

Resources