minuteman 0.1.1 → 0.2.0.pre

Sign up to get free protection for your applications and to get access to all the features.
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