How to apply validation conditionally in Rails? - ruby-on-rails

class Test < ActiveRecord::Base
def skip_validation
if self.type == 'A'
# skip all validation
else
# Do notihng.
end
end
Note : In my test model, I have added validations and callback. I need to apply these validations only if above condition matches.
Can I use object.save(validates :false)?

You can pass an :if option to do so:
validates_presence_of :password, :if => :something_is_true?
And :something_is_true is a method actually in your method where you can describe your logic.
class Test < ActiveRecord::Base # Though I would never name it 'Test'
validates_presence_of :password, :if => :should_validate_password?
def should_validate_password
# define your logic here
# at the end, it should return 'true' or 'false'
end
end
If the method through which you would like to validate the instance, belongs to the instance, then you can pass that method as a string like following:
validates_presence_of :password, if: 'admin?'
It would call admin? on the current instance, and if it returns true, it will validate the presence of password, and vice versa.

Related

How to validate Rails model based on a parameter?

I have User model, and need to validate phone number attribute based on the controller param.
class User < ActiveRecord::Base
validates_presence_of :phone_number
end
This validation should validate phone_number in the Create action.
Let's say the param I should check is
params[:phone_number]
you can use before_save validation, in User model you can write
before_save :validate_phone_number
private
def validate_phome_number
self.phone_number = /some regex/
end
In self.phone_number you will get controller params by default
validate :custom_validation, :on => :create
private
def custom_validation
//whatever you want to check
end
I have tried many ways to complete this task,
I used Inheritance - Created a sub class from the User class
Call a method in the model from the controller to set the attribute and bind that attribute with the validation
Use the context option
I guess the context option is the most reliable solution for this issue i faced. So here when i set the context as :interface the model validation will trigger only based on that value
Model - User.rb
class User < ActiveRecord::Base
validates_presence_of :phone_number, on: :interface
end
Controller - users_controller.rb
#user = User.new(user_params)
#save_result = false
if params[:invitation_token] == nil
save_result = #user.save(context: :interface)
else
save_result = #user.save
end
If you use multiple options in ON:
validates :terms_and_conditions, acceptance: {accept: true}, on: [:create, :interface], unless: :child
validates :privacy_policy, acceptance: {accept: true}, on: [:create, :interface], unless: :child

How to update model attribute from Model?

I have a method to change user status inside the it's model, is it possible to use this do something like this inside the user model:
class User < ActiveRecord::Base
def confirm!
super
self.update_column(:status => "active")
end
end
I saw these two examples;
Rails update_attribute
how to update attributes in self model rails
couldn't quite get which one to go with!
It depends on whether or not you want any validations in the model to run. update_attribute will not run the validations, but update_attributes will. Here are a couple of examples.
Using update_attributes:
class User < ActiveRecord::Base
validates :email, presence: true
def confirm!
update_attributes(status: 'active')
end
end
The following will return false and will not update the record, because not email has been set:
user = User.new
user.confirm! # returns false
Using update_attribute:
class User < ActiveRecord::Base
validates :email, presence: true
def confirm!
update_attribute(:status, 'active')
end
end
The following will update status to active regardless of whether or not email has been set:
user = User.new
user.confirm! # returns true

Skip password validation with a specific method in the user model

In my user model i have validation of password and an instance method like this:
class User < ActiveRecord::Base
validate :email ......
validates :password, length: { minimum: 6 }
def my_method
# .....
# save!
end
end
As you can see inside this method i have a call to the save! method which save the user after altering some fields, so i want to skip the validation of password but not other validations only when i call my_method on a user instance , how i can do this please ? thank you
I find the solution if someone is interesting, i simply add attr_accessor :skip_password_validation to my model, then i add a condition to my password validation validates :password, length: { minimum: 6 }, unless: :skip_password_validation, and when i call my_method in the controller with an instance of user model, i set this attribute above with true. it's all , here what the user model will look like:
class User < ActiveRecord::Base
validate :email ......
validates :password, length: { minimum: 6 }, unless: :skip_password_validation
attr_accessor :skip_password_validation
def my_method
# .....
# save!
end
end
In the controller before i call user.my_method i just add the line : user.skip_password_validation = true.
I hope this help you :)
You can do it with 2 methods.
model.save(:validate => false)
See here and here
OR
Skipping the Callback
skip_callback :validate, :before, :check_membership, :if => lambda { self.age > 18 }
API Doc

validate inclusion not working on create

Okay I have quite a weird scenario that I do not know how to deal with so please bear with me as I try to explain it to you.
I have the following model.
class User < ActiveRecord::Base
Roles = { pending: 'pending_user', role2: 'role2', etc: 'etc' }
attr_accessible :role
validates :role, inclusion: {in: Roles.values}
before_create :add_pendng_role #Set user role to Roles[:pending]
end
Now the problem is when creating a record for the first time, this validation fails! For example in my controller I have the following code:
class UsersController < ActionController::Base
#user = User.new params[:user]
if #user.save # --------------- ALWAYS FAILS -------------------------------
#do something
else
#do something else
end
end
Now the reason I believe it fails is because a role is only added before_create which is called after the validations have passed. Now I know that I can't replace the before_create :add_role with before_validation :add_role because I think that it will add the role each time a validation is done. The reason I can't have that is because the user role will change in the application and I don't want to reset the role each time a validations are done on the user.
Any clues on how I could tackle this?
You could try:
before_validation :add_role, on: :create
Use *before_validation*, as explained in the rails callback guide
class User < ActiveRecord::Base
Roles = { pending: 'pending_user', role2: 'role2', etc: 'etc' }
attr_accessible :role
validates :role, inclusion: {in: Roles.values}
before_validation :add_pendng_role, on: :create #Set user role to Roles[:pending]
end
Looks like you'll be able to change before_create to before_validation if you use the :on argument:
before_validation :add_pendng_role, :on => :create

Rails updating attributes of a User Model from OrdersController

This my code:
class OrdersController
def create
#order = Order.new(params[:order])
if #order.purchase
work = GATEWAY.store(credit_card, options)
result = work.params['billingid']
current_user.update_attributes(:billing_id => result)
end
end
end
billingid is returned by running GATEWAY.store(credit_card, options)
I am trying to save this returned billingid into :billing_id column in User Model. Is it not possible to update attribute of User model from a that is not UsersController?
Simply put, is it not possible to update an attribute of model #1 from a controller of model #2?
Thanks
UPDATE:
With the help of the men below, I was able to verify two things:
1. result = work.params ['billingid'] returns string
2. That I am able to save into a different model from any controller
However, even though I have attr_accessible :billing_id I am still unable to save the result into billing_id column of User table. I was successful in saving the result in a store_name column of a Store table, so I don't know what it is about User model that is preventing me from saving.
I ran,
#mystore = Store.find(current_user)
#mystore.store_name = result
#mystore.save
and it was successful. But,
#thisuser = User.find(current_user)
#thisuser.billing_id = result
#thisuser.save
This fails even though attr_accessible is set correctly. What else could prevent from saving certain attributes other than attr_accessible? Thanks everyone!
UPDATE 2: User Model
require 'digest'
class User < ActiveRecord::Base
has_one :store
has_many :products
attr_accessor :password
# attr_accessible was commented out completely just to check as well. Neither worked
attr_accessible :name, :email, :password, :password_confirmation, :username, :billing_id
validates :name, :presence => true,
:length => { :maximum => 50 }
validates :email, :presence => true,
:format => { :with => email_regex },
:uniqueness => { :case_sensitive => false }
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 }
username_regex = /^([a-zA-Z0-9]{1,15})$/
before_save :encrypt_password
def has_password?(submitted_password)
encrypted_password == encrypt(submitted_password)
end
private
def encrypt_password
self.salt = make_salt if new_record?
self.encrypted_password = encrypt(password)
end
def encrypt(string)
secure_hash("#{salt}--#{string}")
end
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end
end
end
UPDATE FINAL: SOLUTION
using #thisusers.errors, I was able to find out that it was trying to validate the presence of password during this request. Once I commented it out, it saved without an issue. I am unsure why this is happening, but I will take it from here. Thanks everyone esp. dmarkow!
There should be no issue updating any number of models from a controller.
Make sure that work.params['billingid'] actually contains a value.
Your User model may have some attributes marked as attr_accessible (since you have current_user, I assume you have authentication, and this often means needing to protect your model's attributes by default). If this is the case, that means that only those attributes can be changed by mass assignment (e.g. using update_attributes). Either add billing_id to the list of attributes that are attr_accessible, or don't use mass assignment. (Instead, you would just do current_user.billing_id = result and then current_user.save)
Edit: The problem wound up being a validation error on the User model. Always make sure to check the user.errors when user.save returns false.

Resources