FactoryGirl, has_one association and validation failed
Asked Answered
S

4

21

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?

Susquehanna answered 10/6, 2013 at 23:29 Comment(2)
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.Arathorn
Factory's associations stand for foreign_keys and not for has_one/has_many associations. Am I wrong?Susquehanna
S
36

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.

Susquehanna answered 13/6, 2013 at 20:13 Comment(3)
other callbacks include after(:create) and after(:stub) which are mentioned at robots.thoughtbot.com/aint-no-calla-back-girlDuly
@MichDart I couldn't see this working. I assigned it in build callback, printed in before create call back., object is not there. Is it updated?Patience
@7H3IN5ID3R, first I couldn't get it working as well... Check that your validation does not include _id at the end. E.g, validates :asset, presence: true instead of validates :asset_id, presence: true.Heathenish
K
2

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.

Korwin answered 11/6, 2013 at 2:55 Comment(1)
Thank you. I've updated the first post. But I'm still in trouble. Can you help me?Susquehanna
B
0

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)
Boadicea answered 11/6, 2013 at 2:42 Comment(4)
Thanks, but does your solution create an asset with the same fields I've defined into my Asset factory?Susquehanna
If you want it to create an asset with the same fields defined in your asset factory: asset { FactoryGirl.create(:asset) }Boadicea
Please take a look at the last update above... I still encounter errorsSusquehanna
Does your Asset factory create a Post model? If so you'll end up in an infinite loop.Boadicea
B
0

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

Brassiere answered 11/6, 2013 at 2:43 Comment(1)
I've tried that solution, but didn't work. It creates a loop (I think). Have you tried on your own? thanks anywaySusquehanna

© 2022 - 2024 — McMap. All rights reserved.