Rails model validations have a host of options
that allow developers to write complex rules for clean data entry.
A common validation is to ensure that the length of a string is within a certain range.
For example, a user’s name should be between 2
and 20 characters.
This can be achieved using the length
validator.
app/models/user.rb
class User
validates_length_of :first_name, in: 2..30
end
For most part this gets the job done. When a user model is created with a name less than 2 characters or more than 30 characters, an appropriate error is thrown,
irb(main):001:0> User.create! first_name: "A"
/Users/swaathi/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rails-c41e2f1bca65/activerecord/lib/active_record/validations.rb:82:in `raise_validation_error': Validation failed: First name is too short (minimum is 2 characters) (ActiveRecord::RecordInvalid)
irb(main):002:0> User.create! first_name: "A"*31
/Users/swaathi/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/bundler/gems/rails-c41e2f1bca65/activerecord/lib/active_record/validations.rb:82:in `raise_validation_error': Validation failed: First name is too long (maximum is 30 characters) (ActiveRecord::RecordInvalid)
Before
However not all attributes require a minimum or maximum length.
For example, a middle name can have a maximum length of 30 characters,
but it can be empty.
This is not possible with the current implementation of the length
validator.
The length validation or a custom one would have to be written.
class User
validates :middle_name, length: { maximum: 30 }
validate :middle_name_length
private
def middle_name_length
if middle_name.present? && middle_name.length > 30
errors.add(:middle_name, "is too long (maximum is 30 characters)")
end
end
end
After
Thanks to this PR which introduces extensions to the Range class, the first or the last value of a range can be infinite. This allows us to write the following validations,
class User
validates_length_of :first_name, in: 2..
validates_length_of :middle_name, in: ..30
end
This allows validations to be written in a more concise way.
The length
validator now supports infinite ranges in the :in
and :within
options.
This change is possible due to the Float::INFINITY
option which is a number that is always the largest in a comparison.
This is how the code looks now,
activemodel/lib/active_model/validations/length.rb
def initialize(options)
if range = (options.delete(:in) || options.delete(:within))
raise ArgumentError, ":in and :within must be a Range" unless range.is_a?(Range)
options[:minimum] = range.min if range.begin
options[:maximum] = (range.exclude_end? ? range.end - 1 : range.end) if range.end
end
if options[:allow_blank] == false && options[:minimum].nil? && options[:is].nil?
...
end
def check_validity!
keys.each do |key|
value = options[key]
unless (value.is_a?(Integer) && value >= 0) ||
value == Float::INFINITY || value == -Float::INFINITY ||
value.is_a?(Symbol) || value.is_a?(Proc)
raise ArgumentError, ":#{key} must be a non-negative Integer, Infinity, Symbol, or Proc"
end
end
...
end