user2490003 user2490003 - 7 months ago 16
Ruby Question

How to avoid circular creation of associated models in factory_girl?

I have an app where a user can sign in with multiple services, e.g. Google Plus, Facebook, Twitter, etc.

To facilitate this, I have a base

User
model which
has_many
Identity
records.


  • Each
    Identity
    record has a
    provider
    field (e.g.
    "Google"
    ,
    "Facebook"
    , etc...) to indicate what provider is used to sign in.

  • There's an ActiveRecord validation that only lets a user have one of each type of provider. So a user can't have 2
    "Google"
    identities
    .



I set up my factories as follows:

FactoryGirl.define do
factory :user do
sequence(:name) { |n| "Julio Jones-#{n}"}
sequence(:email) { |n| "julio.jones-#{n}@atl.com" }

after(:create) do |user|
create(:identity, user: user)
end
end

factory :identity do
user

provider "Google"
email { user.email }
password "password"
end
end


The
User
model has a callback that creates an
Identity
record. It works great when running

user = FactoryGirl.create(:user)


However, if I create the
identity
instead

identity = FactoryGirl.create(:identity)


the
identity
factory will first try to create a parent
user
, which will in turn create another
identity
. When it finally gets back to creating the
identity
I made the call to, another
identity
already exists with the same
provider
for that
user
and it fails.

Essentially, I need a way for the
after(:create)
callback to NOT trigger when the
user
is being created by the
:identity
factory. Is there a way to tell what made the call to create a particular factory?

Answer

I don't think there's a nice way for a factory to tell that it's been called by another without collaboration. (You can always inspect caller_locations, but that's not nice.) Instead, have one factory tell the other to behave differently using a transient attribute:

FactoryGirl.define do
  factory :user do
    transient do
      create_identity true
    end

    after(:create) do |user, evaluator|
      if evaluator.create_identity
        create(:identity, user: user)
      end
    end

  end

  factory :identity do
    association :user, factory: :user, create_identity: false
  end

end