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
© 2023 Keith Pitty, all rights reserved.
Comments
Hi Keith, I have resolved the problem. `ActiveRecord::Tasks` is in Rails 4. Previously, I was in Rails 3. Thanks.
Posted by: zc 4 months later
Hi zc,
It’s difficult to help without a bit more context. If you can create a gist showing the command you ran and the output, I may be able to help.
Keith
Posted by: Keith Pitty 4 months later
I am getting `NameError: uninitialized constant ActiveRecord::Tasks` when using `ActiveRecord::Tasks::DatabaseTasks`. Could you help? Thanks.
Posted by: zc 4 months later
Comments are closed for this post.