diff --git a/README.md b/README.md index 8c98adc..8c6454e 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,64 @@ It's just that easy! [2]: http://stackoverflow.com/questions/328496/when-would-you-use-the-builder-pattern "Builder Pattern" +## Block parameters + +Parameters can be passed to the DSL block. + +Supposing you want to make some sort of cheap [Sinatra][3] knockoff: +```ruby +@last_request = nil +respond '/path' do |request| + puts "Request received: #{request}" + @last_request = request +end + +def ride bike + # Play with your new bike +end + +respond '/new_bike' do |bike| + ride(bike) +end +``` + +You'd put together a dispatcher something like this: +```ruby +require 'singleton' + +class DispatchScope + def a_method_you_can_call_from_inside_the_block + :useful_huh? + end +end + +class MessageDispatch + include Singleton + + def initialize + @responders = {} + end + + def add_responder path, &block + @responders[path] = block + end + + def dispatch path, request + Docile.dsl_eval(DispatchScope.new, request, &@responders[path]) + end +end + +def respond path, &handler + MessageDispatch.instance.add_responder path, handler +end + +def send_request path, request + MessageDispatch.instance.dispatch path, request +end +``` + +[3]: http://www.sinatrarb.com "Sinatra" + ## Features 1. method lookup falls back from the DSL object to the block's context diff --git a/lib/docile.rb b/lib/docile.rb index 1f1b74d..3198958 100644 --- a/lib/docile.rb +++ b/lib/docile.rb @@ -15,14 +15,15 @@ module Docile # #=> [1, 3] # # @param dsl [Object] an object whose methods represent a DSL + # @param args [Array] arguments to be passed to the block # @param block [Proc] a block to execute in the DSL context # @return [Object] the dsl object, after execution of the block - def dsl_eval(dsl, &block) + def dsl_eval(dsl, *args, &block) block_context = eval("self", block.binding) proxy_context = FallbackContextProxy.new(dsl, block_context) begin block_context.instance_variables.each { |ivar| proxy_context.instance_variable_set(ivar, block_context.instance_variable_get(ivar)) } - proxy_context.instance_eval(&block) + proxy_context.instance_exec(*args,&block) ensure block_context.instance_variables.each { |ivar| block_context.instance_variable_set(ivar, proxy_context.instance_variable_get(ivar)) } end diff --git a/lib/docile/fallback_context_proxy.rb b/lib/docile/fallback_context_proxy.rb index 4dd7ae8..4a783ed 100644 --- a/lib/docile/fallback_context_proxy.rb +++ b/lib/docile/fallback_context_proxy.rb @@ -2,7 +2,7 @@ require 'set' module Docile class FallbackContextProxy - NON_PROXIED_METHODS = Set[:object_id, :__send__, :__id__, :==, :equal?, :"!", :"!=", :instance_eval, + NON_PROXIED_METHODS = Set[:object_id, :__send__, :__id__, :==, :equal?, :"!", :"!=", :instance_exec, :instance_variables, :instance_variable_get, :instance_variable_set, :remove_instance_variable] @@ -50,4 +50,4 @@ module Docile end end end -end \ No newline at end of file +end diff --git a/spec/docile_spec.rb b/spec/docile_spec.rb index 858f398..12f6c24 100644 --- a/spec/docile_spec.rb +++ b/spec/docile_spec.rb @@ -45,12 +45,46 @@ describe Docile do def inner(&block) Docile.dsl_eval(InnerDSL.new, &block) end + + def inner_with_params(param,&block) + Docile.dsl_eval(InnerDSL.new, param, :foo, &block) + end end def outer(&block) Docile.dsl_eval(OuterDSL.new, &block) end + def parameterized(*args,&block) + Docile.dsl_eval(OuterDSL.new, *args, &block) + end + + context "parameters" do + it "should pass parameters to the block" do + parameterized(1,2,3) do |x,y,z| + x.should == 1 + y.should == 2 + z.should == 3 + end + end + + it "should find parameters before methods" do + parameterized(1) { |a| a.should == 1 } + end + + it "should find outer parameters in inner dsl scope" do + parameterized(1,2,3) do |a,b,c| + inner_with_params(c) do |d,e| + a.should == 1 + b.should == 2 + c.should == 3 + d.should == c + e.should == :foo + end + end + end + end + context "methods" do it "should find method of outer dsl in outer dsl scope" do outer { a.should == 'a' } @@ -111,6 +145,65 @@ describe Docile do end end + class DispatchScope + def params + { :a => 1, :b => 2, :c => 3 } + end + end + + class MessageDispatch + include Singleton + + def initialize + @responders = {} + end + + def add_responder path, &block + @responders[path] = block + end + + def dispatch path, request + Docile.dsl_eval(DispatchScope.new, request, &@responders[path]) + end + end + + def respond path, &block + MessageDispatch.instance.add_responder path, &block + end + + def send_request path, request + MessageDispatch.instance.dispatch path, request + end + + it "should handle the dispatch pattern" do + @first = @second = nil + respond '/path' do |request| + @first = request + end + + respond '/new_bike' do |bike| + @second = "Got a new #{bike}" + end + + def x(y) ; "Got a #{y}"; end + respond '/third' do |third| + x(third).should == 'Got a third thing' + end + + fourth = nil + respond '/params' do |arg| + fourth = params[arg] + end + + send_request '/path', 1 + send_request '/new_bike', 'ten speed' + send_request '/third', 'third thing' + send_request '/params', :b + + @first.should == 1 + @second.should == 'Got a new ten speed' + fourth.should == 2 + end end -end \ No newline at end of file +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3508f15..6e213eb 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,5 +1,6 @@ require 'rubygems' require 'rspec' +require 'singleton' test_dir = File.dirname(__FILE__) $LOAD_PATH.unshift test_dir unless $LOAD_PATH.include?(test_dir) @@ -7,4 +8,4 @@ $LOAD_PATH.unshift test_dir unless $LOAD_PATH.include?(test_dir) lib_dir = File.join(File.dirname(test_dir), 'lib') $LOAD_PATH.unshift lib_dir unless $LOAD_PATH.include?(lib_dir) -require 'docile' \ No newline at end of file +require 'docile'