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.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.gitignore +27 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +297 -0
- data/Rakefile +69 -0
- data/lib/redisrank.rb +106 -0
- data/lib/redisrank/buffer.rb +110 -0
- data/lib/redisrank/collection.rb +20 -0
- data/lib/redisrank/connection.rb +89 -0
- data/lib/redisrank/core_ext.rb +5 -0
- data/lib/redisrank/core_ext/bignum.rb +8 -0
- data/lib/redisrank/core_ext/date.rb +8 -0
- data/lib/redisrank/core_ext/fixnum.rb +8 -0
- data/lib/redisrank/core_ext/hash.rb +20 -0
- data/lib/redisrank/core_ext/time.rb +3 -0
- data/lib/redisrank/date.rb +88 -0
- data/lib/redisrank/event.rb +98 -0
- data/lib/redisrank/finder.rb +245 -0
- data/lib/redisrank/finder/date_set.rb +99 -0
- data/lib/redisrank/key.rb +84 -0
- data/lib/redisrank/label.rb +69 -0
- data/lib/redisrank/mixins/database.rb +11 -0
- data/lib/redisrank/mixins/date_helper.rb +8 -0
- data/lib/redisrank/mixins/options.rb +41 -0
- data/lib/redisrank/mixins/synchronize.rb +52 -0
- data/lib/redisrank/model.rb +77 -0
- data/lib/redisrank/result.rb +18 -0
- data/lib/redisrank/scope.rb +18 -0
- data/lib/redisrank/summary.rb +90 -0
- data/lib/redisrank/version.rb +3 -0
- data/redisrank.gemspec +31 -0
- data/spec/Find Results +3349 -0
- data/spec/buffer_spec.rb +104 -0
- data/spec/collection_spec.rb +20 -0
- data/spec/connection_spec.rb +67 -0
- data/spec/core_ext/hash_spec.rb +26 -0
- data/spec/database_spec.rb +10 -0
- data/spec/date_spec.rb +95 -0
- data/spec/event_spec.rb +86 -0
- data/spec/finder/date_set_spec.rb +527 -0
- data/spec/finder_spec.rb +205 -0
- data/spec/key_spec.rb +129 -0
- data/spec/label_spec.rb +86 -0
- data/spec/model_helper.rb +31 -0
- data/spec/model_spec.rb +191 -0
- data/spec/options_spec.rb +36 -0
- data/spec/redis-test.conf +9 -0
- data/spec/result_spec.rb +23 -0
- data/spec/scope_spec.rb +27 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/summary_spec.rb +177 -0
- data/spec/synchronize_spec.rb +125 -0
- data/spec/thread_safety_spec.rb +39 -0
- 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
|