rr 0.10.2 → 0.10.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGES +3 -0
- data/README.rdoc +1 -0
- data/Rakefile +5 -1
- data/VERSION.yml +1 -1
- data/lib/rr.rb +8 -1
- data/lib/rr/double.rb +18 -78
- data/lib/rr/double_definitions/double_definition_creator_proxy.rb +1 -1
- data/lib/rr/hash_with_object_id_key.rb +1 -3
- data/lib/rr/injections/double_injection.rb +119 -0
- data/lib/rr/injections/injection.rb +22 -0
- data/lib/rr/injections/method_missing_injection.rb +62 -0
- data/lib/rr/injections/singleton_method_added_injection.rb +58 -0
- data/lib/rr/method_dispatches/base_method_dispatch.rb +84 -0
- data/lib/rr/method_dispatches/method_dispatch.rb +58 -0
- data/lib/rr/method_dispatches/method_missing_dispatch.rb +59 -0
- data/lib/rr/space.rb +44 -4
- data/lib/rr/spy_verification_proxy.rb +1 -1
- data/spec/rr/double_injection/double_injection_spec.rb +523 -66
- data/spec/rr/double_injection/double_injection_verify_spec.rb +3 -3
- data/spec/rr/double_spec.rb +18 -18
- data/spec/rr/space/hash_with_object_id_key_spec.rb +1 -1
- data/spec/rr/space/space_spec.rb +250 -64
- data/spec/spec_helper.rb +5 -0
- metadata +9 -7
- data/lib/rr/double_injection.rb +0 -136
- data/spec/rr/double_injection/double_injection_bind_spec.rb +0 -99
- data/spec/rr/double_injection/double_injection_dispatching_spec.rb +0 -244
- data/spec/rr/double_injection/double_injection_has_original_method_spec.rb +0 -58
- data/spec/rr/double_injection/double_injection_reset_spec.rb +0 -72
    
        data/CHANGES
    CHANGED
    
    | @@ -1,3 +1,6 @@ | |
| 1 | 
            +
            - Handle lazily defined methods (where respond_to? returns true yet the method is not yet defined and the first call to method_missing defines the method). This pattern is used in ActiveRecord and ActionMailer.
         | 
| 2 | 
            +
            - Fixed warning about aliasing #instance_exec in jruby. http://github.com/btakita/rr/issues#issue/9 (Patch by Nathan Sobo)
         | 
| 3 | 
            +
             | 
| 1 4 | 
             
            0.10.2
         | 
| 2 5 | 
             
            - RR properly proxies subjects with private methods [http://github.com/btakita/rr/issues/#issue/7]. Identified by Matthew O'Connor.
         | 
| 3 6 |  | 
    
        data/README.rdoc
    CHANGED
    
    | @@ -322,6 +322,7 @@ With any development effort, there are countless people who have contributed | |
| 322 322 | 
             
            to making it possible. We all are standing on the shoulders of giants.
         | 
| 323 323 | 
             
            * Andreas Haller for patches
         | 
| 324 324 | 
             
            * Aslak Hellesoy for Developing Rspec
         | 
| 325 | 
            +
            * Christopher Redinger for patches
         | 
| 325 326 | 
             
            * Dan North for syntax ideas
         | 
| 326 327 | 
             
            * Dave Astels for some BDD inspiration
         | 
| 327 328 | 
             
            * David Chelimsky for encouragement to make the RR framework, for developing the Rspec mock framework, syntax ideas, and patches
         | 
    
        data/Rakefile
    CHANGED
    
    | @@ -47,7 +47,11 @@ begin | |
| 47 47 | 
             
            rescue LoadError
         | 
| 48 48 | 
             
              puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
         | 
| 49 49 | 
             
            end
         | 
| 50 | 
            -
            RUBYFORGE_PACKAGE_NAME = "rr (Double  | 
| 50 | 
            +
            RUBYFORGE_PACKAGE_NAME = "rr (Double Ruby)"
         | 
| 51 | 
            +
            # The package was renamed from "rr (Double R)" to "rr (Double Ruby)".
         | 
| 52 | 
            +
            # When this was last run, the script did not work for the new name but it did work for the old name.
         | 
| 53 | 
            +
            # Perhaps more time was needed for the name change to propagate?
         | 
| 54 | 
            +
            #RUBYFORGE_PACKAGE_NAME = "rr (Double R)"
         | 
| 51 55 |  | 
| 52 56 | 
             
            # This is hacked to get around the 3 character limitation for package names on Rubyforge.
         | 
| 53 57 | 
             
            # http://rubyforge.org/tracker/index.php?func=detail&aid=27026&group_id=5&atid=102
         | 
    
        data/VERSION.yml
    CHANGED
    
    
    
        data/lib/rr.rb
    CHANGED
    
    | @@ -1,5 +1,6 @@ | |
| 1 1 | 
             
            dir = File.dirname(__FILE__)
         | 
| 2 2 | 
             
            require 'rubygems'
         | 
| 3 | 
            +
            require 'forwardable'
         | 
| 3 4 |  | 
| 4 5 | 
             
            require "#{dir}/rr/errors/rr_error"
         | 
| 5 6 | 
             
            require "#{dir}/rr/errors/subject_does_not_implement_method_error"
         | 
| @@ -14,7 +15,13 @@ require "#{dir}/rr/errors/spy_verification_errors/double_injection_not_found_err | |
| 14 15 | 
             
            require "#{dir}/rr/errors/spy_verification_errors/invocation_count_error"
         | 
| 15 16 |  | 
| 16 17 | 
             
            require "#{dir}/rr/space"
         | 
| 17 | 
            -
            require "#{dir}/rr/ | 
| 18 | 
            +
            require "#{dir}/rr/injections/injection"
         | 
| 19 | 
            +
            require "#{dir}/rr/injections/double_injection"
         | 
| 20 | 
            +
            require "#{dir}/rr/injections/method_missing_injection"
         | 
| 21 | 
            +
            require "#{dir}/rr/injections/singleton_method_added_injection"
         | 
| 22 | 
            +
            require "#{dir}/rr/method_dispatches/base_method_dispatch"
         | 
| 23 | 
            +
            require "#{dir}/rr/method_dispatches/method_dispatch"
         | 
| 24 | 
            +
            require "#{dir}/rr/method_dispatches/method_missing_dispatch"
         | 
| 18 25 | 
             
            require "#{dir}/rr/hash_with_object_id_key"
         | 
| 19 26 | 
             
            require "#{dir}/rr/recorded_calls"
         | 
| 20 27 | 
             
            require "#{dir}/rr/proc_from_block"
         | 
    
        data/lib/rr/double.rb
    CHANGED
    
    | @@ -28,22 +28,6 @@ module RR | |
| 28 28 | 
             
                  verify_method_signature if definition.verify_method_signature?
         | 
| 29 29 | 
             
                  double_injection.register_double self
         | 
| 30 30 | 
             
                end
         | 
| 31 | 
            -
                
         | 
| 32 | 
            -
                # Double#call calls the Double's implementation. The return
         | 
| 33 | 
            -
                # value of the implementation is returned.
         | 
| 34 | 
            -
                #
         | 
| 35 | 
            -
                # A TimesCalledError is raised when the times called
         | 
| 36 | 
            -
                # exceeds the expected TimesCalledExpectation.
         | 
| 37 | 
            -
                def call(double_injection, *args, &block)
         | 
| 38 | 
            -
                  if verbose?
         | 
| 39 | 
            -
                    puts Double.formatted_name(double_injection.method_name, args)
         | 
| 40 | 
            -
                  end
         | 
| 41 | 
            -
                  times_called_expectation.attempt if definition.times_matcher
         | 
| 42 | 
            -
                  space.verify_ordered_double(self) if ordered?
         | 
| 43 | 
            -
                  yields!(block)
         | 
| 44 | 
            -
                  return_value = call_implementation(double_injection, *args, &block)
         | 
| 45 | 
            -
                  definition.after_call_proc ? extract_subject_from_return_value(definition.after_call_proc.call(return_value)) : return_value
         | 
| 46 | 
            -
                end
         | 
| 47 31 |  | 
| 48 32 | 
             
                # Double#exact_match? returns true when the passed in arguments
         | 
| 49 33 | 
             
                # exactly match the ArgumentEqualityExpectation arguments.
         | 
| @@ -98,6 +82,18 @@ module RR | |
| 98 82 | 
             
                  self.class.formatted_name(method_name, expected_arguments)
         | 
| 99 83 | 
             
                end
         | 
| 100 84 |  | 
| 85 | 
            +
                def method_call(args)
         | 
| 86 | 
            +
                  if verbose?
         | 
| 87 | 
            +
                    puts Double.formatted_name(method_name, args)
         | 
| 88 | 
            +
                  end
         | 
| 89 | 
            +
                  times_called_expectation.attempt if definition.times_matcher
         | 
| 90 | 
            +
                  space.verify_ordered_double(self) if ordered?
         | 
| 91 | 
            +
                end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                def implementation_is_original_method?
         | 
| 94 | 
            +
                  definition.implementation_is_original_method?
         | 
| 95 | 
            +
                end
         | 
| 96 | 
            +
             | 
| 101 97 | 
             
                protected
         | 
| 102 98 | 
             
                def ordered?
         | 
| 103 99 | 
             
                  definition.ordered?
         | 
| @@ -106,21 +102,6 @@ module RR | |
| 106 102 | 
             
                def verbose?
         | 
| 107 103 | 
             
                  definition.verbose?
         | 
| 108 104 | 
             
                end
         | 
| 109 | 
            -
                
         | 
| 110 | 
            -
                def yields!(block)
         | 
| 111 | 
            -
                  if definition.yields_value
         | 
| 112 | 
            -
                    if block
         | 
| 113 | 
            -
                      block.call(*definition.yields_value)
         | 
| 114 | 
            -
                    else
         | 
| 115 | 
            -
                      raise ArgumentError, "A Block must be passed into the method call when using yields"
         | 
| 116 | 
            -
                    end
         | 
| 117 | 
            -
                  end
         | 
| 118 | 
            -
                end
         | 
| 119 | 
            -
             | 
| 120 | 
            -
                def call_implementation(double_injection, *args, &block)
         | 
| 121 | 
            -
                  return_value = do_call_implementation_and_get_return_value(double_injection, *args, &block)
         | 
| 122 | 
            -
                  extract_subject_from_return_value(return_value)
         | 
| 123 | 
            -
                end
         | 
| 124 105 |  | 
| 125 106 | 
             
                def verify_times_matcher_is_set
         | 
| 126 107 | 
             
                  unless definition.times_matcher
         | 
| @@ -138,19 +119,19 @@ module RR | |
| 138 119 | 
             
                  raise RR::Errors::SubjectDoesNotImplementMethodError unless definition.subject.respond_to?(double_injection.send(:original_method_alias_name))
         | 
| 139 120 | 
             
                  raise RR::Errors::SubjectHasDifferentArityError unless arity_matches?
         | 
| 140 121 | 
             
                end
         | 
| 141 | 
            -
             | 
| 122 | 
            +
             | 
| 142 123 | 
             
                def subject_arity
         | 
| 143 124 | 
             
                  definition.subject.method(double_injection.send(:original_method_alias_name)).arity
         | 
| 144 125 | 
             
                end
         | 
| 145 | 
            -
             | 
| 126 | 
            +
             | 
| 146 127 | 
             
                def subject_accepts_only_varargs?
         | 
| 147 128 | 
             
                  subject_arity == -1
         | 
| 148 129 | 
             
                end
         | 
| 149 | 
            -
             | 
| 130 | 
            +
             | 
| 150 131 | 
             
                def subject_accepts_varargs?
         | 
| 151 132 | 
             
                  subject_arity < 0
         | 
| 152 133 | 
             
                end
         | 
| 153 | 
            -
             | 
| 134 | 
            +
             | 
| 154 135 | 
             
                def arity_matches?
         | 
| 155 136 | 
             
                  return true if subject_accepts_only_varargs?
         | 
| 156 137 | 
             
                  if subject_accepts_varargs?
         | 
| @@ -159,54 +140,13 @@ module RR | |
| 159 140 | 
             
                    return subject_arity == args.size
         | 
| 160 141 | 
             
                  end
         | 
| 161 142 | 
             
                end
         | 
| 162 | 
            -
             | 
| 143 | 
            +
             | 
| 163 144 | 
             
                def args
         | 
| 164 145 | 
             
                  definition.argument_expectation.expected_arguments
         | 
| 165 146 | 
             
                end
         | 
| 166 | 
            -
                
         | 
| 167 | 
            -
                def do_call_implementation_and_get_return_value(double_injection, *args, &block)
         | 
| 168 | 
            -
                  if definition.implementation_is_original_method?
         | 
| 169 | 
            -
                    if double_injection.object_has_original_method?
         | 
| 170 | 
            -
                      double_injection.call_original_method(*args, &block)
         | 
| 171 | 
            -
                    else
         | 
| 172 | 
            -
                      double_injection.subject.__send__(
         | 
| 173 | 
            -
                        :method_missing,
         | 
| 174 | 
            -
                        method_name,
         | 
| 175 | 
            -
                        *args,
         | 
| 176 | 
            -
                        &block
         | 
| 177 | 
            -
                      )
         | 
| 178 | 
            -
                    end
         | 
| 179 | 
            -
                  else
         | 
| 180 | 
            -
                    if implementation
         | 
| 181 | 
            -
                      if implementation.is_a?(Method)
         | 
| 182 | 
            -
                        implementation.call(*args, &block)
         | 
| 183 | 
            -
                      else
         | 
| 184 | 
            -
                        args << ProcFromBlock.new(&block) if block
         | 
| 185 | 
            -
                        implementation.call(*args)
         | 
| 186 | 
            -
                      end
         | 
| 187 | 
            -
                    else
         | 
| 188 | 
            -
                      nil
         | 
| 189 | 
            -
                    end
         | 
| 190 | 
            -
                  end
         | 
| 191 | 
            -
                end
         | 
| 192 | 
            -
             | 
| 193 | 
            -
                def extract_subject_from_return_value(return_value)
         | 
| 194 | 
            -
                  case return_value
         | 
| 195 | 
            -
                  when DoubleDefinitions::DoubleDefinition
         | 
| 196 | 
            -
                    return_value.root_subject
         | 
| 197 | 
            -
                  when DoubleDefinitions::DoubleDefinitionCreatorProxy
         | 
| 198 | 
            -
                    return_value.__creator__.root_subject
         | 
| 199 | 
            -
                  else
         | 
| 200 | 
            -
                    return_value
         | 
| 201 | 
            -
                  end
         | 
| 202 | 
            -
                end
         | 
| 203 | 
            -
             | 
| 204 | 
            -
                def implementation
         | 
| 205 | 
            -
                  definition.implementation
         | 
| 206 | 
            -
                end
         | 
| 207 147 |  | 
| 208 148 | 
             
                def argument_expectation
         | 
| 209 149 | 
             
                  definition.argument_expectation
         | 
| 210 | 
            -
                end | 
| 150 | 
            +
                end
         | 
| 211 151 | 
             
              end
         | 
| 212 152 | 
             
            end
         | 
| @@ -4,7 +4,7 @@ module RR | |
| 4 4 | 
             
                  class << self
         | 
| 5 5 | 
             
                    def blank_slate_methods
         | 
| 6 6 | 
             
                      instance_methods.each do |m|
         | 
| 7 | 
            -
                        unless m =~ /^_/ || m.to_s == 'object_id' || m.to_s == 'respond_to?' || m.to_s == 'method_missing'
         | 
| 7 | 
            +
                        unless m =~ /^_/ || m.to_s == 'object_id' || m.to_s == 'respond_to?' || m.to_s == 'method_missing' || m.to_s == 'instance_eval' || m.to_s == 'instance_exec'
         | 
| 8 8 | 
             
                          alias_method "__blank_slated_#{m}", m
         | 
| 9 9 | 
             
                          undef_method m
         | 
| 10 10 | 
             
                        end
         | 
| @@ -0,0 +1,119 @@ | |
| 1 | 
            +
            module RR
         | 
| 2 | 
            +
              module Injections
         | 
| 3 | 
            +
                # RR::DoubleInjection is the binding of an subject and a method.
         | 
| 4 | 
            +
                # A double_injection has 0 to many Double objects. Each Double
         | 
| 5 | 
            +
                # has Argument Expectations and Times called Expectations.
         | 
| 6 | 
            +
                class DoubleInjection < Injection
         | 
| 7 | 
            +
                  attr_reader :subject_class, :method_name, :doubles
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  MethodArguments = Struct.new(:arguments, :block)
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  def initialize(subject, method_name, subject_class)
         | 
| 12 | 
            +
                    @subject = subject
         | 
| 13 | 
            +
                    @subject_class = subject_class
         | 
| 14 | 
            +
                    @method_name = method_name.to_sym
         | 
| 15 | 
            +
                    @doubles = []
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  # RR::DoubleInjection#register_double adds the passed in Double
         | 
| 19 | 
            +
                  # into this DoubleInjection's list of Double objects.
         | 
| 20 | 
            +
                  def register_double(double)
         | 
| 21 | 
            +
                    @doubles << double
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  # RR::DoubleInjection#bind injects a method that acts as a dispatcher
         | 
| 25 | 
            +
                  # that dispatches to the matching Double when the method
         | 
| 26 | 
            +
                  # is called.
         | 
| 27 | 
            +
                  def bind
         | 
| 28 | 
            +
                    if subject_respond_to_method?(method_name)
         | 
| 29 | 
            +
                      if subject_has_method_defined?(method_name)
         | 
| 30 | 
            +
                        bind_method_with_alias
         | 
| 31 | 
            +
                      else
         | 
| 32 | 
            +
                        space.method_missing_injection(subject)
         | 
| 33 | 
            +
                        space.singleton_method_added_injection(subject)
         | 
| 34 | 
            +
                      end
         | 
| 35 | 
            +
                    else
         | 
| 36 | 
            +
                      bind_method
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
                    self
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  # RR::DoubleInjection#verify verifies each Double
         | 
| 42 | 
            +
                  # TimesCalledExpectation are met.
         | 
| 43 | 
            +
                  def verify
         | 
| 44 | 
            +
                    @doubles.each do |double|
         | 
| 45 | 
            +
                      double.verify
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
                  # RR::DoubleInjection#reset removes the injected dispatcher method.
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  # It binds the original method implementation on the subject
         | 
| 51 | 
            +
                  # if one exists.
         | 
| 52 | 
            +
                  def reset
         | 
| 53 | 
            +
                    if subject_has_original_method?
         | 
| 54 | 
            +
                      subject_class.__send__(:alias_method, method_name, original_method_alias_name)
         | 
| 55 | 
            +
                      subject_class.__send__(:remove_method, original_method_alias_name)
         | 
| 56 | 
            +
                    else
         | 
| 57 | 
            +
                      if subject_has_method_defined?(method_name)
         | 
| 58 | 
            +
                        subject_class.__send__(:remove_method, method_name)
         | 
| 59 | 
            +
                      end
         | 
| 60 | 
            +
                    end
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                  def dispatch_method(args, block)
         | 
| 64 | 
            +
                    dispatch = MethodDispatches::MethodDispatch.new(self, args, block)
         | 
| 65 | 
            +
                    if @bypass_bound_method
         | 
| 66 | 
            +
                      dispatch.call_original_method
         | 
| 67 | 
            +
                    else
         | 
| 68 | 
            +
                      dispatch.call
         | 
| 69 | 
            +
                    end
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  def dispatch_method_missing(method_name, args, block)
         | 
| 73 | 
            +
                    MethodDispatches::MethodMissingDispatch.new(subject, method_name, args, block).call
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                  def subject_has_original_method_missing?
         | 
| 77 | 
            +
                    subject_respond_to_method?(original_method_missing_alias_name)
         | 
| 78 | 
            +
                  end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                  def original_method_alias_name
         | 
| 81 | 
            +
                    "__rr__original_#{@method_name}"
         | 
| 82 | 
            +
                  end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                  def original_method_missing_alias_name
         | 
| 85 | 
            +
                    MethodDispatches::MethodMissingDispatch.original_method_missing_alias_name
         | 
| 86 | 
            +
                  end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                  def bypass_bound_method
         | 
| 89 | 
            +
                    @bypass_bound_method = true
         | 
| 90 | 
            +
                    yield
         | 
| 91 | 
            +
                  ensure
         | 
| 92 | 
            +
                    @bypass_bound_method = nil
         | 
| 93 | 
            +
                  end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                  protected
         | 
| 96 | 
            +
                  def deferred_bind_method
         | 
| 97 | 
            +
                    unless subject_has_method_defined?(original_method_alias_name)
         | 
| 98 | 
            +
                      bind_method_with_alias
         | 
| 99 | 
            +
                    end
         | 
| 100 | 
            +
                    @performed_deferred_bind = true
         | 
| 101 | 
            +
                  end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                  def bind_method_with_alias
         | 
| 104 | 
            +
                    subject_class.__send__(:alias_method, original_method_alias_name, method_name)
         | 
| 105 | 
            +
                    bind_method
         | 
| 106 | 
            +
                  end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                  def bind_method
         | 
| 109 | 
            +
                    returns_method = <<-METHOD
         | 
| 110 | 
            +
                    def #{@method_name}(*args, &block)
         | 
| 111 | 
            +
                      arguments = MethodArguments.new(args, block)
         | 
| 112 | 
            +
                      RR::Space.double_injection(self, :#{@method_name}).dispatch_method(arguments.arguments, arguments.block)
         | 
| 113 | 
            +
                    end
         | 
| 114 | 
            +
                    METHOD
         | 
| 115 | 
            +
                    subject_class.class_eval(returns_method, __FILE__, __LINE__ - 5)
         | 
| 116 | 
            +
                  end
         | 
| 117 | 
            +
                end
         | 
| 118 | 
            +
              end
         | 
| 119 | 
            +
            end
         | 
| @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            module RR
         | 
| 2 | 
            +
              module Injections
         | 
| 3 | 
            +
                class Injection
         | 
| 4 | 
            +
                  include Space::Reader
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  attr_reader :subject
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def subject_has_method_defined?(method_name)
         | 
| 9 | 
            +
                    @subject.methods.include?(method_name.to_s) || @subject.protected_methods.include?(method_name.to_s) || @subject.private_methods.include?(method_name.to_s)
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def subject_has_original_method?
         | 
| 13 | 
            +
                    subject_respond_to_method?(original_method_alias_name)
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  protected
         | 
| 17 | 
            +
                  def subject_respond_to_method?(method_name)
         | 
| 18 | 
            +
                    subject_has_method_defined?(method_name) || @subject.respond_to?(method_name)
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
            end
         | 
| @@ -0,0 +1,62 @@ | |
| 1 | 
            +
            module RR
         | 
| 2 | 
            +
              module Injections
         | 
| 3 | 
            +
                class MethodMissingInjection < Injection
         | 
| 4 | 
            +
                  def initialize(subject)
         | 
| 5 | 
            +
                    @subject = subject
         | 
| 6 | 
            +
                  end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def bind
         | 
| 9 | 
            +
                    unless subject.respond_to?(original_method_alias_name)
         | 
| 10 | 
            +
                      unless subject.respond_to?(:method_missing)
         | 
| 11 | 
            +
                        @placeholder_method_defined = true
         | 
| 12 | 
            +
                        subject_class.class_eval do
         | 
| 13 | 
            +
                          def method_missing(method_name, *args, &block)
         | 
| 14 | 
            +
                            super
         | 
| 15 | 
            +
                          end
         | 
| 16 | 
            +
                        end
         | 
| 17 | 
            +
                      end
         | 
| 18 | 
            +
                      subject_class.__send__(:alias_method, original_method_alias_name, :method_missing)
         | 
| 19 | 
            +
                      bind_method
         | 
| 20 | 
            +
                    end
         | 
| 21 | 
            +
                    self
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  def reset
         | 
| 25 | 
            +
                    if subject_has_method_defined?(original_method_alias_name)
         | 
| 26 | 
            +
                      memoized_original_method_alias_name = original_method_alias_name
         | 
| 27 | 
            +
                      placeholder_method_defined = @placeholder_method_defined
         | 
| 28 | 
            +
                      subject_class.class_eval do
         | 
| 29 | 
            +
                        if placeholder_method_defined
         | 
| 30 | 
            +
                          remove_method :method_missing
         | 
| 31 | 
            +
                        else
         | 
| 32 | 
            +
                          alias_method :method_missing, memoized_original_method_alias_name
         | 
| 33 | 
            +
                        end
         | 
| 34 | 
            +
                        remove_method memoized_original_method_alias_name
         | 
| 35 | 
            +
                      end
         | 
| 36 | 
            +
                    end
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  def dispatch_method(method_name, args, block)
         | 
| 40 | 
            +
                    MethodDispatches::MethodMissingDispatch.new(subject, method_name, args, block).call
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  protected
         | 
| 44 | 
            +
                  def subject_class
         | 
| 45 | 
            +
                    class << subject; self; end
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  def bind_method
         | 
| 49 | 
            +
                    returns_method = <<-METHOD
         | 
| 50 | 
            +
                    def method_missing(method_name, *args, &block)
         | 
| 51 | 
            +
                      RR::Space.method_missing_injection(self).dispatch_method(method_name, args, block)
         | 
| 52 | 
            +
                    end
         | 
| 53 | 
            +
                    METHOD
         | 
| 54 | 
            +
                    subject_class.class_eval(returns_method, __FILE__, __LINE__ - 4)
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                  def original_method_alias_name
         | 
| 58 | 
            +
                    MethodDispatches::MethodMissingDispatch.original_method_missing_alias_name
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
              end
         | 
| 62 | 
            +
            end
         | 
| @@ -0,0 +1,58 @@ | |
| 1 | 
            +
            module RR
         | 
| 2 | 
            +
              module Injections
         | 
| 3 | 
            +
                class SingletonMethodAddedInjection < Injection
         | 
| 4 | 
            +
                  def initialize(subject)
         | 
| 5 | 
            +
                    @subject = subject
         | 
| 6 | 
            +
                  end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def bind
         | 
| 9 | 
            +
                    unless subject.respond_to?(original_method_alias_name)
         | 
| 10 | 
            +
                      unless subject.respond_to?(:singleton_method_added)
         | 
| 11 | 
            +
                        @placeholder_method_defined = true
         | 
| 12 | 
            +
                        subject_class.class_eval do
         | 
| 13 | 
            +
                          def singleton_method_added(method_name)
         | 
| 14 | 
            +
                            super
         | 
| 15 | 
            +
                          end
         | 
| 16 | 
            +
                        end
         | 
| 17 | 
            +
                      end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                      memoized_subject = subject
         | 
| 20 | 
            +
                      memoized_space = space
         | 
| 21 | 
            +
                      memoized_original_method_alias_name = original_method_alias_name
         | 
| 22 | 
            +
                      subject_class.__send__(:alias_method, original_method_alias_name, :singleton_method_added)
         | 
| 23 | 
            +
                      subject_class.__send__(:define_method, :singleton_method_added) do |method_name_arg|
         | 
| 24 | 
            +
                        if memoized_space.double_injection_exists?(memoized_subject, method_name_arg)
         | 
| 25 | 
            +
                          memoized_space.double_injection(memoized_subject, method_name_arg).send(:deferred_bind_method)
         | 
| 26 | 
            +
                        end
         | 
| 27 | 
            +
                        send(memoized_original_method_alias_name, method_name_arg)
         | 
| 28 | 
            +
                      end
         | 
| 29 | 
            +
                    end
         | 
| 30 | 
            +
                    self
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  def reset
         | 
| 34 | 
            +
                    if subject_has_method_defined?(original_method_alias_name)
         | 
| 35 | 
            +
                      memoized_original_method_alias_name = original_method_alias_name
         | 
| 36 | 
            +
                      placeholder_method_defined = @placeholder_method_defined
         | 
| 37 | 
            +
                      subject_class.class_eval do
         | 
| 38 | 
            +
                        if placeholder_method_defined
         | 
| 39 | 
            +
                          remove_method :singleton_method_added
         | 
| 40 | 
            +
                        else
         | 
| 41 | 
            +
                          alias_method :singleton_method_added, memoized_original_method_alias_name
         | 
| 42 | 
            +
                        end
         | 
| 43 | 
            +
                        remove_method memoized_original_method_alias_name
         | 
| 44 | 
            +
                      end
         | 
| 45 | 
            +
                    end
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  protected
         | 
| 49 | 
            +
                  def subject_class
         | 
| 50 | 
            +
                    class << subject; self; end
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  def original_method_alias_name
         | 
| 54 | 
            +
                    "__rr__original_singleton_method_added"
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
              end
         | 
| 58 | 
            +
            end
         |