ruby-on-railsrubyfabrication-gem

How to refer to the object I'm Fabricating inside my Fabricator?


I'm using Ruby 2.1.1p76 and Rails 4.0.4 and the Fabrication gem.

Is it possible to refer to the object currently being fabricated?

I have a class Foo and a class Bar. I have fabricators for each. My problem is that each of class Foo and Bar contain a field that refers to the other class:

class Foo < ActiveRecord::Base
  has_many :bars
  belongs_to :current_bar, class_name: "Bar"
end

class Bar < ActiveRecord::Base
  belongs_to: :foo
end

It's bothersome to have to fabricate one, then the other and then set the reference for the first in my specs:

let!( :foo ) { Fabricate( :foo ) }
let!( :bar ) { Fabricate( :bar, foo: foo ) }

before( :each ) do
  foo.update( current_bar: bar )
end 

I'd much rather just fabricate a Foo and have its current_bar fabricated and already referring to the Foo I'm fabricating. I've read through the fabrication gem documentation and I can't find any way that this is possible. I may just be overlooking it. Does anyone know of a way to accomplish this?

For completeness -- fabricators:

Fabricator( :foo ) do
  current_bar nil
end

Fabricator( :bar ) do
  foo
end

Solution

  • Yup. overlooked it in the documentation.


    You can define them in your Fabricators as a block that optionally receives the object being fabricated and a hash of any transient attributes defined. As with anything that works in the Fabricator, you can also define them when you call Fabricate and they will work just like you’d expect. The callbacks are also stackable, meaning that you can declare multiple of the same type in a fabricator and they will not be clobbered when you inherit another fabricator.

    Fabricator(:place) do
      before_validation { |place, transients| place.geolocate! }
      after_create { |place, transients| Fabricate(:restaurant, place: place) }
    end
    

    Also, in my case, I needed to use the after_save callback. I was able set the current_bar on my foo object inside the fabricator, but once in the spec, the current_bar was still nil. The update method isn't available inside after_create (I'm new to ruby so I'm not sure why), but it is available inside after_save. Calling update got me going.

    Fabricator(:foo) do
    
      transient :current_bar_data
    
      after_save { |foo, transients|
        bar = Fabricate( :bar, foo: foo, bar_data: transients[ :current_bar_data ] )
        foo.update( current_bar: bar )
      }
    
      current_bar nil
    end
    

    Now I can fabricate a foo complete with current_bar for my specs:

    let!( :some_foo ) { Fabricate( :foo, current_bar_data: "some bar data" ) }