hopsoft/field_mapper

View on GitHub
lib/field_mapper/standard/field.rb

Summary

Maintainability
B
6 hrs
Test Coverage
require "csv"
require_relative "value"

module FieldMapper
  module Standard
    class Field
      include FieldMapper::Marshaller

      attr_reader(
        :name,
        :type,
        :desc,
        :default,
        :values,
      )

      attr_accessor :placeholder

      def initialize(
        name,
        type: nil,
        desc: nil,
        default: nil,
        placeholder: nil
      )
        raise TypeNotSpecified.new("type not specified for: #{name}") if type.nil?
        @name = name.to_sym
        @type = type
        @desc= desc
        @default = default
        @placeholder = placeholder
      end

      def list?
        type.name == "FieldMapper::Types::List"
      end

      def list_with_emtpy_default?
        list? && default == []
      end

      def plat?
        type.name == "FieldMapper::Types::Plat"
      end

      def plat_field?
        plat? || plat_list?
      end

      def plat_list?
        list? && type.plat_list?
      end

      def raw_values
        return nil unless has_values?
        values.map { |v| v.value }
      end

      # Adds a value to a Field instance.
      # Intended use is from within a Plat class declaration.
      #
      # @example
      #   class ExamplePlat < FieldMapper::Standard::Plat
      #     field :example do
      #       value 1
      #     end
      #   end
      def value(val)
        @values ||= []
        @values << FieldMapper::Standard::Value.new(val, field: self)
        @values.last
      end

      # Adds values to a Field instance that are defined in a CSV file.
      #
      # Intended use is from within a Plat class declaration.
      # @example
      #   class ExamplePlat < FieldMapper::Standard::Plat
      #     field :example do
      #       load_values "/path/to/file.csv"
      #     end
      #   end
      #
      # The format of the CSV file should contain a single column with a header row.
      # @example
      #   Name of Field
      #   1
      #   2
      #
      def load_values(path_to_csv)
        CSV.foreach(path_to_csv, :headers => true) do |row|
          value row["standard_value"].to_s.strip
        end
      end

      def has_values?
        !values.nil?
      end

      def find_value(value)
        return nil unless has_values?
        values.find { |val| val == value || val.value == value }
      end

      def cast(value, as_single_value: false)
        value = cast_value(type, value, as_single_value: as_single_value)
        return nil if value.nil? || value.to_s.blank?
        value = clean_value(value) unless as_single_value
        value
      end

      alias_method :to_s, :name

      private

      def cast_value(type, value, as_single_value: false)
        return nil if value.nil?
        case type.name
        when "String"                       then return string(value)
        when "FieldMapper::Types::Boolean"  then return boolean(value)
        when "Time"                         then return time(value)
        when "Integer"                      then return value.to_i
        when "Float"                        then return value.to_f
        when "Money"                        then return money(value)
        when "FieldMapper::Types::Plat"     then return plat_instance(type, value)
        when "FieldMapper::Types::List"     then
          return cast_value(type.type, value) if as_single_value
          return plat_instance_list(type, value) if type.plat_list?
          get_list value
        else
          nil
        end
      end

      def get_list(value)
        value = unmarshal(value) if value.is_a?(String)
        raise InvalidListValue.new("#{name} is not a list") unless value.respond_to?(:map)
        value.map { |val| cast_value(type.type, val) }
      end

      def clean_value(value)
        return value unless has_values?
        return value unless type.name == "FieldMapper::Types::List"
        value & raw_values
      end

      def string(value)
        return value if value.is_a?(String)
        value.to_s.strip
      end

      def boolean(value)
        return value if value.is_a?(TrueClass) || value.is_a?(FalseClass)
        FieldMapper::Types::Boolean.parse(value)
      end

      def time(value)
        return value.utc if value.is_a?(Time)
        return value.to_time.utc if value.is_a?(Date)
        return value.to_time(:utc) rescue nil if value.is_a?(String)
        nil
      end

      def money(value)
        return value if value.is_a?(Money)
        return Monetize.parse(value) rescue nil
      end

      def plat_instance(type, value)
        return value if value.is_a?(FieldMapper::Standard::Plat)
        return value if value.is_a?(Numeric)
        return value.to_i if value.is_a?(String) && value =~ /\A\d+\z/
        return type.type.new(value) if value.is_a?(Hash)
        return type.type.new(unmarshal(value)) if value.is_a?(String)
        nil
      end

      def plat_instance_list(type, value)
        return value if value.is_a?(Array) && value.empty?
        value = unmarshal(value) if value.is_a?(String)
        return value.map { |val| plat_instance(type, val) }
      end

    end
  end
end