railties 8.0.3 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +101 -206
  3. data/lib/minitest/rails_plugin.rb +48 -12
  4. data/lib/rails/application/bootstrap.rb +5 -0
  5. data/lib/rails/application/configuration.rb +31 -9
  6. data/lib/rails/application/default_middleware_stack.rb +1 -1
  7. data/lib/rails/application/finisher.rb +2 -1
  8. data/lib/rails/application/routes_reloader.rb +0 -1
  9. data/lib/rails/application.rb +1 -3
  10. data/lib/rails/code_statistics.rb +4 -1
  11. data/lib/rails/command/base.rb +0 -2
  12. data/lib/rails/command/environment_argument.rb +0 -1
  13. data/lib/rails/command.rb +1 -1
  14. data/lib/rails/commands/app/update_command.rb +1 -0
  15. data/lib/rails/commands/console/irb_console.rb +4 -4
  16. data/lib/rails/commands/credentials/credentials_command.rb +25 -5
  17. data/lib/rails/commands/encrypted/encrypted_command.rb +0 -1
  18. data/lib/rails/engine.rb +0 -1
  19. data/lib/rails/gem_version.rb +2 -2
  20. data/lib/rails/generators/actions.rb +2 -3
  21. data/lib/rails/generators/app_base.rb +49 -28
  22. data/lib/rails/generators/database.rb +1 -1
  23. data/lib/rails/generators/erb/authentication/authentication_generator.rb +2 -0
  24. data/lib/rails/generators/erb/scaffold/templates/partial.html.erb.tt +2 -2
  25. data/lib/rails/generators/generated_attribute.rb +1 -1
  26. data/lib/rails/generators/migration.rb +0 -1
  27. data/lib/rails/generators/rails/app/app_generator.rb +16 -5
  28. data/lib/rails/generators/rails/app/templates/Dockerfile.tt +19 -15
  29. data/lib/rails/generators/rails/app/templates/Gemfile.tt +6 -1
  30. data/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt +5 -0
  31. data/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt +1 -0
  32. data/lib/rails/generators/rails/app/templates/bin/bundler-audit.tt +5 -0
  33. data/lib/rails/generators/rails/app/templates/bin/ci.tt +5 -0
  34. data/lib/rails/generators/rails/app/templates/bin/rubocop.tt +1 -1
  35. data/lib/rails/generators/rails/app/templates/bin/setup.tt +1 -0
  36. data/lib/rails/generators/rails/app/templates/config/bundler-audit.yml.tt +5 -0
  37. data/lib/rails/generators/rails/app/templates/config/ci.rb.tt +38 -0
  38. data/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt +9 -1
  39. data/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt +10 -2
  40. data/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml.tt +1 -1
  41. data/lib/rails/generators/rails/app/templates/config/databases/trilogy.yml.tt +9 -1
  42. data/lib/rails/generators/rails/app/templates/config/deploy.yml.tt +21 -16
  43. data/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +8 -0
  44. data/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +2 -2
  45. data/lib/rails/generators/rails/app/templates/config/initializers/content_security_policy.rb.tt +4 -0
  46. data/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_8_1.rb.tt +74 -0
  47. data/lib/rails/generators/rails/app/templates/config/puma.rb.tt +3 -2
  48. data/lib/rails/generators/rails/app/templates/config/storage.yml.tt +0 -7
  49. data/lib/rails/generators/rails/app/templates/docker-entrypoint.tt +0 -6
  50. data/lib/rails/generators/rails/app/templates/github/ci.yml.tt +107 -21
  51. data/lib/rails/generators/rails/app/templates/github/dependabot.yml +2 -2
  52. data/lib/rails/generators/rails/app/templates/kamal-secrets.tt +4 -1
  53. data/lib/rails/generators/rails/app/templates/public/400.html +26 -5
  54. data/lib/rails/generators/rails/app/templates/public/404.html +27 -6
  55. data/lib/rails/generators/rails/app/templates/public/406-unsupported-browser.html +25 -4
  56. data/lib/rails/generators/rails/app/templates/public/422.html +26 -5
  57. data/lib/rails/generators/rails/app/templates/public/500.html +27 -6
  58. data/lib/rails/generators/rails/authentication/authentication_generator.rb +8 -6
  59. data/lib/rails/generators/rails/authentication/templates/app/controllers/passwords_controller.rb.tt +6 -0
  60. data/lib/rails/generators/rails/authentication/templates/app/controllers/sessions_controller.rb.tt +2 -2
  61. data/lib/rails/generators/rails/authentication/templates/app/views/passwords_mailer/reset.html.erb.tt +3 -1
  62. data/lib/rails/generators/rails/authentication/templates/app/views/passwords_mailer/reset.text.erb.tt +3 -1
  63. data/lib/rails/generators/rails/benchmark/USAGE +1 -1
  64. data/lib/rails/generators/rails/benchmark/templates/benchmark.rb.tt +0 -2
  65. data/lib/rails/generators/rails/devcontainer/devcontainer_generator.rb +1 -1
  66. data/lib/rails/generators/rails/devcontainer/templates/devcontainer/Dockerfile.tt +4 -0
  67. data/lib/rails/generators/rails/devcontainer/templates/devcontainer/compose.yaml.tt +2 -2
  68. data/lib/rails/generators/rails/devcontainer/templates/devcontainer/devcontainer.json.tt +1 -1
  69. data/lib/rails/generators/rails/encryption_key_file/encryption_key_file_generator.rb +17 -5
  70. data/lib/rails/generators/rails/master_key/master_key_generator.rb +0 -12
  71. data/lib/rails/generators/rails/plugin/plugin_generator.rb +1 -0
  72. data/lib/rails/generators/rails/plugin/templates/Rakefile.tt +0 -4
  73. data/lib/rails/generators/rails/plugin/templates/github/ci.yml.tt +20 -9
  74. data/lib/rails/generators/rails/plugin/templates/github/dependabot.yml +2 -2
  75. data/lib/rails/generators/rails/script/USAGE +1 -1
  76. data/lib/rails/generators/test_unit/authentication/authentication_generator.rb +13 -0
  77. data/lib/rails/generators/test_unit/authentication/templates/test/controllers/passwords_controller_test.rb.tt +67 -0
  78. data/lib/rails/generators/test_unit/authentication/templates/test/controllers/sessions_controller_test.rb +33 -0
  79. data/lib/rails/generators/test_unit/authentication/templates/test/models/user_test.rb.tt +4 -3
  80. data/lib/rails/generators/test_unit/authentication/templates/test/test_helpers/session_test_helper.rb.tt +19 -0
  81. data/lib/rails/generators/test_unit/model/templates/fixtures.yml.tt +1 -1
  82. data/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb +4 -2
  83. data/lib/rails/generators/testing/behavior.rb +0 -3
  84. data/lib/rails/generators.rb +3 -1
  85. data/lib/rails/health_controller.rb +8 -2
  86. data/lib/rails/info.rb +4 -5
  87. data/lib/rails/info_controller.rb +4 -5
  88. data/lib/rails/initializable.rb +63 -19
  89. data/lib/rails/rack/silence_request.rb +5 -2
  90. data/lib/rails/railtie/configurable.rb +0 -1
  91. data/lib/rails/railtie.rb +0 -1
  92. data/lib/rails/tasks/statistics.rake +3 -21
  93. data/lib/rails/tasks.rb +1 -3
  94. data/lib/rails/templates/rails/info/notes.html.erb +23 -0
  95. data/lib/rails/templates/rails/mailers/email.html.erb +2 -1
  96. data/lib/rails/templates/rails/welcome/index.html.erb +19 -3
  97. data/lib/rails/test_unit/reporter.rb +5 -4
  98. data/lib/rails/test_unit/runner.rb +8 -5
  99. data/lib/rails.rb +9 -2
  100. metadata +18 -15
  101. data/lib/rails/console/methods.rb +0 -7
  102. data/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_8_0.rb.tt +0 -30
  103. data/lib/rails/generators/test_unit/plugin/plugin_generator.rb +0 -15
  104. data/lib/rails/generators/test_unit/plugin/templates/%file_name%_test.rb.tt +0 -7
  105. data/lib/rails/generators/test_unit/plugin/templates/test_helper.rb +0 -2
@@ -32,22 +32,10 @@ module Rails
32
32
  end
33
33
  end
34
34
 
35
- def ignore_master_key_file
36
- key_file_generator.ignore_key_file(MASTER_KEY_PATH, ignore: key_ignore)
37
- end
38
-
39
- def ignore_master_key_file_silently
40
- key_file_generator.ignore_key_file_silently(MASTER_KEY_PATH, ignore: key_ignore)
41
- end
42
-
43
35
  private
44
36
  def key_file_generator
45
37
  EncryptionKeyFileGenerator.new([], options)
46
38
  end
47
-
48
- def key_ignore
49
- [ "", "# Ignore master key for decrypting credentials and more.", "/#{MASTER_KEY_PATH}", "" ].join("\n")
50
- end
51
39
  end
52
40
  end
53
41
  end
@@ -124,6 +124,7 @@ module Rails
124
124
  opts[:force] = force
125
125
  opts[:skip_thruster] = true
126
126
  opts[:skip_brakeman] = true
127
+ opts[:skip_bundler_audit] = true
127
128
  opts[:skip_bundle] = true
128
129
  opts[:skip_ci] = true
129
130
  opts[:skip_kamal] = true
@@ -4,10 +4,6 @@ require "bundler/setup"
4
4
  APP_RAKEFILE = File.expand_path("<%= dummy_path -%>/Rakefile", __dir__)
5
5
  load "rails/tasks/engine.rake"
6
6
  <% end -%>
7
- <% if engine? -%>
8
-
9
- load "rails/tasks/statistics.rake"
10
- <% end -%>
11
7
  <% unless options[:skip_gemspec] -%>
12
8
 
13
9
  require "bundler/gem_tasks"
@@ -3,22 +3,35 @@ name: CI
3
3
  on:
4
4
  pull_request:
5
5
  push:
6
- branches: [ main ]
6
+ branches: [ <%= user_default_branch %> ]
7
7
 
8
8
  jobs:
9
9
  <%- unless skip_rubocop? -%>
10
10
  lint:
11
11
  runs-on: ubuntu-latest
12
+ env:
13
+ RUBY_VERSION: <%= ENV["RBENV_VERSION"] || ENV["rvm_ruby_string"] || "#{RUBY_ENGINE}-#{RUBY_ENGINE_VERSION}" %>
14
+ RUBOCOP_CACHE_ROOT: tmp/rubocop
12
15
  steps:
13
16
  - name: Checkout code
14
- uses: actions/checkout@v4
17
+ uses: actions/checkout@v5
15
18
 
16
19
  - name: Set up Ruby
17
20
  uses: ruby/setup-ruby@v1
18
21
  with:
19
- ruby-version: <%= ENV["RBENV_VERSION"] || ENV["rvm_ruby_string"] || "#{RUBY_ENGINE}-#{RUBY_ENGINE_VERSION}" %>
22
+ ruby-version: ${{ env.RUBY_VERSION }}
20
23
  bundler-cache: true
21
24
 
25
+ - name: Prepare RuboCop cache
26
+ uses: actions/cache@v4
27
+ env:
28
+ DEPENDENCIES_HASH: ${{ hashFiles('**/.rubocop.yml', '**/.rubocop_todo.yml', 'Gemfile.lock') }}
29
+ with:
30
+ path: ${{ env.RUBOCOP_CACHE_ROOT }}
31
+ key: rubocop-${{ runner.os }}-${{ env.RUBY_VERSION }}-${{ env.DEPENDENCIES_HASH }}-${{ github.ref_name == github.event.repository.default_branch && github.run_id || 'default' }}
32
+ restore-keys: |
33
+ rubocop-${{ runner.os }}-${{ env.RUBY_VERSION }}-${{ env.DEPENDENCIES_HASH }}-
34
+
22
35
  - name: Lint code for consistent style
23
36
  run: bin/rubocop -f github
24
37
 
@@ -30,7 +43,7 @@ jobs:
30
43
  <%- if options[:database] == "sqlite3" -%>
31
44
  # services:
32
45
  # redis:
33
- # image: redis
46
+ # image: valkey/valkey:8
34
47
  # ports:
35
48
  # - 6379:6379
36
49
  # options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
@@ -56,18 +69,15 @@ jobs:
56
69
  <%- end -%>
57
70
 
58
71
  # redis:
59
- # image: redis
72
+ # image: valkey/valkey:8
60
73
  # ports:
61
74
  # - 6379:6379
62
75
  # options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
63
76
 
64
77
  <%- end -%>
65
78
  steps:
66
- - name: Install packages
67
- run: sudo apt-get update && sudo apt-get install --no-install-recommends -y <%= ci_packages.join(" ") %>
68
-
69
79
  - name: Checkout code
70
- uses: actions/checkout@v4
80
+ uses: actions/checkout@v5
71
81
 
72
82
  - name: Set up Ruby
73
83
  uses: ruby/setup-ruby@v1
@@ -91,6 +101,7 @@ jobs:
91
101
  <%- elsif options[:database] == "postgresql" -%>
92
102
  DATABASE_URL: postgres://postgres:postgres@localhost:5432
93
103
  <%- end -%>
104
+ # RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
94
105
  # REDIS_URL: redis://localhost:6379/0
95
106
  run: <%= test_command %>
96
107
 
@@ -3,10 +3,10 @@ updates:
3
3
  - package-ecosystem: bundler
4
4
  directory: "/"
5
5
  schedule:
6
- interval: daily
6
+ interval: weekly
7
7
  open-pull-requests-limit: 10
8
8
  - package-ecosystem: github-actions
9
9
  directory: "/"
10
10
  schedule:
11
- interval: daily
11
+ interval: weekly
12
12
  open-pull-requests-limit: 10
@@ -9,7 +9,7 @@ Example:
9
9
  script/my_script.rb
10
10
 
11
11
  You can run the script using:
12
- `ruby script/my_script.rb`
12
+ `bin/rails runner script/my_script.rb`
13
13
 
14
14
  You can specify a folder:
15
15
  `bin/rails generate script cleanup/my_script`
@@ -10,9 +10,22 @@ module TestUnit # :nodoc:
10
10
  template "test/models/user_test.rb"
11
11
  end
12
12
 
13
+ def create_controller_test_files
14
+ template "test/controllers/sessions_controller_test.rb"
15
+ template "test/controllers/passwords_controller_test.rb"
16
+ end
17
+
13
18
  def create_mailer_preview_files
14
19
  template "test/mailers/previews/passwords_mailer_preview.rb" if defined?(ActionMailer::Railtie)
15
20
  end
21
+
22
+ def create_test_helper_files
23
+ template "test/test_helpers/session_test_helper.rb"
24
+ end
25
+
26
+ def configure_test_helper
27
+ inject_into_file "test/test_helper.rb", "require_relative \"test_helpers/session_test_helper\"\n", after: "require \"rails/test_help\"\n"
28
+ end
16
29
  end
17
30
  end
18
31
  end
@@ -0,0 +1,67 @@
1
+ require "test_helper"
2
+
3
+ class PasswordsControllerTest < ActionDispatch::IntegrationTest
4
+ setup { @user = User.take }
5
+
6
+ test "new" do
7
+ get new_password_path
8
+ assert_response :success
9
+ end
10
+
11
+ test "create" do
12
+ post passwords_path, params: { email_address: @user.email_address }
13
+ assert_enqueued_email_with PasswordsMailer, :reset, args: [ @user ]
14
+ assert_redirected_to new_session_path
15
+
16
+ follow_redirect!
17
+ assert_notice "reset instructions sent"
18
+ end
19
+
20
+ test "create for an unknown user redirects but sends no mail" do
21
+ post passwords_path, params: { email_address: "missing-user@example.com" }
22
+ assert_enqueued_emails 0
23
+ assert_redirected_to new_session_path
24
+
25
+ follow_redirect!
26
+ assert_notice "reset instructions sent"
27
+ end
28
+
29
+ test "edit" do
30
+ get edit_password_path(@user.password_reset_token)
31
+ assert_response :success
32
+ end
33
+
34
+ test "edit with invalid password reset token" do
35
+ get edit_password_path("invalid token")
36
+ assert_redirected_to new_password_path
37
+
38
+ follow_redirect!
39
+ assert_notice "reset link is invalid"
40
+ end
41
+
42
+ test "update" do
43
+ assert_changes -> { @user.reload.password_digest } do
44
+ put password_path(@user.password_reset_token), params: { password: "new", password_confirmation: "new" }
45
+ assert_redirected_to new_session_path
46
+ end
47
+
48
+ follow_redirect!
49
+ assert_notice "Password has been reset"
50
+ end
51
+
52
+ test "update with non matching passwords" do
53
+ token = @user.password_reset_token
54
+ assert_no_changes -> { @user.reload.password_digest } do
55
+ put password_path(token), params: { password: "no", password_confirmation: "match" }
56
+ assert_redirected_to edit_password_path(token)
57
+ end
58
+
59
+ follow_redirect!
60
+ assert_notice "Passwords did not match"
61
+ end
62
+
63
+ private
64
+ def assert_notice(text)
65
+ assert_select "div", /#{text}/
66
+ end
67
+ end
@@ -0,0 +1,33 @@
1
+ require "test_helper"
2
+
3
+ class SessionsControllerTest < ActionDispatch::IntegrationTest
4
+ setup { @user = User.take }
5
+
6
+ test "new" do
7
+ get new_session_path
8
+ assert_response :success
9
+ end
10
+
11
+ test "create with valid credentials" do
12
+ post session_path, params: { email_address: @user.email_address, password: "password" }
13
+
14
+ assert_redirected_to root_path
15
+ assert cookies[:session_id]
16
+ end
17
+
18
+ test "create with invalid credentials" do
19
+ post session_path, params: { email_address: @user.email_address, password: "wrong" }
20
+
21
+ assert_redirected_to new_session_path
22
+ assert_nil cookies[:session_id]
23
+ end
24
+
25
+ test "destroy" do
26
+ sign_in_as(User.take)
27
+
28
+ delete session_path
29
+
30
+ assert_redirected_to new_session_path
31
+ assert_empty cookies[:session_id]
32
+ end
33
+ end
@@ -1,7 +1,8 @@
1
1
  require "test_helper"
2
2
 
3
3
  class UserTest < ActiveSupport::TestCase
4
- # test "the truth" do
5
- # assert true
6
- # end
4
+ test "downcases and strips email_address" do
5
+ user = User.new(email_address: " DOWNCASED@EXAMPLE.COM ")
6
+ assert_equal("downcased@example.com", user.email_address)
7
+ end
7
8
  end
@@ -0,0 +1,19 @@
1
+ module SessionTestHelper
2
+ def sign_in_as(user)
3
+ Current.session = user.sessions.create!
4
+
5
+ ActionDispatch::TestRequest.create.cookie_jar.tap do |cookie_jar|
6
+ cookie_jar.signed[:session_id] = Current.session.id
7
+ cookies["session_id"] = cookie_jar[:session_id]
8
+ end
9
+ end
10
+
11
+ def sign_out
12
+ Current.session&.destroy!
13
+ cookies.delete("session_id")
14
+ end
15
+ end
16
+
17
+ ActiveSupport.on_load(:action_dispatch_integration_test) do
18
+ include SessionTestHelper
19
+ end
@@ -4,7 +4,7 @@
4
4
  <%= name %>:
5
5
  <% attributes.each do |attribute| -%>
6
6
  <%- if attribute.password_digest? -%>
7
- password_digest: <%%= BCrypt::Password.create("secret") %>
7
+ password_digest: <%= BCrypt::Password.create("secret") %> # Generated with BCrypt::Password.create("secret")
8
8
  <%- elsif attribute.reference? -%>
9
9
  <%= yaml_key_value(attribute.column_name.delete_suffix("_id"), attribute.default || name) %>
10
10
  <%- elsif !attribute.virtual? -%>
@@ -14,7 +14,7 @@ module TestUnit # :nodoc:
14
14
  desc: "Generate API functional tests"
15
15
 
16
16
  class_option :system_tests, type: :string,
17
- desc: "Skip system test files"
17
+ desc: "Generate system test files (set to 'true' to enable)"
18
18
 
19
19
  argument :attributes, type: :array, default: [], banner: "field:type field:type"
20
20
 
@@ -23,7 +23,9 @@ module TestUnit # :nodoc:
23
23
  template template_file,
24
24
  File.join("test/controllers", controller_class_path, "#{controller_file_name}_controller_test.rb")
25
25
 
26
- if !options.api? && options[:system_tests]
26
+ # Generate system tests if this isn't an API only app and the system
27
+ # tests option is true
28
+ if !options.api? && options[:system_tests] == "true"
27
29
  template "system_test.rb", File.join("test/system", class_path, "#{file_name.pluralize}_test.rb")
28
30
  end
29
31
  end
@@ -1,11 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/class/attribute"
4
- require "active_support/core_ext/module/delegation"
5
3
  require "active_support/core_ext/hash/reverse_merge"
6
4
  require "active_support/core_ext/kernel/reporting"
7
5
  require "active_support/testing/stream"
8
- require "active_support/concern"
9
6
  require "rails/generators"
10
7
 
11
8
  module Rails
@@ -157,7 +157,9 @@ module Rails
157
157
  "action_text:install",
158
158
  "action_mailbox:install",
159
159
  "devcontainer"
160
- ]
160
+ ].tap do |h|
161
+ h << "test_unit" if test.to_s != "test_unit"
162
+ end
161
163
  end
162
164
  end
163
165
 
@@ -43,11 +43,17 @@ module Rails
43
43
 
44
44
  private
45
45
  def render_up
46
- render html: html_status(color: "green")
46
+ respond_to do |format|
47
+ format.html { render html: html_status(color: "green") }
48
+ format.json { render json: { status: "up", timestamp: Time.current.iso8601 } }
49
+ end
47
50
  end
48
51
 
49
52
  def render_down
50
- render html: html_status(color: "red"), status: 500
53
+ respond_to do |format|
54
+ format.html { render html: html_status(color: "red"), status: 500 }
55
+ format.json { render json: { status: "down", timestamp: Time.current.iso8601 }, status: 500 }
56
+ end
51
57
  end
52
58
 
53
59
  def html_status(color:)
data/lib/rails/info.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "cgi/escape"
4
- require "cgi/util" if RUBY_VERSION < "3.5"
3
+ require "active_support/core_ext/erb/util"
5
4
 
6
5
  module Rails
7
6
  # This module helps build the runtime properties that are displayed in
@@ -44,11 +43,11 @@ module Rails
44
43
  def to_html
45
44
  (+"<table>").tap do |table|
46
45
  properties.each do |(name, value)|
47
- table << %(<tr><td class="name">#{CGI.escapeHTML(name.to_s)}</td>)
46
+ table << %(<tr><td class="name">#{ERB::Util.html_escape(name.to_s)}</td>)
48
47
  formatted_value = if value.kind_of?(Array)
49
- "<ul>" + value.map { |v| "<li>#{CGI.escapeHTML(v.to_s)}</li>" }.join + "</ul>"
48
+ "<ul>" + value.map { |v| "<li>#{ERB::Util.html_escape(v.to_s)}</li>" }.join + "</ul>"
50
49
  else
51
- CGI.escapeHTML(value.to_s)
50
+ ERB::Util.html_escape(value.to_s)
52
51
  end
53
52
  table << %(<td class="value">#{formatted_value}</td></tr>)
54
53
  end
@@ -27,15 +27,14 @@ class Rails::InfoController < Rails::ApplicationController # :nodoc:
27
27
  fuzzy: matching_routes(query: query, exact_match: false)
28
28
  }
29
29
  else
30
- @routes_inspector = ActionDispatch::Routing::RoutesInspector.new(_routes.routes)
30
+ @routes_inspector = ActionDispatch::Routing::RoutesInspector.new(Rails.application.routes.routes)
31
31
  @page_title = "Routes"
32
32
  end
33
33
  end
34
34
 
35
35
  def notes
36
- @annotations = Rails::SourceAnnotationExtractor.new(
37
- Rails::SourceAnnotationExtractor::Annotation.tags.join("|")
38
- ).find(
36
+ tags = params[:tag].presence || Rails::SourceAnnotationExtractor::Annotation.tags.join("|")
37
+ @annotations = Rails::SourceAnnotationExtractor.new(tags).find(
39
38
  Rails::SourceAnnotationExtractor::Annotation.directories
40
39
  )
41
40
  end
@@ -47,7 +46,7 @@ class Rails::InfoController < Rails::ApplicationController # :nodoc:
47
46
  normalized_path = ("/" + query).squeeze("/")
48
47
  query_without_url_or_path_suffix = query.gsub(/(\w)(_path$)/, '\1').gsub(/(\w)(_url$)/, '\1')
49
48
 
50
- _routes.routes.filter_map do |route|
49
+ Rails.application.routes.routes.filter_map do |route|
51
50
  route_wrapper = ActionDispatch::Routing::RouteWrapper.new(route)
52
51
 
53
52
  if exact_match
@@ -9,23 +9,15 @@ module Rails
9
9
  end
10
10
 
11
11
  class Initializer
12
- attr_reader :name, :block
12
+ attr_reader :name, :block, :before, :after
13
13
 
14
- def initialize(name, context, options, &block)
15
- options[:group] ||= :default
16
- @name, @context, @options, @block = name, context, options, block
17
- end
18
-
19
- def before
20
- @options[:before]
21
- end
22
-
23
- def after
24
- @options[:after]
14
+ def initialize(name, context, before:, after:, group: nil, &block)
15
+ @group = group || :default
16
+ @name, @before, @after, @context, @block = name, before, after, context, block
25
17
  end
26
18
 
27
19
  def belongs_to?(group)
28
- @options[:group] == group || @options[:group] == :all
20
+ @group == group || @group == :all
29
21
  end
30
22
 
31
23
  def run(*args)
@@ -34,7 +26,7 @@ module Rails
34
26
 
35
27
  def bind(context)
36
28
  return self if @context
37
- Initializer.new(@name, context, @options, &block)
29
+ Initializer.new(@name, context, before:, after:, group: @group, &block)
38
30
  end
39
31
 
40
32
  def context_class
@@ -42,16 +34,66 @@ module Rails
42
34
  end
43
35
  end
44
36
 
45
- class Collection < Array
37
+ class Collection
38
+ include Enumerable
46
39
  include TSort
47
40
 
41
+ delegate_missing_to :@collection
42
+
43
+ def initialize(initializers = nil)
44
+ @order = Hash.new { |hash, key| hash[key] = Set.new }
45
+ @resolve = Hash.new { |hash, key| hash[key] = Set.new }
46
+ @collection = []
47
+ concat(initializers) if initializers
48
+ end
49
+
50
+ def to_a
51
+ @collection
52
+ end
53
+
54
+ def last
55
+ @collection.last
56
+ end
57
+
58
+ def each(&block)
59
+ @collection.each(&block)
60
+ end
61
+
48
62
  alias :tsort_each_node :each
49
63
  def tsort_each_child(initializer, &block)
50
- select { |i| i.before == initializer.name || i.name == initializer.after }.each(&block)
64
+ @order[initializer.name].each do |name|
65
+ @resolve[name].each(&block)
66
+ end
51
67
  end
52
68
 
53
69
  def +(other)
54
- Collection.new(to_a + other.to_a)
70
+ dup.concat(other.to_a)
71
+ end
72
+
73
+ def <<(initializer)
74
+ @collection << initializer
75
+ @order[initializer.before] << initializer.name if initializer.before
76
+ @order[initializer.name] << initializer.after if initializer.after
77
+ @resolve[initializer.name] << initializer
78
+ self
79
+ end
80
+
81
+ def push(*initializers)
82
+ initializers.each(&method(:<<))
83
+ self
84
+ end
85
+
86
+ alias_method(:append, :push)
87
+
88
+ def concat(*initializer_collections)
89
+ initializer_collections.each do |initializers|
90
+ initializers.each(&method(:<<))
91
+ end
92
+ self
93
+ end
94
+
95
+ def has?(name)
96
+ @resolve.key?(name)
55
97
  end
56
98
  end
57
99
 
@@ -87,8 +129,10 @@ module Rails
87
129
 
88
130
  def initializer(name, opts = {}, &blk)
89
131
  raise ArgumentError, "A block must be passed when defining an initializer" unless blk
90
- opts[:after] ||= initializers.last.name unless initializers.empty? || initializers.find { |i| i.name == opts[:before] }
91
- initializers << Initializer.new(name, nil, opts, &blk)
132
+ opts[:after] ||= initializers.last&.name unless initializers.has?(opts[:before])
133
+ initializers << Initializer.new(
134
+ name, nil, before: opts[:before], after: opts[:after], group: opts[:group], &blk
135
+ )
92
136
  end
93
137
  end
94
138
  end
@@ -10,11 +10,14 @@ module Rails
10
10
  # This is useful for preventing recurring requests like health checks from clogging the logging.
11
11
  # This middleware is used to do just that against the path /up in production by default.
12
12
  #
13
- # Example:
13
+ # Examples:
14
14
  #
15
15
  # config.middleware.insert_before \
16
16
  # Rails::Rack::Logger, Rails::Rack::SilenceRequest, path: "/up"
17
17
  #
18
+ # config.middleware.insert_before \
19
+ # Rails::Rack::Logger, Rails::Rack::SilenceRequest, path: /test$/
20
+ #
18
21
  # This middleware can also be configured using `config.silence_healthcheck_path = "/up"` in Rails.
19
22
  class SilenceRequest
20
23
  def initialize(app, path:)
@@ -22,7 +25,7 @@ module Rails
22
25
  end
23
26
 
24
27
  def call(env)
25
- if env["PATH_INFO"] == @path
28
+ if @path === env["PATH_INFO"]
26
29
  Rails.logger.silence { @app.call(env) }
27
30
  else
28
31
  @app.call(env)
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/concern"
4
3
 
5
4
  module Rails
6
5
  class Railtie
data/lib/rails/railtie.rb CHANGED
@@ -4,7 +4,6 @@ require "rails/initializable"
4
4
  require "active_support/descendants_tracker"
5
5
  require "active_support/inflector"
6
6
  require "active_support/core_ext/module/introspection"
7
- require "active_support/core_ext/module/delegation"
8
7
 
9
8
  module Rails
10
9
  # +Rails::Railtie+ is the core of the \Rails framework and provides
@@ -1,23 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "rails/code_statistics"
4
- STATS_DIRECTORIES = ActiveSupport::Deprecation::DeprecatedObjectProxy.new(
5
- Rails::CodeStatistics::DIRECTORIES,
6
- "`STATS_DIRECTORIES` is deprecated and will be removed in Rails 8.1! Use `Rails::CodeStatistics.register_directory('My Directory', 'path/to/dir)` instead.",
7
- Rails.deprecator
8
- )
9
-
10
- desc "Report code statistics (KLOCs, etc) from the application or engine"
11
- task :stats do
12
- require "rails/code_statistics"
13
- stat_directories = STATS_DIRECTORIES.collect do |name, dir|
14
- [ name, "#{File.dirname(Rake.application.rakefile_location)}/#{dir}" ]
15
- end.select { |name, dir| File.directory?(dir) }
16
-
17
- $stderr.puts Rails.deprecator.warn(<<~MSG, caller_locations(0..1))
18
- `bin/rake stats` has been deprecated and will be removed in Rails 8.1.
19
- Please use `bin/rails stats` as Rails command instead.\n
20
- MSG
21
-
22
- Rails::CodeStatistics.new(*stat_directories).to_s
23
- end
3
+ Rails.deprecator.warn <<~TEXT
4
+ rails/tasks/statistics.rake is deprecated and will be removed in Rails 8.2 without replacement.
5
+ TEXT
data/lib/rails/tasks.rb CHANGED
@@ -10,8 +10,6 @@ require "rake"
10
10
  tmp
11
11
  yarn
12
12
  zeitwerk
13
- ).tap { |arr|
14
- arr << "statistics" if Rake.application.current_scope.empty?
15
- }.each do |task|
13
+ ).each do |task|
16
14
  load "rails/tasks/#{task}.rake"
17
15
  end
@@ -33,12 +33,35 @@
33
33
  background: #282828;
34
34
  }
35
35
  }
36
+
37
+ .filter {
38
+ display: flex;
39
+ width: 100%;
40
+ justify-content: flex-end;
41
+ align-items: center;
42
+ }
43
+
44
+ .filter label[for="tag"] {
45
+ margin-right: 10px;
46
+ }
47
+
48
+ .filter select[name="tag"] {
49
+ border-radius: 8px;
50
+ padding: 0.25em;
51
+ }
36
52
  </style>
37
53
 
38
54
  <h2>
39
55
  Notes
40
56
  </h2>
41
57
 
58
+ <div class="filter">
59
+ <%= form_tag("/rails/info/notes", method: :get) do %>
60
+ <%= label_tag :tag, "Filter by Tag:" %>
61
+ <%= select_tag(:tag, options_for_select(Rails::SourceAnnotationExtractor::Annotation.tags, params[:tag]), { include_blank: "All", onchange: "this.form.submit();" }) %>
62
+ <% end %>
63
+ </div>
64
+
42
65
  <table id="route_table" class="table">
43
66
  <thead>
44
67
  <th>File Name</th>