Tricks and Tips for using Fixtures effectively in Rails

Prathamesh Sonpatki

Prathamesh Sonpatki

September 21, 2014

Recently I gave a talk at RubyKaigi 2014 about Rails fixtures. In this blog post I will be discussing some of the tips and tricks for using fixtures effectively.

_You can also the see the video of this talk

Don't use ids.. unless required

In fixtures, we can specify id of a fixture.

1john:
2  id: 1
3  email: [email protected]

I would recommend not to specify the id. Rails generates the id automatically if we don't explicitly specify it. Moreover there are a few more advantages of not specifying the id.

Stable ids for every fixture.

Rails will generate the id based on key name. It will ensure that the id is unique for every fixture. It can also generate ids for uuid primary keys.

Labeled references for associations like belongs_to, has_many.

Lets say we have a users table. And a user has many cars.

Car ferrari belongs to john. So we have mentioned user_id as 1.

1ferrari:
2  name: ferrari
3  make: 2014
4  user_id: 1

When I'm looking at cars.yml I see user_id as 1. But now I to lookup to see which user has id as 1.

Here is another implementation.

1john:
2  name: john
3  email: [email protected]
4
5ferrari:
6  name: ferrari
7  make: 2014
8  user: john

Notice that I no longer specify user_id for John. I have mentioned name. And now I can reference that name in cars.yml to mention that ferrari belongs to john.

How to set a value to nil from fixture

Let's say that I have a boolean column which is false by default. But for an edge case, I want it to be nil. I can obviously mutate the data generated by fixture before testing. However I can achieve this in fixtures also.

Specify null to make the value nil

1
2require 'yaml'
3YAML.load "--- \n:private: null\n")
4=> {:private=>nil}
5

As you can see above if the value is null then YAML will treat it as nil.

1john:
2  name: john
3  email: [email protected]
4  private: null

Leave the value blank to make the value nil

1
2require 'yaml'
3YAML.load "--- \n:private: \n")
4=> {:private=>nil}
5

As you can see above if the value is blank then YAML will treat it as nil.

1john:
2  name: john
3  email: [email protected]
4  private:

When model name and table name does not match

Generally in Rails, the model name and table name follow a strict convention. Post model will have table name posts. Using this convention, the fixture file for Post models is obviously fixtures/posts.yml.

But sometimes models do not match directly with the table name. This could be because of legacy reason or because of namespacing of models. In such cases automatic detection of fixture files becomes difficult.

Rails provides set_fixture_class method for this purpose. This is a class method which accepts a hash where key should be name of the fixture or relative path to fixture file and value should be model class.

I can use this method inside test_helper.rb in any class inheriting from ActiveSupport::TestCase.

1
2# test_helper.rb
3class ActiveSupport::TestCase
4
5  # table name is "morning_appts". It is being mapped to model "MorningAppointment".
6  self.set_fixture_class morning_appts: MorningAppointment
7
8  # in this case fixture is namespaced
9  self.set_fixture_class '/legacy/users' => User
10
11  # in this case the model is namespaced.
12  self.set_fixture_class outdoor_games: Legacy::OutdoorGame
13end
14

values interpolation using $LABEL

Rails provides many ways to keep our fixtures DRY. Label interpolation is one of them. It allows the use of key of fixture as a value in the fixture. For example:

1john:
2  name: john
3  email: [email protected]

becomes:

1john:
2  name: $LABEL
3  email: [email protected]

$LABEL is not a global variable here. Its just a placeholder. $LABEL is replaced by the key of the fixture. And as discussed earlier the key of the fixture in this case is john. So $LABLE has value john.

Before this PR, I could only use this feature if the value is exactly $LABEL. So if the email is [email protected] I could not use the [email protected]. But after this PR, I can $LABEL anywhere in the string, and Rails will replace it with the key.

So the earlier example becomes:

1john:
2  name: $LABEL
3  email: [email protected]

YAML defaults

I use YAML defaults in database.yml for drying it up and keeping common configuration at one place.

1defaults: &defaults
2  adapter: postgresql
3  encoding: utf8
4  pool: 5
5  host: localhost
6  password:
7
8development:
9  <<: *defaults
10  database: wheel_development
11
12test:
13  <<: *defaults
14  database: wheel_test
15
16production:
17  <<: *defaults
18  database: wheel_production

I can use it for drying up fixtures too for extracting common part in our fixtures.

1DEFAULTS: &DEFAULTS
2  company: BigBinary
3  website: bigbinary.com
4  blog: blog.bigbinary.com
5
6john:
7  <<: *DEFAULTS
8  name: John Smith
9  email: [email protected]
10
11prathamesh:
12  <<: *DEFAULTS
13  name: Prathamesh Sonpatki
14  email: [email protected]

Note the usage of key DEFAULTS for defining default fixture. Rails will automatically ignore any fixture with key DEFAULTS.

If we use any other key then a record with that key will also get inserted in the database.

Database specific tricks

Fixtures bypass the normal Active Record object creation process. After reading them from YAML file, they are inserted into database directly using insert query. So they skip callbacks and validations check. This also has an interesting side-effect which can be used for drying up fixtures.

Suppose we have fixture with timestamp:

1john:
2  name: John Smith
3  email: [email protected]
4  last_active_at: <%= Time.now %>

If I are using PostgreSQL, I can replace the last_active_at value with now:

1john:
2  name: John Smith
3  email: [email protected]
4  last_active_at: now

now is not a keyword here. It is just a string. The actual query looks like this:

1
2INSERT INTO "users"
3("name", "email", "last_active_at", "id")
4VALUES
5('John Smith', '[email protected]', 'now',1144934)
6

So the value for last_active_at is still just now when the query is executed.

The magic starts as PostgreSQL starts reading the values. now is a shorthand for the current timestamp . As soon as Postgres reads it, it replaces now with the current timestamp and the column last_active_at gets populated with current timestamp.

I can also use the now() function instead of just now.

This function is available in PostgreSQL as well as MySQL. So the usage of now() works in both of these databases.

If this blog was helpful, check out our full blog archive.

Stay up to date with our blogs.

Subscribe to receive email notifications for new blog posts.