Skip to content

Commit 5ca7223

Browse files
committed
Add ActiveRecord::Tasks::AbstractTasks for subclassing per adapter behavior
The main motivation was to be able to override `check_current_protected_environment!` in the case of SQLite3 which raises an error on read-only databases in Linux.
1 parent d1fea34 commit 5ca7223

File tree

11 files changed

+137
-150
lines changed

11 files changed

+137
-150
lines changed

activerecord/lib/active_record.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,8 @@ module Tasks
174174
extend ActiveSupport::Autoload
175175

176176
autoload :DatabaseTasks
177-
autoload :MySQLDatabaseTasks, "active_record/tasks/mysql_database_tasks"
177+
autoload :AbstractTasks, "active_record/tasks/abstract_tasks"
178+
autoload :MySQLDatabaseTasks, "active_record/tasks/mysql_database_tasks"
178179
autoload :PostgreSQLDatabaseTasks, "active_record/tasks/postgresql_database_tasks"
179180
autoload :SQLiteDatabaseTasks, "active_record/tasks/sqlite_database_tasks"
180181
end
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# frozen_string_literal: true
2+
3+
module ActiveRecord
4+
module Tasks # :nodoc:
5+
class AbstractTasks # :nodoc:
6+
def self.using_database_configurations?
7+
true
8+
end
9+
10+
def initialize(db_config)
11+
@db_config = db_config
12+
@configuration_hash = db_config.configuration_hash
13+
end
14+
15+
def charset
16+
connection.encoding
17+
end
18+
19+
def collation
20+
connection.collation
21+
end
22+
23+
def check_current_protected_environment!(db_config, migration_class)
24+
with_temporary_pool(db_config, migration_class) do |pool|
25+
migration_context = pool.migration_context
26+
current = migration_context.current_environment
27+
stored = migration_context.last_stored_environment
28+
29+
if migration_context.protected_environment?
30+
raise ActiveRecord::ProtectedEnvironmentError.new(stored)
31+
end
32+
33+
if stored && stored != current
34+
raise ActiveRecord::EnvironmentMismatchError.new(current: current, stored: stored)
35+
end
36+
rescue ActiveRecord::NoDatabaseError
37+
end
38+
end
39+
40+
private
41+
attr_reader :db_config, :configuration_hash
42+
43+
def connection
44+
ActiveRecord::Base.lease_connection
45+
end
46+
47+
def establish_connection(config = db_config)
48+
ActiveRecord::Base.establish_connection(config)
49+
end
50+
51+
def configuration_hash_without_database
52+
configuration_hash.merge(database: nil)
53+
end
54+
55+
def run_cmd(cmd, *args, **opts)
56+
fail run_cmd_error(cmd, args) unless Kernel.system(cmd, *args, opts)
57+
end
58+
59+
def run_cmd_error(cmd, args)
60+
msg = +"failed to execute:\n"
61+
msg << "#{cmd} #{args.join(' ')}\n\n"
62+
msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
63+
msg
64+
end
65+
66+
def with_temporary_pool(db_config, migration_class, clobber: false)
67+
original_db_config = migration_class.connection_db_config
68+
pool = migration_class.connection_handler.establish_connection(db_config, clobber: clobber)
69+
70+
yield pool
71+
ensure
72+
migration_class.connection_handler.establish_connection(original_db_config, clobber: clobber)
73+
end
74+
end
75+
end
76+
end

activerecord/lib/active_record/tasks/database_tasks.rb

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def check_protected_environments!(environment = env)
6666
return if ENV["DISABLE_DATABASE_ENVIRONMENT_CHECK"]
6767

6868
configs_for(env_name: environment).each do |db_config|
69-
check_current_protected_environment!(db_config)
69+
database_adapter_for(db_config).check_current_protected_environment!(db_config, migration_class)
7070
end
7171
end
7272

@@ -640,23 +640,6 @@ def structure_load_flags_for(adapter)
640640
end
641641
end
642642

643-
def check_current_protected_environment!(db_config)
644-
with_temporary_pool(db_config) do |pool|
645-
migration_context = pool.migration_context
646-
current = migration_context.current_environment
647-
stored = migration_context.last_stored_environment
648-
649-
if migration_context.protected_environment?
650-
raise ActiveRecord::ProtectedEnvironmentError.new(stored)
651-
end
652-
653-
if stored && stored != current
654-
raise ActiveRecord::EnvironmentMismatchError.new(current: current, stored: stored)
655-
end
656-
rescue ActiveRecord::NoDatabaseError
657-
end
658-
end
659-
660643
def initialize_database(db_config)
661644
with_temporary_pool(db_config) do
662645
begin

activerecord/lib/active_record/tasks/mysql_database_tasks.rb

Lines changed: 3 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,7 @@
22

33
module ActiveRecord
44
module Tasks # :nodoc:
5-
class MySQLDatabaseTasks # :nodoc:
6-
def self.using_database_configurations?
7-
true
8-
end
9-
10-
def initialize(db_config)
11-
@db_config = db_config
12-
@configuration_hash = db_config.configuration_hash
13-
end
14-
5+
class MySQLDatabaseTasks < AbstractTasks # :nodoc:
156
def create
167
establish_connection(configuration_hash_without_database)
178
connection.create_database(db_config.database, creation_options)
@@ -33,10 +24,6 @@ def charset
3324
connection.charset
3425
end
3526

36-
def collation
37-
connection.collation
38-
end
39-
4027
def structure_dump(filename, extra_flags)
4128
args = prepare_command_options
4229
args.concat(["--result-file", "#{filename}"])
@@ -53,7 +40,7 @@ def structure_dump(filename, extra_flags)
5340
args.concat([db_config.database.to_s])
5441
args.unshift(*extra_flags) if extra_flags
5542

56-
run_cmd("mysqldump", args, "dumping")
43+
run_cmd("mysqldump", *args)
5744
end
5845

5946
def structure_load(filename, extra_flags)
@@ -62,24 +49,10 @@ def structure_load(filename, extra_flags)
6249
args.concat(["--database", db_config.database.to_s])
6350
args.unshift(*extra_flags) if extra_flags
6451

65-
run_cmd("mysql", args, "loading")
52+
run_cmd("mysql", *args)
6653
end
6754

6855
private
69-
attr_reader :db_config, :configuration_hash
70-
71-
def connection
72-
ActiveRecord::Base.lease_connection
73-
end
74-
75-
def establish_connection(config = db_config)
76-
ActiveRecord::Base.establish_connection(config)
77-
end
78-
79-
def configuration_hash_without_database
80-
configuration_hash.merge(database: nil)
81-
end
82-
8356
def creation_options
8457
Hash.new.tap do |options|
8558
options[:charset] = configuration_hash[:encoding] if configuration_hash.include?(:encoding)
@@ -105,16 +78,6 @@ def prepare_command_options
10578

10679
args
10780
end
108-
109-
def run_cmd(cmd, args, action)
110-
fail run_cmd_error(cmd, args, action) unless Kernel.system(cmd, *args)
111-
end
112-
113-
def run_cmd_error(cmd, args, action)
114-
msg = +"failed to execute: `#{cmd}`\n"
115-
msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
116-
msg
117-
end
11881
end
11982
end
12083
end

activerecord/lib/active_record/tasks/postgresql_database_tasks.rb

Lines changed: 5 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,11 @@
44

55
module ActiveRecord
66
module Tasks # :nodoc:
7-
class PostgreSQLDatabaseTasks # :nodoc:
7+
class PostgreSQLDatabaseTasks < AbstractTasks # :nodoc:
88
DEFAULT_ENCODING = ENV["CHARSET"] || "utf8"
99
ON_ERROR_STOP_1 = "ON_ERROR_STOP=1"
1010
SQL_COMMENT_BEGIN = "--"
1111

12-
def self.using_database_configurations?
13-
true
14-
end
15-
16-
def initialize(db_config)
17-
@db_config = db_config
18-
@configuration_hash = db_config.configuration_hash
19-
end
20-
2112
def create(connection_already_established = false)
2213
establish_connection(public_schema_config) unless connection_already_established
2314
connection.create_database(db_config.database, configuration_hash.merge(encoding: encoding))
@@ -29,14 +20,6 @@ def drop
2920
connection.drop_database(db_config.database)
3021
end
3122

32-
def charset
33-
connection.encoding
34-
end
35-
36-
def collation
37-
connection.collation
38-
end
39-
4023
def purge
4124
ActiveRecord::Base.connection_handler.clear_active_connections!(:all)
4225
drop
@@ -72,7 +55,7 @@ def structure_dump(filename, extra_flags)
7255
end
7356

7457
args << db_config.database
75-
run_cmd("pg_dump", args, "dumping")
58+
run_cmd("pg_dump", *args)
7659
remove_sql_header_comments(filename)
7760
File.open(filename, "a") { |f| f << "SET search_path TO #{connection.schema_search_path};\n\n" }
7861
end
@@ -82,20 +65,10 @@ def structure_load(filename, extra_flags)
8265
args.concat(Array(extra_flags)) if extra_flags
8366
args.concat(["--file", filename])
8467
args << db_config.database
85-
run_cmd("psql", args, "loading")
68+
run_cmd("psql", *args)
8669
end
8770

8871
private
89-
attr_reader :db_config, :configuration_hash
90-
91-
def connection
92-
ActiveRecord::Base.lease_connection
93-
end
94-
95-
def establish_connection(config = db_config)
96-
ActiveRecord::Base.establish_connection(config)
97-
end
98-
9972
def encoding
10073
configuration_hash[:encoding] || DEFAULT_ENCODING
10174
end
@@ -117,15 +90,8 @@ def psql_env
11790
end
11891
end
11992

120-
def run_cmd(cmd, args, action)
121-
fail run_cmd_error(cmd, args, action) unless Kernel.system(psql_env, cmd, *args)
122-
end
123-
124-
def run_cmd_error(cmd, args, action)
125-
msg = +"failed to execute:\n"
126-
msg << "#{cmd} #{args.join(' ')}\n\n"
127-
msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
128-
msg
93+
def run_cmd(cmd, *args, **opts)
94+
fail run_cmd_error(cmd, args) unless Kernel.system(psql_env, cmd, *args, **opts)
12995
end
13096

13197
def remove_sql_header_comments(filename)

activerecord/lib/active_record/tasks/sqlite_database_tasks.rb

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,7 @@
22

33
module ActiveRecord
44
module Tasks # :nodoc:
5-
class SQLiteDatabaseTasks # :nodoc:
6-
def self.using_database_configurations?
7-
true
8-
end
9-
5+
class SQLiteDatabaseTasks < AbstractTasks # :nodoc:
106
def initialize(db_config, root = ActiveRecord::Tasks::DatabaseTasks.root)
117
@db_config = db_config
128
@root = root
@@ -37,10 +33,6 @@ def purge
3733
connection.reconnect!
3834
end
3935

40-
def charset
41-
connection.encoding
42-
end
43-
4436
def structure_dump(filename, extra_flags)
4537
args = []
4638
args.concat(Array(extra_flags)) if extra_flags
@@ -54,36 +46,32 @@ def structure_dump(filename, extra_flags)
5446
else
5547
args << ".schema --nosys"
5648
end
57-
run_cmd("sqlite3", args, filename)
49+
50+
run_cmd("sqlite3", *args, out: filename)
5851
end
5952

6053
def structure_load(filename, extra_flags)
6154
flags = extra_flags.join(" ") if extra_flags
6255
`sqlite3 #{flags} #{db_config.database} < "#{filename}"`
6356
end
6457

65-
private
66-
attr_reader :db_config, :root
67-
68-
def connection
69-
ActiveRecord::Base.lease_connection
58+
def check_current_protected_environment!(db_config, migration_class)
59+
super
60+
rescue ActiveRecord::StatementInvalid => e
61+
case e.cause
62+
when SQLite3::ReadOnlyException
63+
else
64+
raise e
7065
end
66+
end
67+
68+
private
69+
attr_reader :root
7170

7271
def establish_connection(config = db_config)
7372
ActiveRecord::Base.establish_connection(config)
7473
connection.connect!
7574
end
76-
77-
def run_cmd(cmd, args, out)
78-
fail run_cmd_error(cmd, args) unless Kernel.system(cmd, *args, out: out)
79-
end
80-
81-
def run_cmd_error(cmd, args)
82-
msg = +"failed to execute:\n"
83-
msg << "#{cmd} #{args.join(' ')}\n\n"
84-
msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
85-
msg
86-
end
8775
end
8876
end
8977
end

0 commit comments

Comments
 (0)