Hash Validator

Gem Unit Tests Maintainability

Ruby library to validate hashes (Hash) against user-defined requirements

Requirements

  • Ruby 3.0+

Installation

Add this line to your application's Gemfile:

gem 'hash_validator'

And then execute:

$ bundle

Or install it yourself as:

$ gem install hash_validator

Quick Start

require 'hash_validator'

# Define validation rules
validations = { name: 'string', age: 'integer', email: 'email' }

# Validate a hash
validator = HashValidator.validate(
  { name: 'John', age: 30, email: '[email protected]' },
  validations
)

validator.valid?  # => true

Examples

Successful Validation

validations = { name: 'string', active: 'boolean', tags: 'array' }
hash = { name: 'Product', active: true, tags: ['new', 'featured'] }

validator = HashValidator.validate(hash, validations)
validator.valid?  # => true
validator.errors  # => {}

Failed Validation

validations = { 
  user: {
    first_name: 'string',
    age: 'integer',
    email: 'email'
  }
}

hash = { 
  user: {
    first_name: 'James',
    age: 'thirty',  # Should be integer
    # email missing
  }
}

validator = HashValidator.validate(hash, validations)
validator.valid?  # => false
validator.errors  # => { user: { age: "integer required", email: "email required" } }

Rails Controller Example

class UsersController < ApplicationController
  def create
    validations = {
      user: {
        name: 'string',
        email: 'email',
        age: 'integer',
        website: 'url',
        preferences: {
          theme: ['light', 'dark'],
          notifications: 'boolean'
        }
      }
    }

    validator = HashValidator.validate(params, validations)

    if validator.valid?
      user = User.create(params[:user])
      render json: { user: user }, status: :created
    else
      render json: { errors: validator.errors }, status: :unprocessable_entity
    end
  end
end

# Example request that would pass validation:
# POST /users
# {
#   "user": {
#     "name": "John Doe",
#     "email": "[email protected]", 
#     "age": 30,
#     "website": "https://johndoe.com",
#     "preferences": {
#       "theme": "dark",
#       "notifications": true
#     }
#   }
# }

Usage

Define a validation hash which will be used to validate. This hash can be nested as deeply as required using the following validators:

Validator Validation Configuration Example Valid Payload
alpha { name: 'alpha' } { name: 'James' }
alphanumeric { username: 'alphanumeric' } { username: 'user123' }
array { tags: 'array' } { tags: ['ruby', 'rails'] }
boolean { active: 'boolean' } { active: true }
complex { number: 'complex' } { number: Complex(1, 2) }
digits { zip_code: 'digits' } { zip_code: '12345' }
email { contact: 'email' } { contact: '[email protected]' }
enumerable { status: ['active', 'inactive'] } { status: 'active' }
float { price: 'float' } { price: 19.99 }
hex_color { color: 'hex_color' } { color: '#ff0000' }
integer { age: 'integer' } { age: 25 }
json { config: 'json' } { config: '{"theme": "dark"}' }
numeric { score: 'numeric' } { score: 95.5 }
range { priority: 1..10 } { priority: 5 }
rational { ratio: 'rational' } { ratio: Rational(3, 4) }
regexp { code: /^[A-Z]{3}$/ } { code: 'ABC' }
required { id: 'required' } { id: 'any_value' }
string { title: 'string' } { title: 'Hello World' }
symbol { type: 'symbol' } { type: :user }
time { created_at: 'time' } { created_at: Time.now }
url { website: 'url' } { website: 'https://example.com' }

For hash validation, use nested validations or {} to just require a hash to be present.

Advanced Features

Class Validation

On top of the pre-defined validators, classes can be used directly to validate the presence of a value of a specific class:

validations = { created_at: Time, user_id: Integer }
hash = { created_at: Time.now, user_id: 123 }
HashValidator.validate(hash, validations).valid?  # => true

Enumerable Validation

Validate that a value is contained within a supplied enumerable:

validations = { status: ['active', 'inactive', 'pending'] }
hash = { status: 'active' }
HashValidator.validate(hash, validations).valid?  # => true

Lambda/Proc Validation

Use custom logic with lambdas or procs (must accept only one argument):

validations = { age: ->(value) { value.is_a?(Integer) && value >= 18 } }
hash = { age: 25 }
HashValidator.validate(hash, validations).valid?  # => true

Regular Expression Validation

Validate that a string matches a regex pattern:

validations = { product_code: /^[A-Z]{2}\d{4}$/ }
hash = { product_code: 'AB1234' }
HashValidator.validate(hash, validations).valid?  # => true

Multiple Validators

Apply multiple validators to a single key:

HashValidator.validate(
  { score: 85 },
  { score: HashValidator.multiple('numeric', 1..100) }
).valid?  # => true

This is particularly useful when combining built-in validators with custom validation logic.

Custom Validations

Allows custom defined validations (must inherit from HashValidator::Validator::Base). Example:

# Define our custom validator
class HashValidator::Validator::OddValidator < HashValidator::Validator::Base
  def initialize
    super('odd')  # The name of the validator
  end

  def validate(key, value, validations, errors)
    unless value.is_a?(Integer) && value.odd?
      errors[key] = presence_error_message
    end
  end
end

# Add the validator
HashValidator.append_validator(HashValidator::Validator::OddValidator.new)

# Now the validator can be used! e.g.
validator = HashValidator.validate({ age: 27 }, { age: 'odd' })
validator.valid?  # => true
validator.errors  # => {}

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request