通过factory_girl协会查找或创建记录

Slo*_*vic 60 ruby cucumber ruby-on-rails-3 factory-bot

我有一个属于一个组的用户模型.组必须具有唯一的名称属性.用户工厂和组工厂定义为:

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

Factory.define :group do |f|
  f.name "default"
end
Run Code Online (Sandbox Code Playgroud)

创建第一个用户时,也会创建一个新组.当我尝试创建第二个用户时,它失败了,因为它想要再次创建相同的组.

有没有办法告诉factory_girl关联方法首先查看现有记录?

注意:我确实尝试定义一个方法来处理这个,但后来我不能使用f.association.我希望能够在像这样的Cucumber场景中使用它:

Given the following user exists:
  | Email          | Group         |
  | test@email.com | Name: mygroup |
Run Code Online (Sandbox Code Playgroud)

这只有在工厂定义中使用关联时才有效.

Fer*_*ida 103

你可以用initialize_withfind_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
Run Code Online (Sandbox Code Playgroud)

它也可以与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
Run Code Online (Sandbox Code Playgroud)

对于Rails 4

Rails 4中的正确方法是Group.find_or_create_by(name: name),所以你要使用

initialize_with { Group.find_or_create_by(name: name) } 
Run Code Online (Sandbox Code Playgroud)

代替.

  • Rails 4中的首选方法实际上是`Group.where(name:name).first_or_create`. (20认同)
  • 这很好.但我建议`find_or_initialize_by`所以它适用于FactoryGirl的`build`. (7认同)
  • 工作得非常好,谢谢.在Rails 4中,首选方式是:`Group.find_or_create_by(name:name)` (6认同)
  • 这通常不起作用:Factory Girl重置创建的模型.1.`create(:user)`2.`Group.first.update_attributes(name:"new name")`3.`create(:user)`.现在`Group.first.name =="name"`为真,步骤3重置步骤2.在复杂的黄瓜设置中,这很容易发生.有什么建议吗? (3认同)
  • 我将扩展@Kelvin的答案,并提到执行此操作的_safe_方法也是传递原始属性,因此它们不会被初始化为`nil`:`Model.where(name:name). first_or_initialize(属性)` (2认同)

Slo*_*vic 17

我最终使用了网络上发现的混合方法,其中一种是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
Run Code Online (Sandbox Code Playgroud)


Joe*_*eng 7

为了确保 FactoryBot 的行为build仍然create正常,我们应该只覆盖 , 的逻辑create,方法是:

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

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

  name { "default" }
end
Run Code Online (Sandbox Code Playgroud)

这确保build维持“构建/初始化对象”的默认行为,并且不执行任何数据库读取或写入,因此它总是很快。仅覆盖 的逻辑create以获取现有记录(如果存在),而不是尝试始终创建新记录。

我写了一篇文章解释这一点。


Hia*_*nho 6

您还可以使用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
Run Code Online (Sandbox Code Playgroud)

你可以使用这个要点.然后执行以下操作

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

  factory :user do
    association :group, factory: :group, strategy: :find_or_create, name: "name"
  end
end
Run Code Online (Sandbox Code Playgroud)

不过,这对我有用.


L.Y*_*oul 6

我遇到了类似的问题并提出了这个解决方案。它按名称查找组,如果找到,则将用户与该组关联起来。否则,它会使用该名称创建一个组,然后与其关联。

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

我希望这对某人有用:)