Building a Ruby on Rails (RoR) API Part III: Factories

Using factories

  ·   4 min read

I already have introduced the RoR API project and told about the models and their tests. Having this logic model set, it’s time to materialize in any way how this is going to be presented to the user. Thus we need test our controllers. In order to achieve this, we have to guarantee we have enough dummy objects to test. But as you can imagine, setting data by our own means can be difficult. We have to use Factories. Let’s check it out.

Factories

To test what’s going to happen with the controllers for real request and the real TDD, we need to set the factories for our entities.

A factory is an instance of a model holding dummy data to test. As we’ve done before, let’s start with the Producer factory:

The Producer Factory

Keeping on mind that the Producer model inherits from the User model, it’s pertinent to check the User factory first.

1# /spec/factories/users.rb
2FactoryGirl.define do
3  factory :user do
4    username {Faker::Internet.user_name}
5    password {Faker::Internet.password(8, 20)}
6    email {Faker::Internet.email}
7  end
8end

Quick explanation. As we only need the username, the password and the email for a user, this factory will take care, precisely, of that, we only have to set the name of the factory (:user) right after the factory reserved word, and populate using Faker. This utility will let you use common data for those purposes. This particular factory will serve as our base for both the Producer and the Warehouse factories. Now let’s review the three factories for producers:

 1# /spec/factories/producers.rb
 2FactoryGirl.define do
 3  factory :producer, class: Producer, parent: :user do
 4    first_name {Faker::Name.first_name}
 5    last_name {Faker::Name.last_name}
 6  end
 7
 8  factory :producer_with_places, class: Producer, parent: :user do |f|
 9    f.first_name {Faker::Name.first_name}
10    f.last_name {Faker::Name.last_name}
11
12    transient do
13      places_count 5
14    end
15
16    f.after(:create) {|pwp, eval|
17      pwp.places << create_list(:producer_place, eval.places_count, localizable: pwp)
18    }
19  end
20
21  factory :producer_products, class: Producer, parent: :user do |f|
22    f.first_name {Faker::Name.first_name}
23    f.last_name {Faker::Name.last_name}
24
25    transient do
26      crops_count 5
27    end
28
29    f.after(:create) {|pp, eval|
30      products = create_list(:product, 10)
31      containers = create_list(:container, 10)
32      crops = Array.new([])
33      eval.crops_count.times do |i|
34        crops.push(create(:crop, producer: pp, product: products.sample, container: containers.sample))
35      end
36      pp.crops << crops
37    }
38  end
39end

Again, this factory is the result of many iterations. When I wrote the initial versions of the API, though I had a clear picture of what it was going to be, I didn’t envision the details. As you can imagine I only wrote the first factory (:producer). The subsequent two factories were the result of walking through the different stages of the API.

What’s common for these three factories is the fact that they have been defined as having a parent (:user). For the first factory it only completes what’s missing from the parent, in this case it adds the values for first_name and last_name.

The second factory (as you might guess from the factory name) creates a producer that has places. The transient value defines 5 places for a :producer_with_places, nonetheless this value can be overwritten when needed. For the list of places the current Producer is associated.

For the third one it’s pretty much a similar procedure.

The Place Factory

Not a hard one, but good to show the difference between places for a producer and places for a warehouse.

 1# /spec/factories/places.rb
 2FactoryGirl.define do
 3  factory :producer_place, class: Place do
 4    tag { Faker::Address.street_name }
 5    lat { Faker::Address.latitude }
 6    lon { Faker::Address.longitude }
 7    localizable { FactoryGirl.create(:producer) }
 8  end
 9
10  factory :warehouse_place, class: Place do
11    tag { Faker::Address.street_name }
12    lat { Faker::Address.latitude }
13    lon { Faker::Address.longitude }
14    localizable { FactoryGirl.create(:warehouse) }
15  end
16end

Notice how the localizable is created depending on the type of place.

The Route Factory

Remember that a Route should have an origin and a destination. It doesn’t matter what type it is. The factory here defines the origin as a :producer_place and the destination as a :warehouse_place.

1# /spec/factories/routes.rb
2FactoryGirl.define do
3  factory :route do
4    origin { FactoryGirl.create(:producer_place) }
5    destination { FactoryGirl.create(:warehouse_place) }
6  end
7end