Skip to content

How validation works

Scott Williams edited this page Jun 18, 2014 · 2 revisions

How Validation Works

Validation allows you to make sure your objects are in a state that is acceptable. Here's how the library works. Here's a simple validation sample:

class Widget
  include Arkenstone::Document
  include Arkenstone::Validation
  
  url 'http://example.com/widgets'
  
  attributes :name, :size, :sku
  
  validates :name, presence: true
end

Arkenstone::Validation

This module is kept in lib/arkenstone/validation/validations.rb. When the module is included in a class, the included method in class << self is executed, which in turn adds a module for instance methods, and another for class methods.

Class Methods

The class ClassMethods module adds two accessors to the class to contain a collection of validation methods to execute.

  • fields_to_validate — is a hash that uses the property name as a key. Its value is an array of pre-baked validators to run against that property.
  • custom_validators — is an array of custom validators that will run during validation.

validates(attr, *options)

This method is used to declare how a property is validated. First, it makes sure that fields_to_validate exists. Next, it normalizes the attr by downcasing it and converting it to a symbol.

Since the value for a key in the fields_to_validate hash is an array, Arkenstone will create a new one if it did not already exist in fields_to_validate. The options splat is then turned into a validator hash by create_validator and then pushed onto the array.

create_validator(options_hash)

This creates a hash that the validate method will read and then call the appropriate validator method. It does this by taking the first key from the options_hash, which corresponds to the type of validation to perform. The options_hash itself will be set as the value and used as parameters for the validation method when it is valled. If {:presence => true} was passed in, that means that the resulting hash will be {:presence => {:presence=>true}}`.

This process is a little complex. This diagram helps explain:

Validation Diagram

validate(custom_validation_method)

This is similar to validates in that it stores methods to run, but is much simpler. First, it ensures that custom_validators array exists. Then it pushes the custom_validation_method onto it. custom_validation_method is expected to be a symbol that has the same name as a method on the object.

Instance Methods

Validation provides you with two public methods to use to validate your models. There is also an errors accessor that will contain any validation errors that occur.

valid?

Calls the validate method and then returns whether there were any errors or not.

validate

First, the errors array is reinitialized (so that each call the validate starts anew). Then the pre-baked validators are run via validate_with_validators.

validate_with_validators

When the validates method is declared in a class, the validator provided is stored in the fields_to_validate class accessor. The keys of this hash are property names converted to symbols, and the values are arrays containing the validators.

Each key/value of the fields_to_validate hash is iterated over. The array of validators is assigned to a validators variable. This array is then iterated over so that the validator method can be derived and called.

So, if our validator structure looks like this:

  {
    name: [
      { presence: { presence: true } },
      { format: { SNIP } }
    ],
    arbitrary_number: [
      { type: { type: Integer } }
    ]
  } 

Each iteration of the loop will look like this:

# Iteration 1
attr: :name
validator_hash: { presence: { presence: true } }

# Iteration 2
attr: :name
validator_hash: { format: { SNIP } }

# Iteration 3
attr: :arbitrary_number
validator_hash: { type: { type: Integer } }

Each built-in validation method is prefixed with validate_ with the type of validation appended to it. So, when validates :name, presence: true is declared, the validate_presence (ref) method within the module is called.

Within the validation loop, a key variable is created from the first key in the validator_hash. In our first iteration, that value is presence. Therefore, the validation_method to call is validate_presence. The value of the validator hash becomes the options argument that is sent to the validator method. In our example, this is what is called for each iteration:

# Iteration 1
validate_presence :name, { presence: true }

# Iteration 2
validate_format :name, {:format=>{:with=>/\w+/, :message=>"Must be a word or number"}}

# Iteration 3
validate_type :arbitrary_number, { type: Integer }

If the validation method was successful nil is returned. Any other value will be treated as an error. Errors are added to the @errors instance object. Flow is then returned back to validate.

validate (continued)

Once the pre-baked validators run, the custom ones are next. These are run in validate_with_custom_validators.

validate_with_custom_validators

This method loops through the custom_validators class property. custom_validators is an array of method names. Each method is executed on the object. It is up to this custom method to add errors to the @errors instance variable. Once this is complete, flow is returned back to validate which has no more work to do and bubbles back to valid?.

valid? (continued)

If any validation failures were encountered, they were stored in the @errors instance variable. valid? returns whether this object is empty or not.

Clone this wiki locally