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 ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.3"
4
+ - "jruby-19mode"
5
+ - "rbx-19mode"
6
+ services:
7
+ - "redis-server"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- minuteman (0.1.1)
4
+ minuteman (0.2.0.pre)
5
5
  redis (~> 3.0.2)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # Minuteman
2
+ [![Code Climate](https://codeclimate.com/badge.png)](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").map(&:id))
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.map(&:id))
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
- ==> This are the same
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
@@ -97,7 +97,7 @@ class Minuteman
97
97
  #
98
98
  def operations_cache_key_prefix
99
99
  [
100
- PREFIX, Minuteman::BitOperations::BIT_OPERATION_PREFIX
100
+ PREFIX, Minuteman::KeysMethods::BIT_OPERATION_PREFIX
101
101
  ].join("_")
102
102
  end
103
103
  end
@@ -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| redis.getbit(key, id) == 1 }
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
- bit_operation("NOT", key)
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
- self ^ (self & timespan)
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
- bit_operation("XOR", [key, timespan.key])
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
- bit_operation("OR", [key, timespan.key])
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
- bit_operation("AND", [key, timespan.key])
63
+ operation("AND", timespan)
62
64
  end
63
65
 
64
66
  private
65
67
 
66
- # Private: The destination key for the operation
67
- #
68
- # type - The bitwise operation
69
- # events - The events to permuted
70
- #
71
- def destination_key(type, events)
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: Executes a bit operation
76
+ # Private: executes an operation between the current timespan and another
81
77
  #
82
- # type - The bitwise operation
83
- # events - The events to permuted
78
+ # type: The operation type
79
+ # timespan: The given timespan
84
80
  #
85
- def bit_operation(type, events)
86
- key = destination_key(type, Array(events))
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
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "minuteman"
3
- s.version = "0.1.1"
3
+ s.version = "0.2.0.pre"
4
4
  s.summary = "Bit Analytics"
5
5
  s.description = "Fast and furious tracking system using Redis bitwise operations"
6
6
  s.authors = ["elcuervo"]
@@ -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::BitOperation, multi_operation
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.1.1
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-06 00:00:00.000000000 Z
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: '0'
96
+ version: 1.3.1
90
97
  requirements: []
91
98
  rubyforge_project:
92
99
  rubygems_version: 1.8.23