I have a business model with a decimal field. Its value must be between 0.00 and 999.99. So I have created a decimal field and add validations to the model.
class Order < ApplicationRecord
validates :price, format: { with: /\A\d+(?:\.\d{0,2})?\z/ }, numericality: { greater_than: 0, less_than: 1000 }
end
When I create the Order object with a decimal value which is greater than 1000 or less than 0, I get the error I expected and that's ok. I can see that
"numericality: { greater_than: 0, less_than: 1000 }" validations work as expected.
Howeber, when I try to create an Order object with a decimal value of 45.45554 or 45.45666, rails persists the object to the database with the price value of 45.45. I expect getting a format error, but it seems that
format validation doesn't work.
What I'm doing wrong ?
Any suggestions,
Thanks.
What is the precision of your price field? Via the docs:
precision Defines the precision for the decimal fields, representing the total number of digits in the number.
I'm assuming price only has a precision of 2, and that's why it's getting rounded.
The value is converted to decimal by the setter. Which means that format validation will always pass.
If you need validate the format use read_attribute_before_type_cast in a custom validation.
class Order < ApplicationRecord
validates :price, numericality: { greater_than: 0, less_than: 1000 }
validates :price_format
PRICE_REGEXP = /\A\d+(?:\.\d{0,2})?\z/.freeze
private
def price_format
unless read_attribute_before_type_cast('price') =~ PRICE_REGEXP
errors.add('price', 'must match the correct format')
end
end
end
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.
Given an integer column how do I prevent a alphanumeric being stored as zero?
I tried using the numericality validator but it seems that the value is checked post typecast (using to_i) so a string like 'dd1' is typecast to 0 and thus passes the validation.
validates :portfolio_number, numericality: { only_integer: true, allow_nil: true }
You can create your own validation which checks the value for typecasting:
validate :portfolio_number_is_numeric
private
def portfolio_number_is_numeric
return unless value.present?
errors.add(:portfolio_number, 'must be numeric') unless read_attribute_before_type_cast(:portfolio_number).match?(/\A[0-9]*\z/)
end
This could potentially be extracted in to a validator object for reusability.
I've added validation to my Rails model for less_than and greater_than, but they obviously conflict each other.
I want to make sure Rails validates a field on model to never be 0. So less than OR greater than 0, but not both because that's not possible.
How can I do this?
There is already a validator for this called numericality
http://edgeguides.rubyonrails.org/active_record_validations.html#numericality
class Player < ApplicationRecord
validates :salary, numericality: { other_than: 0 }
end
validate :non_zero
def non_zero
if self.field_name == 0
self.errors.add(:field_name, "Field can't be zero")
end
end
validates :field, exclusion: { in: [0] }
https://guides.rubyonrails.org/active_record_validations.html#exclusion
works for fields that allow nil values; easy to test
I am trying to validate the entry a user makes in an amount field.
The field is amount_money
This field is a string which is validated on form submission
monetize :amount, :as => :amount_money
validates :amount, numericality: {only_integer: true}
validates :amount_money, numericality: {greater_than_or_equal_to: 0}
validate :amount_money_within_limit
validate :is_a_valid_number
I want to ensure there are no letters or symbols and that the amount is in an acceptable range.
the code to do this is
def amount_money_within_limit
if amount_money && amount_money.cents > 10_000_00
errors.add(:amount_money, 'cannot exceed $10,000.')
end
if amount_money && amount_money.cents < 1_00
errors.add(:amount_money, 'Problem with Amount')
end
end
this works great for input numbers, of numbers and letters, of letters, of special characters but
If I try Bob - the validation kicks in
but if I try BBob - the validation is bypassed.
If the input contains 2 Capital letters next to each other - it fails.
I've tried a downcase - but that doesn't suit as the field is monetized (money gem) - and the downcase screws up if there is valid input.
If the input to the field contains two uppercase letters - all the validations are bypassed So something like AA is not caught by any on the above validations
Why don't you use regular expressions? Something like this:
def is_a_valid_number? amount_money
amount_money =~ /\d+/
end
It seems you have put 1 validation on the wrong field, you should've put validations only on amount field (your real db field), and not on the amount_money which is automagical field from rails-money gem. I'll apply their documentation on numerical validations to your case:
monetize :amount,
:numericality => {
:only_integer => true,
:greater_than_or_equal_to => 1_00,
:less_than_or_equal_to => 10_000_00
}
You won't need any other custom validations with this setup.
I have Integer attribute in my model and I need back-end validation for that (it should be up to 7 digts only).
In my model I do some validations like:
validates :duration, format: { with: /([0-9]{1,7})/ }
validates :duration, numericality: { less_than_or_equal_to: 9999999 }
This works when I try to put letters into the form (it returns 'is not a number' error), works well when I put valid integer (11045555 - returns 'must be less than or equal to 9999999') BUT it crashes when I put really big number - let's say 11045555766666666666. Error is "11045555766666666666 is out of range for ActiveRecord::Type::Integer with limit 4"
How can I skip this crash when user puts that big number?
I use mysql2 DB.
You have to declare the limit on your migration file. By default, an integer is coded in 4 bytes, so the limit is 2147483647.
If you want to set a bigger limit, declare it like this
add_column :yourtable, :duration, :integer, :limit => 8
8 bytes have a 9223372036854775807 limit.
Just check How to specify the size of an integer in the migration script