Skip to content

Commit

Permalink
Load Rake tasks only once for command suggestions
Browse files Browse the repository at this point in the history
Follow-up to rails#47208.

`UnrecognizedCommandError` calls `printing_commands` to make "Did you
mean?" suggestions.  `printing_commands` calls `RakeCommand#rake_tasks`,
which loads the Rake tasks if they have not been memoized.  If the tasks
have already been loaded by `RakeCommand#perform` (but not memoized by
`RakeCommand#rake_tasks`), then the tasks will be loaded a second time.
This can cause, for example, constant redefinition warnings if the task
files define constants.

Therefore, this commit memoizes the tasks from `RakeCommand#perform`
before raising `UnrecognizedCommandError`.
  • Loading branch information
jonathanhefner committed Mar 20, 2023
1 parent 52d1920 commit a7dbd57
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 11 deletions.
22 changes: 11 additions & 11 deletions railties/lib/rails/commands/rake/rake_command.rb
Expand Up @@ -18,7 +18,9 @@ def perform(task, args, config)
Rake.with_application do |rake|
rake.init("rails", [task, *args])
rake.load_rakefile

if unrecognized_task = rake.top_level_tasks.find { |task| !rake.lookup(task[/[^\[]+/]) }
@rake_tasks = rake.tasks
raise UnrecognizedCommandError.new(unrecognized_task)
end

Expand All @@ -31,24 +33,22 @@ def perform(task, args, config)

private
def rake_tasks
require_rake

return @rake_tasks if defined?(@rake_tasks)

require_application!

Rake::TaskManager.record_task_metadata = true
Rake.application.instance_variable_set(:@name, "rails")
load_tasks
@rake_tasks = Rake.application.tasks.select(&:comment)
@rake_tasks ||= begin
require_rake
require_application!
Rake.application.instance_variable_set(:@name, "rails")
load_tasks
Rake.application.tasks
end
end

def formatted_rake_tasks
rake_tasks.map { |t| [ t.name_with_args, t.comment ] }
rake_tasks.filter_map { |t| [ t.name_with_args, t.comment ] if t.comment }
end

def require_rake
require "rake" # Defer booting Rake until we know it's needed.
Rake::TaskManager.record_task_metadata = true # Preserve task comments.
end
end
end
Expand Down
12 changes: 12 additions & 0 deletions railties/test/command/help_integration_test.rb
Expand Up @@ -14,6 +14,18 @@ class Rails::Command::HelpIntegrationTest < ActiveSupport::TestCase
assert_match "Did you mean? version", output
end

test "loads Rake tasks only once on unrecognized command" do
app_file "lib/tasks/my_task.rake", <<~RUBY
puts "MY_TASK already defined? => \#{!!defined?(MY_TASK)}"
MY_TASK = true
RUBY

output = rails "vershen", allow_failure: true

assert_match "MY_TASK already defined? => false", output
assert_no_match "MY_TASK already defined? => true", output
end

test "prints help via `X:help` command when running `X` and `X:X` command is not defined" do
help = rails "dev:help"
output = rails "dev", allow_failure: true
Expand Down

0 comments on commit a7dbd57

Please sign in to comment.