ruprict/oriented

View on GitHub
lib/oriented/type_converters.rb

Summary

Maintainability
B
4 hrs
Test Coverage
module Oriented

  # This module was copied from neo4j-wrapper
  module TypeConverters

    # The default converter to use if there isn't a specific converter for the type
    class DefaultConverter
      class << self

        def to_java(value)
          value
        end

        def to_ruby(value)
          value
        end

        def index_as
          String
        end
      end
    end


    class BooleanConverter
      class << self

        def convert?(class_or_symbol)
          :boolean == class_or_symbol
        end

        def to_java(value)
          return nil if value.nil?
          !!value && value != '0'
        end

        def to_ruby(value)
          return nil if value.nil?
          !!value && value != '0'
        end

        def index_as
          String
        end
      end

    end

    class SymbolConverter
      class << self

        def convert?(class_or_symbol)
          :symbol == class_or_symbol || Symbol == class_or_symbol
        end

        def to_java(value)
          return nil if value.nil?
          value.to_s
        end

        def to_ruby(value)
          return nil if value.nil?
          value.to_sym
        end

        def index_as
          String
        end

      end
    end


    class StringConverter
      class << self

        def convert?(class_or_symbol)
          [String, :string, :text].include? class_or_symbol
        end

        def to_java(value)
          return nil if value.nil?
          Array === value ? value.map(&:to_s)  : value.to_s
        end

        def to_ruby(value)
          return nil if value.nil?
          value
        end

        def index_as
          String
        end

      end
    end

    class FixnumConverter
      class << self

        def convert?(class_or_symbol)
          Fixnum == class_or_symbol || :fixnum == class_or_symbol || :numeric == class_or_symbol
        end

        def to_java(value)
          return nil if value.nil?
          Array === value ? value.map(&:to_i) : value.to_i
        end

        def to_ruby(value)
          return nil if value.nil?
          value#.to_i
        end

        def index_as
          Fixnum
        end

      end
    end

    class FloatConverter
      class << self

        def convert?(clazz_or_symbol)
          Float == clazz_or_symbol || :float == clazz_or_symbol
        end

        def to_java(value)
          return nil if value.nil?
          Array === value ? value.map(&:to_f) : value.to_f
        end

        def to_ruby(value)
          return nil if value.nil?
          value
        end

        def index_as
          Float
        end

      end
    end

    # Converts Date objects to Java long types. Must be timezone UTC.
    class DateConverter
      class << self

        def convert?(clazz_or_symbol)
          Date == clazz_or_symbol || Java::JavaUtil::Date == clazz_or_symbol || :date == clazz_or_symbol
        end

        def to_java(value)
          return nil if value.nil?
          return value if value.class == Java::JavaUtil::Date
          value = parse_date_from_string(value) if value.class == String
          # We should now have a date
          Time.utc(value.year, value.month, value.day).to_date
        end

        def to_ruby(value)
          return nil if value.nil?
          return value if value.class == Date
          jv = value.getTime/1000 if value.class == Java::JavaUtil::Date
          return nil if jv.nil?
          t = Time.at(jv).utc.to_date
        end


        def index_as
          Fixnum
        end

        private

        def parse_date_from_string(value)
          return value unless value.class == String
          match = value.match(/[-|\/]\d{2}$/)
          if match
            if value[/\//]
              #dd/mm/yy
              return Date.strptime(value, '%m/%d/%y')
            else value[/-/]
              #dd-mm-yy
              return Date.strptime(value, '%m-%d-%y')
            end
          else
            if value[/\//]
              #dd/mm/yyyy
              return Date.strptime(value, '%m/%d/%Y')
            else value[/-/]
              #dd-mm-yyyy
              return Date.strptime(value, '%m-%d-%Y')
            end
          end
          Date.strptime(value)
        rescue => e
          value
        end
      end
    end

    # Converts DateTime objects to and from Java long types. Must be timezone UTC.
    class DateTimeConverter
      class << self

        def convert?(clazz_or_symbol)
          DateTime == clazz_or_symbol || :datetime == clazz_or_symbol
        end

        # Converts the given DateTime (UTC) value to an Fixnum.
        # DateTime values are automatically converted to UTC.
        def to_java(value)
          return nil if value.nil?
          value = value.new_offset(0) if value.respond_to?(:new_offset)
          if value.class == Date
            Time.utc(value.year, value.month, value.day, 0, 0, 0)
          else
            Time.utc(value.year, value.month, value.day, value.hour, value.min, value.sec)
          end
        end

        def to_ruby(value)
          return nil if value.nil?
          jv = value.getTime/1000 if value.class == Java.JavaUtil::Date
          t = Time.at(jv).utc
          DateTime.civil(t.year, t.month, t.day, t.hour, t.min, t.sec)
        end

        def index_as
          Fixnum
        end

      end
    end

    class TimeConverter
      class << self

        def convert?(clazz_or_symbol)
          Time == clazz_or_symbol || :time == clazz_or_symbol
        end

        # Converts the given DateTime (UTC) value to an Fixnum.
        # Only utc times are supported !
        def to_java(value)
          return nil if value.nil?
          if value.class == Date
            Time.utc(value.year, value.month, value.day, 0, 0, 0)
          else
            value.utc
          end
        end


        def to_ruby(value)
          return nil if value.nil?
          jv = value.getTime/1000 if value.class == Java.JavaUtil::Date
          Time.at(jv).utc
        end

        def index_as
          Fixnum
        end

      end
    end

    class EmbeddedSetConverter
      class << self
        def convert?(clazz_or_symbol)
          Set == clazz_or_symbol || :embedded_set == clazz_or_symbol
        end

        def to_java(value)
          return Set.new if value.nil?
          value.to_java
        end

        def to_ruby(value)
          value
        end

        def index_as
          Set
        end
      end
    end

    class HashConverter
      class << self

        def convert?(clazz_or_symbol)
          [Hash, :hash, :embedded_map].include? clazz_or_symbol
        end
        def to_java(value)
          return {} if value.nil? || value.empty?
          value.to_java
        end

        def to_ruby(value)
          return {} if value.nil? || value.empty?
          value
        end

        def index_as
          Hash
        end
      end
    end

    class << self

      # Mostly for testing purpose, You can use this method in order to
      # add a converter while the neo4j has already started.
      def converters=(converters)
        @converters = converters
      end

      # Always returns a converter that handles to_ruby or to_java
      # if +enforce_type+ is set to false then it will raise in case of unknown type
      # otherwise it will return the DefaultConverter.
      def converter(type = nil, enforce_type = true)
        return DefaultConverter unless type
        @converters ||= begin
                          Oriented::TypeConverters.constants.find_all do |c|
                            Oriented::TypeConverters.const_get(c).respond_to?(:convert?)
                          end.map do  |c|
                            Oriented::TypeConverters.const_get(c)
                          end
                        end
        found = @converters.find {|c| c.convert?(type) }
        raise "The type #{type.inspect} is unknown. Use one of #{@converters.map{|c| c.name }.join(", ")} or create a custom type converter." if !found && enforce_type
        found or DefaultConverter
      end

      # Converts the given value to a Java type by using the registered converters.
      # It just looks at the class of the given value unless an attribute name is given.
      def convert(value, attribute = nil, klass = nil, enforce_type = true)
        converter(attribute_type(value, attribute, klass), enforce_type).to_java(value)
      end

      # Converts the given property (key, value) to Java if there is a type converter for given type.
      # The type must also be declared using  property_name, :type => clazz
      # If no Converter is defined for this value then it simply returns the given value.
      def to_java(clazz, key, value)
        type = clazz._decl_props[key.to_sym] && clazz._decl_props[key.to_sym][:type]
        converter(type).to_java(value)
      end

      # Converts the given property (key, value) to Ruby if there is a type converter for given type.
      # If no Converter is defined for this value then it simply returns the given value.
      def to_ruby(clazz, key, value)
        type = clazz._decl_props[key.to_sym] && clazz._decl_props[key.to_sym][:type]
        converter(type).to_ruby(value)
      end

      private
      def attribute_type(value, attribute = nil, klass = nil)
        type = (attribute && klass) ? attribute_type_from_attribute_and_klass(value, attribute, klass) : nil
        type || attribute_type_from_value(value)
      end

      def attribute_type_from_attribute_and_klass(value, attribute, klass)
        if klass.respond_to?(:_decl_props)
          (klass._decl_props.has_key?(attribute) && klass._decl_props[attribute][:type]) ? klass._decl_props[attribute][:type] : nil
        end
      end

      def attribute_type_from_value(value)
        value.class
      end
    end
  end
end