monocle 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Guardfile +1 -2
- data/lib/monocle.rb +45 -11
- data/lib/monocle/version.rb +1 -1
- data/spec/monocle_spec.rb +64 -16
- data/spec/spec_helper.rb +1 -0
- metadata +1 -1
data/Guardfile
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
guard :rspec, version: 2, all_after_pass: true, all_on_start: false, keep_failed: false, cli: '--tty --color --format nested' do
|
2
2
|
watch(%r{^spec/.+_spec\.rb})
|
3
|
-
watch(%r{^
|
4
|
-
watch(%r{^lib/(.+)\.rb}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
3
|
+
watch(%r{^lib/(.+)\.rb}) { |m| "spec/#{m[1]}_spec.rb" }
|
5
4
|
watch('spec/spec_helper.rb') { 'spec' }
|
6
5
|
end
|
data/lib/monocle.rb
CHANGED
@@ -13,7 +13,9 @@ module Monocle
|
|
13
13
|
:_monocle_redis_connection
|
14
14
|
|
15
15
|
self._monocle_options = {
|
16
|
-
cache_view_counts: false
|
16
|
+
cache_view_counts: false,
|
17
|
+
cache_threshold: 15.minutes,
|
18
|
+
cache_threshold_check_field: :updated_at
|
17
19
|
}
|
18
20
|
|
19
21
|
self._monocle_view_types = {
|
@@ -52,23 +54,55 @@ module Monocle
|
|
52
54
|
end
|
53
55
|
end
|
54
56
|
|
55
|
-
def
|
56
|
-
|
57
|
-
|
57
|
+
def recently_viewed_since(since, options = {})
|
58
|
+
options[:limit] ||= 1000
|
59
|
+
options[:with_objects] = options.has_key?(:with_objects) ? options[:with_objects] : true
|
60
|
+
|
61
|
+
results = self._monocle_redis_connection.zrevrangebyscore(self.monocle_key('recently_viewed'), Time.now.to_i, since.to_i, limit:[0, options[:limit]])
|
62
|
+
options[:with_objects] ? results.map { |id| self.find(id) } : results
|
63
|
+
end
|
64
|
+
|
65
|
+
def most_viewed_since(since, options = {})
|
66
|
+
options[:limit] ||= 1000
|
67
|
+
options[:with_objects] = options.has_key?(:with_objects) ? options[:with_objects] : true
|
68
|
+
|
69
|
+
viewed_by_score = self._monocle_redis_connection.zrevrangebyscore(self.monocle_key('view_counts'), '+inf', '-inf', limit: [0, options[:limit]])
|
70
|
+
results = viewed_by_score & recently_viewed_since(since, with_objects: false, limit: options[:limit])
|
71
|
+
options[:with_objects] ? results.map { |id| self.find(id) } : results
|
58
72
|
end
|
59
73
|
end
|
60
74
|
|
61
75
|
def view!
|
62
|
-
self.
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
76
|
+
results = self._monocle_redis_connection.pipelined do
|
77
|
+
self._monocle_view_types.keys.each do |view_type|
|
78
|
+
self._monocle_redis_connection.hincrby(self.class.monocle_key(id), self.send("#{view_type}_views_field"), 1)
|
79
|
+
end
|
80
|
+
self._monocle_redis_connection.zadd(self.class.monocle_key('recently_viewed'), Time.now.to_i, id)
|
81
|
+
self._monocle_redis_connection.zincrby(self.class.monocle_key('view_counts'), 1, id)
|
82
|
+
end
|
83
|
+
|
84
|
+
if should_cache_view_count?
|
85
|
+
self._monocle_view_types.keys.each_with_index do |view_type, i|
|
86
|
+
cache_view_count(view_type, results[i])
|
68
87
|
end
|
69
88
|
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def cache_field_for_view(view_type)
|
92
|
+
:"#{view_type}_views"
|
93
|
+
end
|
94
|
+
|
95
|
+
def should_cache_view_count?
|
96
|
+
if self._monocle_options[:cache_view_counts]
|
97
|
+
self.send(self._monocle_options[:cache_threshold_check_field]) < (Time.now - self._monocle_options[:cache_threshold])
|
98
|
+
else
|
99
|
+
false
|
100
|
+
end
|
101
|
+
end
|
70
102
|
|
71
|
-
|
103
|
+
def cache_view_count(view_type, count)
|
104
|
+
update_column(cache_field_for_view(view_type), count) if respond_to?(:update_column)
|
105
|
+
set(cache_field_for_view(view_type), count) if respond_to?(:set)
|
72
106
|
end
|
73
107
|
|
74
108
|
def destroy_views
|
data/lib/monocle/version.rb
CHANGED
data/spec/monocle_spec.rb
CHANGED
@@ -3,15 +3,17 @@ require 'spec_helper'
|
|
3
3
|
class TestObject
|
4
4
|
include Monocle
|
5
5
|
|
6
|
-
monocle_options cache_view_counts: true
|
6
|
+
monocle_options cache_view_counts: true,
|
7
|
+
cache_threshold: 15.minutes,
|
8
|
+
cache_threshold_check_field: :updated_at
|
7
9
|
|
8
10
|
monocle_views overall: -> { 'overall' },
|
9
11
|
yearly: -> { Time.now.beginning_of_year },
|
12
|
+
quarterly: -> { Time.now.beginning_of_quarter },
|
10
13
|
monthly: -> { Time.now.beginning_of_month },
|
11
14
|
weekly: -> { Time.now.beginning_of_week },
|
12
15
|
daily: -> { Time.now.beginning_of_day },
|
13
|
-
hourly: -> { Time.now.beginning_of_hour }
|
14
|
-
quarterly: -> { Time.now.beginning_of_quarter }
|
16
|
+
hourly: -> { Time.now.beginning_of_hour }
|
15
17
|
|
16
18
|
attr_accessor :id
|
17
19
|
attr_accessor :overall_views, :yearly_views, :monthly_views
|
@@ -32,26 +34,47 @@ class TestObject
|
|
32
34
|
def update_column(field, count)
|
33
35
|
self.send("#{field}=", count)
|
34
36
|
end
|
37
|
+
|
38
|
+
def updated_at
|
39
|
+
Time.now - 1.hour
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def make_viewed_objects(number_of_objects_to_make)
|
44
|
+
number_of_objects_to_make.times do |i|
|
45
|
+
o = TestObject.new
|
46
|
+
o.id = i
|
47
|
+
o.view!
|
48
|
+
end
|
35
49
|
end
|
36
50
|
|
37
51
|
describe Monocle do
|
52
|
+
|
38
53
|
let(:object) { TestObject.new }
|
39
54
|
|
55
|
+
describe '#recently_viewed' do
|
56
|
+
before { make_viewed_objects(10) }
|
57
|
+
let(:recently_viewed) { TestObject.recently_viewed_since(1.day.ago) }
|
58
|
+
|
59
|
+
it 'returns the most recently viewed object at the top of the list' do
|
60
|
+
recently_viewed.first.id.should == 9
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'returns the last recently viewed object at the bottom of the list' do
|
64
|
+
recently_viewed.last.id.should == 0
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
40
68
|
describe '#most_viewed_since' do
|
41
69
|
before do
|
42
|
-
10
|
43
|
-
|
44
|
-
o.id = i
|
45
|
-
(i + 10).times do
|
46
|
-
o.view!
|
47
|
-
end
|
48
|
-
end
|
70
|
+
make_viewed_objects(10)
|
71
|
+
10.times { TestObject.find(3).view! }
|
49
72
|
end
|
50
73
|
|
51
74
|
it 'returns top viewed objects since a given time' do
|
52
75
|
viewed = TestObject.most_viewed_since(Time.now.beginning_of_day)
|
53
76
|
viewed.class.should == Array
|
54
|
-
viewed.first.id.should ==
|
77
|
+
viewed.first.id.should == 3
|
55
78
|
end
|
56
79
|
end
|
57
80
|
|
@@ -63,14 +86,39 @@ describe Monocle do
|
|
63
86
|
end
|
64
87
|
end
|
65
88
|
|
66
|
-
describe '#
|
67
|
-
|
68
|
-
|
89
|
+
describe '#cache_field_for_view' do
|
90
|
+
it 'returns the cache field for the given view type' do
|
91
|
+
object.cache_field_for_view('overall').should == :overall_views
|
69
92
|
end
|
93
|
+
end
|
70
94
|
|
71
|
-
|
72
|
-
|
95
|
+
describe '#should_cache_view_count?' do
|
96
|
+
context 'the class has cache_view_counts set to true' do
|
97
|
+
context 'the objects last updated time is greater than the threshold' do
|
98
|
+
it 'returns true' do
|
99
|
+
object.should_cache_view_count?.should == true
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context 'the objects last updated time is less than the threshold' do
|
104
|
+
it 'returns true' do
|
105
|
+
object.stub(:updated_at).and_return(Time.now)
|
106
|
+
object.should_cache_view_count?.should == false
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context 'the class has cache_view_counts set to false' do
|
112
|
+
it 'returns false' do
|
113
|
+
object.class.stub(:_monocle_options).and_return({cache_view_counts:false})
|
114
|
+
object.should_cache_view_count?.should == false
|
115
|
+
end
|
73
116
|
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe '#view!' do
|
120
|
+
before { 50.times { object.view! }}
|
121
|
+
after { object.destroy_views }
|
74
122
|
|
75
123
|
%w(overall yearly monthly weekly daily hourly quarterly).each do |view_type|
|
76
124
|
it "sets #{view_type} views count" do
|
data/spec/spec_helper.rb
CHANGED