lib/kanade/engine.rb in kanade-0.1.0.beta1 vs lib/kanade/engine.rb in kanade-0.1.0.beta2

- old
+ new

@@ -1,87 +1,149 @@ -require 'json' - -module Kanade - class Engine - @@converters = {} - @@name_resolvers = {} - - def initialize(configuration=nil) - @config = configuration || Kanade::Config.default - end - - def configure - yield @config - end - - def serialize(object) - traverse_field(object).to_json - end - - def traverse_field(object) - raise NotSupportedError.new("Serializer only works for Kanade::Dto, and #{object.class.name} does not extend Kanade::Dto") unless object.is_a?(Kanade::Dto) - - stacks = [] - result = {} - stacks += object.__fields - - object.__fields.each do |field| - name = field.key_json || name_to_json(field.sym) - value = field.converter.serialize(object.send(field.sym), field) - result[name] = value - end - - result - end - - def deserialize(definition, json) - raise NotSupportedError.new("Can not process non-class!") unless definition.is_a?(Class) - raise NotSupportedError.new("Can not process other than DTO!") unless definition < Dto - result = definition.new - raw = JSON.parse json - result.__fields.each do |field| - name = field.key_json || name_to_json(field.sym) - value = raw[name] - next if value.nil? - - result.send("#{field.key_ruby}=", value) - end - result - end - - def self.register_converter!(klass) - key = klass.name.split('::').last.underscore.to_sym - - return if key === :base - - # We don't support multiple converter for now - raise NotSupportedError.new("#{key} registered twice") if not @@converters[key].nil? - - @@converters[key] = klass.new - end - - def self.register_name_resolver!(klass) - key = klass.name.split('::').last.underscore.to_sym - - return if key === :base - - # We don't support multiple converter for now - raise NotSupportedError.new("#{key} registered twice") if not @@name_resolvers[key].nil? - - @@name_resolvers[key] = klass.new - end - - def name_to_ruby(string) - strategy = @config.contract - @@name_resolvers[strategy].deserialize(string) - end - - def name_to_json(sym) - strategy = @config.contract - @@name_resolvers[strategy].serialize(sym) - end - - def self.converter(sym) - @@converters[sym] - end - end -end +require 'json' + +module Kanade + class Engine + @@converters = {} + @@name_resolvers = {} + + def initialize(configuration=nil) + @config = configuration || Kanade::Config.default + end + + def configure + yield @config + end + + def serialize(object) + traverse_field(object).to_json + end + + def traverse_field(object) + return nil if object.nil? + raise NotSupportedError.new("Serializer only works for Kanade::Dto, and #{object.class.name} does not extend Kanade::Dto") unless object.class < Kanade::Dto + + result = {} + + object.__fields.each do |field| + name = field.key_json || name_to_json(field.sym) + + if field.options[:as] == :list + value = serialize_list(object.send(field.sym), field) + elsif field.options[:as] == :dto + value = traverse_field(object.send(field.sym)) + else + value = field.converter.serialize(object.send(field.sym), field) + end + result[name] = value + end + + result + end + + def serialize_list(list, field_info) + return nil if list.nil? + list.map do |entry| + if field_info.options[:of].is_a?(Class) and field_info.options[:of] < Dto + traverse_field(entry) + else + conversion_method = field_info.options[:of] + # TODO how to refer to static field? + converter = Engine.converter(conversion_method) + + raise NotSupportedError.new("Can not process unknown converter! #{conversion_method}") if converter.nil? + + converter.serialize(entry, field_info) + end + end + end + + def deserialize(definition, json) + raise NotSupportedError.new("Can not process non-class!") unless definition.is_a?(Class) + raise NotSupportedError.new("Can not process other than DTO!") unless definition < Dto + + hash = JSON.parse(json) + deserialize_object(definition, hash) + end + + # IF engine contains deserialization logic, we can no more + # unit test the converters. Seems like, the conversion logic + # must be outsourced to its respective converter + def deserialize_object(definition, hash) + return nil if hash.nil? + result = definition.new + result.__fields.each do |field| + name = field.key_json || name_to_json(field.sym) + + if field.options[:as] == :list + value = deserialize_list(hash[name], field) + elsif field.options[:as] == :dto + value = deserialize_object(field.options[:of], hash[name]) + else + value = hash[name] + end + + next if value.nil? + + result.send("#{field.key_ruby}=", value) + end + result + end + + def deserialize_list(value, field_info) + return nil if value.nil? + + # Catatan pribadi: Jadi "of" itu harusnya mengandung field definition? + # bukan of: Product, tapi of: {as: :dto, of: Product} + # dengan field definition ini, kita bisa membuat hal yang lebih konfleks, misal: + # of: {as: :symbol, mapping: {success: 'RES_SUCCESS'}} + value.map do |v| + if field_info.options[:of].is_a?(Class) and field_info.options[:of] < Dto + deserialize_object(field_info.options[:of], v) + else + conversion_method = field_info.options[:of] + # TODO how to refer to static field? + converter = Engine.converter(conversion_method) + + raise NotSupportedError.new("Can not process unknown converter! #{conversion_method}") if converter.nil? + + converter.deserialize(v, field_info) + end + end + end + + def self.register_converter!(klass) + key = klass.name.split('::').last.underscore.to_sym + + return if key === :base + + # We don't support multiple converter for now + raise NotSupportedError.new("#{key} registered twice") if not @@converters[key].nil? + + @@converters[key] = klass.new + end + + def self.register_name_resolver!(klass) + key = klass.name.split('::').last.underscore.to_sym + + return if key === :base + + # We don't support multiple converter for now + raise NotSupportedError.new("#{key} registered twice") if not @@name_resolvers[key].nil? + + @@name_resolvers[key] = klass.new + end + + def name_to_ruby(string) + strategy = @config.contract + @@name_resolvers[strategy].deserialize(string) + end + + def name_to_json(sym) + strategy = @config.contract + @@name_resolvers[strategy].serialize(sym) + end + + def self.converter(sym) + @@converters[sym] + end + end +end