svenyurgensson/parametron

View on GitHub
lib/parametron.rb

Summary

Maintainability
A
3 hrs
Test Coverage
require "parametron/version"

module Parametron

  def self.included(base)
    base.extend(ClassMethods)
    base.send(:attr_accessor, :validation_error_cause)
  end

  module ClassMethods
    attr_reader :params_validator
    # Declare known parameter keys
    # opts [Hash]
    #     :strict => false ; Raise exception on unknown key
    #     :reject => true  ; Reject unknown keys
    def params_for(method_name, opts={}, &block)
      instance_eval do
        @_method_name     = method_name.to_sym
        @params_validator = Parametron::ParamsValidator.new(opts)
        @params_validator.instance_eval(&block)
      end
    end

    def method_added(name)
      return if name != @_method_name or instance_variable_get(:"@_METHOD_#{name}_WRAPPED")
      instance_variable_set(:"@_METHOD_#{name}_WRAPPED", true)
      original = instance_method(name.to_sym)

      remove_method(name.to_sym)

      define_method(name) do |params={}|
        begin
          new_params = _rename_params!(_cast!(_validate!(_set_defaults!(params))))
        rescue => e
          if self.class.params_validator.on_exception_handler
            return self.class.params_validator.on_exception_handler.call(e)
          else
            raise e
          end
        end
        original.bind(self).call(new_params)
      end
    end
  end

  private
  def _cast!(params)
    new_par = params.dup
    _validators_list.each do |v|
      next unless v.cast
      key = v.name.to_sym
      val = new_par[key]
      next if val.nil? and not v.required?
      new_par[key] =
        case
        when v.cast.to_s == "Integer" then Integer(val)
        when v.cast.to_s == "Float"   then Float(val)
        when Proc === v.cast
          case v.cast.arity
          when 0 then v.cast.call()
          when 1 then v.cast.call(val)
          when 2 then v.cast.call(val, new_par)
          else v.cast.call(val, new_par, params)
          end
        else
          raise MalformedParams.new("Unknown cast type: '#{v.cast.inspect}'")
        end
    end
    new_par
  end

  def _rename_params!(params)
    new_par = params.dup
    _validators_list.each do |v|
      next unless v.as
      new_par[v.as] = new_par.delete(v.name.to_sym) || new_par.delete(v.name.to_str)
    end
    new_par
  end

  def _set_defaults!(params)
    new_par = params.dup
    _validators_list.each do |v|
      if new_par[v.name].nil? && new_par[v.name.to_sym].nil?
        case v.default
        when nil  then next
        when Proc
          case v.default.arity
          when 0 then new_par[v.name.to_sym] = v.default.call()
          when 1 then new_par[v.name.to_sym] = v.default.call(self)
          else
            raise ArgumentError.new "Too much arguments for #{v.name} default lambda"
          end
        else
          new_par[v.name.to_sym] = v.default
        end
      end
    end
    new_par
  end

  def _validate!(params)
    _params_validator.validate!(self, params)
    params
  end

  def _params_validator
    self.class.params_validator
  end

  def _validators_list
    _params_validator.required_vals + _params_validator.optional_vals
  end

  MalformedParams    = Class.new(ArgumentError)
  RequiredParamError = Class.new(ArgumentError)
  ErrorMethodParams  = Class.new(ArgumentError)
  ExcessParameter    = Class.new(ArgumentError)

end

require 'parametron/params_validator'