1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Add rails db:system:change command

Add `rails db:system:change` command for changing databases.

```
bin/rails db:system:change --to=postgresql
   force  config/database.yml
    gsub  Gemfile
```

The change command copies a template `config/database.yml` with
the target database adapter into your app, and replaces your database
gem with the target database gem.
This commit is contained in:
Gannon McGibbon 2018-12-31 13:46:30 -05:00
parent e3204b9c33
commit 4b1ae57f0f
8 changed files with 265 additions and 1 deletions

View file

@ -1,3 +1,15 @@
* Add `rails db:system:change` command for changing databases.
```
bin/rails db:system:change --to=postgresql
force config/database.yml
gsub Gemfile
```
The change command copies a template `config/database.yml` with the target database adapter into your app, and replaces your database gem with the target database gem.
*Gannon McGibbon*
* Use original `bundler` environment variables during the process of generating a new rails project.
*Marco Costa*

View file

@ -0,0 +1,20 @@
# frozen_string_literal: true
require "rails/generators"
require "rails/generators/rails/db/system/change/change_generator"
module Rails
module Command
module Db
module System
class ChangeCommand < Base # :nodoc:
class_option :to, desc: "The database system to switch to."
def perform
Rails::Generators::Db::System::ChangeGenerator.start
end
end
end
end
end
end

View file

@ -220,6 +220,7 @@ module Rails
rails.delete("encryption_key_file")
rails.delete("master_key")
rails.delete("credentials")
rails.delete("db:system:change")
hidden_namespaces.each { |n| groups.delete(n.to_s) }

View file

@ -0,0 +1,55 @@
# frozen_string_literal: true
require "rails/generators/base"
module Rails
module Generators
module Db
module System
class ChangeGenerator < Base # :nodoc:
include Database
include AppName
class_option :to, required: true,
desc: "The database system to switch to."
def self.default_generator_root
path = File.expand_path(File.join(base_name, "app"), base_root)
path if File.exist?(path)
end
def initialize(*)
super
unless DATABASES.include?(options[:to])
raise Error, "Invalid value for --to option. Supported for preconfiguration are: #{DATABASES.join(", ")}."
end
opt = options.dup
opt[:database] ||= opt[:to]
self.options = opt.freeze
end
def edit_database_config
template("config/databases/#{options[:database]}.yml", "config/database.yml")
end
def edit_gemfile
database_gem_name, _ = gem_for_database
gsub_file("Gemfile", all_database_gems_regex, database_gem_name)
end
private
def all_database_gems
DATABASES.map { |database| gem_for_database(database) }
end
def all_database_gems_regex
all_database_gem_names = all_database_gems.map(&:first)
/(\b#{all_database_gem_names.join('\b|\b')}\b)/
end
end
end
end
end
end

View file

@ -0,0 +1,62 @@
# frozen_string_literal: true
require "isolation/abstract_unit"
require "rails/command"
require "rails/commands/db/system/change/change_command"
class Rails::Command::Db::System::ChangeCommandTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
setup { build_app }
teardown { teardown_app }
test "change to existing database" do
change_database(to: "sqlite3")
output = change_database(to: "sqlite3")
assert_match "identical config/database.yml", output
assert_match "gsub Gemfile", output
end
test "change to invalid database" do
output = change_database(to: "invalid-db")
assert_match <<~MSG.squish, output
Invalid value for --to option.
Supported for preconfiguration are:
mysql, postgresql, sqlite3, oracle, frontbase,
ibm_db, sqlserver, jdbcmysql, jdbcsqlite3,
jdbcpostgresql, jdbc.
MSG
end
test "change to postgresql" do
output = change_database(to: "postgresql")
assert_match "force config/database.yml", output
assert_match "gsub Gemfile", output
end
test "change to mysql" do
output = change_database(to: "mysql")
assert_match "force config/database.yml", output
assert_match "gsub Gemfile", output
end
test "change to sqlite3" do
change_database(to: "postgresql")
output = change_database(to: "sqlite3")
assert_match "force config/database.yml", output
assert_match "gsub Gemfile", output
end
private
def change_database(to:, **options)
args = ["--to", to]
rails "db:system:change", args, **options
end
end

View file

@ -0,0 +1,78 @@
# frozen_string_literal: true
require "generators/generators_test_helper"
require "rails/generators/rails/db/system/change/change_generator"
module Rails
module Generators
module Db
module System
class ChangeGeneratorTest < Rails::Generators::TestCase
include GeneratorsTestHelper
setup do
copy_gemfile(
GemfileEntry.new("sqlite3", nil, "Use sqlite3 as the database for Active Record")
)
end
test "change to invalid database" do
output = capture(:stderr) do
run_generator ["--to", "invalid-db"]
end
assert_match <<~MSG.squish, output
Invalid value for --to option.
Supported for preconfiguration are:
mysql, postgresql, sqlite3, oracle, frontbase,
ibm_db, sqlserver, jdbcmysql, jdbcsqlite3,
jdbcpostgresql, jdbc.
MSG
end
test "change to postgresql" do
run_generator ["--to", "postgresql"]
assert_file("config/database.yml") do |content|
assert_match "adapter: postgresql", content
assert_match "database: test_app", content
end
assert_file("Gemfile") do |content|
assert_match "# Use pg as the database for Active Record", content
assert_match "gem 'pg'", content
end
end
test "change to mysql" do
run_generator ["--to", "mysql"]
assert_file("config/database.yml") do |content|
assert_match "adapter: mysql2", content
assert_match "database: test_app", content
end
assert_file("Gemfile") do |content|
assert_match "# Use mysql2 as the database for Active Record", content
assert_match "gem 'mysql2'", content
end
end
test "change to sqlite3" do
run_generator ["--to", "sqlite3"]
assert_file("config/database.yml") do |content|
assert_match "adapter: sqlite3", content
assert_match "db/development.sqlite3", content
end
assert_file("Gemfile") do |content|
assert_match "# Use sqlite3 as the database for Active Record", content
assert_match "gem 'sqlite3'", content
end
end
end
end
end
end
end

View file

@ -30,6 +30,12 @@ module GeneratorsTestHelper
include ActiveSupport::Testing::Stream
include ActiveSupport::Testing::MethodCallAssertions
GemfileEntry = Struct.new(:name, :version, :comment, :options, :commented_out) do
def initialize(name, version, comment, options = {}, commented_out = false)
super
end
end
def self.included(base)
base.class_eval do
destination File.join(Rails.root, "tmp")
@ -63,4 +69,34 @@ module GeneratorsTestHelper
FileUtils.mkdir_p(destination)
FileUtils.cp routes, File.join(destination, "routes.rb")
end
def copy_gemfile(*gemfile_entries)
locals = gemfile_locals.merge(gemfile_entries: gemfile_entries)
gemfile = File.expand_path("../../lib/rails/generators/rails/app/templates/Gemfile.tt", __dir__)
gemfile = evaluate_template(gemfile, locals)
destination = File.join(destination_root)
File.write File.join(destination, "Gemfile"), gemfile
end
def evaluate_template(file, locals = {})
erb = ERB.new(File.read(file), nil, "-", "@output_buffer")
context = Class.new do
locals.each do |local, value|
class_attribute local, default: value
end
end
erb.result(context.new.instance_eval("binding"))
end
private
def gemfile_locals
{
skip_active_storage: true,
depend_on_bootsnap: false,
depend_on_listen: false,
spring_install: false,
depends_on_system_test: false,
options: ActiveSupport::OrderedOptions.new,
}
end
end

View file

@ -476,7 +476,7 @@ Module.new do
`yarn build`
end
`#{Gem.ruby} #{RAILS_FRAMEWORK_ROOT}/railties/exe/rails new #{app_template_path} --skip-gemfile --skip-listen --no-rc`
`#{Gem.ruby} #{RAILS_FRAMEWORK_ROOT}/railties/exe/rails new #{app_template_path} --skip-bundle --skip-listen --no-rc`
File.open("#{app_template_path}/config/boot.rb", "w") do |f|
f.puts "require 'rails/all'"
end