Datamapper for Multiple Databases in Rails 3
DataMapper is awesome. It’s flexible, you define the properties in the model itself, it deals with all sorts of columns, it can create UUIDs easily, it is fantastic for dealing with legacy, or just not designed with Rails’ ActiveRecord in mind databases. Hell, it shoveled my driveway, hooked up my cable, and delivered our beautiful, 7 pound, 2 ounce baby boy last Tuesday. (I kid! I don’t have a baby. Yet.)
It can also connect to multiple databases, even when used as the ORM in
a Rails 3 app. Unfortunately, this isn’t mentioned on DataMapper’s
website, that I could find. However, people
with their wits about them (I am not in that set of people) might think
to check out the README for the dm-rails project on
Github. In there, it shows
how to lay out your database.yml
properly. It’s important. Go see it.
Go. I’ll wait.
I mention this, and forced you to go look, mostly to help someone searching frantically. You there. Guy who has been searching for this frantically in futile frustration for an hour: Your journey is over. Let’s move on.
I had to do a bit of work to get RSpec and Cucumber behaving correctly.
I want my test databases, or repositories, cleared after each test. I
solved RSpec first, so we’ll start with that. My spec/spec_helper.rb
looks something like this:
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
RSpec.configure do |config|
config.mock_with :rspec
config.before(:each) do
DataMapper::Model.descendants.each do |model|
DataMapper.repository(model.default_repository_name).adapter.execute("SET foreign_key_checks = 0")
DataMapper.repository(model.default_repository_name).adapter.execute("TRUNCATE TABLE #{model.storage_name}")
end
end
end
Clearly the code above is specific to MySQL. If you’d rather avoid shackling a specific database server to your app’s ankle, you might try something like database_cleaner.
The interesting part is probably
DataMapper.repository(model.default_repository_name)
. I define
self.default_repository_name
differently for models that aren’t using,
well, :default
. That allows this to work (among other things in
DataMapper). I recommend it. For instance:
class SecondaryUsinThing
include DataMapper::Resource
def self.default_repository_name
:secondary
end
# ...
end
I needed Cucumber to do some of this crazy junk also, of course. The
dm-rails gem recommended a before and after block to add to a
features/support/datamapper.rb
, but I had to add a bit to mine to make
it work.
Before do
DataMapper::Model.descendants.each do |model|
DataMapper.repository(model.default_repository_name).adapter.execute("SET foreign_key_checks = 0")
DataMapper.repository(model.default_repository_name).adapter.execute("TRUNCATE TABLE #{model.storage_name}")
end
[:default, :secondary].each do |repository|
DataMapper.send("repository", repository) do |repo|
transaction = DataMapper::Transaction.new(repo)
transaction.begin
repo.adapter.push_transaction(transaction)
end
end
end
After do
[:default, :secondary].each do |repository|
DataMapper.send("repository", repository) do |repo|
repo.adapter.pop_transaction.rollback rescue nil
end
end
end
Familiar, right? That should get your testing mojo rising. It’s basically opening the door for DataMapper, so he can come in and repaint your nursery while teaching your newborn sign language, or one of his many other talents.