Factory_bot: Documentation for creating has_many associations

Created on 19 Sep 2011  路  5Comments  路  Source: thoughtbot/factory_bot

I'm sorry to have to revert to the issues list here, but I can't for the life of me find any documentation on creating has_many relationships in FG 2.0. After some experimentation I found the solution below, but I don't know if this is the "right" way to do it:

class Post < ActiveRecord::Base
  has_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :post
end

FactoryGirl.define do
  factory :comment do
    body 'This is a comment'
  end

  factory :post do
    title 'This is a Post'
    comments { FactoryGirl.create_list(:comment, 2) }
    # or, to create only one =>  comments { [FactoryGirl.create(:comment)] }
  end
end

Factory.create :post

One problem is that I won't know if you called build or create when you factoried (nice, right?) your post, but this will always create the comments. So I played with the syntax a bit to get association in there instead and came up with this:

  factory :post do
    title 'This is a Post'
    comments { [association(:comment)] }
  end

Is that the right way to do it? GETTING_STARTED has a great reference for just about everything you can do with FG except this one case. Thanks!

Most helpful comment

Creating records for a has_many association is sort of tricky, for a couple reasons:

First, there's always a chicken-and-egg problem doing this in a relational database, because one record has to exist before the other. You can't create records in separate tables simultaneously, and one table has to hold the primary key of the other.

Second, there's no knowing what kind of validations or callbacks are in place on the models in question, so it's hard to know if the collection needs to be saved or unsaved.

If you're validating that at least member is present in the collection, you can try passing in a list of unsaved records (using Factory.build).

If you're not validating anything about the collection, I'd recommend doing it in an after_create callback.

All 5 comments

By the way, if that later method is the correct one I'd be happy to update the docs myself an issue a pull request.

+1 something like this is needed

Creating records for a has_many association is sort of tricky, for a couple reasons:

First, there's always a chicken-and-egg problem doing this in a relational database, because one record has to exist before the other. You can't create records in separate tables simultaneously, and one table has to hold the primary key of the other.

Second, there's no knowing what kind of validations or callbacks are in place on the models in question, so it's hard to know if the collection needs to be saved or unsaved.

If you're validating that at least member is present in the collection, you can try passing in a list of unsaved records (using Factory.build).

If you're not validating anything about the collection, I'd recommend doing it in an after_create callback.

After talking about this with @jferris, it seems like the ideal syntax may be something like this:

FactoryGirl.define do
  factory :user, :aliases => [:author]

  factory :comment do
    author
    post

    factory :old_comment do
      created_at { 1.year.ago }
    end
  end

  factory :post do
    factory :commented_post do
      comments(2) # this would create two `:comment`s
      # alternatively...
      comments # this would create one `:comment`
      old_comments(2, :body => 'something static') # this would create two `:comment`s, both with body of 'something static'
      old_comments # this would create one `:old_comment`
    end
  end
end

I've added more documentation in this commit: https://github.com/thoughtbot/factory_girl/commit/9c6c252d0fb58ba6321d8db56778bb3469ff5a77

It includes tests (since they're a great form of documentation) but the primary example is in the GETTING_STARTED document.

While it's beneficial to discuss syntactical improvements and ways to make this more straightforward, I think that's better handled in a separate pull request when an implementation is started; for now, this focuses on documentation and this commit should handle that request.

Thanks everyone for their contribution to the discussion!

Was this page helpful?
0 / 5 - 0 ratings