2012-03-13 11 views
9

Esta pregunta es una extensión a la planteada aquí:factorygirl en carriles - Asociaciones w/Restricciones de unicidad

Using factory_girl in Rails with associations that have unique constraints. Getting duplicate errors

La respuesta ofrecida ha funcionado perfectamente para mí. Esto es lo que parece:

# Creates a class variable for factories that should be only created once. 

module FactoryGirl 

    class Singleton 
    @@singletons = {} 

    def self.execute(factory_key) 
     begin 
     @@singletons[factory_key] = FactoryGirl.create(factory_key) 
     rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique 
     # already in DB so return nil 
     end 

     @@singletons[factory_key] 
    end 
    end 

end 

El problema que ha surgido para mí es cuando necesito para construir manualmente una asociación para apoyar una asociación polimórfica con una restricción de unicidad en un gancho. Por ejemplo:

class Matchup < ActiveRecord::Base 
    belongs_to :event 
    belongs_to :matchupable, :polymorphic => true 

    validates :event_id, :uniqueness => { :scope => [:matchupable_id, :matchupable_type] } 
end 

class BaseballMatchup < ActiveRecord::Base 
    has_one :matchup, :as => :matchupable 
end 

FactoryGirl.define do 
    factory :matchup do 
    event { FactoryGirl::Singleton.execute(:event) } 
    matchupable { FactoryGirl::Singleton.execute(:baseball_matchup) } 
    home_team_record '10-5' 
    away_team_record '9-6' 
    end 

    factory :baseball_matchup do 
    home_pitcher 'Joe Bloe' 
    home_pitcher_record '21-0' 
    home_pitcher_era 1.92 
    home_pitcher_arm 'R' 
    away_pitcher 'Jack John' 
    away_pitcher_record '0-21' 
    away_pitcher_era 9.92 
    away_pitcher_arm 'R' 
    after_build do |bm| 
     bm.matchup = Factory.create(:matchup, :matchupable => bm) 
    end 
    end 
end 

Mi aplicación Singleton actual no admite llamar FactoryGirl::Singleton.execute(:matchup, :matchupable => bm), solamente FactoryGirl::Singleton.execute(:matchup).

¿Cómo recomendaría modificar la fábrica de singleton para admitir una llamada como FactoryGirl::Singleton.execute(:matchup, :matchupable => bm) O FactoryGirl::Singleton.execute(:matchup)?

Porque en este momento, el código anterior arrojará un error de validación de unicidad ("El evento ya está hecho") cada vez que se ejecute el gancho en fábrica: baseball_matchup. En última instancia, esto es lo que se debe corregir para que no haya más de un emparejamiento o baseball_matchup en el DB.

métodos

Respuesta

1

Es necesario hacer dos cosas para hacer este trabajo:

  1. Aceptar atributos como un argumento a su método execute.
  2. Desactive tanto el nombre de fábrica como los atributos al crear las fábricas de singleton.

Tenga en cuenta que el paso 1 no es suficiente para resolver su problema.Incluso si permite que execute acepte atributos, la primera llamada al execute(:matchup, attributes) almacenará en caché ese resultado y lo devolverá cada vez que execute(:matchup), incluso si intenta pasar diferentes atributos a execute. Es por eso que también necesita cambiar lo que está utilizando como la clave hash para su hash @@singletons.

He aquí una aplicación que probé a cabo:

module FactoryGirl 
    class Singleton 
    @@singletons = {} 

    def self.execute(factory_key, attributes = {}) 

     # form a unique key for this factory and set of attributes 
     key = [factory_key.to_s, '?', attributes.to_query].join 

     begin 
     @@singletons[key] = FactoryGirl.create(factory_key, attributes) 
     rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique 
     # already in DB so return nil 
     end 

     @@singletons[key] 
    end 
    end 
end 

La clave es una cadena formada por el nombre de la fábrica y una representación de cadena de consulta de la hash de atributos (algo así como "matchup?event=6&matchupable=2"). Pude crear múltiples combinaciones diferentes con diferentes atributos, pero respetaba la singularidad de la combinación evento/emparejamiento.

> e = FactoryGirl.create(:event) 
> bm = FactoryGirl.create(:baseball_matchup) 
> m = FactoryGirl::Singleton.execute(:matchup, :event => e, :matchupable => bm) 
> m.id 
2 
> m = FactoryGirl::Singleton.execute(:matchup, :event => e, :matchupable => bm) 
> m.id 
2 
> f = FactoryGirl.create(:event) 
> m = FactoryGirl::Singleton.execute(:matchup, :event => f, :matchupable => bm) 
> m.id 
3 

Avísame si eso no te sirve.

1

Ruby puede tener valores predeterminados para los argumentos, por lo que definir su método de Singleton con una opciones por defecto vacío de hash:

def self.execute(factory_key, options={}) 

Ahora puede llamar a las dos cosas:

FactoryGirl::Singleton.execute(:matchup) 
    FactoryGirl::Singleton.execute(:matchup, :matchupable => bm) 

dentro del método , pruebe el argumento de opciones hash para ver si ha pasado algo:

if options.empty? 
    # no options specified 
else 
    # options were specified 
end 
3

A s zetetic ha mencionado, puede definir un segundo parámetro en su función de ejecución para enviar los atributos que se utilizarán durante la llamada a FactoryGirl.create, con un valor predeterminado de un hash vacío para que no anule ninguno de ellos en el caso no lo usa (no necesita verificar en este caso particular si el atributo hash está vacío).

Observe también que no es necesario definir un bloque begin..end en este caso, porque no hay nada que hacer después de su rescate, por lo que puede simplificar su método definiendo el rescate como parte de la definición del método La asignación en el caso de que la inicialización fue buena también devolverá el valor asignado, por lo que no es necesario acceder explícitamente al hash nuevamente para devolverlo. Con todos estos cambios, el código va a terminar como:

# Creates a class variable for factories that should be only created once. 

module FactoryGirl 

    class Singleton 
    @@singletons = {} 

    def self.execute(factory_key, attrs = {}) 
     @@singletons[factory_key] = FactoryGirl.create(factory_key, attrs) 
    rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique 
     # already in DB so return nil 
    end 
    end 

end 
+0

Creo que el problema aquí es que cuando llamas a FactoryGirl.create y hay un registro existente en la base de datos (por lo tanto, se genera una excepción), no se devolverá el singleton porque no estamos llamando a @@ singletons [factory_key ] después del rescate. – keruilin

Cuestiones relacionadas