lib/representable/binding.rb in representable-2.3.0 vs lib/representable/binding.rb in representable-2.4.0.rc1

- old
+ new

@@ -1,258 +1,101 @@ 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. + # The Binding provides methods to read/write the fragment for one property. # - # Serialization: Serializer -> {frag}/[frag]/frag -> Binding#write + # Actually parsing the fragment from the document happens in Binding#read, everything after that is generic. class Binding class FragmentNotFound end - def self.build(definition, *args) - # DISCUSS: move #create_binding to this class? - return definition.create_binding(*args) if definition[:binding] - build_for(definition, *args) + def self.build(definition) + return definition.create_binding if definition[:binding] + build_for(definition) end - def initialize(definition, parent_decorator) + def initialize(definition) @definition = definition - @parent_decorator = parent_decorator # DISCUSS: where's this needed? - - # static options. do this once. - @representable = @definition.representable? @name = @definition.name - @skip_filters = self[:readable]==false || self[:writeable]==false || self[:if] # Does this binding contain :if, :readable or :writeable settings? @getter = @definition.getter @setter = @definition.setter - @array = @definition.array? - @typed = @definition.typed? - @has_default = @definition.has_default? - end - attr_reader :user_options, :represented # TODO: make private/remove. - - # DISCUSS: an overall strategy to speed up option reads will come around 3.0. - attr_reader :representable, :name, :getter, :setter, :array, :typed, :skip_filters, :has_default - alias_method :representable?, :representable - alias_method :array?, :array - alias_method :typed?, :typed - alias_method :skip_filters?, :skip_filters - alias_method :has_default?, :has_default - - def as # DISCUSS: private? - @as ||= evaluate_option(:as) + setup_exec_context! end + attr_reader :name, :getter, :setter + extend Uber::Delegates + delegates :@definition, :has_default?, :representable?, :array?, :typed? + # Single entry points for rendering and parsing a property are #compile_fragment # and #uncompile_fragment in Mapper. - # - # DISCUSS: - # currently, we need to call B#update! before compile_fragment/uncompile_fragment. - # this will change to B#renderer(represented, options).call - # B#parser (represented, options).call - # goal is to have two objects for 2 entirely different tasks. - # Retrieve value and write fragment to the doc. - def compile_fragment(doc) - evaluate_option(:writer, doc) do - value = render_filter(get, doc) - write_fragment(doc, value) + module Deprecatable + # Retrieve value and write fragment to the doc. + def compile_fragment(options) + render_pipeline(nil, options).(nil, options) end - end - # Parse value from doc and update the model property. - def uncompile_fragment(doc) - evaluate_option(:reader, doc) do - read_fragment(doc) + # Parse value from doc and update the model property. + def uncompile_fragment(options) + parse_pipeline(options[:doc], options).(options[:doc], options) end end + include Deprecatable - def write_fragment(doc, value) - value = default_for(value) - - return if skipable_empty_value?(value) - - render_fragment(value, doc) - end - - def render_fragment(value, doc) - fragment = serialize(value) { return } # render fragments of hash, xml, yaml. - - write(doc, fragment) - end - - def read_fragment(doc) - fragment = read(doc) # scalar, Array, or Hash (abstract format) or un-deserialised fragment(s). - - populator.call(fragment, doc) - end - - def render_filter(value, doc) - evaluate_option(:render_filter, value, doc) { value } - end - - def parse_filter(value, doc) - evaluate_option(:parse_filter, value, doc) { value } - end - - def get - evaluate_option(:getter) do - exec_context.send(getter) + module EvaluateOption + def evaluate_option(name, input=nil, options={}) + proc = self[name] + proc.(send(:exec_context, options), options) # from Uber::Options::Value. # NOTE: this can also be the Proc object if it's not wrapped by Uber:::Value. end end + # include EvaluateOption - def set(value) - evaluate_option(:setter, value) do - exec_context.send(setter, value) - end - end - - # DISCUSS: do we really need that? - # 1.38 0.104 0.021 0.000 0.083 40001 Representable::Binding#representer_module_for - # 1.13 0.044 0.017 0.000 0.027 40001 Representable::Binding#representer_module_for (with memoize). - def representer_module_for(object, *args) - # TODO: cache this! - 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 = @definition[name] # TODO: this could dispatch directly to the @definition using #send? - 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, parent_decorator) : user_options - - proc.evaluate(exec_context, *(args<<options)) # from Uber::Options::Value. - end - def [](name) @definition[name] end - # 1.55 0.031 0.022 0.000 0.009 60004 Representable::Binding#skipable_empty_value? - # 1.51 0.030 0.022 0.000 0.008 60004 Representable::Binding#skipable_empty_value? - # after polymorphism: - # 1.44 0.031 0.022 0.000 0.009 60002 Representable::Binding#skipable_empty_value? def skipable_empty_value?(value) value.nil? and not self[:render_nil] end def default_for(value) return self[:default] if skipable_empty_value?(value) value end - # Note: this method is experimental. - def update!(represented, user_options) - @represented = represented - - setup_user_options!(user_options) - setup_exec_context! - end - attr_accessor :cached_representer + require "representable/pipeline_factories" + include Factories + private - def setup_user_options!(user_options) - @user_options = user_options - # this is the propagated_options. - @user_options = user_options.merge(wrap: false) if self[:wrap] == false - # @user_options = user_options.merge(wrap: self[:wrap]) if self[:wrap] + def setup_exec_context! + @exec_context = ->(options) { options[:represented] } unless self[:exec_context] + @exec_context = ->(options) { self } if self[:exec_context] == :binding + @exec_context = ->(options) { options[:decorator] } if self[:exec_context] == :decorator end - # 1.80 0.066 0.027 0.000 0.039 30002 Representable::Binding#setup_exec_context! - # 0.98 0.034 0.014 0.000 0.020 30002 Representable::Binding#setup_exec_context! - def setup_exec_context! - return @exec_context = @represented unless self[:exec_context] - @exec_context = self if self[:exec_context] == :binding - @exec_context = parent_decorator if self[:exec_context] == :decorator + def exec_context(options) + @exec_context.(options) end - attr_reader :exec_context, :parent_decorator - - def serialize(object, &block) - serializer.call(object, &block) + def parse_pipeline(input, options) + @parse_pipeline ||= pipeline_for(:parse_pipeline, input, options) { Pipeline[*parse_functions] } end - module Factories - def serializer_class - Serializer - end - - def serializer - @serializer ||= serializer_class.new(self) - end - - def populator - @populator ||= populator_class.new(self) - end - - def populator_class - Populator - end - - def deserializer_class - Deserializer - end - - def deserializer - @deserializer ||= deserializer_class.new(self) - end + def render_pipeline(input, options) + @render_pipeline ||= pipeline_for(:render_pipeline, input, options) { Pipeline[*render_functions] } end - include Factories - - # 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) - - # generics for collection bindings. module Collection - private - def populator_class - Populator::Collection - end - - def serializer_class - Serializer::Collection - end - - def deserializer_class - Deserializer::Collection - end - def skipable_empty_value?(value) # TODO: this can be optimized, again. return true if value.nil? and not self[:render_nil] # FIXME: test this without the "and" return true if self[:render_empty] == false and value and value.size == 0 # TODO: change in 2.0, don't render emtpy. end end - - # and the same for hashes. - module Hash - private - def populator_class - Populator::Hash - end - - def serializer_class - Serializer::Hash - end - - def deserializer_class - Deserializer::Hash - end - end end - class DeserializeError < RuntimeError end end