monocle 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|