This plugin fixes the rails db: tasks to respect multiple databases, and allows you to place migrations for your other dbs under db/migrate/your-special-db, starting from 0. The plugin aims to be small and non-invasive.
Making Jim Weirich feel a little bit uncomfortable
You might wonder WTF this is supposed to mean, since nobody wants to make Jim Weirich feel even slightly uncomfortable. Let me explain!
When writing this plugin, I tried to use alias_method_chain to stay the hell out of monkey patching land. The other plugin I found that does DB stuff like this had screeds of stuff copy/pasted, and this just sucks in terms of maintainablity.
You don’t get very far with this approach when you’re trying to change existing rake tasks, such as db:migrate. Sure, you can inject tasks before db:migrate like this:
task :nefarious_activities do
puts "Pinky, are you pondering what I'm pondering?"
end
task :migrate => :nefarious_activities
But what if you want to actually run db:migrate once for each extra db you defined?
Enter alias_task_chain. It can be used just like alias_method_chain, but for tasks. Here’s an example:
desc "migrates all defined databases."
task :migrate_with_other_databases => :environment do
puts "Migrating #{ RAILS_ENV }...\n\n"
puts %x{ rake db:migrate_without_other_databases }
for database in Loopy::MultipleDatabases.find_databases do
all, matching_db, *mop = *RAILS_ENV.match(Loopy::MultipleDatabases::ANY_DB)
unless matching_db.blank?
target_environment = "#{ database }_#{ matching_db }"
puts "Migrating #{ database }...\n\n"
puts %x{ rake db:migrate_without_other_databases RAILS_ENV=#{ target_environment } }
end
end
end
alias_task_chain :migrate, :other_databases
I mailed Jim Weirich with this idea, and I think he found it quite unsavoury. I think he said something along the lines of ‘it makes me feel uncomfortable’. I can’t say I can blame him - it kind of creeps me out too.
I can’t think of a more natural way to achieve this though. Any ideas out there?
Tell us what the plugin is about already!
Lets say you have an Analytics DB. Start off by creating a base class like this:
class AnalyticsModel < ActiveRecord::Base
self.abstract_class = true
establish_connection("analytics_#{ RAILS_ENV }")
end
now you need to add the appropriate entries in your database.yml:
analytics_development:
username: analytics
database: analytics_development
password: super_secret
analytics_test:
username: analytics
database: analytics_test
password: super_secret
analytics_production:
username: analytics_prod
database: analytics_production
password: super_secret
finally, add environment files to /environments. ie: analytics_test.rb, analytics_production.rb, analytics_development.rb.
db:migrate and db:test:clone have been enhanced to take care of all databases.
Example
With the above setup in place, you can now create a foder under db/migrate for your separate db. In this example, you’d create db/migrate/analytics.
Now create migrations in there, starting with 001-your-first-migration.rb.
You can run all the db tasks for your special dbs by setting RAILS_ENV. For example:
export RAILS_ENV=analytics_development
rake db:schema:dump
this will create db/analytics_development_schema.rb.
rake db:migrate works as normal. if you set RAILS_ENV=production, it will migrate ‘analytics_production’ as well as ‘production’.
Installation
Install the plugin:
./script/plugin install http://svn.playtype.net/plugins/loopy_multiple_databases/
Then add the following lines to your application’s Rakefile.rb, just above require ‘tasks/rails’:
# enhances rake with alias_task_chain method.
module Loopy
module RakeExtensions
module AliasTaskChain
def alias_task_chain(enhancable_task, feature)
original_name = Rake.application.current_scope.empty? ?
target_task : ( Rake.application.current_scope << enhancable_task ).join(":")
chained_orignal_name = "#{ original_name }_without_#{ feature }"
chained_enhanced_name = "#{ enhancable_task }_with_#{ feature }"
target_task = Rake::Task[original_name]
tasks = Rake.application.send(:eval, "@tasks")
target_task.instance_variable_set("@name", chained_orignal_name)
tasks.delete(original_name)
tasks[chained_orignal_name] = target_task
task enhancable_task => chained_enhanced_name
end
end
end
end
send :include, Loopy::RakeExtensions::AliasTaskChain



