Skip to content

tristandunn/factory_manager

Repository files navigation

factory_manager Build Status

A factory manager of factory bots.

Why?

Creating a deeply nested set of records for seeding or testing can be difficult. factory_manager creates a DSL based on available factory_bot factories, allowing deeply nested records to be created easily.

Examples

Single-use

Provide a block to build or create to generate the factories within the block.

Create a forum, with a single category, a first post for the category, post with a sequence generated title, an administrative user, and one-hundred approved posts by the administrator. Then create a featured category with a single post.

result = FactoryManager.create do |locals|
  forum do
    category(name: "News") do
      post(title: "First!")
      post(title: generate(:title))

      locals.administrator = user(:admin)

      post(100, :approved, user: locals.administrator)
    end

    featured_category do
      post
    end
  end
end

result.administrator
# => User(id: 1, name: "DHH", admin: true)
result.administrator.posts.count
# => 100
result.administrator.forum.categories.first.posts.count
# => 102
View the factories for the example.
FactoryBot.defined do
  factory :forum do
    name { "Ruby on Rails" }
  end

  factory :category do
    association :forum

    name { "Announcements" }

    factory :featured_category do
      featured { true }
    end
  end

  factory :user do
    association :forum

    name { "DHH" }

    trait :admin do
      admin { true }
    end
  end

  factory :post do
    association :category
    association :user

    title { "How to install Ruby." }

    trait :approved do
      approved { true }
    end
  end

  sequence(:title) { "Title ##{rand}" }
end
View the example with inline comments.
# Starts a manager that will create records. Alternatively use
# +FactoryManager.build+ to build records.
result = FactoryManager.create do |locals|
  # Creates a +Forum+ record using the default attributes from the factory.
  forum do
    # Creates a +Category+ record with the default attributes but overrides the
    # name. The +category.forum+ association will automatically be set to the
    # +Forum+ record created above.
    category(name: "News") do
      # Create a +Post+ record with a custom title, automatically setting the
      # +post.category+ association to the news category created above.
      post(title: "First!")

      # Create a +Post+ record with a sequence generated title, also automatically
      # setting the # +post.category+ association to the news category created above.
      post(title: generate(:title))

      # Create a +User+ record using the +:admin+ trait. The +user.forum+
      # association will automatically be set to the +Forum+ created above but
      # a category will not be assigned. The +locals.administrator+ assignment
      # will result in the user being available on the +result+ object.
      locals.administrator = user(:admin)

      # Create one-hundred +Post+ records using the +:approved+ trait setting
      # the +post.user+ association to the administrator user created above and
      # the +post.category+ to the news category created above.
      post(100, :approved, user: locals.administrator)

      # Create a +Category+ using the +featured_category+ factory and it
      # automatically knows the parent factory is a +category+ to correctly
      # associate child records, such as the single post created in it.
      featured_category do
        post
      end
    end
  end
end

Multi-use

Provide a name and block to register to register a manager, allowing you to provide the name to build and create to generate the factories within the registered block multiple times. Think of it as a factory of factories.

FactoryManager.register(:lumbergh) do |locals|
  locals.user = user do
    tps_report(2, :incomplete)
  end
end

peter = FactoryManager.create(:lumbergh).user
samir = FactoryManager.create(:lumbergh).user

peter.id == samir.id
# => false
peter.tps_reports == samir.tps_reports
# => false
peter.tps_reports.count == samir.tps_reports.count
# => true

License

factory_manager uses the MIT license. See LICENSE for more details.