pecorino 0.7.3 → 0.7.4

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: c0e0c8fa1a6bc734dc645b5b021264eda84bcadd1bec8da65c533a33cd243fb6
4
- data.tar.gz: c6c053d8ed02f8e180786d4614c607087ca76b016b8acac35b35a3b3cb544391
3
+ metadata.gz: 392d3fe294cb751ad452716ef2b569f48f536a57464bdac39379042c29aa8242
4
+ data.tar.gz: 36309ce687caa5e2d32bf5e5cc7c862e264a7065cf5f211062e9ae7f51118ced
5
5
  SHA512:
6
- metadata.gz: 02207b3f3d52aa635a960c3914768237a25da4b4cdd7a5ad5da222adf44e738ac987f5182392d0617c60848c745fb0cc5225e2e2392dff50b6e83f58b703d191
7
- data.tar.gz: 350d04b9e366d229d87146465574632a00e5e0c88eda7cbc540b494d8ab1d3520d87092f5ae6a6b11f0dd323cc7fd15436728c9b6210fe2264be384b0e9505b4
6
+ metadata.gz: 14b6cadec3609d946d786330e5c912e2ae57054ba6f740d4a0dd4b82bc4660417df2bd71e8ac97fa44990b47f20f60540d82a2299c5a176da4b8c191752f46ba
7
+ data.tar.gz: 5059d7a546df2a1a6822a70ed73290165f7506ba6f36f41daf3779cad158781768c004aa5bd063f24452c15dcb6d5889efd16362b3e79ef51e2f88bbcaffc3e8
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 0.7.4
2
+
3
+ - Ensure deprecated ActiveRecord::Base.connection is replaced with ActiveRecord::Base.connection_pool.with_connection. This prevents permanent connection checkout
4
+
1
5
  ## 0.7.3
2
6
 
3
7
  - Fix a number of YARD issues and generate both .rbi and .rbs typedefs
data/README.md CHANGED
@@ -189,8 +189,10 @@ The Pecorino buckets and blocks are stateful. If you are not running tests with
189
189
  ```ruby
190
190
  setup do
191
191
  # Delete all transient records
192
- ActiveRecord::Base.connection.execute("TRUNCATE TABLE pecorino_blocks")
193
- ActiveRecord::Base.connection.execute("TRUNCATE TABLE pecorino_leaky_buckets")
192
+ ActiveRecord::Base.connection_pool.with_connection do |connection|
193
+ connection.execute("TRUNCATE TABLE pecorino_blocks")
194
+ connection.execute("TRUNCATE TABLE pecorino_leaky_buckets")
195
+ end
194
196
  end
195
197
  ```
196
198
 
@@ -224,8 +226,10 @@ cached_throttle.request!
224
226
  Throttles and leaky buckets are transient resources. If you are using Postgres replication, it might be prudent to set the Pecorino tables to `UNLOGGED` which will exclude them from replication - and save you bandwidth and storage on your RR. To do so, add the following statements to your migration:
225
227
 
226
228
  ```ruby
227
- ActiveRecord::Base.connection.execute("ALTER TABLE pecorino_leaky_buckets SET UNLOGGED")
228
- ActiveRecord::Base.connection.execute("ALTER TABLE pecorino_blocks SET UNLOGGED")
229
+ ActiveRecord::Base.connection_pool.with_connection do |connection|
230
+ connection.execute("ALTER TABLE pecorino_leaky_buckets SET UNLOGGED")
231
+ connection.execute("ALTER TABLE pecorino_blocks SET UNLOGGED")
232
+ end
229
233
  ```
230
234
 
231
235
  ## Development
@@ -30,7 +30,7 @@ class Pecorino::Adapters::PostgresAdapter
30
30
 
31
31
  # If the return value of the query is a NULL it means no such bucket exists,
32
32
  # so we assume the bucket is empty
33
- current_level = @model_class.connection.uncached { @model_class.connection.select_value(sql) } || 0.0
33
+ current_level = @model_class.connection_pool.with_connection { |connection| connection.uncached { connection.select_value(sql) } } || 0.0
34
34
  [current_level, capacity - current_level.abs < 0.01]
35
35
  end
36
36
 
@@ -83,7 +83,7 @@ class Pecorino::Adapters::PostgresAdapter
83
83
  # query as a repeat (since we use "select_one" for the RETURNING bit) and will not call into Postgres
84
84
  # correctly, thus the clock_timestamp() value would be frozen between calls. We don't want that here.
85
85
  # See https://stackoverflow.com/questions/73184531/why-would-postgres-clock-timestamp-freeze-inside-a-rails-unit-test
86
- upserted = @model_class.connection.uncached { @model_class.connection.select_one(sql) }
86
+ upserted = @model_class.connection_pool.with_connection { |connection| connection.uncached { connection.select_one(sql) } }
87
87
  capped_level_after_fillup, at_capacity = upserted.fetch("level"), upserted.fetch("at_capacity")
88
88
  [capped_level_after_fillup, at_capacity]
89
89
  end
@@ -141,7 +141,7 @@ class Pecorino::Adapters::PostgresAdapter
141
141
  level AS level_after
142
142
  SQL
143
143
 
144
- upserted = @model_class.connection.uncached { @model_class.connection.select_one(sql) }
144
+ upserted = @model_class.connection_pool.with_connection { |connection| connection.uncached { connection.select_one(sql) } }
145
145
  level_after = upserted.fetch("level_after")
146
146
  level_before = upserted.fetch("level_before")
147
147
  [level_after, level_after >= capacity, level_after != level_before]
@@ -159,19 +159,21 @@ class Pecorino::Adapters::PostgresAdapter
159
159
  blocked_until = GREATEST(EXCLUDED.blocked_until, t.blocked_until)
160
160
  RETURNING blocked_until
161
161
  SQL
162
- @model_class.connection.uncached { @model_class.connection.select_value(block_set_query) }
162
+ @model_class.connection_pool.with_connection { |connection| connection.uncached { connection.select_value(block_set_query) } }
163
163
  end
164
164
 
165
165
  def blocked_until(key:)
166
166
  block_check_query = @model_class.sanitize_sql_array([<<~SQL, key])
167
167
  SELECT blocked_until FROM pecorino_blocks WHERE key = ? AND blocked_until >= clock_timestamp() LIMIT 1
168
168
  SQL
169
- @model_class.connection.uncached { @model_class.connection.select_value(block_check_query) }
169
+ @model_class.connection_pool.with_connection { |connection| connection.uncached { connection.select_value(block_check_query) } }
170
170
  end
171
171
 
172
172
  def prune
173
- @model_class.connection.execute("DELETE FROM pecorino_blocks WHERE blocked_until < NOW()")
174
- @model_class.connection.execute("DELETE FROM pecorino_leaky_buckets WHERE may_be_deleted_after < NOW()")
173
+ @model_class.connection_pool.with_connection do |connection|
174
+ connection.execute("DELETE FROM pecorino_blocks WHERE blocked_until < NOW()")
175
+ connection.execute("DELETE FROM pecorino_leaky_buckets WHERE may_be_deleted_after < NOW()")
176
+ end
175
177
  end
176
178
 
177
179
  def create_tables(active_record_schema)
@@ -37,7 +37,7 @@ class Pecorino::Adapters::SqliteAdapter
37
37
 
38
38
  # If the return value of the query is a NULL it means no such bucket exists,
39
39
  # so we assume the bucket is empty
40
- current_level = @model_class.connection.uncached { @model_class.connection.select_value(sql) } || 0.0
40
+ current_level = @model_class.connection_pool.with_connection { |connection| connection.uncached { connection.select_value(sql) } } || 0.0
41
41
  [current_level, capacity - current_level.abs < 0.01]
42
42
  end
43
43
 
@@ -91,7 +91,7 @@ class Pecorino::Adapters::SqliteAdapter
91
91
  # query as a repeat (since we use "select_one" for the RETURNING bit) and will not call into Postgres
92
92
  # correctly, thus the clock_timestamp() value would be frozen between calls. We don't want that here.
93
93
  # See https://stackoverflow.com/questions/73184531/why-would-postgres-clock-timestamp-freeze-inside-a-rails-unit-test
94
- upserted = @model_class.connection.uncached { @model_class.connection.select_one(sql) }
94
+ upserted = @model_class.connection_pool.with_connection { |connection| connection.uncached { connection.select_one(sql) } }
95
95
  capped_level_after_fillup, one_if_did_overflow = upserted.fetch("level"), upserted.fetch("did_overflow")
96
96
  [capped_level_after_fillup, one_if_did_overflow == 1]
97
97
  end
@@ -130,7 +130,7 @@ class Pecorino::Adapters::SqliteAdapter
130
130
  -- so that it can't be deleted between our INSERT and our UPDATE
131
131
  may_be_deleted_after = EXCLUDED.may_be_deleted_after
132
132
  SQL
133
- @model_class.connection.execute(insert_sql)
133
+ @model_class.connection_pool.with_connection { |connection| connection.execute(insert_sql) }
134
134
 
135
135
  sql = @model_class.sanitize_sql_array([<<~SQL, query_params])
136
136
  -- With SQLite MATERIALIZED has to be used so that level_post is calculated before the UPDATE takes effect
@@ -156,7 +156,7 @@ class Pecorino::Adapters::SqliteAdapter
156
156
  level AS level_after
157
157
  SQL
158
158
 
159
- upserted = @model_class.connection.uncached { @model_class.connection.select_one(sql) }
159
+ upserted = @model_class.connection_pool.with_connection { |connection| connection.uncached { connection.select_one(sql) } }
160
160
  level_after = upserted.fetch("level_after")
161
161
  level_before = upserted.fetch("level_before")
162
162
  [level_after, level_after >= capacity, level_after != level_before]
@@ -174,7 +174,7 @@ class Pecorino::Adapters::SqliteAdapter
174
174
  blocked_until = MAX(EXCLUDED.blocked_until, t.blocked_until)
175
175
  RETURNING blocked_until;
176
176
  SQL
177
- blocked_until_s = @model_class.connection.uncached { @model_class.connection.select_value(block_set_query) }
177
+ blocked_until_s = @model_class.connection_pool.with_connection { |connection| connection.uncached { connection.select_value(block_set_query) } }
178
178
  Time.at(blocked_until_s)
179
179
  end
180
180
 
@@ -188,14 +188,16 @@ class Pecorino::Adapters::SqliteAdapter
188
188
  WHERE
189
189
  key = :key AND blocked_until >= :now_s LIMIT 1
190
190
  SQL
191
- blocked_until_s = @model_class.connection.uncached { @model_class.connection.select_value(block_check_query) }
191
+ blocked_until_s = @model_class.connection_pool.with_connection { |connection| connection.uncached { connection.select_value(block_check_query) } }
192
192
  blocked_until_s && Time.at(blocked_until_s)
193
193
  end
194
194
 
195
195
  def prune
196
196
  now_s = Time.now.to_f
197
- @model_class.connection.execute("DELETE FROM pecorino_blocks WHERE blocked_until < ?", now_s)
198
- @model_class.connection.execute("DELETE FROM pecorino_leaky_buckets WHERE may_be_deleted_after < ?", now_s)
197
+ @model_class.connection_pool.with_connection do |connection|
198
+ connection.execute("DELETE FROM pecorino_blocks WHERE blocked_until < ?", now_s)
199
+ connection.execute("DELETE FROM pecorino_leaky_buckets WHERE may_be_deleted_after < ?", now_s)
200
+ end
199
201
  end
200
202
 
201
203
  def create_tables(active_record_schema)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pecorino
4
- VERSION = "0.7.3"
4
+ VERSION = "0.7.4"
5
5
  end
data/lib/pecorino.rb CHANGED
@@ -65,7 +65,7 @@ module Pecorino
65
65
  # @return [Pecorino::Adapters::BaseAdapter]
66
66
  def self.default_adapter_from_main_database
67
67
  model_class = ActiveRecord::Base
68
- adapter_name = model_class.connection.adapter_name
68
+ adapter_name = model_class.connection_pool.with_connection(&:adapter_name)
69
69
  case adapter_name
70
70
  when /postgres/i
71
71
  Pecorino::Adapters::PostgresAdapter.new(model_class)
@@ -23,7 +23,7 @@ class PostgresAdapterTest < ActiveSupport::TestCase
23
23
 
24
24
  def create_postgres_database_if_none
25
25
  self.class.establish_connection(encoding: "unicode", database: SEED_DB_NAME.call)
26
- ActiveRecord::Base.connection.execute("SELECT 1 FROM pecorino_leaky_buckets")
26
+ ActiveRecord::Base.connection_pool.with_connection { |connection| connection.execute("SELECT 1 FROM pecorino_leaky_buckets") }
27
27
  rescue ActiveRecord::NoDatabaseError, ActiveRecord::ConnectionNotEstablished
28
28
  create_postgres_database
29
29
  retry
@@ -38,20 +38,24 @@ class PostgresAdapterTest < ActiveSupport::TestCase
38
38
  def create_postgres_database
39
39
  ActiveRecord::Migration.verbose = false
40
40
  self.class.establish_connection(database: "postgres")
41
- ActiveRecord::Base.connection.create_database(SEED_DB_NAME.call, charset: :unicode)
41
+ ActiveRecord::Base.connection_pool.with_connection { |connection| connection.create_database(SEED_DB_NAME.call, charset: :unicode) }
42
42
  ActiveRecord::Base.connection.close
43
43
  self.class.establish_connection(encoding: "unicode", database: SEED_DB_NAME.call)
44
44
  end
45
45
 
46
46
  def truncate_test_tables
47
- ActiveRecord::Base.connection.execute("TRUNCATE TABLE pecorino_leaky_buckets")
48
- ActiveRecord::Base.connection.execute("TRUNCATE TABLE pecorino_blocks")
47
+ ActiveRecord::Base.connection_pool.with_connection do |connection|
48
+ connection.execute("TRUNCATE TABLE pecorino_leaky_buckets")
49
+ connection.execute("TRUNCATE TABLE pecorino_blocks")
50
+ end
49
51
  end
50
52
 
51
53
  def test_create_tables
52
54
  ActiveRecord::Base.transaction do
53
- ActiveRecord::Base.connection.execute("DROP TABLE pecorino_leaky_buckets")
54
- ActiveRecord::Base.connection.execute("DROP TABLE pecorino_blocks")
55
+ ActiveRecord::Base.connection_pool.with_connection do |connection|
56
+ connection.execute("DROP TABLE pecorino_leaky_buckets")
57
+ connection.execute("DROP TABLE pecorino_blocks")
58
+ end
55
59
  # The adapter has to be in a variable as the schema definition is scoped to the migrator, not self
56
60
  retained_adapter = create_adapter # the schema define block is run via instance_exec so it does not retain scope
57
61
  ActiveRecord::Schema.define(version: 1) do |via_definer|
@@ -64,6 +68,6 @@ class PostgresAdapterTest < ActiveSupport::TestCase
64
68
  Minitest.after_run do
65
69
  ActiveRecord::Base.connection.close
66
70
  establish_connection(database: "postgres")
67
- ActiveRecord::Base.connection.drop_database(SEED_DB_NAME.call)
71
+ ActiveRecord::Base.connection_pool.with_connection { |connection| connection.drop_database(SEED_DB_NAME.call) }
68
72
  end
69
73
  end
@@ -33,8 +33,10 @@ class SqliteAdapterTest < ActiveSupport::TestCase
33
33
 
34
34
  def test_create_tables
35
35
  ActiveRecord::Base.transaction do
36
- ActiveRecord::Base.connection.execute("DROP TABLE pecorino_leaky_buckets")
37
- ActiveRecord::Base.connection.execute("DROP TABLE pecorino_blocks")
36
+ ActiveRecord::Base.connection_pool.with_connection do |connection|
37
+ connection.execute("DROP TABLE pecorino_leaky_buckets")
38
+ connection.execute("DROP TABLE pecorino_blocks")
39
+ end
38
40
  # The adapter has to be in a variable as the schema definition is scoped to the migrator, not self
39
41
  retained_adapter = create_adapter # the schema define block is run via instance_exec so it does not retain scope
40
42
  ActiveRecord::Schema.define(version: 1) do |via_definer|
data/test/block_test.rb CHANGED
@@ -4,20 +4,24 @@ require "test_helper"
4
4
  require "base64"
5
5
 
6
6
  class BlockTest < ActiveSupport::TestCase
7
+ def setup
8
+ @adapter = Pecorino::Adapters::MemoryAdapter.new
9
+ end
10
+
7
11
  test "sets a block" do
8
12
  k = Base64.strict_encode64(Random.bytes(4))
9
- assert_nil Pecorino::Block.blocked_until(key: k)
10
- assert Pecorino::Block.set!(key: k, block_for: 30.minutes)
13
+ assert_nil Pecorino::Block.blocked_until(key: k, adapter: @adapter)
14
+ assert Pecorino::Block.set!(key: k, block_for: 30.minutes, adapter: @adapter)
11
15
 
12
- blocked_until = Pecorino::Block.blocked_until(key: k)
16
+ blocked_until = Pecorino::Block.blocked_until(key: k, adapter: @adapter)
13
17
  assert_in_delta Time.now + 30.minutes, blocked_until, 10
14
18
  end
15
19
 
16
20
  test "does not return a block which has lapsed" do
17
21
  k = Base64.strict_encode64(Random.bytes(4))
18
- assert_nil Pecorino::Block.blocked_until(key: k)
19
- Pecorino::Block.set!(key: k, block_for: -30.minutes)
20
- blocked_until = Pecorino::Block.blocked_until(key: k)
22
+ assert_nil Pecorino::Block.blocked_until(key: k, adapter: @adapter)
23
+ Pecorino::Block.set!(key: k, block_for: -30.minutes, adapter: @adapter)
24
+ blocked_until = Pecorino::Block.blocked_until(key: k, adapter: @adapter)
21
25
  assert_nil blocked_until
22
26
  end
23
27
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pecorino
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.3
4
+ version: 0.7.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julik Tarkhanov
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-04-01 00:00:00.000000000 Z
10
+ date: 2025-08-07 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activerecord