How can I keep state between Fabricators ?

77 views
Skip to first unread message

Benjamin Thomas

unread,
Aug 17, 2013, 4:50:03 AM8/17/13
to fabrica...@googlegroups.com
Hi,

Thanks for the great gem!

I'm looking for a way to seed my Fabricators with custom data, stored as an array. The problem is I'd like one line of sample data to be dispatch between different Fabricators (due to database normalization).

In short here is what I'm trying to do:

 1 Fabricator(:machine) do
 2   transient sample_line: FakeData::Machine.new.random_line
 
 4   internal_name { |attrs| attrs[:sample_line][1] }
 5   commercial_name { |attrs| attrs[:sample_line][2] }
 6 end   
 7     
 8 Fabricator(:valid_machine, from: :machine) do
 9   brand do
10     Fabricate.build(:valid_brand) do
11       name { |attrs| attrs[:sample_line][0] }
12     end
13   end
14 end

The problem with this approach is that once the transient attribute is loaded, line 2 will not be re-evaluated. I can't pass a lambda as a transient attribute either.

Ideally I would like to have my own object instance passed around the fabricators. That way I could call methods inside each block definitions such as :

Fabricator(:example) do
  transient sample: -> { FakeData::RandomItem.new }
  name { |attrs| attrs[:sample].name }
  speed { |attrs| attrs[:sample].speed }
end

Another approach I took is too use an external module to keep track of which line of sample data is currently being used, here:

 1 Fabricator(:machine) do
 2   internal_name { FakeData::Machine.current_sample[1] } # On the first call I grab a random line
 3   commercial_name { FakeData::Machine.current_sample[2] }
 4   
 5   after_build { FakeData::Machine.delete_current_sample }
 6 end
 
 8 Fabricator(:valid_machine, from: :machine) do
 9   brand do
10     Fabricate.build(:valid_brand) do
11       name { FakeData::Machine.current_sample[0] }
12     end
13   end
14 end

Here I'm just grabbing a random line of sample data, then deleting it from the a array since I don't want to use that line twice in my tests.
This works however it's pretty messy and doesn't feel right.

Is there some other means to achieve this ? Thanks for your input !

Paul Elliott

unread,
Aug 17, 2013, 9:30:36 AM8/17/13
to fabrica...@googlegroups.com
If you break the transient declaration and default into separate lines, then it will be evaluated with each call to fabricate.

Fabricator(:example) do
  transient :sample
  sample { FakeData::RandomItem.new }
  name { |attrs| attrs[:sample].name }
  speed { |attrs| attrs[:sample].speed }
end

Let me know if that works for you.

-- Paul

--
You received this message because you are subscribed to the Google Groups "fabrication" group.
To unsubscribe from this group and stop receiving emails from it, send an email to fabricationge...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Benjamin Thomas

unread,
Aug 17, 2013, 4:31:08 PM8/17/13
to fabrica...@googlegroups.com
Thanks so much!

Of course it totally makes sense. Somehow I forgot that I was actually setting a *default* value.
For anyone interested and future reference, here is a basic working snippet. The tricky part is "grabbing" the parrent attributes (here machine_attrs) at the right place when inheriting from another Fabricator.

   1 Fabricator(:machine) do
   2   transient :seed
   3 
   4   seed { ["brand_of_item", "name_of_item"] }
   5   internal_name { |attrs| attrs[:seed][1] }
   6 end
   7 
   8 Fabricator(:valid_machine, from: :machine) do
   9   brand do |machine_attrs|
  10     Fabricate.build(:valid_brand) do
  11       name { machine_attrs[:seed][0] }
  12     end
  13   end
  14 end


Reply all
Reply to author
Forward
0 new messages