Rails, validations within a with_options block don't work - ruby-on-rails

I have an old Rails 2 app where I have a User model that handles profiles of users coming from web and mobile applications. I would like to have 2 separate groups of validations for web and mobile users. I use DEVISE for authentication.
In order to recognize if a user relates to web or mobile, I use an attr_accessor called validation_for_mobile that I set on the controller side, before saving/updating the record.
It appears that some validations, especially the "validates_length_of :password", get executed even if they are inside a block with_options that should not be evaluated.
Inside the User model I have this code:
# WEB VALIDATIONS
with_options :if => "self.validation_for_mobile == false && !self.remote_sync" do |vm1|
vm1.validates_date :birthday, :allow_blank => true
vm1.validates_presence_of :company, :name, :surname, :address, :city, :province, :postal_code, :position, :company_type, :phone, :unless => :is_imported
vm1.validates_format_of :phone, :with => /\A([0-9]+)\Z/i, :message => 'deve contenere solo numeri', :unless => :is_imported
vm1.validates_presence_of :vat, :if => "!imported && !self.script_imported && company_type != 'individuale' && !company_type.blank?"
vm1.validates_presence_of :social_number, :if => "!imported && !self.script_imported && company_type == 'individuale' && !company_type.blank? "
vm1.validates_presence_of :remote_id, :if => "!came_from_user && !imported && !script_imported"
vm1.validates_acceptance_of :privacy_accepted, :privacy_third_part, :accept => true, :allow_nil => false, :unless => :is_imported
vm1.validates_presence_of :login, :email
vm1.validates_email_format_of :email, :message => "email non valida"
vm1.validates_presence_of :remote_sap_code, :remote_as_code, :if => "!came_from_user"
vm1.validates_uniqueness_of :login, :scope => :branch_id, :case_sensitive => false
vm1.with_options :if => :password_required? do |v|
v.validates_presence_of :password
v.validates_confirmation_of :password
v.validates_format_of :password, :with => /^(?=(.*\d){1})(?=.*[a-zA-Z])[0-9a-zA-Z]+$/i, :message => 'solo lettere e almeno 1 numero!'
v.validates_length_of :password, :within => 6..10, :allow_blank => true
end
end
# MOBILE VALIDATIONS
with_options :if => "self.validation_for_mobile == true && !self.remote_sync" do |vm2|
with_options :if => :mobile_validations do |vm2|
vm2.validates_presence_of :company, :vat, :name, :surname, :phone
vm2.validates_format_of :phone, :with => /\A([0-9]+)\Z/i, :message => 'deve contenere solo numeri'
vm2.validates_length_of :vat, :within => 11..15
vm2.validates_presence_of :login, :email, :branch_id
vm2.validates_email_format_of :email, :message => "email non valida"
vm2.validates_uniqueness_of :login, :scope => :branch_id, :case_sensitive => false
vm2.with_options :if => :password_required? do |v|
v.validates_presence_of :password
v.validates_confirmation_of :password
v.validates_format_of :password, :with => /^(?=(.*\d){1})(?=.*[a-zA-Z])[0-9a-zA-Z]+$/i, :message => 'solo lettere e almeno 1 numero!'
v.validates_length_of :password, :within => 1..10, :allow_blank => true
end
end
Every time I try to create a new user from mobile, I expect only the second block of validations to be evaluated, where "validates_length_of :password" is "within => 1..10"
But I receive the error that password is too short (at least 6 chars). And that's the first block!!!
Can you help me?
I also tried to move the conditions of with_options inside a method, like this:
with_options :if => :web_validations
with_options :if => :mobile_validations
but nothing changed.
This is the code I use to test it:
#user = User.new
#user.company = "Testuser"
#user.vat = "123456789012"
#user.name = "Fafag"
#user.surname = "Fafag"
#user.phone = "9182624"
#user.email = "utentest9876#test.it"
#user.login = "Utentest63"
#user.branch_id = 2
#user.password = "TEST1"
#user.password_confirmation = "TEST1"
#user.plain_password = "TEST1"
#user.mobile_signup = true
#user.mobile = true
#user.validation_for_mobile = true # model custom validation
#user.renew_mobile_token!
#user.came_from_user = true
#user.remote_id = 0
#user.confirmed = nil
#user.active = false
if #user.save
return true
else
puts #user.errors.full_messages.inspect
end

Ok, I solved this puzzle!
It seems nested with_options blocks don't inherit the outer condition. So I had to specify the first condition inside the nested with_option block, like this:
with_options :if => :mobile_validations do |vm2|
vm2.validates_presence_of :company, :vat, :name, :surname, :phone
vm2.validates_format_of :phone, :with => /\A([0-9]+)\Z/i, :message => 'deve contenere solo numeri'
vm2.validates_length_of :vat, :within => 11..15
vm2.validates_presence_of :login, :email, :branch_id
vm2.validates_email_format_of :email, :message => "email non valida"
vm2.validates_uniqueness_of :login, :scope => :branch_id, :case_sensitive => false
vm2.with_options :if => [:password_required?, :mobile_validations] do |v2|
v2.validates_presence_of :password
v2.validates_confirmation_of :password
v2.validates_format_of :password, :with => /^(?=(.*\d){1})(?=.*[a-zA-Z])[0-9a-zA-Z]+$/i, :message => 'solo lettere e almeno 1 numero!'
v2.validates_length_of :password, :within => 1..10, :allow_blank => true
end
end

Related

Rails4 validation uniqueness scope check for more than two condition

I have following validation for my email in the model
:uniqueness => { :scope => :company_id, :message => "Email has already been taken" }
How to give two scope condition for validating uniqueness
:uniqueness => { :scope => :company_id and :status => 0, :message => "Email has already been taken" }
Try this out
:uniqueness => { :scope => [:company_id, :status], :message => "Email has already been taken"}, if: Proc.new { |obj| obj.status == 0 }

Validates allow_blank of multiple columns

I validate Realty objects depending on their active_state, so if it's pending, multiple fields are allowed to be blank.
with_options :if => Proc.new { |a| a.active_state == 'pending'} do |realty|
realty.validates :street, :length => {:in => 1..100}, :allow_blank => true
realty.validates :postalcode, :numericality => {:only_integer => true}, :length => {:in => 4..5}, :allow_blank => true
realty.validates :city, :length => {:in => 1..50}, :allow_blank => true
realty.validates :description, :length => {:maximum => 8000}, :allow_blank => true
realty.validates :leasing_costs, :numericality => {:only_integer => true}, :length => {:in => 1..9}, :allow_blank => true
end
with_options :if => Proc.new { |a| a.active_state != 'pending'} do |realty|
realty.validates :street, :length => {:in => 1..100}
realty.validates :postalcode, :numericality => {:only_integer => true}, :length => {:in => 4..5}
realty.validates :city, :length => {:in => 1..50}
realty.validates :description, :length => {:maximum => 8000}
realty.validates :leasing_costs, :numericality => {:only_integer => true}, :length => {:in => 1..9}
end
The only difference are those :allow_blank => true options.
I want to make this code more dry, so my attempt would be to use the normal validations block once:
validates :street, :length => {:in => 1..100}
validates :postalcode, :numericality => {:only_integer => true}, :length => {:in => 4..5}
validates :city, :length => {:in => 1..50}
validates :description, :length => {:maximum => 8000}
validates :leasing_costs, :numericality => {:only_integer => true}, :length => {:in => 1..9}
and then simply call some function on all of those fields in case the state is pending:
with_options :if => Proc.new { |a| a.active_state == 'pending'} do |realty|
realty.allow_blank_of :street, :postalcode, :city, :description, :leasing_costs
end
Similar to all those validates_uniqueness_of :x, :y, :z methods.
I couldn't find a function to fit my need. How can I approach this?
It would be nice if the :allow_blank option took a proc, but I don't think it does. However, you can achieve the same result using the :if option since :allow_blank is essentially a way of saying "if the attribute is blank then don't run this validation requirement". So try this:
with_options :if => Proc.new { |a| a.active_state == 'pending' ? a.present? : true } do |realty|
realty.validates :street, :length => {:in => 1..100}
realty.validates :postalcode, :numericality => {:only_integer => true}, :length => {:in => 4..5}
realty.validates :city, :length => {:in => 1..50}
realty.validates :description, :length => {:maximum => 8000}
realty.validates :leasing_costs, :numericality => {:only_integer => true}, :length => {:in => 1..9}
end
The proc in this case is saying... if the active_state is 'pending' and the attribute is present then execute this validation. But if the active_state is not 'pending' then the attribute isn't allowed to be blank so always run the validation. I hope I got that logic right based on your needs.
From pdobb's answer
However, you can achieve the same result using the :if
option since :allow_blank is essentially a way of saying "if the
attribute is blank then don't run this validation requirement".
Based on pdobbs explanation what allow_blank => true actually does, I could narrow the validations down to:
validates :street, :length => {:in => 1..100}, :unless => :pending?
validates :postalcode, :numericality => {:only_integer => true}, :length => {:in => 4..5}, :unless => :pending?
validates :city, :length => {:in => 1..50}, :unless => :pending?
validates :description, :length => {:maximum => 8000}, :unless => :pending?
validates :leasing_costs, :numericality => {:only_integer => true}, :length => {:in => 1..9}, :unless => :pending?
def pending?
active_state == "pending"
end
So I simply skip all of those validations if the active_state is "pending".
Allot more DRY :)
I haven't ran this, but I think this may work
before_commit :build_callback_validator, :on => :create
def build_callback_validator
validates :street, :length => {:in => 1..100}
validates :postalcode, :numericality => {:only_integer => true}, :length => {:in => 4..5}
validates :city, :length => {:in => 1..50}
validates :description, :length => {:maximum => 8000}
validates :leasing_costs, :numericality => {:only_integer => true}, :length => {:in => 1..9}
if self.active_state != 'pending'
self.allow_blank_of :street, :postalcode, :city, :description, :leasing_costs
end
end

Rails multiple conditional validation

I have the following model
class User < ActiveRecord::Base
VALID_EMAIL_REGEX = /^.+#.+\..+$/i
attr_accessible :active_list_id, :password, :password_confirmation, :email, :temp
has_secure_password
before_create { generate_token(:auth_token) }
if :temp.nil?
before_validation :downcase_email
validates_presence_of :email, :password, :password_confirmation, :on => :create
validates_confirmation_of :password
#something#something.something
validates :email, :uniqueness => true,
:format => {:with => VALID_EMAIL_REGEX }
end
after_create { make_list([email,"'s shopping list"].join('')) }
has_many :shopping_lists
has_many :transactions, :class_name => 'Order'
HUMANIZED_ATTRIBUTES = {
:password_digest => "Password"
}
...
end
I try to create a model in my rails console by calling User.create(:temp => true) (which is a boolean and is defined in my migrations/schema). But it always rollsback the transaction. What am I doing wrong?
I also tried doing :if => temp.nil? and if => "temp.nil?" and :if => lambda { |user| user.temp.nil? } for all 3 of my validations.
create a method called temp_is_nil? and use that on the if condition
def temp_is_nil?
temp.nil?
end
make sure that temp is an attribute of user.
before_validation :downcase_email, if: temp_is_nil?
validates_presence_of :email, :password, :password_confirmation, :on => :create, if: temp_is_nil?
validates_confirmation_of :password, if: temp_is_nil?
validates :email, :uniqueness => true,
:format => {:with => VALID_EMAIL_REGEX }, if: temp_is_nil?

Rails conditional validation of Zip Code for many countries

I need a model-level validation for Zip codes in USA and Canada. This code makes me feel bad:
zip_regex_usa = %r{\d{5}(-\d{4})?}
zip_regex_canada = %r{[ABCEGHJKLMNPRSTVXY]\d[A-Z] \d[A-Z]\d}
validates :shipping_zip, :presence => true, :format => { :with => zip_regex_usa }, :if => :shipping_to_usa?
validates :shipping_zip, :presence => true, :format => { :with => zip_regex_canada }, :if => :shipping_to_canada?
validates :billing_zip, :presence => true, :format => { :with => zip_regex_usa }, :if => :billing_to_usa?
validates :billing_zip, :presence => true, :format => { :with => zip_regex_canada }, :if => :billing_to_canada?
def shipping_to_usa?
shipping_country == 'US'
end
def billing_to_usa?
billing_country == 'US'
end
def shipping_to_canada?
shipping_country == 'CA'
end
def billing_to_canada?
billing_country == 'CA'
end
How to make this code more elegant, writing a single validation line for each field?
You can use gem validates_as_postal_code
It allows you to check zip codes like this:
class Person < ActiveRecord::Base
validates_as_postal_code :postal_code, :country => "CA", :allow_blank => true
end
and there're more options
EDIT:
There's also one nice gem: going_postal check it out!
I pulled some bits together into this gem: validates_zipcode.
It currently supports 259 countries zipcode formats and plays nice with Rails 3 & 4.
You can use it like this:
class Address < ActiveRecord::Base
validates_zipcode :zipcode
validates :zipcode, zipcode: true
validates :zipcode, zipcode: { country_code: :ru }
validates :zipcode, zipcode: { country_code_attribute: :my_zipcode }
end

how to NOT update a field Ruby on Rails

I'm a newbie in rails. how put a validated on password field. specified only on create and used an allow_blank method. but everytime i update it still creates a nil in the password field. any help?
validates :password, :presence => { :on => :create },
:confirmation => true,
:length => { :within => 8..40},
:allow_blank => true,
Try this:
validates :password, :presence => { :if => :new_record? },
:confirmation => true,
:length => { :within => 8..40 }

Resources