2011-02-17 19 views
5

En Railscasts del proyecto, podrá ver este código:no puede entender la magia de Ruby

before(:each) do 
    login_as Factory(:user, :admin => true) 
end 

la correspondiente definición de la función es:

Factory.define :user do |f| 
    f.sequence(:github_username) { |n| "foo#{n}" } 
end 

No puedo entender cómo es el parámetro de administración pasando a la función, mientras que en la función no hay palabras sobre el parámetro de administración. Gracias

Respuesta

9

Factory.define no es una definición de función, es un método que toma un símbolo o cadena (en este caso el usuario) y un bloque que define la fábrica que está realizando. Factory(:user, :admin => true) hace un objeto User, con atributos de administrador. No está llamando al código en su segundo fragmento, llama al Factory() que inicializa una fábrica y selecciona uno (en este caso, el definido en el segundo fragmento). Luego pasa opciones en formato hash a Factory también.

Factory selecciona la fábrica :user que es muy genérica. La opción :admin=>true simplemente dice Factory para establecer la variable de instancia de administrador en Usuario en verdadero.

This is actually what it is calling in factory.rb in factory girl 

def initialize(name, options = {}) #:nodoc: 
    assert_valid_options(options) 
    @name = factory_name_for(name) 
    @options = options 
    @attributes = [] 
end 

Así fábrica (nombre, opciones) es equivalente a Factory.new (nombre, opciones) en este código.

http://www.ruby-doc.org/core/classes/Kernel.html Aviso Array y String etc. tienen construcciones similares. Estoy tratando de descubrir cómo lo hicieron ahora.

Todo esto es confuso incluso para los programadores Ruby decentes. Recomiendo encarecidamente el libro "Metaprogramación de Ruby". Es probablemente el mejor libro que he leído en ruby ​​y te dice mucho sobre este material mágico.

+1

En segundo lugar el libro Metaprogramming Ruby. Soy nuevo para Ruby y este libro me ha dado una comprensión mucho más profunda de Ruby. –

-1

No creo que el segundo fragmento sea la definición de la función. Las definiciones de función tienen def y end. Creo que el segundo fragmento parece una función o método llamado con un argumento de :user y un bloque que toma un parámetro f.

Por supuesto, con la metaprogramación nunca se puede estar seguro de lo que está pasando.

3

Hola megas,

respuesta de Michael Papile es esencialmente correcta. Sin embargo, me gustaría detallarlo un poco, ya que hay algunos matices técnicos que quizás desee conocer. Miré el código para Railscasts y factory_girl y creo que hay algunas piezas extra para el rompecabezas que explican cómo la : termina arg admin => true hasta la creación de la administrador atributo de la fábrica de usuario. La adición del atributo en realidad no ocurre por medio del método de fábrica initialize(), aunque como Michael señaló que efectivamente se está llamando al método en servicio para construir el nuevo objeto de fábrica del usuario.

Voy a incluir en esta explicación todos los pasos que tomé en caso de que quiera ver cómo investigar las preguntas similares que pueda tener.

Dado que su publicación original está fechada en feb.17th miré a la versión de railscasts que coincide estrechamente con esa fecha.

Miré en su Gemfile:

https://github.com/ryanb/railscasts/blob/d124319f4ca2a2367c1fa705f5c8229cce70921d/Gemfile

Línea 18:

gem "factory_girl_rails" 

entonces Fui a ver el compromiso de factory_girl_rails que la mayor parte estrecha similitud, la fecha 17o Feb.

https://github.com/thoughtbot/factory_girl_rails/blob/544868740c3e26d8a5e8337940f9de4990b1cd0b/factory_girl_rails.gemspec

Línea 16:

s.add_runtime_dependency('factory_girl', '~> 2.0.0.beta') 

factory_girl versión 2.0.0.beta no era realmente tan fácil de encontrar. No hay etiquetas github con ese nombre, así que acabo de ver el más cercano en términos de fecha de confirmación.

https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/syntax/vintage.rb

Líneas 122-128:

# Shortcut for Factory.default_strategy. 
# 
# Example: 
# Factory(:user, :name => 'Joe') 
def Factory(name, attrs = {}) 
    Factory.default_strategy(name, attrs) 
end 

Así que la invocaciónfábrica en Railscasts es en realidad llamar a un método de conveniencia que invoca la "estrategia por defecto", que se encuentra en el mismo archivo :

Líneas 39-52:

# Executes the default strategy for the given factory. This is usually create, 
# but it can be overridden for each factory. 
# 
# Arguments: 
# * name: +Symbol+ or +String+ 
# The name of the factory that should be used. 
# * overrides: +Hash+ 
# Attributes to overwrite for this instance. 
# 
# Returns: +Object+ 
# The result of the default strategy. 
def self.default_strategy(name, overrides = {}) 
    self.send(FactoryGirl.find(name).default_strategy, name, overrides) 
end 

Tenga en cuenta que FactoryGirl.find se invoca para obtener el objeto al que se llama default_strategy. El método encontrar resuelve aquí:

https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/registry.rb

Líneas 12-14:

def find(name) 
    @items[name.to_sym] or raise ArgumentError.new("Not registered: #{name.to_s}") 
end 

Aquí el nombre de usuario es :. Por lo tanto, deseamos invocar default_strategy en la fábrica usuario. Como señaló Michael Papile, esta fábrica de usuarios fue definida y registrada por el código Railscasts que originalmente pensaste que era la definición de clase para Factory.

https://ryanb/railscasts/blob/d124319f4ca2a2367c1fa705f5c8229cce70921d/spec/factories.rb

Líneas 23-25:

Factory.define :user do |f| 
    f.sequence(:github_username) { |n| "foo#{n}" } 
end 

Así que en la investigación de lo que es la estrategia por defecto para la fábrica de usuario, que miraron a su alrededor en el proyecto y Railscasts encontraron esta:

https://ryanb/railscasts/blob/d124319f4ca2a2367c1fa705f5c8229cce70921d/spec/factories.rb

Líneas 43-45:

def default_strategy #:nodoc: 
    @options[:default_strategy] || :create 
end 

: crear es la estrategia predeterminada. Volvemos a factory_girl para encontrar la def para create.

https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/syntax/methods.rb

Líneas 37-55:

# Generates, saves, and returns an instance from this factory. Attributes can 
# be individually overridden by passing in a Hash of attribute => value 
# pairs. 
# 
# Instances are saved using the +save!+ method, so ActiveRecord models will 
# raise ActiveRecord::RecordInvalid exceptions for invalid attribute sets. 
# 
# Arguments: 
# * name: +Symbol+ or +String+ 
# The name of the factory that should be used. 
# * overrides: +Hash+ 
# Attributes to overwrite for this instance. 
# 
# Returns: +Object+ 
# A saved instance of the class this factory generates, with generated 
# attributes assigned. 
def create(name, overrides = {}) 
    FactoryGirl.find(name).run(Proxy::Create, overrides) 
end 

La estrategia de crear llama a la plazo método definido aquí:

https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/factory.rb

Líneas 86-97:

def run(proxy_class, overrides) #:nodoc: 
    proxy = proxy_class.new(build_class) 
    overrides = symbolize_keys(overrides) 
    overrides.each {|attr, val| proxy.set(attr, val) } 
    passed_keys = overrides.keys.collect {|k| FactoryGirl.aliases_for(k) }.flatten 
    @attributes.each do |attribute| 
    unless passed_keys.include?(attribute.name) 
     attribute.add_to(proxy) 
    end 
    end 
    proxy.result(@to_create_block) 
end 

Una traducción/resumen de lo que este código está haciendo:

primer lugar, el objetoproxy está construido llamando nueva en el proxy_class, que en este caso es Proxy :: Create , que se define aquí:

https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/proxy/create.rb

Básicamente todo lo que necesita saber es que proxy está creando un nuevo objeto de fábrica de usuario e invocando devoluciones de llamada antes y después de que se cree el objeto de fábrica.

Volviendo al método plazo, vemos que todos los argumentos adicionales que se aprobaron inicialmente en el método de conveniencia fábrica (en este caso, : admin => true) están siendo etiquetados como reemplaza. El objeto proxy invoca un establece el método, pasando cada par de nombre-valor de atributo como args.

El set() método es parte de la clase Construir, la clase padre de Proxy.

https://thoughtbot/factory_girl/blob/9fb8a3b40f24f0c8477776133a2f9cd654ca1c8c/lib/factory_girl/proxy/build.rb

líneas 12-14:

def set(attribute, value) 
    @instance.send(:"#{attribute}=", value) 
end 

Aquí @instance se refiere a los objetos proxy, el objeto de fábrica de usuario.

Esto entonces, es como : admin => true se establece como un atributo en la fábrica del usuario que crea el código de especificaciones de railscasts.

Si lo desea, puede googlear "patrones de diseño de programación" y leer sobre los siguientes patrones: fábrica, apoderado, constructor, estrategia.

Espero que esta publicación sea útil.

==============================

@ Michael Papile:

que iba a agrega un comentario a tu publicación, pero aún no tengo suficientes puntos de reputación de stackoverflow.com para hacerlo.

Usted escribió:

http://www.ruby-doc.org/core/classes/Kernel.html Aviso matriz y cadena etc tienen construcciones similares. Estoy tratando de averiguar cómo lo hicieron ahora .

Si todavía tiene curiosidad, la Matriz y la Cadena que ve en el documento Kernel son en realidad solo métodos de fábrica utilizados para crear nuevos objetos de esos tipos. Es por eso que no se necesita la nueva invocación de método . En realidad, no son llamadas de constructor, sino que asignan e inicializan objetos Array y String, y por lo tanto, bajo el capó están haciendo el equivalente de invocar initialize() en objetos de esos tipos. (En C, sin embargo, por supuesto, no en Ruby)

+0

@favo - Gracias por la edición y la habilitación de los hipervínculos. Una vez que me di cuenta de que la creación de esta primera publicación me dio suficientes puntos para volver a habilitar los enlaces que hice clic en "editar" y de repente apareció una pantalla verde/roja que muestra exactamente las ediciones que quería hacer, con un comentario de edición adecuado, sin Menos. Como principiante de stackoverflow, al principio pensé que era la sugerencia generada automáticamente por stackoverflow.com ¡y estaba completamente asombrada! :) – David

+0

su bienvenida :-) afortunadamente ahora puede publicar más enlaces en futuras respuestas :-) – favo

Cuestiones relacionadas