once 0.0.1
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 +7 -0
- data/.gitignore +17 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE +13 -0
- data/README.md +51 -0
- data/Rakefile +1 -0
- data/lib/once.rb +48 -0
- data/lib/once/version.rb +3 -0
- data/once.gemspec +26 -0
- data/spec/once_spec.rb +87 -0
- data/spec/spec_helper.rb +3 -0
- metadata +127 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8172b01a3e28f6aaa417166e71b88c7172596863
|
4
|
+
data.tar.gz: 0ea53bf46f8db4fde91bccfecc8d070fb8a1df1b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 88f0716b9fe70077e28bece1c4225a75becc08d0931ddcc768e12b401ddabcbe9355c24f4ce947c6b3bc86f30a8912ff078d12508b33041ea1a2fcb6caf9b3ce
|
7
|
+
data.tar.gz: 84235be3a2ad656371f0a430e5f0531ef5d7b9f3c5bec50ce0b93cf7d1fe57443134852635adc1f335bd14c78555d813f399f86254c1d57961e9472747034349
|
data/.gitignore
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.2
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2015 Reverb.com, LLC
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# Once
|
2
|
+
|
3
|
+
Executes a block of code only once within a specified timeframe. Uses Redis to ensure uniqueness.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'once'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install once
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
First, connect to redis:
|
22
|
+
|
23
|
+
Once.redis = Redis.new(...)
|
24
|
+
|
25
|
+
If you don't specify the redis connection, we will assume the presence of a $redis global
|
26
|
+
|
27
|
+
Now, use Once to wrap a call that you want done uniquely
|
28
|
+
|
29
|
+
Once.do(name: "sending_email", params: { email: "foo@bar.com" }, within: 1.hour) do
|
30
|
+
# executes once
|
31
|
+
end
|
32
|
+
|
33
|
+
The combination of the name and params makes the check unique. So typically it would be the
|
34
|
+
command you're executing, plus the params to that command
|
35
|
+
|
36
|
+
## Notes
|
37
|
+
|
38
|
+
This does not implement an atomic lock, nor does it take into account
|
39
|
+
distributed locks across a cluster. This is more of a simple "probably
|
40
|
+
guarantee uniqueness" implementation used to guard against things happening
|
41
|
+
multiple times within minutes/hours/days, not milliseconds.
|
42
|
+
|
43
|
+
See: [redlock](https://github.com/antirez/redlock-rb) for an example of distributed locking.
|
44
|
+
|
45
|
+
## Contributing
|
46
|
+
|
47
|
+
1. Fork it
|
48
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
49
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
50
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
51
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/once.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require "once/version"
|
2
|
+
require 'digest'
|
3
|
+
|
4
|
+
# Usage:
|
5
|
+
#
|
6
|
+
# 1. Connect to redis:
|
7
|
+
# Once.redis = Redis.new(...)
|
8
|
+
#
|
9
|
+
# If you don't specify the redis connection, we will assume the presence of a $redis global
|
10
|
+
#
|
11
|
+
# 2. Use to wrap a call that you want done uniquely
|
12
|
+
# Once.do(name: "sending_email", params: { email: "foo@bar.com" }, within: 1.hour) do
|
13
|
+
# .. stuff that should happen only once ..
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# The combination of the name and params makes the check unique. So typically it would be the
|
17
|
+
# command you're executing, plus the params to that command
|
18
|
+
module Once
|
19
|
+
DEFAULT_TIME = 3600 # seconds
|
20
|
+
|
21
|
+
class << self
|
22
|
+
def redis
|
23
|
+
@redis || $redis
|
24
|
+
end
|
25
|
+
|
26
|
+
def redis=(redis)
|
27
|
+
@redis = redis
|
28
|
+
end
|
29
|
+
|
30
|
+
# Checks the given params to see if this is a unique string
|
31
|
+
# If we've seen it within the expiry period (default: 1.hour),
|
32
|
+
# then we will not execute the block
|
33
|
+
#
|
34
|
+
# name: The name of the check, used as a namespace
|
35
|
+
# params: The params that will control whether or not the body executes
|
36
|
+
def do(name:, params:, within: DEFAULT_TIME, &block)
|
37
|
+
hash = Digest::MD5.hexdigest(params.inspect)
|
38
|
+
redis_key = "uniquecheck:#{name}:#{hash}"
|
39
|
+
|
40
|
+
unless redis.exists(redis_key)
|
41
|
+
block.call
|
42
|
+
end
|
43
|
+
|
44
|
+
redis.set(redis_key, true)
|
45
|
+
redis.expire(redis_key, within)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/once/version.rb
ADDED
data/once.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'once/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "once"
|
8
|
+
spec.version = Once::VERSION
|
9
|
+
spec.authors = ["Yan Pritzker"]
|
10
|
+
spec.email = ["yan@reverb.com"]
|
11
|
+
spec.description = %q{Uses Redis to guarantee uniqueness for executing a particular command}
|
12
|
+
spec.summary = %q{Execute commands only once within a specified period of time}
|
13
|
+
spec.homepage = "https://github.com/reverbdotcom/once"
|
14
|
+
spec.license = "Apache License, Version 2.0"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
spec.add_development_dependency "fakeredis"
|
25
|
+
spec.add_development_dependency "timecop"
|
26
|
+
end
|
data/spec/once_spec.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'once'
|
3
|
+
require 'fakeredis/rspec'
|
4
|
+
require 'timecop'
|
5
|
+
|
6
|
+
describe Once do
|
7
|
+
before do
|
8
|
+
Once.redis = Redis.new
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:thing_to_execute) { double(execute: nil) }
|
12
|
+
let(:params) {{foo: "bar"}}
|
13
|
+
|
14
|
+
it "executes the given command" do
|
15
|
+
described_class.do(name: "mycheck", params: params) do
|
16
|
+
thing_to_execute.execute
|
17
|
+
end
|
18
|
+
|
19
|
+
thing_to_execute.should have_received(:execute).once
|
20
|
+
end
|
21
|
+
|
22
|
+
it "executes when called from within different namespaces" do
|
23
|
+
described_class.do(name: "mycheck1", params: params) do
|
24
|
+
thing_to_execute.execute
|
25
|
+
end
|
26
|
+
described_class.do(name: "mycheck2", params: params) do
|
27
|
+
thing_to_execute.execute
|
28
|
+
end
|
29
|
+
|
30
|
+
thing_to_execute.should have_received(:execute).twice
|
31
|
+
end
|
32
|
+
|
33
|
+
it "executes when given different params" do
|
34
|
+
described_class.do(name: "mycheck", params: {foo: "bar"}) do
|
35
|
+
thing_to_execute.execute
|
36
|
+
end
|
37
|
+
described_class.do(name: "mycheck", params: {baz: "quux"}) do
|
38
|
+
thing_to_execute.execute
|
39
|
+
end
|
40
|
+
|
41
|
+
thing_to_execute.should have_received(:execute).twice
|
42
|
+
end
|
43
|
+
|
44
|
+
it "does not execute twice within a period of time" do
|
45
|
+
2.times do
|
46
|
+
described_class.do(name: "mycheck", params: params) do
|
47
|
+
thing_to_execute.execute
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
thing_to_execute.should have_received(:execute).once
|
52
|
+
end
|
53
|
+
|
54
|
+
it "treats different groups of params independently" do
|
55
|
+
# Prevents a bug where we overwrite the uniqueness key
|
56
|
+
# when we were doing a get(key) == hash check instead of
|
57
|
+
# using the hash as part of the unique identifier
|
58
|
+
|
59
|
+
2.times do
|
60
|
+
described_class.do(name: "mycheck", params: {foo: "bar"}) do
|
61
|
+
thing_to_execute.execute
|
62
|
+
end
|
63
|
+
described_class.do(name: "mycheck", params: {baz: "quux"}) do
|
64
|
+
thing_to_execute.execute
|
65
|
+
end
|
66
|
+
described_class.do(name: "mycheck", params: {foo: "bar"}) do
|
67
|
+
thing_to_execute.execute
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
thing_to_execute.should have_received(:execute).twice
|
72
|
+
end
|
73
|
+
|
74
|
+
it "executes again after the time period passes" do
|
75
|
+
described_class.do(name: "mycheck", within: 60, params: params) do
|
76
|
+
thing_to_execute.execute
|
77
|
+
end
|
78
|
+
|
79
|
+
Timecop.travel(DateTime.now + 61) do
|
80
|
+
described_class.do(name: "mycheck", within: 60, params: params) do
|
81
|
+
thing_to_execute.execute
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
thing_to_execute.should have_received(:execute).twice
|
86
|
+
end
|
87
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: once
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Yan Pritzker
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-09-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
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: rspec
|
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: fakeredis
|
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: timecop
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Uses Redis to guarantee uniqueness for executing a particular command
|
84
|
+
email:
|
85
|
+
- yan@reverb.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- ".ruby-version"
|
92
|
+
- Gemfile
|
93
|
+
- LICENSE
|
94
|
+
- README.md
|
95
|
+
- Rakefile
|
96
|
+
- lib/once.rb
|
97
|
+
- lib/once/version.rb
|
98
|
+
- once.gemspec
|
99
|
+
- spec/once_spec.rb
|
100
|
+
- spec/spec_helper.rb
|
101
|
+
homepage: https://github.com/reverbdotcom/once
|
102
|
+
licenses:
|
103
|
+
- Apache License, Version 2.0
|
104
|
+
metadata: {}
|
105
|
+
post_install_message:
|
106
|
+
rdoc_options: []
|
107
|
+
require_paths:
|
108
|
+
- lib
|
109
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
requirements: []
|
120
|
+
rubyforge_project:
|
121
|
+
rubygems_version: 2.4.8
|
122
|
+
signing_key:
|
123
|
+
specification_version: 4
|
124
|
+
summary: Execute commands only once within a specified period of time
|
125
|
+
test_files:
|
126
|
+
- spec/once_spec.rb
|
127
|
+
- spec/spec_helper.rb
|