Подтвердить что ты не робот

Rails: избегайте ошибок дублирования в Factory Девушка... я делаю это неправильно?

Предположим, что у меня есть модель "пользователь", которая имеет ограничение уникальности в поле "электронная почта"

Если я вызову Factory (: user), как только все будет хорошо, но если я его повторю во второй раз, он потерпит неудачу с ошибкой "запись уже существует".

В настоящее время я использую простой помощник для поиска существующей записи в БД до создания factory... и вызова любого factory, который я делаю через этого помощника.

Это работает, но это не совсем изящно, и учитывая, насколько я предполагаю, что эта проблема должна быть, я предполагаю, что там лучшее решение.... Так есть встроенный способ в factory girl to return_or_create a factory вместо того, чтобы просто заряжать вперед с помощью create()? Если нет, то как большинство людей избегают дублирования записей со своими фабриками?

4b9b3361

Ответ 1

Простой ответ: используйте factory.sequence

Если у вас есть поле, которое должно быть уникальным, вы можете добавить последовательность в factory_girl, чтобы гарантировать, что она никогда не будет одинаковой:

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

Это будет увеличивать n каждый раз, чтобы создать уникальный адрес электронной почты, такой как `user52 @factory.com. (См. https://github.com/thoughtbot/factory_girl/wiki/Usage для получения дополнительной информации)

Однако это не всегда удобно в Rails.env.development...

Со временем я обнаружил, что это не самый полезный способ создания уникальных адресов электронной почты. Причина в том, что, хотя factory всегда уникален для вашей тестовой среды, он не всегда уникален для вашей среды разработки, а n сбрасывается при запуске среды вверх и вниз. В :test это не проблема, потому что база данных стирается, но в :development вы продолжаете иметь тенденцию к тем же данным некоторое время.

Затем вы получаете столкновение и обнаруживаете, что вам нужно вручную переопределить электронное письмо на то, что вы знаете, уникальное, что раздражает.

Часто полезно: используйте случайное число

Поскольку я вызываю u = Factory :user из консоли на регулярной основе, я вместо этого запускаю случайное число. Вы не можете избежать столкновений, но на практике это почти никогда не происходит:

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

N.B. Вы должны использовать Random.rand вместо rand() из-за столкновения (ошибка?) В FactoryGirl [https://github.com/thoughtbot/factory_girl/issues/219](see здесь).

Это освобождает вас от необходимости создавать пользователей из командной строки независимо от того, есть ли в базе данных уже factory сгенерированные пользователи.

Дополнительно для упрощения проверки электронной почты

Когда вы проходите тестирование электронной почты, вы часто хотите проверить, что действие определенного пользователя вызвало электронное письмо другому пользователю.

Войдите в систему как Robin Hood, отправьте электронное письмо на адрес Maid Marion, а затем перейдите в свой почтовый ящик, чтобы проверить его. То, что вы видите в своем почтовом ящике, есть что-то от [email protected]. Кто это, черт возьми?

Вам нужно вернуться в свою базу данных, чтобы проверить, было ли отправлено/получено электронное письмо тем, кем вы ожидали. Опять это немного боль.

Вместо этого я хотел бы создать электронное письмо, используя имя пользователя factory в сочетании со случайным числом. Это значительно упрощает проверку того, что происходит (а также делает конфликты исчезающе маловероятными). Используя камень Faker (http://faker.rubyforge.org/), чтобы создать имена, которые мы получаем:

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

Наконец, поскольку Faker иногда генерирует имена, которые не являются дружественными к электронной почте (Майк О'Доннелл), нам нужно присваивать белый список приемлемым символам: .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

Это дает нам персонифицированные, но уникальные электронные письма, такие как [email protected] и [email protected]

Ответ 2

Вот что я делаю, чтобы заставить "n" в моей девушковой последовательности factory быть таким же, как этот идентификатор объекта, и тем самым избежать столкновений:

Во-первых, я определяю метод, который находит, что следующий идентификатор должен быть в app/models/user.rb:

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

Затем я вызываю User.next_id из spec/factories.rb, чтобы начать последовательность:

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

Ответ 3

Я нашел это хорошим способом убедиться, что тесты всегда будут проходить. В противном случае вы не можете быть уверены, что в 100% случаев вы создадите уникальное электронное письмо.

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

Ответ 4

Если вам нужно всего лишь создать несколько значений для атрибутов, вы также можете добавить метод в String, который отслеживает предыдущие строки, используемые для атрибута. Затем вы можете сделать что-то вроде этого:

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

Я использую этот подход для посева. Я хотел избежать порядковых номеров, потому что они не выглядят реалистично.

Здесь расширение строки, которое делает это:

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

Внимание: это не должно использоваться для множества атрибутов, так как мы отслеживаем все предыдущие строки (возможно оптимизация).