PureValidator is a simple, mostly dependency-free (except i18n) library to validate your domain Ruby objects.
It keeps the concerns of validation separate from the Entity / Value object itself.
This gives you the option to have different validations rules for different ocasions for the same object in a very clean and unit-testable way.
It is a simple step to separate those concerns, but it will give you unlimited flexibility and save your *SS from debugging entangled and conflicting validations for non-trivial requirements.
Do yourself a favor and start using PureValidator today, you will never look back!
Add this line to your application's Gemfile:
gem'pure_validator'Let's say you have the following class and you want to validate its instances:
classContactattr_accessor:first_name,:last_name,:position,:age,:type,:email,:color,:status,:stage,:description,:companies,:addressdefto_h{first_name: first_name,last_name: last_name,position: position,age: age,type: type,email: email,color: color,status: status,stage: stage,description: description,companies: (companies||[]).map(&:to_h),address: (address && address.to_h),}endendTo validate objects of the Contact class define following validator:
classContactValidatorincludePureValidator::Validatorvalidates:first_name,presence: true,length: {min: 4,max: 7}validates:last_name,length: {equal_to: 5}validates:position,length: {not_equal_to: 5}validates:age,numericality: {greater_than: 0,less_than: 150}validates:type,numericality: {greater_than_or_equal_to: 1,less_than_or_equal_to: 5}validates:email,email: truevalidates:color,regexp: /#\w{6}/validates:status,inclusion: {in: [:new,:lead]}validates:stage,exclusion: {in: [:wrong,:bad]}validate_associated:companies,validator: CompanyValidatorvalidate:check_descriptiondefcheck_description(entity,errors)ifentity.description.nil?errors.add(:description,"can't be empty")endendendInstantiate the validator and call validate with a contact object:
errors=ContactValidator.new.validate(contact)It also works with plain hashes:
errors=ContactValidator.new.validate(contact.to_h)errors is a Hash that contains all validation errors.
If the object is valid then errors will be an empty Hash.
PureValidator can be extended by adding your own validators.
To add a validator define a class with 2 static methods: validate and validate_options:
The following example shows the built-in inclusion validator, it validates that specified value is one of the defined values.
classPureValidator::Validators::InclusionValidator# Validates that given value inscluded in the specified list# @param value [Object] object to validate# @parm options [Hash] validation options, e.g. { in: [:small, :medium, :large], message: "not included in the list of allowed items" }# where :in - list of allowed values,# message - is a message to return if value is not included in the list# @return [Array] empty array if object is valid, list of errors otherwisedefself.validate(value,options)return[]ifvalue.nil?errors=[]ifoptions[:in]unlessoptions[:in].include?(value)errors << (options[:message] || PureValidator::I18n.t('errors.should_be_included_in_list',list: options[:in]))endenderrorsend# Validates that options specified in# :inclusion are validdefself.validate_options(options)raiseArgumentError,"validation options should be a Hash"unlessoptions.is_a?(Hash)raiseArgumentError,"validation options should have :in option and it should be an array of allowed values"unlessoptions[:in].is_a?(Array)endendAnd register it in PureValidator:
PureValidator.add_validator(:inclusion,PureValidator::Validators::InclusionValidator)Now you can use it:
classSomeValidatorincludePureValidator::Validatorvalidates:size,inclusion: {in: [:small,:medium,:large]}endAdd this line to your application's Gemfile:
gem 'pure_validator'
And then execute:
$ bundle
Or install it yourself as:
$ gem install pure_validator
- Fork it
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create new Pull Request
- Albert Gazizov, @deeper4k
- Roman Heinrich, @mindreframer