Metaprogramming: How to discover the real class of an object?

匿名 (未验证) 提交于 2019-12-03 03:10:03

问题:

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.

回答1:

Here's another answer:

a = FakeClass.new(1) b = 1 da = Marshal.dump(a) db = Marshal.dump(b) puts da == db            #=> false 


回答2:

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 :)



回答3:

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 


回答4:

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) 


回答5:

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.



回答6:

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.



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!