goalkeeper 0.2 → 0.3.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 +4 -4
- data/.gitignore +2 -0
- data/README.md +29 -3
- data/Rakefile +1 -1
- data/lib/goalkeeper.rb +62 -132
- data/lib/goalkeeper/goal.rb +66 -0
- data/lib/goalkeeper/set.rb +87 -0
- data/lib/goalkeeper/version.rb +2 -2
- data/test/goalkeeper/goal_test.rb +103 -0
- data/test/goalkeeper/set_test.rb +104 -0
- data/test/goalkeeper_test.rb +0 -114
- data/test/test_helper.rb +0 -3
- metadata +8 -3
- data/LICENSE.txt +0 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 131ffcb82da5ac820f63710b94d5139cf249bc9d
|
4
|
+
data.tar.gz: d0ccd33c05701a7b3c5bb669008eb5bea1eedf32
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ced9ac47bf52311689d02ded1b17c59067c9bd55906de495f53a4c3a59cf7242b8b3dce7f0eafb662326c6c8c3ab050d5377718dad07d95c221704fc4e2953d3
|
7
|
+
data.tar.gz: 928f802e2557c64bb046e9eec2769e66c9d4f075d34fc6b8b202111cd7b703693dfa4ac44e2bea08966ab2af33bc543e2a8172c0de9f397e5a60a84c761fc332
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -5,8 +5,7 @@ Goalkeeper is a simple system for tracking if system wide requirements have been
|
|
5
5
|
An example usage would be validating if each customer was sent a daily report.
|
6
6
|
|
7
7
|
|
8
|
-
|
9
|
-
## Installation
|
8
|
+
##### Installation
|
10
9
|
|
11
10
|
Add this line to your application's Gemfile:
|
12
11
|
|
@@ -24,7 +23,34 @@ Or install it yourself as:
|
|
24
23
|
|
25
24
|
## Usage
|
26
25
|
|
27
|
-
|
26
|
+
### Goal
|
27
|
+
| method | description | example |
|
28
|
+
| -------- | ------ | ----- |
|
29
|
+
| new(label) | Creates a new Goal with the unique label | `g = Goal.new('process')` |
|
30
|
+
| met? | Returns true if the Goal has been met | `g.met? #=> false` |
|
31
|
+
| met! | Marks the goal as completed with a timestamp | `g.met! #=> Time.now` |
|
32
|
+
| met\_at | Nil if unmet, otherwise returns the time the Goal was met. | `g.met_at #=> 2015-02-09 22:24:07 -0500` |
|
33
|
+
| ttl | The Redis ttl value if there is a record for the goal. | `g.ttl #=> 86400` |
|
34
|
+
| key | The key used to store the record in Redis. | `g.key #=> "Goalkeeper:process"` |
|
35
|
+
| clear! | Deletes the `met` record. Goal is now unmet. | `g.clear!` |
|
36
|
+
|
37
|
+
|
38
|
+
### Set
|
39
|
+
| method | description | example |
|
40
|
+
| -------- | ------ | ----- |
|
41
|
+
| new | creates an empty set of goals. | `s = set.new` |
|
42
|
+
| add(label) | Adds a new Goal the Set. _chainable_ | `s.add('process.a').add('process.b')` |
|
43
|
+
| met? | Returns true if _all_ the Set's Goals have been met. | `s.met? #=> false` |
|
44
|
+
| met! | Calls `met!` on all Goals. | `s.met! #=> Time.now` |
|
45
|
+
| met\_at | Returns the most recent met_at for the Set's Goals. Nil if no Goal met. | `s.met_at #=> 2015-02-09 22:24:07 -0500` |
|
46
|
+
| clear! | Calls `clear!` on all Goals. | `s.clear!` |
|
47
|
+
| met | Returns a new Set with all Goals which have been met. | `s.met #=> Set(...)` |
|
48
|
+
| unmet | Returns a new Set with all Goals which have not been met. | `s.unmet` #=> Set(...) |
|
49
|
+
|
50
|
+
### Goalkeeper
|
51
|
+
| method | description | example |
|
52
|
+
| -------- | ------ | ----- |
|
53
|
+
| ::met!(label) | Creates a new Goal and marks it met. | `Goalkeeper.met!('process') #=> <Goalkeepr::Goal>`
|
28
54
|
|
29
55
|
## Contributing
|
30
56
|
|
data/Rakefile
CHANGED
data/lib/goalkeeper.rb
CHANGED
@@ -1,27 +1,64 @@
|
|
1
|
-
require "goalkeeper/version"
|
2
1
|
require 'forwardable'
|
3
2
|
require 'redis'
|
4
3
|
require 'time' # for Time.parse
|
5
4
|
|
6
|
-
# Goalkeeper provides methods to track if specific events(Goals) have been
|
5
|
+
# Goalkeeper provides methods to track if specific events(Goals) have been
|
6
|
+
# completed(met).
|
7
7
|
#
|
8
|
-
#
|
9
|
-
#
|
8
|
+
# Its goes likes this..
|
9
|
+
#
|
10
|
+
# Lets ensure we wakeup New Years Day 2020. The goal will be called 'wakeup:2020-01-01'
|
11
|
+
# g = Goalkeeper::Goal.new('wakeup:2020-01-01')
|
12
|
+
# g.met? #=> false
|
10
13
|
#
|
11
|
-
#
|
12
|
-
#
|
14
|
+
# Time flies... it is New Years Day 2020.
|
15
|
+
# g.met! # or Goalkeeper.met!('wakeup:2020-01-01')
|
16
|
+
# g.met? #=> true
|
17
|
+
# g.met_at #=> 2020-01-01 05:01:31 -0500
|
13
18
|
#
|
14
|
-
#
|
15
|
-
#
|
19
|
+
# Now if our application checks our goal, it will be met.
|
20
|
+
# Goalkeeper::Goal.new('wakeup:2020-01-01').met? #=> true
|
21
|
+
# Goalkeeper.met?('wakeup:2020-01-01') #=> true
|
22
|
+
#
|
23
|
+
# Note: Once a Goal is 'met' the 'met_at' timestamp will not change, unless
|
24
|
+
# 'clear!' is called.
|
16
25
|
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
# # or
|
21
|
-
# Goalkeeper::Goal.new("jobkey").met!
|
26
|
+
# We are probably only interested in this goal being complete for a limited
|
27
|
+
# time, so it will expire and be removed from Redis.
|
28
|
+
# g.ttl #=> 86400 (1 day)
|
22
29
|
#
|
23
|
-
#
|
24
|
-
# Goalkeeper
|
30
|
+
# If you need to reference the Redis key
|
31
|
+
# g.key #=> Goalkeeper:wakeup:2020-01-01
|
32
|
+
#
|
33
|
+
# Finally clear the Goal is simple
|
34
|
+
# g.clear!
|
35
|
+
#
|
36
|
+
# === Sets
|
37
|
+
#
|
38
|
+
# Perhaps you have a series of Goals you want to track, and see if they all have been met, or not.
|
39
|
+
#
|
40
|
+
# set = Goalkeeper::Set.new
|
41
|
+
# set.add('goal1').add('goal2')
|
42
|
+
# set.met? #=> false
|
43
|
+
#
|
44
|
+
# Lets have 1 goal met:
|
45
|
+
# Goalkeeper.met!('goal1')
|
46
|
+
#
|
47
|
+
# But our set is not met yet
|
48
|
+
# set.met? #=> false
|
49
|
+
#
|
50
|
+
# See which goals are met, or unmet
|
51
|
+
# set.met #=> [#<Goalkeeper::Goal @label="goal1">]
|
52
|
+
# set.unmet #=> [#<Goalkeeper::Goal @label="goal2">]
|
53
|
+
#
|
54
|
+
# Lets complete our set.
|
55
|
+
# Goalkeeper.met!('goal1')
|
56
|
+
# set.met? #=> true
|
57
|
+
#
|
58
|
+
# See the time the final goal was met
|
59
|
+
# set.met_at #=> 2015-01-01 08:02:15 -0500
|
60
|
+
#
|
61
|
+
# === Customization
|
25
62
|
#
|
26
63
|
# Customize the redis client by setting it in your application
|
27
64
|
# Goalkeeper.redis = your_redis_client
|
@@ -29,11 +66,14 @@ require 'time' # for Time.parse
|
|
29
66
|
# Each record has a default expiration of 24 hours, but this can be modified.
|
30
67
|
# Goalkeeper.expiration = number_of_seconds
|
31
68
|
#
|
32
|
-
# Redis keys are stored under the default namespace of "Goalkeeper:". The
|
33
|
-
#
|
69
|
+
# Redis keys are stored under the default namespace of "Goalkeeper:". The
|
70
|
+
# namespace can be configured:
|
34
71
|
# Goalkeeper.namespace = string
|
35
72
|
#
|
36
|
-
|
73
|
+
module Goalkeeper
|
74
|
+
require "goalkeeper/version"
|
75
|
+
require "goalkeeper/goal"
|
76
|
+
require "goalkeeper/set"
|
37
77
|
|
38
78
|
# Set the Redis client to a non default setting
|
39
79
|
def self.redis=(redis)
|
@@ -49,6 +89,10 @@ class Goalkeeper
|
|
49
89
|
Goal.new(label).met!
|
50
90
|
end
|
51
91
|
|
92
|
+
def self.met?(label)
|
93
|
+
Goal.new(label).met?
|
94
|
+
end
|
95
|
+
|
52
96
|
# The TTL set for each met Goal record created in Redis
|
53
97
|
# Default is 24 hours
|
54
98
|
def self.expiration
|
@@ -67,118 +111,4 @@ class Goalkeeper
|
|
67
111
|
def self.namespace=(ns)
|
68
112
|
@namespace = ns
|
69
113
|
end
|
70
|
-
|
71
|
-
# List is a collection of Goals to simplify tracking multiple goals.
|
72
|
-
#
|
73
|
-
# Create a new list
|
74
|
-
# mylist = Goalkeeper::List.new
|
75
|
-
#
|
76
|
-
# Add Goals you want to check for completion
|
77
|
-
# mylist.add("job1").add("job2")
|
78
|
-
# mylist.size
|
79
|
-
# #=> 2
|
80
|
-
#
|
81
|
-
# Check if all the goals are completed
|
82
|
-
# mylist.met?
|
83
|
-
# #=> false
|
84
|
-
#
|
85
|
-
# Get the unmet Goals
|
86
|
-
# mylist.unmet
|
87
|
-
# #=> [...]
|
88
|
-
#
|
89
|
-
# Get the met Goals
|
90
|
-
# mylist.met
|
91
|
-
# #=> [...]
|
92
|
-
#
|
93
|
-
# Iterate all Goals
|
94
|
-
# myslist.each {|goal| ...}
|
95
|
-
# myslist.map {|goal| ...}
|
96
|
-
class List
|
97
|
-
extend Forwardable
|
98
|
-
def_delegators :@list, :size, :[], :each, :map
|
99
|
-
|
100
|
-
def initialize
|
101
|
-
@list = []
|
102
|
-
end
|
103
|
-
|
104
|
-
# Creates a new Goal.
|
105
|
-
# see Goal#new for usage
|
106
|
-
def add(label, ref: nil)
|
107
|
-
@list.push(Goal.new(label, ref: ref))
|
108
|
-
self
|
109
|
-
end
|
110
|
-
|
111
|
-
# met? returns true if all Goals in the set have been met.
|
112
|
-
def met?
|
113
|
-
unmet.empty?
|
114
|
-
end
|
115
|
-
|
116
|
-
def unmet
|
117
|
-
@list.select {|g| ! g.met?}
|
118
|
-
end
|
119
|
-
|
120
|
-
def met
|
121
|
-
@list.select {|g| g.met?}
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
class Goal
|
126
|
-
# The unique label to identify this Goal
|
127
|
-
attr_reader :label
|
128
|
-
|
129
|
-
# An optional object refrence which allows an application author to
|
130
|
-
# associate this goal to an object. The +ref+ is not used by Goalkeeper.
|
131
|
-
attr_reader :ref
|
132
|
-
|
133
|
-
# the TTL value for the Redis record. Defalts to Goalkeeper.expiration
|
134
|
-
attr_reader :expiration
|
135
|
-
|
136
|
-
# +label+ is a unique string to identify this Goal.
|
137
|
-
# There is no checking if it is truly unique.
|
138
|
-
#
|
139
|
-
# +ref+ is an optional reference to any object. This
|
140
|
-
# would be used by the end user's application.
|
141
|
-
#
|
142
|
-
# +expiration+ can be set to override the gobal expiratin.
|
143
|
-
def initialize(label, ref: nil, expiration: Goalkeeper.expiration)
|
144
|
-
@label = label
|
145
|
-
@ref = ref
|
146
|
-
@expiration = expiration
|
147
|
-
end
|
148
|
-
|
149
|
-
def met!
|
150
|
-
write
|
151
|
-
self
|
152
|
-
end
|
153
|
-
|
154
|
-
def met?
|
155
|
-
! read.nil?
|
156
|
-
end
|
157
|
-
|
158
|
-
# Time the goal was completed.
|
159
|
-
# WARNING retuns nil if the job is not met
|
160
|
-
def met_at
|
161
|
-
if met?
|
162
|
-
Time.parse(read)
|
163
|
-
else
|
164
|
-
nil
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
# a namespaced key for the goal
|
169
|
-
def key
|
170
|
-
"#{Goalkeeper.namespace}:#{label}"
|
171
|
-
end
|
172
|
-
|
173
|
-
protected
|
174
|
-
|
175
|
-
def write
|
176
|
-
Goalkeeper.redis.set(self.key, Time.now)
|
177
|
-
Goalkeeper.redis.expire(self.key, self.expiration)
|
178
|
-
end
|
179
|
-
|
180
|
-
def read
|
181
|
-
Goalkeeper.redis.get self.key
|
182
|
-
end
|
183
|
-
end
|
184
114
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Goalkeeper
|
2
|
+
# Goal represents a label which has either been +met+ or not.
|
3
|
+
#
|
4
|
+
class Goal
|
5
|
+
# The unique label to identify this Goal. There is no logic to check that the
|
6
|
+
# label is unique.
|
7
|
+
attr_reader :label
|
8
|
+
|
9
|
+
# the TTL value for the Redis record. Defalts to Goalkeeper.expiration
|
10
|
+
attr_reader :expiration
|
11
|
+
|
12
|
+
# +label+ is a unique string to identify this Goal.
|
13
|
+
# +expiration+ number secconds. This can be set to override the gobal expiration.
|
14
|
+
def initialize(label, expiration: Goalkeeper.expiration)
|
15
|
+
@label = label
|
16
|
+
@expiration = expiration
|
17
|
+
end
|
18
|
+
|
19
|
+
def met!
|
20
|
+
write unless met?
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def met?
|
25
|
+
!read.nil?
|
26
|
+
end
|
27
|
+
|
28
|
+
# Time the goal was completed.
|
29
|
+
# WARNING retuns nil if the job is not met
|
30
|
+
def met_at
|
31
|
+
return Time.parse(read) if met?
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
|
35
|
+
# clear! removes the met state of the Goal.
|
36
|
+
def clear!
|
37
|
+
Goalkeeper.redis.del(key)
|
38
|
+
end
|
39
|
+
|
40
|
+
# a namespaced key for the goal
|
41
|
+
def key
|
42
|
+
"#{Goalkeeper.namespace}:#{label}"
|
43
|
+
end
|
44
|
+
|
45
|
+
# ttl returns the time to live on the redis key
|
46
|
+
def ttl
|
47
|
+
Goalkeeper.redis.ttl(key)
|
48
|
+
end
|
49
|
+
|
50
|
+
# All Goalkeeper::Goals with the same label are equal
|
51
|
+
def ==(other)
|
52
|
+
other.is_a?(Goalkeeper::Goal) && other.label == label
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
|
57
|
+
def write
|
58
|
+
Goalkeeper.redis.set(key, Time.now)
|
59
|
+
Goalkeeper.redis.expire(key, expiration)
|
60
|
+
end
|
61
|
+
|
62
|
+
def read
|
63
|
+
Goalkeeper.redis.get(key)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Goalkeeper
|
2
|
+
# Set is an Array of Goals to simplify tracking multiple goals.
|
3
|
+
# Since Set is an array, you have all Array methods available.
|
4
|
+
#
|
5
|
+
# Create a new set
|
6
|
+
# myset = Goalkeeper::Set.new
|
7
|
+
#
|
8
|
+
# Add Goals you want to check for completion
|
9
|
+
# myset.add("job1").add("job2")
|
10
|
+
# myset.size
|
11
|
+
# #=> 2
|
12
|
+
#
|
13
|
+
# Check if all the goals are completed
|
14
|
+
# myset.met?
|
15
|
+
# #=> false
|
16
|
+
#
|
17
|
+
# Get the unmet Goals
|
18
|
+
# myset.unmet
|
19
|
+
# #=> [...]
|
20
|
+
#
|
21
|
+
# Get the met Goals
|
22
|
+
# myset.met
|
23
|
+
# #=> [...]
|
24
|
+
#
|
25
|
+
# Iterate all Goals
|
26
|
+
# myset.each {|goal| ...}
|
27
|
+
# myset.map {|goal| ...}
|
28
|
+
class Set < Array
|
29
|
+
def initialize
|
30
|
+
super
|
31
|
+
end
|
32
|
+
|
33
|
+
# Creates a new Goal.
|
34
|
+
# see Goal#initialize for usage
|
35
|
+
def add(*args)
|
36
|
+
push(Goal.new(*args))
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
# met? returns true if all Goals in the set have been met.
|
41
|
+
def met?
|
42
|
+
unmet.empty?
|
43
|
+
end
|
44
|
+
|
45
|
+
# unmet returns a Set with the all the Goals that have not been met.
|
46
|
+
def unmet
|
47
|
+
subset(select { |g| !g.met? })
|
48
|
+
end
|
49
|
+
|
50
|
+
# met returns a Set with the all the Goals that have been met.
|
51
|
+
def met
|
52
|
+
subset(select { |g| g.met? })
|
53
|
+
end
|
54
|
+
|
55
|
+
# nil if this set is not met?
|
56
|
+
# otherwise returns the met_at Time for the last met goal
|
57
|
+
def met_at
|
58
|
+
if met?
|
59
|
+
self.map(&:met_at).sort.last
|
60
|
+
else
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def <<(other)
|
66
|
+
if other.is_a?(Goal) && !include?(other)
|
67
|
+
super
|
68
|
+
else
|
69
|
+
false
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def push(*others)
|
74
|
+
others.each do |o|
|
75
|
+
self << o
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
protected
|
80
|
+
|
81
|
+
def subset(set)
|
82
|
+
Goalkeeper::Set.new.tap do |s|
|
83
|
+
set.each {|i| s << i}
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/lib/goalkeeper/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
|
2
|
-
VERSION = "0.
|
1
|
+
module Goalkeeper
|
2
|
+
VERSION = "0.3.0"
|
3
3
|
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Goalkeeper::Goal do
|
4
|
+
before do
|
5
|
+
Goalkeeper.redis.flushdb
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:goal) { Goalkeeper::Goal.new("test") }
|
9
|
+
|
10
|
+
it "has a label" do
|
11
|
+
assert_equal "test", goal.label
|
12
|
+
end
|
13
|
+
|
14
|
+
it "has a namespaced key" do
|
15
|
+
assert_equal "Goalkeeper:test", goal.key
|
16
|
+
end
|
17
|
+
|
18
|
+
it "is met? if the label has a Redis record" do
|
19
|
+
assert !goal.met?
|
20
|
+
Goalkeeper.redis.set goal.key, Time.now
|
21
|
+
assert goal.met?
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "met_at" do
|
25
|
+
it "is nil if the Goal is not met" do
|
26
|
+
assert_equal nil, goal.met_at
|
27
|
+
end
|
28
|
+
|
29
|
+
it "is the timestamp that the Goal was met" do
|
30
|
+
@t = Time.parse(Time.now.to_s)
|
31
|
+
goal.met!
|
32
|
+
assert_equal @t, goal.met_at
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#met!" do
|
37
|
+
it "creates a timestamp record with the Goal's key" do
|
38
|
+
Time.stub(:now, 'timestamp') do
|
39
|
+
assert_equal nil, Goalkeeper.redis.get(goal.key)
|
40
|
+
goal.met!
|
41
|
+
assert_equal 'timestamp', Goalkeeper.redis.get(goal.key)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it "has a default ttl expiration" do
|
46
|
+
goal.met!
|
47
|
+
assert_equal goal.expiration, Goalkeeper.redis.ttl(goal.key)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "does nothing if the goal is already met" do
|
51
|
+
t = Time.now
|
52
|
+
Time.stub(:now, t) do
|
53
|
+
goal.met!
|
54
|
+
assert_equal Time.parse(t.to_s), goal.met_at
|
55
|
+
end
|
56
|
+
|
57
|
+
Time.stub(:now, t + 1000) do
|
58
|
+
goal.met!
|
59
|
+
assert_equal Time.parse(t.to_s), goal.met_at
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "#clear!" do
|
65
|
+
it "removes the met state of the Goal" do
|
66
|
+
goal.met!
|
67
|
+
assert_equal true, goal.met?
|
68
|
+
goal.clear!
|
69
|
+
assert_equal false, goal.met?
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "#expiration" do
|
74
|
+
it "has a default of 24 hours" do
|
75
|
+
assert_equal 24 * 60 * 60, goal.expiration
|
76
|
+
end
|
77
|
+
|
78
|
+
it "can be set at initialization" do
|
79
|
+
g = Goalkeeper::Goal.new("x", expiration: 60)
|
80
|
+
assert_equal 60, g.expiration
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "equality" do
|
85
|
+
it "should be true when the labels are the same" do
|
86
|
+
a = Goalkeeper::Goal.new("a")
|
87
|
+
b = Goalkeeper::Goal.new("b")
|
88
|
+
a2 = Goalkeeper::Goal.new("a")
|
89
|
+
|
90
|
+
assert_equal a, a2
|
91
|
+
assert a != b
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "#ttl" do
|
96
|
+
it "returns the ttl on the Redis record" do
|
97
|
+
a = Goalkeeper::Goal.new("a")
|
98
|
+
assert_equal(-2, a.ttl)
|
99
|
+
a.met!
|
100
|
+
assert_equal Goalkeeper.expiration, a.ttl
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Goalkeeper::Set do
|
4
|
+
before do
|
5
|
+
Goalkeeper.redis.flushdb
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:goals) { Goalkeeper::Set.new }
|
9
|
+
|
10
|
+
describe "#add" do
|
11
|
+
it "creates a Goal from the label" do
|
12
|
+
goals.add("a:1")
|
13
|
+
assert_equal 1, goals.size
|
14
|
+
assert_equal "a:1", goals[0].label
|
15
|
+
end
|
16
|
+
|
17
|
+
it "accepts an option expiration" do
|
18
|
+
goals.add("a:1", expiration: 20)
|
19
|
+
assert_equal 20, goals.first.expiration
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should return itself (so it is chainable)" do
|
23
|
+
assert_equal goals, goals.add("a:1")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it "behaves as a unique set" do
|
28
|
+
goals << Goalkeeper::Goal.new("a")
|
29
|
+
goals << Goalkeeper::Goal.new("b")
|
30
|
+
goals << Goalkeeper::Goal.new("a")
|
31
|
+
goals.push Goalkeeper::Goal.new("a")
|
32
|
+
|
33
|
+
assert_equal 2, goals.size
|
34
|
+
end
|
35
|
+
|
36
|
+
it "ignores insertion of nonGoals" do
|
37
|
+
goals << "a"
|
38
|
+
goals.push 1, 2, :a
|
39
|
+
assert_equal 0, goals.size
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "with goals" do
|
43
|
+
before do
|
44
|
+
goals.add("x").add("y")
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "#met" do
|
48
|
+
it "returns all Goals which have been met" do
|
49
|
+
assert goals.met.empty?
|
50
|
+
goals[0].met!
|
51
|
+
assert_equal ["x"], goals.met.map(&:label)
|
52
|
+
goals[1].met!
|
53
|
+
assert_equal(%w( x y ), goals.met.map(&:label))
|
54
|
+
|
55
|
+
assert goals.met.is_a?(Goalkeeper::Set)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "#unmet" do
|
60
|
+
it "returns all Goals which have not been met" do
|
61
|
+
assert_equal(%w( x y ), goals.unmet.map(&:label))
|
62
|
+
goals[0].met!
|
63
|
+
assert_equal ["y"], goals.unmet.map(&:label)
|
64
|
+
goals[1].met!
|
65
|
+
assert goals.unmet.empty?
|
66
|
+
|
67
|
+
assert goals.unmet.is_a?(Goalkeeper::Set)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "#met?" do
|
72
|
+
it "is true when all Goals have been met" do
|
73
|
+
assert !goals.met?
|
74
|
+
goals.each(&:met!)
|
75
|
+
assert goals.met?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "#met_at" do
|
80
|
+
before do
|
81
|
+
goals << Goalkeeper::Goal.new("a")
|
82
|
+
goals << Goalkeeper::Goal.new("b")
|
83
|
+
goals << Goalkeeper::Goal.new("a")
|
84
|
+
end
|
85
|
+
|
86
|
+
it "is nil unless all Goals are met" do
|
87
|
+
goals[0].met!
|
88
|
+
goals[1].met!
|
89
|
+
assert_equal nil, goals.met_at
|
90
|
+
end
|
91
|
+
|
92
|
+
it "is the most recent met_at from the Goals" do
|
93
|
+
t = Time.now
|
94
|
+
Time.stub(:now, t + 1000) do
|
95
|
+
goals.first.met!
|
96
|
+
end
|
97
|
+
# meet the reset
|
98
|
+
goals.each &:met!
|
99
|
+
|
100
|
+
assert_equal((t + 1000).to_a, goals.met_at.to_a)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
data/test/goalkeeper_test.rb
CHANGED
@@ -28,118 +28,4 @@ describe Goalkeeper do
|
|
28
28
|
Goalkeeper.namespace = ns
|
29
29
|
end
|
30
30
|
end
|
31
|
-
|
32
|
-
describe Goalkeeper::List do
|
33
|
-
before do
|
34
|
-
@goals = Goalkeeper::List.new
|
35
|
-
end
|
36
|
-
|
37
|
-
describe "#add" do
|
38
|
-
it "creates a Goal" do
|
39
|
-
@goals.add("a:1")
|
40
|
-
assert_equal 1, @goals.size
|
41
|
-
assert_equal "a:1", @goals[0].label
|
42
|
-
end
|
43
|
-
|
44
|
-
it "accepts an optional reference object" do
|
45
|
-
o = Object.new
|
46
|
-
@goals.add("a:1", ref: o)
|
47
|
-
assert_equal o, @goals[0].ref
|
48
|
-
end
|
49
|
-
|
50
|
-
it "should return itself (so it is chainable)" do
|
51
|
-
assert_equal @goals, @goals.add("a:1")
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
describe "with goals" do
|
56
|
-
before do
|
57
|
-
@goals.add("x").add("y")
|
58
|
-
end
|
59
|
-
|
60
|
-
describe "#met" do
|
61
|
-
it "returns all Goals which have been met" do
|
62
|
-
assert @goals.met.empty?
|
63
|
-
@goals[0].met!
|
64
|
-
assert_equal ["x"], @goals.met.map(&:label)
|
65
|
-
@goals[1].met!
|
66
|
-
assert_equal ["x","y"], @goals.met.map(&:label)
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
describe "#unmet" do
|
71
|
-
it "returns all Goals which have not been met" do
|
72
|
-
assert_equal ["x","y"], @goals.unmet.map(&:label)
|
73
|
-
@goals[0].met!
|
74
|
-
assert_equal ["y"], @goals.unmet.map(&:label)
|
75
|
-
@goals[1].met!
|
76
|
-
assert @goals.unmet.empty?
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
describe "#met?" do
|
81
|
-
it "is true when all Goals have been met" do
|
82
|
-
assert ! @goals.met?
|
83
|
-
@goals.each(&:met!)
|
84
|
-
assert @goals.met?
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
describe Goalkeeper::Goal do
|
91
|
-
before do
|
92
|
-
@goal = Goalkeeper::Goal.new("b")
|
93
|
-
end
|
94
|
-
|
95
|
-
it "has a label" do
|
96
|
-
assert_equal "b", @goal.label
|
97
|
-
end
|
98
|
-
|
99
|
-
it "has a namespaced key" do
|
100
|
-
assert_equal "Goalkeeper:b", @goal.key
|
101
|
-
end
|
102
|
-
|
103
|
-
it "is met? if the label has a Redis record" do
|
104
|
-
assert ! @goal.met?
|
105
|
-
Goalkeeper.redis.set @goal.key, Time.now
|
106
|
-
assert @goal.met?
|
107
|
-
end
|
108
|
-
|
109
|
-
describe "met_at" do
|
110
|
-
it "is nil if the Goal is not met" do
|
111
|
-
assert_equal nil, @goal.met_at
|
112
|
-
end
|
113
|
-
|
114
|
-
it "is the timestamp that the Goal was met" do
|
115
|
-
@t = Time.parse(Time.now.to_s)
|
116
|
-
@goal.met!
|
117
|
-
assert_equal @t, @goal.met_at
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
describe "#met!" do
|
122
|
-
it "creates a Redis record" do
|
123
|
-
assert Goalkeeper.redis.get(@goal.key).nil?
|
124
|
-
@goal.met!
|
125
|
-
assert ! Goalkeeper.redis.get(@goal.key).nil?
|
126
|
-
end
|
127
|
-
|
128
|
-
it "has a default ttl expiration" do
|
129
|
-
@goal.met!
|
130
|
-
assert_equal @goal.expiration, Goalkeeper.redis.ttl(@goal.key)
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
describe "#expiration" do
|
135
|
-
it "has a default of 24 hours" do
|
136
|
-
assert_equal 24 * 60 * 60, @goal.expiration
|
137
|
-
end
|
138
|
-
|
139
|
-
it "can be set at initialization" do
|
140
|
-
g = Goalkeeper::Goal.new("x", expiration: 60)
|
141
|
-
assert_equal 60, g.expiration
|
142
|
-
end
|
143
|
-
end
|
144
|
-
end
|
145
31
|
end
|
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: goalkeeper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Weir
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-02-
|
11
|
+
date: 2015-02-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|
@@ -63,12 +63,15 @@ files:
|
|
63
63
|
- ".gitignore"
|
64
64
|
- Gemfile
|
65
65
|
- LICENSE
|
66
|
-
- LICENSE.txt
|
67
66
|
- README.md
|
68
67
|
- Rakefile
|
69
68
|
- goalkeeper.gemspec
|
70
69
|
- lib/goalkeeper.rb
|
70
|
+
- lib/goalkeeper/goal.rb
|
71
|
+
- lib/goalkeeper/set.rb
|
71
72
|
- lib/goalkeeper/version.rb
|
73
|
+
- test/goalkeeper/goal_test.rb
|
74
|
+
- test/goalkeeper/set_test.rb
|
72
75
|
- test/goalkeeper_test.rb
|
73
76
|
- test/support/redis_instance.rb
|
74
77
|
- test/test_helper.rb
|
@@ -97,6 +100,8 @@ signing_key:
|
|
97
100
|
specification_version: 4
|
98
101
|
summary: A Todo App for your application.
|
99
102
|
test_files:
|
103
|
+
- test/goalkeeper/goal_test.rb
|
104
|
+
- test/goalkeeper/set_test.rb
|
100
105
|
- test/goalkeeper_test.rb
|
101
106
|
- test/support/redis_instance.rb
|
102
107
|
- test/test_helper.rb
|
data/LICENSE.txt
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
Copyright (c) 2015 John Weir
|
2
|
-
|
3
|
-
MIT License
|
4
|
-
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
-
a copy of this software and associated documentation files (the
|
7
|
-
"Software"), to deal in the Software without restriction, including
|
8
|
-
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
-
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
-
permit persons to whom the Software is furnished to do so, subject to
|
11
|
-
the following conditions:
|
12
|
-
|
13
|
-
The above copyright notice and this permission notice shall be
|
14
|
-
included in all copies or substantial portions of the Software.
|
15
|
-
|
16
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
-
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
-
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|