I want to validate an input box value in two cases:
If nil? then save successfully, no errors
If not nil? then validate its format
I have a simple line here:
validates :ip_addr, format: { with: Regexp.union(Resolv::IPv4::Regex)}
This will work in all cases but won't allow a nil/empty value as it throws an exception. But:
validates :ip_addr, format: { with: Regexp.union(Resolv::IPv4::Regex)}, allow_nil: true
and
validates :ip_addr, format: { with: Regexp.union(Resolv::IPv4::Regex)}, allow_blank: true
Will allow nil/empty values but If the input is invalid e.g. "33####$" then it returns "true".
How could I include both cases ? Is that possible ?
EDIT: It seems that Regexp.union(Resolv::IPv4::Regex).match("something") returns nil so If the validation works the same way, it will return nil in wrong values and allow_nil: true will allow them to be persisted like this.
Try this
validates :ip_addr, format: { with: Regexp.union(Resolv::IPv4::Regex)}, if: :ip_addr
I am not sure whether the above one will work. If it doesn't, do this
validate :ip_addr_format_check, if: :ip_addr
def ip_addr_format_check
unless ip_addr =~ Regexp.union(Resolv::IPv4::Regex)
errors.add(:base, "Error you want to show")
end
end
Typecasting seems to be the problem.
The solution below works fine:
validates :ip_addr, format: { with: Resolv::IPv4::Regex },
presence: false, unless: Proc.new { |ifc| ifc.ip_addr_before_type_cast.blank? }
Most probably we need to check If the variable's value is blank before casting it to inet (postgreSQL).
Related
Model Plan has a jsonb column :per_unit_quantities_configuration . It is a hash with 3 keys/values pairs, min, max and step.
class Plan < ApplicationRecord
store_accessor :per_unit_quantities_configuration, :min, :max, :step, prefix: true
end
I have validations in place to prevent awkward configurations and/or infinite loops building an array of options based on those configuration settings.
First, I cast the values to from string to float before_validation
before_validation :cast_per_unit_quantities_config_values
def cast_per_unit_quantities_config_values
return unless per_unit_quantities_configuration_changed?
self.per_unit_quantities_configuration_min = per_unit_quantities_configuration_min&.to_f
self.per_unit_quantities_configuration_max = per_unit_quantities_configuration_max&.to_f
self.per_unit_quantities_configuration_step = per_unit_quantities_configuration_step&.to_f
end
And then I have each individual fields values' validations:
validates :per_unit_quantities_configuration_min,
numericality: { greater_than_or_equal_to: 0 }, allow_nil: false
validates :per_unit_quantities_configuration_max,
numericality: { greater_than: lambda { |p| p.per_unit_quantities_configuration_min } }
validates :per_unit_quantities_configuration_max
numericality: { greater_than_or_equal_to: 0 }, allow_nil: false
validates :per_unit_quantities_configuration_step,
numericality: { greater_than: 0 }, allow_nil: false
The problem I'm having is that when the user tries to send the form with the min field empty (nil), it is transformed to 0 which is a valid value for the field but is not appopiate since API users would receive no feedback that the change is being made.
What is converting the nil value to 0 ? And why is the allow_nil: false validation not triggered instead?
What is converting the nil value to 0?
The call to .to_f:
nil.to_f
=> 0.0
If you're starting with an empty string instead of a nil then the safe navigation operator won't save you:
nil&.to_f
=> nil
""&.to_f
=> 0.0
And why is the allow_nil: false validation not triggered instead?
You changed the value in a before_validation callback. The validations will run against the new value.
You may want to remove the before_validation. This way, you can use the Rails numericality validations to filter out any invalid values (nil, "", "x", etc.). If you still need to do something to the value before it gets stored in the database you may want to use an after_validation callback instead.
I have one model validation like below
validates :value, presence: true, allow_blank: false, uniqueness: { scope: [:account_id, :provider] }
I want to add one more condition of case_sensitive inside uniqueness like below
validates :value, presence: true, allow_blank: false, uniqueness: { scope: [:account_id, :provider], case_sensitive: :is_email? }
def is_email?
provider != email
end
In short, it should not validate case_sensitive when email provider is not email, But currently, it's not working it is expecting true or false only not any method or any conditions.
How can I achieve this in rails? I already wrote custom validation because it was not working.
UPDATE
if I add another validation like below
validates_uniqueness_of :value, case_sensitive: false, if: -> { provider == 'email' }
It's giving me same error for twice :value=>["has already been taken", "has already been taken"]
In the specific case of case_sensitive, the value passed to the option will always be compared against its truthy value.
As you can see in the class UniquenessValidator, when the relation is built, it uses the options passed to check if the value of case_sensitive is truthy (not false nor nil), if so, it takes the elsif branch of the condition:
def build_relation(klass, attribute, value)
...
if !options.key?(:case_sensitive) || bind.nil?
klass.connection.default_uniqueness_comparison(attr, bind, klass)
elsif options[:case_sensitive] # <--------------------------------- sadly, this returns true for :is_email?
klass.connection.case_sensitive_comparison(attr, bind)
else
# will use SQL LOWER function before comparison, unless it detects a case insensitive collation
klass.connection.case_insensitive_comparison(attr, bind)
end
...
end
As you're passing the method name is_email? to case_sensitive, which is in fact a symbol, the condition takes that branch.
tl;dr;. You must always use true or false with case_sensitive.
Is it a good idea to having validates like this:
validates :serialnumber, presence: true, length: {7..20}, format: {with: /\d{7,20/}
As you see it generates three errors if I don't type serialnumber.
I would like to see only one error.
If I type nothing, I would like to see 'serial number is required' only.
If I type 123ABC I would like to see 'wrong length' only
And if I type 123-ABC-123 I would like to see 'wrong format' only
How to do it?
Regards
You could split it into 2 validators, check if this would work
validates :serialnumber, presence: true
validates :serialnumber, length: {7..20}, format: { with: /\d{7,20}/ }, allow_blank: true
As I understand then you want to see only one error message at a time. If that's the case then custom validation method might help. For ex.
validate :serial_number_validation_one_by_one
private
def serial_number_validation_one_by_one
if !self.serial_number.present?
errors.add(:serial_number, "must be present!")
elsif self.serial_number.length < 7 || self.serial_number.length > 20
errors.add(:serial_number, "must have length between 7 and 20!")
elsif self.serial_number.match(<your regex pattern here>)
errors.add(:serial_number, "must be properly formatted!")
end
end
Keep in mind that custom validation methods are called by validate not by validates.
I'm currently facing 2 problems using custom validation on Rails 4. First problem, how can I make the following code more generic and efficient (if it's possible) ?
validates :p1, presence: true, numericality: { only_integer: false }
validate :p1_is_greater_than_p2_and_p3
validate :p2_between_p1_and_p3
validate :p3_is_less_than_p2_and_p1
def p1_is_greater_than_p2_and_p3
if self.p1.present?
errors.add(:p1, 'p1 must be greater than everything') unless
(self.p1 > self.p2) && (self.p1 > self.p3)
end
true
end
def p2_between_p1_and_p3
if self.p3.present?
errors.add(:p2, 'p2 bewteen p1 and p3') unless
self.p2.between?(self.p1, self.p3)
end
true
end
def p3_is_less_than_p2_and_p1
if self.p2.present? and self.p3.present?
errors.add(:p3, 'p3 must be inferior to eveything') unless
(self.p2 > self.p3) && (self.p1 > self.p3)
end
true
end
It's really bloated and dirty, isn't it?
Second issue, on errors.add, I can pass a symbol and an error message. However, if I don't pass any message, how can I define a custom yml key for my locales ? such as :
en:
activerecord:
errors:
models:
prices:
attributes:
custom_key_message_here: 'p1 must be greater than everything'
I want to keep this seperation of concern between locales and model. However, if I don't pass any message, it's show me is invalid. I would like something more explixit.
Thanks for your help.
From a quick look at the numericality validator, could you not just use:
validates :p1, presence: true, numericality: { greater_than: :p2 }
validates :p2, presence: true, numericality: { greater_than: :p3 }
validates :p3, presence: true
As long as p1 > p2 and p2 > p3, you shouldn't need to compare p1 and p3 directly. This is assuming all three values must be present, but you could probably adjust things to work if they're optional.
Making a simple Ruby on Rails app as practise that requires a user to sign up.
Everything works well until I implement regex validation on a 'profile_name' field
Here's my 'user' model:
validates :profile_name, presence: true,
uniqueness: true,
format: {
with: /^a-zA-Z0-9_-$/,
message: 'Must be formatted correctly.'
}
And yet the profile name 'jon' simply refuses to pass. Where could this error be coming from, other than my 'user' model?
You need to add brackets around the ranges so that the regex matches "any of the range" rather than "all of the range in order". Put a + on the end to allow it to match anything in the range more than once.
You also need to change your beginning and ending of lines to beginning and ending of strings!
validates :profile_name, presence: true,
uniqueness: true,
format: {
with: /\A[a-zA-Z0-9_-]+\z/,
message: 'Must be formatted correctly.'
}
Details:
\A # Beginning of a string (not a line!)
\z # End of a string
[...] # match anything within the brackets
+ # match the preceding element one or more times
Really useful resource for generating and checking regex: http://www.myezapp.com/apps/dev/regexp/show.ws
Try like this , It is working fine
validates :name, presence: true,
uniqueness: true,
format: {
with: /\A[a-zA-Z0-9_-$]+\z/,
message: 'Must be formatted correctly.'
}
I just tested your regular expression in Rubular with the 'jon'. There is no matching.
I am not optimized regular expression coder. But still the below regular expression works.
/^[a-zA-Z0-9_-]+$/
So try
validates :name, presence: true,
uniqueness: true,
format: {
with: /^[a-zA-Z0-9_-]+$/,
message: 'Must be formatted correctly.'
}