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

Найти или создать запись через ассоциацию factory_girl

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

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

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

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

Можно ли указать метод ассоциации factory_girl для поиска первой существующей записи?

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

Given the following user exists:
  | Email          | Group         |
  | [email protected] | Name: mygroup |

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

4b9b3361

Ответ 1

В итоге я использовал сочетание методов, найденных вокруг сети, один из которых был унаследованными фабриками, предложенными 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

Ответ 2

Вы можете использовать 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) } 

вместо.

Ответ 3

Вы также можете использовать стратегию 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

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

Ответ 4

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

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

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

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

Given a user exists with email: "[email protected]"
And a group exists with name: "default"
And the user: "[email protected]" has joined that group
When somethings happens....

Ответ 5

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

Given the following user exists:
  | Email          | Group         |
  | [email protected] | Name: mygroup |

Вы можете расширить его, как:

Given the following user exists:
  | Email          | Group         |
  | [email protected] | Name: mygroup |
  | [email protected]  | Name: mygroup |
  | [email protected]  | Name: mygroup |

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

Ответ 6

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

Чтобы FactoryBot build и create по-прежнему вели себя должным образом, мы должны только переопределить логику 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 переопределяется для извлечения существующей записи, если она существует, вместо попытки всегда создавать новую запись.

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

Ответ 7

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

factory :user do
  group { Group.find_by(name: 'unique_name') || FactoryBot.create(:group, name: 'unique_name') }
end

Я надеюсь, что это может быть полезным для кого-то :)