Rails: Loading fixtures to development with a specified order

September 11th, 2006

Background

My development style is heavily test driven. One of the very first things I do write are fixtures, in order to write tests on them. For this reason the very first database I use in a Rails projects is the test database, rather than the development one.

When I have to populate the development db, I already have plenty of fixtures: if they aren’t enough to try the user interface (that is what is left to do), they weren’t enough to test the models and the controllers.

Of course this should be no problem, there is the rake task db:load:fixtures that plays well with this kind of issues. However, I tend to use db constraints rules such as

  ALTER TABLE bars ADD FOREIGN KEY(`foo_id`)  REFERENCES foos  (`id`) ON DELETE CASCADE;

if you use MySQL or

  ALTER TABLE bars ADD CONSTRAINT valid_foo FOREIGN KEY (foo_id) REFERENCES foos (id) MATCH FULL;

if you use Postgres.

Solution

Of course, in this case you can’t load fixtures in any order and you must supply correct fixtures loading order.

After some searching I found this solution.

However, it does not work. In fact it builds a Hash inserting fixture names in the specified order, than builds an array with other fixtures that had not been specified as needing some particular order.

Eventually, it sums the keys of the hash and the array, and loads fixtures in this order. I don’t understand how the poster got this code working, since Ruby Hashes are not ordered in any way and the documentation explicitly says that iterators and methods return values in arbitrary order and do not rely on insertion order.

This meant I had to change the code. Now it uses arrays everywhere (which seems the most reasonable thing to do, since this rake task it’s about order). I also added namespaces.

You still have to define something like

  ENV["FIXTURE_ORDER"] =
    %w( entities interaction_sources interaction_devices
        payments interactions accounts employments
        enrollments payables receivables tenures wards ).join(' ')

My code is

  require File.expand_path(File.dirname(__FILE__) + "/../../config/environment")

  ENV["FIXTURE_ORDER"] ||= ""

  def print_separator_line(n=80)
    puts "\n" + '='*n
  end

  desc "Load fixtures into #{ENV['RAILS_ENV']} database"

  namespace :db do
    namespace :fixtures do
      task :load_ordered => :environment do
        require 'active_record/fixtures'

        print_separator_line
        puts "Collecting specified ordered fixtures\n"

        ordered_fixtures = ENV["FIXTURE_ORDER"].split
        fixture_files = Dir.glob(File.join(RAILS_ROOT, 'test', 'fixtures', '*.{yml,csv}'))

        other_fixtures = fixture_files.collect { |file| File.basename(file, '.*') }.reject {|fx| ordered_fixtures.include? fx }

        ActiveRecord::Base.establish_connection(ENV['RAILS_ENV'])

        all_fixtures = ordered_fixtures + other_fixtures
        print_separator_line
        puts "Fixtures will be loaded in following order\n"

        all_fixtures.each_with_index do |fx, i|
          puts "#{i+1}. #{fx}"
        end

        print_separator_line
        puts "Actually loading fixtures to #{ENV['RAILS_ENV']} db..."

        all_fixtures.each do |fixture|
          puts "Loading #{fixture}..."
          Fixtures.create_fixtures('test/fixtures',  fixture)
        end unless :environment == 'production'
        # You really don't want to load your *fixtures*
        # into your production database, do you?
      end
    end
  end

Open Source and Free Software, Programming, Ruby | Comments | Trackback

9 Responses to “Rails: Loading fixtures to development with a specified order”

  1. 1Guido SohneNo Gravatar
    September 19th, 2006 @ 00:43

    Hi Enrico!

    Thanks for pointing out the problem in the code. I’ve updated my site to link to this post so you can get the full credit for fixing it :-)

    For what it’s worth, I wrote that code in a time period where I was not encouraged to write about anything that competition to the employer could use to learn anything or to do anything.

    So in such a case, you can leave in a bug or two :-) The real programmers will know how to fix it.

  2. 2Enrico FranchiNo Gravatar
    September 19th, 2006 @ 16:59

    Don’t worry about it. Without your code, I’m afraid I would have been too lazy to write it from scratch by myself. It was really helpful to me! :)

  3. 3Guido SohneNo Gravatar
    September 21st, 2006 @ 15:58

    Actually, I think I had the same problem as you. Problems with loading tests and all of that schema dependecies and it was getting very tedious to run some tests, so I decided to try and do something …

  4. 4Enrico FranchiNo Gravatar
    September 22nd, 2006 @ 06:42

    yes.. I thinks a lot of software gets written this way: writing it is less tedious than doing things manually!

  5. 5Alexis LiNo Gravatar
    October 11th, 2006 @ 08:34

    The following code worked for me… !

    Notably you missed out the second require, and I put the ‘production’ check more visibly and efficiently at the top of the task. I removed some random ‘n’ characters from puts’ as well.

    Of course, thanks to both Enrico and Guido for making this available and into a state trivial to fix :D

    require File.join(”#{RAILS_ROOT}/config/environment”)
    require File.join(”#{RAILS_ROOT}/test/fixture_order”)

    ENV["FIXTURE_ORDER"] ||= “”

    def print_separator_line(n=70)
    puts ‘=’*n
    end

    desc “Load fixtures into #{ENV['RAILS_ENV']} database”

    namespace :db do
    namespace :fixtures do
    task :load_ordered => :environment do
    if :environment == ‘production’
    puts “You really don’t want to load your *fixtures*”+
    ” into your production database, do you?”
    return
    end

    require ‘active_record/fixtures’

    print_separator_line
    puts “Collecting specified ordered fixtures”

    ordered_fixtures = ENV["FIXTURE_ORDER"].split
    fixture_files = Dir.glob(File.join(RAILS_ROOT, ‘test’, ‘fixtures’, ‘*.{yml,csv}’))

    other_fixtures = fixture_files.
    collect { |file| File.basename(file, ‘.*’) }.
    reject {|fx| ordered_fixtures.include? fx }

    ActiveRecord::Base.establish_connection(ENV['RAILS_ENV'])

    all_fixtures = ordered_fixtures + other_fixtures
    print_separator_line
    puts “Fixtures will be loaded in this order:”

    all_fixtures.each_with_index do |fx, i|
    puts “#{i+1}. #{fx}”
    end

    print_separator_line
    puts “Actually loading fixtures to #{ENV['RAILS_ENV']} db…”

    all_fixtures.each do |fixture|
    puts “Loading #{fixture}…”
    Fixtures.create_fixtures(’test/fixtures’, fixture)
    end
    end
    end
    end

  6. 6Esad HajdarevicNo Gravatar
    November 27th, 2006 @ 18:50

    It seems that default db:fixtures:load task takes FIXTURES argument as well, respecting the order! So all you have to do is

    rake db:fixtures:load FIXTURES = a,b,c

    or you can put

    ENV['FIXTURES'] = “a,b,c”

    to your environment.rb

  7. 7Enrico FranchiNo Gravatar
    November 28th, 2006 @ 04:20

    It is likely they added this useful feature.
    In fact this article has two months, that are a lot of time considering rails development pace.

    Anyway, many thanks for pointing out this nice piece of new. I do prefer using standard things, as far as it is possible.

  8. 8Ben WNo Gravatar
    April 29th, 2007 @ 15:48

    The best solution I’ve found so far is to enable foreign key deferrals while loading fixtures.

    In PostgreSQL you can enable them in a session and then they stay enabled until the end of the transaction.

    Getting them enabled as part of fixtures load is not trivial to do the first time, but I’ve ended up with a suitable runtime patch for TestHelper.

  9. 9Enrico FranchiNo Gravatar
    May 8th, 2007 @ 17:58

    However, this seems to be not portable. Which is not a great problem for software I develop (since I do use Postgres), but I’m afraid would not work with another notorious open source DB. And unfortunately it’s the one many hostings offer.

Leave a Reply

  1.  
  2.  
  3.  
  4. XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>
You can keep track of new comments to this post with the comments feed.

Recent Posts

Blogroll

Siti amici

Misc

Recent Comments

Categories

Enrico Franchi graduated in Maths and Computer Science and is now studying for a Computet Science MSc (though because of italian bureaucracy that very course is to be cancelled).

RiK0's Tech Temple is using WP-Gravatar