Validations
Role of Validations
Validation is used to validate the coherence of data in a context. It can be as simple as validating if a field is present or it can be more complex and use other data and logic.
Validation is tied to a context: A model can be valid at the creation and then invalid at update. Or it can be valid if an admin creates it and invalid if a regular user creates it.
Validation can also be triggered at the collection level. One model can't be created or updated if there is a conflict with existing data.
Location
app/predicates/
Usage
Validation can be used:
- Inside the model itself (validation is tied to a model)
- Inside a Service (when creating new models)
- Inside a controller to display error messages to the user
Naming
The naming should be explicit regarding the model.
UserIsValid
AdminIsValid
OfferIsValidForCompany
Structure
- All validations classes are
Predicates
and should inherit fromPredicate::Base
. - They are initialized with a single model in the
initialize
method.
Code
module Accounts
# Is the user valid? Is it ok to be saved in the database?
class UserIsValid < Predicate::Base
def initialize(user)
@user = user
end
validates_presence_of :first_name, :last_name
validates_presence_of :email
validates_format_of :email, with: User::EMAIL_FORMAT, allow_blank: true, if: :email_changed?
validate :unique_key_is_unique, if: -> { @user.email_changed? || @user.school_id_changed? }
# Password validations
validates_presence_of :password, if: -> { @user.new_record? }
validates_confirmation_of :password, unless: 'password.blank?'
validate :password_is_secure
validate :content_locales, presence: true
# Mandatory fields for external users
validates_presence_of(
:phone_number,
:address,
:zip_code,
:city,
:country,
:position,
if: :require_contact_information?
)
validate :promotion_date_is_valid, if: :promotion_date_changed?
private
delegate :first_name, :last_name, :email, :password, :password_confirmation, :email_changed?,
:school_id_changed?, :promotion_date_changed?, :phone_number, :address,
:zip_code, :city, :country, :position, :content_locales, to: :@user
def require_contact_information?
@user.extern_user?
end
def password_is_secure
return if password.nil?
password_security = CheckPasswordSecurity.new(password, @user.role)
errors.add(:password, password_security.error_message) unless password_security.valid?
end
def promotion_date_is_valid
return if @user.promotion_date.blank?
return if @user.promotion_date.to_s =~ /\A[1-9][0-9]{3}\Z/
errors.add(:promotion_date, I18n.t('invalid_year_format'))
end
def unique_key_is_unique
errors.add(:email, :is_already_taken) if User.other_user_with_same_unique_key_exists?(@user)
end
end
end