Class: Ripar::Combinder

Inherits:
BasicObject
Defined in:
lib/ripar/combinder.rb

Overview

This is the part that lets an instance_eval-style block access variables defined outside of it. Arguably that’s a horrible idea, really.

Implements method_missing to figure out which of binding.self and obj can handle the missing method. Can have somewhat weird side effect of making variables assigned to lambdas (or anything implementing call actually) directly callable.

TODO @binding.eval(‘self’) might define method_missing, so we may actually have to attempt to call the method to see if it’s missing.

Defined Under Namespace

Modules: BindingNiceness Classes: AmbiguousMethod, BindingWrapper

Instance Method Summary collapse

Constructor Details

#initialize(obj, saved_binding) ⇒ Combinder

Returns a new instance of Combinder.



22
23
24
25
# File 'lib/ripar/combinder.rb', line 22

def initialize( obj, saved_binding )
  @obj, @binding = obj, saved_binding
  @binding.extend BindingNiceness
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(meth, *args, &blk) ⇒ Object

long method, but we want to keep BasicObject really empty. TODO could split into private __methods



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/ripar/combinder.rb', line 31

def method_missing( meth, *args, &blk )
  if @obj.respond_to?( meth ) && (@binding.self.methods - ::Object.instance_methods).include?( meth )
    begin
      return @obj.__ambiguous_method__( @binding.self, meth, *args, &blk )
    rescue ::NoMethodError => ex
      unless ::Object::RUBY_VERSION == '2.0.0'
        # for some reason, any references to ex.message fail here for 2.0.0
        # so only for other versions just double-check versions that it was in fact caused by __ambiguous_method__
        # otherwise just raise whatever was missing.
        ::Kernel.raise unless ex.message =~ /__ambiguous_method__/
      end
      ::Kernel.raise AmbiguousMethod, "method :#{meth} exists on both #{@binding.self.inspect} (outside) and #{@obj.inspect} (inside)", ex.backtrace[3..-1]
    end
  end

  if @binding.local_variables.include?( meth )
  # This branch is only necessary to do lambda calls with (),
  # because variables in the binding are already, well, part of the binding.
  # So they are picked up before the code ever calls method_missing
    bound_value = @binding.eval meth.to_s

    if bound_value.respond_to?( :call )
      # It's a local variable, but it's been forced to come here by (). So call it.
      bound_value.call(*args)
    else
      # assume that the user really wants to call the object's method
      # rather than access the outside variable.
      @obj.send meth, *args, &blk
    end
  elsif @binding.self.respond_to?( meth )
    @binding.self.send meth, *args, &blk
  else
    @obj.send meth, *args, &blk
  end
end

Instance Method Details

#__inside__Object

for disambiguating outside variables vs method calls, otherwise Ruby interpreter will find the outside variable name, and use that instead of the method call. You could also force the method call with (), but that is sometimes ugly.



77
78
79
# File 'lib/ripar/combinder.rb', line 77

def __inside__
  @obj
end

#__outside__Object

access the outside of the block, ie its binding.



110
111
112
# File 'lib/ripar/combinder.rb', line 110

def __outside__
  BindingWrapper.new @binding
end

#respond_to?(meth, include_all = false) ⇒ Boolean

Returns:

  • (Boolean)


67
68
69
70
71
# File 'lib/ripar/combinder.rb', line 67

def respond_to?( meth, include_all = false )
  # ::Kernel.puts "Combinder#respond_to #{meth}"
  # ::Kernel.puts "Combinder local variables #{@binding.local_variables}"
  return @binding.local_variables.include?( meth ) || @binding.self.respond_to?( meth, include_all ) || @obj.respond_to?( meth, include_all )
end