🛤️ Ruby on Rails - Validation
Updated at 2015-08-13 19:16
Related to Ruby Guide and Ruby on Rails Guide.
Use model validations.
# these methods trigger model validation
# bang (!) variants raise exception if it fails
create
create!
save
save!
update
update!
# valid? tells if a record is valid
class Person < ActiveRecord::Base
validates :name, presence: true
end
p = Person.new
p.errors.messages # => {}
p.valid? # => false
p.errors.messages # => {name:["can't be blank"]}
Use the concise validation variant.
# bad
validates_presence_of :email
validates_length_of :email, maximum: 100
# good
validates :email, presence: true, length: { maximum: 100 }
Use validation helpers:
class Person < ActiveRecord::Base
validates :name, :login, :email, presence: true
# checkbox must be checked
validates :terms_of_service, acceptance: true
validates :terms_of_service, acceptance: { accept: 'yes' } # what is accepted?
# related models need to be valid on save too
has_many :books
validates_associated :books
# must also have email_confirmation field speficied with the same value
validates :email, confirmation: true
validates :email_confirmation, presence: true
# validate by regex
validates :legacy_code, format: {
with: /\A[a-zA-Z]+\z/,
message: "only allows letters"
}
# validate by whitelist
validates :size, inclusion: {
in: %w(small medium large),
message: "%{value} is not a valid size"
}
# validate length
validates :name, length: { minimum: 2 }
validates :bio, length: {
maximum: 500,
too_long: "%{count} characters is the maximum allowed"
}
validates :password, length: { in: 6..20 }
validates :registration_number, length: { is: 6 }
# change how the length it calculated with tokenizer
validates :content, length: {
minimum: 300,
maximum: 400,
tokenizer: lambda { |str| str.split(/\s+/) },
too_short: "must have at least %{count} words",
too_long: "must have at most %{count} words"
}
# allow only numeric values
validates :points, numericality: true
validates :games_played, numericality: { only_integer: true }
validates :games_bought, numericality: { greater_than: 10 }
validates :games_viewed, numericality: { even: true }
end
Note that validates_presence_of
doesn't validate booleans properly.
# bad
validates_presence_of :protected
# good
validates_inclusion_of :protected, in: [true, false]
Beware of the behavior of the update_attribute
method. It doesn't run the model validations unlike update_attributes
and could easily corrupt the model state.
Use custom validators if used more than once. Really help to keep code easy to maintain. Keep custom validators under app/validators
.
# bad
class Person
validates :email, format: {
with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
}
end
# good
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
record.errors[attribute] << (options[:message] || 'is not a valid email')
end
end
end
class Person
validates :email, email: true
end
Consider creating a validator gem. Consider extracting custom validators to a shared gem if you're maintaining several related apps and the validators are generic enough.