Metaprogramming: How to discover the real class of an object?
I was kidding with metaprogramming in Ruby and I did this code:
class Class
def ===(other)
other.kind_of?(self)
end
end
class FakeClass
def initialize(object)
methods.each {|m| eval "undef #{m}" if m.to_sym != :methods }
define = proc do |m|
eval(<<-END)
def #{m}(*a, &b)
@object.#{m}(*a, &b)
rescue Object
raise $!.class, $!.message.gsub("FakeClass", @object.class.to_s),
$!.backtrace-[$!.backtrace[-caller.size-1]]
end
END
end
object.methods.each {|m| define[m] }
def method_missing(name, *a, &b)
if @object.methods.include?(name.to_s)
define[name]
eval "@object.#{name}(*a, &b)"
elsif @object.methods.include?("method_missing")
eval "@object.#{name}(*a, &b)"
else
super
end
rescue Object
raise $!.class, $!.message.gsub("FakeClass", @object.class.to_s),
$!.backtrace-[$!.backtrace[-caller.size-1]]
end
@object = object
end
end
This creates a fake class that mimics a object. Look:
a 开发者_开发技巧= FakeClass.new(1) # => 1
a.class # => Fixnum
a.methods # => Return all Fixnum methods
a + 1 # => 2 (is not a FakeClass)
Fixnum === a # => true
a.something # => NoMethodError:
# undefined method `something' for 1:Fixnum
class Fixnum
def foo
true
end
end
a.foo # => true
The problem is, now I don't know how to know if a object is real or fake. In others words, if #class
returns the true class of the object. Exist some pure ruby way to differentiate?
Think in a scenario where I don't know that FakeClass exist or I don't know what is the name of the FakeClass. It means I can't edit the FakeClass to add a method like #is_fake?
.
PS: I know that a.instance_eval {self}
returns the object (not fake). But it doesn't help to check if a
is fake.
Here's another answer:
a = FakeClass.new(1)
b = 1
da = Marshal.dump(a)
db = Marshal.dump(b)
puts da == db #=> false
A facetious answer: change Fixnum and see if it sticks. It is rather specific to this method of proxying, I guess, but it will do the job.
class Fixnum
def foo
return true
end
end
a.foo # NoMethodError
I should make this into a nice function, but lazy :)
You could have FakeClass implement #class, and return FakeClass
such as:
class FakeClass
#code
def class
FakeClass
end
end
To see what class is the object you are proxying, just call
a.object.class #=> Fixnum
For that FakeClass, I know about the @object, and I'd try:
a = FakeClass.new(5)
class << a
def fake?
self.instance_variable_defined? :@object
end
end
a.fake? #=> true
About an unknown FakeClass, I'd compare the number of instance variables:
fake = (a.instance_variables.length > 1.instance_variables.length)
You can define any method you want in FakeClass and check for it with respond_to?
a = FakeClass.new(1)
a.respond_to?( 'is_fake_class?' )
for instance.
I was wondering the same thing. And if you think this is just playing around, try digging through Rails ActiveRecord associations (Specifically: AssociationProxy) and you'll find exactly this kind of magic (the scary kind!) going on.
Here is one way that I ran across to at least detect this kind of thing:
a = FakeClass.new(5)
Fixnum.instance_method(:class).bind(a)
throws:
TypeError: bind argument must be an instance of Fixnum
I feel like there must be a better way though.
精彩评论