redisrank 0.1.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.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.gitignore +27 -0
  4. data/.rspec +2 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE +20 -0
  7. data/README.md +297 -0
  8. data/Rakefile +69 -0
  9. data/lib/redisrank.rb +106 -0
  10. data/lib/redisrank/buffer.rb +110 -0
  11. data/lib/redisrank/collection.rb +20 -0
  12. data/lib/redisrank/connection.rb +89 -0
  13. data/lib/redisrank/core_ext.rb +5 -0
  14. data/lib/redisrank/core_ext/bignum.rb +8 -0
  15. data/lib/redisrank/core_ext/date.rb +8 -0
  16. data/lib/redisrank/core_ext/fixnum.rb +8 -0
  17. data/lib/redisrank/core_ext/hash.rb +20 -0
  18. data/lib/redisrank/core_ext/time.rb +3 -0
  19. data/lib/redisrank/date.rb +88 -0
  20. data/lib/redisrank/event.rb +98 -0
  21. data/lib/redisrank/finder.rb +245 -0
  22. data/lib/redisrank/finder/date_set.rb +99 -0
  23. data/lib/redisrank/key.rb +84 -0
  24. data/lib/redisrank/label.rb +69 -0
  25. data/lib/redisrank/mixins/database.rb +11 -0
  26. data/lib/redisrank/mixins/date_helper.rb +8 -0
  27. data/lib/redisrank/mixins/options.rb +41 -0
  28. data/lib/redisrank/mixins/synchronize.rb +52 -0
  29. data/lib/redisrank/model.rb +77 -0
  30. data/lib/redisrank/result.rb +18 -0
  31. data/lib/redisrank/scope.rb +18 -0
  32. data/lib/redisrank/summary.rb +90 -0
  33. data/lib/redisrank/version.rb +3 -0
  34. data/redisrank.gemspec +31 -0
  35. data/spec/Find Results +3349 -0
  36. data/spec/buffer_spec.rb +104 -0
  37. data/spec/collection_spec.rb +20 -0
  38. data/spec/connection_spec.rb +67 -0
  39. data/spec/core_ext/hash_spec.rb +26 -0
  40. data/spec/database_spec.rb +10 -0
  41. data/spec/date_spec.rb +95 -0
  42. data/spec/event_spec.rb +86 -0
  43. data/spec/finder/date_set_spec.rb +527 -0
  44. data/spec/finder_spec.rb +205 -0
  45. data/spec/key_spec.rb +129 -0
  46. data/spec/label_spec.rb +86 -0
  47. data/spec/model_helper.rb +31 -0
  48. data/spec/model_spec.rb +191 -0
  49. data/spec/options_spec.rb +36 -0
  50. data/spec/redis-test.conf +9 -0
  51. data/spec/result_spec.rb +23 -0
  52. data/spec/scope_spec.rb +27 -0
  53. data/spec/spec_helper.rb +18 -0
  54. data/spec/summary_spec.rb +177 -0
  55. data/spec/synchronize_spec.rb +125 -0
  56. data/spec/thread_safety_spec.rb +39 -0
  57. metadata +235 -0
@@ -0,0 +1,245 @@
1
+ require 'redisrank/finder/date_set'
2
+
3
+ module Redisrank
4
+ class Finder
5
+ include Database
6
+
7
+ class << self
8
+ def find(*args)
9
+ new.find(*args)
10
+ end
11
+
12
+ def scope(scope)
13
+ new.scope(scope)
14
+ end
15
+
16
+ def label(label)
17
+ new.label(label)
18
+ end
19
+
20
+ def dates(from, till)
21
+ new.dates(from, till)
22
+ end
23
+ alias :date :dates
24
+
25
+ def from(date)
26
+ new.from(date)
27
+ end
28
+
29
+ def till(date)
30
+ new.till(date)
31
+ end
32
+ alias :untill :till
33
+
34
+ def depth(unit)
35
+ new.depth(unit)
36
+ end
37
+
38
+ def interval(unit)
39
+ new.interval(unit)
40
+ end
41
+ end
42
+
43
+ attr_reader :options
44
+
45
+ def initialize(opts = {})
46
+ set_options(opts)
47
+ end
48
+
49
+ def options
50
+ @options ||= {}
51
+ end
52
+
53
+ def all(reload = false)
54
+ @result = nil if reload
55
+ @result ||= find
56
+ end
57
+
58
+ def rank
59
+ all.rank
60
+ end
61
+
62
+ def each(&block)
63
+ all.each(&block)
64
+ end
65
+
66
+ def map(&block)
67
+ all.map(&block)
68
+ end
69
+
70
+ def each_with_index(&block)
71
+ all.each_with_index(&block)
72
+ end
73
+
74
+ def parent
75
+ @parent ||= self.class.new(options.merge(:label => options[:label].parent)) unless options[:label].nil?
76
+ end
77
+
78
+ def children
79
+ build_key.children.map { |key|
80
+ self.class.new(options.merge(:label => key.label.to_s))
81
+ }
82
+ end
83
+
84
+ def connection_ref(ref = nil)
85
+ return options[:connection_ref] if ref.nil?
86
+ reset! if options[:connection_ref] != ref
87
+ options[:connection_ref] = ref
88
+ self
89
+ end
90
+
91
+ def scope(input = nil)
92
+ return options[:scope] if input.nil?
93
+ reset! if !options[:scope].nil? && options[:scope].to_s != input.to_s
94
+ options[:scope] = Scope.new(input)
95
+ self
96
+ end
97
+
98
+ def label(input = nil)
99
+ return options[:label] if input.nil?
100
+ reset! if options.has_key?(:label) && options[:label].to_s != input.to_s
101
+ options[:label] = (!input.nil?) ? Label.new(input) : nil
102
+ self
103
+ end
104
+
105
+ def dates(start, finish)
106
+ from(start).till(finish)
107
+ end
108
+ alias :date :dates
109
+
110
+ def from(date = nil)
111
+ return options[:from] if date.nil?
112
+ reset! if options[:from] != date
113
+ options[:from] = date
114
+ self
115
+ end
116
+
117
+ def till(date = nil)
118
+ return options[:till] if date.nil?
119
+ reset! if options[:till] != date
120
+ options[:till] = date
121
+ self
122
+ end
123
+ alias :until :till
124
+
125
+ def depth(unit = nil)
126
+ return options[:depth] if unit.nil?
127
+ reset! if options[:depth] != unit
128
+ options[:depth] = unit
129
+ self
130
+ end
131
+
132
+ def interval(unit = nil)
133
+ return options[:interval] if unit.nil?
134
+ reset! if options[:interval] != unit
135
+ options[:interval] = unit
136
+ self
137
+ end
138
+
139
+ def find(opts = {})
140
+ set_options(opts)
141
+ raise InvalidOptions.new if !valid_options?
142
+ if options[:interval].nil? || !options[:interval]
143
+ find_by_magic
144
+ else
145
+ find_by_interval
146
+ end
147
+ end
148
+
149
+ private
150
+
151
+ def set_options(opts = {})
152
+ opts = opts.clone
153
+ opts.each do |key, value|
154
+ self.send(key, opts.delete(key)) if self.respond_to?(key)
155
+ end
156
+ self.options.merge!(opts)
157
+ end
158
+
159
+ def find_by_interval
160
+ raise InvalidOptions.new if !valid_options?
161
+ key = build_key
162
+ col = Collection.new(options)
163
+ col.rank = Result.new(options)
164
+ build_date_sets.each do |set|
165
+ set[:add].each do |date|
166
+ result = Result.new
167
+ result.date = Date.new(date).to_time
168
+ db.zrevrange("#{key.prefix}#{date}", 0, -1, :with_scores => true).each do |array|
169
+ result[array.first] = array.last unless (result[array.first] || 0) > array.last
170
+ col.rank.merge_to_max!({array.first => array.last})
171
+ end
172
+ col << result
173
+ end
174
+ end
175
+ col
176
+ end
177
+
178
+ def find_by_magic
179
+ raise InvalidOptions.new if !valid_options?
180
+ key = build_key
181
+ col = Collection.new(options)
182
+ col.rank = Result.new(options)
183
+ sum = []
184
+ build_date_sets.each do |set|
185
+ _sum = summarize_add_ranks(set[:add], key, [])
186
+ _sum = summarize_rem_ranks(set[:rem], key, _sum)
187
+ _sum = summarize_ranks(_sum)
188
+ sum += _sum
189
+ end
190
+ sum.map{|s| {s.first => s.last}}.each{|i| col.rank.merge_to_max! i}
191
+ col
192
+ end
193
+
194
+ def reset!
195
+ @result = nil
196
+ @parent = nil
197
+ end
198
+
199
+ def valid_options?
200
+ return true if !options[:scope].blank? && !options[:label].blank? && !options[:from].blank? && !options[:till].blank?
201
+ false
202
+ end
203
+
204
+ def build_date_sets
205
+ Finder::DateSet.new(options[:from], options[:till], options[:depth], options[:interval])
206
+ end
207
+
208
+ def build_key
209
+ Key.new(options[:scope], options[:label])
210
+ end
211
+
212
+ def summarize_add_ranks(sets, key, sum)
213
+ sets.each do |date|
214
+ db.zrevrange("#{key.prefix}#{date}", 0, -1, :with_scores => true).each do |r|
215
+ sum << r
216
+ end
217
+ end
218
+ sum
219
+ end
220
+
221
+ def summarize_rem_ranks(sets, key, sum)
222
+ sets.each do |date|
223
+ db.zrevrange("#{key.prefix}#{date}").each do |r|
224
+ sum.select!{|s| !(s == r)}
225
+ end
226
+ end
227
+ sum
228
+ end
229
+
230
+ def summarize_ranks(sum)
231
+ result = []
232
+ keys = sum.map{|r| r.first}.uniq
233
+ keys.each do |r|
234
+ r_max = sum.select{|r_keys| r_keys.first == r}.map{|r_keys| r_keys.last}.max
235
+ result << [r, r_max]
236
+ end
237
+ result
238
+ end
239
+
240
+ def db
241
+ super(options[:connection_ref])
242
+ end
243
+
244
+ end
245
+ end
@@ -0,0 +1,99 @@
1
+ module Redisrank
2
+ class Finder
3
+ class DateSet < Array
4
+
5
+ def initialize(start_date = nil, end_date = nil, depth = nil, interval = false)
6
+ if !start_date.nil? && !end_date.nil?
7
+ find_date_sets(start_date, end_date, depth, interval)
8
+ end
9
+ end
10
+
11
+ def find_date_sets(start_date, end_date, depth = nil, interval = false)
12
+ if depth.nil? && interval.is_a?(Symbol)
13
+ depth = interval
14
+ interval = true
15
+ end
16
+ start_date = start_date.to_time if start_date.is_a?(::Date)
17
+ end_date = end_date.to_time if end_date.is_a?(::Date)
18
+ if !interval
19
+ find_date_sets_by_magic(start_date, end_date, depth)
20
+ else
21
+ find_date_sets_by_interval(start_date, end_date, depth)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def find_date_sets_by_magic(start_date, end_date, depth = nil)
28
+ depth ||= :hour
29
+ depths = Date::DEPTHS[Date::DEPTHS.index(:year), Date::DEPTHS.index(depth)+1].reverse
30
+ depths.each_with_index do |d, i|
31
+ sets = [find_start_keys_for(d, start_date, end_date, (i == 0))]
32
+ sets << find_end_keys_for(d, start_date, end_date, (i == 0))
33
+ sets.each do |set|
34
+ self << set if set != { :add => [], :rem => [] }
35
+ end
36
+ end
37
+ self
38
+ end
39
+
40
+ def find_date_sets_by_interval(start_date, end_date, depth, inclusive = true)
41
+ depth ||= :hour
42
+ self << { :add => start_date.map_beginning_of_each(depth, :include_start => inclusive, :include_end => inclusive).until(end_date) { |t| t.to_rs.to_s(depth) }, :rem => [] }
43
+ end
44
+
45
+ def find_start_keys_for(unit, start_date, end_date, lowest_depth = false)
46
+ return find_start_year_for(start_date, end_date, lowest_depth) if unit == :year
47
+ index = Date::DEPTHS.index(unit)
48
+ nunit = Date::DEPTHS[(index > 0) ? index-1 : index]
49
+ if start_date < start_date.beginning_of_closest(nunit) || start_date.next(nunit).beginning_of(nunit) > end_date.beginning_of(nunit)
50
+ add = []
51
+ start_date.beginning_of_each(unit, :include_start => lowest_depth).until(start_date.end_of(nunit)) do |t|
52
+ add << t.to_rs.to_s(unit) if t < end_date.beginning_of(unit)
53
+ end
54
+ { :add => add, :rem => [] }
55
+ else
56
+ { :add => [start_date.beginning_of(nunit).to_rs.to_s(nunit)],
57
+ :rem => start_date.beginning_of(nunit).map_beginning_of_each(unit, :include_start => true, :include_end => !lowest_depth).until(start_date) { |t| t.to_rs.to_s(unit) } }
58
+ end
59
+ end
60
+
61
+ def find_end_keys_for(unit, start_date, end_date, lowest_depth = false)
62
+ return find_end_year_for(start_date, end_date, lowest_depth) if unit == :year
63
+ index = Date::DEPTHS.index(unit)
64
+ nunit = Date::DEPTHS[(index > 0) ? index-1 : index]
65
+ has_nunit = end_date.prev(nunit).beginning_of(nunit) >= start_date.beginning_of(nunit)
66
+ nearest_nunit = end_date.beginning_of_closest(nunit)
67
+ if end_date >= nearest_nunit && has_nunit
68
+ add = []
69
+ end_date.beginning_of(nunit).beginning_of_each(unit, :include_start => true, :include_end => lowest_depth).until(end_date) do |t|
70
+ add << t.to_rs.to_s(unit) if t > start_date.beginning_of(unit)
71
+ end
72
+ { :add => add, :rem => [] }
73
+ elsif has_nunit
74
+ { :add => [end_date.beginning_of(nunit).to_rs.to_s(nunit)],
75
+ :rem => end_date.map_beginning_of_each(unit, :include_start => !lowest_depth).until(end_date.end_of(nunit)) { |t| t.to_rs.to_s(unit) } }
76
+ else
77
+ { :add => [], :rem => [] }
78
+ end
79
+ end
80
+
81
+ def find_start_year_for(start_date, end_date, lowest_depth = false)
82
+ if start_date.years_since(1).beginning_of_year < end_date.beginning_of_year
83
+ { :add => start_date.map_beginning_of_each_year(:include_end => lowest_depth).until(end_date) { |t| t.to_rs.to_s(:year) }, :rem => [] }
84
+ else
85
+ { :add => [], :rem => [] }
86
+ end
87
+ end
88
+
89
+ def find_end_year_for(start_date, end_date, lowest_depth = false)
90
+ if lowest_depth
91
+ { :add => [end_date.beginning_of_year.to_rs.to_s(:year)], :rem => [] }
92
+ else
93
+ { :add => [], :rem => [] }
94
+ end
95
+ end
96
+
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,84 @@
1
+ module Redisrank
2
+ class Key
3
+ include Database
4
+ include Options
5
+
6
+ def default_options
7
+ { :depth => :hour }
8
+ end
9
+
10
+ def initialize(scope, label_name = nil, time_stamp = nil, opts = {})
11
+ parse_options(opts)
12
+ self.scope = scope
13
+ self.label = label_name if !label_name.nil?
14
+ self.date = time_stamp ||= Time.now
15
+ end
16
+
17
+ def prefix
18
+ key = "#{@scope}"
19
+ key << "/#{label.name}" if !label.nil?
20
+ key << ":"
21
+ key
22
+ end
23
+
24
+ def date=(input)
25
+ @date = (input.instance_of?(Redisrank::Date)) ? input : Date.new(input) # Redisrank::Date, not ::Date
26
+ end
27
+ attr_reader :date
28
+
29
+ def depth
30
+ options[:depth]
31
+ end
32
+
33
+ # def scope
34
+ # @scope.to_s
35
+ # end
36
+
37
+ def scope=(input)
38
+ @scope = (input.instance_of?(Redisrank::Scope)) ? input : Scope.new(input)
39
+ end
40
+ attr_reader :scope
41
+
42
+ def label=(input)
43
+ @label = (input.instance_of?(Redisrank::Label)) ? input : Label.create(input, @options)
44
+ end
45
+ attr_reader :label
46
+
47
+ def label_hash
48
+ @label.hash
49
+ end
50
+
51
+ def parent
52
+ @parent ||= self.class.new(self.scope, @label.parent, self.date, @options) unless @label.parent.nil?
53
+ end
54
+
55
+ def children
56
+ members = db.smembers("#{scope}#{LABEL_INDEX}#{@label}") || [] # older versions of Redis returns nil
57
+ members.map { |member|
58
+ child_label = [@label, member].reject { |i| i.nil? }
59
+ self.class.new(self.scope, child_label.join(Redisrank.group_separator), self.date, @options)
60
+ }
61
+ end
62
+
63
+ def update_index
64
+ @label.groups.each do |label|
65
+ parent = (label.parent || "")
66
+ db.sadd("#{scope}#{LABEL_INDEX}#{parent}", label.me)
67
+ end
68
+ end
69
+
70
+ def groups
71
+ @groups ||= @label.groups.map do |label|
72
+ self.class.new(@scope, label, self.date, @options)
73
+ end
74
+ end
75
+
76
+ def to_s(depth = nil)
77
+ depth ||= @options[:depth]
78
+ key = self.prefix
79
+ key << @date.to_s(depth)
80
+ key
81
+ end
82
+
83
+ end
84
+ end
@@ -0,0 +1,69 @@
1
+ module Redisrank
2
+ class Label
3
+ include Database
4
+ include Options
5
+
6
+ def default_options
7
+ { :hashed_label => false }
8
+ end
9
+
10
+ def self.create(name, opts = {})
11
+ self.new(name, opts).save
12
+ end
13
+
14
+ def self.join(*args)
15
+ args = args.map {|i| i.to_s}
16
+ self.new(args.reject {|i| i.blank? }.join(Redisrank.group_separator))
17
+ end
18
+
19
+ def initialize(str, opts = {})
20
+ parse_options(opts)
21
+ @raw = str.to_s
22
+ end
23
+
24
+ def to_s
25
+ @raw
26
+ end
27
+
28
+ def name
29
+ @options[:hashed_label] ? hash : self.to_s
30
+ end
31
+
32
+ def hash
33
+ @hash ||= Digest::SHA1.hexdigest(self.to_s)
34
+ end
35
+
36
+ def save
37
+ @saved = db.hset(KEY_LABELS, hash, self.to_s) if @options[:hashed_label]
38
+ self
39
+ end
40
+
41
+ def saved?
42
+ return true unless @options[:hashed_label]
43
+ @saved ||= false
44
+ end
45
+
46
+ def parent
47
+ @parent ||= groups[1] if groups.size > 1
48
+ end
49
+
50
+ def me
51
+ self.to_s.split(Redisrank.group_separator).last
52
+ end
53
+
54
+ def groups
55
+ return @groups unless @groups.nil?
56
+ @groups = []
57
+ parent = ""
58
+ self.to_s.split(Redisrank.group_separator).each do |part|
59
+ if !part.blank?
60
+ group = ((parent.blank?) ? "" : "#{parent}#{Redisrank.group_separator}") + part
61
+ @groups << Label.new(group)
62
+ parent = group
63
+ end
64
+ end
65
+ @groups.reverse!
66
+ end
67
+
68
+ end
69
+ end