Improved extract_fixtures

I’m not sure where I originally came across the extract_fixtures rake task (maybe here)but there’s nothing better than using real data to run your Rails unit tests. Well, real in the sense that it was generated by actually using your app. But there’s a problem with extract_fixtures. Once you get some real data to base your tests on you don’t want it to change because it would break your tests. So, after the first run extract_fixtures becomes almost useless because it’ll wipe out the fixtures you’ve been working with.

So what’s a person to do? Well, in my case the answer is to beef it up a bit. The following version of extract_fixtures takes two optional parameters:

  • TABLES=foos,bars,other_foos
    Tables takes a comma delimited (no spaces) list of table names that you want to extract. If you pass this in it will only extract those tables.
  • OUTPUT_DIR=/some/fully/qualified/non-relative/path
    Tell it what directory to output to.

If you improve it further please drop me a line and I’ll add or link to your enhancements here.

desc "Create YAML test fixtures from data in an existing database.  " +
" Defaults to development database.  Set RAILS_ENV to override. " +
"\nSet OUTPUT_DIR to specify an output directory. Defaults to test/fixtures. " +
"\nSet TABLES (a coma separated list of table names) to specify which tables to extract. " +
"Leaving it blank will extract all tables."

task :extract_fixtures => :environment do
  sql  = "SELECT * FROM %s"
  skip_tables = ["schema_info"]
  ActiveRecord::Base.establish_connection
  if (not ENV['TABLES'])
    tables = ActiveRecord::Base.connection.tables - skip_tables
  else
    tables = ENV['TABLES'].split(/, */)
  end
  if (not ENV['OUTPUT_DIR'])
    output_dir="#{RAILS_ROOT}/test/fixtures"
  else
    output_dir = ENV['OUTPUT_DIR'].sub(/\/$/, '')
  end
  (tables).each do |table_name|
    i = "000"
    File.open("#{output_dir}/#{table_name}.yml", 'w') do |file|
      data = ActiveRecord::Base.connection.select_all(sql % table_name)
      file.write data.inject({}) { |hash, record|
        hash["#{table_name}_#{i.succ!}"] = record
        hash
      }.to_yaml
    puts "wrote #{table_name} to #{output_dir}/"
    end
  end
end