require 'date' module Neo4j::Shared module TypeConverters # Converts Date objects to Java long types. Must be timezone UTC. class DateConverter class << self def convert_type Date end def to_db(value) Time.utc(value.year, value.month, value.day).to_i end def to_ruby(value) Time.at(value).utc.to_date end end end # Converts DateTime objects to and from Java long types. Must be timezone UTC. class DateTimeConverter class << self def convert_type DateTime end # Converts the given DateTime (UTC) value to an Integer. # DateTime values are automatically converted to UTC. def to_db(value) value = value.new_offset(0) if value.respond_to?(:new_offset) args = [value.year, value.month, value.day] args += (value.class == Date ? [0, 0, 0] : [value.hour, value.min, value.sec]) Time.utc(*args).to_i end DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S %z' def to_ruby(value) t = case value when Integer Time.at(value).utc when String DateTime.strptime(value, DATETIME_FORMAT) else fail ArgumentError, "Invalid value type for DateType property: #{value.inspect}" end DateTime.civil(t.year, t.month, t.day, t.hour, t.min, t.sec) end end end class TimeConverter class << self def convert_type Time end def primitive_type Integer end # Converts the given DateTime (UTC) value to an Integer. # Only utc times are supported ! def to_db(value) if value.class == Date Time.utc(value.year, value.month, value.day, 0, 0, 0).to_i else value.utc.to_i end end def to_ruby(value) Time.at(value).utc end alias_method :call, :to_ruby end end # Converts hash to/from YAML class YAMLConverter class << self def convert_type Hash end def to_db(value) Psych.dump(value) end def to_ruby(value) Psych.load(value) end end end # Converts hash to/from JSON class JSONConverter class << self def convert_type JSON end def to_db(value) value.to_json end def to_ruby(value) JSON.parse(value, quirks_mode: true) end end end def convert_properties_to(obj, medium, properties) converter = medium == :ruby ? :to_ruby : :to_db properties.each_pair do |attr, value| next if skip_conversion?(obj, attr, value) properties[attr] = converted_property(primitive_type(attr.to_sym), value, converter) end end private def converted_property(type, value, converter) TypeConverters.converters[type].nil? ? value : TypeConverters.to_other(converter, value, type) end # If the attribute is to be typecast using a custom converter, which converter should it use? If no, returns the type to find a native serializer. def primitive_type(attr) case when self.serialized_properties_keys.include?(attr) serialized_properties[attr] when self.magic_typecast_properties_keys.include?(attr) self.magic_typecast_properties[attr] else self.fetch_upstream_primitive(attr) end end # Returns true if the property isn't defined in the model or if it is nil def skip_conversion?(obj, attr, value) !obj.class.attributes[attr] || value.nil? end class << self attr_reader :converters def included(_) return if @converters @converters = {} Neo4j::Shared::TypeConverters.constants.each do |constant_name| constant = Neo4j::Shared::TypeConverters.const_get(constant_name) register_converter(constant) if constant.respond_to?(:convert_type) end end def typecaster_for(primitive_type) return nil if primitive_type.nil? converters.key?(primitive_type) ? converters[primitive_type] : nil end # @param [Symbol] direction either :to_ruby or :to_other def to_other(direction, value, type) fail "Unknown direction given: #{direction}" unless direction == :to_ruby || direction == :to_db found_converter = converters[type] found_converter ? found_converter.send(direction, value) : value end def register_converter(converter) converters[converter.convert_type] = converter end end end end