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 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{^app/(.+)\.rb}) { |m| "spec/#{m[1]}_spec.rb" }
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 most_viewed_since(since, limit = 1000)
56
- objects = self._monocle_redis_connection.zrevrangebyscore(self.monocle_key, Time.now.to_i, since.to_i, limit: [0, limit])
57
- objects.collect { |o| self.find(o[0]) }
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._monocle_view_types.keys.each do |view_type|
63
- cache_field = "#{view_type}_views".to_sym
64
- count = self._monocle_redis_connection.hincrby(self.class.monocle_key(id), self.send("#{view_type}_views_field"), 1)
65
- if self._monocle_options[:cache_view_counts] && respond_to?(cache_field)
66
- update_column(cache_field, count) if respond_to?(:update_column)
67
- set(cache_field, count) if respond_to?(:set)
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
- self._monocle_redis_connection.zadd(self.class.monocle_key, Time.now.to_i, id)
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
@@ -1,3 +1,3 @@
1
1
  module Monocle
2
- VERSION = '0.2.0'
2
+ VERSION = '0.2.1'
3
3
  end
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.times do |i|
43
- o = TestObject.new
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 == TestObject.find(9).id
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 '#view!' do
67
- before do
68
- 50.times { object.view! }
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
- after do
72
- object.destroy_views
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
@@ -17,5 +17,6 @@ RSpec.configure do |config|
17
17
  REDIS.keys('monocle*').each do |key|
18
18
  REDIS.del(key)
19
19
  end
20
+ sleep(0.5)
20
21
  end
21
22
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: monocle
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors: