I took a bit to completely rewrite the tiny application I had been using (and subsequently neglecting) called Oughtve. I will write more about that later, but a part of the rewrite process was to move away from the semi-binary text files I was using and into a real database. I opted to use SQLite as the backend and for ease of use and (possibly) allowing the user to select a different DB(database) at some point, I wanted to use an ORM. This turned out to be the source of never before seen frustration for me.
Initially I wanted to use DataMapper because its interface seemed to be the best of the bunch. It had an issue with namespaced model names not working (which I will ticket as soon as Lighthouse sees fit to allow me to) but the deeper problem was the inability to define the model classes without being connected to the database which is not good when you are trying to control the database setup phase. Sequel had the same problem if using models. For a brief time, I tried to use Sequel just as a “better SQL” but that got old really quick. Eventually I looked at Og which I must admit I had thought dead only to find it quite vibrant (although very poorly documented.) Og does also, indeed, allow defining the models without an existing database connection which got me past my first annoyance.
The second one really drove me up the wall, though. None of the frameworks has an easy (or any) way of exactly defining the point where the database and schema are created. The situation for destroying the database is the same, and additional problems are created by the general inability to be able to definitively drop the database connection. I went through a huge mess of attempts to be able to run my specs in some deterministic way.
I have to take a lot of the blame for the frustration myself, though. While I think that such features would be immensely useful, in truth I was only trying to do this to be able to run my specs! The setup spec actually does require being able to control the creation but in isolation it is quite possible to do (with just a little bit of stubbing to help.) All the other files would run just fine in isolation, too, but when I tried to run multiple the problems started. I am sure that you already see where I went wrong. Truthfully, I was just immensely annoyed at the entire process at this point and I think it clouded my judgement and caused me to forge ahead, pushing against the walls of the box I had built for myself (with the kind assistance of the ORM frameworks.) It took me entirely too long to put my two ducks in a row.
- In the application itself, I do not need the arbitrary setup and teardown (the initial setup can be handled easily enough.)
- The specs were only failing when run together—or in other words, in the same process than another spec file.
In hindsight, the solution is obvious and I feel stupid for having bashed my head against the wall for as long as I did. Is it perfect? No. Does it address the ORMs’ shortcomings? No. But it works. So I stopped worrying about it and just wrote a little spec runner of my own that still allows running multiple specs at a time, just in different processes. It is reproduced here for posterity, in case anyone ever needs a template:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
#!/usr/bin/env ruby # The spec files are designed to be run one at a time # because it removes the necessity of completely tearing # down the DB setup which is *exceedingly* difficult in # any ORM. require 'fileutils' dummy_dir = File.expand_path "#{File.dirname(__FILE__)}/spec/dummy_dir" FileUtils.rm_r dummy_dir, :secure => true rescue nil FileUtils.mkdir dummy_dir rescue nil # Split any files from options to pass to the spec program split = ARGV.index('--') opts = if split ARGV.slice!((split + 1)..-1).join ' ' else '-f p' end files = if ARGV.empty? Dir.glob("spec/*_spec.rb") else ARGV.pop if split ARGV.map {|f| "spec/#{File.basename(f)}" } end begin real_home = ENV['HOME'] ENV['HOME'] = dummy_dir files.each do |spec| pid = fork { puts "\n#{spec}:" exec "spec", opts, File.expand_path(spec) } Process.waitpid pid FileUtils.rm_r dummy_dir, :secure => true rescue nil end ensure ENV['HOME'] = real_home end |