railties 7.1.4 → 7.2.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +174 -781
  3. data/lib/minitest/rails_plugin.rb +5 -2
  4. data/lib/rails/all.rb +1 -3
  5. data/lib/rails/api/task.rb +3 -2
  6. data/lib/rails/application/bootstrap.rb +5 -6
  7. data/lib/rails/application/configuration.rb +31 -36
  8. data/lib/rails/application/dummy_config.rb +2 -2
  9. data/lib/rails/application/finisher.rb +7 -0
  10. data/lib/rails/application.rb +13 -57
  11. data/lib/rails/backtrace_cleaner.rb +14 -1
  12. data/lib/rails/cli.rb +0 -1
  13. data/lib/rails/command.rb +1 -1
  14. data/lib/rails/commands/app/update_command.rb +86 -0
  15. data/lib/rails/commands/console/console_command.rb +2 -21
  16. data/lib/rails/commands/console/irb_console.rb +137 -0
  17. data/lib/rails/commands/credentials/credentials_command.rb +2 -2
  18. data/lib/rails/commands/dbconsole/dbconsole_command.rb +21 -30
  19. data/lib/rails/commands/devcontainer/devcontainer_command.rb +34 -0
  20. data/lib/rails/commands/rake/rake_command.rb +1 -1
  21. data/lib/rails/commands/runner/runner_command.rb +14 -3
  22. data/lib/rails/commands/server/server_command.rb +5 -3
  23. data/lib/rails/commands/test/test_command.rb +2 -0
  24. data/lib/rails/configuration.rb +10 -1
  25. data/lib/rails/console/app.rb +5 -32
  26. data/lib/rails/console/helpers.rb +5 -16
  27. data/lib/rails/console/methods.rb +23 -0
  28. data/lib/rails/engine.rb +5 -5
  29. data/lib/rails/gem_version.rb +3 -3
  30. data/lib/rails/generators/app_base.rb +70 -49
  31. data/lib/rails/generators/base.rb +5 -1
  32. data/lib/rails/generators/database.rb +227 -69
  33. data/lib/rails/generators/erb/scaffold/templates/edit.html.erb.tt +2 -0
  34. data/lib/rails/generators/erb/scaffold/templates/index.html.erb.tt +2 -0
  35. data/lib/rails/generators/erb/scaffold/templates/new.html.erb.tt +2 -0
  36. data/lib/rails/generators/generated_attribute.rb +26 -1
  37. data/lib/rails/generators/rails/app/app_generator.rb +52 -23
  38. data/lib/rails/generators/rails/app/templates/Dockerfile.tt +23 -14
  39. data/lib/rails/generators/rails/app/templates/Gemfile.tt +16 -16
  40. data/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt +4 -0
  41. data/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt +8 -1
  42. data/lib/rails/generators/rails/app/templates/app/views/pwa/manifest.json.erb.tt +22 -0
  43. data/lib/rails/generators/rails/app/templates/app/views/pwa/service-worker.js +26 -0
  44. data/lib/rails/generators/rails/app/templates/bin/brakeman.tt +6 -0
  45. data/lib/rails/generators/rails/app/templates/bin/rubocop.tt +7 -0
  46. data/lib/rails/generators/rails/app/templates/bin/setup.tt +6 -2
  47. data/lib/rails/generators/rails/app/templates/config/application.rb.tt +1 -1
  48. data/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt +3 -3
  49. data/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt +7 -0
  50. data/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml.tt +8 -1
  51. data/lib/rails/generators/rails/app/templates/config/databases/trilogy.yml.tt +3 -3
  52. data/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +11 -6
  53. data/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +2 -0
  54. data/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt +6 -5
  55. data/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb.tt +1 -1
  56. data/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_7_2.rb.tt +70 -0
  57. data/lib/rails/generators/rails/app/templates/config/puma.rb.tt +39 -29
  58. data/lib/rails/generators/rails/app/templates/config/routes.rb.tt +4 -0
  59. data/lib/rails/generators/rails/app/templates/docker-entrypoint.tt +5 -0
  60. data/lib/rails/generators/rails/app/templates/dockerignore.tt +13 -0
  61. data/lib/rails/generators/rails/app/templates/github/ci.yml.tt +138 -0
  62. data/lib/rails/generators/rails/app/templates/github/dependabot.yml +12 -0
  63. data/lib/rails/generators/rails/app/templates/gitignore.tt +3 -3
  64. data/lib/rails/generators/rails/app/templates/public/406-unsupported-browser.html +66 -0
  65. data/lib/rails/generators/rails/app/templates/public/icon.png +0 -0
  66. data/lib/rails/generators/rails/app/templates/public/icon.svg +3 -0
  67. data/lib/rails/generators/rails/app/templates/rubocop.yml.tt +8 -0
  68. data/lib/rails/generators/rails/app/templates/test/application_system_test_case.rb.tt +1 -1
  69. data/lib/rails/generators/rails/controller/controller_generator.rb +1 -1
  70. data/lib/rails/generators/rails/db/system/change/change_generator.rb +131 -20
  71. data/lib/rails/generators/rails/devcontainer/devcontainer_generator.rb +166 -0
  72. data/lib/rails/generators/rails/migration/migration_generator.rb +4 -0
  73. data/lib/rails/generators/rails/plugin/plugin_generator.rb +38 -7
  74. data/lib/rails/generators/rails/plugin/templates/%name%.gemspec.tt +2 -2
  75. data/lib/rails/generators/rails/plugin/templates/Gemfile.tt +5 -1
  76. data/lib/rails/generators/rails/plugin/templates/bin/rubocop.tt +7 -0
  77. data/lib/rails/generators/rails/plugin/templates/github/ci.yml.tt +103 -0
  78. data/lib/rails/generators/rails/plugin/templates/github/dependabot.yml +12 -0
  79. data/lib/rails/generators/rails/plugin/templates/rubocop.yml.tt +8 -0
  80. data/lib/rails/generators/rails/plugin/templates/test/application_system_test_case.rb.tt +1 -1
  81. data/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb +10 -0
  82. data/lib/rails/generators/test_unit/scaffold/templates/system_test.rb.tt +2 -0
  83. data/lib/rails/generators/test_unit/system/templates/application_system_test_case.rb.tt +1 -1
  84. data/lib/rails/generators/testing/assertions.rb +20 -0
  85. data/lib/rails/generators/testing/behavior.rb +7 -6
  86. data/lib/rails/generators.rb +1 -1
  87. data/lib/rails/health_controller.rb +1 -1
  88. data/lib/rails/info.rb +2 -2
  89. data/lib/rails/mailers_controller.rb +14 -1
  90. data/lib/rails/paths.rb +2 -2
  91. data/lib/rails/pwa_controller.rb +15 -0
  92. data/lib/rails/rack/logger.rb +15 -7
  93. data/lib/rails/railtie/configurable.rb +2 -2
  94. data/lib/rails/railtie.rb +2 -3
  95. data/lib/rails/tasks/framework.rake +0 -26
  96. data/lib/rails/tasks/tmp.rake +1 -1
  97. data/lib/rails/templates/layouts/application.html.erb +1 -1
  98. data/lib/rails/templates/rails/mailers/email.html.erb +12 -8
  99. data/lib/rails/templates/rails/welcome/index.html.erb +3 -2
  100. data/lib/rails/test_help.rb +2 -4
  101. data/lib/rails/test_unit/reporter.rb +8 -2
  102. data/lib/rails/test_unit/runner.rb +21 -2
  103. data/lib/rails/test_unit/test_parser.rb +45 -0
  104. data/lib/rails.rb +6 -3
  105. metadata +38 -32
  106. data/lib/rails/app_updater.rb +0 -52
  107. data/lib/rails/commands/secrets/USAGE +0 -61
  108. data/lib/rails/commands/secrets/secrets_command.rb +0 -47
  109. data/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml.tt +0 -68
  110. data/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml.tt +0 -54
  111. data/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml.tt +0 -70
  112. data/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml.tt +0 -24
  113. data/lib/rails/generators/rails/app/templates/config/databases/oracle.yml.tt +0 -62
  114. data/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml.tt +0 -53
  115. data/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_7_1.rb.tt +0 -280
  116. data/lib/rails/generators/rails/app/templates/public/apple-touch-icon-precomposed.png +0 -0
  117. data/lib/rails/generators/rails/app/templates/public/apple-touch-icon.png +0 -0
  118. data/lib/rails/generators/rails/app/templates/public/favicon.ico +0 -0
  119. data/lib/rails/ruby_version_check.rb +0 -17
  120. data/lib/rails/secrets.rb +0 -110
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module Rails
6
+ module Generators
7
+ class DevcontainerGenerator < Base # :nodoc:
8
+ class_option :app_name, type: :string, default: "rails_app",
9
+ desc: "Name of the app"
10
+
11
+ class_option :database, enum: Database::DATABASES, type: :string, default: "sqlite3",
12
+ desc: "Include configuration for selected database"
13
+
14
+ class_option :redis, type: :boolean, default: true,
15
+ desc: "Include configuration for Redis"
16
+
17
+ class_option :system_test, type: :boolean, default: true,
18
+ desc: "Include configuration for System Tests"
19
+
20
+ class_option :active_storage, type: :boolean, default: true,
21
+ desc: "Include configuration for Active Storage"
22
+
23
+ class_option :node, type: :boolean, default: false,
24
+ desc: "Include configuration for Node"
25
+
26
+ class_option :dev, type: :boolean, default: false,
27
+ desc: "For applications pointing to a local Rails checkout"
28
+
29
+ source_paths << File.expand_path(File.join(base_name, "app", "templates"), base_root)
30
+
31
+ def create_devcontainer
32
+ empty_directory ".devcontainer"
33
+
34
+ template ".devcontainer/devcontainer.json"
35
+ template ".devcontainer/Dockerfile"
36
+ template ".devcontainer/compose.yaml"
37
+ end
38
+
39
+ def update_application_system_test_case
40
+ return unless options[:system_test]
41
+ return unless File.exist?("test/application_system_test_case.rb")
42
+
43
+ gsub_file("test/application_system_test_case.rb", /^(\s*driven_by\b.*)/, system_test_configuration)
44
+ end
45
+
46
+ def update_database_yml
47
+ # Only postgresql has devcontainer specific configuration, so only update database.yml if we are using postgres
48
+ return unless options[:database] == "postgresql"
49
+
50
+ template("config/databases/#{options[:database]}.yml", "config/database.yml")
51
+ end
52
+
53
+ private
54
+ def devcontainer?
55
+ true
56
+ end
57
+
58
+ def app_name
59
+ options[:app_name]
60
+ end
61
+
62
+ def dependencies
63
+ return @dependencies if @dependencies
64
+
65
+ @dependencies = []
66
+
67
+ @dependencies << "selenium" if options[:system_test]
68
+ @dependencies << "redis" if options[:redis]
69
+ @dependencies << database.name if database.service
70
+ @dependencies
71
+ end
72
+
73
+ def container_env
74
+ return @container_env if @container_env
75
+
76
+ @container_env = {}
77
+
78
+ @container_env["CAPYBARA_SERVER_PORT"] = "45678" if options[:system_test]
79
+ @container_env["SELENIUM_HOST"] = "selenium" if options[:system_test]
80
+ @container_env["REDIS_URL"] = "redis://redis:6379/1" if options[:redis]
81
+ @container_env["DB_HOST"] = database.name if database.service
82
+
83
+ @container_env
84
+ end
85
+
86
+ def volumes
87
+ return @volumes if @volumes
88
+
89
+ @volumes = []
90
+
91
+ @volumes << "redis-data" if options[:redis]
92
+ @volumes << database.volume if database.volume
93
+
94
+ @volumes
95
+ end
96
+
97
+ def features
98
+ return @features if @features
99
+
100
+ @features = {
101
+ "ghcr.io/devcontainers/features/github-cli:1" => {}
102
+ }
103
+
104
+ @features["ghcr.io/rails/devcontainer/features/activestorage"] = {} if options[:active_storage]
105
+ @features["ghcr.io/devcontainers/features/node:1"] = {} if options[:node]
106
+
107
+ @features.merge!(database.feature) if database.feature
108
+
109
+ @features
110
+ end
111
+
112
+ def mounts
113
+ return @mounts if @mounts
114
+
115
+ @mounts = []
116
+
117
+ @mounts << local_rails_mount if options[:dev]
118
+
119
+ @mounts
120
+ end
121
+
122
+ def forward_ports
123
+ return @forward_ports if @forward_ports
124
+
125
+ @forward_ports = [3000]
126
+ @forward_ports << database.port if database.port
127
+ @forward_ports << 6379 if options[:redis]
128
+
129
+ @forward_ports
130
+ end
131
+
132
+ def database
133
+ @database ||= Database.build(options[:database])
134
+ end
135
+
136
+ def devcontainer_db_service_yaml(**options)
137
+ return unless service = database.service
138
+
139
+ { database.name => service }.to_yaml(**options)[4..-1]
140
+ end
141
+
142
+ def local_rails_mount
143
+ {
144
+ type: "bind",
145
+ source: Rails::Generators::RAILS_DEV_PATH,
146
+ target: Rails::Generators::RAILS_DEV_PATH
147
+ }
148
+ end
149
+
150
+ def system_test_configuration
151
+ <<~RUBY
152
+ if ENV["CAPYBARA_SERVER_PORT"]
153
+ served_by host: "rails-app", port: ENV["CAPYBARA_SERVER_PORT"]
154
+
155
+ driven_by :selenium, using: :headless_chrome, screen_size: [ 1400, 1400 ], options: {
156
+ browser: :remote,
157
+ url: "http://#{ENV["SELENIUM_HOST"]}:4444"
158
+ }
159
+ else
160
+ driven_by :selenium, using: :headless_chrome, screen_size: [ 1400, 1400 ]
161
+ end
162
+ RUBY
163
+ end
164
+ end
165
+ end
166
+ end
@@ -5,6 +5,10 @@ module Rails
5
5
  class MigrationGenerator < NamedBase # :nodoc:
6
6
  argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]"
7
7
  hook_for :orm, required: true, desc: "ORM to be invoked"
8
+
9
+ def self.exit_on_failure? # :nodoc:
10
+ true
11
+ end
8
12
  end
9
13
  end
10
14
  end
@@ -66,6 +66,16 @@ module Rails
66
66
  template "gitignore", ".gitignore"
67
67
  end
68
68
 
69
+ def cifiles
70
+ empty_directory ".github/workflows"
71
+ template "github/ci.yml", ".github/workflows/ci.yml"
72
+ template "github/dependabot.yml", ".github/dependabot.yml"
73
+ end
74
+
75
+ def rubocop
76
+ template "rubocop.yml", ".rubocop.yml"
77
+ end
78
+
69
79
  def version_control
70
80
  if !options[:skip_git] && !options[:pretend]
71
81
  run git_init_command, capture: options[:quiet], abort_on_failure: false
@@ -112,9 +122,12 @@ module Rails
112
122
  def generate_test_dummy(force = false)
113
123
  opts = options.transform_keys(&:to_sym).except(*DUMMY_IGNORE_OPTIONS)
114
124
  opts[:force] = force
125
+ opts[:skip_brakeman] = true
115
126
  opts[:skip_bundle] = true
127
+ opts[:skip_ci] = true
116
128
  opts[:skip_git] = true
117
129
  opts[:skip_hotwire] = true
130
+ opts[:skip_rubocop] = true
118
131
  opts[:dummy_app] = true
119
132
 
120
133
  invoke Rails::Generators::AppGenerator,
@@ -144,7 +157,7 @@ module Rails
144
157
  def test_dummy_clean
145
158
  inside dummy_path do
146
159
  remove_file ".ruby-version"
147
- remove_file "db/seeds.rb"
160
+ remove_dir "db"
148
161
  remove_file "Gemfile"
149
162
  remove_file "lib/tasks"
150
163
  remove_file "public/robots.txt"
@@ -167,12 +180,12 @@ module Rails
167
180
  end
168
181
  end
169
182
 
170
- def bin(force = false)
171
- bin_file = engine? ? "bin/rails.tt" : "bin/test.tt"
172
- template bin_file, force: force do |content|
183
+ def bin
184
+ exclude_pattern = Regexp.union([(engine? ? /test\.tt/ : /rails\.tt/), (/rubocop/ if skip_rubocop?)].compact)
185
+ directory "bin", { exclude_pattern: exclude_pattern } do |content|
173
186
  "#{shebang}\n" + content
174
187
  end
175
- chmod "bin", 0755, verbose: false
188
+ chmod "bin", 0755 & ~File.umask, verbose: false
176
189
  end
177
190
 
178
191
  def gemfile_entry
@@ -180,7 +193,7 @@ module Rails
180
193
 
181
194
  gemfile_in_app_path = File.join(rails_app_path, "Gemfile")
182
195
  if File.exist? gemfile_in_app_path
183
- entry = "\ngem '#{name}', path: '#{relative_path}'"
196
+ entry = %{\ngem "#{name}", path: "#{relative_path}"}
184
197
  append_file gemfile_in_app_path, entry
185
198
  end
186
199
  end
@@ -243,6 +256,16 @@ module Rails
243
256
  build(:app)
244
257
  end
245
258
 
259
+ def create_rubocop_file
260
+ return if skip_rubocop?
261
+ build(:rubocop)
262
+ end
263
+
264
+ def create_cifiles
265
+ return if skip_ci?
266
+ build(:cifiles)
267
+ end
268
+
246
269
  def create_config_files
247
270
  build(:config)
248
271
  end
@@ -341,7 +364,7 @@ module Rails
341
364
  build(:test_dummy_sprocket_assets) unless skip_sprockets?
342
365
  build(:test_dummy_clean)
343
366
  # ensure that bin/rails has proper dummy_path
344
- build(:bin, true)
367
+ build(:bin)
345
368
  end
346
369
  end
347
370
 
@@ -465,6 +488,14 @@ module Rails
465
488
  return unless inside_application?
466
489
  app_path.delete_prefix("#{rails_app_path}/")
467
490
  end
491
+
492
+ def test_command
493
+ if engine? && !options[:skip_active_record] && with_dummy_app?
494
+ "db:test:prepare test"
495
+ else
496
+ "test"
497
+ end
498
+ end
468
499
  end
469
500
  end
470
501
  end
@@ -3,8 +3,8 @@ require_relative "lib/<%= namespaced_name %>/version"
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = <%= name.inspect %>
5
5
  spec.version = <%= camelized_modules %>::VERSION
6
- spec.authors = [<%= author.inspect %>]
7
- spec.email = [<%= email.inspect %>]
6
+ spec.authors = [ <%= author.inspect %> ]
7
+ spec.email = [ <%= email.inspect %> ]
8
8
  spec.homepage = "TODO"
9
9
  spec.summary = "TODO: Summary of <%= camelized_modules %>."
10
10
  spec.description = "TODO: Description of <%= camelized_modules %>."
@@ -1,5 +1,4 @@
1
1
  source "https://rubygems.org"
2
- git_source(:github) { |repo| "https://github.com/#{repo}.git" }
3
2
  <% unless options[:skip_gemspec] -%>
4
3
 
5
4
  # Specify your gem's dependencies in <%= name %>.gemspec.
@@ -8,6 +7,11 @@ gemspec
8
7
  <% gemfile_entries.each do |gemfile_entry| %>
9
8
  <%= gemfile_entry %>
10
9
  <% end -%>
10
+ <%- unless options.skip_rubocop? -%>
11
+
12
+ # Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/]
13
+ gem "rubocop-rails-omakase", require: false
14
+ <%- end -%>
11
15
  <% if RUBY_ENGINE == "ruby" -%>
12
16
 
13
17
  # Start debugger with binding.b [https://github.com/ruby/debug]
@@ -0,0 +1,7 @@
1
+ require "rubygems"
2
+ require "bundler/setup"
3
+
4
+ # explicit rubocop config increases performance slightly while avoiding config confusion.
5
+ ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__))
6
+
7
+ load Gem.bin_path("rubocop", "rubocop")
@@ -0,0 +1,103 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [ main ]
7
+
8
+ jobs:
9
+ <%- unless skip_rubocop? -%>
10
+ lint:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - name: Checkout code
14
+ uses: actions/checkout@v4
15
+
16
+ - name: Set up Ruby
17
+ uses: ruby/setup-ruby@v1
18
+ with:
19
+ ruby-version: <%= ENV["RBENV_VERSION"] || ENV["rvm_ruby_string"] || "#{RUBY_ENGINE}-#{RUBY_ENGINE_VERSION}" %>
20
+ bundler-cache: true
21
+
22
+ - name: Lint code for consistent style
23
+ run: bin/rubocop -f github
24
+
25
+ <% end -%>
26
+ <% unless options[:skip_test] -%>
27
+ test:
28
+ runs-on: ubuntu-latest
29
+
30
+ <%- if options[:database] == "sqlite3" -%>
31
+ # services:
32
+ # redis:
33
+ # image: redis
34
+ # ports:
35
+ # - 6379:6379
36
+ # options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
37
+ <%- else -%>
38
+ services:
39
+ <%- if options[:database] == "mysql" || options[:database] == "trilogy" -%>
40
+ mysql:
41
+ image: mysql
42
+ env:
43
+ MYSQL_ALLOW_EMPTY_PASSWORD: true
44
+ ports:
45
+ - 3306:3306
46
+ options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
47
+ <%- elsif options[:database] == "postgresql" -%>
48
+ postgres:
49
+ image: postgres
50
+ env:
51
+ POSTGRES_USER: postgres
52
+ POSTGRES_PASSWORD: postgres
53
+ ports:
54
+ - 5432:5432
55
+ options: --health-cmd="pg_isready" --health-interval=10s --health-timeout=5s --health-retries=3
56
+ <%- end -%>
57
+
58
+ # redis:
59
+ # image: redis
60
+ # ports:
61
+ # - 6379:6379
62
+ # options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
63
+
64
+ <%- end -%>
65
+ steps:
66
+ - name: Install packages
67
+ run: sudo apt-get update && sudo apt-get install --no-install-recommends -y google-chrome-stable <%= (dockerfile_base_packages + [database.base_package]).join(" ") %>
68
+
69
+ - name: Checkout code
70
+ uses: actions/checkout@v4
71
+
72
+ - name: Set up Ruby
73
+ uses: ruby/setup-ruby@v1
74
+ with:
75
+ ruby-version: <%= ENV["RBENV_VERSION"] || ENV["rvm_ruby_string"] || "#{RUBY_ENGINE}-#{RUBY_ENGINE_VERSION}" %>
76
+ bundler-cache: true
77
+ <%- if using_bun? -%>
78
+
79
+ - uses: oven-sh/setup-bun@v1
80
+ with:
81
+ bun-version: <%= dockerfile_bun_version %>
82
+ <%- end -%>
83
+
84
+ - name: Run tests
85
+ env:
86
+ RAILS_ENV: test
87
+ <%- if options[:database] == "mysql" || options[:database] == "trilogy" -%>
88
+ DATABASE_URL: mysql2://127.0.0.1:3306
89
+ <%- elsif options[:database] == "postgresql" -%>
90
+ DATABASE_URL: postgres://postgres:postgres@localhost:5432
91
+ <%- end -%>
92
+ # REDIS_URL: redis://localhost:6379/0
93
+ run: bin/rails <%= test_command %>
94
+
95
+ - name: Keep screenshots from failed system tests
96
+ uses: actions/upload-artifact@v4
97
+ if: failure()
98
+ with:
99
+ name: screenshots
100
+ path: ${{ github.workspace }}/tmp/screenshots
101
+ if-no-files-found: ignore
102
+ <% end -%>
103
+
@@ -0,0 +1,12 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: bundler
4
+ directory: "/"
5
+ schedule:
6
+ interval: daily
7
+ open-pull-requests-limit: 10
8
+ - package-ecosystem: github-actions
9
+ directory: "/"
10
+ schedule:
11
+ interval: daily
12
+ open-pull-requests-limit: 10
@@ -0,0 +1,8 @@
1
+ # Omakase Ruby styling for Rails
2
+ inherit_gem: { rubocop-rails-omakase: rubocop.yml }
3
+
4
+ # Overwrite or add rules to create your own house style
5
+ #
6
+ # # Use `[a, [b, c]]` not `[ a, [ b, c ] ]`
7
+ # Layout/SpaceInsideArrayLiteralBrackets:
8
+ # Enabled: false
@@ -1,5 +1,5 @@
1
1
  require "test_helper"
2
2
 
3
3
  class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
4
- driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
4
+ driven_by :selenium, using: :headless_chrome, screen_size: [ 1400, 1400 ]
5
5
  end
@@ -63,6 +63,16 @@ module TestUnit # :nodoc:
63
63
  attribute = attributes.find { |attr| attr.name == name }
64
64
  attribute&.virtual?
65
65
  end
66
+
67
+ def datetime?(name)
68
+ attribute = attributes.find { |attr| attr.name == name }
69
+ attribute&.type == :datetime
70
+ end
71
+
72
+ def time?(name)
73
+ attribute = attributes.find { |attr| attr.name == name }
74
+ attribute&.type == :time
75
+ end
66
76
  end
67
77
  end
68
78
  end
@@ -35,6 +35,8 @@ class <%= class_name.pluralize %>Test < ApplicationSystemTestCase
35
35
  <%- attributes_hash.each do |attr, value| -%>
36
36
  <%- if boolean?(attr) -%>
37
37
  check "<%= attr.humanize %>" if <%= value %>
38
+ <%- elsif datetime?(attr) || time?(attr) -%>
39
+ fill_in "<%= attr.humanize %>", with: <%= value %>.to_s
38
40
  <%- else -%>
39
41
  fill_in "<%= attr.humanize %>", with: <%= value %>
40
42
  <%- end -%>
@@ -1,5 +1,5 @@
1
1
  require "test_helper"
2
2
 
3
3
  class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
4
- driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
4
+ driven_by :selenium, using: :headless_chrome, screen_size: [ 1400, 1400 ]
5
5
  end
@@ -121,6 +121,26 @@ module Rails
121
121
  assert_equal(value, create_generated_attribute(attribute_type).default)
122
122
  end
123
123
  end
124
+
125
+ # Asserts a given initializer exists. You need to supply a path relative
126
+ # to the `config/initializers/` directory.
127
+ #
128
+ # assert_initializer "mail_interceptors.rb"
129
+ #
130
+ # You can also give extra arguments. If the argument is a regexp, it will check if the
131
+ # regular expression matches the given file content. If it's a string, it compares the
132
+ # file with the given string:
133
+ #
134
+ # assert_initializer "mail_interceptors.rb", /SandboxEmailInterceptor/
135
+ #
136
+ # Finally, when a block is given, it yields the file content:
137
+ #
138
+ # assert_initializer "mail_interceptors.rb" do |initializer|
139
+ # assert_match(/SandboxEmailInterceptor/, initializer)
140
+ # end
141
+ def assert_initializer(name, *contents, &block)
142
+ assert_file("config/initializers/#{name}", *contents, &block)
143
+ end
124
144
  end
125
145
  end
126
146
  end
@@ -65,11 +65,15 @@ module Rails
65
65
  # You can provide a configuration hash as second argument. This method returns the output
66
66
  # printed by the generator.
67
67
  def run_generator(args = default_arguments, config = {})
68
- capture(:stdout) do
69
- args += ["--skip-bundle"] unless args.include?("--no-skip-bundle") || args.include?("--dev")
70
- args += ["--skip-bootsnap"] unless args.include?("--no-skip-bootsnap") || args.include?("--skip-bootsnap")
68
+ args += ["--skip-bundle"] unless args.include?("--no-skip-bundle") || args.include?("--dev")
69
+ args += ["--skip-bootsnap"] unless args.include?("--no-skip-bootsnap") || args.include?("--skip-bootsnap")
71
70
 
71
+ if ENV["RAILS_LOG_TO_STDOUT"] == "true"
72
72
  generator_class.start(args, config.reverse_merge(destination_root: destination_root))
73
+ else
74
+ capture(:stdout) do
75
+ generator_class.start(args, config.reverse_merge(destination_root: destination_root))
76
+ end
73
77
  end
74
78
  end
75
79
 
@@ -107,9 +111,6 @@ module Rails
107
111
  Dir.glob("#{dirname}/[0-9]*_*.rb").grep(/\d+_#{file_name}.rb$/).first
108
112
  end
109
113
  end
110
-
111
- include ActiveSupport::Deprecation::DeprecatedConstantAccessor
112
- deprecate_constant "Behaviour", "Rails::Generators::Testing::Behavior", deprecator: Rails.deprecator
113
114
  end
114
115
  end
115
116
  end
@@ -23,6 +23,7 @@ module Rails
23
23
  autoload :NamedBase, "rails/generators/named_base"
24
24
  autoload :ResourceHelpers, "rails/generators/resource_helpers"
25
25
  autoload :TestCase, "rails/generators/test_case"
26
+ autoload :Devcontainer, "rails/generators/devcontainer"
26
27
 
27
28
  mattr_accessor :namespace
28
29
 
@@ -202,7 +203,6 @@ module Rails
202
203
  rails.map! { |n| n.delete_prefix("rails:") }
203
204
  rails.delete("app")
204
205
  rails.delete("plugin")
205
- rails.delete("encrypted_secrets")
206
206
  rails.delete("encrypted_file")
207
207
  rails.delete("encryption_key_file")
208
208
  rails.delete("master_key")
@@ -24,7 +24,7 @@ module Rails
24
24
  # The health check will now be accessible via the +/healthz+ path.
25
25
  #
26
26
  # NOTE: This endpoint does not reflect the status of all of your application's
27
- # dependencies, such as the database or redis cluster. Replace
27
+ # dependencies, such as the database or Redis cluster. Replace
28
28
  # <tt>"rails/health#show"</tt> with your own controller action if you have
29
29
  # application specific needs.
30
30
  #
data/lib/rails/info.rb CHANGED
@@ -95,11 +95,11 @@ module Rails
95
95
 
96
96
  # The name of the database adapter for the current environment.
97
97
  property "Database adapter" do
98
- ActiveRecord::Base.connection.pool.db_config.adapter
98
+ ActiveRecord::Base.connection_pool.db_config.adapter
99
99
  end
100
100
 
101
101
  property "Database schema version" do
102
- ActiveRecord::Base.connection.migration_context.current_version rescue nil
102
+ ActiveRecord::Base.connection_pool.migration_context.current_version rescue nil
103
103
  end
104
104
  end
105
105
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rails/application_controller"
4
+ require "active_support/core_ext/enumerable"
4
5
 
5
6
  class Rails::MailersController < Rails::ApplicationController # :nodoc:
6
7
  prepend_view_path ActionDispatch::DebugView::RESCUES_TEMPLATE_PATHS
@@ -9,7 +10,7 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc:
9
10
  before_action :find_preview, only: [:preview, :download]
10
11
  before_action :require_local!, unless: :show_previews?
11
12
 
12
- helper_method :part_query, :locale_query
13
+ helper_method :attachment_url, :part_query, :locale_query
13
14
 
14
15
  content_security_policy(false)
15
16
 
@@ -38,6 +39,8 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc:
38
39
  if @preview.email_exists?(@email_action)
39
40
  @page_title = "Mailer Preview for #{@preview.preview_name}##{@email_action}"
40
41
  @email = @preview.call(@email_action, params)
42
+ @attachments = attachments_for(@email).reject { |filename, attachment| attachment.inline? }
43
+ @inline_attachments = attachments_for(@email).select { |filename, attachment| attachment.inline? }
41
44
 
42
45
  if params[:part]
43
46
  part_type = Mime::Type.lookup(params[:part])
@@ -95,6 +98,16 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc:
95
98
  end
96
99
  end
97
100
 
101
+ def attachments_for(email)
102
+ email.all_parts.to_a.select(&:attachment?).index_by do |attachment|
103
+ attachment.respond_to?(:original_filename) ? attachment.original_filename : attachment.filename
104
+ end
105
+ end
106
+
107
+ def attachment_url(attachment)
108
+ "data:application/octet-stream;charset=utf-8;base64,#{Base64.encode64(attachment.body.to_s)}"
109
+ end
110
+
98
111
  def part_query(mime_type)
99
112
  request.query_parameters.merge(part: mime_type).to_query
100
113
  end
data/lib/rails/paths.rb CHANGED
@@ -105,8 +105,8 @@ module Rails
105
105
  private
106
106
  def filter_by(&block)
107
107
  all_paths.find_all(&block).flat_map { |path|
108
- paths = path.existent
109
- paths - path.children.flat_map { |p| yield(p) ? [] : p.existent }
108
+ paths = path.existent_directories
109
+ paths - path.children.flat_map { |p| yield(p) ? [] : p.existent_directories }
110
110
  }.uniq
111
111
  end
112
112
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/application_controller"
4
+
5
+ class Rails::PwaController < Rails::ApplicationController # :nodoc:
6
+ skip_forgery_protection
7
+
8
+ def service_worker
9
+ render template: "pwa/service-worker", layout: false
10
+ end
11
+
12
+ def manifest
13
+ render template: "pwa/manifest", layout: false
14
+ end
15
+ end