departure-76c9880 6.2.0
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.
- checksums.yaml +7 -0
- data/.codeclimate.yml +8 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.rubocop.yml +60 -0
- data/.travis.yml +30 -0
- data/CHANGELOG.md +192 -0
- data/CODE_OF_CONDUCT.md +50 -0
- data/Dockerfile +32 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +227 -0
- data/RELEASING.md +17 -0
- data/Rakefile +17 -0
- data/bin/console +14 -0
- data/bin/rspec +16 -0
- data/bin/setup +7 -0
- data/config.yml.erb +4 -0
- data/configuration.rb +16 -0
- data/departure.gemspec +35 -0
- data/docker-compose.yml +23 -0
- data/lib/active_record/connection_adapters/for_alter.rb +91 -0
- data/lib/active_record/connection_adapters/percona_adapter.rb +168 -0
- data/lib/departure.rb +43 -0
- data/lib/departure/alter_argument.rb +49 -0
- data/lib/departure/cli_generator.rb +84 -0
- data/lib/departure/command.rb +96 -0
- data/lib/departure/configuration.rb +20 -0
- data/lib/departure/connection_base.rb +9 -0
- data/lib/departure/connection_details.rb +96 -0
- data/lib/departure/dsn.rb +24 -0
- data/lib/departure/errors.rb +39 -0
- data/lib/departure/log_sanitizers/password_sanitizer.rb +22 -0
- data/lib/departure/logger.rb +42 -0
- data/lib/departure/logger_factory.rb +16 -0
- data/lib/departure/migration.rb +96 -0
- data/lib/departure/null_logger.rb +15 -0
- data/lib/departure/option.rb +75 -0
- data/lib/departure/railtie.rb +21 -0
- data/lib/departure/runner.rb +62 -0
- data/lib/departure/user_options.rb +44 -0
- data/lib/departure/version.rb +3 -0
- data/lib/lhm.rb +23 -0
- data/lib/lhm/adapter.rb +107 -0
- data/lib/lhm/column_with_sql.rb +96 -0
- data/lib/lhm/column_with_type.rb +29 -0
- data/test_database.rb +80 -0
- metadata +245 -0
data/RELEASING.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# Releasing Departure
|
2
|
+
|
3
|
+
All releases come from the master branch. All other branches won't be maintained
|
4
|
+
and will receive bug fix releases only.
|
5
|
+
|
6
|
+
In order to give support to a new major Rails version, we'll branch off of
|
7
|
+
master, name it following the Rails repo convention, such as `v4.2`, and
|
8
|
+
we'll keep it open for bug fixes.
|
9
|
+
|
10
|
+
1. Update `lib/departure/version.rb` accordingly
|
11
|
+
2. Review the `CHANGELOG.md` and add a new section following the format
|
12
|
+
`[version] - YYYY-MM-DD`. We conform to the guidelines of
|
13
|
+
http://keepachangelog.com/
|
14
|
+
3. Commit the changes with the message `Prepare release VERSION`
|
15
|
+
4. Execute the release rake task as `bundle exec rake release`. It creates the
|
16
|
+
tag, builds and pushes the gem to Rubygems.
|
17
|
+
5. Announce it! :tada:
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
4
|
+
require './configuration'
|
5
|
+
require './test_database'
|
6
|
+
|
7
|
+
RSpec::Core::RakeTask.new(:spec)
|
8
|
+
|
9
|
+
task default: :spec
|
10
|
+
|
11
|
+
namespace :db do
|
12
|
+
desc 'Create the test database'
|
13
|
+
task :create do
|
14
|
+
config = Configuration.new
|
15
|
+
TestDatabase.new(config).setup_test_database
|
16
|
+
end
|
17
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'percona_migrator'
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require 'irb'
|
14
|
+
IRB.start
|
data/bin/rspec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# This file was generated by Bundler.
|
5
|
+
#
|
6
|
+
# The application 'rspec' is installed as part of a gem, and
|
7
|
+
# this file is here to facilitate running it.
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'pathname'
|
11
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('rspec-core', 'rspec')
|
data/bin/setup
ADDED
data/config.yml.erb
ADDED
data/configuration.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'erb'
|
3
|
+
|
4
|
+
class Configuration
|
5
|
+
CONFIG_PATH = 'config.yml.erb'.freeze
|
6
|
+
|
7
|
+
attr_reader :config
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@config = YAML.load(ERB.new(File.read(CONFIG_PATH)).result).freeze
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](key)
|
14
|
+
config[key]
|
15
|
+
end
|
16
|
+
end
|
data/departure.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
|
6
|
+
require 'departure/version'
|
7
|
+
|
8
|
+
# This environment variable is set on CI to facilitate testing with multiple
|
9
|
+
# versions of Rails.
|
10
|
+
RAILS_DEPENDENCY_VERSION = ENV.fetch('RAILS_VERSION', ['>= 5.2.0', '< 6.1'])
|
11
|
+
|
12
|
+
Gem::Specification.new do |spec|
|
13
|
+
spec.name = 'departure-76c9880'
|
14
|
+
spec.version = Departure::VERSION
|
15
|
+
spec.authors = ['Ilya Zayats', 'Pau Pérez', 'Fran Casas', 'Jorge Morante', 'Enrico Stano', 'Adrian Serafin', 'Kirk Haines', 'Guillermo Iguaran']
|
16
|
+
spec.email = ['ilya.zayats@redbooth.com', 'pau.perez@redbooth.com', 'nflamel@gmail.com', 'jorge.morante@redbooth.com', 'adrian@softmad.pl', 'wyhaines@gmail.com', 'guilleiguaran@gmail.com']
|
17
|
+
|
18
|
+
spec.summary = %q(pt-online-schema-change runner for ActiveRecord migrations)
|
19
|
+
spec.description = %q(Execute your ActiveRecord migrations with Percona's pt-online-schema-change. Formerly known as Percona Migrator.)
|
20
|
+
spec.homepage = 'https://github.com/departurerb/departure'
|
21
|
+
spec.license = 'MIT'
|
22
|
+
|
23
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
24
|
+
spec.require_paths = ['lib']
|
25
|
+
|
26
|
+
spec.add_runtime_dependency 'railties', *Array(RAILS_DEPENDENCY_VERSION)
|
27
|
+
spec.add_runtime_dependency 'activerecord', *Array(RAILS_DEPENDENCY_VERSION)
|
28
|
+
spec.add_runtime_dependency 'mysql2', '>= 0.4.0', '<= 0.5.3'
|
29
|
+
|
30
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
31
|
+
spec.add_development_dependency 'rspec', '~> 3.4', '>= 3.4.0'
|
32
|
+
spec.add_development_dependency 'rspec-its', '~> 1.2'
|
33
|
+
spec.add_development_dependency 'byebug', '~> 8.2', '>= 8.2.1'
|
34
|
+
spec.add_development_dependency 'climate_control', '~> 0.0.3'
|
35
|
+
end
|
data/docker-compose.yml
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
version: '2'
|
2
|
+
services:
|
3
|
+
db:
|
4
|
+
image: mysql/mysql-server
|
5
|
+
ports:
|
6
|
+
- "3306:3306"
|
7
|
+
environment:
|
8
|
+
MYSQL_DATABASE: departure_test
|
9
|
+
MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
|
10
|
+
MYSQL_ROOT_HOST: '%'
|
11
|
+
rails:
|
12
|
+
build: .
|
13
|
+
links:
|
14
|
+
- db
|
15
|
+
depends_on:
|
16
|
+
- db
|
17
|
+
tty: true
|
18
|
+
stdin_open: true
|
19
|
+
environment:
|
20
|
+
PERCONA_DB_USER: root
|
21
|
+
PERCONA_DB_HOST: db
|
22
|
+
PERCONA_DB_PASSWORD:
|
23
|
+
PERCONA_DB_NAME: departure_test
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'active_record/connection_adapters/mysql/schema_statements'
|
2
|
+
|
3
|
+
module ForAlterStatements
|
4
|
+
class << self
|
5
|
+
def included(_)
|
6
|
+
STDERR.puts 'Including for_alter statements'
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def bulk_change_table(table_name, operations) #:nodoc:
|
11
|
+
sqls = operations.flat_map do |command, args|
|
12
|
+
table = args.shift
|
13
|
+
arguments = args
|
14
|
+
|
15
|
+
method = :"#{command}_for_alter"
|
16
|
+
|
17
|
+
raise "Unknown method called : #{method}(#{arguments.inspect})" unless respond_to?(method, true)
|
18
|
+
public_send(method, table, *arguments)
|
19
|
+
end.join(', ')
|
20
|
+
|
21
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
|
22
|
+
end
|
23
|
+
|
24
|
+
def change_column_for_alter(table_name, column_name, type, options = {})
|
25
|
+
column = column_for(table_name, column_name)
|
26
|
+
type ||= column.sql_type
|
27
|
+
|
28
|
+
options = {
|
29
|
+
default: column.default,
|
30
|
+
null: column.null,
|
31
|
+
comment: column.comment
|
32
|
+
}.merge(options)
|
33
|
+
|
34
|
+
td = create_table_definition(table_name)
|
35
|
+
cd = td.new_column_definition(column.name, type, options)
|
36
|
+
schema_creation.accept(ActiveRecord::ConnectionAdapters::ChangeColumnDefinition.new(cd, column.name))
|
37
|
+
end
|
38
|
+
|
39
|
+
def rename_column_for_alter(table_name, column_name, new_column_name)
|
40
|
+
column = column_for(table_name, column_name)
|
41
|
+
options = {
|
42
|
+
default: column.default,
|
43
|
+
null: column.null,
|
44
|
+
auto_increment: column.auto_increment?
|
45
|
+
}
|
46
|
+
|
47
|
+
columns_sql = "SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}"
|
48
|
+
current_type = exec_query(columns_sql, 'SCHEMA').first['Type']
|
49
|
+
td = create_table_definition(table_name)
|
50
|
+
cd = td.new_column_definition(new_column_name, current_type, options)
|
51
|
+
schema_creation.accept(ActiveRecord::ConnectionAdapters::ChangeColumnDefinition.new(cd, column.name))
|
52
|
+
end
|
53
|
+
|
54
|
+
def add_index_for_alter(table_name, column_name, options = {})
|
55
|
+
index_name, index_type, index_columns, _,
|
56
|
+
index_algorithm, index_using = add_index_options(table_name, column_name, options)
|
57
|
+
|
58
|
+
index_algorithm[0, 0] = ', ' if index_algorithm.present?
|
59
|
+
"ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}"
|
60
|
+
end
|
61
|
+
|
62
|
+
def remove_index_for_alter(table_name, options = {})
|
63
|
+
index_name = index_name_for_remove(table_name, options)
|
64
|
+
"DROP INDEX #{quote_column_name(index_name)}"
|
65
|
+
end
|
66
|
+
|
67
|
+
def add_timestamps_for_alter(table_name, options = {})
|
68
|
+
[
|
69
|
+
add_column_for_alter(table_name, :created_at, :datetime, options),
|
70
|
+
add_column_for_alter(table_name, :updated_at, :datetime, options)
|
71
|
+
]
|
72
|
+
end
|
73
|
+
|
74
|
+
def remove_timestamps_for_alter(table_name, _options = {})
|
75
|
+
[remove_column_for_alter(table_name, :updated_at), remove_column_for_alter(table_name, :created_at)]
|
76
|
+
end
|
77
|
+
|
78
|
+
def add_column_for_alter(table_name, column_name, type, options = {})
|
79
|
+
td = create_table_definition(table_name)
|
80
|
+
cd = td.new_column_definition(column_name, type, options)
|
81
|
+
schema_creation.accept(ActiveRecord::ConnectionAdapters::AddColumnDefinition.new(cd))
|
82
|
+
end
|
83
|
+
|
84
|
+
def remove_column_for_alter(_table_name, column_name, _type = nil, _options = {})
|
85
|
+
"DROP COLUMN #{quote_column_name(column_name)}"
|
86
|
+
end
|
87
|
+
|
88
|
+
def remove_columns_for_alter(table_name, *column_names)
|
89
|
+
column_names.map { |column_name| remove_column_for_alter(table_name, column_name) }
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'active_record/connection_adapters/abstract_mysql_adapter'
|
2
|
+
require 'active_record/connection_adapters/statement_pool'
|
3
|
+
require 'active_record/connection_adapters/mysql2_adapter'
|
4
|
+
require 'departure'
|
5
|
+
require 'forwardable'
|
6
|
+
|
7
|
+
module ActiveRecord
|
8
|
+
module ConnectionHandling
|
9
|
+
# Establishes a connection to the database that's used by all Active
|
10
|
+
# Record objects.
|
11
|
+
def percona_connection(config)
|
12
|
+
config[:username] = 'root' if config[:username].nil?
|
13
|
+
mysql2_connection = mysql2_connection(config)
|
14
|
+
|
15
|
+
connection_details = Departure::ConnectionDetails.new(config)
|
16
|
+
verbose = ActiveRecord::Migration.verbose
|
17
|
+
sanitizers = [
|
18
|
+
Departure::LogSanitizers::PasswordSanitizer.new(connection_details)
|
19
|
+
]
|
20
|
+
percona_logger = Departure::LoggerFactory.build(sanitizers: sanitizers, verbose: verbose)
|
21
|
+
cli_generator = Departure::CliGenerator.new(connection_details)
|
22
|
+
|
23
|
+
runner = Departure::Runner.new(
|
24
|
+
percona_logger,
|
25
|
+
cli_generator,
|
26
|
+
mysql2_connection
|
27
|
+
)
|
28
|
+
|
29
|
+
connection_options = { mysql_adapter: mysql2_connection }
|
30
|
+
|
31
|
+
ConnectionAdapters::DepartureAdapter.new(
|
32
|
+
runner,
|
33
|
+
logger,
|
34
|
+
connection_options,
|
35
|
+
config
|
36
|
+
)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
module ConnectionAdapters
|
41
|
+
class DepartureAdapter < AbstractMysqlAdapter
|
42
|
+
class Column < ActiveRecord::ConnectionAdapters::MySQL::Column
|
43
|
+
def adapter
|
44
|
+
DepartureAdapter
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class SchemaCreation < ActiveRecord::ConnectionAdapters::MySQL::SchemaCreation
|
49
|
+
def visit_DropForeignKey(name) # rubocop:disable Naming/MethodName
|
50
|
+
fk_name =
|
51
|
+
if name =~ /^__(.+)/
|
52
|
+
Regexp.last_match(1)
|
53
|
+
else
|
54
|
+
"_#{name}"
|
55
|
+
end
|
56
|
+
|
57
|
+
"DROP FOREIGN KEY #{fk_name}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
extend Forwardable
|
62
|
+
|
63
|
+
unless method_defined?(:change_column_for_alter)
|
64
|
+
include ForAlterStatements
|
65
|
+
end
|
66
|
+
|
67
|
+
ADAPTER_NAME = 'Percona'.freeze
|
68
|
+
|
69
|
+
def_delegators :mysql_adapter, :last_inserted_id, :each_hash, :set_field_encoding
|
70
|
+
|
71
|
+
def initialize(connection, _logger, connection_options, _config)
|
72
|
+
@mysql_adapter = connection_options[:mysql_adapter]
|
73
|
+
super
|
74
|
+
@prepared_statements = false
|
75
|
+
end
|
76
|
+
|
77
|
+
def exec_delete(sql, name, binds)
|
78
|
+
execute(to_sql(sql, binds), name)
|
79
|
+
@connection.affected_rows
|
80
|
+
end
|
81
|
+
alias exec_update exec_delete
|
82
|
+
|
83
|
+
def exec_insert(sql, name, binds, pk = nil, sequence_name = nil) # rubocop:disable Lint/UnusedMethodArgument, Metrics/LineLength
|
84
|
+
execute(to_sql(sql, binds), name)
|
85
|
+
end
|
86
|
+
|
87
|
+
def exec_query(sql, name = 'SQL', _binds = [])
|
88
|
+
result = execute(sql, name)
|
89
|
+
ActiveRecord::Result.new(result.fields, result.to_a)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Executes a SELECT query and returns an array of rows. Each row is an
|
93
|
+
# array of field values.
|
94
|
+
|
95
|
+
def select_rows(arel, name = nil, binds = [])
|
96
|
+
select_all(arel, name, binds).rows
|
97
|
+
end
|
98
|
+
|
99
|
+
# Executes a SELECT query and returns an array of record hashes with the
|
100
|
+
# column names as keys and column values as values.
|
101
|
+
def select(sql, name = nil, binds = [])
|
102
|
+
exec_query(sql, name, binds)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns true, as this adapter supports migrations
|
106
|
+
def supports_migrations?
|
107
|
+
true
|
108
|
+
end
|
109
|
+
|
110
|
+
# rubocop:disable Metrics/ParameterLists
|
111
|
+
def new_column(field, default, type_metadata, null, table_name, default_function, collation, comment)
|
112
|
+
Column.new(field, default, type_metadata, null, table_name, default_function, collation, comment)
|
113
|
+
end
|
114
|
+
# rubocop:enable Metrics/ParameterLists
|
115
|
+
|
116
|
+
# Adds a new index to the table
|
117
|
+
#
|
118
|
+
# @param table_name [String, Symbol]
|
119
|
+
# @param column_name [String, Symbol]
|
120
|
+
# @param options [Hash] optional
|
121
|
+
def add_index(table_name, column_name, options = {})
|
122
|
+
index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options)
|
123
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} ADD #{index_type} INDEX #{quote_column_name(index_name)} (#{index_columns})#{index_options}" # rubocop:disable Metrics/LineLength
|
124
|
+
end
|
125
|
+
|
126
|
+
# Remove the given index from the table.
|
127
|
+
#
|
128
|
+
# @param table_name [String, Symbol]
|
129
|
+
# @param options [Hash] optional
|
130
|
+
def remove_index(table_name, options = {})
|
131
|
+
index_name = index_name_for_remove(table_name, options)
|
132
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} DROP INDEX #{quote_column_name(index_name)}"
|
133
|
+
end
|
134
|
+
|
135
|
+
def schema_creation
|
136
|
+
SchemaCreation.new(self)
|
137
|
+
end
|
138
|
+
|
139
|
+
def change_table(table_name, _options = {})
|
140
|
+
recorder = ActiveRecord::Migration::CommandRecorder.new(self)
|
141
|
+
yield update_table_definition(table_name, recorder)
|
142
|
+
bulk_change_table(table_name, recorder.commands)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Returns the MySQL error number from the exception. The
|
146
|
+
# AbstractMysqlAdapter requires it to be implemented
|
147
|
+
def error_number(_exception); end
|
148
|
+
|
149
|
+
def full_version
|
150
|
+
if ActiveRecord::VERSION::MAJOR < 6
|
151
|
+
get_full_version
|
152
|
+
else
|
153
|
+
schema_cache.database_version.full_version_string
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# This is a method defined in Rails 6.0, and we have no control over the
|
158
|
+
# naming of this method.
|
159
|
+
def get_full_version # rubocop:disable Naming/AccessorMethodName
|
160
|
+
mysql_adapter.raw_connection.server_info[:version]
|
161
|
+
end
|
162
|
+
|
163
|
+
private
|
164
|
+
|
165
|
+
attr_reader :mysql_adapter
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
data/lib/departure.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'active_support/all'
|
3
|
+
|
4
|
+
require 'active_record/connection_adapters/for_alter'
|
5
|
+
|
6
|
+
require 'departure/version'
|
7
|
+
require 'departure/log_sanitizers/password_sanitizer'
|
8
|
+
require 'departure/runner'
|
9
|
+
require 'departure/cli_generator'
|
10
|
+
require 'departure/logger'
|
11
|
+
require 'departure/null_logger'
|
12
|
+
require 'departure/logger_factory'
|
13
|
+
require 'departure/configuration'
|
14
|
+
require 'departure/errors'
|
15
|
+
require 'departure/command'
|
16
|
+
require 'departure/connection_base'
|
17
|
+
require 'departure/migration'
|
18
|
+
|
19
|
+
require 'departure/railtie' if defined?(Rails)
|
20
|
+
|
21
|
+
# We need the OS not to buffer the IO to see pt-osc's output while migrating
|
22
|
+
$stdout.sync = true
|
23
|
+
|
24
|
+
ActiveSupport.on_load(:active_record) do
|
25
|
+
ActiveRecord::Migration.class_eval do
|
26
|
+
include Departure::Migration
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module Departure
|
31
|
+
class << self
|
32
|
+
attr_accessor :configuration
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.configure
|
36
|
+
self.configuration ||= Configuration.new
|
37
|
+
yield(configuration)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.load
|
41
|
+
# No-op left for compatibility
|
42
|
+
end
|
43
|
+
end
|