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
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
9 Responses to “Rails: Loading fixtures to development with a specified order”
1Guido Sohne
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.
2Enrico Franchi
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!
3Guido Sohne
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 …
4Enrico Franchi
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!
5Alexis Li
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
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
6Esad Hajdarevic
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
7Enrico Franchi
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.
8Ben W
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.
9Enrico Franchi
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