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) # DISCUSS: move #create_binding to this class? return definition.create_binding(*args) if definition[:binding] build_for(definition, *args) end def initialize(definition, parent_decorator) @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) end # 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) end end # Parse value from doc and update the model property. def uncompile_fragment(doc) evaluate_option(:reader, doc) do read_fragment(doc) end end 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) end end 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<