Найти или создать запись через ассоциацию factory_girl - PullRequest
59 голосов
/ 22 августа 2011

У меня есть модель пользователя, принадлежащая группе. Группа должна иметь уникальный атрибут имени. Фабрика пользователя и фабрика группы определены как:

Factory.define :user do |f|
  f.association :group, :factory => :group
  # ...
end

Factory.define :group do |f|
  f.name "default"
end

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

Есть ли способ указать методу ассоциации factory_girl, чтобы он сначала искал существующую запись?

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

Given the following user exists:
  | Email          | Group         |
  | test@email.com | Name: mygroup |

, и это может работать, только если в определении Фабрики используется ассоциация.

Ответы [ 6 ]

101 голосов
/ 03 августа 2012

Вы можете использовать initialize_with с find_or_create методом

FactoryGirl.define do
  factory :group do
    name "name"
    initialize_with { Group.find_or_create_by_name(name)}
  end

  factory :user do
    association :group
  end
end

Также можно использовать с id

FactoryGirl.define do
  factory :group do
    id     1
    attr_1 "default"
    attr_2 "default"
    ...
    attr_n "default"
    initialize_with { Group.find_or_create_by_id(id)}
  end

  factory :user do
    association :group
  end
end

Для Rails 4

В Rails 4 правильный путь - Group.find_or_create_by(name: name), поэтому вместо него следует использовать

initialize_with { Group.find_or_create_by(name: name) } 

.

16 голосов
/ 27 августа 2011

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

Я сделал следующее:

# in groups.rb factory

def get_group_named(name)
  # get existing group or create new one
  Group.where(:name => name).first || Factory(:group, :name => name)
end

Factory.define :group do |f|
  f.name "default"
end

# in users.rb factory

Factory.define :user_in_whatever do |f|
  f.group { |user| get_group_named("whatever") }
end
6 голосов
/ 05 января 2015

Вы также можете использовать стратегию FactoryGirl для достижения этой цели

module FactoryGirl
  module Strategy
    class Find
      def association(runner)
        runner.run
      end

      def result(evaluation)
        build_class(evaluation).where(get_overrides(evaluation)).first
      end

      private

      def build_class(evaluation)
        evaluation.instance_variable_get(:@attribute_assigner).instance_variable_get(:@build_class)
      end

      def get_overrides(evaluation = nil)
        return @overrides unless @overrides.nil?
        evaluation.instance_variable_get(:@attribute_assigner).instance_variable_get(:@evaluator).instance_variable_get(:@overrides).clone
      end
    end

    class FindOrCreate
      def initialize
        @strategy = FactoryGirl.strategy_by_name(:find).new
      end

      delegate :association, to: :@strategy

      def result(evaluation)
        found_object = @strategy.result(evaluation)

        if found_object.nil?
          @strategy = FactoryGirl.strategy_by_name(:create).new
          @strategy.result(evaluation)
        else
          found_object
        end
      end
    end
  end

  register_strategy(:find, Strategy::Find)
  register_strategy(:find_or_create, Strategy::FindOrCreate)
end

Вы можете использовать эту суть .И затем сделайте следующее

FactoryGirl.define do
  factory :group do
    name "name"
  end

  factory :user do
    association :group, factory: :group, strategy: :find_or_create, name: "name"
  end
end

Это работает для меня, хотя.

2 голосов
/ 25 августа 2011

Обычно я просто делаю несколько фабричных определений. Один для пользователя с группой и один для пользователя без группы:

Factory.define :user do |u|
  u.email "email"
  # other attributes
end

Factory.define :grouped_user, :parent => :user do |u|
  u.association :group
  # this will inherit the attributes of :user
end

Тогда вы можете использовать их в своих определениях шагов для отдельного создания пользователей и групп и объединения их по желанию. Например, вы можете создать одного сгруппированного пользователя и одного одиночного пользователя и присоединить одинокого пользователя к группе сгруппированных пользователей.

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

Given a user exists with email: "hello@email.com"
And a group exists with name: "default"
And the user: "hello@gmail.com" has joined that group
When somethings happens....
0 голосов
/ 19 марта 2019

В последнее время я сталкивался с подобной проблемой, и вот что я пытался.

Чтобы гарантировать, что build и create FactoryBot по-прежнему ведут себя как следует, мы должны только переопределить логику createделать:

factory :user do
  association :group, factory: :group
  # ...
end

factory :group do
  to_create do |instance|
    instance.attributes = Group.find_or_create_by(name: instance.name).attributes
    instance.reload
  end

  name { "default" }
end

Это гарантирует, что build поддерживает поведение по умолчанию «построение / инициализация объекта» и не выполняет чтение или запись в базу данных, поэтому это всегда быстро.Только логика create переопределяется для извлечения существующей записи, если она существует, вместо того, чтобы пытаться всегда создавать новую запись.

Я написал статью , объясняющую это.

0 голосов
/ 17 февраля 2012

Я использую точно сценарий Cucumber, который вы описали в своем вопросе:

Given the following user exists:
  | Email          | Group         |
  | test@email.com | Name: mygroup |

Вы можете расширить его следующим образом:

Given the following user exists:
  | Email          | Group         |
  | test@email.com | Name: mygroup |
  | foo@email.com  | Name: mygroup |
  | bar@email.com  | Name: mygroup |

Это создаст 3 пользователей с группой "моя группа".При таком использовании используется функция 'find_or_create_by', при первом вызове создается группа, при следующих двух вызовах - уже созданная группа.

...