Wednesday, May 28, 2008

Use YAML to load sample data into database

When reading the Active Record, I was wondering if I can create a rake to automatically populate some data into database.

After some research, I have the following code samples.

Suppose I have three tables: blogs, posts and comments.

And I have three prepared yaml files: blogs.yml, posts.yml and comments.yml. They are saved at /#{RAILS_ROOT}/lib/tasks/sample_data/

blogs.yml looks like:

B1000:
id: 1000
blog_name: "Xianese Photo"

B2000:
id: 2000
blog_name: "Mark's World"

B3000:
id: 3000
blog_name: "May's Beautiful World"




Then the rake file "sample_data.rake" is saved under
/#{RAILS_ROOT}/lib/tasks/.

I use TABLES array to hold the table names.

TABLES = %w-blogs comments posts-


From table names, I use Inflector's classify function to convert the table names to Model Class names and then use const_get function to get the ActiveRecord Class instance. You can't directly use a string name to refer to the Class instance. So this step is necessary.


path = DATA_DIRECTORY + table_name.to_s + '.yml'
model_name = Inflector.classify table_name
model_class = ActiveRecord.const_get(model_name)


Next step is easy, ust YAML.load_file to load each yml files:


data = YAML.load_file(path)


Notice that the data is actually a hash like "B1000" => <...>. The <...> is another hash with real data: blog_name => "Xianese's blog", etc. So you have to tranverse the hash then use model_class to create a new instance of the Model object and save it:


data = YAML.load_file(path)
data.each do |key, value|
model_instance = model_class.create(value)
model_instance.save
end


That is it! It is pretty simple.

Removing sample data is even simpler. I use ruby's eval method to pass in a class string name and delete all records for each Model.

Here is the final code:


require 'yaml'

namespace :db do
DATA_DIRECTORY = "#{RAILS_ROOT}/lib/tasks/sample_data/"

namespace :sample_data do
TABLES = %w-blogs comments posts-

desc 'Load the sample data'
task :load => :environment do |t|
TABLES.each do |table_name|
path = DATA_DIRECTORY + table_name.to_s + '.yml'
model_name = Inflector.classify table_name
model_class = ActiveRecord.const_get(model_name)

data = YAML.load_file(path)
data.each do |key, value|
model_instance = model_class.create(value)
model_instance.save
end

puts "Loaded data from #{table_name}.yml"
end
end

desc 'Remove the sample data'
task :remove => :environment do |t|
TABLES.each do |table_name|
model_name = Inflector.classify table_name
eval "#{model_name}.delete_all"
end
end

end
end



I hope this helps.

1 comment:

Unknown said...

I love it when I look for a simple solution to a simple problem, and actually find a simply answer.

Thanks,
Torey