2011-04-24 13 views
9
o = Object.new 
o.instance_eval { @str = "foo" } 
p o # => #<Object:0x5dd1a0 @foo="bar"> 

Esto es bueno. Llamar a p con un objeto como argumento imprime el resultado del método de los objetos inspect. Pero, por desgracia, si el objeto tiene un método to_s anulado entonces será salida de la salida de ese:Emular objeto predeterminado # inspeccionar Salida?

class << o 
    def to_s; @str; end 
end 
p o.to_s # => "foo" 
p o # => foo 

Así que para solucionar este problema, tenemos que definir un método inspect en nuestro objetivo:

class << o 
    def inspect; "blah"; end 
end 
p o # => "blah" 

¿Cómo puedo hacer que el método inspect de mi objeto muestre la forma predeterminada de Ruby como se muestra en la línea 3 del primer ejemplo de código?

El más cercano que tengo es el siguiente, pero no estoy seguro si es del todo bien

class << o 
    def inspect 
    vars = instance_variables.collect { |v| v.to_s << "=#{instance_variable_get(v).inspect}"}.join(", ") 
    "#<#{self.class}:0x#{object_id} #{vars}>" 
    end 
end 
+0

¿Qué versión de Ruby estás usando? –

Respuesta

5

El método predeterminado inspect resulta ser sorprendentemente complejo, ya que necesita manejar adecuadamente las llamadas recursivas a sí mismo. Aquí hay una implementación basada en el código fuente Rubinius que ignora la presencia de to_s.

module DefaultInspect 

    Thread.current[:inspected_objects] = {} 

    def inspected_objects 
     Thread.current[:inspected_objects] 
    end 

    def inspect_recursion_guard 
     inspected_objects[object_id] = true 
     begin 
     yield 
     ensure 
     inspected_objects.delete object_id 
     end 
    end 

    def inspect_recursion? 
     inspected_objects[object_id]  
    end 

    def inspect 
     prefix = "#<#{self.class}:0x#{self.__id__.to_s(16)}" 

     # If it's already been inspected, return the ... 
     return "#{prefix} ...>" if inspect_recursion? 

     # Otherwise, gather the ivars and show them. 
     parts = [] 

     inspect_recursion_guard do 
     instance_variables.each do |var| 
      parts << "#{var}=#{instance_variable_get(var).inspect}" 
     end 
     end 

     if parts.empty? 
     str = "#{prefix}>" 
     else 
     str = "#{prefix} #{parts.join(' ')}>" 
     end 

     str.taint if tainted? 

     return str 
    end 

end 

Para utilizar este módulo, usted haría algo como esto:

class Foo 

    include DefaultInspect 

    def to_s 
    @foo 
    end 
end 

f = Foo.new 
f.instance_eval { @foo = f } 
p f  #=> #<Foo:0x8042ad58 @foo=#<Foo:0x8042ad58 ...>> 
0
irb> o = Object.new.tap{ |o| o.instance_variable_set :@foo, "bar" } 
#=> #<Object:0x00000102849600 @foo="bar"> 

irb> def o.to_s; @foo; end; o 
#=> bar 

irb> module MyInspect 
irb> def inspect 
irb>  vars = instance_variables.map do |n| 
irb>  "#{n}=#{instance_variable_get(n).inspect}" 
irb>  end 
irb>  "#<%s:0x%x %s>" % [self.class,object_id,vars.join(', ')] 
irb> end 
irb> end 

irb> o.extend MyInspect 
#=> #<Object:0x81424b00 @foo="bar"> 

Editar: Bueno, parece que se me ocurrió básicamente lo que ya hizo . El tuyo y el mío dan como resultado diferentes representaciones object_id que el original, sin embargo.

Déjame investigar si hay alguna manera de vincular a la implementación oficial y usar eso.

+0

El "object_id" es en realidad el '(void *)' del objeto en MRI, y una referencia de puntero similar en jruby. Es inaccesible en código simple de rubí. Esto es raro. –

7

Para hacer que los números coinciden con la implementación original, sólo tiene que desplazar la izquierda OBJECT_ID por un bit que es tan se muestra de la siguiente manera:

(object_id << 1).to_s(16) 

Debe haber un bit adicional usado para una bandera.