Skip to content
Browse files

Give DSL blocks access to calling context

When doing an instance_eval for a DSL block, now use a special class
that delegates to the block's calling context if the method is not
available on the DSL class itself. This means that, assuming no
namespace conflict, users can refer to instance method in the calling
context even though the block is being instance_eval'd in the DSL class.

Thanks much to Dmitry Dzema for the idea:
http://blog.dzema.name/2009/11/ruby-dsl-and-instance_eval
  • Loading branch information...
1 parent 45ce872 commit e42cb8baa28503a6a5e486eb008ee5f029b71970 Mat Brown committed Dec 21, 2009
Showing with 79 additions and 5 deletions.
  1. +0 −2 TODO
  2. +1 −1 lib/sunspot/setup.rb
  3. +39 −1 lib/sunspot/util.rb
  4. +38 −0 spec/api/binding_spec.rb
  5. +1 −1 spec/api/query/dynamic_fields_spec.rb
View
2 TODO
@@ -1,8 +1,6 @@
=== 1.0 ===
-* Built-in session proxies; #thread_local_sessions! method
* Sunspot Solr Installer
* Hand-rolled gem tasks
-* Maybe use bindings instead of instance_eval
=== Future ===
* commitWithin (need solr-ruby support for this)
View
2 lib/sunspot/setup.rb
@@ -93,7 +93,7 @@ def add_document_boost(attr_name, &block)
# Builder method for evaluating the setup DSL
#
def setup(&block)
- @dsl.instance_eval(&block)
+ Util.instance_eval_or_call(@dsl, &block)
end
#
View
40 lib/sunspot/util.rb
@@ -83,7 +83,7 @@ def instance_eval_or_call(object, &block)
if block.arity > 0
block.call(object)
else
- object.instance_eval(&block)
+ ContextBoundDelegate.instance_eval_with_context(object, &block)
end
end
@@ -210,5 +210,43 @@ def lng
end.to_f
end
end
+
+ class ContextBoundDelegate
+ class <<self
+ def instance_eval_with_context(receiver, &block)
+ calling_context = eval('self', block.binding)
+ if parent_calling_context = calling_context.instance_eval{@__calling_context__}
+ calling_context = parent_calling_context
+ end
+ new(receiver, calling_context).instance_eval(&block)
+ end
+ private :new
+ end
+
+ BASIC_METHODS = Set[:==, :equal?, :"!", :"!=", :instance_eval,
+ :object_id, :__send__, :__id__]
+
+ instance_methods.each do |method|
+ unless BASIC_METHODS.include?(method.to_sym)
+ undef_method(method)
+ end
+ end
+
+ def initialize(receiver, calling_context)
+ @__receiver__, @__calling_context__ = receiver, calling_context
+ end
+
+ def method_missing(method, *args, &block)
+ begin
+ @__receiver__.send(method.to_sym, *args, &block)
+ rescue ::NoMethodError => e
+ begin
+ @__calling_context__.send(method.to_sym, *args, &block)
+ rescue ::NoMethodError
+ raise(e)
+ end
+ end
+ end
+ end
end
end
View
38 spec/api/binding_spec.rb
@@ -0,0 +1,38 @@
+require File.join(File.dirname(__FILE__), 'spec_helper')
+
+describe "DSL bindings" do
+ it 'should give access to calling context\'s methods in search DSL' do
+ value = nil
+ session.search(Post) do
+ value = test_method
+ end
+ value.should == 'value'
+ end
+
+ it 'should give access to calling context\'s methods in nested DSL block' do
+ value = nil
+ session.search(Post) do
+ any_of do
+ value = test_method
+ end
+ end
+ value.should == 'value'
+ end
+
+ it 'should give access to calling context\'s methods in double-nested DSL block' do
+ value = nil
+ session.search(Post) do
+ any_of do
+ all_of do
+ value = test_method
+ end
+ end
+ end
+ end
+
+ private
+
+ def test_method
+ 'value'
+ end
+end
View
2 spec/api/query/dynamic_fields_spec.rb
@@ -101,7 +101,7 @@
lambda do
session.search Post do
dynamic :custom_string do
- paginate 3, 10
+ paginate :page => 3, :per_page => 10
end
end
end.should raise_error(NoMethodError)

0 comments on commit e42cb8b

Please sign in to comment.
Something went wrong with that request. Please try again.