arproxy 0.2.9 → 1.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 97417dad77bf18a6a058b5a7bf30cfd2118e70a2db8da4eb9574f5d6694b1119
4
- data.tar.gz: 76361afc976b7fd086c702fb7dd0629b3d0ad85776667b1438d323db64173139
3
+ metadata.gz: e079e98bb0b0f67bb3bc2189c4ee95b27e7177b4db0e9ba52ac746b8cc492366
4
+ data.tar.gz: e6e388e344e42d79aca1a40eddb3ee4e2b08f55a2e470ee34bc75ac97ec8963c
5
5
  SHA512:
6
- metadata.gz: 0515356f2ddafdb1f77bbb6dfb49769fbc4d08d1381bf2657f32210d3dd9841f3e29d5ca157f6e24e23392d219f0123dc703c0e1c34e083ee06ed58d3f09ec32
7
- data.tar.gz: 95ce473dc91f7d5a38009eb105b35b3f8760f31f8fbb85b9fe45c66151864c98120585f6476363dc395f3a1d66a3d3433e4d2355c420b36dff4e392464c9c51d
6
+ metadata.gz: 97cba7c8a4a525a072ddc48bc89425b53a98f6f2f3445f4e3733a9b97feb3f83fc303536ce0962a23891b1281e6feb949e85ce36a90e209d2c62f3e614319c4b
7
+ data.tar.gz: dff9dc34f6349c4fd61aa9cbaa786b81a11d648c26d3905a600221b27a8235933158ecae199c44036de498bcae648bc976807f666d93c584b6aa48b63bd1227f
data/lib/arproxy/base.rb CHANGED
@@ -1,9 +1,5 @@
1
1
  module Arproxy
2
+ # This class is no longer used since Arproxy v1.
2
3
  class Base
3
- attr_accessor :proxy_chain, :next_proxy
4
-
5
- def execute(sql, name=nil, **kwargs)
6
- next_proxy.execute sql, name, **kwargs
7
- end
8
4
  end
9
5
  end
@@ -1,5 +1,7 @@
1
- require "active_record"
2
- require "active_record/base"
1
+ require 'active_record'
2
+ require 'active_record/base'
3
+ require 'arproxy/base'
4
+ require 'arproxy/error'
3
5
 
4
6
  module Arproxy
5
7
  class Config
@@ -9,22 +11,31 @@ module Arproxy
9
11
  def initialize
10
12
  @proxies = []
11
13
  if defined?(Rails)
12
- @adapter = Rails.application.config_for(:database)["adapter"]
14
+ @adapter = Rails.application.config_for(:database)['adapter']
13
15
  end
14
16
  end
15
17
 
16
18
  def use(proxy_class, *options)
19
+ if proxy_class.is_a?(Class) && proxy_class.ancestors.include?(Arproxy::Base)
20
+ raise Arproxy::Error, "Error on loading a proxy `#{proxy_class.inspect}`: the superclass `Arproxy::Base` is no longer supported since Arproxy v1. Use `Arproxy::Proxy` instead. See: https://github.com/cookpad/arproxy/blob/main/UPGRADING.md"
21
+ end
22
+
17
23
  ::Arproxy.logger.debug("Arproxy: Mounting #{proxy_class.inspect} (#{options.inspect})")
18
24
  @proxies << [proxy_class, options]
19
25
  end
20
26
 
21
27
  def plugin(name, *options)
22
28
  plugin_class = Plugin.get(name)
29
+
30
+ if plugin_class.is_a?(Class) && plugin_class.ancestors.include?(Arproxy::Base)
31
+ raise Arproxy::Error, "Error on loading a plugin `#{plugin_class.inspect}`: the superclass `Arproxy::Base` is no longer supported since Arproxy v1. Use `Arproxy::Proxy` instead. See: https://github.com/cookpad/arproxy/blob/main/UPGRADING.md"
32
+ end
33
+
23
34
  use(plugin_class, *options)
24
35
  end
25
36
 
26
37
  def adapter_class
27
- raise Arproxy::Error, "config.adapter must be set" unless @adapter
38
+ raise Arproxy::Error, 'config.adapter must be set' unless @adapter
28
39
  case @adapter
29
40
  when String, Symbol
30
41
  eval "::ActiveRecord::ConnectionAdapters::#{camelized_adapter_name}Adapter"
@@ -37,19 +48,19 @@ module Arproxy
37
48
 
38
49
  private
39
50
 
40
- def camelized_adapter_name
41
- adapter_name = @adapter.to_s.split("_").map(&:capitalize).join
51
+ def camelized_adapter_name
52
+ adapter_name = @adapter.to_s.split('_').map(&:capitalize).join
42
53
 
43
- case adapter_name
44
- when 'Sqlite3'
45
- 'SQLite3'
46
- when 'Sqlserver'
47
- 'SQLServer'
48
- when 'Postgresql'
49
- 'PostgreSQL'
50
- else
51
- adapter_name
54
+ case adapter_name
55
+ when 'Sqlite3'
56
+ 'SQLite3'
57
+ when 'Sqlserver'
58
+ 'SQLServer'
59
+ when 'Postgresql'
60
+ 'PostgreSQL'
61
+ else
62
+ adapter_name
63
+ end
52
64
  end
53
- end
54
65
  end
55
66
  end
@@ -0,0 +1,118 @@
1
+ module Arproxy
2
+ class ConnectionAdapterPatch
3
+ attr_reader :adapter_class
4
+
5
+ def initialize(adapter_class)
6
+ @adapter_class = adapter_class
7
+ @applied_patches = Set.new
8
+ end
9
+
10
+ def self.register_patches(adapter_name, patches: [], binds_patches: [])
11
+ @@patches ||= {}
12
+ @@patches[adapter_name] = {
13
+ patches: patches,
14
+ binds_patches: binds_patches
15
+ }
16
+ end
17
+
18
+ if ActiveRecord.version >= Gem::Version.new('8.0')
19
+ register_patches('Mysql2', patches: [], binds_patches: [:raw_execute])
20
+ register_patches('Trilogy', patches: [], binds_patches: [:raw_execute])
21
+ elsif ActiveRecord.version >= Gem::Version.new('7.0')
22
+ register_patches('Mysql2', patches: [:raw_execute], binds_patches: [])
23
+ register_patches('Trilogy', patches: [:raw_execute], binds_patches: [])
24
+ else
25
+ register_patches('Mysql2', patches: [:execute], binds_patches: [])
26
+ register_patches('Trilogy', patches: [:raw_execute], binds_patches: [])
27
+ end
28
+
29
+ if ActiveRecord.version >= Gem::Version.new('8.0')
30
+ register_patches('PostgreSQL', patches: [], binds_patches: [:raw_execute])
31
+ register_patches('SQLServer', patches: [], binds_patches: [:raw_execute])
32
+ register_patches('SQLite', patches: [], binds_patches: [:raw_execute])
33
+ elsif ActiveRecord.version >= Gem::Version.new('7.1')
34
+ register_patches('PostgreSQL', patches: [:raw_execute], binds_patches: [:exec_no_cache, :exec_cache])
35
+ register_patches('SQLServer', patches: [:raw_execute], binds_patches: [:internal_exec_query])
36
+ register_patches('SQLite', patches: [:raw_execute], binds_patches: [:internal_exec_query])
37
+ else
38
+ register_patches('PostgreSQL', patches: [:execute], binds_patches: [:exec_no_cache, :exec_cache])
39
+ register_patches('SQLServer', patches: [:execute], binds_patches: [:exec_query])
40
+ register_patches('SQLite', patches: [:execute], binds_patches: [:exec_query])
41
+ end
42
+
43
+ def enable!
44
+ patches = @@patches[adapter_class::ADAPTER_NAME]
45
+ if patches
46
+ patches[:patches]&.each do |patch|
47
+ apply_patch patch
48
+ end
49
+ patches[:binds_patches]&.each do |binds_patch|
50
+ apply_patch_binds binds_patch
51
+ end
52
+ else
53
+ raise Arproxy::Error, "Unexpected connection adapter: patches not registered for #{adapter_class&.name}"
54
+ end
55
+ ::Arproxy.logger.debug("Arproxy: Enabled (#{adapter_class::ADAPTER_NAME})")
56
+ end
57
+
58
+ def disable!
59
+ @applied_patches.dup.each do |target_method|
60
+ adapter_class.class_eval do
61
+ if instance_methods.include?(:"#{target_method}_with_arproxy")
62
+ alias_method target_method, :"#{target_method}_without_arproxy"
63
+ remove_method :"#{target_method}_with_arproxy"
64
+ end
65
+ end
66
+ @applied_patches.delete(target_method)
67
+ end
68
+ ::Arproxy.logger.debug("Arproxy: Disabled (#{adapter_class::ADAPTER_NAME})")
69
+ end
70
+
71
+ private
72
+
73
+ def apply_patch(target_method)
74
+ return if @applied_patches.include?(target_method)
75
+ adapter_class.class_eval do
76
+ raw_execute_method_name = :"#{target_method}_without_arproxy"
77
+ patched_execute_method_name = :"#{target_method}_with_arproxy"
78
+ break if instance_methods.include?(patched_execute_method_name)
79
+ define_method(patched_execute_method_name) do |sql, name=nil, **kwargs|
80
+ context = QueryContext.new(
81
+ raw_connection: self,
82
+ execute_method_name: raw_execute_method_name,
83
+ with_binds: false,
84
+ name: name,
85
+ kwargs: kwargs,
86
+ )
87
+ ::Arproxy.proxy_chain.head.execute(sql, context)
88
+ end
89
+ alias_method raw_execute_method_name, target_method
90
+ alias_method target_method, patched_execute_method_name
91
+ end
92
+ @applied_patches << target_method
93
+ end
94
+
95
+ def apply_patch_binds(target_method)
96
+ return if @applied_patches.include?(target_method)
97
+ adapter_class.class_eval do
98
+ raw_execute_method_name = :"#{target_method}_without_arproxy"
99
+ patched_execute_method_name = :"#{target_method}_with_arproxy"
100
+ break if instance_methods.include?(patched_execute_method_name)
101
+ define_method(patched_execute_method_name) do |sql, name=nil, binds=[], **kwargs|
102
+ context = QueryContext.new(
103
+ raw_connection: self,
104
+ execute_method_name: raw_execute_method_name,
105
+ with_binds: true,
106
+ name: name,
107
+ binds: binds,
108
+ kwargs: kwargs,
109
+ )
110
+ ::Arproxy.proxy_chain.head.execute(sql, context)
111
+ end
112
+ alias_method raw_execute_method_name, target_method
113
+ alias_method target_method, patched_execute_method_name
114
+ end
115
+ @applied_patches << target_method
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,15 @@
1
+ require 'arproxy/query_context'
2
+
3
+ module Arproxy
4
+ class Proxy
5
+ attr_accessor :context, :next_proxy
6
+
7
+ def execute(sql, context)
8
+ unless context.instance_of?(QueryContext)
9
+ raise Arproxy::Error, "`context` is expected a `Arproxy::QueryContext` but got `#{context.class}`"
10
+ end
11
+
12
+ next_proxy.execute(sql, context)
13
+ end
14
+ end
15
+ end
@@ -1,20 +1,21 @@
1
- module Arproxy
2
- autoload :ChainTail, "arproxy/chain_tail"
1
+ require 'arproxy/proxy_chain_tail'
2
+ require 'arproxy/connection_adapter_patch'
3
3
 
4
+ module Arproxy
4
5
  class ProxyChain
5
- attr_reader :head, :tail
6
+ attr_reader :head, :tail, :patch
6
7
 
7
- def initialize(config)
8
+ def initialize(config, patch)
8
9
  @config = config
10
+ @patch = patch
9
11
  setup
10
12
  end
11
13
 
12
14
  def setup
13
- @tail = ChainTail.new self
15
+ @tail = ProxyChainTail.new
14
16
  @head = @config.proxies.reverse.inject(@tail) do |next_proxy, proxy_config|
15
17
  cls, options = proxy_config
16
18
  proxy = cls.new(*options)
17
- proxy.proxy_chain = self
18
19
  proxy.next_proxy = next_proxy
19
20
  proxy
20
21
  end
@@ -28,31 +29,11 @@ module Arproxy
28
29
  end
29
30
 
30
31
  def enable!
31
- @config.adapter_class.class_eval do
32
- def execute_with_arproxy(sql, name=nil, **kwargs)
33
- ::Arproxy.proxy_chain.connection = self
34
- ::Arproxy.proxy_chain.head.execute sql, name, **kwargs
35
- end
36
- alias_method :execute_without_arproxy, :execute
37
- alias_method :execute, :execute_with_arproxy
38
- ::Arproxy.logger.debug("Arproxy: Enabled")
39
- end
32
+ @patch.enable!
40
33
  end
41
34
 
42
35
  def disable!
43
- @config.adapter_class.class_eval do
44
- alias_method :execute, :execute_without_arproxy
45
- ::Arproxy.logger.debug("Arproxy: Disabled")
46
- end
36
+ @patch.disable!
47
37
  end
48
-
49
- def connection
50
- Thread.current[:arproxy_connection]
51
- end
52
-
53
- def connection=(val)
54
- Thread.current[:arproxy_connection] = val
55
- end
56
-
57
38
  end
58
39
  end
@@ -0,0 +1,18 @@
1
+ require 'arproxy/proxy'
2
+ require 'arproxy/query_context'
3
+
4
+ module Arproxy
5
+ class ProxyChainTail < Proxy
6
+ def execute(sql, context)
7
+ unless context.instance_of?(QueryContext)
8
+ raise Arproxy::Error, "`context` is expected a `Arproxy::QueryContext` but got `#{context.class}`"
9
+ end
10
+
11
+ if context.with_binds?
12
+ context.raw_connection.send(context.execute_method_name, sql, context.name, context.binds, **context.kwargs)
13
+ else
14
+ context.raw_connection.send(context.execute_method_name, sql, context.name, **context.kwargs)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ module Arproxy
2
+ class QueryContext
3
+ attr_accessor :raw_connection, :execute_method_name, :with_binds, :name, :binds, :kwargs
4
+
5
+ def initialize(raw_connection:, execute_method_name:, with_binds:, name: nil, binds: [], kwargs: {})
6
+ @raw_connection = raw_connection
7
+ @execute_method_name = execute_method_name
8
+ @with_binds = with_binds
9
+ @name = name
10
+ @binds = binds
11
+ @kwargs = kwargs
12
+ end
13
+
14
+ def with_binds?
15
+ !!@with_binds
16
+ end
17
+ end
18
+ end
@@ -1,3 +1,3 @@
1
1
  module Arproxy
2
- VERSION = '0.2.9'
2
+ VERSION = '1.0.0'
3
3
  end
data/lib/arproxy.rb CHANGED
@@ -1,73 +1,81 @@
1
- require "logger"
2
- require "arproxy/base"
3
- require "arproxy/config"
4
- require "arproxy/proxy_chain"
5
- require "arproxy/error"
6
- require "arproxy/plugin"
1
+ require 'logger'
2
+ require 'arproxy/base'
3
+ require 'arproxy/config'
4
+ require 'arproxy/connection_adapter_patch'
5
+ require 'arproxy/proxy_chain'
6
+ require 'arproxy/error'
7
+ require 'arproxy/plugin'
7
8
 
8
9
  module Arproxy
9
- @config = @enabled = nil
10
+ @config = nil
11
+ @enabled = nil
12
+ @patch = nil
10
13
 
11
14
  module_function
12
- def clear_configuration
13
- @config = Config.new
14
- end
15
-
16
- def configure
17
- clear_configuration unless @config
18
- yield @config
19
- end
20
-
21
- def enable!
22
- if enable?
23
- Arproxy.logger.warn "Arproxy has been already enabled"
24
- return
15
+
16
+ def clear_configuration
17
+ @config = nil
25
18
  end
26
19
 
27
- unless @config
28
- raise Arproxy::Error, "Arproxy should be configured"
20
+ def configure
21
+ @config ||= Config.new
22
+ yield @config
29
23
  end
30
24
 
31
- @proxy_chain = ProxyChain.new @config
32
- @proxy_chain.enable!
25
+ def enable!
26
+ if enable?
27
+ Arproxy.logger.warn 'Arproxy has already been enabled'
28
+ return
29
+ end
30
+
31
+ unless @config
32
+ raise Arproxy::Error, 'Arproxy has not been configured'
33
+ end
33
34
 
34
- @enabled = true
35
- end
35
+ @patch = ConnectionAdapterPatch.new(@config.adapter_class)
36
+ @proxy_chain = ProxyChain.new(@config, @patch)
37
+ @proxy_chain.enable!
36
38
 
37
- def disable!
38
- unless enable?
39
- Arproxy.logger.warn "Arproxy is not enabled yet"
40
- return
39
+ @enabled = true
41
40
  end
42
41
 
43
- if @proxy_chain
44
- @proxy_chain.disable!
45
- @proxy_chain = nil
42
+ def disable!
43
+ unless enable?
44
+ Arproxy.logger.warn 'Arproxy is not enabled yet'
45
+ return
46
+ end
47
+
48
+ if @proxy_chain
49
+ @proxy_chain.disable!
50
+ @proxy_chain = nil
51
+ end
52
+
53
+ @enabled = false
46
54
  end
47
- @enabled = false
48
- end
49
-
50
- def enable?
51
- !!@enabled
52
- end
53
-
54
- def reenable!
55
- if enable?
56
- @proxy_chain.reenable!
57
- else
58
- enable!
55
+
56
+ def enable?
57
+ !!@enabled
58
+ end
59
+
60
+ def reenable!
61
+ if enable?
62
+ @proxy_chain.reenable!
63
+ else
64
+ enable!
65
+ end
66
+ end
67
+
68
+ def logger
69
+ @logger ||= @config && @config.logger ||
70
+ defined?(::Rails) && ::Rails.logger ||
71
+ ::Logger.new(STDOUT)
72
+ end
73
+
74
+ def proxy_chain
75
+ @proxy_chain
76
+ end
77
+
78
+ def connection_adapter_patch
79
+ @patch
59
80
  end
60
- end
61
-
62
- def logger
63
- @logger ||= begin
64
- @config && @config.logger ||
65
- defined?(::Rails) && ::Rails.logger ||
66
- ::Logger.new(STDOUT)
67
- end
68
- end
69
-
70
- def proxy_chain
71
- @proxy_chain
72
- end
73
81
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: arproxy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.9
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Issei Naruta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-18 00:00:00.000000000 Z
11
+ date: 2025-01-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,83 +16,31 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 4.2.0
19
+ version: '6.1'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 4.2.0
27
- - !ruby/object:Gem::Dependency
28
- name: bundler
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: rake
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: 12.3.3
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: 12.3.3
55
- - !ruby/object:Gem::Dependency
56
- name: rspec
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '3.0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '3.0'
69
- - !ruby/object:Gem::Dependency
70
- name: appraisal
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: '2.1'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: '2.1'
83
- description: Arproxy is a proxy between ActiveRecord and database adapter
84
- email: naruta@cookpad.com
26
+ version: '6.1'
27
+ description: Arproxy is a proxy layer that allows hooking into ActiveRecord query
28
+ execution and injecting custom processing
29
+ email: mimitako@gmail.com
85
30
  executables: []
86
31
  extensions: []
87
32
  extra_rdoc_files: []
88
33
  files:
89
34
  - lib/arproxy.rb
90
35
  - lib/arproxy/base.rb
91
- - lib/arproxy/chain_tail.rb
92
36
  - lib/arproxy/config.rb
37
+ - lib/arproxy/connection_adapter_patch.rb
93
38
  - lib/arproxy/error.rb
94
39
  - lib/arproxy/plugin.rb
40
+ - lib/arproxy/proxy.rb
95
41
  - lib/arproxy/proxy_chain.rb
42
+ - lib/arproxy/proxy_chain_tail.rb
43
+ - lib/arproxy/query_context.rb
96
44
  - lib/arproxy/version.rb
97
45
  homepage: https://github.com/cookpad/arproxy
98
46
  licenses:
@@ -113,8 +61,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
113
61
  - !ruby/object:Gem::Version
114
62
  version: '0'
115
63
  requirements: []
116
- rubygems_version: 3.2.22
64
+ rubygems_version: 3.5.11
117
65
  signing_key:
118
66
  specification_version: 4
119
- summary: Proxy between ActiveRecord and DB adapter
67
+ summary: A proxy layer between ActiveRecord and database adapters
120
68
  test_files: []
@@ -1,11 +0,0 @@
1
- module Arproxy
2
- class ChainTail < Base
3
- def initialize(proxy_chain)
4
- self.proxy_chain = proxy_chain
5
- end
6
-
7
- def execute(sql, name=nil, **kwargs)
8
- self.proxy_chain.connection.execute_without_arproxy sql, name, **kwargs
9
- end
10
- end
11
- end