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