justin․searls․co

Fix your Rails Fixtures with this one neat trick

If you have any Rails models that define a custom table_name AND you load fixtures in your test database, then you're probably going to have a bad time. Maybe you're here from Google. If so, hi, hello! You're in the right place.

Here's the model I just ran into this issue with:

# app/models/build/program.rb
module Build
  class Program < ApplicationRecord
    self.table_name = "build_programs"
  end
end

And its corresponding fixture file:

# test/fixtures/build_programs.yml
first:
  started_on: 2018-01-01
  workout_a_summary: "Squat, Bench, Row"
  workout_b_summary: "Deadlift, Press, Chinup"
  workout_c_summary: "Overhead Squat, Incline Bench, Pullup"

If you're using the default test_helper.rb file, this fixture will be auto-loaded before your tests run:

# test/test_helper.rb
module ActiveSupport
  class TestCase
    # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
    fixtures :all
  end
end

And if, like me, you've specified t.timestamps in the migration of any such models (which have been non-nullable by default since Rails 5.0), you'll be in for a rude awakening when you try to run any of your tests:

ActiveRecord::NotNullViolation: PG::NotNullViolation: ERROR:  null value in column "created_at" of relation "build_programs" violates not-null constraint
DETAIL:  Failing row contains (309456474, 2018-01-01, Squat, Bench, Row, Deadlift, Press, Chinup, Overhead Squat, Incline Bench, Pullup, null, null).

This doesn't happen to any of your other models, though! What gives?!

What gives

Here's what's happening.

  1. Inside Rails' activerecord/lib/active_record/fixtures.rb tries to guess the model name by constantizing the YAML file name, which in my case yields BuildProgram, which isn't a class
  2. It just happily sets model_class to nil as a result
  3. Later on, in activerecord/lib/active_record/fixture_set/table_row.rb and because model_class is nil, Rails will just silently fail to do any of the fancy things you expect fixtures to do
  4. Among those fancy things is setting the class's timestamps attributes

Now if it were up to me, personally, I'd have made this an error case. If a fixture file is being loaded into a table and the framework can't figure out what class it corresponds to, that should probably be an error by default.

But it wasn't up to me and so instead it fails silently. And as a result the top search results for this issue are all red herrings about missing or irregular inflections.

How to fix it

So here's how to fix it. Maybe you didn't know, but there's a secret _fixture key that you can set with metadata instructing Rails fixtures as to what the class constant is.

# test/fixtures/build_programs.yml
_fixture:
  model_class: Build::Program

first:
  started_on: 2018-01-01
  workout_a_summary: "Squat, Bench, Row"
  workout_b_summary: "Deadlift, Press, Chinup"
  workout_c_summary: "Overhead Squat, Incline Bench, Pullup"

Once set, Rails will no longer incorrectly infer the class from the name of the fixture's YAML file and instead just contstantize whatever you set model_class to.

Run your tests again and it should work.


Got a taste for fresh, hot takes?

Then you're in luck, because you can subscribe to this site via RSS or Mastodon! And if that ain't enough, then sign up for my newsletter and I'll send you a usually-pretty-good essay once a month. I also have a solo podcast, because of course I do.