monocle 0.1.1 → 0.2.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.
- data/.gitignore +45 -0
- data/.travis.yml +3 -0
- data/Gemfile +16 -18
- data/Guardfile +6 -0
- data/README.rdoc +2 -0
- data/Rakefile +9 -37
- data/lib/monocle.rb +77 -9
- data/lib/monocle/core_ext.rb +5 -0
- data/lib/monocle/server.rb +7 -8
- data/lib/monocle/version.rb +3 -0
- data/monocle.gemspec +10 -88
- data/spec/monocle_spec.rb +85 -0
- data/spec/spec_helper.rb +8 -31
- metadata +69 -163
- data/lib/generators/monocle/install_generator.rb +0 -17
- data/lib/generators/monocle/templates/migration.rb +0 -21
- data/lib/monocle/daily_view.rb +0 -22
- data/lib/monocle/monthly_view.rb +0 -5
- data/lib/monocle/overall_view.rb +0 -4
- data/lib/monocle/view.rb +0 -38
- data/lib/monocle/views.rb +0 -26
- data/lib/monocle/weekly_view.rb +0 -5
- data/lib/monocle/yearly_view.rb +0 -5
- data/spec/db/migrate/20110502201938_create_viewables.rb +0 -11
- data/spec/db/migrate/20110502223022_create_monocle_views.rb +0 -21
- data/spec/view_spec.rb +0 -36
- data/spec/viewable_spec.rb +0 -36
data/.gitignore
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# rcov generated
|
2
|
+
coverage
|
3
|
+
|
4
|
+
# rdoc generated
|
5
|
+
rdoc
|
6
|
+
|
7
|
+
# yard generated
|
8
|
+
doc
|
9
|
+
.yardoc
|
10
|
+
|
11
|
+
# bundler
|
12
|
+
.bundle
|
13
|
+
|
14
|
+
# jeweler generated
|
15
|
+
pkg
|
16
|
+
|
17
|
+
spec/db/*.sqlite3
|
18
|
+
Gemfile.lock
|
19
|
+
|
20
|
+
# Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
|
21
|
+
#
|
22
|
+
# * Create a file at ~/.gitignore
|
23
|
+
# * Include files you want ignored
|
24
|
+
# * Run: git config --global core.excludesfile ~/.gitignore
|
25
|
+
#
|
26
|
+
# After doing this, these files will be ignored in all your git projects,
|
27
|
+
# saving you from having to 'pollute' every project you touch with them
|
28
|
+
#
|
29
|
+
# Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
|
30
|
+
#
|
31
|
+
# For MacOS:
|
32
|
+
#
|
33
|
+
.DS_Store
|
34
|
+
#
|
35
|
+
# For TextMate
|
36
|
+
#*.tmproj
|
37
|
+
#tmtags
|
38
|
+
#
|
39
|
+
# For emacs:
|
40
|
+
#*~
|
41
|
+
#\#*
|
42
|
+
#.\#*
|
43
|
+
#
|
44
|
+
# For vim:
|
45
|
+
#*.swp
|
data/.travis.yml
ADDED
data/Gemfile
CHANGED
@@ -1,20 +1,18 @@
|
|
1
|
-
source
|
2
|
-
# Add dependencies required to use your gem here.
|
3
|
-
# Example:
|
4
|
-
# gem "activesupport", ">= 2.3.5"
|
1
|
+
source 'http://rubygems.org'
|
5
2
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
gem
|
11
|
-
gem
|
12
|
-
gem
|
13
|
-
gem
|
14
|
-
gem
|
15
|
-
gem
|
16
|
-
gem
|
17
|
-
|
18
|
-
|
19
|
-
|
3
|
+
gem 'activesupport'
|
4
|
+
gem 'redis'
|
5
|
+
|
6
|
+
group :development, :test do
|
7
|
+
gem 'rails'
|
8
|
+
gem 'sinatra'
|
9
|
+
gem 'yard'
|
10
|
+
gem 'guard'
|
11
|
+
gem 'guard-rspec'
|
12
|
+
gem 'terminal-notifier-guard'
|
13
|
+
gem 'rb-fsevent', '~> 0.9.1'
|
14
|
+
end
|
15
|
+
|
16
|
+
group :test do
|
17
|
+
gem 'rspec'
|
20
18
|
end
|
data/Guardfile
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
guard :rspec, version: 2, all_after_pass: true, all_on_start: false, keep_failed: false, cli: '--tty --color --format nested' do
|
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" }
|
5
|
+
watch('spec/spec_helper.rb') { 'spec' }
|
6
|
+
end
|
data/README.rdoc
CHANGED
data/Rakefile
CHANGED
@@ -1,43 +1,15 @@
|
|
1
|
-
require '
|
2
|
-
require 'bundler'
|
3
|
-
begin
|
4
|
-
Bundler.setup(:default, :development)
|
5
|
-
rescue Bundler::BundlerError => e
|
6
|
-
$stderr.puts e.message
|
7
|
-
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
-
exit e.status_code
|
9
|
-
end
|
1
|
+
require 'bundler/gem_tasks'
|
10
2
|
require 'rake'
|
11
|
-
|
12
|
-
require 'jeweler'
|
13
|
-
Jeweler::Tasks.new do |gem|
|
14
|
-
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
15
|
-
gem.name = "monocle"
|
16
|
-
gem.homepage = "http://github.com/russ/monocle"
|
17
|
-
gem.license = "MIT"
|
18
|
-
gem.summary = %Q{A history of view events.}
|
19
|
-
gem.description = %Q{A history of view events.}
|
20
|
-
gem.email = "russ@bashme.org"
|
21
|
-
gem.authors = ["Russ Smith"]
|
22
|
-
# Include your dependencies below. Runtime dependencies are required when using your gem,
|
23
|
-
# and development dependencies are only needed for development (ie running rake tasks, tests, etc)
|
24
|
-
# gem.add_runtime_dependency 'jabber4r', '> 0.1'
|
25
|
-
# gem.add_development_dependency 'rspec', '> 1.2.3'
|
26
|
-
end
|
27
|
-
Jeweler::RubygemsDotOrgTasks.new
|
28
|
-
|
29
|
-
require 'rspec/core'
|
30
3
|
require 'rspec/core/rake_task'
|
31
|
-
RSpec::Core::RakeTask.new(:spec) do |spec|
|
32
|
-
spec.pattern = FileList['spec/**/*_spec.rb']
|
33
|
-
end
|
34
4
|
|
35
|
-
|
36
|
-
|
37
|
-
|
5
|
+
namespace :spec do
|
6
|
+
RSpec::Core::RakeTask.new(:normal) do |t|
|
7
|
+
t.pattern ='spec/**/*_spec.rb'
|
8
|
+
t.rcov = false
|
9
|
+
end
|
38
10
|
end
|
39
11
|
|
40
|
-
|
12
|
+
desc "RSpec tests"
|
13
|
+
task "spec" => "spec:normal"
|
41
14
|
|
42
|
-
|
43
|
-
YARD::Rake::YardocTask.new
|
15
|
+
task "default" => "spec"
|
data/lib/monocle.rb
CHANGED
@@ -1,11 +1,79 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'active_support/core_ext'
|
3
|
+
require 'redis'
|
4
|
+
|
5
|
+
require 'monocle/core_ext'
|
6
|
+
|
1
7
|
module Monocle
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
class_attribute :_monocle_options,
|
12
|
+
:_monocle_view_types,
|
13
|
+
:_monocle_redis_connection
|
14
|
+
|
15
|
+
self._monocle_options = {
|
16
|
+
cache_view_counts: false
|
17
|
+
}
|
18
|
+
|
19
|
+
self._monocle_view_types = {
|
20
|
+
overall: -> { 'overall' },
|
21
|
+
yearly: -> { Time.now.beginning_of_year },
|
22
|
+
monthly: -> { Time.now.beginning_of_month },
|
23
|
+
weekly: -> { Time.now.beginning_of_week },
|
24
|
+
daily: -> { Time.now.beginning_of_day },
|
25
|
+
hourly: -> { Time.now.beginning_of_hour }
|
26
|
+
}
|
27
|
+
|
28
|
+
self._monocle_redis_connection = Redis.new || REDIS
|
29
|
+
end
|
30
|
+
|
31
|
+
module ClassMethods
|
32
|
+
def monocle_key(*append)
|
33
|
+
extra = (append.empty?) ? '' : ':' + append.join(':')
|
34
|
+
"monocle:#{self.to_s.underscore}" + extra
|
35
|
+
end
|
36
|
+
|
37
|
+
def monocle_options(options = {})
|
38
|
+
self._monocle_options = self._monocle_options.merge(options)
|
39
|
+
end
|
40
|
+
|
41
|
+
def monocle_views(types = {})
|
42
|
+
self._monocle_view_types = types
|
43
|
+
self._monocle_view_types.each do |k,v|
|
44
|
+
define_method("#{k}_views_count") do
|
45
|
+
self._monocle_redis_connection.hget(self.class.monocle_key(id), self.send("#{k}_views_field")).to_i || 0
|
46
|
+
end
|
47
|
+
|
48
|
+
define_method("#{k}_views_field") do
|
49
|
+
field = v.call
|
50
|
+
field.is_a?(String) ? field : field.to_i
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
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]) }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
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)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
self._monocle_redis_connection.zadd(self.class.monocle_key, Time.now.to_i, id)
|
72
|
+
end
|
73
|
+
|
74
|
+
def destroy_views
|
75
|
+
self._monocle_redis_connection.del(self.class.monocle_key(id))
|
76
|
+
end
|
77
|
+
|
78
|
+
autoload :Server, 'monocle/server'
|
11
79
|
end
|
data/lib/monocle/server.rb
CHANGED
@@ -1,14 +1,13 @@
|
|
1
|
-
require
|
1
|
+
require 'sinatra/base'
|
2
2
|
|
3
3
|
module Monocle
|
4
4
|
class Server < Sinatra::Base
|
5
|
-
post
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
"o_0"
|
5
|
+
post '/:type/:id(.:format)' do
|
6
|
+
if object = params[:type].classify.constantize.find(params[:id])
|
7
|
+
object.view!
|
8
|
+
'o_0 +1'
|
9
|
+
else
|
10
|
+
'o_0'
|
12
11
|
end
|
13
12
|
end
|
14
13
|
end
|
data/monocle.gemspec
CHANGED
@@ -1,101 +1,23 @@
|
|
1
|
-
# Generated by jeweler
|
2
|
-
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
-
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
1
|
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "monocle/version"
|
5
4
|
|
6
5
|
Gem::Specification.new do |s|
|
7
6
|
s.name = %q{monocle}
|
8
|
-
s.version =
|
9
|
-
|
10
|
-
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
-
s.authors = ["Russ Smith"]
|
7
|
+
s.version = Monocle::VERSION
|
8
|
+
s.authors = ["Russ Smith (russ@bashme.org)"]
|
12
9
|
s.date = %q{2011-08-31}
|
13
10
|
s.description = %q{A history of view events.}
|
14
11
|
s.email = %q{russ@bashme.org}
|
15
|
-
s.extra_rdoc_files = [
|
16
|
-
"LICENSE.txt",
|
17
|
-
"README.rdoc"
|
18
|
-
]
|
19
|
-
s.files = [
|
20
|
-
".document",
|
21
|
-
".rspec",
|
22
|
-
"Gemfile",
|
23
|
-
"LICENSE.txt",
|
24
|
-
"README.rdoc",
|
25
|
-
"Rakefile",
|
26
|
-
"VERSION",
|
27
|
-
"lib/generators/monocle/install_generator.rb",
|
28
|
-
"lib/generators/monocle/templates/migration.rb",
|
29
|
-
"lib/monocle.rb",
|
30
|
-
"lib/monocle/daily_view.rb",
|
31
|
-
"lib/monocle/monthly_view.rb",
|
32
|
-
"lib/monocle/overall_view.rb",
|
33
|
-
"lib/monocle/server.rb",
|
34
|
-
"lib/monocle/view.rb",
|
35
|
-
"lib/monocle/views.rb",
|
36
|
-
"lib/monocle/weekly_view.rb",
|
37
|
-
"lib/monocle/yearly_view.rb",
|
38
|
-
"monocle.gemspec",
|
39
|
-
"spec/db/migrate/20110502201938_create_viewables.rb",
|
40
|
-
"spec/db/migrate/20110502223022_create_monocle_views.rb",
|
41
|
-
"spec/spec_helper.rb",
|
42
|
-
"spec/view_spec.rb",
|
43
|
-
"spec/viewable_spec.rb",
|
44
|
-
"transparent.gif"
|
45
|
-
]
|
46
12
|
s.homepage = %q{http://github.com/russ/monocle}
|
47
13
|
s.licenses = ["MIT"]
|
48
14
|
s.require_paths = ["lib"]
|
49
15
|
s.rubygems_version = %q{1.6.2}
|
50
16
|
s.summary = %q{A history of view events.}
|
51
|
-
s.
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
]
|
58
|
-
|
59
|
-
if s.respond_to? :specification_version then
|
60
|
-
s.specification_version = 3
|
61
|
-
|
62
|
-
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
63
|
-
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
64
|
-
s.add_development_dependency(%q<database_cleaner>, [">= 0"])
|
65
|
-
s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
|
66
|
-
s.add_development_dependency(%q<mocha>, [">= 0"])
|
67
|
-
s.add_development_dependency(%q<rails>, [">= 0"])
|
68
|
-
s.add_development_dependency(%q<rcov>, [">= 0"])
|
69
|
-
s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
|
70
|
-
s.add_development_dependency(%q<sinatra>, [">= 0"])
|
71
|
-
s.add_development_dependency(%q<sqlite3>, [">= 0"])
|
72
|
-
s.add_development_dependency(%q<shoulda-matchers>, ["= 1.0.0.beta1"])
|
73
|
-
s.add_development_dependency(%q<yard>, ["~> 0.6.0"])
|
74
|
-
else
|
75
|
-
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
76
|
-
s.add_dependency(%q<database_cleaner>, [">= 0"])
|
77
|
-
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
78
|
-
s.add_dependency(%q<mocha>, [">= 0"])
|
79
|
-
s.add_dependency(%q<rails>, [">= 0"])
|
80
|
-
s.add_dependency(%q<rcov>, [">= 0"])
|
81
|
-
s.add_dependency(%q<rspec>, ["~> 2.3.0"])
|
82
|
-
s.add_dependency(%q<sinatra>, [">= 0"])
|
83
|
-
s.add_dependency(%q<sqlite3>, [">= 0"])
|
84
|
-
s.add_dependency(%q<shoulda-matchers>, ["= 1.0.0.beta1"])
|
85
|
-
s.add_dependency(%q<yard>, ["~> 0.6.0"])
|
86
|
-
end
|
87
|
-
else
|
88
|
-
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
89
|
-
s.add_dependency(%q<database_cleaner>, [">= 0"])
|
90
|
-
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
91
|
-
s.add_dependency(%q<mocha>, [">= 0"])
|
92
|
-
s.add_dependency(%q<rails>, [">= 0"])
|
93
|
-
s.add_dependency(%q<rcov>, [">= 0"])
|
94
|
-
s.add_dependency(%q<rspec>, ["~> 2.3.0"])
|
95
|
-
s.add_dependency(%q<sinatra>, [">= 0"])
|
96
|
-
s.add_dependency(%q<sqlite3>, [">= 0"])
|
97
|
-
s.add_dependency(%q<shoulda-matchers>, ["= 1.0.0.beta1"])
|
98
|
-
s.add_dependency(%q<yard>, ["~> 0.6.0"])
|
99
|
-
end
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.add_dependency('activesupport', '>= 2.3.10')
|
21
|
+
s.add_dependency('redis', '>= 2.2.2')
|
22
|
+
s.add_dependency('sinatra', '>= 0')
|
100
23
|
end
|
101
|
-
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class TestObject
|
4
|
+
include Monocle
|
5
|
+
|
6
|
+
monocle_options cache_view_counts: true
|
7
|
+
|
8
|
+
monocle_views overall: -> { 'overall' },
|
9
|
+
yearly: -> { Time.now.beginning_of_year },
|
10
|
+
monthly: -> { Time.now.beginning_of_month },
|
11
|
+
weekly: -> { Time.now.beginning_of_week },
|
12
|
+
daily: -> { Time.now.beginning_of_day },
|
13
|
+
hourly: -> { Time.now.beginning_of_hour },
|
14
|
+
quarterly: -> { Time.now.beginning_of_quarter }
|
15
|
+
|
16
|
+
attr_accessor :id
|
17
|
+
attr_accessor :overall_views, :yearly_views, :monthly_views
|
18
|
+
attr_accessor :weekly_views, :daily_views, :hourly_views
|
19
|
+
attr_accessor :quarterly_views
|
20
|
+
|
21
|
+
def self.find(id)
|
22
|
+
o = new
|
23
|
+
o.id = id.to_i
|
24
|
+
o
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize
|
28
|
+
@id = '12345'
|
29
|
+
@overall_views = 0
|
30
|
+
end
|
31
|
+
|
32
|
+
def update_column(field, count)
|
33
|
+
self.send("#{field}=", count)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe Monocle do
|
38
|
+
let(:object) { TestObject.new }
|
39
|
+
|
40
|
+
describe '#most_viewed_since' do
|
41
|
+
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
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'returns top viewed objects since a given time' do
|
52
|
+
viewed = TestObject.most_viewed_since(Time.now.beginning_of_day)
|
53
|
+
viewed.class.should == Array
|
54
|
+
viewed.first.id.should == TestObject.find(9).id
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#destroy_views' do
|
59
|
+
it 'deletes views for object' do
|
60
|
+
object.view!
|
61
|
+
object.destroy_views
|
62
|
+
REDIS.hget('monocle:test_object:12345', 'overall_views').should == nil
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '#view!' do
|
67
|
+
before do
|
68
|
+
50.times { object.view! }
|
69
|
+
end
|
70
|
+
|
71
|
+
after do
|
72
|
+
object.destroy_views
|
73
|
+
end
|
74
|
+
|
75
|
+
%w(overall yearly monthly weekly daily hourly quarterly).each do |view_type|
|
76
|
+
it "sets #{view_type} views count" do
|
77
|
+
object.send("#{view_type}_views_count").should == 50
|
78
|
+
end
|
79
|
+
|
80
|
+
it "updates cached #{view_type} views count" do
|
81
|
+
object.send("#{view_type}_views").should == 50
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,44 +1,21 @@
|
|
1
1
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
2
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
-
require "rspec"
|
4
|
-
require "monocle"
|
5
3
|
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
|
11
|
-
ActiveRecord::Migration.verbose = false
|
12
|
-
ActiveRecord::Base.logger = Logger.new(nil)
|
13
|
-
|
14
|
-
ActiveRecord::Base.establish_connection(
|
15
|
-
:adapter => "sqlite3",
|
16
|
-
:database => File.expand_path("spec/db/monocle.sqlite3"))
|
17
|
-
|
18
|
-
ActiveRecord::Migrator.migrate(File.expand_path("spec/db/migrate"))
|
4
|
+
require 'rspec'
|
5
|
+
require 'monocle'
|
6
|
+
require 'rails'
|
7
|
+
require 'redis'
|
19
8
|
|
20
9
|
# Requires supporting files with custom matchers and macros, etc,
|
21
10
|
# in ./support/ and its subdirectories.
|
22
11
|
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
23
12
|
|
24
|
-
|
25
|
-
values = {
|
26
|
-
:viewable_type => "Viewable",
|
27
|
-
:viewable_id => 1,
|
28
|
-
:viewed_on_start_date => Time.now - 10.days,
|
29
|
-
:views => 10 }.merge(options)
|
30
|
-
|
31
|
-
%w( Daily Weekly Monthly Yearly Overall ).each do |time_span|
|
32
|
-
klass = "Monocle::#{time_span}View".constantize
|
33
|
-
klass.create(values)
|
34
|
-
end
|
35
|
-
end
|
13
|
+
REDIS = Redis.new
|
36
14
|
|
37
15
|
RSpec.configure do |config|
|
38
|
-
config.mock_with :mocha
|
39
|
-
|
40
16
|
config.after(:each) do
|
41
|
-
|
42
|
-
|
17
|
+
REDIS.keys('monocle*').each do |key|
|
18
|
+
REDIS.del(key)
|
19
|
+
end
|
43
20
|
end
|
44
21
|
end
|
metadata
CHANGED
@@ -1,208 +1,114 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: monocle
|
3
|
-
version: !ruby/object:Gem::Version
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
4
5
|
prerelease:
|
5
|
-
version: 0.1.1
|
6
6
|
platform: ruby
|
7
|
-
authors:
|
8
|
-
- Russ Smith
|
7
|
+
authors:
|
8
|
+
- Russ Smith (russ@bashme.org)
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
name: bundler
|
18
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
12
|
+
date: 2011-08-31 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activesupport
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
19
17
|
none: false
|
20
|
-
requirements:
|
21
|
-
- -
|
22
|
-
- !ruby/object:Gem::Version
|
23
|
-
version:
|
24
|
-
type: :
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.3.10
|
22
|
+
type: :runtime
|
25
23
|
prerelease: false
|
26
|
-
version_requirements:
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: database_cleaner
|
29
|
-
requirement: &id002 !ruby/object:Gem::Requirement
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
25
|
none: false
|
31
|
-
requirements:
|
32
|
-
- -
|
33
|
-
- !ruby/object:Gem::Version
|
34
|
-
version:
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
- !ruby/object:Gem::Dependency
|
39
|
-
name: jeweler
|
40
|
-
requirement: &id003 !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
|
-
requirements:
|
43
|
-
- - ~>
|
44
|
-
- !ruby/object:Gem::Version
|
45
|
-
version: 1.5.2
|
46
|
-
type: :development
|
47
|
-
prerelease: false
|
48
|
-
version_requirements: *id003
|
49
|
-
- !ruby/object:Gem::Dependency
|
50
|
-
name: mocha
|
51
|
-
requirement: &id004 !ruby/object:Gem::Requirement
|
52
|
-
none: false
|
53
|
-
requirements:
|
54
|
-
- - ">="
|
55
|
-
- !ruby/object:Gem::Version
|
56
|
-
version: "0"
|
57
|
-
type: :development
|
58
|
-
prerelease: false
|
59
|
-
version_requirements: *id004
|
60
|
-
- !ruby/object:Gem::Dependency
|
61
|
-
name: rails
|
62
|
-
requirement: &id005 !ruby/object:Gem::Requirement
|
63
|
-
none: false
|
64
|
-
requirements:
|
65
|
-
- - ">="
|
66
|
-
- !ruby/object:Gem::Version
|
67
|
-
version: "0"
|
68
|
-
type: :development
|
69
|
-
prerelease: false
|
70
|
-
version_requirements: *id005
|
71
|
-
- !ruby/object:Gem::Dependency
|
72
|
-
name: rcov
|
73
|
-
requirement: &id006 !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 2.3.10
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: redis
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
74
33
|
none: false
|
75
|
-
requirements:
|
76
|
-
- -
|
77
|
-
- !ruby/object:Gem::Version
|
78
|
-
version:
|
79
|
-
type: :
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 2.2.2
|
38
|
+
type: :runtime
|
80
39
|
prerelease: false
|
81
|
-
version_requirements:
|
82
|
-
- !ruby/object:Gem::Dependency
|
83
|
-
name: rspec
|
84
|
-
requirement: &id007 !ruby/object:Gem::Requirement
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
41
|
none: false
|
86
|
-
requirements:
|
87
|
-
- -
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: 2.
|
90
|
-
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: *id007
|
93
|
-
- !ruby/object:Gem::Dependency
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 2.2.2
|
46
|
+
- !ruby/object:Gem::Dependency
|
94
47
|
name: sinatra
|
95
|
-
requirement:
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
96
49
|
none: false
|
97
|
-
requirements:
|
98
|
-
- -
|
99
|
-
- !ruby/object:Gem::Version
|
100
|
-
version:
|
101
|
-
type: :
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
102
55
|
prerelease: false
|
103
|
-
version_requirements:
|
104
|
-
- !ruby/object:Gem::Dependency
|
105
|
-
name: sqlite3
|
106
|
-
requirement: &id009 !ruby/object:Gem::Requirement
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
57
|
none: false
|
108
|
-
requirements:
|
109
|
-
- -
|
110
|
-
- !ruby/object:Gem::Version
|
111
|
-
version:
|
112
|
-
type: :development
|
113
|
-
prerelease: false
|
114
|
-
version_requirements: *id009
|
115
|
-
- !ruby/object:Gem::Dependency
|
116
|
-
name: shoulda-matchers
|
117
|
-
requirement: &id010 !ruby/object:Gem::Requirement
|
118
|
-
none: false
|
119
|
-
requirements:
|
120
|
-
- - "="
|
121
|
-
- !ruby/object:Gem::Version
|
122
|
-
version: 1.0.0.beta1
|
123
|
-
type: :development
|
124
|
-
prerelease: false
|
125
|
-
version_requirements: *id010
|
126
|
-
- !ruby/object:Gem::Dependency
|
127
|
-
name: yard
|
128
|
-
requirement: &id011 !ruby/object:Gem::Requirement
|
129
|
-
none: false
|
130
|
-
requirements:
|
131
|
-
- - ~>
|
132
|
-
- !ruby/object:Gem::Version
|
133
|
-
version: 0.6.0
|
134
|
-
type: :development
|
135
|
-
prerelease: false
|
136
|
-
version_requirements: *id011
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
137
62
|
description: A history of view events.
|
138
63
|
email: russ@bashme.org
|
139
64
|
executables: []
|
140
|
-
|
141
65
|
extensions: []
|
142
|
-
|
143
|
-
|
144
|
-
- LICENSE.txt
|
145
|
-
- README.rdoc
|
146
|
-
files:
|
66
|
+
extra_rdoc_files: []
|
67
|
+
files:
|
147
68
|
- .document
|
69
|
+
- .gitignore
|
148
70
|
- .rspec
|
71
|
+
- .travis.yml
|
149
72
|
- Gemfile
|
73
|
+
- Guardfile
|
150
74
|
- LICENSE.txt
|
151
75
|
- README.rdoc
|
152
76
|
- Rakefile
|
153
77
|
- VERSION
|
154
|
-
- lib/generators/monocle/install_generator.rb
|
155
|
-
- lib/generators/monocle/templates/migration.rb
|
156
78
|
- lib/monocle.rb
|
157
|
-
- lib/monocle/
|
158
|
-
- lib/monocle/monthly_view.rb
|
159
|
-
- lib/monocle/overall_view.rb
|
79
|
+
- lib/monocle/core_ext.rb
|
160
80
|
- lib/monocle/server.rb
|
161
|
-
- lib/monocle/
|
162
|
-
- lib/monocle/views.rb
|
163
|
-
- lib/monocle/weekly_view.rb
|
164
|
-
- lib/monocle/yearly_view.rb
|
81
|
+
- lib/monocle/version.rb
|
165
82
|
- monocle.gemspec
|
166
|
-
- spec/
|
167
|
-
- spec/db/migrate/20110502223022_create_monocle_views.rb
|
83
|
+
- spec/monocle_spec.rb
|
168
84
|
- spec/spec_helper.rb
|
169
|
-
- spec/view_spec.rb
|
170
|
-
- spec/viewable_spec.rb
|
171
85
|
- transparent.gif
|
172
|
-
has_rdoc: true
|
173
86
|
homepage: http://github.com/russ/monocle
|
174
|
-
licenses:
|
87
|
+
licenses:
|
175
88
|
- MIT
|
176
89
|
post_install_message:
|
177
90
|
rdoc_options: []
|
178
|
-
|
179
|
-
require_paths:
|
91
|
+
require_paths:
|
180
92
|
- lib
|
181
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
182
94
|
none: false
|
183
|
-
requirements:
|
184
|
-
- -
|
185
|
-
- !ruby/object:Gem::Version
|
186
|
-
|
187
|
-
|
188
|
-
- 0
|
189
|
-
version: "0"
|
190
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
191
100
|
none: false
|
192
|
-
requirements:
|
193
|
-
- -
|
194
|
-
- !ruby/object:Gem::Version
|
195
|
-
version:
|
101
|
+
requirements:
|
102
|
+
- - ! '>='
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
196
105
|
requirements: []
|
197
|
-
|
198
106
|
rubyforge_project:
|
199
|
-
rubygems_version: 1.
|
107
|
+
rubygems_version: 1.8.23
|
200
108
|
signing_key:
|
201
109
|
specification_version: 3
|
202
110
|
summary: A history of view events.
|
203
|
-
test_files:
|
204
|
-
- spec/
|
205
|
-
- spec/db/migrate/20110502223022_create_monocle_views.rb
|
111
|
+
test_files:
|
112
|
+
- spec/monocle_spec.rb
|
206
113
|
- spec/spec_helper.rb
|
207
|
-
|
208
|
-
- spec/viewable_spec.rb
|
114
|
+
has_rdoc:
|
@@ -1,17 +0,0 @@
|
|
1
|
-
require "rails/generators/active_record"
|
2
|
-
|
3
|
-
module Monocle
|
4
|
-
class InstallGenerator < Rails::Generators::Base
|
5
|
-
include Rails::Generators::Migration
|
6
|
-
|
7
|
-
source_root File.expand_path("../templates", __FILE__)
|
8
|
-
|
9
|
-
def copy_migration
|
10
|
-
migration_template "migration.rb", "db/migrate/create_monocle_views.rb"
|
11
|
-
end
|
12
|
-
|
13
|
-
def self.next_migration_number(dirname)
|
14
|
-
ActiveRecord::Generators::Base.next_migration_number(dirname)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
class CreateMonocleViews < ActiveRecord::Migration
|
2
|
-
def self.up
|
3
|
-
create_table :views do |t|
|
4
|
-
t.string :viewable_type
|
5
|
-
t.integer :viewable_id
|
6
|
-
t.string :type
|
7
|
-
t.date :viewed_on_start_date
|
8
|
-
t.integer :views, :default => 0
|
9
|
-
t.timestamps
|
10
|
-
end
|
11
|
-
|
12
|
-
add_index :views, [ :type, :viewable_type, :viewable_id, :views ], :name => :viewable_type_views
|
13
|
-
add_index :views, [ :type, :viewable_type, :viewed_on_start_date, :viewable_id, :views ], :name => :viewable_type_start_date_views
|
14
|
-
add_index :views, [ :type, :viewable_type, :viewed_on_start_date, :viewable_id ], :name => :unique_time_period_views, :unique => true
|
15
|
-
add_index :views, [ :viewable_type, :viewable_id], :name => :index_views_on_viewable_type_and_viewable_id
|
16
|
-
end
|
17
|
-
|
18
|
-
def self.down
|
19
|
-
drop_table :views
|
20
|
-
end
|
21
|
-
end
|
data/lib/monocle/daily_view.rb
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
module Monocle
|
2
|
-
class DailyView < View
|
3
|
-
validates_presence_of :viewed_on_start_date
|
4
|
-
|
5
|
-
def view!
|
6
|
-
%w( Weekly Monthly Yearly Overall ).each do |time_span|
|
7
|
-
klass = "Monocle::#{time_span}View".constantize
|
8
|
-
start_date = nil
|
9
|
-
|
10
|
-
unless time_span == "Overall"
|
11
|
-
time_span.downcase!.gsub!("ly", "")
|
12
|
-
start_date = viewed_on_start_date.send("beginning_of_#{time_span}")
|
13
|
-
end
|
14
|
-
|
15
|
-
view = klass.find_or_create_by_viewable_type_and_viewable_id_and_viewed_on_start_date(viewable_type, viewable_id, start_date)
|
16
|
-
view.view!
|
17
|
-
end
|
18
|
-
|
19
|
-
super
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
data/lib/monocle/monthly_view.rb
DELETED
data/lib/monocle/overall_view.rb
DELETED
data/lib/monocle/view.rb
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
module Monocle
|
2
|
-
class View < ActiveRecord::Base
|
3
|
-
belongs_to :viewable, :polymorphic => true
|
4
|
-
|
5
|
-
validates_presence_of :viewable_id
|
6
|
-
validates_presence_of :viewable_type
|
7
|
-
|
8
|
-
def self.by_type(type)
|
9
|
-
where(:type => type)
|
10
|
-
end
|
11
|
-
|
12
|
-
def self.since(since, options = {})
|
13
|
-
options[:limit] ||= 10
|
14
|
-
options[:order] ||= "SUM(views) DESC"
|
15
|
-
options[:type] ||= :yearly
|
16
|
-
|
17
|
-
by_type("Monocle::" + options[:type].to_s.classify + "View")
|
18
|
-
.where("viewed_on_start_date >= ?", Date.today - since)
|
19
|
-
.group("viewable_type, viewable_id")
|
20
|
-
.order(options[:order])
|
21
|
-
.limit(options[:limit])
|
22
|
-
end
|
23
|
-
|
24
|
-
def self.all_time(options = {})
|
25
|
-
options[:limit] ||= 10
|
26
|
-
options[:order] ||= "views DESC"
|
27
|
-
|
28
|
-
by_type("Monocle::OverallView")
|
29
|
-
.group("viewable_type, viewable_id")
|
30
|
-
.order(options[:order])
|
31
|
-
.limit(options[:limit])
|
32
|
-
end
|
33
|
-
|
34
|
-
def view!
|
35
|
-
increment!(:views)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
data/lib/monocle/views.rb
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
module Monocle
|
2
|
-
module Views
|
3
|
-
extend ActiveSupport::Concern
|
4
|
-
|
5
|
-
included do
|
6
|
-
has_one :overall_view, :as => :viewable, :dependent => :destroy, :class_name => "Monocle::OverallView"
|
7
|
-
|
8
|
-
%w( daily weekly monthly yearly ).each do |time_span|
|
9
|
-
has_many "#{time_span}_views".to_sym, :as => :viewable, :dependent => :destroy, :class_name => "Monocle::#{time_span.classify}View"
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
module ClassMethods
|
14
|
-
def viewed_since(since, options = {})
|
15
|
-
View.since(since, { :viewable_type => self }.merge(options))
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
delegate :views, :to => :overall_view, :prefix => :overall, :allow_nil => true
|
20
|
-
|
21
|
-
def view!
|
22
|
-
view = DailyView.find_or_create_by_viewable_type_and_viewable_id_and_viewed_on_start_date(self.class.name, id, Date.today)
|
23
|
-
view.view!
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
data/lib/monocle/weekly_view.rb
DELETED
data/lib/monocle/yearly_view.rb
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
class CreateMonocleViews < ActiveRecord::Migration
|
2
|
-
def self.up
|
3
|
-
create_table :views do |t|
|
4
|
-
t.string :viewable_type
|
5
|
-
t.integer :viewable_id
|
6
|
-
t.string :type
|
7
|
-
t.date :viewed_on_start_date
|
8
|
-
t.integer :views, :default => 0
|
9
|
-
t.timestamps
|
10
|
-
end
|
11
|
-
|
12
|
-
add_index :views, [ :type, :viewable_type, :viewable_id, :views ], :name => :viewable_type_views
|
13
|
-
add_index :views, [ :type, :viewable_type, :viewed_on_start_date, :viewable_id, :views ], :name => :viewable_type_start_date_views
|
14
|
-
add_index :views, [ :type, :viewable_type, :viewed_on_start_date, :viewable_id ], :name => :unique_time_period_views, :unique => true
|
15
|
-
add_index :views, [ :viewable_type, :viewable_id], :name => :index_views_on_viewable_type_and_viewable_id
|
16
|
-
end
|
17
|
-
|
18
|
-
def self.down
|
19
|
-
drop_table :views
|
20
|
-
end
|
21
|
-
end
|
data/spec/view_spec.rb
DELETED
@@ -1,36 +0,0 @@
|
|
1
|
-
require "spec_helper"
|
2
|
-
|
3
|
-
include Monocle
|
4
|
-
|
5
|
-
describe View do
|
6
|
-
describe "since" do
|
7
|
-
before do
|
8
|
-
50.times do |i|
|
9
|
-
create_view(
|
10
|
-
:viewable_id => i,
|
11
|
-
:viewed_on_start_date => Time.now - i.days,
|
12
|
-
:views => rand(100))
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
# TODO: Find a decent way to test this.
|
17
|
-
it "returns most viewed objects since the given date" do
|
18
|
-
View.since(7.days).all.count.should == 8
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
describe "all_time" do
|
23
|
-
before do
|
24
|
-
50.times do |i|
|
25
|
-
create_view(
|
26
|
-
:viewable_id => i,
|
27
|
-
:viewed_on_start_date => Time.now - i.days,
|
28
|
-
:views => rand(100))
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
it "returns most viewed objects of all time" do
|
33
|
-
View.all_time.all.count.should == 10
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
data/spec/viewable_spec.rb
DELETED
@@ -1,36 +0,0 @@
|
|
1
|
-
require "spec_helper"
|
2
|
-
|
3
|
-
class Viewable < ActiveRecord::Base
|
4
|
-
include Monocle::Views
|
5
|
-
end
|
6
|
-
|
7
|
-
describe Viewable do
|
8
|
-
subject { Viewable.new }
|
9
|
-
|
10
|
-
it { should have_one(:overall_view) }
|
11
|
-
it { should have_many(:daily_views) }
|
12
|
-
it { should have_many(:weekly_views) }
|
13
|
-
it { should have_many(:monthly_views) }
|
14
|
-
it { should have_many(:yearly_views) }
|
15
|
-
|
16
|
-
describe "view!" do
|
17
|
-
before do
|
18
|
-
subject.save!
|
19
|
-
end
|
20
|
-
|
21
|
-
it "should increment overall view count" do
|
22
|
-
subject.view!
|
23
|
-
subject.overall_views.should == 1
|
24
|
-
end
|
25
|
-
|
26
|
-
%w( daily weekly monthly yearly ).each do |time_span|
|
27
|
-
it "should increment #{time_span} view count" do
|
28
|
-
5.times do |i|
|
29
|
-
view = "#{time_span}_views"
|
30
|
-
subject.view!
|
31
|
-
subject.send(view).first.views.should == i + 1
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|