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.
- Inside Rails'
activerecord/lib/active_record/fixtures.rb
tries to guess the model name by constantizing the YAML file name, which in my case yieldsBuildProgram
, which isn't a class - It just happily sets
model_class
tonil
as a result - Later on, in
activerecord/lib/active_record/fixture_set/table_row.rb
and becausemodel_class
is nil, Rails will just silently fail to do any of the fancy things you expect fixtures to do - 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.