提交 a8c0ebcc 编写于 作者: B Bogdan 提交者: David Heinemeier Hansson

Allow `truncate` for SQLite3 adapter and add `rails db:seed:replant` (#34779)

* Add `ActiveRecord::Base.connection.truncate` for SQLite3 adapter.

SQLite doesn't support `TRUNCATE TABLE`, but SQLite3 adapter can support
`ActiveRecord::Base.connection.truncate` by using `DELETE FROM`.

`DELETE` without `WHERE` uses "The Truncate Optimization",
see https://www.sqlite.org/lang_delete.html.

* Add `rails db:seed:replant` that truncates database tables and loads the seeds

Closes #34765
上级 076e8edd
* Add `rails db:seed:replant` that truncates tables of each database
for current environment and loads the seeds.
*bogdanvlviv*, *DHH*
* Add `ActiveRecord::Base.connection.truncate` for SQLite3 adapter.
*bogdanvlviv*
* Deprecate mismatched collation comparison for uniqueness validator.
Uniqueness validator will no longer enforce case sensitive comparison in Rails 6.1.
......
......@@ -154,6 +154,10 @@ def clear_cache!
@statements.clear
end
def truncate(table_name, name = nil)
execute "DELETE FROM #{quote_table_name(table_name)}", name
end
def supports_index_sort_order?
true
end
......
......@@ -66,6 +66,11 @@ db_namespace = namespace :db do
end
end
# desc "Truncates tables of each database for current environment"
task truncate_all: [:load_config, :check_protected_environments] do
ActiveRecord::Tasks::DatabaseTasks.truncate_all
end
# desc "Empty the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:purge:all to purge all databases in the config). Without RAILS_ENV it defaults to purging the development and test databases."
task purge: [:load_config, :check_protected_environments] do
ActiveRecord::Tasks::DatabaseTasks.purge_current
......@@ -223,6 +228,11 @@ db_namespace = namespace :db do
ActiveRecord::Tasks::DatabaseTasks.load_seed
end
namespace :seed do
desc "Truncates tables of each database for current environment and loads the seeds"
task replant: [:load_config, :truncate_all, :seed]
end
namespace :fixtures do
desc "Loads fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
task load: :load_config do
......
......@@ -182,6 +182,24 @@ def drop_current(environment = env)
}
end
def truncate_tables(configuration)
ActiveRecord::Base.connected_to(database: { truncation: configuration }) do
table_names = ActiveRecord::Base.connection.tables
internal_table_names = [
ActiveRecord::Base.schema_migrations_table_name,
ActiveRecord::Base.internal_metadata_table_name
]
class_for_adapter(configuration["adapter"]).new(configuration).truncate_tables(*table_names.without(*internal_table_names))
end
end
def truncate_all(environment = env)
ActiveRecord::Base.configurations.configs_for(env_name: environment).each do |db_config|
truncate_tables db_config.config
end
end
def migrate
check_target_version
......
......@@ -31,6 +31,16 @@ def purge
connection.recreate_database configuration["database"], creation_options
end
def truncate_tables(*table_names)
return if table_names.empty?
ActiveRecord::Base.connection.disable_referential_integrity do
table_names.each do |table_name|
ActiveRecord::Base.connection.truncate(table_name)
end
end
end
def charset
connection.charset
end
......
......@@ -48,6 +48,18 @@ def purge
create true
end
def truncate_tables(*table_names)
return if table_names.empty?
ActiveRecord::Base.connection.disable_referential_integrity do
quoted_table_names = table_names.map do |table_name|
ActiveRecord::Base.connection.quote_table_name(table_name)
end
ActiveRecord::Base.connection.execute "TRUNCATE TABLE #{quoted_table_names.join(", ")}"
end
end
def structure_dump(filename, extra_flags)
set_psql_env
......
......@@ -33,6 +33,16 @@ def purge
create
end
def truncate_tables(*table_names)
return if table_names.empty?
ActiveRecord::Base.connection.disable_referential_integrity do
table_names.each do |table_name|
ActiveRecord::Base.connection.truncate(table_name)
end
end
end
def charset
connection.encoding
end
......
# frozen_string_literal: true
require "cases/helper"
class SQLite3ConnectionTest < ActiveRecord::SQLite3TestCase
fixtures :comments
def test_truncate
rows = ActiveRecord::Base.connection.exec_query("select count(*) from comments")
count = rows.first.values.first
assert_operator count, :>, 0
ActiveRecord::Base.connection.truncate("comments")
rows = ActiveRecord::Base.connection.exec_query("select count(*) from comments")
count = rows.first.values.first
assert_equal 0, count
end
end
......@@ -2,6 +2,7 @@
require "cases/helper"
require "active_record/tasks/database_tasks"
require "models/author"
module ActiveRecord
module DatabaseTasksSetupper
......@@ -944,6 +945,127 @@ def test_purge_all_local_configurations
end
end
unless in_memory_db?
class DatabaseTasksTruncateAllTest < ActiveRecord::TestCase
self.use_transactional_tests = false
fixtures :authors, :author_addresses
def test_truncate_tables
assert_operator Author.count, :>, 0
assert_operator AuthorAddress.count, :>, 0
old_configurations = ActiveRecord::Base.configurations
configurations = { development: ActiveRecord::Base.configurations["arunit"] }
ActiveRecord::Base.configurations = configurations
ActiveRecord::Tasks::DatabaseTasks.stub(:root, nil) do
ActiveRecord::Tasks::DatabaseTasks.truncate_all(
ActiveSupport::StringInquirer.new("development")
)
end
assert_equal 0, Author.count
assert_equal 0, AuthorAddress.count
ensure
ActiveRecord::Base.configurations = old_configurations
end
end
end
class DatabaseTasksTruncateAllWithMultipleDatabasesTest < ActiveRecord::TestCase
def setup
@configurations = {
"development" => { "primary" => { "database" => "dev-db" }, "secondary" => { "database" => "secondary-dev-db" } },
"test" => { "primary" => { "database" => "test-db" }, "secondary" => { "database" => "secondary-test-db" } },
"production" => { "primary" => { "url" => "abstract://prod-db-host/prod-db" }, "secondary" => { "url" => "abstract://secondary-prod-db-host/secondary-prod-db" } }
}
end
def test_truncate_all_databases_for_environment
with_stubbed_configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:truncate_tables,
[
["database" => "test-db"],
["database" => "secondary-test-db"]
]
) do
ActiveRecord::Tasks::DatabaseTasks.truncate_all(
ActiveSupport::StringInquirer.new("test")
)
end
end
end
def test_truncate_all_databases_with_url_for_environment
with_stubbed_configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:truncate_tables,
[
["adapter" => "abstract", "database" => "prod-db", "host" => "prod-db-host"],
["adapter" => "abstract", "database" => "secondary-prod-db", "host" => "secondary-prod-db-host"]
]
) do
ActiveRecord::Tasks::DatabaseTasks.truncate_all(
ActiveSupport::StringInquirer.new("production")
)
end
end
end
def test_truncate_all_development_databases_when_env_was_no_specified
with_stubbed_configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:truncate_tables,
[
["database" => "dev-db"],
["database" => "secondary-dev-db"]
]
) do
ActiveRecord::Tasks::DatabaseTasks.truncate_all(
ActiveSupport::StringInquirer.new("development")
)
end
end
end
def test_truncate_all_development_databases_when_env_is_development
old_env = ENV["RAILS_ENV"]
ENV["RAILS_ENV"] = "development"
with_stubbed_configurations do
assert_called_with(
ActiveRecord::Tasks::DatabaseTasks,
:truncate_tables,
[
["database" => "dev-db"],
["database" => "secondary-dev-db"]
]
) do
ActiveRecord::Tasks::DatabaseTasks.truncate_all(
ActiveSupport::StringInquirer.new("development")
)
end
end
ensure
ENV["RAILS_ENV"] = old_env
end
private
def with_stubbed_configurations
old_configurations = ActiveRecord::Base.configurations
ActiveRecord::Base.configurations = @configurations
yield
ensure
ActiveRecord::Base.configurations = old_configurations
end
end
class DatabaseTasksCharsetTest < ActiveRecord::TestCase
include DatabaseTasksSetupper
......
# frozen_string_literal: true
require "isolation/abstract_unit"
require "env_helpers"
module ApplicationTests
module RakeTests
class RakeDbsTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
include ActiveSupport::Testing::Isolation, EnvHelpers
def setup
build_app
......@@ -139,6 +140,59 @@ def with_bad_permissions
end
end
test "db:truncate_all truncates all not internal tables" do
Dir.chdir(app_path) do
rails "generate", "model", "book", "title:string"
rails "db:migrate"
require "#{app_path}/config/environment"
Book.create!(title: "Remote")
assert_equal 1, Book.count
schema_migrations = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"")
internal_metadata = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"")
rails "db:truncate_all"
assert_equal(
schema_migrations,
ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"")
)
assert_equal(
internal_metadata,
ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"")
)
assert_equal 0, Book.count
end
end
test "db:truncate_all does not truncate any tables when environment is protected" do
with_rails_env "production" do
Dir.chdir(app_path) do
rails "generate", "model", "book", "title:string"
rails "db:migrate"
require "#{app_path}/config/environment"
Book.create!(title: "Remote")
assert_equal 1, Book.count
schema_migrations = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"")
internal_metadata = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"")
books = ActiveRecord::Base.connection.execute("SELECT * from \"books\"")
output = rails("db:truncate_all", allow_failure: true)
assert_match(/ActiveRecord::ProtectedEnvironmentError/, output)
assert_equal(
schema_migrations,
ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"")
)
assert_equal(
internal_metadata,
ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"")
)
assert_equal 1, Book.count
assert_equal(books, ActiveRecord::Base.connection.execute("SELECT * from \"books\""))
end
end
end
def db_migrate_and_status(expected_database)
rails "generate", "model", "book", "title:string"
rails "db:migrate"
......@@ -387,6 +441,72 @@ def db_test_load_structure
assert_equal "test", test_environment.call
end
test "db:seed:replant truncates all not internal tables and loads the seeds" do
Dir.chdir(app_path) do
rails "generate", "model", "book", "title:string"
rails "db:migrate"
require "#{app_path}/config/environment"
Book.create!(title: "Remote")
assert_equal 1, Book.count
schema_migrations = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"")
internal_metadata = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"")
app_file "db/seeds.rb", <<-RUBY
Book.create!(title: "Rework")
Book.create!(title: "Ruby Under a Microscope")
RUBY
rails "db:seed:replant"
assert_equal(
schema_migrations,
ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"")
)
assert_equal(
internal_metadata,
ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"")
)
assert_equal 2, Book.count
assert_not_predicate Book.where(title: "Remote"), :exists?
assert_predicate Book.where(title: "Rework"), :exists?
assert_predicate Book.where(title: "Ruby Under a Microscope"), :exists?
end
end
test "db:seed:replant does not truncate any tables and does not load the seeds when environment is protected" do
with_rails_env "production" do
Dir.chdir(app_path) do
rails "generate", "model", "book", "title:string"
rails "db:migrate"
require "#{app_path}/config/environment"
Book.create!(title: "Remote")
assert_equal 1, Book.count
schema_migrations = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"")
internal_metadata = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"")
books = ActiveRecord::Base.connection.execute("SELECT * from \"books\"")
app_file "db/seeds.rb", <<-RUBY
Book.create!(title: "Rework")
RUBY
output = rails("db:seed:replant", allow_failure: true)
assert_match(/ActiveRecord::ProtectedEnvironmentError/, output)
assert_equal(
schema_migrations,
ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"")
)
assert_equal(
internal_metadata,
ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"")
)
assert_equal 1, Book.count
assert_equal(books, ActiveRecord::Base.connection.execute("SELECT * from \"books\""))
assert_not_predicate Book.where(title: "Rework"), :exists?
end
end
end
end
end
end
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册