I have a form for my Subscription model and users have the option to enter a phone number. The Twilio API is being used.
class Subscription < ActiveRecord::Base
validates_numericality_of :phone_number, allow_blank: true
validate :mobile_phone_number
def mobile_phone_number
lookup_client = Twilio::REST::LookupsClient.new Rails.application.secrets.twilio_sid, Rails.application.secrets.twilio_token
begin
response = lookup_client.phone_numbers.get("#{self.phone_number}")
response.phone_number #if invalid, throws an exception. If valid, no problems.
return true
rescue => e
errors.add(:base, "That phone number is not valid.")
end
end
What is occurring: I can only enter valid phone numbers given the Twilio lookup API...this is a good thing. However, users need to be able to enter a BLANK phone number.
Currently, the validates_numericality_of method is getting overwritten by the mobile_phone_number method.
If I enter a blank phone_number, "That phone number is not valid" gets returned. This should not be happening.
How do I make a special case for the rescue? For example, "rescue this unless phone number is blank". Or what am I doing wrong here?
All input is appreciated.
This way you won't even enter your validating function.
Take a look at conditional validations
validate :mobile_phone_number, unless: proc { phone_number.blank? }
for those who dont like unless
validate :mobile_phone_number, if: proc { phone_number.present? }
At the beginning of the begin:
if self.phone_number.blank?
return true
end
Related
I wrote a custom validation to error out once a user attempts to withdraw more than the minimum allowed withdrawal amount. The validation fails as it should when the user's withdrawal is below the minimum allowed amount but the code continues running. It does not act like regular validations on model fields.
validate :minimum_withdrawal_amount, on: :create
validates :amount, numericality: {greater_than: 0}
def minimum_withdrawal_amount
if sum.nil? || sum < currency.min_withdraw_amount
errors.add :base, -> { I18n.t('activerecord.errors.models.withdraw.amount.min_withdraw_amount', currency: currency.key, amount: currency.min_withdraw_amount ) }
end
end
It goes ahead and validates the amount that comes after it. If that validation fails, it then errors out. I'll like my custom validation to act like the validation on amount. Hope I'm clear enough
Following replacement might work for you:
errors.add(:base, I18n.t('activerecord.errors.models.withdraw.amount.min_withdraw_amount', currency: currency.key, amount: currency.min_withdraw_amount ))
I have two validations:
validates :email, format: { with: /\A(.+)#(aol|gmail|office365|outlook|verizon|yahoo)\.com\Z/i }, if: Proc.new { |user| user.imap_server.blank? }
validates :email, presence: true
validates :imap_server, presence: true, if: Proc.new { |user| user.email.present? && user.email_invalid? }
def email_invalid?
self.email =~ /\A(.+)#(aol|gmail|office365|outlook|verizon|yahoo)\.com\Z/i
end
I show a user a form. It displays an email field but not imap_server field. If the value in email field does not match the specific regex, then I want to show them form again with the imap_server field present as well. If they enter a value for the imap_server field, then I no longer want to validate the regex of email field (although it must still be present).
The problem it feels like I am duplicating a validation. Both email_invalid? and the validates :email, format: ... do the same thing. How can I clean this up?
You could replace the validates :email, format: ... with
validate :email_format
def email_format
errors.add(:email, 'format invalid') if imap_server.blank? && email_invalid?
end
which is slightly more lines but lets you define the format validation in one place.
I suspect that the issue is you're trying to check the result of a validation (email_invalid?) while you're * still doing * the validations... you don't know what order the validations are going to be run (the order on the page is not something I'd trust)... so the best way to solve it is to just write all these things into a single validates method eg a quick-and-dirty way:
validates :email_or_imap_server
def email_or_imap_server
email_valid = false # for scoping
if email.present?
# note: email validation via regex is harder than you think...
# google it...
email_valid = email.match(/#{VALID_EMAIL_FORMATS}/)
if email_invalid
errors.add(:email, "email invalid format should be...")
errors.add(:imap_server, "email or imap-server must be present") unless imap_server.present?
end
else
errors.add(:imap_server, "either email or imap-server must be present") unless imap_server.present?
end
end
etc.
Note: the code above is almost certainly full of bugs and typos... don't copy/paste it almost certainly won't work and the logic doesn't exactly match that of your validations... but do something like this.
What is the best way to adjust your validation of a model based on a parameter or action? Say I am entering a lead into a system, so all that is required is basic contact info. But then later I may enter a new contract for that user at which point I need more detailed information. So I may need phone number when just entering a lead but once they are entering into a contract I might need their birthdate and alternate phone number.
I considered using a state machine but the user could actually enter into two contracts at the same time so state doesn't really work for this scenario.
I also considered storing the extra info with the contract but since the user can have more than one contract, it needs to live with the user so it is not redundant.
So basically when saving a contract, it would tell me that the attached user is invalid if said user doesn't have the extra fields.
Check out conditional validations:
class Person
validates_presence_of :given_name, family_name
validates_presence_of :phone_number, :email_address, :if => :registered
with_options :if => :registered do |person|
# validations in this block are scoped to a registered user
person.validates_presence_of :dob
end
end
The :if option can take:
a symbol that corresponds to a method on the class that evaluates to true or false
a proc or lambda that returns a value that evaluates to true or false
a string containing ruby code (god knows why you'd want to do that)
You also have access to an :unless option which works in a similar fashion.
You can also encapsulate the logic to determine the current state of the user and use that to determine what validation steps you can take:
class Person
validates_presence_of :email_address, :if => ->(p) { p.state == :pending_confirmation }
# I actually prefer validations in this format
validate do # stricter validations when user is confirming registration
if confirming_membership && (given_name.blank? || family_name.blank?
errors.add(:base, 'A full name is required')
end
end
def state
# your logic could be anything, this is just an example
if self.confirmed_email
:registered
elsif confirming_membership
:pending_confirmation
else
:new_user
end
end
def confirming_membership
# some logic
end
end
You can use conditional validation for example:
validates_presence_of :phone, :if => Proc.new { |p| p.lead? }
In whatever action the lead posts to, you could just do this:
#object.save(validate: false)
Then, when they need to enter the contract, leave off that validate: false option to ensure that those validations run.
Also, see this post if you want to skip only certain validations.
In an multilingual application, a user can input their Chinese and English names. The user can input either or both, but must input at least one name.
class Person < ActiveRecord::Base
validates :zh_name, :presence => true
validates :en_name, :presence => true
validates :fr_name, :presence => true
end
Since the built-in :validates_presence_of method can only validate both attributes at once, is there a way to validate the presence of at least one of many attributes in rails?
Like a magical, validates_one_of :zh_name, :en_name, :fr_name
Thank you in advance,
validate :at_least_one_name
def at_least_one_name
if [self.zh_name, self.en_name, self.fr_name].reject(&:blank?).size == 0
errors[:base] << ("Please choose at least one name - any language will do.")
end
end
Taking #micapam's answer a step futher, may I suggest:
validate :has_a_name
def has_a_name
unless [zh_name?, en_name?, fr_name?].include?(true)
errors.add :base, 'You need at least one name in some language!'
end
end
just a quick shot out, you can pass a "if" or "unless" to the validator, maybe you can get it working this way. i have something like this in mind
validates :zh_name, :presence => { :if => (fr_name.blank? && en_name.blank?) }
validate :has_a_name
def has_a_name
unless [zh_name, en_name, fr_name].any?{|val| val.present? }
errors.add :base, 'You need at least one name in some language!'
end
end
Max Williams' answer is fine, but I didn't see the need to count hits when any? returns a boolean.
I have a form on my site that lets users direct a message at other users, but I want to ensure that they can't direct a message at themselves.
The class has attributes :username and :target_user, and I just want to set a validation that checks to make sure that these attributes can't have the same value, before anything gets saved.
I figured it would look something like this:
validates_presence_of :user_id, :username, :target_user, :message, :tag
validate :username != :target_user
But clearly don't know enough Ruby to do it correctly.
Up at the top with your validations:
validate :username_does_not_equal_target
and then a private/protected method within your model code:
def username_does_not_equal_target
#errors.add(:base, "The username should not be the same as the target user") if self.username == self.target_user
end
OR to attach the error to a specific attribute:
def username_does_not_equal_target
#errors.add(:username, "should not be the same as the target user") if self.username == self.target_user
end
You can change the text of your error message or the name of your method.
to read more on errors: http://api.rubyonrails.org/classes/ActiveRecord/Errors.html
to read more on validations: http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html
I hope this helps, happy coding!
Use validate
def validate
if self.username == self.target_user
self.errors.add :base, "You can't send message to yourself."
end
end