maki-tetsu/anodator

View on GitHub
lib/anodator/validator/base.rb

Summary

Maintainability
A
1 hr
Test Coverage
require 'anodator/validator/value_proxy'

module Anodator
  module Validator
    # +Validator::Base+ is basic class for validations
    class Base
      # value of target for validation
      @@values = nil

      # data source set
      @@data_source_set = nil

      # valid option keys
      #
      # :allow_blank options are all available options in Validator.
      # In addition, Validator All you need to consider the case of the blank.
      @valid_option_keys = %i[allow_blank description]

      # default options
      #
      # Is used as the initial value was not set when you create a new Validator.
      @default_options   = { allow_blank: false }

      # target specify value
      attr_reader :target

      # option values
      attr_reader :options

      # @@valid_option_keys = [:allow_blank]

      # class methods
      class << self
        # set and/or get valid option keys
        def valid_option_keys(*option_keys)
          # initialize from superclass
          if @valid_option_keys.nil?
            @valid_option_keys = superclass.valid_option_keys
          end

          unless option_keys.size.zero?
            option_keys.each do |key|
              if @valid_option_keys.include?(key)
                raise ArgumentError, "Validator already has option for '#{key}'"
              else
                @valid_option_keys << key
              end
            end
          end

          @valid_option_keys.dup
        end

        # set and/or get default options
        def default_options(options = nil)
          # initialize from superclass
          @default_options = superclass.default_options if @default_options.nil?

          unless options.nil?
            unless options.is_a? Hash
              raise ArgumentError, 'default_options must call with Hash'
            end
            options.each do |option, default_value|
              if @valid_option_keys.include?(option)
                @default_options[option] = default_value
              else
                raise ArgumentError, "Unknown option '#{option}'"
              end
            end
          end

          @default_options.dup
        end

        # Set the data to be checked.
        #
        # Data to be checked can be set to any object that responds to [].
        def values=(values)
          if values.respond_to?(:[])
            @@values = values
          else
            raise ArgumentError, 'values must be respond to [] method for validations.'
          end
        end

        # Get the data to be checked
        def values
          @@values
        end

        # Set the data source with check.
        def data_source_set=(data_source_set)
          msg = 'data_source_set must be Anodator::DataSourceSet'
          raise ArgumentError, msg unless data_source_set.is_a? DataSourceSet

          @@data_source_set = data_source_set
        end

        def data_source_set
          @@data_source_set
        end
      end

      # initializer
      #
      # Specify +options+ as an additional parameter to hold the verification
      # and other options for specifying the +target_expression+ be verified.
      # The default options are optional and will be an empty hash.
      # Key to use additional parameters are stored in class variable
      # +valid_option_keys+.
      # If necessary add additional parameters for the new validator is defined
      # in the inherited class to add.
      def initialize(target_expression, options = {})
        if target_expression.to_s.length.zero?
          raise ArgumentError, 'target cannot be nil or blank'
        else
          @target = target_expression.to_s
        end
        @options = {}
        merge_options!(self.class.default_options)
        merge_options!(options)
      end

      # validation method
      #
      # +validate+ method must be defined in a class that inherits.
      # In the +validate+ method implements the verification method.
      # Can be implemented to return a boolean value to the final,
      # +valid?+ The method used is called.
      def validate
        raise NoMethodError, "must define method 'validate'"
      end

      # Call the +validate+ method to return a boolean value accordingly
      #
      # If any exception occurs in the +validate+ method displays the contents
      # to standard error. Then raise same error.
      def valid?
        validate
      end

      # merge options
      #
      # If the key parameter is illegal to throw an ArgumentError.
      def merge_options!(options)
        options.each do |key, value|
          if self.class.valid_option_keys.include?(key)
            @options[key] = value
          else
            raise ArgumentError, "Unknown option key '#{key}'."
          end
        end
      end

      private :merge_options!

      # target_value
      #
      # always return String object use to_s method
      def target_value
        @@values[target].to_s
      end

      def argument_value_at(name_or_index)
        @@values[name_or_index].to_s
      end

      def data_source_at(identifier, key, column)
        @@data_source_set.fetch(identifier, key, column)
      end

      def allow_blank?
        @options[:allow_blank]
      end

      def description
        @options[:description]
      end

      def proxy_value(target)
        ValueProxy.new(target, self)
      end
      private :proxy_value

      def to_s(level = 4, _step = 2)
        (' ' * level) + "- #{self.class}(#{description})"
      end

      def validate_configuration
        @@values.spec_item_by_expression(@target)
      rescue UnknownTargetExpressionError => e
        raise InvalidConfiguration, e.to_s
      end
    end
  end
end