2010-11-22 18 views
5

Soy desarrollador de PHP y en este momento estoy aprendiendo Rails (3) y, por supuesto, Ruby. I don't want to believe in magic y por eso trato de entender todo lo que pueda sobre las cosas que ocurren "detrás" de Rails. Lo que me pareció interesante son las llamadas a métodos como has_one o belongs_to en los modelos de ActiveRecord.¿Cómo funciona el método Rails como "has_one"?

Me trataron de reproducir esa, y venía con ingenua ejemplo: "¿Funcionará"

# has_one_test_1.rb 
module Foo 
    class Base 
    def self.has_one 
     puts 'Will it work?' 
    end 
    end 
end 

class Model2 < Foo::Base 
    has_one 
end 

Sólo se ejecuta este archivo es la salida, como lo esperaba.

Mientras buscaba a través de la fuente de los rieles encontré la función responsable: def has_one(association_id, options = {}).

Como podría ser esto, porque obviamente es una instancia (?) Y no un método de clase, no debería funcionar.

Después de una cierta investigación me encontré con un ejemplo que podría ser una respuesta:

# has_one_test_2.rb 
module Foo 
    module Bar 
    module Baz 
     def has_one stuff 
     puts "I CAN HAS #{stuff}?" 
     end 
    end 
    def self.included mod 
     mod.extend(Baz) 
    end 
    end 
    class Base 
    include Bar 
    end 
end 

class Model < Foo::Base 
    has_one 'CHEEZBURGER' 
end 

Ahora se ejecuta es la salida has_one_test_2.rb archivo que he tengo cheezburger. Si lo entendí bien, lo primero que sucede es que la clase Base intenta incluir el módulo Bar. En el momento de esta inclusión, se invoca el método self.included, que amplía el módulo Bar con el módulo Baz (y su método de instancia has_one). Entonces, en esencia, el método has_one está incluido (¿mezclado?) En la clase Base. Pero aún así, no lo entiendo del todo. Object#extend agrega el método del módulo, pero aún así, no estoy seguro de cómo reproducir este comportamiento utilizando extender. Así que mis preguntas son:

  1. ¿Qué pasó exactamente aquí. Quiero decir, ¿todavía no sé cómo se ha convertido un método en el método de clase? ¿Qué parte exactamente lo causó?

  2. Esta posibilidad de hacer llamadas a este método (que se parece a la configuración) es realmente genial. ¿Hay una manera alternativa o más simple de lograr esto?

Respuesta

9

Puede extend y include un módulo.

extend añade los métodos del módulo como métodos de clase Una implementación más simple de su ejemplo:

module Bar 
    def has_one stuff 
    puts "I CAN HAS #{stuff}?" 
    end 
end 

class Model 
    extend Bar 
    has_one 'CHEEZBURGER' 
end 

include añade los métodos del módulo como métodos de instancia

class Model 
    include Bar 
end 
Model.new.has_one 'CHEEZBURGER' 

Rails utiliza esto para añadir dinámicamente métodos a su clase.

Por ejemplo, podría utilizar define_method:

module Bar 
    def has_one stuff 
    define_method(stuff) do 
     puts "I CAN HAS #{stuff}?" 
    end 
    end 
end 

class Model 
    extend Bar 
    has_one 'CHEEZBURGER' 
end 

Model.new.CHEEZBURGER # => I CAN HAS CHEEZBURGER? 
3

felicito por negarse a creer en la magia. I altamente le recomiendo que obtenga el libro Metaprogramming Ruby.Recientemente lo obtuve y estaba desencadenando epifanías de izquierda a derecha en mah brainz. Repasa muchas de estas cosas a las que las personas comúnmente se refieren como 'magia'. Una vez que los cubre a todos, pasa Active Record como un ejemplo para mostrarle que ahora comprende los temas. Lo mejor de todo es que el libro se lee muy fácilmente: es muy digerible y breve.

0

Por otra parte, se puede utilizar una (por lo general muy maltratado, pero a veces bastante útil) method_missing concepto:

class Foo 
    def method_missing(method, *arg) 
     # Here you are if was some method that wasn't defined before called to an object 
     puts "method = #{method.inspect}" 
     puts "args = #{arg.inspect}" 
     return nil 
    end 
end 

Foo.new.abracadabra(1, 2, 'a') 

produce

method = :abracadabra 
args = [1, 2, "a"]

En general, este mecanismo se utiliza muy a menudo como

def method_missing(method, *arg) 
    case method 
    when :has_one 
    # implement has_one method 
    when :has_many 
    # ... 
    else 
    raise NoMethodError.new 
    end 
end 
Cuestiones relacionadas