polyfox/moon-data_model

View on GitHub
lib/data_model/field.rb

Summary

Maintainability
A
0 mins
Test Coverage
require 'data_model/type'
require 'data_model/validators'

module Moon
  module DataModel
    # Object for storing information about a Field on a model
    class Field
      # @return [Symbol] name of the field
      attr_reader :name

      # @return [Array, Hash, Class] type
      attr_reader :type

      # @return [Proc, Object] default
      attr_reader :default

      # @return [Boolean] allow_nil  are nils allowed for the value?
      attr_reader :allow_nil

      # @return [Boolean] coerce_values  should the field coerce its values?
      attr_reader :coerce_values

      # @return [Boolean] is_key  Is this a key field (such as an id)?
      attr_reader :is_key

      # @return [Array<Validator::Base<>>]
      attr_reader :validators

      # @param [Hash<Symbol, Object>] options
      # @option options [Symbol] :name
      #   Name of the field
      # @option options [Object] :type
      #   An object that can be transformed into a DataModel::Type
      # @option options [Boolean] :allow_nil
      #   Does this field allow nil values?
      # @option options [Boolean] :coerce_values
      #   Coerce values, may cause undesirable behaviour with certain types.
      # @option options [Boolean] :is_key
      #   Mark this field as the `key` field
      # @option options [Hash<Symbol, Object>] :validate
      #   Validation parameters, keys are the name of the validator
      #   and their values are the parameters for that validator.
      def initialize(options)
        @name          = options.fetch(:name)
        initialize_type(options.fetch(:type))
        @default       = options.fetch(:default) { ->(_, _) { nil } }
        @allow_nil     = options.fetch(:allow_nil, false)
        @coerce_values = options.fetch(:coerce_values, false)
        @is_key        = options.fetch(:is_key, false)
        initialize_validators(options.fetch(:validate) { {} })
      end

      # Finalizes the type, this will replace the current {Field#type} with the
      # finalized version. see also {Type#finalize}
      #
      # @return [void]
      def finalize
        @type = @type.finalize
      end

      # @param [Object] type
      # @return [void]
      #
      # @api private
      def initialize_type(type)
        @type = Moon::DataModel::Type[type]
      end

      # Creates validators from the given options, also appends a
      # Validators::Type if none is given.
      #
      # @param [Hash<Symbol, Object>] options
      # @return [void]
      #
      # @api private
      def initialize_validators(options)
        @validators = []

        options.each_pair do |key, opts|
          @validators << Validators.fetch(key).new(opts)
        end

        # if the @validators already includes a Type validator, just skip this.
        unless options.find { |v| v.is_a?(Validators::Type) }
          k = Validators.fetch(:type)
          type_validator = k.new(type: @type, allow_nil: @allow_nil,
                                 ctx: { key: @name })
          @validators = [type_validator].concat(@validators)
        end
      end

      # Attempts to convert the provided value to the Field's type
      #
      # @param [Object] value
      # @return [Object] coerced value, may return the value given.
      #
      # @api public
      def coerce(value)
        return value unless @coerce_values
        @type.coerce(value)
      end

      # Returns the default value for the field, a model must be provided
      # if the default is a Proc and not a value.
      #
      # @return [Object]
      #
      # @api public
      def make_default(model = nil)
        @default.call(@type, model)
      end

      # Checks that the given value is valid for the field.
      #
      # @param [Object] value
      # @return [Boolean]
      def valid?(value)
        @validators.all? do |validator|
          validator.valid?(value)
        end
      end

      # Invokes each validator on the provided value, if the value
      # fails validation, the Validator +may+ raise an error.
      #
      # @param [Object] value
      # @return [void]
      #
      # @api public
      def validate(value, quiet = false)
        @validators.each do |validator|
          validator.validate(value)
        end
      end

      # Generic field `default` proc.
      # The best way to use this is to use it as a general field_setting
      #
      # @return [Proc]
      #
      # @example
      #   # as a single line
      #   field :my_model, type: MyModel, default: Moon::DataModel::Field.default_proc
      #   # as a field_setting, all fields inside the block will get the default
      #   field_settings default: Moon::DataModel::Field.default_proc do
      #     field :id, type: String
      #     field :a, type: Integer
      #     field :a_model, type: AModel
      #   end
      def self.default_proc
        lambda do |type, _|
          # Integer, Numeric and Float cannot be created using .new
          if type.model == Integer then 0
          elsif type.model == Numeric then 0
          elsif type.model == Float then 0.0
          else
            type.model.new
          end
        end
      end

      alias :is_key? :is_key
      alias :allow_nil? :allow_nil
    end
  end
end