monocle 0.2.0 → 0.2.1

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.
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: