class Ripar::Combinder

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.

Public Class Methods

new( obj, saved_binding ) click to toggle source
# File lib/ripar/combinder.rb, line 22
def initialize( obj, saved_binding )
  @obj, @binding = obj, saved_binding
  @binding.extend BindingNiceness
end

Public Instance Methods

__inside__() click to toggle source

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.

# File lib/ripar/combinder.rb, line 77
def __inside__
  @obj
end
__outside__() click to toggle source

access the outside of the block, ie its binding.

# File lib/ripar/combinder.rb, line 110
def __outside__
  BindingWrapper.new @binding
end
method_missing( meth, *args, &blk ) click to toggle source

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

# 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
respond_to?( meth, include_all = false ) click to toggle source
# 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