redisrank 0.1.0

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