tutorial rails factorybot example bot association ruby-on-rails-3 factory-bot

ruby on rails 3 - rails - Rieles: evitar errores de duplicación en Factory Girl... ¿Lo estoy haciendo mal?



factorybot gem (4)

Supongamos que tengo un user modelo, que tiene una restricción de exclusividad en el campo de email

Si llamo a Factory(:user) una vez que todo está bien, pero si lo llamo por segunda vez, fallará con un error de "entrada ya existente".

Actualmente estoy usando un simple ayudante para buscar una entrada existente en el DB antes de crear la fábrica ... y llamar a cualquier fábrica que fabrique a través de ese ayudante.

Funciona, pero no es del todo elegante, y considerando lo común que supongo que debe ser este problema, supongo que hay una mejor solución. Entonces, ¿hay una forma incorporada en la fábrica de return_or_create o return_or_create una fábrica, en lugar de simplemente cargar con create() ? Si no, ¿cómo la mayoría de la gente evita las entradas duplicadas con sus fábricas?


Encontré esta una buena manera de asegurarme de que las pruebas siempre pasen. De lo contrario, no puede estar seguro de que el 100% de las veces creará un correo electrónico único.

FactoryGirl.define do factory :user do name { Faker::Company.name } email { generate(:email) } end sequence(:email) do gen = "user_#{rand(1000)}@factory.com" while User.where(email: gen).exists? gen = "user_#{rand(1000)}@factory.com" end gen end end


Esto es lo que hago para forzar a la ''n'' en mi secuencia de chica de fábrica a ser la misma que la identificación de ese objeto, y así evitar las colisiones:

Primero, defino un método que encuentra cuál debe ser la próxima identificación en app / models / user.rb:

def self.next_id self.last.nil? ? 1 : self.last.id + 1 end

Luego, invoco User.next_id desde spec / factories.rb para iniciar la secuencia:

factory :user do association(:demo) association(:location) password "password" sequence(:email, User.next_id) {|n| "darth_#{n}@sunni.ru" } end


Si solo necesita generar algunos valores para los atributos, también puede agregar un método a Cadena, que realiza un seguimiento de las cadenas anteriores utilizadas para un atributo. Entonces podrías hacer algo como esto:

factory :user do fullname { Faker::Name.name.unique(''user_fullname'') } end

Yo uso este enfoque para sembrar. Quería evitar los números de secuencia, porque no se ven realistas.

Aquí la extensión String que hace que esto suceda:

class String # Makes sure that the current string instance is unique for the given id. # If you call unique multiple times on equivalent strings, this method will suffix it with a upcounting number. # Example: # puts "abc".unique("some_attribute") #=> "abc" # puts "abc".unique("some_attribute") #=> "abc-1" # puts "abc".unique("some_attribute") #=> "abc-2" # puts "abc".unique("other") #=> "abc" # # Internal: # We keep a data structure of the following format: # @@unique_values = { # "some_for_id" => { "used_string_1" : 1, "used_string_2": 2 } # the numbers represent the counter to be used as suffix for the next item # } def unique(for_id) @@unique_values ||= {} # initialize structure in case this method was never called before @@unique_values[for_id] ||= {} # initialize structure in case we have not seen this id yet counter = @@unique_values[for_id][self] || 0 result = (counter == 0) ? self : "#{self}-#{counter}" counter += 1 @@unique_values[for_id][self] = counter return result end end

Precaución: Esto no debe usarse para muchos atributos, ya que rastreamos todas las cadenas anteriores (optimizaciones posibles).


Respuesta simple: use factory.sequence

Si tiene un campo que necesita ser único, puede agregar una secuencia en factory_girl para asegurarse de que nunca vuelva a ser el mismo:

Factory.define :user do |user| sequence(:email){|n| "user#{n}@factory.com" } user.password{ "secret" } end

Esto aumentará n cada vez para generar una dirección de correo electrónico única, como `[email protected]. (Consulte https://github.com/thoughtbot/factory_girl/wiki/Usage para obtener más información)

Sin embargo, esto no siempre es genial en Rails.env.development ...

Con el tiempo, he descubierto que esta no es realmente la forma más útil de crear direcciones de correo electrónico únicas. La razón es que aunque la fábrica siempre es única para su entorno de prueba, no siempre es única para su entorno de desarrollo y n reinicia a medida que inicia el entorno hacia arriba y hacia abajo. En :test esto no es un problema porque la base de datos se borra, pero en :development se mantiene tienden a los mismos datos por un tiempo.

A continuación, obtiene colisiones y se encuentra teniendo que anular manualmente el correo electrónico a algo que sabe que es único y que es molesto.

A menudo más útil: use un número aleatorio

Como llamo a u = Factory :user desde la consola de forma regular, voy en lugar de generar un número aleatorio. No está garantizado para evitar colisiones, pero en la práctica casi nunca sucede:

Factory.define :user do |user| user.email {"user_#{Random.rand(1000).to_s}@factory.com" } user.password{ "secret" } end

NB: tiene que usar Random.rand lugar de rand () debido a una colisión (¿error?) En FactoryGirl [ https://github.com/thoughtbot/factory_girl/issues/219](see aquí).

Esto le permite crear usuarios a voluntad desde la línea de comandos, independientemente de si ya hay usuarios generados en fábrica en la base de datos.

Opcional adicional para facilitar las pruebas de correo electrónico

Cuando entra en las pruebas de correo electrónico, a menudo quiere verificar que una acción de un usuario en particular provocó un correo electrónico a otro usuario.

Inicia sesión como Robin Hood , envía un correo electrónico a Maid Marion y luego ve a tu bandeja de entrada para verificarlo. Lo que ves en tu bandeja de entrada es algo de [email protected] . ¿Quién diablos es él?

Debe volver a su base de datos para verificar si el correo electrónico fue enviado / recibido por quien lo esperaba. De nuevo, esto es un poco doloroso.

Lo que me gusta hacer es generar el correo electrónico usando el nombre del usuario de fábrica combinado con un número aleatorio. Esto hace que sea mucho más fácil verificar de quién provienen las cosas (y también hace que las colisiones sean increíblemente improbables). Usando la gema Faker ( http://faker.rubyforge.org/ ) para crear los nombres que obtenemos:

Factory.define :user do |user| user.first_name { Faker::Name::first_name } user.last_name { Faker::Name::last_name } user.email {|u| "#{u.first_name}_#{u.last_name}_#{Random.rand(1000).to_s}@factory.com" } end

finalmente, dado que a veces Faker genera nombres que no son amigables con el correo electrónico (Mike O''Donnell), necesitamos .gsub(/[^a-zA-Z1-10]/, '''') en la lista blanca caracteres aceptables: .gsub(/[^a-zA-Z1-10]/, '''')

Factory.define :user do |user| user.first_name { Faker::Name::first_name } user.last_name { Faker::Name::last_name } user.email {|u| "#{u.first_name.gsub(/[^a-zA-Z1-10]/, '''')}_#{u.last_name.gsub(/[^a-zA-Z1-10]/, '''')}_#{Random.rand(1000).to_s}@factory.com" } end

Esto nos da correos electrónicos agradables pero únicos como [email protected] y [email protected]