Rails Rake Tasks - Namespaces Can Make Shorter Tasks
Please note that all opinions are that of the author.
Last Updated On: 2025-11-26 06:51:47 -0500
One of the brilliant (and frustrating) things about coding is that you can use the same language, same framework for, oh, 20 years and still find something new.
What I just realized – yesterday – was that namespaces for rake tasks can make tasks shorter.
I write a lot of rake tasks whenever I build a production system. Rake tasks are just that glue code that lets you do … anything. What I all too often find is that a rake task that starts with 20 lines because, well, a few hundred. Now that is almost never a single rake task with a few hundred lines. Normally my 20 line tasks adds another related task of say 15 lines and then a rake task of 40 lines and so on. Individually none of the rake tasks are conceptually hard to understand but as a single file with N routines in it of say N * 30 lines long (on average), that rake task feels like it is way more complex than it actually is. And that then makes you less than willing to touch it, improve it, etc.
And here’s where namespaces make all the difference. Take this rake task (tenant_databases.rake):
namespace :tenant_databases do
# bundle exec rake tenant_databases:drop\[helpfulhippie.com]
desc "Drop a tenant database (DANGEROUS)"
task :drop, [:domain] => :environment do |t, args|
domain = args[:domain]
if domain.blank?
puts "Domain required on command line i.e.: rake tenant_databases:drop[example.com]"
exit
end
tenant_database = TenantDatabase.where(domain: domain).first
if tenant_database.blank?
puts "Unable to find tenant_database for: #{domain} so exiting..."
exit
end
puts "WARNING: This will permanently delete the database for #{domain}"
puts "Database: #{tenant_database.database_name}"
print "Type the domain name to confirm: "
confirmation = STDIN.gets.chomp
if confirmation == domain
puts "Dropping database..."
config = tenant_database.connection_config
ActiveRecord::Tasks::DatabaseTasks.drop(config)
puts "Deleting tenant record..."
tenant_database.destroy
puts "Dropped tenant database and record"
else
puts "Confirmation failed. Aborted."
exit
end
end
# bundle exec rake tenant_databases:list --trace
task list: :environment do
puts "\n=== Tenants ==="
tenant_databases = TenantDatabase.order(:domain).to_a
if tenant_databases.empty?
puts "No tenant databases found"
else
tenant_databases.each do |tenant_database|
status = tenant_database.active? ? "Y" : "N"
puts "#{status} #{tenant_database.domain.ljust(30)} → #{tenant_database.database_name}"
end
puts "\nTotal: #{tenant_databases.count} tenant database(s)"
end
end
end
But the thing to realize is that the same namespace can exist in different code files. So what if we had two different rake tasks:
- tenant_databases_drop.rake
- tenant_databases_list.rake
namespace :tenant_databases do
# bundle exec rake tenant_databases:drop --trace
desc "Drop a tenant database (DANGEROUS)"
task :drop, [:domain] => :environment do |t, args|
domain = args[:domain]
if domain.blank?
puts "Domain required on command line i.e.: rake tenant_databases:drop[example.com]"
exit
end
tenant_database = TenantDatabase.where(domain: domain).first
if tenant_database.blank?
puts "Unable to find tenant_database for: #{domain} so exiting..."
exit
end
puts "WARNING: This will permanently delete the database for #{domain}"
puts "Database: #{tenant_database.database_name}"
print "Type the domain name to confirm: "
confirmation = STDIN.gets.chomp
if confirmation == domain
puts "Dropping database..."
config = tenant_database.connection_config
ActiveRecord::Tasks::DatabaseTasks.drop(config)
puts "Deleting tenant record..."
tenant_database.destroy
puts "Dropped tenant database and record"
else
puts "Confirmation failed. Aborted."
exit
end
end
end
namespace :tenant_databases do
# bundle exec rake tenant_databases:list --trace
task list: :environment do
puts "\n=== Tenants ==="
tenant_databases = TenantDatabase.order(:domain).to_a
if tenant_databases.empty?
puts "No tenant databases found"
else
tenant_databases.each do |tenant_database|
status = tenant_database.active? ? "Y" : "N"
puts "#{status} #{tenant_database.domain.ljust(30)} → #{tenant_database.database_name}"
end
puts "\nTotal: #{tenant_databases.count} tenant database(s)"
end
end
end
So at the complexity cost of 2 files versus 1 file, you get the benefit of when you own that rake task in your editor, you only see ONE task. That’s a huge win. And if you’re concerned about common methods defined in the rake task (which you really shouldn’t have since Rake tasks still can’t be easily tested), remember that Rake tasks are a single namespace which means that any common code anywhere in the namespace will be available to you.
This was a mildly long blog post to point out something that maybe a bunch of readers find obvious but it did take me a long time (2 decades) to realize this so maybe it helps someone else out there.
Happy Thanksgiving if you are in the Americas!