开发者

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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜