How to validate Rails model based on a parameter? - ruby-on-rails

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

Related

Rails 6 validate model only for specific controller

I've got User model with validation:
validates :experience_level, inclusion: { in: EXPERIENCE_LEVEL, allow_blank: true }
But one of the part of full registration is to update User's experience level. User can do this by inside of below controller:
module Users
class ExperienceLevelsController < SignupBaseController
def edit
authorize current_user
end
def update
authorize current_user
if current_user.update(user_experience_level_params)
redirect_to new_appropriateness_test_step_one_path,
else
render :edit
end
end
end
And for that endpoint I want to use
validates :experience_level, presence: true, inclusion: { in: EXPERIENCE_LEVEL }
I know I could use on: :update but in such case User will not be able to update e.g. password if it doesn't go through the experience update form first.
If you want to make the model state aware you can do it by explicitly passing information into the model:
class User < ApplicationRecord
attr_accessor :stage
validates :experience_level,
inclusion: { in: EXPERIENCE_LEVEL }
validates :experience_level, presence: true, if: :requires_experience_level?
def requires_experience_level?
stage == :add_experience_level
end
end
module Users
class ExperienceLevelsController < SignupBaseController
def edit
authorize current_user
end
def update
authorize current_user
if current_user.update(user_experience_level_params.merge(stage: :add_experience_level))
redirect_to new_appropriateness_test_step_one_path,
else
render :edit
end
end
end
end
There is also ActiveSupport::CurrentAttributes:
Abstract super class that provides a thread-isolated attributes
singleton, which resets automatically before and after each request.
This allows you to keep all the per-request attributes easily
available to the whole system.
# app/models/current.rb
class Current < ActiveSupport::CurrentAttributes
attribute :stage
end
def update
authorize current_user
Current.stage = :add_experience_level
end
class User < ApplicationRecord
attribute_accessor :stage
validates :experience_level,
inclusion: { in: EXPERIENCE_LEVEL }
validates :experience_level, presence: true, if: :requires_experience_level?
def requires_experience_level?
Current.stage == :add_experience_level
end
end
Its really up to you if you want use it though as it can be considered harmful. If it quacks like a global...

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

rails ActiveRecord validation on specific controller and action

is it possible to run ActiveRecord validates on given controller and action.
For example I have user_controller and signup_controller
I need to run password required validation only on signup_controller#create action
You can run validations using an if conditional:
validates :email, presence: true, if: :validate_email?
Now you need to define this instance method (in your model):
def validate_email?
validate_email == 'true' || validate_email == true
end
This validate_email attribute could be a virtual attribute in your model:
attr_accessor :validate_email
And now, you can perform email validation depending on that virtual attribute. For example, in signup_controller#create you can do something like:
def create
...
#user.validate_email = true
#user.save
...
end
use validates :password, :if => :password_changed? in user.rb
if form in users_controller does not submit password field then you should be ok.
Just a tip for implementing #markets' answer
We can use
with_options if: :validate_email? do |z|
z.validates :email, presence: true
z.validates :name, presence: true
end
for multiple validations on our specific action.
Also, we use session to pass a variable which indicate params from this action will need some validations
Controller:
before_action :no_validate, only: [:first_action, :second_action, ..]
before_action :action_based_validation, only: [:first_action, :second_action, ..]
def first_action; end
def second_action; end
def update
..
#instance.validate = session[:validate]
..
if #instance.update(instance_params)
..
end
end
private
def no_validate
session[:validate] = nil
end
def action_based_validation
# action_name in first_action will = "first_action"
session[:validate] = action_name
end
Model
attr_accessor :validate
with_options if: "validate == 'first_action'" do |z|
z.validates :email, presence: true
..more validations..
end
with_options if: "validate == 'second_action'" do |z|
z.validates :name, presence: true
..more validations..
end
more details:
http://guides.rubyonrails.org/active_record_validations.html#conditional-validation

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