In the last post of this series I tried to find a DRY solution to deal with tables storing
“ancillary” data, i.e. names of user roles, predefined categories, page state names and other similar
things.
I personally chose to put this kind of data to make my application more dynamic, although I could have decided to
use ENUMs or simply ordinary varchar fields — that would have been easier, but less flexible. For now,
I'm sticking with my original choice.
The data in these tables is kind of a prerequisite for the application to run: I must be able to have a status to assign to a user when creating it, and the same applies to roles. Sure, I could spend 20 minutes populating these tables manually, but it would be nice if there was a less tedious way, wouldn't it?
There is indeed. The inspiration came from a technique described in the book (which I highly recommend) Agile Web
Development With Rails, in which the author outlines how it would be possible to use Rails' fixtures
and migrations to load data in the database automatically from YAML files.
All you have to do is create a migration to load the specified YAML files and you're
all set.
I wanted to take a little step further, allowing the migration to load data from all YAML files in a specific directory, automatically.Let's start creating the
YAML files then and place them all in one directory of the application like
/db/migrate/defaults
. Here's the one I used for user roles, for example:
visitor:
id: 1
name: Visitor
level: 0
user:
id: 2
name: User
level: 10
contributor:
id: 3
name: Contributor
level: 20
provider:
id: 4
name: Provider
level: 50
operator:
id: 5
name: Operator
level: 100
administrator:
id: 6
name: Administrator
level: 500
webmaster:
id: 7
name: Webmaster
level: 1000
The important thing to remember is to provide a unique string to identify each record, before specifying each fiels. The other files look similar, so I won't bother listing them here.
And here's the simple code for the migration:
require 'active_record/fixtures'
class LoadDefaults < ActiveRecord::Migration
def self.up
down
models = self.default_models
models.each do |m|
Fixtures.create_fixtures(self.default_directory, m)
end
end
def self.down
models = self.default_models
models.each do |m|
eval("#{m.singularize.capitalize}.delete_all")
end
end
def self.default_directory
File.join(File.dirname(__FILE__), "defaults" )
end
def self.default_models
files, names = Dir.glob("#{self.default_directory}/*.yml"), []
unless files.blank?
files.each { |f| names << File.basename(f, '.yml') }
names
else
[]
end
end
end
Basically the migration will look in a directory named “defaults” for some YAML
files named after a particular database table, and it will attempt to load all the records defined in each one of
them.
The down
method of the migration deletes all the data in the specified tables, so use with
care…