Maintaining Rails applications in the real world is not always straight-forward.
Recently I needed to enable a Rails 4.0.13 application which used a MySQL database as it’s default to also use a PostgreSQL database. Being a good TDD citizen, I also needed to ensure that the new tests would run in our CI environment.
Whilst I did find a helpful post, it didn’t tell me everything I needed to know. So I thought I’d share what I did.
First of all, I configured environments for the PostgreSQL database
within config/database.yml
as follows:
# in addition to existing contents
postgres: &postgresql_defaults
adapter: postgresql
pool: 10
timeout: 60000
host: localhost
template: template0
development_foo:
database: foo_development
<<: *postgresql_defaults
test_foo:
database: foo_test
<<: *postgresql_defaults
staging_foo:
database: foo_staging
<<: *postgresql_defaults
production_foo:
database: foo_production
<<: *postgresql_defaults
In my case, I only needed to use one model class backed by a table from
the extra database. Let’s imaginatively call this class
Bar
. So the class would be defined like this:
class Bar < ActiveRecord::Base
establish_connection “#{Rails.env}\_foo”
# the rest of the class
end
Note that you probably want to introduce an abstract class which establishes the connection if you have more than one model class which needs it.
So far so good. The next step was to ensure that the CI script on
Buildkite handled the extra database. As you
would expect, the script prepares to run the tests by dropping, creating
and loading the schema of the database. To do this it uses some code
common to all our builds, which includes running bundle exec rake
db:drop
and bundle exec rake db:create
db:schema:load
.
I wanted to find a way for the setup of the extra database to piggy-back
on these rake tasks and, in so doing, use the existing script unchanged.
I discovered I could do so by using the Rake::Task#enhance
method. This allowed the rake db tasks to be augmented as follows:
namespace :db do
task drop_foo: :environment do
ActiveRecord::Tasks::DatabaseTasks.drop(foo_db_config)
end
task create_foo: :environment do
ActiveRecord::Tasks::DatabaseTasks.create(foo_db_config)
reset_connection_to_mysql_db
end
namespace :structure do
task load_foo: :environment do
if File.exist?(foo_structure_filename)
ActiveRecord::Tasks::DatabaseTasks.structure_load(foo_db_config,
foo_structure_filename)
end
end
end
end
def foo_structure_filename
"#{Rails.root}/db/foo_structure.sql"
end
def foo_db_config
ActiveRecord::Base.configurations\["#{Rails.env}\_foo"\]
end
def default_db_config
ActiveRecord::Base.configurations\["#{Rails.env}"\]
end
def reset_connection_to_mysql_db
ActiveRecord::Base.establish_connection(default_db_config)
end
Rake::Task\['db:drop'\].enhance do
Rake::Task\['db:drop_foo'\].invoke
end
Rake::Task\['db:create'\].enhance do
Rake::Task\['db:create_foo'\].invoke
end
Rake::Task\['db:schema:load'\].enhance do
Rake::Task\['db:structure:load_foo'\].invoke
end
There are a couple of things I should explain here.
Let’s assume FooApp
is the name of application which has
foo
as it’s default database. FooApp
uses SQL
rather than Ruby to define it’s schema so I copied
db/foo_structure.sql
from db/structure.sql
in
FooApp
.
Also worthy of close attention is the call to the
reset_connection_to_mysql_db
method after the
create_foo
task. Without this, rake
db:schema:load
will load the default MySQL schema into the
PostgreSQL database!
So there you have it. How to add an extra database to your Rails
application and not break CI in the process. Discovering how to use the
Rake::Task#enhance
method to facilitate this was my main
motivation for sharing this.
I hope it helps someone else.
Previous post: Bundler and Disk Space
More recently: Effective Collaboration using GitHub PRs
© 2024 Keith Pitty, all rights reserved.