2 weeks paternity leave from work means a couple of things. Firstly, it means I’ve been listening to a lot of music, and it seems the most fitting for Spring/Summer 2010 is some awesome space-rock. Order of the day seems to be Hawkwind.
Might get a mountain bike ride in on Friday. Mountain-biking + acid-rock == flashbacks whilst hurtling downhill at 30 mph.
Running Rails tests using an in-memory database
The second thing being off work enables is the cleaning up of old projects and things on the to-do list. One thing thats been bugging for a while is the length of time it takes to run my rails test cases (running MySQL):
~/git/cs(master) $ rake test ......................... Finished in 264.208584 seconds. 25 tests, 2697 assertions, 0 failures, 0 errors
Over 4 minutes, thats a long time when you’ve changed one line of code. A challenge! Onwards and upwards, heres how to get your rails test to run using sqlite3.
1. Install the sqlite gem
sudo gem install sqlite3-ruby
This may or may not go smoothly. Native extension compilation problems could lead to macports on OSX, or the equivalent apt-get on Ubuntu.
2. Modify database.xml
test: adapter: sqlite3 database: ":memory:"
Speaks for itself. Keep the old one around in case you want to run comparison speed-tests.
3. Install the ‘in_memory_fix’ plugin
I grabbed it from the following svn repo:
./script/plugin install http://topfunky.net/svn/plugins/memory_test_fix
But there are also a bunch of git forks floating around.
What does it do ? Well, the short of it is that because the db is in memory, it needs to be re-created each time, which is what the plugin does. You can find more info over here.
4. Differences between MySQL and Sqlite3
At last, some meat. You can take what you have and try to run your rake tests. There’s are chance you could be good to go. In my case, there were a couple of syntactic differences between the two databases that needed resolving:
RAND vs RANDOM
First up was that the sqlite3 random function is called ‘RANDOM()’ whilst the MySQL one is called ‘RAND()’. This cause an active_record lookup like the following one to fail:
@bar = Foo.find(:all, :order => 'RAND()')
Couple of ways to fix this. Easiest from my point of view was simply to create the ‘RANDOM’ function in MySQL. Sounds like a migration:
class CreateRandomFunction < ActiveRecord::Migration def self.up adapter = User.connection. instance_variable_get("@config") [:adapter] if adapter == 'mysql' execute "create function random() returns integer return rand();" end end def self.down adapter = User.connection. instance_variable_get("@config") [:adapter] if adapter == 'mysql' execute "drop function if exists random;" end end end
Doesn’t this apply to test-cases ?
Err, I suspect that should be enough, but my test cases were failing because around the time that fixtures were loaded, the function got blown away again. I ended up with this in my test_helper.rb:
adapter = Rails.configuration. database_configuration [Rails.configuration.environment] ["adapter"] if adapter == "mysql" && ENV["RAILS_ENV"] == 'test' ActiveRecord::Base.connection.execute("drop function if exists random;"); ActiveRecord::Base.connection.execute("create function random() returns integer return rand();"); end
Fixtures and “1” vs “t”
Almost out of the woods, but not yet. If you’re using fixtures, and you’ve auto-magically generated them (as I have), you might see the following entry for Booleans:
boolean_variable_in_mysql: "1"
This is all and well in MySQL, but sqlite3 doesn’t like it. It stores its bools as the char “t” or “f” and you may need to convert them. true or false should work fine, i.e.
boolean_variable_in_mysql: true
And thats it. My tests running in memory. Time to turn the space-rock up.