railties 7.0.8.7 → 7.2.2.1

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 (221) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +210 -272
  3. data/MIT-LICENSE +1 -1
  4. data/RDOC_MAIN.md +99 -0
  5. data/README.rdoc +4 -4
  6. data/lib/minitest/rails_plugin.rb +67 -1
  7. data/lib/rails/all.rb +1 -3
  8. data/lib/rails/api/task.rb +39 -6
  9. data/lib/rails/application/bootstrap.rb +28 -10
  10. data/lib/rails/application/configuration.rb +228 -72
  11. data/lib/rails/application/default_middleware_stack.rb +8 -2
  12. data/lib/rails/application/dummy_config.rb +19 -0
  13. data/lib/rails/application/finisher.rb +50 -33
  14. data/lib/rails/application.rb +117 -76
  15. data/lib/rails/backtrace_cleaner.rb +19 -4
  16. data/lib/rails/cli.rb +5 -3
  17. data/lib/rails/command/actions.rb +10 -12
  18. data/lib/rails/command/base.rb +55 -53
  19. data/lib/rails/command/environment_argument.rb +32 -16
  20. data/lib/rails/command/helpers/editor.rb +17 -12
  21. data/lib/rails/command.rb +84 -33
  22. data/lib/rails/commands/about/about_command.rb +14 -0
  23. data/lib/rails/commands/app/update_command.rb +102 -0
  24. data/lib/rails/commands/application/application_command.rb +2 -0
  25. data/lib/rails/commands/boot/boot_command.rb +14 -0
  26. data/lib/rails/commands/console/console_command.rb +11 -30
  27. data/lib/rails/commands/console/irb_console.rb +146 -0
  28. data/lib/rails/commands/credentials/USAGE +53 -55
  29. data/lib/rails/commands/credentials/credentials_command/diffing.rb +5 -3
  30. data/lib/rails/commands/credentials/credentials_command.rb +64 -70
  31. data/lib/rails/commands/db/system/change/change_command.rb +2 -1
  32. data/lib/rails/commands/dbconsole/dbconsole_command.rb +32 -131
  33. data/lib/rails/commands/destroy/destroy_command.rb +3 -2
  34. data/lib/rails/commands/dev/dev_command.rb +1 -6
  35. data/lib/rails/commands/devcontainer/devcontainer_command.rb +39 -0
  36. data/lib/rails/commands/encrypted/USAGE +15 -20
  37. data/lib/rails/commands/encrypted/encrypted_command.rb +46 -35
  38. data/lib/rails/commands/gem_help/USAGE +16 -0
  39. data/lib/rails/commands/gem_help/gem_help_command.rb +13 -0
  40. data/lib/rails/commands/generate/generate_command.rb +2 -2
  41. data/lib/rails/commands/help/USAGE +13 -13
  42. data/lib/rails/commands/help/help_command.rb +21 -2
  43. data/lib/rails/commands/initializers/initializers_command.rb +1 -4
  44. data/lib/rails/commands/middleware/middleware_command.rb +17 -0
  45. data/lib/rails/commands/new/new_command.rb +2 -0
  46. data/lib/rails/commands/notes/notes_command.rb +2 -1
  47. data/lib/rails/commands/plugin/plugin_command.rb +2 -0
  48. data/lib/rails/commands/rake/rake_command.rb +25 -22
  49. data/lib/rails/commands/restart/restart_command.rb +14 -0
  50. data/lib/rails/commands/routes/routes_command.rb +13 -1
  51. data/lib/rails/commands/runner/USAGE +14 -12
  52. data/lib/rails/commands/runner/runner_command.rb +42 -19
  53. data/lib/rails/commands/secret/secret_command.rb +13 -0
  54. data/lib/rails/commands/server/server_command.rb +37 -34
  55. data/lib/rails/commands/test/USAGE +14 -0
  56. data/lib/rails/commands/test/test_command.rb +58 -14
  57. data/lib/rails/commands/unused_routes/unused_routes_command.rb +75 -0
  58. data/lib/rails/commands/version/version_command.rb +1 -0
  59. data/lib/rails/configuration.rb +15 -6
  60. data/lib/rails/console/app.rb +5 -35
  61. data/lib/rails/console/helpers.rb +5 -16
  62. data/lib/rails/console/methods.rb +23 -0
  63. data/lib/rails/deprecator.rb +7 -0
  64. data/lib/rails/engine/configuration.rb +50 -6
  65. data/lib/rails/engine.rb +53 -25
  66. data/lib/rails/gem_version.rb +4 -4
  67. data/lib/rails/generators/actions.rb +6 -15
  68. data/lib/rails/generators/active_model.rb +28 -14
  69. data/lib/rails/generators/app_base.rb +382 -88
  70. data/lib/rails/generators/app_name.rb +3 -14
  71. data/lib/rails/generators/base.rb +21 -9
  72. data/lib/rails/generators/database.rb +231 -35
  73. data/lib/rails/generators/erb/mailer/templates/layout.html.erb.tt +1 -1
  74. data/lib/rails/generators/erb/scaffold/templates/edit.html.erb.tt +2 -0
  75. data/lib/rails/generators/erb/scaffold/templates/index.html.erb.tt +2 -0
  76. data/lib/rails/generators/erb/scaffold/templates/new.html.erb.tt +2 -0
  77. data/lib/rails/generators/generated_attribute.rb +38 -1
  78. data/lib/rails/generators/migration.rb +4 -5
  79. data/lib/rails/generators/model_helpers.rb +2 -1
  80. data/lib/rails/generators/rails/app/USAGE +22 -6
  81. data/lib/rails/generators/rails/app/app_generator.rb +135 -86
  82. data/lib/rails/generators/rails/app/templates/Dockerfile.tt +110 -0
  83. data/lib/rails/generators/rails/app/templates/Gemfile.tt +19 -21
  84. data/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt +4 -0
  85. data/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt +8 -1
  86. data/lib/rails/generators/rails/app/templates/app/views/layouts/mailer.html.erb.tt +1 -1
  87. data/lib/rails/generators/rails/app/templates/app/views/pwa/manifest.json.erb.tt +22 -0
  88. data/lib/rails/generators/rails/app/templates/app/views/pwa/service-worker.js +26 -0
  89. data/lib/rails/generators/rails/app/templates/bin/brakeman.tt +6 -0
  90. data/lib/rails/generators/rails/app/templates/bin/rubocop.tt +7 -0
  91. data/lib/rails/generators/rails/app/templates/bin/setup.tt +15 -2
  92. data/lib/rails/generators/rails/app/templates/config/application.rb.tt +6 -17
  93. data/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt +3 -3
  94. data/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt +11 -6
  95. data/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml.tt +10 -3
  96. data/lib/rails/generators/rails/app/templates/config/databases/{jdbcmysql.yml.tt → trilogy.yml.tt} +12 -7
  97. data/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +25 -8
  98. data/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +40 -28
  99. data/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt +20 -13
  100. data/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt +3 -1
  101. data/lib/rails/generators/rails/app/templates/config/initializers/content_security_policy.rb.tt +2 -2
  102. data/lib/rails/generators/rails/app/templates/config/initializers/cors.rb.tt +1 -1
  103. data/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb.tt +4 -4
  104. data/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_7_2.rb.tt +70 -0
  105. data/lib/rails/generators/rails/app/templates/config/initializers/permissions_policy.rb.tt +11 -9
  106. data/lib/rails/generators/rails/app/templates/config/locales/en.yml +11 -13
  107. data/lib/rails/generators/rails/app/templates/config/puma.rb.tt +25 -34
  108. data/lib/rails/generators/rails/app/templates/config/routes.rb.tt +11 -1
  109. data/lib/rails/generators/rails/app/templates/db/seeds.rb.tt +6 -4
  110. data/lib/rails/generators/rails/app/templates/docker-entrypoint.tt +15 -0
  111. data/lib/rails/generators/rails/app/templates/dockerignore.tt +56 -0
  112. data/lib/rails/generators/rails/app/templates/github/ci.yml.tt +138 -0
  113. data/lib/rails/generators/rails/app/templates/github/dependabot.yml +12 -0
  114. data/lib/rails/generators/rails/app/templates/gitignore.tt +7 -11
  115. data/lib/rails/generators/rails/app/templates/node-version.tt +1 -0
  116. data/lib/rails/generators/rails/app/templates/public/406-unsupported-browser.html +66 -0
  117. data/lib/rails/generators/rails/app/templates/public/icon.png +0 -0
  118. data/lib/rails/generators/rails/app/templates/public/icon.svg +3 -0
  119. data/lib/rails/generators/rails/app/templates/rubocop.yml.tt +8 -0
  120. data/lib/rails/generators/rails/app/templates/test/application_system_test_case.rb.tt +1 -1
  121. data/lib/rails/generators/rails/app/templates/test/channels/application_cable/connection_test.rb.tt +10 -8
  122. data/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt +9 -7
  123. data/lib/rails/generators/rails/application_record/application_record_generator.rb +4 -0
  124. data/lib/rails/generators/rails/benchmark/benchmark_generator.rb +2 -1
  125. data/lib/rails/generators/rails/controller/USAGE +12 -4
  126. data/lib/rails/generators/rails/controller/controller_generator.rb +6 -1
  127. data/lib/rails/generators/rails/controller/templates/controller.rb.tt +1 -1
  128. data/lib/rails/generators/rails/credentials/credentials_generator.rb +29 -24
  129. data/lib/rails/generators/rails/credentials/templates/credentials.yml.tt +8 -0
  130. data/lib/rails/generators/rails/db/system/change/change_generator.rb +146 -5
  131. data/lib/rails/generators/rails/devcontainer/devcontainer_generator.rb +166 -0
  132. data/lib/rails/generators/rails/devcontainer/templates/devcontainer/Dockerfile.tt +3 -0
  133. data/lib/rails/generators/rails/devcontainer/templates/devcontainer/compose.yaml.tt +47 -0
  134. data/lib/rails/generators/rails/devcontainer/templates/devcontainer/devcontainer.json.tt +37 -0
  135. data/lib/rails/generators/rails/encryption_key_file/encryption_key_file_generator.rb +1 -2
  136. data/lib/rails/generators/rails/migration/USAGE +21 -11
  137. data/lib/rails/generators/rails/migration/migration_generator.rb +4 -0
  138. data/lib/rails/generators/rails/model/model_generator.rb +4 -0
  139. data/lib/rails/generators/rails/plugin/USAGE +17 -6
  140. data/lib/rails/generators/rails/plugin/plugin_generator.rb +45 -22
  141. data/lib/rails/generators/rails/plugin/templates/%name%.gemspec.tt +2 -2
  142. data/lib/rails/generators/rails/plugin/templates/Gemfile.tt +7 -3
  143. data/lib/rails/generators/rails/plugin/templates/MIT-LICENSE.tt +1 -1
  144. data/lib/rails/generators/rails/plugin/templates/app/views/layouts/%namespaced_name%/application.html.erb.tt +2 -0
  145. data/lib/rails/generators/rails/plugin/templates/bin/rails.tt +1 -17
  146. data/lib/rails/generators/rails/plugin/templates/bin/rubocop.tt +7 -0
  147. data/lib/rails/generators/rails/plugin/templates/github/ci.yml.tt +103 -0
  148. data/lib/rails/generators/rails/plugin/templates/github/dependabot.yml +12 -0
  149. data/lib/rails/generators/rails/plugin/templates/gitignore.tt +0 -2
  150. data/lib/rails/generators/rails/plugin/templates/rubocop.yml.tt +8 -0
  151. data/lib/rails/generators/rails/plugin/templates/test/application_system_test_case.rb.tt +1 -1
  152. data/lib/rails/generators/rails/plugin/templates/test/test_helper.rb.tt +5 -5
  153. data/lib/rails/generators/rails/resource/resource_generator.rb +6 -0
  154. data/lib/rails/generators/rails/scaffold/scaffold_generator.rb +2 -1
  155. data/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb +1 -1
  156. data/lib/rails/generators/test_case.rb +2 -2
  157. data/lib/rails/generators/test_unit/mailer/templates/functional_test.rb.tt +6 -4
  158. data/lib/rails/generators/test_unit/mailer/templates/preview.rb.tt +3 -2
  159. data/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb +16 -2
  160. data/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb.tt +2 -2
  161. data/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb.tt +2 -2
  162. data/lib/rails/generators/test_unit/scaffold/templates/system_test.rb.tt +2 -0
  163. data/lib/rails/generators/test_unit/system/templates/application_system_test_case.rb.tt +1 -1
  164. data/lib/rails/generators/testing/assertions.rb +20 -0
  165. data/lib/rails/generators/testing/{behaviour.rb → behavior.rb} +8 -4
  166. data/lib/rails/generators.rb +12 -16
  167. data/lib/rails/health_controller.rb +55 -0
  168. data/lib/rails/info.rb +3 -3
  169. data/lib/rails/info_controller.rb +33 -11
  170. data/lib/rails/mailers_controller.rb +29 -6
  171. data/lib/rails/paths.rb +15 -12
  172. data/lib/rails/pwa_controller.rb +15 -0
  173. data/lib/rails/rack/logger.rb +27 -16
  174. data/lib/rails/rackup/server.rb +15 -0
  175. data/lib/rails/railtie/configurable.rb +2 -2
  176. data/lib/rails/railtie/configuration.rb +14 -1
  177. data/lib/rails/railtie.rb +33 -34
  178. data/lib/rails/source_annotation_extractor.rb +67 -18
  179. data/lib/rails/tasks/engine.rake +8 -8
  180. data/lib/rails/tasks/framework.rake +2 -34
  181. data/lib/rails/tasks/log.rake +1 -1
  182. data/lib/rails/tasks/misc.rake +3 -14
  183. data/lib/rails/tasks/statistics.rake +5 -4
  184. data/lib/rails/tasks/tmp.rake +6 -6
  185. data/lib/rails/tasks/zeitwerk.rake +15 -35
  186. data/lib/rails/tasks.rb +0 -2
  187. data/lib/rails/templates/layouts/application.html.erb +1 -1
  188. data/lib/rails/templates/rails/mailers/email.html.erb +44 -8
  189. data/lib/rails/templates/rails/mailers/index.html.erb +14 -7
  190. data/lib/rails/templates/rails/mailers/mailer.html.erb +11 -5
  191. data/lib/rails/templates/rails/welcome/index.html.erb +5 -2
  192. data/lib/rails/test_help.rb +11 -18
  193. data/lib/rails/test_unit/line_filtering.rb +1 -1
  194. data/lib/rails/test_unit/reporter.rb +14 -4
  195. data/lib/rails/test_unit/runner.rb +62 -20
  196. data/lib/rails/test_unit/test_parser.rb +133 -0
  197. data/lib/rails/test_unit/testing.rake +13 -33
  198. data/lib/rails/testing/maintain_test_schema.rb +16 -0
  199. data/lib/rails/version.rb +1 -1
  200. data/lib/rails/zeitwerk_checker.rb +15 -0
  201. data/lib/rails.rb +20 -17
  202. metadata +87 -40
  203. data/RDOC_MAIN.rdoc +0 -97
  204. data/lib/rails/app_updater.rb +0 -40
  205. data/lib/rails/application/dummy_erb_compiler.rb +0 -18
  206. data/lib/rails/commands/secrets/USAGE +0 -66
  207. data/lib/rails/commands/secrets/secrets_command.rb +0 -65
  208. data/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml.tt +0 -68
  209. data/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml.tt +0 -70
  210. data/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml.tt +0 -24
  211. data/lib/rails/generators/rails/app/templates/config/databases/oracle.yml.tt +0 -62
  212. data/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml.tt +0 -53
  213. data/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_7_0.rb.tt +0 -143
  214. data/lib/rails/generators/rails/app/templates/public/apple-touch-icon-precomposed.png +0 -0
  215. data/lib/rails/generators/rails/app/templates/public/apple-touch-icon.png +0 -0
  216. data/lib/rails/generators/rails/app/templates/public/favicon.ico +0 -0
  217. data/lib/rails/generators/rails/model/USAGE +0 -113
  218. data/lib/rails/ruby_version_check.rb +0 -15
  219. data/lib/rails/secrets.rb +0 -110
  220. data/lib/rails/tasks/middleware.rake +0 -9
  221. data/lib/rails/tasks/restart.rake +0 -9
@@ -6,6 +6,7 @@ module Rails
6
6
  argument :actions, type: :array, default: [], banner: "action action"
7
7
  class_option :skip_routes, type: :boolean, desc: "Don't add routes to config/routes.rb."
8
8
  class_option :helper, type: :boolean
9
+ class_option :parent, type: :string, default: "ApplicationController", desc: "The parent class for the generated controller"
9
10
 
10
11
  check_class_collision suffix: "Controller"
11
12
 
@@ -16,7 +17,7 @@ module Rails
16
17
  def add_routes
17
18
  return if options[:skip_routes]
18
19
  return if actions.empty?
19
- routing_code = actions.map { |action| "get '#{file_name}/#{action}'" }.join("\n")
20
+ routing_code = actions.map { |action| %(get "#{file_name}/#{action}") }.join("\n")
20
21
  route routing_code, namespace: regular_class_path
21
22
  end
22
23
 
@@ -25,6 +26,10 @@ module Rails
25
26
  end
26
27
 
27
28
  private
29
+ def parent_class_name
30
+ options[:parent]
31
+ end
32
+
28
33
  def file_name
29
34
  @_file_name ||= remove_possible_suffix(super)
30
35
  end
@@ -1,5 +1,5 @@
1
1
  <% module_namespacing do -%>
2
- class <%= class_name %>Controller < ApplicationController
2
+ class <%= class_name %>Controller < <%= parent_class_name.classify %>
3
3
  <% actions.each do |action| -%>
4
4
  def <%= action %>
5
5
  end
@@ -7,49 +7,54 @@ require "active_support/encrypted_configuration"
7
7
  module Rails
8
8
  module Generators
9
9
  class CredentialsGenerator < Base # :nodoc:
10
+ argument :content_path, default: "config/credentials.yml.enc"
11
+ argument :key_path, default: "config/master.key"
12
+ class_option :skip_secret_key_base, type: :boolean
13
+
10
14
  def add_credentials_file
11
- unless credentials.content_path.exist?
12
- template = credentials_template
15
+ in_root do
16
+ return if File.exist?(content_path)
13
17
 
14
- say "Adding #{credentials.content_path} to store encrypted credentials."
18
+ say "Adding #{content_path} to store encrypted credentials."
15
19
  say ""
20
+
21
+ content = render_template_to_encrypted_file
22
+
16
23
  say "The following content has been encrypted with the Rails master key:"
17
24
  say ""
18
- say template, :on_green
25
+ say content, :on_green
19
26
  say ""
20
-
21
- add_credentials_file_silently(template)
22
-
23
27
  say "You can edit encrypted credentials with `bin/rails credentials:edit`."
24
28
  say ""
25
29
  end
26
30
  end
27
31
 
28
- def add_credentials_file_silently(template = nil)
29
- unless credentials.content_path.exist?
30
- credentials.write(credentials_template)
31
- end
32
- end
33
-
34
32
  private
35
- def credentials
33
+ def encrypted_file
36
34
  ActiveSupport::EncryptedConfiguration.new(
37
- config_path: "config/credentials.yml.enc",
38
- key_path: "config/master.key",
35
+ config_path: content_path,
36
+ key_path: key_path,
39
37
  env_key: "RAILS_MASTER_KEY",
40
38
  raise_if_missing_key: true
41
39
  )
42
40
  end
43
41
 
44
- def credentials_template
45
- <<~YAML
46
- # aws:
47
- # access_key_id: 123
48
- # secret_access_key: 345
42
+ def secret_key_base
43
+ @secret_key_base ||= SecureRandom.hex(64)
44
+ end
45
+
46
+ def render_template_to_encrypted_file
47
+ empty_directory File.dirname(content_path)
48
+
49
+ content = nil
50
+
51
+ encrypted_file.change do |tmp_path|
52
+ template("credentials.yml", tmp_path, force: true, verbose: false) do |rendered|
53
+ content = rendered
54
+ end
55
+ end
49
56
 
50
- # Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
51
- secret_key_base: #{SecureRandom.hex(64)}
52
- YAML
57
+ content
53
58
  end
54
59
  end
55
60
  end
@@ -0,0 +1,8 @@
1
+ # aws:
2
+ # access_key_id: 123
3
+ # secret_access_key: 345
4
+ <% unless options.skip_secret_key_base? -%>
5
+
6
+ # Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
7
+ secret_key_base: <%= secret_key_base %>
8
+ <% end -%>
@@ -1,15 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rails/generators/base"
4
+ require "yaml"
5
+ require "json"
4
6
 
5
7
  module Rails
6
8
  module Generators
7
9
  module Db
8
10
  module System
9
11
  class ChangeGenerator < Base # :nodoc:
10
- include Database
11
12
  include AppName
12
13
 
14
+ BASE_PACKAGES = %w( curl libvips )
15
+ BUILD_PACKAGES = %w( build-essential git )
16
+
13
17
  class_option :to, required: true,
14
18
  desc: "The database system to switch to."
15
19
 
@@ -21,8 +25,8 @@ module Rails
21
25
  def initialize(*)
22
26
  super
23
27
 
24
- unless DATABASES.include?(options[:to])
25
- raise Error, "Invalid value for --to option. Supported preconfigurations are: #{DATABASES.join(", ")}."
28
+ unless Database::DATABASES.include?(options[:to])
29
+ raise Error, "Invalid value for --to option. Supported preconfigurations are: #{Database::DATABASES.join(", ")}."
26
30
  end
27
31
 
28
32
  opt = options.dup
@@ -35,14 +39,53 @@ module Rails
35
39
  end
36
40
 
37
41
  def edit_gemfile
38
- name, version = gem_for_database
42
+ name, version = database.gem
39
43
  gsub_file("Gemfile", all_database_gems_regex, name)
40
44
  gsub_file("Gemfile", gem_entry_regex_for(name), gem_entry_for(name, *version))
41
45
  end
42
46
 
47
+ def edit_dockerfile
48
+ dockerfile_path = File.expand_path("Dockerfile", destination_root)
49
+ return unless File.exist?(dockerfile_path)
50
+
51
+ gsub_file("Dockerfile", all_docker_bases_regex, docker_base_packages(database.base_package))
52
+ gsub_file("Dockerfile", all_docker_builds_regex, docker_build_packages(database.build_package))
53
+ end
54
+
55
+ def edit_devcontainer_files
56
+ return unless devcontainer?
57
+
58
+ edit_devcontainer_json
59
+ edit_compose_yaml
60
+ end
61
+
43
62
  private
44
63
  def all_database_gems
45
- DATABASES.map { |database| gem_for_database(database) }
64
+ Database.all.map { |database| database.gem }
65
+ end
66
+
67
+ def all_docker_bases
68
+ Database.all.map { |database| docker_base_packages(database.base_package) }.uniq
69
+ end
70
+
71
+ def docker_base_packages(database_package)
72
+ if database_package
73
+ [database_package].concat(BASE_PACKAGES).sort
74
+ else
75
+ BASE_PACKAGES
76
+ end.join("\s")
77
+ end
78
+
79
+ def all_docker_builds
80
+ Database.all.map { |database| docker_build_packages(database.build_package) }.uniq
81
+ end
82
+
83
+ def docker_build_packages(database_package)
84
+ if database_package
85
+ [database_package].concat(BUILD_PACKAGES).sort
86
+ else
87
+ BUILD_PACKAGES
88
+ end.join("\s")
46
89
  end
47
90
 
48
91
  def all_database_gems_regex
@@ -50,6 +93,14 @@ module Rails
50
93
  /(\b#{all_database_gem_names.join('\b|\b')}\b)/
51
94
  end
52
95
 
96
+ def all_docker_bases_regex
97
+ /(\b#{all_docker_bases.join('\b|\b')}\b)/
98
+ end
99
+
100
+ def all_docker_builds_regex
101
+ /(\b#{all_docker_builds.join('\b|\b')}\b)/
102
+ end
103
+
53
104
  def gem_entry_regex_for(gem_name)
54
105
  /^gem.*\b#{gem_name}\b.*/
55
106
  end
@@ -58,6 +109,96 @@ module Rails
58
109
  gem_name_and_version.map! { |segment| "\"#{segment}\"" }
59
110
  "gem #{gem_name_and_version.join(", ")}"
60
111
  end
112
+
113
+ def edit_devcontainer_json
114
+ return unless devcontainer_json
115
+
116
+ update_devcontainer_db_host
117
+ update_devcontainer_db_feature
118
+ end
119
+
120
+ def edit_compose_yaml
121
+ compose_yaml_path = File.expand_path(".devcontainer/compose.yaml", destination_root)
122
+ return unless File.exist?(compose_yaml_path)
123
+
124
+ compose_config = YAML.load_file(compose_yaml_path)
125
+
126
+ Database.all.each do |database|
127
+ compose_config["services"].delete(database.name)
128
+ compose_config["volumes"]&.delete(database.volume)
129
+ compose_config["services"]["rails-app"]["depends_on"]&.delete(database.name)
130
+ end
131
+
132
+ if database.service
133
+ compose_config["services"][database.name] = database.service
134
+ compose_config["volumes"] = { database.volume => nil }.merge(compose_config["volumes"] || {})
135
+ compose_config["services"]["rails-app"]["depends_on"] = [
136
+ database.name,
137
+ compose_config["services"]["rails-app"]["depends_on"]
138
+ ].flatten.compact
139
+ end
140
+
141
+ compose_config.delete("volumes") unless compose_config["volumes"]&.any?
142
+ compose_config["services"]["rails-app"].delete("depends_on") unless compose_config["services"]["rails-app"]["depends_on"]&.any?
143
+
144
+ File.write(compose_yaml_path, compose_config.to_yaml)
145
+ end
146
+
147
+ def update_devcontainer_db_host
148
+ container_env = devcontainer_json["containerEnv"]
149
+ db_name = database.name
150
+
151
+ if container_env["DB_HOST"]
152
+ if database.service
153
+ container_env["DB_HOST"] = db_name
154
+ else
155
+ container_env.delete("DB_HOST")
156
+ end
157
+ else
158
+ if database.service
159
+ container_env["DB_HOST"] = db_name
160
+ end
161
+ end
162
+
163
+ new_json = JSON.pretty_generate(container_env, indent: " ", object_nl: "\n ")
164
+
165
+ gsub_file(".devcontainer/devcontainer.json", /("containerEnv"\s*:\s*)(.|\n)*?(^\s{2}})/, "\\1#{new_json}")
166
+ end
167
+
168
+ def update_devcontainer_db_feature
169
+ features = devcontainer_json["features"]
170
+ db_feature = database.feature
171
+
172
+ Database.all.each do |database|
173
+ features.delete(database.feature_name)
174
+ end
175
+
176
+ features.merge!(db_feature) if db_feature
177
+
178
+ new_json = JSON.pretty_generate(features, indent: " ", object_nl: "\n ")
179
+
180
+ gsub_file(".devcontainer/devcontainer.json", /("features"\s*:\s*)(.|\n)*?(^\s{2}})/, "\\1#{new_json}")
181
+ end
182
+
183
+ def devcontainer_json
184
+ return unless File.exist?(devcontainer_json_path)
185
+
186
+ @devcontainer_json ||= JSON.parse(File.read(devcontainer_json_path))
187
+ end
188
+
189
+ def devcontainer_json_path
190
+ File.expand_path(".devcontainer/devcontainer.json", destination_root)
191
+ end
192
+
193
+ def database
194
+ @database ||= Database.build(options[:database])
195
+ end
196
+
197
+ def devcontainer?
198
+ return @devcontainer if defined?(@devcontainer)
199
+
200
+ @devcontainer = File.exist?(File.expand_path(".devcontainer", destination_root))
201
+ end
61
202
  end
62
203
  end
63
204
  end
@@ -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", ".devcontainer/devcontainer.json"
35
+ template "devcontainer/Dockerfile", ".devcontainer/Dockerfile"
36
+ template "devcontainer/compose.yaml", ".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
+ optimize_indentation(<<-'RUBY', 2).chomp
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
@@ -0,0 +1,3 @@
1
+ # Make sure RUBY_VERSION matches the Ruby version in .ruby-version
2
+ ARG RUBY_VERSION=<%= gem_ruby_version %>
3
+ FROM ghcr.io/rails/devcontainer/images/ruby:$RUBY_VERSION
@@ -0,0 +1,47 @@
1
+ name: "<%= options[:app_name] %>"
2
+
3
+ services:
4
+ rails-app:
5
+ build:
6
+ context: ..
7
+ dockerfile: .devcontainer/Dockerfile
8
+
9
+ volumes:
10
+ - ../..:/workspaces:cached
11
+
12
+ # Overrides default command so things don't shut down after the process ends.
13
+ command: sleep infinity
14
+
15
+ # Uncomment the next line to use a non-root user for all processes.
16
+ # user: vscode
17
+
18
+ # Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
19
+ # (Adding the "ports" property to this file will not forward from a Codespace.)
20
+ <%- if !dependencies.empty? -%>
21
+ depends_on:
22
+ <%- dependencies.each do |dependency| -%>
23
+ - <%= dependency %>
24
+ <%- end -%>
25
+ <%- end -%>
26
+ <%- if options[:system_test] -%>
27
+
28
+ selenium:
29
+ image: selenium/standalone-chromium
30
+ restart: unless-stopped
31
+ <%- end -%>
32
+
33
+ <%- if options[:redis] -%>
34
+ redis:
35
+ image: redis:7.2
36
+ restart: unless-stopped
37
+ volumes:
38
+ - redis-data:/data
39
+
40
+ <%- end -%>
41
+ <%= devcontainer_db_service_yaml(indentation: 4) %>
42
+ <%- if !volumes.empty? -%>
43
+ volumes:
44
+ <%- volumes.each do |volume| -%>
45
+ <%= volume %>:
46
+ <%- end -%>
47
+ <%- end -%>
@@ -0,0 +1,37 @@
1
+ // For format details, see https://aka.ms/devcontainer.json. For config options, see the
2
+ // README at: https://github.com/devcontainers/templates/tree/main/src/ruby
3
+ {
4
+ "name": "<%= options[:app_name] %>",
5
+ "dockerComposeFile": "compose.yaml",
6
+ "service": "rails-app",
7
+ "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
8
+
9
+ // Features to add to the dev container. More info: https://containers.dev/features.
10
+ "features": {
11
+ <%= features.map { |key, value| "\"#{key}\": #{value.as_json}" }.join(",\n ") %>
12
+ },
13
+
14
+ <%- if !container_env.empty? -%>
15
+ "containerEnv": {
16
+ <%= container_env.map { |key, value| "\"#{key}\": \"#{value}\"" }.join(",\n ") %>
17
+ },
18
+ <%- end -%>
19
+
20
+ // Use 'forwardPorts' to make a list of ports inside the container available locally.
21
+ "forwardPorts": <%= forward_ports.as_json %>,
22
+
23
+ // Configure tool-specific properties.
24
+ // "customizations": {},
25
+
26
+ // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
27
+ // "remoteUser": "root",
28
+
29
+ <%- if !mounts.empty? -%>
30
+ "mounts": [
31
+ <%= mounts.map { |mount| "{\n " + mount.map { |key, value| "\"#{key}\": \"#{value}\"" }.join(",\n ") + "\n }" }.join(",\n ") %>
32
+ ],
33
+ <%- end -%>
34
+
35
+ // Use 'postCreateCommand' to run commands after the container is created.
36
+ "postCreateCommand": "bin/setup"
37
+ }
@@ -26,8 +26,7 @@ module Rails
26
26
  end
27
27
 
28
28
  def add_key_file_silently(key_path, key = nil)
29
- create_file key_path, key || ActiveSupport::EncryptedFile.generate_key
30
- key_path.chmod 0600
29
+ create_file key_path, key || ActiveSupport::EncryptedFile.generate_key, perm: 0600
31
30
  end
32
31
 
33
32
  def ignore_key_file(key_path, ignore: key_ignore(key_path))
@@ -5,31 +5,41 @@ Description:
5
5
  A migration class is generated in db/migrate prefixed by a timestamp of the current date and time.
6
6
 
7
7
  You can name your migration in either of these formats to generate add/remove
8
- column lines from supplied attributes: AddColumnsToTable or RemoveColumnsFromTable
8
+ column lines from supplied attributes: add_{columns}_to_{table} or remove_{columns}_from_{table}.
9
9
 
10
- Example:
11
- `bin/rails generate migration AddSslFlag`
10
+ A migration name containing JoinTable will generate join tables for use with
11
+ has_and_belongs_to_many associations.
12
+
13
+ You can also name your migration create_{table} along with any attributes to generate a regular table.
14
+
15
+ Examples:
16
+ `bin/rails generate migration add_ssl_flag`
12
17
 
13
18
  If the current date is May 14, 2008 and the current time 09:09:12, this creates the AddSslFlag migration
14
19
  db/migrate/20080514090912_add_ssl_flag.rb
15
20
 
16
- `bin/rails generate migration AddTitleBodyToPost title:string body:text published:boolean`
21
+ `bin/rails generate migration add_title_body_published_to_post title:string body:text published:boolean`
17
22
 
18
- This will create the AddTitleBodyToPost in db/migrate/20080514090912_add_title_body_to_post.rb with this in the Change migration:
23
+ This will create db/migrate/20080514090912_add_title_body_published_to_post.rb with this in the migration:
19
24
 
20
25
  add_column :posts, :title, :string
21
26
  add_column :posts, :body, :text
22
27
  add_column :posts, :published, :boolean
23
28
 
24
- Migration names containing JoinTable will generate join tables for use with
25
- has_and_belongs_to_many associations.
29
+ `bin/rails generate migration create_media_join_table artists musics:uniq`
26
30
 
27
- Example:
28
- `bin/rails g migration CreateMediaJoinTable artists musics:uniq`
29
-
30
- will create the migration
31
+ This will create a join table migration:
31
32
 
32
33
  create_join_table :artists, :musics do |t|
33
34
  # t.index [:artist_id, :music_id]
34
35
  t.index [:music_id, :artist_id], unique: true
35
36
  end
37
+
38
+ `bin/rails generate migration create_users email:string`
39
+
40
+ This will create the migration:
41
+
42
+ create_table :users do |t|
43
+ t.string :email
44
+ t.timestamps
45
+ 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
@@ -9,6 +9,10 @@ module Rails
9
9
 
10
10
  argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]"
11
11
  hook_for :orm, required: true, desc: "ORM to be invoked"
12
+
13
+ class << self
14
+ delegate(:desc, to: :orm_generator)
15
+ end
12
16
  end
13
17
  end
14
18
  end