NullDB for DataMapper

The first Ruby library I ever released, and still the one I get the most emails about, is NullDB. NullDB is an ActiveRecord database adapter which simply turns every database operation into a no-op. It is useful for speeding up tests which don’t rely on the database, as well as verifying that they are, in fact, independent of the database.

Lately I’ve been working with DataMapper and I’d begun to wonder how difficult it would be to write a DataMapper version of NullDB. As it turns out, the answer is “trivially easy”. In fact, it probably doesn’t even warrant making a library out of it – all the tools needed are right in the DataMapper distribution.

The task of a DataMapper null adapter is simplified in comparison to the ActiveRecord version by dint of DataMapper’s differing architecture. Where ActiveRecord scans the DB schema to extrapolate tables and columns, DataMapper makes the model classes the sole canonical source of schema information. This means that all the mucking about with schema.rb files that the original NullDB had to do is unnecessary.

The simplest form of a null database adapter is one that simply raises an exception every time code tries to touch the database. This kind of null backend is useful when you want to ensure that the software under test is completely isolated from the database. Rails Views are one example of code which you might want to keep rigidly database-independent in this fashion.

Setting up this type of null database backend in DataMapper is trivial:

  DataMapper.setup(:default, "abstract::")

This code uses the “abstract” adapter which comes with DataMapper. The Abstract adapter is primarily intended to provide a base class on which to build other adapters. But for our purposes it makes for a poor-man’s null database backend. Any attempt to write to or query from the database while this adapter is in effect will result in a NotImplementedError being raised:

  p = Post.new(:title => "NullDB for DataMapper")
  p.save # => NotImplementedError

What if we want to incoporate this technique into a complete test suite which includes both database-dependent and database-free tests? We need a way to make the null database active only while selected tests are being run. Once again, DataMapper’s design makes it easy to achieve our ends. DataMapper incorporates the concept of a “repository stack”. instead of only having a single database adapter active for a whole session, or having different models tied to different adapters, DataMapper keeps a stack of adapter sessions in a stack. New sessions can be pushed onto the stack, and then popped off when they are no longer needed.

Here’s an example of how to set up a single Test::Unit test case to use a null database backend, while leaving other tests unaffected:

class FooTest < Test::Unit::TestCase
  def setup
    DataMapper.setup(:null, "abstract::")
    DataMapper::Repository.context.push(DataMapper.repository(:null))
  end

  def teardown
    DataMapper::Repository.context.pop
  end

  def test_bar
    # ...
  end
end

There’s one kind of test that NullDB enables which the code we’ve looked at so far does not allow. That is, testing of after-save hooks. We can’t test an after-save hook with the code above, because a call to #save will error out before it ever gets to the after-save callback.

While DataMapper does not include an actual null-object style backend which turns all database operations into no-ops, it includes something close which will permit us to test after-save hooks. DataMapper ships with an in-memory store, which implements the DataMapper API in terms of a simple in-memory collection of Ruby objects. Here’s an example of triggering an after-save hook with an in-memory backend:

class Foo
  include DataMapper::Resource

  property :id, Serial

  after :save do
    puts "Saved!"
  end
end

DataMapper.setup(:ephemeral, "in_memory::")

DataMapper.repository(:ephemeral) do
  f = Foo.new
  f.save
end

Running this script produces the output Saved!. This code also demonstrates the block-form of the the DataMapper.repository method, which pushes a session onto the repository stack, yields to the passed block, and then pops the session back off the stack.

So there you have it, a couple of quick and easy ways to approximate a null-database in DataMapper. I think it’s a testament to DataMapper’s design that it’s so easy to assemble the pieces it provides in ways that the writers may not have anticipated. I hope you find these techniques useful!

Leave a Reply

Your email address will not be published. Required fields are marked *