20
votes

These are 2 simple models:

class Post < ActiveRecord::Base
  has_one :asset, :dependent => :destroy

  validates :asset, presence: true
end

class Asset < ActiveRecord::Base
  belongs_to :post
end

I'm trying to create a factory like this:

  factory :post do
    # fields...

    asset { FactoryGirl.create(:asset) }
  end

  factory :asset do
    # fields...

    post
  end

But, running the spec it enters a loop.

I've also tryied this:

  factory :post do
    # fields...

    before(:create) do |post, evaluator|
      FactoryGirl.create_list(:asset, 1, post: post)
    end
  end

But ended up in "Validation failed: Asset can't be blank".

How do I represent my situation?

4
Is there any reason you're setting post on :asset instead of the other way around? Seems weird since Post is the one requiring an Asset during creation.deefour
Factory's associations stand for foreign_keys and not for has_one/has_many associations. Am I wrong?Mich Dart

4 Answers

35
votes

I solved this problem using after(:build) callback.

factory :post do
    # fields...
    after(:build) do |post|
      post.asset ||= FactoryGirl.build(:asset, :post => post)
    end
end

factory :asset do
    # fields...
    after(:build) do |asset|
      asset.post ||= FactoryGirl.build(:post, :asset => asset)
    end
end

By this way, the associated objects will be created before the owning class is saved, so validation pass.

2
votes

The validation is failing because when FactoryGirl creates a Post, an asset must be present. So in your FactoryGirl definitions you can create an Asset as part of creating a Post. Insert something like the FactoryGirl post.rb file:

asset { FactoryGirl.create(:asset) }

or

You can create an Asset as part of your Post declaration in your spec file such as the following:

asset = FactoryGirl.create(:asset)

FactoryGirl.create(:post, :asset => asset)

Thanks.

0
votes

You can preload a child association by passing it in as follows:

FactoryGirl.define do
  factory :post do
    asset { Asset.create! }
  end
end

(Better still, using the Asset factory to generate it's associated asset with details pre-set).

The other manual way would be to create a Asset via FactoryGirl.create(:asset), and passing it into the variable creation, i.e.:

asset = FactoryGirl.create(:asset)
post = FactoryGirl.create(:post, asset: asset)
0
votes

the error Validation failed: Asset can't be blank is because it looks like you have the association backwards in your factories.

factory :post do
  # fields...
end

So when you create a post there is no asset so the validation fails. Try this

factory :post do
# fields...
  asset
end

Take a look at this wiki page and the associations section. It also explains the difference between create and build with associations