diff --git a/railties/lib/rails/generators/devcontainer.rb b/railties/lib/rails/generators/devcontainer.rb index 73295264bee50..5e732f9d14b7c 100644 --- a/railties/lib/rails/generators/devcontainer.rb +++ b/railties/lib/rails/generators/devcontainer.rb @@ -3,12 +3,6 @@ module Rails module Generators module Devcontainer - DB_FEATURES = { - "mysql" => "ghcr.io/rails/devcontainer/features/mysql-client", - "postgresql" => "ghcr.io/rails/devcontainer/features/postgres-client", - "sqlite3" => "ghcr.io/rails/devcontainer/features/sqlite3" - } - private def devcontainer_dependencies return @devcontainer_dependencies if @devcontainer_dependencies @@ -17,7 +11,7 @@ def devcontainer_dependencies @devcontainer_dependencies << "selenium" if depends_on_system_test? @devcontainer_dependencies << "redis" if devcontainer_needs_redis? - @devcontainer_dependencies << db_name_for_devcontainer if db_name_for_devcontainer + @devcontainer_dependencies << devcontainer_database.name if devcontainer_database.service @devcontainer_dependencies end @@ -29,7 +23,7 @@ def devcontainer_variables @devcontainer_variables["CAPYBARA_SERVER_PORT"] = "45678" if depends_on_system_test? @devcontainer_variables["SELENIUM_HOST"] = "selenium" if depends_on_system_test? @devcontainer_variables["REDIS_URL"] = "redis://redis:6379/1" if devcontainer_needs_redis? - @devcontainer_variables["DB_HOST"] = db_name_for_devcontainer if db_name_for_devcontainer + @devcontainer_variables["DB_HOST"] = devcontainer_database.name if devcontainer_database.service @devcontainer_variables end @@ -40,7 +34,7 @@ def devcontainer_volumes @devcontainer_volumes = [] @devcontainer_volumes << "redis-data" if devcontainer_needs_redis? - @devcontainer_volumes << db_volume_name_for_devcontainer if db_volume_name_for_devcontainer + @devcontainer_volumes << devcontainer_database.volume if devcontainer_database.volume @devcontainer_volumes end @@ -55,7 +49,7 @@ def devcontainer_features @devcontainer_features["ghcr.io/rails/devcontainer/features/activestorage"] = {} unless options[:skip_active_storage] @devcontainer_features["ghcr.io/devcontainers/features/node:1"] = {} if using_node? - @devcontainer_features.merge!(db_feature_for_devcontainer) if db_feature_for_devcontainer + @devcontainer_features.merge!(devcontainer_database.feature) if devcontainer_database.feature @devcontainer_features end @@ -74,7 +68,7 @@ def devcontainer_forward_ports return @devcontainer_forward_ports if @devcontainer_forward_ports @devcontainer_forward_ports = [3000] - @devcontainer_forward_ports << db_port_for_devcontainer if db_port_for_devcontainer + @devcontainer_forward_ports << devcontainer_database.port if devcontainer_database.port @devcontainer_forward_ports << 6379 if devcontainer_needs_redis? @devcontainer_forward_ports @@ -84,128 +78,180 @@ def devcontainer_needs_redis? !(options.skip_action_cable? && options.skip_active_job?) end - def db_port_for_devcontainer(database = options[:database]) - case database - when "mysql", "trilogy" then 3306 - when "postgresql" then 5432 - end + def devcontainer_database + @devcontainer_database ||= Database.build(options[:database]) end - def db_name_for_devcontainer(database = options[:database]) - case database - when "mysql" then "mysql" - when "trilogy" then "mariadb" - when "postgresql" then "postgres" - end + def devcontainer_db_service_yaml(**options) + return unless service = devcontainer_database.service + + { devcontainer_database.name => service }.to_yaml(**options)[4..-1] end - def db_volume_name_for_devcontainer(database = options[:database]) - case database - when "mysql" then "mysql-data" - when "trilogy" then "mariadb-data" - when "postgresql" then "postgres-data" - end + def local_rails_mount + { + type: "bind", + source: Rails::Generators::RAILS_DEV_PATH, + target: Rails::Generators::RAILS_DEV_PATH + } end - def db_package_for_dockerfile(database = options[:database]) - case database - when "mysql" then "default-libmysqlclient-dev" - when "postgresql" then "libpq-dev" + class Database + class << self + def build(database_name) + case database_name + when "mysql" then MySQL.new + when "postgresql" then PostgreSQL.new + when "trilogy" then MariaDB.new + when "sqlite3" then SQLite3.new + else Null.new + end + end + + def all + @all ||= [ + MySQL.new, + PostgreSQL.new, + MariaDB.new, + SQLite3.new, + ] + end end - end - def devcontainer_db_service_yaml(**options) - return unless service = db_service_for_devcontainer + def name + raise NotImplementedError + end - service.to_yaml(**options)[4..-1] - end + def service + raise NotImplementedError + end - def db_service_for_devcontainer(database = options[:database]) - case database - when "mysql" then mysql_service - when "trilogy" then mariadb_service - when "postgresql" then postgres_service + def port + raise NotImplementedError end - end - def db_feature_for_devcontainer(database = options[:database]) - case database - when "sqlite3" then sqlite3_feature - when "mysql" then mysql_feature - when "postgresql" then postgres_feature + def feature_name + raise NotImplementedError end - end - def postgres_service - { - "postgres" => { - "image" => "postgres:16.1", - "restart" => "unless-stopped", - "networks" => ["default"], - "volumes" => ["postgres-data:/var/lib/postgresql/data"], - "environment" => { - "POSTGRES_USER" => "postgres", - "POSTGRES_PASSWORD" => "postgres" + def feature + return unless feature_name + + { feature_name => {} } + end + + def volume + return unless service + + "#{name}-data" + end + + class MySQL < Database + def name + "mysql" + end + + def service + { + "image" => "mysql/mysql-server:8.0", + "restart" => "unless-stopped", + "environment" => { + "MYSQL_ALLOW_EMPTY_PASSWORD" => "true", + "MYSQL_ROOT_HOST" => "%" + }, + "volumes" => ["mysql-data:/var/lib/mysql"], + "networks" => ["default"], } - } - } - end + end - def mysql_service - { - "mysql" => { - "image" => "mysql/mysql-server:8.0", - "restart" => "unless-stopped", - "environment" => { - "MYSQL_ALLOW_EMPTY_PASSWORD" => "true", - "MYSQL_ROOT_HOST" => "%" - }, - "volumes" => ["mysql-data:/var/lib/mysql"], - "networks" => ["default"], - } - } - end + def port + 3306 + end - def mariadb_service - { - "mariadb" => { - "image" => "mariadb:10.5", - "restart" => "unless-stopped", - "networks" => ["default"], - "volumes" => ["mariadb-data:/var/lib/mysql"], - "environment" => { - "MARIADB_ALLOW_EMPTY_ROOT_PASSWORD" => "true", - }, - } - } - end + def feature_name + "ghcr.io/rails/devcontainer/features/mysql-client" + end + end - def db_service_names - ["mysql", "mariadb", "postgres"] - end + class PostgreSQL < Database + def name + "postgres" + end + + def service + { + "image" => "postgres:16.1", + "restart" => "unless-stopped", + "networks" => ["default"], + "volumes" => ["postgres-data:/var/lib/postgresql/data"], + "environment" => { + "POSTGRES_USER" => "postgres", + "POSTGRES_PASSWORD" => "postgres" + } + } + end - def mysql_feature - { DB_FEATURES["mysql"] => {} } - end + def port + 5432 + end - def postgres_feature - { DB_FEATURES["postgresql"] => {} } - end + def feature_name + "ghcr.io/rails/devcontainer/features/postgres-client" + end + end - def sqlite3_feature - { DB_FEATURES["sqlite3"] => {} } - end + class MariaDB < Database + def name + "mariadb" + end + + def service + { + "image" => "mariadb:10.5", + "restart" => "unless-stopped", + "networks" => ["default"], + "volumes" => ["mariadb-data:/var/lib/mysql"], + "environment" => { + "MARIADB_ALLOW_EMPTY_ROOT_PASSWORD" => "true", + }, + } + end - def db_features - @db_features ||= DB_FEATURES.values - end + def port + 3306 + end - def local_rails_mount - { - type: "bind", - source: Rails::Generators::RAILS_DEV_PATH, - target: Rails::Generators::RAILS_DEV_PATH - } + def feature_name + nil + end + end + + class SQLite3 < Database + def name + "sqlite3" + end + + def service + nil + end + + def port + nil + end + + def feature_name + "ghcr.io/rails/devcontainer/features/sqlite3" + end + end + + class Null < Database + def name; end + def service; end + def port; end + def volume; end + def feature; end + def feature_name; end + end end end end diff --git a/railties/lib/rails/generators/rails/db/system/change/change_generator.rb b/railties/lib/rails/generators/rails/db/system/change/change_generator.rb index ac76a60f0b0c1..1d8c409b68f67 100644 --- a/railties/lib/rails/generators/rails/db/system/change/change_generator.rb +++ b/railties/lib/rails/generators/rails/db/system/change/change_generator.rb @@ -10,7 +10,6 @@ module Db module System class ChangeGenerator < Base # :nodoc: include Database - include Devcontainer include AppName class_option :to, required: true, @@ -113,19 +112,17 @@ def edit_compose_yaml compose_config = YAML.load_file(compose_yaml_path) - db_service_names.each do |db_service_name| - compose_config["services"].delete(db_service_name) - compose_config["volumes"]&.delete("#{db_service_name}-data") - compose_config["services"]["rails-app"]["depends_on"]&.delete(db_service_name) + Devcontainer::Database.all.each do |database| + compose_config["services"].delete(database.name) + compose_config["volumes"]&.delete(database.volume) + compose_config["services"]["rails-app"]["depends_on"]&.delete(database.name) end - db_service = db_service_for_devcontainer - - if db_service - compose_config["services"].merge!(db_service) - compose_config["volumes"] = { db_volume_name_for_devcontainer => nil }.merge(compose_config["volumes"] || {}) + if devcontainer_database.service + compose_config["services"][devcontainer_database.name] = devcontainer_database.service + compose_config["volumes"] = { devcontainer_database.volume => nil }.merge(compose_config["volumes"] || {}) compose_config["services"]["rails-app"]["depends_on"] = [ - db_name_for_devcontainer, + devcontainer_database.name, compose_config["services"]["rails-app"]["depends_on"] ].flatten.compact end @@ -138,16 +135,16 @@ def edit_compose_yaml def update_devcontainer_db_host container_env = devcontainer_json["containerEnv"] - db_name = db_name_for_devcontainer + db_name = devcontainer_database.name if container_env["DB_HOST"] - if db_name + if devcontainer_database.service container_env["DB_HOST"] = db_name else container_env.delete("DB_HOST") end else - if db_name + if devcontainer_database.service container_env["DB_HOST"] = db_name end end @@ -159,10 +156,10 @@ def update_devcontainer_db_host def update_devcontainer_db_feature features = devcontainer_json["features"] - db_feature = db_feature_for_devcontainer + db_feature = devcontainer_database.feature - db_features.each do |feature| - features.delete(feature) + Devcontainer::Database.all.each do |database| + features.delete(database.feature_name) end features.merge!(db_feature) if db_feature @@ -181,6 +178,10 @@ def devcontainer_json def devcontainer_json_path File.expand_path(".devcontainer/devcontainer.json", destination_root) end + + def devcontainer_database + @devcontainer_database ||= Devcontainer::Database.build(options[:database]) + end end end end diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 83a610961266e..b4338f0459a6f 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -1285,9 +1285,9 @@ def test_devcontainer assert_equal "redis://redis:6379/1", content["containerEnv"]["REDIS_URL"] assert_equal "45678", content["containerEnv"]["CAPYBARA_SERVER_PORT"] assert_equal "selenium", content["containerEnv"]["SELENIUM_HOST"] - assert_equal({}, content["features"]["ghcr.io/rails/devcontainer/features/activestorage"]) - assert_equal({}, content["features"]["ghcr.io/devcontainers/features/github-cli:1"]) - assert_equal({}, content["features"]["ghcr.io/rails/devcontainer/features/sqlite3"]) + assert_includes content["features"].keys, "ghcr.io/rails/devcontainer/features/activestorage" + assert_includes content["features"].keys, "ghcr.io/devcontainers/features/github-cli:1" + assert_includes content["features"].keys, "ghcr.io/rails/devcontainer/features/sqlite3" assert_includes(content["forwardPorts"], 3000) assert_includes(content["forwardPorts"], 6379) end @@ -1365,8 +1365,8 @@ def test_devcontainer_postgresql end assert_devcontainer_json_file do |content| assert_equal "postgres", content["containerEnv"]["DB_HOST"] - assert_equal({}, content["features"]["ghcr.io/rails/devcontainer/features/postgres-client"]) - assert_includes(content["forwardPorts"], 5432) + assert_includes content["features"].keys, "ghcr.io/rails/devcontainer/features/postgres-client" + assert_includes content["forwardPorts"], 5432 end assert_file("config/database.yml") do |content| assert_match(/host: <%= ENV\["DB_HOST"\] %>/, content) @@ -1395,8 +1395,8 @@ def test_devcontainer_mysql end assert_devcontainer_json_file do |content| assert_equal "mysql", content["containerEnv"]["DB_HOST"] - assert_equal({}, content["features"]["ghcr.io/rails/devcontainer/features/mysql-client"]) - assert_includes(content["forwardPorts"], 3306) + assert_includes content["features"].keys, "ghcr.io/rails/devcontainer/features/mysql-client" + assert_includes content["forwardPorts"], 3306 end assert_file("config/database.yml") do |content| assert_match(/host: <%= ENV.fetch\("DB_HOST"\) \{ "localhost" } %>/, content)