From 52f2f9810eaf3d385ca3eef8ed6fc62e4fd1f7d3 Mon Sep 17 00:00:00 2001
From: Dharam Gollapudi <dharam.gollapudi@gmail.com>
Date: Fri, 13 Nov 2015 16:58:51 -0800
Subject: Implement Rake proxy for Rails' command line interface.

Allows any Rake task to be run through `bin/rails` such as `bin/rails db:migrate`,
`bin/rails notes` etc.

The Rake tasks are appended to Rails' help output, and blend in as standard commands.
---
 guides/source/initialization.md               | 12 ++++----
 railties/lib/rails/commands.rb                |  2 +-
 railties/lib/rails/commands/commands_tasks.rb | 14 +++++++++
 railties/lib/rails/commands/rake_proxy.rb     | 42 +++++++++++++++++++++++++++
 4 files changed, 64 insertions(+), 6 deletions(-)
 create mode 100644 railties/lib/rails/commands/rake_proxy.rb

diff --git a/guides/source/initialization.md b/guides/source/initialization.md
index ebe1cb206a..1215931465 100644
--- a/guides/source/initialization.md
+++ b/guides/source/initialization.md
@@ -139,7 +139,8 @@ aliases = {
   "c"  => "console",
   "s"  => "server",
   "db" => "dbconsole",
-  "r"  => "runner"
+  "r"  => "runner",
+  "t"  => "test"
 }
 
 command = ARGV.shift
@@ -158,19 +159,20 @@ defined here to find the matching command.
 
 ### `rails/commands/command_tasks.rb`
 
-When one types an incorrect rails command, the `run_command` is responsible for
-throwing an error message. If the command is valid, a method of the same name
-is called.
+If the command is part of the COMMAND_WHITELIST, a method of the same name is called,
+if not we proxy it to rake.
 
 ```ruby
 COMMAND_WHITELIST = %w(plugin generate destroy console server dbconsole application runner new version help)
 
 def run_command!(command)
   command = parse_command(command)
+
   if COMMAND_WHITELIST.include?(command)
     send(command)
   else
-    write_error_message(command)
+    ARGV.unshift(command)
+    send(:rake)
   end
 end
 ```
diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb
index b9c4e02ca0..7627fcf5a0 100644
--- a/railties/lib/rails/commands.rb
+++ b/railties/lib/rails/commands.rb
@@ -7,7 +7,7 @@ aliases = {
   "s"  => "server",
   "db" => "dbconsole",
   "r"  => "runner",
-  "t"  => "test",
+  "t"  => "test"
 }
 
 command = ARGV.shift
diff --git a/railties/lib/rails/commands/commands_tasks.rb b/railties/lib/rails/commands/commands_tasks.rb
index 7e6b49e2a3..e1713d5798 100644
--- a/railties/lib/rails/commands/commands_tasks.rb
+++ b/railties/lib/rails/commands/commands_tasks.rb
@@ -1,3 +1,5 @@
+require 'rails/commands/rake_proxy'
+
 module Rails
   # This is a class which takes in a rails command and initiates the appropriate
   # initiation sequence.
@@ -5,6 +7,8 @@ module Rails
   # Warning: This class mutates ARGV because some commands require manipulating
   # it before they are run.
   class CommandsTasks # :nodoc:
+    include Rails::RakeProxy
+
     attr_reader :argv
 
     HELP_MESSAGE = <<-EOT
@@ -26,6 +30,7 @@ In addition to those, there are:
  runner       Run a piece of code in the application environment (short-cut alias: "r")
 
 All commands can be run with -h (or --help) for more information.
+
 EOT
 
     COMMAND_WHITELIST = %w(plugin generate destroy console server dbconsole runner new version help test)
@@ -39,6 +44,9 @@ EOT
 
       if COMMAND_WHITELIST.include?(command)
         send(command)
+      else
+        ARGV.unshift(command)
+        send(:rake)
       end
     end
 
@@ -102,6 +110,10 @@ EOT
       end
     end
 
+    def rake
+      invoke_rake
+    end
+
     def version
       argv.unshift '--version'
       require_command!("application")
@@ -109,6 +121,8 @@ EOT
 
     def help
       write_help_message
+      write_rake_tasks_help_message
+      write_rake_tasks
     end
 
     private
diff --git a/railties/lib/rails/commands/rake_proxy.rb b/railties/lib/rails/commands/rake_proxy.rb
new file mode 100644
index 0000000000..13a9aef694
--- /dev/null
+++ b/railties/lib/rails/commands/rake_proxy.rb
@@ -0,0 +1,42 @@
+require 'rake'
+
+module Rails
+  module RakeProxy #:nodoc:
+
+    RAKE_TASKS_HELP_MESSAGE = <<-EOT
+In addition to those, you can run the rake tasks as rails commands:
+
+    EOT
+
+    private
+
+      def write_rake_tasks_help_message
+        puts RAKE_TASKS_HELP_MESSAGE
+      end
+
+      def write_rake_tasks
+        width = rake_tasks.map { |t| t.name_with_args.length }.max || 10
+        rake_tasks.each do |t|
+          printf("#{Rake.application.name} %-#{width}s  # %s\n", t.name_with_args, t.comment)
+        end
+      end
+
+      def rake_tasks
+        return @rake_tasks if defined?(@rake_tasks)
+
+        require_application_and_environment!
+        Rake::TaskManager.record_task_metadata = true
+        Rake.application.instance_variable_set(:@name, 'rails')
+        Rails.application.load_tasks
+        @rake_tasks = Rake.application.tasks.select(&:comment)
+      end
+
+      def invoke_rake
+        Rake.application.standard_exception_handling do
+          Rake.application.init('rails')
+          Rake.application.load_rakefile
+          Rake.application.top_level
+        end
+      end
+  end
+end
-- 
cgit v1.2.3