diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 472f793..8364ff8 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -9,14 +9,13 @@ jobs: strategy: matrix: - ruby-version: ['2.4.0', '2.5.1', '2.6.6', '2.7.2'] + ruby-version: ['2.6.10', '2.7.8', '3.0.6', '3.1.4'] steps: # Pin to this commit: v2 - uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f - name: Set up Ruby - # Lock to this commit, v1.82.0 - uses: ruby/setup-ruby@5e4f0a10bfc39c97cd5358121291e27e5d97e09b + uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby-version }} bundler-cache: true # runs 'bundle install' and caches installed gems automatically diff --git a/.ruby-version b/.ruby-version index 37c2961..6a81b4c 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.7.2 +2.7.8 diff --git a/knockoff.gemspec b/knockoff.gemspec index 539ed4f..580dd3f 100644 --- a/knockoff.gemspec +++ b/knockoff.gemspec @@ -18,8 +18,8 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] - spec.add_runtime_dependency 'activerecord', '>= 4.0.0', '< 6.1' - spec.add_runtime_dependency 'activesupport', '>= 4.0.0', '< 6.1' + spec.add_runtime_dependency 'activerecord', '>= 6', '< 7' + spec.add_runtime_dependency 'activesupport', '>= 6', '< 7' spec.add_development_dependency "bundler" spec.add_development_dependency "rake", "~> 10.0" diff --git a/lib/knockoff/config.rb b/lib/knockoff/config.rb index 5d29572..74adea9 100644 --- a/lib/knockoff/config.rb +++ b/lib/knockoff/config.rb @@ -34,9 +34,12 @@ def replica_env_keys end def update_replica_configs(new_configs) - ActiveRecord::Base.configurations['knockoff_replicas'].merge(new_configs) if ActiveRecord::Base.configurations['knockoff_replicas'].present? + if ActiveRecord::Base.configurations.configs_for(env_name: 'knockoff_replicas').present? + updated_config = new_configs.deep_dup.merge!(ActiveRecord::Base.configurations.configs_for(env_name: 'knockoff_replicas').first.configuration_hash) + end + @replicas_configurations.each do |key, _config| - update_replica_config(key, new_configs) + update_replica_config(key, updated_config) end end @@ -49,9 +52,10 @@ def properly_configured? private - def update_replica_config(key, new_configs) - @replicas_configurations[key].merge!(new_configs) - ActiveRecord::Base.configurations[key].merge!(new_configs) + def update_replica_config(key, updated_config) + merged_config = @replicas_configurations[key].configuration_hash.deep_dup.merge!(updated_config) + @replicas_configurations[key] = ActiveRecord::DatabaseConfigurations::HashConfig.new(key, key, merged_config) + ActiveRecord::Base.configurations.configurations << @replicas_configurations[key] end def set_replica_configs @@ -63,56 +67,11 @@ def parse_knockoff_replica_envs_to_configs # and don't add the uri to the final list if it can't be parsed replica_env_keys.map.with_index(0) do |env_key, index| begin - uri = URI.parse(ENV[env_key]) # Configure parameters such as prepared_statements, pool, reaping_frequency for all replicas. - replica_config = ActiveRecord::Base.configurations['knockoff_replicas'] || {} - - adapter = - if uri.scheme == "postgres" - 'postgresql' - else - uri.scheme - end - - # Base config from the ENV uri. Sqlite is a special case - # and all others follow 'normal' config - uri_config = - if uri.scheme == 'sqlite3' - { - 'adapter' => adapter, - 'database' => uri.to_s.split(':')[1] - } - else - { - 'adapter' => adapter, - 'database' => (uri.path || "").split("/")[1], - 'username' => uri.user, - 'password' => uri.password, - 'host' => uri.host, - 'port' => uri.port - } - end - - # Store the hash in configuration and use it when we establish the connection later. - # TODO: In ActiveRecord >= 6, this is a deprecated way to set a configuration. However - # there appears to be an issue when calling `ActiveRecord::Base.configurations.to_h` in - # version 6.0.4.8 where - # multi-database setup is being ignored / dropped. For example if a database.yml setup - # has something like.. - # - # development: - # primary: - # ... - # other: - # ... - # - # then the 'other' database configuration is being dropped. - key = "knockoff_replica_#{index}" - config = replica_config.merge(uri_config) - ActiveRecord::Base.configurations[key] = config - - @replicas_configurations[key] = config + to_copy = ActiveRecord::Base.configurations.configs_for(env_name: 'knockoff_replicas')&.first&.configuration_hash || {} + register_replica_copy(index, env_key, to_copy) + rescue URI::InvalidURIError Rails.logger.info "LOG NOTIFIER: Invalid URL specified in follower_env_keys. Not including URI, which may result in no followers used." # URI is purposely not printed to logs # Return a 'nil' which will be removed from @@ -122,5 +81,35 @@ def parse_knockoff_replica_envs_to_configs end end.compact end + + def register_replica_copy(index, env_key, configuration_hash) + key = "knockoff_replica_#{index}" + new_config = create_replica_copy(env_key, key, configuration_hash.deep_dup) + ActiveRecord::Base.configurations.configurations << new_config + @replicas_configurations[key] = new_config + end + + def create_replica_copy(env_key, key, replica_config_hash) + uri = URI.parse(ENV[env_key]) + + replica_config_hash[:adapter] = + if uri.scheme == "postgres" + 'postgresql' + else + uri.scheme + end + + if uri.scheme == 'sqlite3' + replica_config_hash[:database] = uri.to_s.split(':')[1] + else + replica_config_hash[:database] = (uri.path || "").split("/")[1] + replica_config_hash[:username] = uri.user + replica_config_hash[:password] = uri.password + replica_config_hash[:host] = uri.host + replica_config_hash[:port] = uri.port + end + + ActiveRecord::DatabaseConfigurations::HashConfig.new(key, key, replica_config_hash) + end end end diff --git a/lib/knockoff/replica_connection_pool.rb b/lib/knockoff/replica_connection_pool.rb index 2274720..b9381c0 100644 --- a/lib/knockoff/replica_connection_pool.rb +++ b/lib/knockoff/replica_connection_pool.rb @@ -52,7 +52,7 @@ def connection_class(config_key) class #{class_name} < ActiveRecord::Base self.abstract_class = true establish_connection :#{config_key} - def self.connection_config + def self.connection_db_config configurations[#{config_key.to_s.inspect}] end end diff --git a/lib/knockoff/version.rb b/lib/knockoff/version.rb index 3e59ef7..dfd1bc3 100644 --- a/lib/knockoff/version.rb +++ b/lib/knockoff/version.rb @@ -1,3 +1,3 @@ module Knockoff - VERSION = '1.3.0'.freeze + VERSION = '1.4.0'.freeze end diff --git a/spec/knockoff_spec.rb b/spec/knockoff_spec.rb index d82bfb3..502d299 100644 --- a/spec/knockoff_spec.rb +++ b/spec/knockoff_spec.rb @@ -140,9 +140,9 @@ class ActiveRecord::Relation Knockoff.clear_all_active_connections! end - it 'defines self.connection_config' do - expect(Knockoff::KnockoffReplica0.connection_config).not_to be_nil - expect(Knockoff::KnockoffReplica0.connection_config['adapter']).to eq 'sqlite3' + it 'defines self.connection_db_config' do + expect(Knockoff::KnockoffReplica0.connection_db_config).not_to be_nil + expect(Knockoff::KnockoffReplica0.connection_db_config[:adapter]).to eq 'sqlite3' end context "bad configurations" do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 454f2ba..8abea9a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -5,9 +5,9 @@ require 'knockoff' -ActiveRecord::Base.configurations = { - 'test' => { 'adapter' => 'sqlite3', 'database' => 'tmp/test_db' } -} +ActiveRecord::Base.configurations = ActiveRecord::DatabaseConfigurations.new({ + :test => { :adapter => 'sqlite3', :database => 'tmp/test_db' } +}) # Setup the ENV's for replicas ENV['KNOCKOFF_REPLICA1'] = 'sqlite3:tmp/test_replica_db' @@ -29,4 +29,4 @@ class User < ActiveRecord::Base User.create # Reconnect to primary -ActiveRecord::Base.establish_connection(:test) \ No newline at end of file +ActiveRecord::Base.establish_connection(:test)