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 }