[Ror-es] #include en ruby

Xavier Noria fxn at hashref.com
Mon Sep 25 21:39:13 GMT 2006


On Sep 25, 2006, at 8:03 PM, Esteban wrote:

> Magnífica solución: funcional, sencilla, elegante, ruby-idiomática

Estupendo, me alegro de que te guste :-).

> Claro también me gustaría entender por qué funciona, un poco de  
> teoría de Ruby ya sabes, así que te disparo unas preguntas si no te  
> molesta
>
> 1. self.abstract_class, le asigna un valor a una variable de clase  
> (o un setter de clase), es este un atributo de ActiveRecord o de  
> Object?

abstract_class es un atributo de la clase ActiveRecord::Base, esta  
definido en el archivo activerecord/lib/active_record/base.rb:

   # Set this to true if this is an abstract class (see  
#abstract_class?).
   attr_accessor :abstract_class

Dada un clase persistente, klass, AR necesita determinar de donde  
leer su metadata, para ello usa el metodo de clase base_clase, y como  
se ve ignora las clases abstractas:

   # Returns the base AR subclass that this class descends from. If A
   # extends AR::Base, A.base_class will return A. If B descends from A
   # through some arbitrarily deep hierarchy, B.base_class will  
return A.
   def base_class
     class_of_active_record_descendant(self)
   end

   # Returns the class descending directly from ActiveRecord in the  
inheritance hierarchy.
   def class_of_active_record_descendant(klass)
     if klass.superclass == Base || klass.superclass.abstract_class?
       klass
     elsif klass.superclass.nil?
       raise ActiveRecordError, "#{name} doesn't belong in a  
hierarchy descending from ActiveRecord"
     else
       class_of_active_record_descendant(klass.superclass)
     end
   end

> 2. A has_* y validates_* les llamas métodos estáticos, vengo de C++  
> y Java, ahí eso equivale a un método de clase, que significa en Ruby

Eso mismo, son metodos de clase. Cuando en Ruby defines un metodo  
"foo" de este modo

   class Foo
     def self.foo
       # ...
     end
   end

estas definiendo un metodo de clase y puedes invocarlo de este modo:

   Foo.foo

Hay idiomas alternativos para definir metodos de clase pero ese es  
para mi el mas evidente. Hay todo un tema aqui muy chulo para  
estudiar para saber exactamente como va esto, ahi intervienen los  
conceptos de metodo singleton, que las clases son instancias de  
Class, que todo esta basado en envio de mensajes, etc. Eso esta muy  
bien explicado en el Pickaxe y en Ruby for Rails.

Por cierto, una de las diferencias con los metodos estaticos de Java  
es que no puedes invocar metodos de clase sobre instancias de dicha  
clase:

   irb(main):001:0> class Foo; def self.foo; true; end; end
   => nil
   irb(main):002:0> Foo.foo
   => true
   irb(main):003:0> f = Foo.new
   => #<Foo:0x35a618>
   irb(main):004:0> f.foo
   NoMethodError: undefined method `foo' for #<Foo:0x35a618>
           from (irb):4
           from :0

OK, pues sucede que ActiveRecord::Base define montones de metodos de  
clase, por ejemplo has_many es uno.

Cuando el codigo que define una clase se evalua, "self" es la clase  
misma, y cuando se esta en la definicion de un metodo, "self" es el  
objeto al cual se esta enviando el mensaje. Si el metodo es de clase  
"self" dentro del metodo sigue siendo la clase, siempre es el  
receptor del mensaje.

Un dato importante es que el codigo que define una clase se ejecuta  
*al interpretar la definicion de la clase*:

   irb(main):005:0> class Foo; puts "foo"; end
   foo
   => nil

Ves que se imprime "foo"? En Java se podria medio emular eso con  
bloques static { ... }.

Por ello, cuando escribes

   class User < ActiveRecord::Base
     has_many :posts
   end

se ejecuta el metodo de clase has_many, con el simbolo :posts como  
argumento, al procesarse la definicion de la clase User. Como no se  
explicita el receptor este es self por definicion, y en ese punto  
self es User. Lo interesante de esto es que como justamente has_many  
se evalua al interpretar la definicion de User tenemos ahi la  
oportunidad de realizar metaprogramacion. Para cuando vayamos a usar  
la clase todos los metodos de coleccion existen como si los hubieras  
picado de verdad.

Todo eso junto explica como funciona la solucion que envie:

    class Abstract < ActiveRecord::Base
      self.abstract_class = true

      def self.setup_common_associations
        has_many :foos
        has_one  :bar
      end
    end

    class X < Abstract
      setup_common_associations
    end

Hemos definido un metodo de clase en Abstract. Como en cualquier  
metodo su cuerpo se ejecuta solo al ser invocado.

Al interpretarse la definicion de X hay una llamada a  
setup_common_associations en el top-level, por tanto dicha llamada se  
realiza. La ejecucion del metodo tiene as su vez una llamada has_many 
(:foos), cuyo receptor es self, que en ese momento es X. Por lo  
tanto, la metaprogramacion se realiza sobre X, del mismo modo que se  
haria si hubieras escrito ese has_many normal en el top-level de X.  
Lo verdaderamente importante es que conseguimos que X sea el receptor  
de ese codigo.

Que tal explicado asi?

-- fxn



More information about the Ror-es mailing list