Search code examples
ruby-on-railsfactory-bot

Add recriprocal association to objects built using FactoryBot


I'm trying to clean up some tests and get them to run a little faster, and I have bunch of FactoryBot generated items which are currently created and persisted to the database. Obviously this isn't good for performance, so I'm trying to change the bulk of them over to use build rather than create to avoid the DB bottleneck. My current problem has to do with getting the objects to have a working association. What I have currently:

class User < ApplicationRecord
  has_many :transaction_items,
           class_name: 'Transaction',
           foreign_key: 'user_id'
end

class Transaction < ApplicationRecord
  belongs_to :user, class_name: 'User'
end

And the tests use these classes like so:

@user = create(:user)

@transaction1 = create(:transaction, user: @user)
@transaction2 = create(:transaction, user: @user,)

I'm fine with the User object being created and saved to the DB, as I need it to be available for authentication purposes. I need to change the Transaction objects to be instantiated using build. If I make that change the Transaction objects are aware of their associated User object (@transaction1.user returns the User instance) but the inverse is not true, i.e. @user.transaction_items does not return the associated Transaction objects. It does return a Transaction::ActiveRecord_Associations_CollectionProxy object but it is an empty collection. This is causing a failure because the code under tests makes use of the #transaction_items method and it needs to have the associated objects in it.

I'm far from an expert in FactoryBot, and while there is a ton of great information out there on FactoryBot associations for some reason I've not been able to get the association to work properly. What do I need to do here to get this kind of more complex association working in my factories?

Using Rails 5.1, FactoryBot v5.0.2


Solution

  • That's easy

    @user = create(:user)
    
    def build_transaction(user)
      user.transaction_items.build(attributes_for(:transaction, user: user))
    end
    
    @transaction1 = build_transaction(@user)
    @transaction2 = build_transaction(@user)