janus-ar 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3fb7b603961432244454f93bc66d35e22466401a694c80b998ed16abbebdd5f9
4
+ data.tar.gz: eb21fbaedd97ff12c7a51f07cc54b971c8af4b2a7bbf8ed235870b810b13dddd
5
+ SHA512:
6
+ metadata.gz: 2c5df8a00ea7f86ef871db80b98b3ecc3ffe2cc9d74014fb71ac6b150cee316f920736deaa3ec97038f8ead55fc97a9487488d95367308c0b37ad746a9a913a6
7
+ data.tar.gz: fadc4ffb8335940a316bb4d66be416578e35976c50b2d9b66a8873d8c92f1c347c7ceb7771eeaf215f482fe0aded1b26480b0de7a710b02d37fa06af9da565eb
@@ -0,0 +1,6 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "bundler"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
@@ -0,0 +1,51 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ ruby:
16
+ - '3.2'
17
+ name: Ruby ${{ matrix.ruby }}
18
+ services:
19
+ mysql:
20
+ image: mysql:5.7
21
+ env:
22
+ MYSQL_ALLOW_EMPTY_PASSWORD: yes
23
+ MYSQL_DATABASE: janus_test
24
+ ports:
25
+ - 3306:3306
26
+ options: >-
27
+ --health-cmd "mysqladmin ping"
28
+ --health-interval 10s
29
+ --health-timeout 5s
30
+ --health-retries 5
31
+ steps:
32
+ - uses: actions/checkout@v2
33
+ - uses: ruby/setup-ruby@v1
34
+ with:
35
+ ruby-version: ${{ matrix.ruby }}
36
+ bundler-cache: true
37
+ - run: |
38
+ bundle exec rspec
39
+ env:
40
+ MYSQL_HOST: 127.0.0.1
41
+ RAILS_ENV: test
42
+ RuboCop:
43
+ runs-on: ubuntu-latest
44
+ steps:
45
+ - uses: actions/checkout@v2
46
+ - uses: ruby/setup-ruby@v1
47
+ with:
48
+ ruby-version: '3.2'
49
+ bundler-cache: true
50
+ - run: |
51
+ bundle exec rubocop --parallel --color
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --force-color
2
+ --format documentation
3
+ --require ./spec/spec_helper.rb
data/.rubocop.yml ADDED
@@ -0,0 +1,12 @@
1
+ require:
2
+ - rubocop-rails
3
+
4
+ Style/Documentation:
5
+ Enabled: false
6
+
7
+ Rails/Delegate:
8
+ Enabled: false
9
+
10
+ Metrics/BlockLength:
11
+ Exclude:
12
+ - spec/**/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'http://rubygems.org'
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,101 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ janus-ar (0.1.0)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ activemodel (7.1.3.2)
10
+ activesupport (= 7.1.3.2)
11
+ activerecord (7.1.3.2)
12
+ activemodel (= 7.1.3.2)
13
+ activesupport (= 7.1.3.2)
14
+ timeout (>= 0.4.0)
15
+ activesupport (7.1.3.2)
16
+ base64
17
+ bigdecimal
18
+ concurrent-ruby (~> 1.0, >= 1.0.2)
19
+ connection_pool (>= 2.2.5)
20
+ drb
21
+ i18n (>= 1.6, < 2)
22
+ minitest (>= 5.1)
23
+ mutex_m
24
+ tzinfo (~> 2.0)
25
+ ast (2.4.2)
26
+ base64 (0.2.0)
27
+ bigdecimal (3.1.7)
28
+ concurrent-ruby (1.2.3)
29
+ connection_pool (2.4.1)
30
+ diff-lcs (1.5.1)
31
+ drb (2.2.1)
32
+ i18n (1.14.4)
33
+ concurrent-ruby (~> 1.0)
34
+ json (2.7.1)
35
+ language_server-protocol (3.17.0.3)
36
+ minitest (5.22.3)
37
+ mutex_m (0.2.0)
38
+ mysql2 (0.5.6)
39
+ parallel (1.24.0)
40
+ parser (3.3.0.5)
41
+ ast (~> 2.4.1)
42
+ racc
43
+ racc (1.7.3)
44
+ rack (3.0.10)
45
+ rainbow (3.1.1)
46
+ rake (13.1.0)
47
+ regexp_parser (2.9.0)
48
+ rexml (3.2.6)
49
+ rspec (3.13.0)
50
+ rspec-core (~> 3.13.0)
51
+ rspec-expectations (~> 3.13.0)
52
+ rspec-mocks (~> 3.13.0)
53
+ rspec-core (3.13.0)
54
+ rspec-support (~> 3.13.0)
55
+ rspec-expectations (3.13.0)
56
+ diff-lcs (>= 1.2.0, < 2.0)
57
+ rspec-support (~> 3.13.0)
58
+ rspec-mocks (3.13.0)
59
+ diff-lcs (>= 1.2.0, < 2.0)
60
+ rspec-support (~> 3.13.0)
61
+ rspec-support (3.13.1)
62
+ rubocop (1.62.1)
63
+ json (~> 2.3)
64
+ language_server-protocol (>= 3.17.0)
65
+ parallel (~> 1.10)
66
+ parser (>= 3.3.0.2)
67
+ rainbow (>= 2.2.2, < 4.0)
68
+ regexp_parser (>= 1.8, < 3.0)
69
+ rexml (>= 3.2.5, < 4.0)
70
+ rubocop-ast (>= 1.31.1, < 2.0)
71
+ ruby-progressbar (~> 1.7)
72
+ unicode-display_width (>= 2.4.0, < 3.0)
73
+ rubocop-ast (1.31.2)
74
+ parser (>= 3.3.0.4)
75
+ rubocop-rails (2.24.1)
76
+ activesupport (>= 4.2.0)
77
+ rack (>= 1.1)
78
+ rubocop (>= 1.33.0, < 2.0)
79
+ rubocop-ast (>= 1.31.1, < 2.0)
80
+ ruby-progressbar (1.13.0)
81
+ timeout (0.4.1)
82
+ tzinfo (2.0.6)
83
+ concurrent-ruby (~> 1.0)
84
+ unicode-display_width (2.5.0)
85
+
86
+ PLATFORMS
87
+ arm64-darwin-23
88
+ x86_64-linux
89
+
90
+ DEPENDENCIES
91
+ activerecord (>= 7.1.0)
92
+ activesupport (>= 7.1.0)
93
+ janus-ar!
94
+ mysql2
95
+ rake
96
+ rspec (~> 3)
97
+ rubocop (~> 1.62.0)
98
+ rubocop-rails (~> 2.24.0)
99
+
100
+ BUNDLED WITH
101
+ 2.4.22
data/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # Janus ActiveRecord
2
+
3
+ ![Build Status](https://github.com/OLIOEX/janus-ar/actions/workflows/ci.yml/badge.svg)
4
+
5
+ > In ancient Roman religion and myth, Janus (/ˈdʒeɪnəs/ JAY-nəs; Latin: Ianvs [ˈi̯aːnʊs]) is the god of beginnings, gates, transitions, time, duality, doorways,[2] passages, frames, and endings. [(wikipedia)](https://en.wikipedia.org/wiki/Janus)
6
+
7
+ Janus ActiveRecord is generic primary/replica proxy for ActiveRecord 7.1+ and MySQL. It handles the switching of connections between primary and replica database servers. It comes with an ActiveRecord database adapter implementation.
8
+
9
+ Janus is heavily inspired by [Makara](https://github.com/instacart/makara) from TaskRabbit and then Instacart. Unfortunately this project is unmaintained and broke for us with Rails 7.1. This is an attempt to start afresh on the project. It is definitely not as fully featured as Makara at this stage.
10
+
11
+ ## Installation
12
+
13
+ Use the current version of the gem from [rubygems](https://rubygems.org/gems/janus-ar) in your `Gemfile`.
14
+
15
+ ```ruby
16
+ gem 'janus-ar'
17
+ ```
18
+
19
+ This project assumes that your read/write endpoints are handled by a separate system (e.g. DNS).
20
+
21
+ ## Usage
22
+
23
+ After a write request during a thread the adapter will continue using the `primary` server, unless the context is specifically released.
24
+
25
+ ### Configuration
26
+
27
+ Update your **database.yml** as follows:
28
+
29
+ ```yml
30
+ development:
31
+ adapter: janus_mysql2
32
+ janus:
33
+ primary:
34
+ <<: *default
35
+ database: database_name
36
+ host: primary-host.local
37
+ replica:
38
+ <<: *default
39
+ password: ithappenstobedifferent
40
+ host: replica-host.local
41
+ ```
42
+
43
+ ### Forcing connections
44
+
45
+ A context is local to the curent thread of execution. This will allow you to stick to the primary safely in a single thread
46
+ in systems such as sidekiq, for instance.
47
+
48
+ #### Releasing stuck connections (clearing context)
49
+
50
+ If you need to clear the current context, releasing any stuck connections, all you have to do is:
51
+
52
+ ```ruby
53
+ Janus::Context.release_all
54
+ ```
55
+
56
+ #### Forcing connection to primary server
57
+
58
+ ```ruby
59
+ Janus::Context.stick_to_primary
60
+ ```
61
+
62
+ ### Logging
63
+
64
+ You can set a logger instance to ::Janus::Logging::Logger.logger and Janus.
65
+
66
+ ```ruby
67
+ Janus::Logging::Logger.logger = ::Logger.new(STDOUT)
68
+ ```
69
+
70
+ ### What queries goes where?
71
+
72
+ In general: Any `SELECT` statements will execute against your replica(s), anything else will go to the primary.
73
+
74
+ There are some edge cases:
75
+ * `SET` operations will be sent to all connections
76
+ * Execution of specific methods such as `connect!`, `disconnect!`, `reconnect!`, and `clear_cache!` are invoked on all underlying connections
77
+ * Calls inside a transaction will always be sent to the primary (otherwise changes from within the transaction could not be read back on most transaction isolation levels)
78
+ * Locking reads (e.g. `SELECT ... FOR UPDATE`) will always be sent to the primary
data/janus-ar.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path('lib/janus/version.rb', __dir__)
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.authors = ['Lloyd Watkin']
7
+ gem.email = ['lloyd@olioex.com']
8
+ gem.description = 'Read/Write proxy for ActiveRecord using primary/replca databases'
9
+ gem.summary = 'Read/Write proxy for ActiveRecord using primary/replca databases'
10
+ gem.homepage = 'https://github.com/olioex/janus-ar'
11
+ gem.licenses = ['MIT']
12
+ gem.metadata = {
13
+ 'source_code_uri' => 'https://github.com/olioex/janus-ar'
14
+ }
15
+
16
+ gem.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR)
17
+ gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
18
+ gem.name = 'janus-ar'
19
+ gem.require_paths = ['lib']
20
+ gem.version = Janus::VERSION
21
+
22
+ gem.required_ruby_version = '>= 3.2.0'
23
+
24
+ gem.add_development_dependency 'activerecord', '>= 7.1.0'
25
+ gem.add_development_dependency 'activesupport', '>= 7.1.0'
26
+ gem.add_development_dependency 'mysql2'
27
+ gem.add_development_dependency 'rake'
28
+ gem.add_development_dependency 'rspec', '~> 3'
29
+ gem.add_development_dependency 'rubocop', '~> 1.62.0'
30
+ gem.add_development_dependency 'rubocop-rails', '~> 2.24.0'
31
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_record/connection_adapters/abstract_adapter'
4
+ require 'active_record/connection_adapters/mysql2_adapter'
5
+ require_relative '../../janus'
6
+
7
+ module ActiveRecord
8
+ module ConnectionHandling
9
+ def janus_mysql2_connection(config)
10
+ ActiveRecord::ConnectionAdapters::JanusMysql2Adapter.new(config)
11
+ end
12
+ end
13
+ end
14
+
15
+ module ActiveRecord
16
+ module ConnectionAdapters
17
+ class JanusMysql2Adapter < ActiveRecord::ConnectionAdapters::Mysql2Adapter
18
+ SQL_PRIMARY_MATCHERS = [
19
+ /\A\s*select.+for update\Z/i, /select.+lock in share mode\Z/i,
20
+ /\A\s*select.+(nextval|currval|lastval|get_lock|release_lock|pg_advisory_lock|pg_advisory_unlock)\(/i,
21
+ /\A\s*show/i
22
+ ].freeze
23
+ SQL_REPLICA_MATCHERS = [/\A\s*(select|with.+\)\s*select)\s/i].freeze
24
+ SQL_ALL_MATCHERS = [/\A\s*set\s/i].freeze
25
+ SQL_SKIP_ALL_MATCHERS = [/\A\s*set\s+local\s/i].freeze
26
+
27
+ def initialize(*args)
28
+ @replica_config = args[0][:janus]['replica']
29
+ args[0] = args[0][:janus]['primary']
30
+
31
+ super(*args)
32
+ @connection_parameters ||= args[0]
33
+ update_config
34
+ end
35
+
36
+ def execute(sql)
37
+ if should_send_to_all?(sql)
38
+ send_to_replica(sql, connection: :all, method: :execute)
39
+ return super(sql)
40
+ end
41
+ return send_to_replica(sql, connection: :replica, method: :execute) if can_go_to_replica?(sql)
42
+
43
+ Janus::Context.stick_to_primary if write_query?(sql)
44
+ Janus::Context.used_connection(:primary)
45
+
46
+ super(sql)
47
+ end
48
+
49
+ def execute_and_free(sql, name = nil, async: false) # :nodoc:#
50
+ if should_send_to_all?(sql)
51
+ send_to_replica(sql, name, connection: :all)
52
+ return super(sql, name, async:)
53
+ end
54
+ return send_to_replica(sql, connection: :replica) if can_go_to_replica?(sql)
55
+
56
+ Janus::Context.stick_to_primary if write_query?(sql)
57
+ Janus::Context.used_connection(:primary)
58
+
59
+ super(sql, name, async:)
60
+ end
61
+
62
+ def connect!(...)
63
+ replica_connection.connect!(...)
64
+ super
65
+ end
66
+
67
+ def reconnect!(...)
68
+ replica_connection.reconnect!(...)
69
+ super
70
+ end
71
+
72
+ def disconnect!(...)
73
+ replica_connection.disconnect!(...)
74
+ super
75
+ end
76
+
77
+ def clear_cache!(...)
78
+ replica_connection.clear_cache!(...)
79
+ super
80
+ end
81
+
82
+ private
83
+
84
+ def should_send_to_all?(sql)
85
+ SQL_ALL_MATCHERS.any? { |matcher| sql =~ matcher } && SQL_SKIP_ALL_MATCHERS.none? { |matcher| sql =~ matcher }
86
+ end
87
+
88
+ def can_go_to_replica?(sql)
89
+ return false if Janus::Context.use_primary? ||
90
+ open_transactions.positive? ||
91
+ SQL_PRIMARY_MATCHERS.any? { |matcher| sql =~ matcher }
92
+
93
+ true
94
+ end
95
+
96
+ def send_to_replica(sql, connection: nil, method: :exec_query)
97
+ Janus::Context.used_connection(connection) if connection
98
+ if method == :execute
99
+ replica_connection.execute(sql)
100
+ else
101
+ replica_connection.exec_query(sql)
102
+ end
103
+ end
104
+
105
+ def write_query?(sql)
106
+ %w[INSERT UPDATE DELETE LOCK].include?(sql.split(' ').first)
107
+ end
108
+
109
+ def replica_connection
110
+ @replica_connection ||= ActiveRecord::ConnectionAdapters::Mysql2Adapter.new(@replica_config)
111
+ end
112
+
113
+ def update_config
114
+ @config[:flags] ||= 0
115
+
116
+ if @config[:flags].is_a? Array
117
+ @config[:flags].push 'FOUND_ROWS'
118
+ else
119
+ @config[:flags] |= ::Mysql2::Client::FOUND_ROWS
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Janus
4
+ class Context
5
+ THREAD_KEY = :janus_ar_context
6
+
7
+ # Stores the staged data with an expiration time based on the current time,
8
+ # and clears any expired entries. Returns true if any changes were made to
9
+ # the current store
10
+ def initialize(primary: false, expiry: nil)
11
+ @primary = primary
12
+ @expiry = expiry
13
+ @last_used_connection = :primary
14
+ end
15
+
16
+ def stick_to_primary
17
+ @primary = true
18
+ end
19
+
20
+ def potential_write
21
+ stick_to_primary
22
+ end
23
+
24
+ def release_all
25
+ @primary = false
26
+ @expiry = nil
27
+ @last_used_connection = nil
28
+ end
29
+
30
+ def use_primary?
31
+ @primary
32
+ end
33
+
34
+ def used_connection(connection)
35
+ @last_used_connection = connection
36
+ end
37
+
38
+ attr_reader :last_used_connection
39
+
40
+ class << self
41
+ def stick_to_primary
42
+ current.stick_to_primary
43
+ end
44
+
45
+ def release_all
46
+ current.release_all
47
+ end
48
+
49
+ def used_connection(connection)
50
+ current.used_connection(connection)
51
+ end
52
+
53
+ def use_primary?
54
+ current.use_primary?
55
+ end
56
+
57
+ def last_used_connection
58
+ current.last_used_connection
59
+ end
60
+
61
+ protected
62
+
63
+ def current
64
+ fetch(THREAD_KEY) { new }
65
+ end
66
+
67
+ def fetch(key)
68
+ get(key) || set(key, yield)
69
+ end
70
+
71
+ def get(key)
72
+ Thread.current.thread_variable_get(key)
73
+ end
74
+
75
+ def set(key, value)
76
+ Thread.current.thread_variable_set(key, value)
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Janus
4
+ module Logging
5
+ class Logger
6
+ class << self
7
+ def log(message, format = :info)
8
+ logger&.send(format, "[Janus] #{message}")
9
+ end
10
+
11
+ attr_accessor :logger
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Janus
4
+ module Logging
5
+ module Subscriber
6
+ IGNORE_PAYLOAD_NAMES = %w[SCHEMA EXPLAIN].freeze
7
+
8
+ def sql(event)
9
+ name = event.payload[:name]
10
+ unless IGNORE_PAYLOAD_NAMES.include?(name)
11
+ name = [current_wrapper_name(event), name].compact.join(' ')
12
+ event.payload[:name] = name
13
+ end
14
+ super(event)
15
+ end
16
+
17
+ protected
18
+
19
+ def current_wrapper_name(_event)
20
+ connection = Janus::Context.last_used_connection
21
+ return nil unless connection
22
+
23
+ "[#{connection}]"
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Janus
4
+ unless defined?(::Janus::VERSION)
5
+ module VERSION
6
+ MAJOR = 0
7
+ MINOR = 1
8
+ PATCH = 0
9
+ PRE = nil
10
+
11
+ def self.to_s
12
+ [MAJOR, MINOR, PATCH, PRE].compact.join('.')
13
+ end
14
+ end
15
+ end
16
+ ::Janus::VERSION
17
+ end
data/lib/janus.rb ADDED
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+
5
+ module Janus
6
+ autoload :Context, 'janus/context'
7
+ autoload :VERSION, 'janus/version'
8
+
9
+ module Logging
10
+ autoload :Subscriber, 'janus/logging/subscriber'
11
+ autoload :Logger, 'janus/logging/logger'
12
+ end
13
+ end
14
+
15
+ ActiveSupport.on_load(:active_record) do
16
+ ActiveRecord::LogSubscriber.log_subscribers.each do |subscriber|
17
+ subscriber.extend Janus::Logging::Subscriber
18
+ end
19
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'janus/context'
4
+
5
+ RSpec.describe Janus::Context do
6
+ describe '#initialize' do
7
+ it 'sets the primary flag and expiry' do
8
+ context = described_class.new(primary: true, expiry: 60)
9
+ expect(context.use_primary?).to be true
10
+ expect(context.last_used_connection).to eq(:primary)
11
+ end
12
+ end
13
+
14
+ describe '#stick_to_primary' do
15
+ it 'sets the primary flag to true' do
16
+ context = described_class.new
17
+ context.stick_to_primary
18
+ expect(context.use_primary?).to be true
19
+ end
20
+ end
21
+
22
+ describe '#potential_write' do
23
+ it 'calls stick_to_primary' do
24
+ context = described_class.new
25
+ expect(context).to receive(:stick_to_primary)
26
+ context.potential_write
27
+ end
28
+ end
29
+
30
+ describe '#release_all' do
31
+ it 'resets the primary flag and expiry' do
32
+ context = described_class.new(primary: true, expiry: 60)
33
+ context.release_all
34
+ expect(context.use_primary?).to be false
35
+ expect(context.last_used_connection).to be_nil
36
+ end
37
+ end
38
+
39
+ describe '#used_connection' do
40
+ it 'sets the last used connection' do
41
+ context = described_class.new
42
+ context.used_connection(:secondary)
43
+ expect(context.last_used_connection).to eq(:secondary)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Janus::Logging::Logger do
4
+ describe '.log' do
5
+ let(:logger) { double('logger') }
6
+
7
+ before do
8
+ described_class.logger = logger
9
+ end
10
+
11
+ it 'logs the message with the specified format' do
12
+ expect(logger).to receive(:send).with(:info, '[Janus] Test message')
13
+ described_class.log('Test message', :info)
14
+ end
15
+
16
+ it 'does not log the message if logger is not set' do
17
+ described_class.logger = nil
18
+ expect(logger).not_to receive(:send)
19
+ described_class.log('Test message', :info)
20
+ end
21
+ end
22
+
23
+ describe '.logger=' do
24
+ let(:logger) { double('logger') }
25
+
26
+ it 'sets the logger' do
27
+ described_class.logger = logger
28
+ expect(described_class.logger).to eq(logger)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require './lib/janus'
metadata ADDED
@@ -0,0 +1,159 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: janus-ar
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Lloyd Watkin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-04-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 7.1.0
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 7.1.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 7.1.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 7.1.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: mysql2
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 1.62.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 1.62.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop-rails
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 2.24.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 2.24.0
111
+ description: Read/Write proxy for ActiveRecord using primary/replca databases
112
+ email:
113
+ - lloyd@olioex.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".github/dependabot.yml"
119
+ - ".github/workflows/ci.yml"
120
+ - ".rspec"
121
+ - ".rubocop.yml"
122
+ - Gemfile
123
+ - Gemfile.lock
124
+ - README.md
125
+ - janus-ar.gemspec
126
+ - lib/active_record/connection_adapters/janus_mysql2_adapter.rb
127
+ - lib/janus.rb
128
+ - lib/janus/context.rb
129
+ - lib/janus/logging/logger.rb
130
+ - lib/janus/logging/subscriber.rb
131
+ - lib/janus/version.rb
132
+ - spec/lib/janus/context_spec.rb
133
+ - spec/lib/janus/logging/logger_spec.rb
134
+ - spec/spec_helper.rb
135
+ homepage: https://github.com/olioex/janus-ar
136
+ licenses:
137
+ - MIT
138
+ metadata:
139
+ source_code_uri: https://github.com/olioex/janus-ar
140
+ post_install_message:
141
+ rdoc_options: []
142
+ require_paths:
143
+ - lib
144
+ required_ruby_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: 3.2.0
149
+ required_rubygems_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ requirements: []
155
+ rubygems_version: 3.4.10
156
+ signing_key:
157
+ specification_version: 4
158
+ summary: Read/Write proxy for ActiveRecord using primary/replca databases
159
+ test_files: []