Archive for March, 2008

Rails Update Test Fixtures Upon Migration

Saturday, March 29th, 2008

Ever get the nagging feeling that everyone knows how to do some obvious thing except for you? This is the feeling I’ve long had with updating my Rails test fixtures. All self-respecting Rails developers know that a strong test infrastructure is a key aspect of any Rails application. And test fixtures are generally seen as the key component that drives test cases. But no Google query I’ve figured out yet has shown me a good way to keep my fixtures updated as my database changes. I’ll going to discuss our current working solution here.

What are fixtures? (aka: newb background information)

For newbs that might not have started their testing yet: test fixtures represent each table in your database as a single file (usually in YAML format) that contain specific, known records you can refer to in your tests.

For example, we have an items fixture that loads in a bunch of sample items records our tests to which our refer. A single record in this items YAML file looks something like:

items_not_valid_not_committed_missing_price:
shipping_price:
price:
title: "dummy item"
quantity:
shipping_id:
id: "71"
item_status_id:
category_id:
committed:
description:
seller_id: "3"
image_id:

Fixtures are usually created initially by exporting the data from one of your real database (development or production) running rake db:extract_fixtures. This will create a fixture file for every table, with the records in every table labeled something like “item_001″, “item_002″, etc. As you can see in the example above, my tendency is to rename these records so that they are more semantic. This makes it easier to remember which fixture record is which when loading them in my tests, by having syntax like item = items(’items_not_valid_not_committed_missing_price‘). A lot easier to remember what that item represents later on, then having it named item = items(’item_001′)… and then six months later asking “What was the item number 1 record again? Oh yeah! The not valid item because it was missing price! Of course.”

Sounds fine to me. So what’s the problem?

The problem is what happens when your database changes. Especially for an application in the midst of development, the database might change weekly. However, if you re-export your fixtures from the database, you’ll lose all of your custom-named fixture records. You’ll also lose any special cases you might have setup. In our case, we have records where we set stuff like “item_expires_at: <%= 1.day.from_now %>”, and that ain’t gonna fly if you re-export the database.

If you don’t re-export the database, though, then you are left with hundreds of records that are missing (or have extra) fields after your migration. What’s a developer to do?

The partial answer: The Rails Fixture Migration Plugin

This plugin, developed by Josh and Jake is a good start. After installing it, you can run “rake db:fixtures:migrate” and automatically have the fields that were added or removed in your migrations correspondingly added or removed from your fixtures. But there are a number of caveats:

* The code breaks if it finds empty fixtures. This can be fixed by wrapping lines 14-16 in migrate_fixtures.rb with an if so they only try to load non-empty fixtures
* The code evaluates and replaces any inline Ruby in your fixtures. So the previously mentioned “item_expires_at: <%= 1.day.from_now %>” will become “”item_expires_at: March 29, 2008″ after migration. I’ve just worked around this by manually replacing those substitutions after running the migration. A bit more annoying is if you have code that does any loops in your fixtures. For example, we have a loop that creates 50 similar items in one of our fixtures, and the fixture migrator simply doesn’t understand what this code is doing, giving an exception when it tries to run it. For the time being, I just remove this loop before migration and re-add it after migration. A pain, surely, but less of a pain than adding new fields to the other 100 records in our items fixture.
* The fixture migration fails with some ambiguous error code if any of the fixtures it wants to migrate already exist. The fixture migration uses a schema_info.yml file in your /test/fixtures directory to keep track of the fixture migration. If you, for example, create a new scaffolding that creates both a migration file and a new fixture, the fixture migration tool will break when it gets to the migration to create the new table, because it seems the new table is already a fixture in your directory.

All in all, they are surmountable obstacles for us at this point, given the lack of other options.

The “better” answer: Uhh…

I’ll be curious to hear if other developers have a more elegant way to deal with the problem of writing tests that stay relevant as your database changes? One school of thought is that one could just write methods that create the records you need programmatically, avoiding fixtures altogether. This has crossed my mind, but fixtures are nice since they come pre-generated, and I think they are generally easier to read through than a hash that creates a new record. A bigger benefit of fixtures is that they are automatically placed in the test database, so you get all the interconnections between your tables loaded at once. It seems to me like it would be quite a headache to create an entire database of data programatically.

So, slightly painful fixture migrations it is, so far.

You can get your own copy of the slightly pain Rails migration plugin here.

Rails Optimize Google Maps Load Times

Tuesday, March 18th, 2008

Ain’t it grand when you get to something on your list that has been getting put off for months, only to discover that somebody did it for you a few days ago?

Such was the case with us and our Google Maps. We had long intended to cache Google Maps (accessed through the YM4R plugin) on account of the 1-2 seconds per page load that the maps cost (!). Most of that time is spent running JS — both from Google (that grabs the map tiles and sets up zooming, scrolling, etc.) and from YM4R (don’t know what that JS is doing, but it’s costly).

Anyway, if you are putting maps on your page, and you don’t need the user to be able to move the map around, don’t be a fool: use static Google Maps and take your load time from 2 seconds to .02 seconds.

And being that we are in the midst of a Rails revolution, it should surprise no one that within 48 hours of Google unveiling the static map service (in late February) someone (<credit>John Wulff</credit>) had already written a gem to use it in Ruby. Unfortunately, gems suck, especially gems like this one with multiple gem dependencies. But there is no reason you can’t just get the functionality as a Rails plugin. Here is the file. All you have to do is put it in your /vendor/plugins directory under something like vendor/plugins/static_gmaps/lib and you’re golden. Usage for the static map is like so:

@map = StaticGmaps::Map.new(:center => [ @lat, @lng ],
:zoom => 10, # gmaps zoom level
:size => [ 120, 75 ], # pixel width, height
:map_type => :mobile, #:mobile or :roadmap, :mobile is a bit more distinct for small maps
:key => YOUR_GMAPS_API_KEY)

Then, you put it in an image tag:

image_tag(@map.url)

Now, I do say, that is easy way to cut out 2 seconds/page load.

Update:  Almost forgot to mention one painful lesson I learned when implementing the static maps, which is that, unlike with the YM4R maps, Google does check your HTTP_REFERER when using static maps.  So, to all you localhosts:  don’t forget to go to Google Maps and grab an API key that works for the IP address in your HTTP_REFERER string.  If your referer string doesn’t match your API key all you’ll get is a blank image and no explanation.

Setup Mongrel Cluster Options

Friday, March 7th, 2008

Damn Google didn’t tell me what I wanted to know when I wanted to know it. I wanted to know what options were available to put in my mongrel_cluster.yml file. Eventually I found them. If Google smiles upon this blog entry, you will find them more quickly than I did.

Usage: mongrel_rails <command> [options]
-e, –environment ENV Rails environment to run as
-d, –daemonize Whether to run in the background or not
-p, –port PORT Which port to bind to
-a, –address ADDR Address to bind to
-l, –log FILE Where to write log messages
-P, –pid FILE Where to write the PID
-n, –num-procs INT Number of processors active before clients denied
-t, –timeout TIME Timeout all requests after 100th seconds time
-m, –mime PATH A YAML file that lists additional MIME types
-c, –chdir PATH Change to dir before starting (will be expanded)
-r, –root PATH Set the document root (default ‘public’)
-B, –debug Enable debugging mode
-C, –config PATH Use a config file
-S, –script PATH Load the given file as an extra config script.
-G, –generate CONFIG Generate a config file for -C
–user USER User to run as
–group GROUP Group to run as
–prefix PATH URL prefix for Rails app
-h, –help Show this message
–version Show version

These options are the same in the mongrel_cluster.yml file, or if you pass these options through the command line to mongrel_rails.  The only exception I’ve found to this rule is that if you want to specify a script to your mongrel_cluster.yml, your line starts with “config_script:” instead of just “script:”.  Don’t ask me why.