placate 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: 1c781f759fd2282b4cbbf32039ef53e5eed2f1959bf46d670b13cc57139e7a91
4
+ data.tar.gz: e96b43f21704a5ae648a1acc2a3dfd8530f5669c301cfdf872d44d8bbbc73510
5
+ SHA512:
6
+ metadata.gz: 7d3005d9ff5ba7a53b501706066bb5d7657fa72e5e40b6846dab2e1b8282dcf973a5a932d4d1c932f02a2d2409851060b56c22d492b759d0915651dea7a60acf
7
+ data.tar.gz: e2be1bab70de91dd7e595c64c50367e3a2985551d69f2f587af4e0d3183c03c9ada2a3b99562497f2396f69609ee08d9e7f8922fcb7820f7663aa500dd2e6f7d
data/.rubocop.yml ADDED
@@ -0,0 +1,17 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.3
3
+
4
+ Style/StringLiterals:
5
+ Enabled: true
6
+ EnforcedStyle: double_quotes
7
+
8
+ Style/StringLiteralsInInterpolation:
9
+ Enabled: true
10
+ EnforcedStyle: double_quotes
11
+
12
+ Layout/LineLength:
13
+ Max: 120
14
+
15
+ Style/Documentation:
16
+ Description: Document classes and non-namespace modules.
17
+ Enabled: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-11-28
4
+
5
+ - Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 TODO: Write your name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,113 @@
1
+ # Placate
2
+
3
+ Placate is a simple Redis-based solution for preventing duplicate job execution in Ruby background job processors. It's designed to work with ActiveJob and any Redis-backed job system.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'placate'
11
+ ```
12
+
13
+ And then execute:
14
+ ```bash
15
+ bundle install
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ### Basic Setup
21
+
22
+ First, configure Placate with your Redis connection:
23
+
24
+ ```ruby
25
+ # config/initializers/placate.rb
26
+ Placate.configure do |config|
27
+ config.redis = Redis.new
28
+ config.default_lock_ttl = 30 # Default time-to-live in seconds
29
+ end
30
+ ```
31
+
32
+ ### In Your Jobs
33
+
34
+ Include the UniqueJob module in any job you want to prevent duplicates:
35
+
36
+ ```ruby
37
+ class ProcessOrderJob < ApplicationJob
38
+ include Placate::UniqueJob
39
+
40
+ def perform(order_id)
41
+ # Your job code here
42
+ end
43
+ end
44
+ ```
45
+
46
+ Now if you try to enqueue the same job with the same arguments within the TTL window, the duplicate will be prevented:
47
+
48
+ ```ruby
49
+ ProcessOrderJob.perform_later(order_id: 123) # First job enqueued
50
+ ProcessOrderJob.perform_later(order_id: 123) # Blocked as duplicate
51
+ ProcessOrderJob.perform_later(order_id: 456) # Different args, will be enqueued
52
+ ```
53
+
54
+ ### Customization
55
+
56
+ You can customize the TTL per job class:
57
+
58
+ ```ruby
59
+ class LongRunningJob < ApplicationJob
60
+ include Placate::UniqueJob
61
+
62
+ self.unique_lock_ttl = 120 # 2 minutes
63
+
64
+ def perform
65
+ # Your job code here
66
+ end
67
+ end
68
+ ```
69
+
70
+ Or use a custom key prefix:
71
+
72
+ ```ruby
73
+ class CustomPrefixJob < ApplicationJob
74
+ include Placate::UniqueJob
75
+
76
+ self.unique_lock_prefix = 'my_app_jobs'
77
+
78
+ def perform
79
+ # Your job code here
80
+ end
81
+ end
82
+ ```
83
+
84
+ ## How It Works
85
+
86
+ Placate uses Redis to maintain a lock based on the job class name and arguments. When a job is enqueued (`before_enqueue`):
87
+
88
+ 1. A unique key is generated based on the job class and arguments
89
+ 2. The current timestamp is stored in Redis with this key
90
+ 3. If a key exists and its timestamp is within the TTL window, the job is not enqueued
91
+ 4. If no key exists or the existing timestamp is older than the TTL, the job is enqueued
92
+
93
+ After the job is performed (`after_perform`):
94
+
95
+ 1. The unique key is deleted, to enable the next job.
96
+
97
+ ## Requirements
98
+
99
+ - Ruby 3.3+
100
+ - Rails 7.0+
101
+ - Redis
102
+
103
+ ## Development
104
+
105
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
106
+
107
+ ## Contributing
108
+
109
+ Bug reports and pull requests are welcome on GitHub. This project is intended to be a safe, welcoming space for collaboration.
110
+
111
+ ## License
112
+
113
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "minitest/test_task"
5
+
6
+ Minitest::TestTask.create
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[test rubocop]
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Placate
4
+ class Configuration
5
+ attr_accessor :redis, :default_lock_ttl
6
+
7
+ def initialize
8
+ @default_lock_ttl = 60 # 1 minute default
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+
5
+ module Placate
6
+ module UniqueJob
7
+ extend ActiveSupport::Concern
8
+
9
+ class_methods do
10
+ def unique_lock_ttl
11
+ @unique_lock_ttl || Placate.configuration.default_lock_ttl
12
+ end
13
+
14
+ def unique_lock_ttl=(seconds)
15
+ @unique_lock_ttl = seconds
16
+ end
17
+ end
18
+
19
+ included do
20
+ class_attribute :unique_lock_prefix, instance_writer: false, default: nil
21
+
22
+ before_enqueue do |job|
23
+ args_hash = Digest::MD5.hexdigest(job.arguments.to_s)
24
+ prefix = job.class.unique_lock_prefix || job.class.name
25
+ lock_key = "#{prefix}:#{args_hash}:lock"
26
+
27
+ current_time = Time.now.to_i
28
+ existing_lock = Placate.redis.get(lock_key)
29
+
30
+ if existing_lock
31
+ lock_time = existing_lock.to_i
32
+ throw :abort if current_time - lock_time < (job.class.unique_lock_ttl || 30)
33
+ end
34
+
35
+ Placate.redis.set(lock_key, current_time)
36
+ end
37
+
38
+ after_perform do |job|
39
+ args_hash = Digest::MD5.hexdigest(job.arguments.to_s)
40
+ prefix = job.class.unique_lock_prefix || job.class.name
41
+ lock_key = "#{prefix}:#{args_hash}:lock"
42
+
43
+ Placate.redis.del(lock_key) if lock_key
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Placate
4
+ VERSION = "0.1.0"
5
+ end
data/lib/placate.rb ADDED
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support"
4
+
5
+ require_relative "placate/version"
6
+ require_relative "placate/configuration"
7
+ require_relative "placate/unique_job"
8
+
9
+ module Placate
10
+ class Error < StandardError; end
11
+ # Your code goes here...
12
+
13
+ class << self
14
+ def configure
15
+ yield(configuration)
16
+ end
17
+
18
+ def configuration
19
+ @configuration ||= Configuration.new
20
+ end
21
+
22
+ def redis
23
+ configuration.redis
24
+ end
25
+ end
26
+ end
data/sig/placate.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Placate
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: placate
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Toby
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-11-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activejob
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '7.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.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.16'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.16'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '13.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '13.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.21'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.21'
69
+ description: Prevents duplicate job execution using Redis locks for any Redis-based
70
+ job processing system
71
+ email:
72
+ - toby@darkroom.tech
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".rubocop.yml"
78
+ - CHANGELOG.md
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - lib/placate.rb
83
+ - lib/placate/configuration.rb
84
+ - lib/placate/unique_job.rb
85
+ - lib/placate/version.rb
86
+ - sig/placate.rbs
87
+ homepage: https://github.com/tobyond/placate
88
+ licenses:
89
+ - MIT
90
+ metadata:
91
+ homepage_uri: https://github.com/tobyond/placate
92
+ source_code_uri: https://github.com/tobyond/placate
93
+ changelog_uri: https://github.com/tobyond/placate/blob/main/CHANGELOG.md
94
+ post_install_message:
95
+ rdoc_options: []
96
+ require_paths:
97
+ - lib
98
+ required_ruby_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: 3.3.0
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ requirements: []
109
+ rubygems_version: 3.5.3
110
+ signing_key:
111
+ specification_version: 4
112
+ summary: Unique job handling for Redis-based job processors
113
+ test_files: []