Search code examples
ruby-on-railsrubytddfixtures

Nil Associations with Rails Fixtures... how to fix?


I have a Rails 5.1 project using rspec/fixtures and I am having trouble getting fixtures to load objects associated with belongs_to/has_one/has_many: the object I requested the fixture for comes back with its _id columns filled with a seemingly-random number and ActiveRecord sees the association as nil. This occurs on large classes with many associations as well as small data classes with only a few fields.

If, in my test code, I assign those associations with normal Ruby code, objects behave as normal and my tests pass. However when loading the same data through fixtures, associated records are not available and tests that require data spanning across associations fail.

As an example, here are two affected classes:

#app/models/location.rb
class Location < ActiveRecord::Base
  has_many :orders
  has_many :end_user
  belongs_to :retailer
  belongs_to :depot
end

#app/models/retailer.rb
class Retailer < ActiveRecord::Base
    has_many :locations
end

And here are two corresponding fixtures files:

#spec/fixtures/locations.yml
loc_paris:
  retailer: ret_europe (Retailer)
  name: "Paris"
  nickname: "paris"

loc_washington:
  retailer: ret_usa (Retailer)
  name: "Washington"
  nickname: "washington"

#spec/fixtures/retailers.yml
ret_europe:
  name: "AcmeCo France"
  nickname: "acmecofr"
  currency_type: "EUR"

ret_usa:
  name: "AcmeCo USA"
  nickname: "acmecousa"
  currency_type: "USD"

With the above data, running pp locations(:loc_paris) results in:

#<Location:0x0000000006eee1d8
 id: 35456173,
 name: "Paris",
 nickname: "paris",
 retailer_id: 399879241,
 created_at: Wed, 23 May 2018 22:39:56 UTC +00:00,
 updated_at: Wed, 23 May 2018 22:39:56 UTC +00:00>

Those id numbers are consistent through multiple calls, at least in the same RSpec context. (I put pp locations(:loc_paris) in a let block.) Yet pp locations(:loc_paris).retailer returns nil.

I tried using FactoryBot however we had to switch away from it. I am trying to give fixtures an honest shake but it seems like we are best off simply building data objects in the actual test code... because that solutions works without complaining :/

Am I doing something wrong here? Are we asking too much of fixtures?

Thank you!

Tom


Solution

  • Problem with fixtures

    Looking at what you've done, locations(:loc_paris) will find the record described in locations.yml, but locations(:loc_paris).retailer won't.

    Rails Associations work like this:

    locations(:loc_paris).retailer will look for the retailer with retailer_id mentioned in locations(:loc_paris) record. In your case retailer_id: 399879241 and there is no reseller with this id that's why it returns Nil.

    Solution: Describe fixtures like this:

    #spec/fixtures/locations.yml
    loc_paris:
      retailer_id: 1
      name: "Paris"
      nickname: "paris"
    
    loc_washington:
      retailer_id: 2
      name: "Washington"
      nickname: "washington"
    
    #spec/fixtures/retailers.yml
    ret_europe:
      id: 1
      name: "AcmeCo France"
      nickname: "acmecofr"
      currency_type: "EUR"
    
    ret_usa:
      id: 2
      name: "AcmeCo USA"
      nickname: "acmecousa"
      currency_type: "USD"
    

    Now, locations(:loc_paris).retailer will look for the retailer with retailer_id mentioned in locations(:loc_paris) record i.e. retailer_id: 1 and there is a reseller ret_europe with this id. Problem Solved

    When you run rspec, at first rspec saves these fixtures into your database with some auto-generated id values (if id not provided explicitly), that's why id and reseller_id are some random values. If you don't want the id of locations.yml record to be some random value, you can provide it yourself like this:

    loc_paris:
      id: 1
      retailer_id: 1
      name: "Paris"
      nickname: "paris"
    

    Tips: As rspec runs in test environment (mentioned in app/spec/rails_helper.rb) and as I mentioned earlier whenever you run rspec, at first it saves the fixtures into your database. If your local and test database are same, fixtures will replace the actual database records of your database. In your case, records in locations and resellers table record will be completely erased and replaced with these fixtures. So, make different database for test environment.

    Hope this answer is helpful