Skip to content

Instantly share code, notes, and snippets.

@myronmarston
Created August 21, 2011 22:19
Show Gist options
  • Save myronmarston/1161260 to your computer and use it in GitHub Desktop.
Save myronmarston/1161260 to your computer and use it in GitHub Desktop.
Crazy hack to intercept all changes to instance variables
module IVarInterceptor
def self.included(klass)
def klass.new(*args, &block)
super.extend IVarInterceptor
end
end
def self.extended(object)
object.singleton_class.class_eval do
original_methods = instance_methods.each_with_object({}) do |method_name, hash|
next if [ :instance_variable_get, :instance_variables, :object_id ].include?(method_name)
next if BasicObject.instance_methods.include?(method_name)
next if IVarInterceptor.instance_methods.include?(method_name)
hash[method_name] = instance_method(method_name)
undef_method method_name
end
define_method :method_missing do |method_name, *args, &block|
return_value, changed_ivars = capture_ivar_changes do
original_methods[method_name].bind(self).call(*args, &block)
end
if changed_ivars.any?
puts "The following instance variables changed as a result of calling ##{method_name}: "
changed_ivars.each do |ivar, change|
puts " - #{ivar} changed from #{change.fetch(:from, "(unset)").inspect} to #{change.fetch(:to, "(unset)").inspect}"
end
else
puts "No instance variables changed as a result of calling ##{method_name}."
end
return_value
end
end
end
def capture_ivar_changes
ivar_values_1 = current_ivar_values
return_value = yield
ivar_values_2 = current_ivar_values
changed_ivars = {}
(ivar_values_1.keys | ivar_values_2.keys).each do |ivar|
case
when ivar_values_1.has_key?(ivar) && !ivar_values_2.has_key?(ivar)
changed_ivars[ivar] = { :from => ivar_values_1[ivar] }
when ivar_values_2.has_key?(ivar) && !ivar_values_1.has_key?(ivar)
changed_ivars[ivar] = { :to => ivar_values_2[ivar] }
when ivar_values_1[ivar] != ivar_values_2[ivar]
changed_ivars[ivar] = { :from => ivar_values_1[ivar], :to => ivar_values_2[ivar] }
end
end
return return_value, changed_ivars
end
def current_ivar_values
instance_variables.each_with_object({}) do |ivar, values|
values[ivar] = instance_variable_get(ivar)
end
end
end
class Point
include IVarInterceptor
attr_accessor :x, :y
def product
@product ||= x * y
end
end
p = Point.new
p.x = 7
p.y = 3
puts "The product is: #{p.product}"
p.x = 2
puts "The product is: #{p.product}"
$ ruby intercept_ivars.rb
The following instance variables changed as a result of calling #x=:
- @x changed from "(unset)" to 7
The following instance variables changed as a result of calling #y=:
- @y changed from "(unset)" to 3
No instance variables changed as a result of calling #x.
No instance variables changed as a result of calling #y.
The following instance variables changed as a result of calling #product:
- @product changed from "(unset)" to 21
The product is: 21
The following instance variables changed as a result of calling #x=:
- @x changed from 7 to 2
No instance variables changed as a result of calling #product.
The product is: 21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment