This post originated from an RSS feed registered with Ruby Buzz
by James Britt.
Original Post: Rails Fixtures from Database Contents
Feed Title: James Britt: Ruby Development
Feed URL: http://feeds.feedburner.com/JamesBritt-Home
Feed Description: James Britt: Playing with better toys
If you use the generator scripts that are a part of Rails, you get assorted niceties, such as stub files for unit tests, and the ability to load fixture files to drive tests with predefined test data.
These fixture files use YAML to define tables, and look something like this:
user_2:
account_id: 1
id: 1
user_type: "0"
first_name: James
access: 0
password: d2048f35eatmeb6970717a6ef753aa209
login: james
last_name: Britt
email_address: foo.bar@gmail.com
The file will typically have several of these entries; it's a hash that maps a field name to a value, for each table row.
There should be a fixture file for each model you plan to test (i.e., all of them, right?)
Fixtures are also handy for pre-populating a test or development database so you can examine or work with well-defined conditions. See this code snippet for a way to do this.
Quite handy.
I've found, though, that as I'm working through some development task, using the application and adding/altering data, I often reach a point where I want to be able to continue later on working with that same data set. Perhaps I'm thinking through how the application should behave under certain conditions.
It's too tedious to re-enter data by hand, so I looked for an automatic process.
One option would be to use some database tool to execute a SQL dump, and then use that tool again to reload the data later.
Or write a Rake task to do this for me.
But I like the YAML fixture format better than raw SQL calls for two reasons. First, the YAML is easier to read and modify than the SQL. It is quite simple to see what data goes where, and I can munge things up to tweak conditions. Second, the fixture files can (theoretically) be loaded into any database without concern for quirks between different databases, or different versions of the same database. (I've had problems with databases refusing to import a file created by its own export tool.)
I poked around, and asked on the Rails dev list, but there was, it seems, no existing tool for this. So I rolled my own.
First, I swiped some code from Nicholas Seckar, who responded to my rails-dev query with an example of turning a record set into a YAML doc. It was cleaner than my first attempt at this, so I replaced my code and made a few changes to create a class method suitable for each model:
def to_fixture
records = self.find(:all)
underscored = self.name.underscore
records.inject({}) do |hash, record|
key = "#{underscored}_#{hash.length + 1}"
hash = record.attributes
hash
end.to_yaml
end
This is part of a base-model library, in a file cleverly named base-model.rb. It gets included in all models for my application, which lets me add a set of handy class methods to every model.
How a method defined in a moduke becomes a class method, rather than an instance method, by virtue of module inclusion, may be less than obvious. (It certainly was to me until some searching brought me to a helpful post by John Wilger.
The magic happens here:
module BaseModel
# Methods defined within this module will be added as class methods to any
# class that includes BaseModel.
module BaseModelClassMethods
# ... many method definitions, much like to_fixture
end
def self.included(mod)
class <
end
My models require the file base-model.rb, and call include BaseModel.
This triggers the call to self.included, which handles adding all the module methods as class methods.
So now each model class knows how to emit itself as a fixture document. The next part is creating a method to loop over each model. I took the stupid-but-fast hack of simply iterating over a hard-coded list. I suppose the "right" way might be to ask the database to loop over its tables; it wasn't instantly obvious to me how to do that, but it was instantly obvious how I could hand-write a list. So there you go.
My first pass embedded the call in a template file; I could then create a Web page with my fixture docs by fetchng a URL. I liked this because it was easy to play around and try stuff out. Even better, it would pull the data from whatever was the current database in use.
Of course, while it gave me some immediate gratification, it also meant repeated cut-n-paste or other manual twiddling.
The next version was a rake task.
(The usual caveat: All code is primarily judged by "Does it work, and can I now go back to my real tasks?")
desc "Create fixture data from a database"
task :create_fixtures => [ :environment, :zip_fixtures] do
if defined? RAILS_ROOT
fixtures_dir = "#{RAILS_ROOT}/test/fixtures/"
else
fixtures_dir = File.dirname(__FILE__) + 'test/fixtures/'
end
ActiveRecord::Base.establish_connection( :development )
text = ''
[ Message,
Account,
User,
Address,
Client,
].each do |klass|
x = eval( "#{klass}.to_fixture " )
File.open( "#{fixtures_dir}/#{klass.to_s.plural.downcase}.yml", "wb"){ |f| f.puts x }
text << "#{x}\n"
end
puts text
end
Two notes: I have a platform/machine-dependent task for zipping up the existing fixture files so that I don't lose anything, Just In Case. I also needed to follow YARNC (yet another Rails naming convention) for the fixture files, meaning I had to pluralize the class names. There may be some built-in Rails method for this, but it was simpler for me to just whip up an addition to the String class to do the work.
And that's it.
Oh, wait. One more thing. In writing this up, I thought a bit more about the hard-coding of the database used to create the fixtures. Surely, I thought, there is a way to define this at the command line when calling the task. Indeed there is. (And note, too, that you could also simply pull the database configuration from either an environment variable or your Rails config.)