Skip to content


Rails Fixtures, Migrations, and Test Data

I encountered an interesting problem the other day with Rails. Doing searches about migrations and fixtures lead me to this post where the author describes various problems with using migrations and fixtures. The part that jumped out at me was this:

2. Load the appropriate sets of data for the test database. “Sets” is plural on purpose; most non-trivial databases include code tables, which constitute base data which are essentially part of the database design itself. Then, test code will want a fixed set of known test data to act upon, so that tests can measure whether the code did the right thing given the test data (the right inputs yield the right outputs).

That’s pretty much exactly my problem. I have a set of base data that I want in every database, and then I have the test fixtures themselves. I tried many things unsuccessfully, even emailing the URUG list, but none of it worked.

While scouring through active_record/fixture.rb I noticed this:

“If you preload your test database with all fixture data (probably in the Rakefile task) and use transactional fixtures, then you may omit all fixtures declarations in your test cases since all the data‘s already there and every case rolls back its changes.”

That’s what sparked an idea, I said to myself “if I load the base data set in the migrations, then I can load the fixtures in addition to what is already in the database.”

With that in mind, I created insert statements in my migrations for the base data, and left the test fixtures in the test/fixtures/*.yml files. Then I went through my tests and removed all the fixtures declarations, this of course meant changing a lot of things that looked like user = users(:first) to user = User.find_by_name('first'), since now the data is expected to be in the database.

The only thing left is how to load the fixtures into the database. To accomplish that, I decided to write a new Rake task:

desc "Loads fixtures without deleting existing fixtures" 
task :load_additional_fixtures => :environment do
  require 'active_record/fixtures'

  ActiveRecord::Base.establish_connection(RAILS_ENV.to_sym)
  ActiveRecord::Base.connection.update "SET FOREIGN_KEY_CHECKS = 0" 

  class << Fixtures
    def create_fixtures(fixtures_directory, table_names, class_names = {})
      table_names = [table_names].flatten.map { |n| n.to_s }
      connection = block_given? ? yield : ActiveRecord::Base.connection
      ActiveRecord::Base.silence do
        fixtures_map = {}
        fixtures = table_names.map do |table_name|
          fixtures_map[table_name] = Fixtures.new(connection, File.split(table_name.to_s).last, class_names[table_name.to_sym], File.join(fixtures_directory, table_name.to_s))
        end               
        all_loaded_fixtures.merge! fixtures_map  

        connection.transaction(Thread.current['open_transactions'] == 0) do
#          fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures }
          fixtures.each { |fixture| fixture.insert_fixtures }

          # Cap primary key sequences to max(pk).
          if connection.respond_to?(:reset_pk_sequence!)
            table_names.each do |table_name|
              connection.reset_pk_sequence!(table_name)
            end
          end
        end

        return fixtures.size > 1 ? fixtures : fixtures.first
      end
    end
  end

  Dir.glob(File.join(RAILS_ROOT, 'test', 'fixtures', '*.{yml,csv}')).each do |fixture_file|
      Fixtures.create_fixtures('test/fixtures', File.basename(fixture_file, '.*'))
  end

  ActiveRecord::Base.connection.update "SET FOREIGN_KEY_CHECKS = 1" 
end

In case you’re wondering, all I did was go copy the create_fixtures method from active_record/fixtures.rb and comment out the line which deletes the existing fixtures. Oh and I turn off foreign key checking since I have foreign key constraints.

Is it a total hack? Yes. Does it get the job done? Yes. Is there any magic involved? No, well assuming you discount the use of Ruby metaclasses ;-), those are wondrously magical. If I weren’t under the gun to have this project done by the end of the month I’d spend more time and add a variable to make the delete conditional, and submit a patch for Rails…

Posted in Rails, Ruby, Software Development, Tips and Tricks.


0 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.



Some HTML is OK

or, reply to this post via trackback.