idempotency_lock 0.1.1 → 0.1.3

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: 3472cab3f3ed5118fb6b5843141d81c215efd5d0c7c7f1a12e88e3989a10315e
4
- data.tar.gz: e070c49739921f73928eecc9ad94b97ce316c6cef1f4357ea109e211360c5ca5
3
+ metadata.gz: 4920afd8d5429063037ecb787cf54915a0837d01dcc0e347143117c92aa1e842
4
+ data.tar.gz: fb92062859dc7f2dad7ecc897c6f327ddf486376b1177f2adbee01b595853429
5
5
  SHA512:
6
- metadata.gz: adf210196f47721787c1bc87e22de3c83ec787cf0b2f9c5b2a630e52aa610ee86c3e1124c19e2cea0401fbfc92a6ebcc795d94a735539eec55d57967e6ab70a1
7
- data.tar.gz: b85bc4c3e9d4713d3e75bbe0032964d746e5eccc13b185937bfcf66617e8542c390414920e8478429812198f5680ef03ce28f07482c017b15e9c9a8a8053120e
6
+ metadata.gz: e81784700e7745eb981190b9aa53268c79f47ee1897ff20ea04669254e684abd1e012247d35de67b6af499539e4034651a9a28486e9c3494137e7749af22a80d
7
+ data.tar.gz: 32838477ada126fce6e6ff26f5a07e6b363d34a846314e46808917e90a934cb283a62d21bb0312c3a451cf2f3cff67e20dd31a5cd8f77f67a7df536fb2f92e5c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.1.3] - 2025-12-06
4
+
5
+ ### Changed
6
+
7
+ - Use `updated_at` instead of `expires_at` for optimistic locking (more reliable
8
+ since it's system-managed and always moves forward)
9
+
10
+ ## [0.1.2] - 2025-12-06
11
+
12
+ ### Fixed
13
+
14
+ - Migration now works with MySQL (uses regular index instead of partial index,
15
+ which MySQL doesn't support)
16
+
17
+ ### Added
18
+
19
+ - `Result#inspect` for easier debugging
20
+
3
21
  ## [0.1.1] - 2025-12-06
4
22
 
5
23
  ### Fixed
data/README.md CHANGED
@@ -186,7 +186,7 @@ The gem creates an `idempotency_locks` table with:
186
186
 
187
187
  Indexes:
188
188
  - Unique index on `name` (provides atomicity)
189
- - Partial index on `expires_at` where not null (for efficient TTL queries)
189
+ - Index on `expires_at` (partial index on PostgreSQL/SQLite, regular index on MySQL)
190
190
 
191
191
  **Note on name length:** Lock names are limited to 255 characters to ensure compatibility with database unique index limits. MySQL users with `utf8mb4` encoding may need to reduce this to 191 characters by modifying the migration before running it.
192
192
 
@@ -13,7 +13,14 @@ class CreateIdempotencyLocks < ActiveRecord::Migration<%= migration_version %>
13
13
  end
14
14
 
15
15
  add_index :idempotency_locks, :name, unique: true
16
- add_index :idempotency_locks, :expires_at, where: "expires_at IS NOT NULL"
16
+
17
+ # Partial indexes are more efficient but not supported by MySQL.
18
+ # Use a regular index for MySQL, partial index for PostgreSQL/SQLite.
19
+ if ActiveRecord::Base.connection.adapter_name.downcase.include?("mysql")
20
+ add_index :idempotency_locks, :expires_at
21
+ else
22
+ add_index :idempotency_locks, :expires_at, where: "expires_at IS NOT NULL"
23
+ end
17
24
  end
18
25
  end
19
26
 
@@ -56,12 +56,12 @@ module IdempotencyLock
56
56
  return false if existing.nil?
57
57
  return false unless existing.expired?(now: now)
58
58
 
59
- # Optimistic locking: use the observed expires_at in the WHERE clause.
59
+ # Optimistic locking: use the observed updated_at in the WHERE clause.
60
60
  # This prevents a race where two processes both see an expired lock and
61
61
  # both try to claim it. Only one UPDATE will match; the other returns 0.
62
- # This is safer than `WHERE expires_at < now` which could allow a second
63
- # process to steal a lock if the first used a short TTL.
64
- updated = where(name: name, expires_at: existing.expires_at)
62
+ # We use updated_at rather than expires_at because it's system-managed
63
+ # and always moves forward, making it more reliable for concurrency control.
64
+ updated = where(name: name, updated_at: existing.updated_at)
65
65
  .update_all(expires_at: expires_at, executed_at: now, updated_at: now)
66
66
 
67
67
  updated.positive?
@@ -40,5 +40,14 @@ module IdempotencyLock
40
40
  def success?
41
41
  @executed && !error?
42
42
  end
43
+
44
+ # @return [String] human-readable representation for debugging
45
+ def inspect
46
+ parts = ["executed=#{@executed}"]
47
+ parts << "skipped=#{@skipped}" if @skipped
48
+ parts << "value=#{@value.inspect}" if @value
49
+ parts << "error=#{@error.class}" if @error
50
+ "#<IdempotencyLock::Result #{parts.join(" ")}>"
51
+ end
43
52
  end
44
53
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module IdempotencyLock
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.3"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: idempotency_lock
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Nagro