minuteman 0.1.1 → 0.2.0.pre
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.
- data/.travis.yml +7 -0
- data/Gemfile.lock +1 -1
- data/README.md +21 -3
- data/lib/minuteman.rb +1 -1
- data/lib/minuteman/bit_operations.rb +23 -37
- data/lib/minuteman/bit_operations/data.rb +32 -0
- data/lib/minuteman/bit_operations/operation.rb +44 -0
- data/lib/minuteman/bit_operations/plain.rb +29 -0
- data/lib/minuteman/bit_operations/result.rb +14 -0
- data/lib/minuteman/bit_operations/with_data.rb +33 -0
- data/lib/minuteman/keys_methods.rb +21 -0
- data/minuteman.gemspec +1 -1
- data/test/unit/minuteman_test.rb +23 -1
- metadata +12 -5
data/.travis.yml
ADDED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# Minuteman
|
2
|
+
[](https://codeclimate.com/github/elcuervo/minuteman)
|
2
3
|
|
3
4
|
_Wikipedia_: Minutemen were members of teams from Massachusetts that were well-prepared
|
4
5
|
militia companies of select men from the American colonial partisan militia
|
@@ -67,7 +68,7 @@ analytics.mark("login:successful", user.id)
|
|
67
68
|
analytics.mark("login:successful", other_user.id)
|
68
69
|
|
69
70
|
# Mark in bulk
|
70
|
-
analytics.mark("programming:love:ruby", User.where(favorite: "ruby").
|
71
|
+
analytics.mark("programming:love:ruby", User.where(favorite: "ruby").pluck(:id))
|
71
72
|
|
72
73
|
# Fetch events for a given time
|
73
74
|
today_events = analytics.day("login:successful", Time.now.utc)
|
@@ -95,7 +96,7 @@ today_events.include?(admin.id)
|
|
95
96
|
#=> false
|
96
97
|
|
97
98
|
# Bulk check
|
98
|
-
today_events.include?(User.all.
|
99
|
+
today_events.include?(User.all.pluck(:id))
|
99
100
|
#=> [true, true, false, false]
|
100
101
|
```
|
101
102
|
|
@@ -112,10 +113,27 @@ set1 | set2
|
|
112
113
|
set1 ^ set2
|
113
114
|
|
114
115
|
~set1 \
|
115
|
-
|
116
|
+
|==> This are the same
|
116
117
|
-set1 /
|
117
118
|
```
|
118
119
|
|
120
|
+
### Intersecting with arrays
|
121
|
+
|
122
|
+
Let's assume this scenario:
|
123
|
+
|
124
|
+
You have a list of users and want to know which of them have been going throught
|
125
|
+
some of the marks you made.
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
paid = analytics.month("buy:complete")
|
129
|
+
payed_from_miami = paid & User.find_all_by_state("MIA").map(&:id)
|
130
|
+
payed_from_miami.size
|
131
|
+
#=> 43
|
132
|
+
payed_users_from_miami = payed_from_miami.map { |id| User.find(id) }
|
133
|
+
```
|
134
|
+
|
135
|
+
Currently the supported commands to interact with arrays are `&` and `-`
|
136
|
+
|
119
137
|
### Example
|
120
138
|
|
121
139
|
```ruby
|
data/lib/minuteman.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
|
+
require "minuteman/bit_operations/operation"
|
2
|
+
|
1
3
|
class Minuteman
|
2
4
|
module BitOperations
|
3
|
-
BIT_OPERATION_PREFIX = "bitop"
|
4
|
-
|
5
5
|
# Public: Checks for the existance of ids on a given set
|
6
6
|
#
|
7
7
|
# ids - Array of ids
|
8
8
|
#
|
9
9
|
def include?(*ids)
|
10
|
-
result = ids.map { |id|
|
10
|
+
result = ids.map { |id| getbit(id) }
|
11
11
|
result.size == 1 ? result.first : result
|
12
12
|
end
|
13
13
|
|
@@ -26,14 +26,16 @@ class Minuteman
|
|
26
26
|
# Public: Calculates the NOT of the current key
|
27
27
|
#
|
28
28
|
def -@
|
29
|
-
|
29
|
+
operation("NOT", key)
|
30
30
|
end
|
31
31
|
alias :~@ :-@
|
32
32
|
|
33
33
|
# Public: Calculates the substract of one set to another
|
34
34
|
#
|
35
|
+
# timespan: Another BitOperations enabled class
|
36
|
+
#
|
35
37
|
def -(timespan)
|
36
|
-
|
38
|
+
operation("MINUS", timespan)
|
37
39
|
end
|
38
40
|
|
39
41
|
# Public: Calculates the XOR against another timespan
|
@@ -41,59 +43,43 @@ class Minuteman
|
|
41
43
|
# timespan: Another BitOperations enabled class
|
42
44
|
#
|
43
45
|
def ^(timespan)
|
44
|
-
|
46
|
+
operation("XOR", timespan)
|
45
47
|
end
|
46
48
|
|
47
49
|
# Public: Calculates the OR against another timespan
|
48
50
|
#
|
49
|
-
# timespan: Another BitOperations enabled class
|
51
|
+
# timespan: Another BitOperations enabled class or an Array
|
50
52
|
#
|
51
53
|
def |(timespan)
|
52
|
-
|
54
|
+
operation("OR", timespan)
|
53
55
|
end
|
54
56
|
alias :+ :|
|
55
57
|
|
56
58
|
# Public: Calculates the AND against another timespan
|
57
59
|
#
|
58
|
-
# timespan: Another BitOperations enabled class
|
60
|
+
# timespan: Another BitOperations enabled class or an Array
|
59
61
|
#
|
60
62
|
def &(timespan)
|
61
|
-
|
63
|
+
operation("AND", timespan)
|
62
64
|
end
|
63
65
|
|
64
66
|
private
|
65
67
|
|
66
|
-
# Private:
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
|
71
|
-
|
72
|
-
[
|
73
|
-
Minuteman::PREFIX,
|
74
|
-
BIT_OPERATION_PREFIX,
|
75
|
-
type,
|
76
|
-
events.join("-")
|
77
|
-
].join("_")
|
68
|
+
# Private: Helper to access the value a given bit
|
69
|
+
#
|
70
|
+
# id: The bit
|
71
|
+
#
|
72
|
+
def getbit(id)
|
73
|
+
redis.getbit(key, id) == 1
|
78
74
|
end
|
79
75
|
|
80
|
-
# Private:
|
76
|
+
# Private: executes an operation between the current timespan and another
|
81
77
|
#
|
82
|
-
# type
|
83
|
-
#
|
78
|
+
# type: The operation type
|
79
|
+
# timespan: The given timespan
|
84
80
|
#
|
85
|
-
def
|
86
|
-
|
87
|
-
redis.bitop(type, key, events)
|
88
|
-
BitOperation.new(redis, key)
|
81
|
+
def operation(type, timespan)
|
82
|
+
Operation.new(redis, type, self, timespan).call
|
89
83
|
end
|
90
84
|
end
|
91
|
-
|
92
|
-
# Public: The result of intersecting results
|
93
|
-
# redis - The Redis connection
|
94
|
-
# key - The key where the result it's stored
|
95
|
-
#
|
96
|
-
class BitOperation < Struct.new(:redis, :key)
|
97
|
-
include BitOperations
|
98
|
-
end
|
99
85
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require "minuteman/bit_operations"
|
2
|
+
|
3
|
+
class Minuteman
|
4
|
+
module BitOperations
|
5
|
+
# Public: The conversion of an array to an operable class
|
6
|
+
#
|
7
|
+
# redis - The Redis connection
|
8
|
+
# key - The key where the result it's stored
|
9
|
+
# data - The original data of the intersection
|
10
|
+
#
|
11
|
+
class Data < Struct.new(:redis, :key, :data)
|
12
|
+
include BitOperations
|
13
|
+
include Enumerable
|
14
|
+
|
15
|
+
def to_ary
|
16
|
+
data
|
17
|
+
end
|
18
|
+
|
19
|
+
def size
|
20
|
+
data.size
|
21
|
+
end
|
22
|
+
|
23
|
+
def each(&block)
|
24
|
+
data.each(&block)
|
25
|
+
end
|
26
|
+
|
27
|
+
def ==(other)
|
28
|
+
other == data
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "minuteman/bit_operations/plain"
|
2
|
+
require "minuteman/bit_operations/with_data"
|
3
|
+
|
4
|
+
class Minuteman
|
5
|
+
module BitOperations
|
6
|
+
# Public: Handles the operations between two timespans
|
7
|
+
#
|
8
|
+
# redis: The Redis connection
|
9
|
+
# type: The operation type
|
10
|
+
# timespan: One of the timespans to be permuted
|
11
|
+
# other: The other timespan to be permuted
|
12
|
+
#
|
13
|
+
class Operation < Struct.new(:redis, :type, :timespan, :other)
|
14
|
+
def call
|
15
|
+
if type == "MINUS" && operable?
|
16
|
+
return timespan ^ (timespan & other)
|
17
|
+
end
|
18
|
+
|
19
|
+
klass.new(redis, type, other, timespan.key).call
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# Private: returns the class to use for the operation
|
25
|
+
#
|
26
|
+
# timespan: The given timespan
|
27
|
+
#
|
28
|
+
def klass
|
29
|
+
case true
|
30
|
+
when other.is_a?(Array) then WithData
|
31
|
+
when other.is_a?(String), operable? then Plain
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Private: Checks if a timespan is operable
|
36
|
+
#
|
37
|
+
# timespan: The given timespan
|
38
|
+
#
|
39
|
+
def operable?
|
40
|
+
other.class.ancestors.include?(BitOperations)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "minuteman/keys_methods"
|
2
|
+
require "minuteman/bit_operations/result"
|
3
|
+
|
4
|
+
class Minuteman
|
5
|
+
module BitOperations
|
6
|
+
# Public: The class to handle operations with others timespans
|
7
|
+
#
|
8
|
+
# redis: The Redis connection
|
9
|
+
# type: The operation type
|
10
|
+
# timespan: The timespan to be permuted
|
11
|
+
# source_key: The original key to do the operation
|
12
|
+
#
|
13
|
+
class Plain < Struct.new(:redis, :type, :timespan, :source_key)
|
14
|
+
include KeysMethods
|
15
|
+
|
16
|
+
def call
|
17
|
+
events = if source_key == timespan
|
18
|
+
Array(source_key)
|
19
|
+
else
|
20
|
+
[source_key, timespan.key]
|
21
|
+
end
|
22
|
+
|
23
|
+
key = destination_key(type, events)
|
24
|
+
redis.bitop(type, key, events)
|
25
|
+
Result.new(redis, key)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "minuteman/bit_operations"
|
2
|
+
|
3
|
+
class Minuteman
|
4
|
+
module BitOperations
|
5
|
+
# Public: The result of intersecting results
|
6
|
+
#
|
7
|
+
# redis - The Redis connection
|
8
|
+
# key - The key where the result it's stored
|
9
|
+
#
|
10
|
+
class Result < Struct.new(:redis, :key)
|
11
|
+
include BitOperations
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "minuteman/keys_methods"
|
2
|
+
require "minuteman/bit_operations/data"
|
3
|
+
|
4
|
+
class Minuteman
|
5
|
+
module BitOperations
|
6
|
+
# Public: The class to handle operations with datasets
|
7
|
+
#
|
8
|
+
# redis: The Redis connection
|
9
|
+
# type: The operation type
|
10
|
+
# data: The data to be permuted
|
11
|
+
# source_key: The original key to do the operation
|
12
|
+
#
|
13
|
+
class WithData < Struct.new(:redis, :type, :data, :source_key)
|
14
|
+
include KeysMethods
|
15
|
+
|
16
|
+
def call
|
17
|
+
normalized_data = Array(data)
|
18
|
+
key = destination_key("data-#{type}", normalized_data)
|
19
|
+
command = case type
|
20
|
+
when "AND" then :select
|
21
|
+
when "MINUS" then :reject
|
22
|
+
end
|
23
|
+
|
24
|
+
intersected_data = normalized_data.send(command) do |id|
|
25
|
+
redis.getbit(source_key, id) == 1
|
26
|
+
end
|
27
|
+
|
28
|
+
intersected_data.each { |id| redis.setbit(key, id, 1) }
|
29
|
+
Data.new(redis, key, intersected_data)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Minuteman
|
2
|
+
module KeysMethods
|
3
|
+
BIT_OPERATION_PREFIX = "bitop"
|
4
|
+
|
5
|
+
private
|
6
|
+
|
7
|
+
# Private: The destination key for the operation
|
8
|
+
#
|
9
|
+
# type - The bitwise operation
|
10
|
+
# events - The events to permuted
|
11
|
+
#
|
12
|
+
def destination_key(type, events)
|
13
|
+
[
|
14
|
+
Minuteman::PREFIX,
|
15
|
+
BIT_OPERATION_PREFIX,
|
16
|
+
type,
|
17
|
+
events.join("-")
|
18
|
+
].join("_")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/minuteman.gemspec
CHANGED
data/test/unit/minuteman_test.rb
CHANGED
@@ -134,6 +134,28 @@ describe Minuteman do
|
|
134
134
|
it "should accept multiple consecutive operations" do
|
135
135
|
multi_operation = @week_events & @last_week_events | @year_events
|
136
136
|
|
137
|
-
assert_kind_of Minuteman::
|
137
|
+
assert_kind_of Minuteman::BitOperations::Result, multi_operation
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should return the ids that belong to a given set" do
|
141
|
+
ids = @week_events & [2, 12, 43]
|
142
|
+
|
143
|
+
assert_kind_of Minuteman::BitOperations::Data, ids
|
144
|
+
assert_equal [2, 12], ids
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should return the ids that do not belong to the given set" do
|
148
|
+
ids = @week_events - [2, 12, 43]
|
149
|
+
|
150
|
+
assert_kind_of Minuteman::BitOperations::Data, ids
|
151
|
+
assert_equal [43], ids
|
152
|
+
end
|
153
|
+
|
154
|
+
it "a resulting set with data should behave like an array" do
|
155
|
+
ids = @week_events & [2, 12, 43]
|
156
|
+
|
157
|
+
assert_kind_of Enumerator, ids.each
|
158
|
+
assert_kind_of Enumerator, ids.map
|
159
|
+
assert_equal 2, ids.size
|
138
160
|
end
|
139
161
|
end
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: minuteman
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.2.0.pre
|
5
|
+
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- elcuervo
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-11-
|
12
|
+
date: 2012-11-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: redis
|
@@ -50,6 +50,7 @@ executables: []
|
|
50
50
|
extensions: []
|
51
51
|
extra_rdoc_files: []
|
52
52
|
files:
|
53
|
+
- .travis.yml
|
53
54
|
- Gemfile
|
54
55
|
- Gemfile.lock
|
55
56
|
- LICENSE
|
@@ -57,6 +58,12 @@ files:
|
|
57
58
|
- Rakefile
|
58
59
|
- lib/minuteman.rb
|
59
60
|
- lib/minuteman/bit_operations.rb
|
61
|
+
- lib/minuteman/bit_operations/data.rb
|
62
|
+
- lib/minuteman/bit_operations/operation.rb
|
63
|
+
- lib/minuteman/bit_operations/plain.rb
|
64
|
+
- lib/minuteman/bit_operations/result.rb
|
65
|
+
- lib/minuteman/bit_operations/with_data.rb
|
66
|
+
- lib/minuteman/keys_methods.rb
|
60
67
|
- lib/minuteman/time_events.rb
|
61
68
|
- lib/minuteman/time_span.rb
|
62
69
|
- lib/minuteman/time_spans.rb
|
@@ -84,9 +91,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
84
91
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
92
|
none: false
|
86
93
|
requirements:
|
87
|
-
- - ! '
|
94
|
+
- - ! '>'
|
88
95
|
- !ruby/object:Gem::Version
|
89
|
-
version:
|
96
|
+
version: 1.3.1
|
90
97
|
requirements: []
|
91
98
|
rubyforge_project:
|
92
99
|
rubygems_version: 1.8.23
|