diff --git a/lib/halibut/builder.rb b/lib/halibut/builder.rb index bb78548..6c1c2cd 100644 --- a/lib/halibut/builder.rb +++ b/lib/halibut/builder.rb @@ -1,4 +1,5 @@ require 'halibut/core/resource' +require 'halibut/dsl' module Halibut @@ -28,10 +29,11 @@ def resource # class RootContext + include DSL + def initialize(resource, &blk) @resource = resource - - instance_eval(&blk) if block_given? + instance_eval_with_previous_context_available(&blk) if block_given? end # Sets a property on the resource. @@ -108,11 +110,13 @@ def relation(rel, &blk) class RelationContext + include DSL + def initialize(resource, rel, &blk) @resource = resource @rel = rel - instance_eval(&blk) if block_given? + instance_eval_with_previous_context_available(&blk) if block_given? end def link(href, opts={}) diff --git a/lib/halibut/dsl.rb b/lib/halibut/dsl.rb new file mode 100644 index 0000000..00047ce --- /dev/null +++ b/lib/halibut/dsl.rb @@ -0,0 +1,61 @@ +module Halibut + + module DSL + + def instance_eval_with_previous_context_available(*args, &block) + DslDelegator.new(self).instance_eval_with_previous_context_available(&block) + end + + class DslDelegator + + def initialize delegation_target + @delegation_target = delegation_target + end + + def instance_eval_with_previous_context_available(*args, &block) + with_previous_context_available(block.binding) do + bind_block_as_instance_method_on_self(&block).call(*args) + end + end + + protected + + def method_missing(method, *args, &block) + if delegation_target_responds_to? method + delegation_target.send(method, *args, &block) + else + previous_context.send(method, *args, &block) + end + end + + private + + attr_accessor :delegation_target, :previous_context + + def bind_block_as_instance_method_on_self(&block) + create_instance_method_from_block(&block).bind(self) + end + + def create_instance_method_from_block &block + meth = self.class.class_eval do + define_method :___block_as_instance_method_, &block + meth = instance_method :___block_as_instance_method_ + remove_method :___block_as_instance_method_ + meth + end + end + + def with_previous_context_available(binding, &block) + @previous_context = binding.eval('self') + result = block.call + @previous_context = nil + result + end + + def delegation_target_responds_to?(method) + delegation_target.respond_to? method + end + + end + end +end \ No newline at end of file diff --git a/spec/builder_spec.rb b/spec/builder_spec.rb index be08afe..f11ab69 100644 --- a/spec/builder_spec.rb +++ b/spec/builder_spec.rb @@ -179,4 +179,22 @@ end end + describe "Using methods declared outside the builder" do + it "evaluates the methods in the context of the block's declaration scope" do + def local_method + 'bar' + end + + builder = Halibut::Builder.new do + property 'foo', local_method + end + + resource = Halibut::Core::Resource.new + resource.set_property 'foo', local_method + + builder.resource.properties['foo'].must_equal local_method + builder.resource.must_equal resource, diff(builder.resource.to_hash, resource.to_hash) + end + end + end