lib/representable/binding.rb in representable-1.7.7 vs lib/representable/binding.rb in representable-1.8.0

- old
+ new

@@ -8,33 +8,39 @@ class FragmentNotFound end def self.build(definition, *args) # DISCUSS: move #create_binding to this class? - return definition.create_binding(*args) if definition.binding + return definition.create_binding(*args) if definition[:binding] build_for(definition, *args) end - def initialize(definition, represented, user_options={}, exec_context=represented) # TODO: remove default arg for user options. # DISCUSS: make exec_context an options hash? + def initialize(definition, represented, decorator, user_options={}) # TODO: remove default arg for user options. super(definition) @represented = represented + @decorator = decorator @user_options = user_options - @exec_context = exec_context + + setup_exec_context! end attr_reader :user_options, :represented # TODO: make private/remove. + def as # DISCUSS: private? + evaluate_option(:as) + end + # Retrieve value and write fragment to the doc. def compile_fragment(doc) - represented_exec_for(:writer, doc) do + evaluate_option(:writer, doc) do write_fragment(doc, get) end end # Parse value from doc and update the model property. def uncompile_fragment(doc) - represented_exec_for(:reader, doc) do + evaluate_option(:reader, doc) do read_fragment(doc) do |value| set(value) end end end @@ -53,90 +59,106 @@ def read_fragment(doc) value = read_fragment_for(doc) if value == FragmentNotFound return unless has_default? - value = default + value = self[:default] end yield value end def read_fragment_for(doc) read(doc) end - # concept: Option#call(*args) => send(string)/lambda() - # dynamic string def get - represented_exec_for(:getter) do + evaluate_option(:getter) do exec_context.send(getter) end end def set(value) - represented_exec_for(:setter, value) do + evaluate_option(:setter, value) do exec_context.send(setter, value) end end - # the remaining methods in this class are format-independent and should be in Definition. + # 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 private - attr_reader :exec_context + def setup_exec_context! + context = represented + context = self if self[:exec_context] == :binding + context = decorator if self[:exec_context] == :decorator - # Execute the block for +option_name+ on the represented object. - # Executes passed block when there's no lambda for option. - def represented_exec_for(option_name, *args) - return yield unless options[option_name] - call_proc_for(options[option_name], *args) + @exec_context = context end - # All lambdas are executed on exec_context which is either represented or the decorator instance. - def call_proc_for(proc, *args) - return proc unless proc.is_a?(Proc) - # TODO: call method when proc is sympbol. - args << user_options # DISCUSS: we assume user_options is a Hash! - exec_context.instance_exec(*args, &proc) - end + attr_reader :exec_context, :decorator - - module Prepare - def representer_module_for(object, *args) - call_proc_for(representer_module, object) # TODO: how to pass additional data to the computing block?` + # 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 - include Prepare + # 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 end - def deserialize(data, object=lambda { get }) + def deserialize(data) # DISCUSS: does it make sense to skip deserialization of nil-values here? - ObjectDeserializer.new(self, object).call(data) + ObjectDeserializer.new(self).call(data) end - def create_object(fragment) - instance_for(fragment) or class_for(fragment) + def create_object(fragment, *args) + instance_for(fragment, *args) or class_for(fragment, *args) end private def class_for(fragment, *args) - item_class = class_from(fragment) or return fragment + item_class = class_from(fragment, *args) or return handle_deprecated_class(fragment) item_class.new end def class_from(fragment, *args) - call_proc_for(deserialize_class, fragment) + evaluate_option(:class, fragment, *args) end def instance_for(fragment, *args) - return unless options[:instance] - call_proc_for(options[:instance], fragment) or get + instance = evaluate_option(:instance, fragment, *args) + + if instance === true # TODO: remove in 2.0. + warn "[Representable] `instance: lambda { true }` is deprecated. Apparently, you know what you're doing, so use `parse_strategy: :sync` instead." + return get + end + + instance + end + + def handle_deprecated_class(fragment) # TODO: remove in 2.0. + warn "[Representable] `class: lambda { nil }` is deprecated. To return the fragment from parsing, use `instance: lambda { |fragment, *args| fragment }` instead." + fragment end end end end