rubyhanami

Inherited Hanami validators


I have Hanami 1.3.3 and two validators there: for create and update actions like these

module Validators
  module MyEntities
    class Create
      include Hanami::Validations::Form
      include Validatable

      messages :i18n

      validations do
        required(:first_attr).filled(:bool?)
        required(:second_attr).filled(:int?)

        required(:attr_for_create).filled(:str?)
      end
    end
  end
end
module Validators
  module MyEntities
    class Update
      include Hanami::Validations::Form
      include Validatable

      messages :i18n

      validations do
        required(:first_attr).filled(:bool?)
        required(:second_attr).filled(:int?)

        required(:attr_for_update).filled(:str?)
      end
    end
  end
end

In fact, the number of validation rules is more than twenty

As you see there is a lot of repetitive code here

I want to DRY it and use some inheritance

I couldn't find some receipts for this. Just discussion in GitHub issue. Outcome of that discussion: it's very tricky

How to use inherited validators in Hanami?


Solution

  • To answer this question, we need to look at the source code of the library

    As you see there is a validations method in Hanami validations gem

    This method builds schema for validation. Among other things, the block passed to this method is used. It is the block with those validations rules that we write in our validators inside the application

    To use inheritance, we can overwrite validations method in the parent class and join two blocks there: basic parent rules and child rules

    To do this, we need instance_eval method

    module Validators
      module MyEntities
        class Base
          include Hanami::Validations::Form
          include Validatable
    
          messages :i18n
    
          class << self
            def validations(&rules)
              schema_predicates = __predicates
    
              base   = _build(predicates: schema_predicates, &_base_rules)
              schema = _build(predicates: schema_predicates, rules: base.rules, &my_entity_rules(&rules)) # overwrite just here
              schema.configure(&_schema_config)
              schema.configure(&_schema_predicates)
              schema.extend(__messages) unless _predicates.empty?
    
              self.schema = schema.new
            end
    
            private
    
            def my_entity_rules(&rules)
              Proc.new do
                instance_eval(&rules) # use child rules
    
                required(:first_attr).filled(:bool?)
                required(:second_attr).filled(:int?)
              end
            end
          end
        end
      end
    end
    
    module Validators
      module MyEntities
        class Create < Base
          validations do
            required(:attr_for_create).filled(:str?)
          end
        end
      end
    end
    
    module Validators
      module MyEntities
        class Update < Base
          validations do
            required(:attr_for_update).filled(:str?)
          end
        end
      end
    end