lib/representable/binding.rb in representable-2.0.4 vs lib/representable/binding.rb in representable-2.1.0

- old
+ new

@@ -1,10 +1,16 @@ +require "representable/populator" require "representable/deserializer" require "representable/serializer" module Representable # The Binding wraps the Definition instance for this property and provides methods to read/write fragments. + + # The flow when parsing is Binding#read_fragment -> Populator -> Deserializer. + # Actual parsing the fragment from the document happens in Binding#read, everything after that is generic. + # + # Serialization: Serializer -> {frag}/[frag]/frag -> Binding#write class Binding class FragmentNotFound end def self.build(definition, *args) @@ -13,15 +19,12 @@ build_for(definition, *args) end def initialize(definition, represented, decorator, user_options={}) # TODO: remove default arg for user options. @definition = definition - @represented = represented - @decorator = decorator - @user_options = user_options - setup_exec_context! + setup!(represented, decorator, user_options) # this can be used in #compile_fragment/#uncompile_fragment in case we wanna reuse the Binding instance. end attr_reader :user_options, :represented # TODO: make private/remove. def as # DISCUSS: private? @@ -37,43 +40,34 @@ end # Parse value from doc and update the model property. def uncompile_fragment(doc) evaluate_option(:reader, doc) do - read_fragment(doc) do |value| - value = parse_filter(value, doc) - set(value) - end + read_fragment(doc) end end def write_fragment(doc, value) value = default_for(value) - write_fragment_for(value, doc) + return if skipable_empty_value?(value) + + render_fragment(value, doc) end - def write_fragment_for(value, doc) - return if skipable_empty_value?(value) - write(doc, value) + def render_fragment(value, doc) + fragment = serialize(value) # render fragments of hash, xml, yaml. + + write(doc, fragment) end def read_fragment(doc) - value = read_fragment_for(doc) + fragment = read(doc) # scalar, Array, or Hash (abstract format) or un-deserialised fragment(s). - if value == FragmentNotFound - return unless has_default? - value = self[:default] - end - - yield value + populator.call(fragment, doc) end - def read_fragment_for(doc) - read(doc) - end - def render_filter(value, doc) evaluate_option(:render_filter, value, doc) { value } end def parse_filter(value, doc) @@ -96,15 +90,79 @@ # DISCUSS: do we really need that? def representer_module_for(object, *args) evaluate_option(:extend, object) # TODO: pass args? do we actually have args at the time this is called (compile-time)? end + # Evaluate the option (either nil, static, a block or an instance method call) or + # executes passed block when option not defined. + def evaluate_option(name, *args) + unless proc = self[name] + return yield if block_given? + return + end + + # TODO: it would be better if user_options was nil per default and then we just don't pass it into lambdas. + options = self[:pass_options] ? Options.new(self, user_options, represented, decorator) : user_options + + proc.evaluate(exec_context, *(args<<options)) # from Uber::Options::Value. + end + + def [](name) + @definition[name] + end + # TODO: i don't want to define all methods here, but it is faster! + # TODO: test public interface. + def getter + @definition.getter + end + def setter + @definition.setter + end + def typed? + @definition.typed? + end + def representable? + @definition.representable? + end + def has_default?(*args) + @definition.has_default?(*args) + end + def name + @definition.name + end + def representer_module + @definition.representer_module + end + # perf : 1.7-1.9 + #extend Forwardable + #def_delegators :@definition, *%w([] getter setter typed? representable? has_default? name representer_module) + # perf : 1.7-1.9 + # %w([] getter setter typed? representable? has_default? name representer_module).each do |name| + # define_method(name.to_sym) { |*args| @definition.send(name, *args) } + # end + + def skipable_empty_value?(value) + return true if array? and self[:render_empty] == false and value and value.size == 0 # TODO: change in 2.0, don't render emtpy. + value.nil? and not self[:render_nil] + end + + def default_for(value) + return self[:default] if skipable_empty_value?(value) + value + end + + def array? + @definition.array? + end + private - # Apparently, SimpleDelegator is super slow due to a regex, so we do it - # ourselves, right, Jimmy? - def method_missing(*args, &block) - @definition.send(*args, &block) + def setup!(represented, decorator, user_options) + @represented = represented + @decorator = decorator + @user_options = user_options + + setup_exec_context! end def setup_exec_context! context = represented context = self if self[:exec_context] == :binding @@ -113,58 +171,56 @@ @exec_context = context end attr_reader :exec_context, :decorator - # Evaluate the option (either nil, static, a block or an instance method call) or - # executes passed block when option not defined. - def evaluate_option(name, *args) - unless proc = self[name] - return yield if block_given? - return - end + def serialize(object) + serializer.call(object) + end - # TODO: it would be better if user_options was nil per default and then we just don't pass it into lambdas. - options = self[:pass_options] ? Options.new(self, user_options, represented, decorator) : user_options + def serializer_class + Serializer + end - proc.evaluate(exec_context, *(args<<options)) # from Uber::Options::Value. + def serializer + serializer_class.new(self) end + def populator + populator_class.new(self) + end + def populator_class + Populator + end + + # Options instance gets passed to lambdas when pass_options: true. # This is considered the new standard way and should be used everywhere for forward-compat. Options = Struct.new(:binding, :user_options, :represented, :decorator) - # Delegates to call #to_*/from_*. - module Object - def serialize(object) - ObjectSerializer.new(self, object).call + # generics for collection bindings. + module Collection + private + def populator_class + Populator::Collection end - def deserialize(data) - # DISCUSS: does it make sense to skip deserialization of nil-values here? - ObjectDeserializer.new(self).call(data) + def serializer_class + Serializer::Collection end + end - def create_object(fragment, *args) - instance_for(fragment, *args) or class_for(fragment, *args) - end - + # and the same for hashes. + module Hash private - # DISCUSS: deprecate :class in favour of :instance and simplicity? - def class_for(fragment, *args) - item_class = class_from(fragment, *args) or raise DeserializeError.new(":class did not return class constant.") - item_class.new + def populator_class + Populator::Hash end - def class_from(fragment, *args) - evaluate_option(:class, fragment, *args) - end - - def instance_for(fragment, *args) - # cool: if no :instance set, { return } will jump out of this method. - evaluate_option(:instance, fragment, *args) { return } or raise DeserializeError.new(":instance did not return object.") + def serializer_class + Serializer::Hash end end end