如何在 Factory Girl 中创建 has_and_belongs_to_many 关联

125

考虑以下情况

class User < ActiveRecord::Base
  has_and_belongs_to_many :companies
end

class Company < ActiveRecord::Base
  has_and_belongs_to_many :users
end

如何为公司和用户定义具有双向关联的工厂?这是我的尝试

Factory.define :company do |f|
  f.users{ |users| [users.association :company]}
end

Factory.define :user do |f|
  f.companies{ |companies| [companies.association :user]}
end

现在我尝试

Factory :user

或许并不令人意外,这将导致无限循环,因为工厂递归地使用彼此来定义自己。

更令人惊讶的是,我在任何地方都没有找到如何解决此问题的提及,是否存在定义必要工厂的模式,或者我正在做一些根本性错误?

11个回答

136

以下是对我有效的解决方案。

FactoryGirl.define do

  factory :company do
    #company attributes
  end

  factory :user do
   companies {[FactoryGirl.create(:company)]}
   #user attributes
  end

end

如果您需要特定的公司,您可以使用工厂模式来实现。

company = FactoryGirl.create(:company, #{company attributes})
user = FactoryGirl.create(:user, :companies => [company])

希望这对某个人有所帮助。


4
谢谢,你是解决方案中最整洁的。 - Mik
谢谢。这解决了我几个小时的困扰。 - Tony Beninate
只有当所有的工厂都在一个文件中时,这个方法才对我有效,但这是相当不可取的。因此,@opsb下面提到的解决方案似乎更好。 - spier

40

38
这个链接实际上没有说明如何处理has_and_belongs_to_many... 我不知道该怎么做... - dmonopoly
3
现在在Factory Girl中,回调语法已更改为 after(:create) 而非 after_create,如此处所述: https://dev59.com/-WUp5IYBdhLWcg3w1qMi - Michael Yagudaev

22

我认为,只需要创建两个不同的工厂:

 Factory.define :user, :class => User do |u|
  # 普通属性初始化
 end
Factory.define :company, :class => Company do |u| # 普通属性初始化 end

当你编写用户测试用例时,只需像这样编写:

 Factory(:user, :companies => [Factory(:company)])

希望这能起作用。


2
谢谢,这是我唯一能够运行的示例。Factory Girl 对于 habtm 来说是一个大头痛。 - jspooner
这个在最新版本的FactoryGirl中不再适用(我想到了Rails 3)。 - Raf

9

我在提供的网站上找不到上述情况的示例(只有1:N和多态关联,但没有habtm)。 我有一个类似的案例,我的代码如下:

Factory.define :user do |user|
 user.name "Foo Bar"
 user.after_create { |u| Factory(:company, :users => [u]) }
end

Factory.define :company do |c|
 c.name "Acme"
end

4
如果存在非零用户数量的验证,会怎样? - dfens

5
我成功的方法是在使用工厂时设置关联。使用你的例子:
user = Factory(:user)
company = Factory(:company)

company.users << user 
company.save! 

4

我觉得这个方法非常易于理解:

FactoryGirl.define do
  factory :foo do
    name "Foo" 
  end

  factory :bar do
    name "Bar"
    foos { |a| [a.association(:foo)] }
  end
end

1
foos { |a| [a.association(:foo)] } 对我很有帮助!谢谢! - monteirobrena

3
  factory :company_with_users, parent: :company do

    ignore do
      users_count 20
    end

    after_create do |company, evaluator|
      FactoryGirl.create_list(:user, evaluator.users_count, users: [user])
    end

  end

警告:对于Ruby 1.8.x,请将用户更改为:users => [user]


4
难道不应该是这样的吗: after_create { |company, evaluator| FactoryGirl.create_list(:user, evaluator.users_count, companies: [company]) } - Raf

1
我使用 traits 和回调函数来处理 HABTM。
假设你有以下模型:
class Catalog < ApplicationRecord
  has_and_belongs_to_many :coursesend
class Course < ApplicationRecordend

您可以在上面定义工厂:
FactoryBot.define do
  factory :catalog do
    description "Catalog description"
    …

    trait :with_courses do
      after :create do |catalog|
        courses = FactoryBot.create_list :course, 2

        catalog.courses << courses
        catalog.save
      end
    end
  end
end

0

关于Rails 5的更新:

不再使用has_and_belongs_to_many关联,建议使用has_many :through关联。

这个关联的用户工厂看起来像这样:

FactoryBot.define do
  factory :user do
    # user attributes

    factory :user_with_companies do
      transient do
        companies_count 10 # default number
      end

      after(:create) do |user, evaluator|
         create_list(:companies, evaluator.companies_count, user: user)
      end
    end
  end
end

你可以以类似的方式创建公司工厂。

一旦两个工厂都设置好了,你就可以使用companies_count选项创建user_with_companies工厂。在这里,您可以指定用户所属的公司数量:create(:user_with_companies, companies_count: 15)

您可以在factory girl associations here找到有关详细说明。


0
首先,我强烈建议您使用has_many:through而不是habtm(有关此内容的更多信息在此处),这样您最终会得到类似于以下内容的东西:
Employment belongs_to :users
Employment belongs_to :companies

User has_many :employments
User has_many :companies, :through => :employments 

Company has_many :employments
Company has_many :users, :through => :employments

完成后,您将在双方都拥有 has_many 关联,并且可以像之前一样在 factory_girl 中进行分配。


3
这句话的翻译如下:这句话的翻译如下:“这应该是 Employment belongs_to :userEmployment belongs_to :company,而连接一个公司和一个用户的联接模型则会连接它们。” - Daniel Beardsley
5
从我快速阅读你提到的帖子中得出的结论是,选择habtm或has_many:through取决于您的使用情况。 没有真正的“赢家”。 - auralbee
使用hmt时唯一的开销是必须在关联表上定义id。目前,我无法想象任何可能导致问题的情况。我并不是说habtm没有用处,只是在99%的情况下,使用hmt更有意义(因为它具有优势)。 - Milan Novota
6
仅仅因为 HMT 有更多的“优势”并不意味着你应该使用它,除非你确实需要这些优势。我现在正在处理一个项目,开发人员在几个情况下使用了 HMT,而使用 HABTM 就足够了。由于此原因,代码库变得更大、更复杂、不太直观,并且 SQL 连接速度较慢。因此,当可以使用 HABTM 时,请使用它;只有当你需要创建一个单独的连接模型来存储每个关联的额外信息时,请使用 HMT。 - sbeam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接