Use models to instantiate your Rails fixtures
Jun 27 by
Andre
Following is a handy Rake script to import your fixtures (or a subset of your fixtures) through the associated model's constructor.
Say what? You import your fixtures with rake db:fixtures:load FIXTURES=neighborhoods,shops right? Yes, but there's a crucial difference:
Say what? You import your fixtures with rake db:fixtures:load FIXTURES=neighborhoods,shops right? Yes, but there's a crucial difference:
- The standard rake fixtures:load does a straight data load into the database -- your Rails Models are not involved. It's as if you did a series of INSERT INTOs through your MySQL command line.
- The instantiation script takes the fixture data, and invokes the associated model's constructor for each item.
Here's the script
require 'active_support/inflector'
desc "instantiate core objects from fixtures yml"
task :import_core => :environment do
print 'starting . . .'
# the list of models (pluralized) you want to import, in order
models = ['Cities','Neighborhoods','Shops','Reviews']
#truncate existing tables
models.reverse.each { |model| ActiveRecord::Base.connection.execute("truncate table #{model.underscore}") }
puts 'done truncating tables'
models.each do |model|
table_name = model.underscore
path = "test/fixtures/#{table_name}.yml"
puts 'loading '+path
model_data = YAML::load_file(path)
if model_data
puts "creating #{model_data.size.to_s} #{model} . . . "
klass=eval(Inflector.singularize(model))
model_data.each do |data|
klass.create(data[1])
end
puts ". . .done creating "+model
end
end
end
Where to put it
Put the above script into your lib/tasks/ directory. It doesn't matter what you call it ( as long as it's a .rake file); I like to call it data_setup.rake. Typically, I end up with multiple data-related rake tasks, and this fileIf you haven't worked with rake tasks much, this is a great time to lean about them. Rake tasks are a snap to create, they automatically have access to your environment, and they can automate much of the drudgery of maintaining your data.
How to invoke it
Call this task like you would any other. From the command line within your application folder: ruby rake import_core . The name import_core is arbitrary -- it's just what I chose to call the task, as specified on the 4th line of the code above.Don't forget to check it in
This task (and all Rake tasks) can/should be checked in along with all your other code. Let's break it down
The task itself is straightforward, but I'll go through it here for clarity. I'm sure there are improvements to be made, so drop me a note if you there's a better way to do anything here.
# needed because we're using singularize,etc in the code
require 'active_support/inflector'
# the name of the Rake task which will show up if you type rake -T
desc "instantiate core objects from fixtures yml"
# specifiy the name of the task here. I chose "import_core"
task :import_core => :environment do
print 'starting . . .'
# modify this to match your own models!
# Note that these should be capitalized like your model name, but be plurals
# The order you specify here will be the order in which the models are imported,
# so it should go from higher-level to lower-level objects
models = ['Cities','Neighborhoods','Shops','Reviews']
# This clears out the tables associated with the models before importing
# erase or comment out if you don't want the tables cleared before importing
# Truncation happens in the reverse order of import
#truncate existing tables
models.reverse.each { |model| ActiveRecord::Base.connection.execute("truncate table #{model.underscore}") }
puts 'done truncating tables'
models.each do |model|
# load the fixture associated with each model. Fixtures are assumed to be in the usual place
table_name = model.underscore
path = "test/fixtures/#{table_name}.yml"
puts 'loading '+path
model_data = YAML::load_file(path)
if model_data
puts "creating #{model_data.size.to_s} #{model} . . . "
# eval'ing the model name gives you the "class" on which you can call the .create method
klass=eval(Inflector.singularize(model))
# For each row of data we loaded earlier . . .
model_data.each do |data|
# Ask the class to create an instance
klass.create(data[1])
end
puts ". . .done creating "+model
end
end
end
require 'active_support/inflector'
# the name of the Rake task which will show up if you type rake -T
desc "instantiate core objects from fixtures yml"
# specifiy the name of the task here. I chose "import_core"
task :import_core => :environment do
print 'starting . . .'
# modify this to match your own models!
# Note that these should be capitalized like your model name, but be plurals
# The order you specify here will be the order in which the models are imported,
# so it should go from higher-level to lower-level objects
models = ['Cities','Neighborhoods','Shops','Reviews']
# This clears out the tables associated with the models before importing
# erase or comment out if you don't want the tables cleared before importing
# Truncation happens in the reverse order of import
#truncate existing tables
models.reverse.each { |model| ActiveRecord::Base.connection.execute("truncate table #{model.underscore}") }
puts 'done truncating tables'
models.each do |model|
# load the fixture associated with each model. Fixtures are assumed to be in the usual place
table_name = model.underscore
path = "test/fixtures/#{table_name}.yml"
puts 'loading '+path
model_data = YAML::load_file(path)
if model_data
puts "creating #{model_data.size.to_s} #{model} . . . "
# eval'ing the model name gives you the "class" on which you can call the .create method
klass=eval(Inflector.singularize(model))
# For each row of data we loaded earlier . . .
model_data.each do |data|
# Ask the class to create an instance
klass.create(data[1])
end
puts ". . .done creating "+model
end
end
end

Comments
Ryan Allen on Jun 27
That's very useful. Thanks for that!
Shane Mingins on Sep 05
Correct me if I am wrong but this approach also bypasses the assigned id's that you have entered into your fixture correct?
Cheers
Shane
Andre on Sep 05
Shane, that's true. It still inserts predictable ids though, since it truncates the table each time you run it. So, the first record will be id=1, second id=2, etc.
Shane Mingins on Sep 25
hi again :-) i was using your script today and found that it did not seem to tell me if i had any errors ... for example i had an address that was too long but it did not seem to report this (that i could see) ... so i changed the loop that uses klass.create to do:
model_data.each {|key, value| klass.new(value).save! }
which i found in the migrations section of the Rails book and this seemed to report any errors (like my address one).
the reason i am using your script is so i can populate development with data to play with. i cannot quite work out if i use data migrations (like in the Rails book) how i would keep them separate from production, i.e. i do not wish to populate production with dev data.
anyway .., thanks for great script.
cheers
shane