acts_as_mongo_rateable 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/MIT-LICENSE +20 -0
- data/Manifest +13 -0
- data/README.markdown +88 -0
- data/Rakefile +35 -0
- data/acts_as_mongo_rateable.gemspec +33 -0
- data/install.rb +1 -0
- data/lib/acts_as_mongo_rateable/acts_as_mongo_rateable.rb +109 -0
- data/lib/acts_as_mongo_rateable/app/models/rating.rb +72 -0
- data/lib/mongo_rateable.rb +1 -0
- data/rails/init.rb +2 -0
- data/tasks/mongo_rateable_tasks.rake +4 -0
- data/test/mongo_rateable_test.rb +171 -0
- data/test/test_helper.rb +58 -0
- data/uninstall.rb +1 -0
- data.tar.gz.sig +3 -0
- metadata +112 -0
- metadata.gz.sig +0 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 [name of plugin creator]
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Manifest
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
MIT-LICENSE
|
2
|
+
README.markdown
|
3
|
+
Rakefile
|
4
|
+
install.rb
|
5
|
+
lib/acts_as_mongo_rateable/acts_as_mongo_rateable.rb
|
6
|
+
lib/acts_as_mongo_rateable/app/models/rating.rb
|
7
|
+
lib/mongo_rateable.rb
|
8
|
+
rails/init.rb
|
9
|
+
tasks/mongo_rateable_tasks.rake
|
10
|
+
test/mongo_rateable_test.rb
|
11
|
+
test/test_helper.rb
|
12
|
+
uninstall.rb
|
13
|
+
Manifest
|
data/README.markdown
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
ActsAsMongoRateable (with weights!)
|
2
|
+
===================================
|
3
|
+
|
4
|
+
Inspired by the old Rails+AR standby "acts_as_rateable," this rating plugin works with MongoDB+MongoMapper
|
5
|
+
and has weighted ratings, as well as bayesian and straight averages, and some friendly class-level helpers.
|
6
|
+
|
7
|
+
Intends to be super-performant by taking advantage of the benefits of document-driven db denormalization.
|
8
|
+
|
9
|
+
Requirements
|
10
|
+
------------
|
11
|
+
|
12
|
+
- MongoDB
|
13
|
+
- MongoMapper gem
|
14
|
+
- Expects you to have a User model that includes MongoMapper::Document
|
15
|
+
|
16
|
+
Installation
|
17
|
+
------------
|
18
|
+
|
19
|
+
Install the plugin:
|
20
|
+
|
21
|
+
./script/plugin install git://github.com/mepatterson/acts_as_mongo_rateable.git
|
22
|
+
|
23
|
+
Add the following 2 lines to the Model class that you want to make rateable:
|
24
|
+
|
25
|
+
include ActsAsMongoRateable
|
26
|
+
RATING_RANGE = (1..5)
|
27
|
+
|
28
|
+
Obviously, change the rating range if you want to rate on a 10-star system or a 14-star or whatever.
|
29
|
+
|
30
|
+
Usage
|
31
|
+
-----
|
32
|
+
|
33
|
+
class User
|
34
|
+
include MongoMapper::Document
|
35
|
+
end
|
36
|
+
|
37
|
+
class Widget
|
38
|
+
include ActsAsMongoRateable
|
39
|
+
RATING_RANGE = (1..5)
|
40
|
+
include MongoMapper::Document
|
41
|
+
end
|
42
|
+
|
43
|
+
widget = Widget.first
|
44
|
+
|
45
|
+
To rate it:
|
46
|
+
|
47
|
+
widget.rate(score, user, weight)
|
48
|
+
|
49
|
+
- score must be an Integer within your RATING_RANGE
|
50
|
+
- user is the User who is rating this widget
|
51
|
+
- weight is optional; defaults to 1)
|
52
|
+
|
53
|
+
Now try all these fun methods:
|
54
|
+
|
55
|
+
widget.average_rating
|
56
|
+
|
57
|
+
widget.bayesian_rating
|
58
|
+
|
59
|
+
widget.rating_stats
|
60
|
+
|
61
|
+
And some useful class methods:
|
62
|
+
|
63
|
+
Widget.highest_rated(how_many)
|
64
|
+
|
65
|
+
Widget.most_rated(how_many)
|
66
|
+
|
67
|
+
Widget.most_rated_by_authorities(how_many)
|
68
|
+
|
69
|
+
Widget.highest_bayesian_rated(how_many)
|
70
|
+
|
71
|
+
('how_many' is a limit and is optional. i.e. Do you want a highest_rated list of 5, 10, 15 widgets?
|
72
|
+
Defaults to just 1 if you don't pass any argument.)
|
73
|
+
|
74
|
+
Future
|
75
|
+
------
|
76
|
+
- Tests (I have tests in the project I cut this from, but they need to be extracted out and I'm lazy)
|
77
|
+
- More helper methods
|
78
|
+
- Performance improvements as I come across the need
|
79
|
+
- Investigate using map/reduce to improve the efficiency of the bayesian calc (?)
|
80
|
+
|
81
|
+
Thanks To...
|
82
|
+
------------
|
83
|
+
- John Nunemaker and the rest of the folks on the MongoMapper Google Group
|
84
|
+
- The MongoDB peoples and the MongoDB Google Group
|
85
|
+
- juixe for the original acts_as_rateable plugin for ActiveRecord
|
86
|
+
- sunlightlabs 'datacatalog-api', from which I borrowed the ratings_stats hash methodology
|
87
|
+
|
88
|
+
Copyright (c) 2009 [M. E. Patterson], released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'echoe'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rake/rdoctask'
|
6
|
+
|
7
|
+
desc 'Default: run unit tests.'
|
8
|
+
task :default => :test
|
9
|
+
|
10
|
+
desc 'Test the mongo_rateable plugin.'
|
11
|
+
Rake::TestTask.new(:test) do |t|
|
12
|
+
t.libs << 'lib'
|
13
|
+
t.libs << 'test'
|
14
|
+
t.pattern = 'test/**/*_test.rb'
|
15
|
+
t.verbose = true
|
16
|
+
end
|
17
|
+
|
18
|
+
desc 'Generate documentation for the mongo_rateable plugin.'
|
19
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
20
|
+
rdoc.rdoc_dir = 'rdoc'
|
21
|
+
rdoc.title = 'MongoRateable'
|
22
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
23
|
+
rdoc.rdoc_files.include('README')
|
24
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
Echoe.new('acts_as_mongo_rateable', '0.2.0') do |p|
|
29
|
+
p.description = "A ratings system for Rails apps using MongoDB, with bayesian and straight averages, and weighting."
|
30
|
+
p.url = "http://github.com/mepatterson/acts_as_mongo_rateable"
|
31
|
+
p.author = "M. E. Patterson"
|
32
|
+
p.email = "madraziel @nospam@ gmail.com"
|
33
|
+
p.ignore_pattern = ["tmp/*", "script/*"]
|
34
|
+
p.development_dependencies = []
|
35
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{acts_as_mongo_rateable}
|
5
|
+
s.version = "0.2.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["M. E. Patterson"]
|
9
|
+
s.cert_chain = ["/Users/mattp/gem-public_cert.pem"]
|
10
|
+
s.date = %q{2010-06-04}
|
11
|
+
s.description = %q{A ratings system for Rails apps using MongoDB, with bayesian and straight averages, and weighting.}
|
12
|
+
s.email = %q{madraziel @nospam@ gmail.com}
|
13
|
+
s.extra_rdoc_files = ["README.markdown", "lib/acts_as_mongo_rateable/acts_as_mongo_rateable.rb", "lib/acts_as_mongo_rateable/app/models/rating.rb", "lib/mongo_rateable.rb", "tasks/mongo_rateable_tasks.rake"]
|
14
|
+
s.files = ["MIT-LICENSE", "README.markdown", "Rakefile", "install.rb", "lib/acts_as_mongo_rateable/acts_as_mongo_rateable.rb", "lib/acts_as_mongo_rateable/app/models/rating.rb", "lib/mongo_rateable.rb", "rails/init.rb", "tasks/mongo_rateable_tasks.rake", "test/mongo_rateable_test.rb", "test/test_helper.rb", "uninstall.rb", "Manifest", "acts_as_mongo_rateable.gemspec"]
|
15
|
+
s.homepage = %q{http://github.com/mepatterson/acts_as_mongo_rateable}
|
16
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Acts_as_mongo_rateable", "--main", "README.markdown"]
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
s.rubyforge_project = %q{acts_as_mongo_rateable}
|
19
|
+
s.rubygems_version = %q{1.3.7}
|
20
|
+
s.signing_key = %q{/Users/mattp/gem-private_key.pem}
|
21
|
+
s.summary = %q{A ratings system for Rails apps using MongoDB, with bayesian and straight averages, and weighting.}
|
22
|
+
s.test_files = ["test/mongo_rateable_test.rb", "test/test_helper.rb"]
|
23
|
+
|
24
|
+
if s.respond_to? :specification_version then
|
25
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
26
|
+
s.specification_version = 3
|
27
|
+
|
28
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
29
|
+
else
|
30
|
+
end
|
31
|
+
else
|
32
|
+
end
|
33
|
+
end
|
data/install.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Install hook code here
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module ActsAsMongoRateable
|
2
|
+
|
3
|
+
module ClassMethods
|
4
|
+
def highest_rated(limit=1)
|
5
|
+
all({:order => "rating_stats.average DESC", :limit => limit})
|
6
|
+
end
|
7
|
+
|
8
|
+
def most_rated(limit=1)
|
9
|
+
all({:order => "rating_stats.count DESC", :limit => limit})
|
10
|
+
end
|
11
|
+
|
12
|
+
def most_rated_by_authorities(limit=1)
|
13
|
+
all({:order => "rating_stats.sum_of_weights DESC", :limit => limit})
|
14
|
+
end
|
15
|
+
|
16
|
+
# TO DO this is awful, awful, awful! make it faster using map/reduce
|
17
|
+
def highest_bayesian_rated(limit=1)
|
18
|
+
stats = all({:select => 'id, rating_stats'})
|
19
|
+
all.sort_by do |doc|
|
20
|
+
rating = doc.bayesian_rating(stats)
|
21
|
+
doc.rating_stats['bayesian_rating'] = rating
|
22
|
+
rating
|
23
|
+
end.reverse[0,limit]
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
module InstanceMethods
|
29
|
+
|
30
|
+
def delete_all_ratings
|
31
|
+
ratings.delete_all
|
32
|
+
end
|
33
|
+
|
34
|
+
def average_rating
|
35
|
+
rating_stats['average']
|
36
|
+
end
|
37
|
+
|
38
|
+
def bayesian_rating(stats=nil)
|
39
|
+
return 0 if rating_stats['count'] == 0
|
40
|
+
stats ||= self.class.all({:select => 'id, rating_stats'})
|
41
|
+
system_counts = stats.map{ |p| [ p.id.to_s, p.rating_stats['count'] ] }
|
42
|
+
avg_rating = stats.map{|p| p.rating_stats['average'] || 0 }.sum / stats.size.to_f
|
43
|
+
avg_num_votes = system_counts.inject(0){|sum, r| sum += r.to_a.flatten[1] } / system_counts.size.to_f
|
44
|
+
my_rating = rating_stats['average'] || 0
|
45
|
+
my_count = rating_stats['count']
|
46
|
+
( (avg_num_votes * avg_rating) + (my_count * my_rating) ) / (avg_num_votes + my_count)
|
47
|
+
end
|
48
|
+
|
49
|
+
def delete_ratings_by_user(user)
|
50
|
+
return false unless user
|
51
|
+
return 0 if ratings.blank?
|
52
|
+
ratings.delete_all(:user_id => user.id.to_s)
|
53
|
+
self.reload
|
54
|
+
end
|
55
|
+
|
56
|
+
def rate(value, user = nil, weight = 1)
|
57
|
+
delete_ratings_by_user(user)
|
58
|
+
validate_rating!(value)
|
59
|
+
r = Rating.new({
|
60
|
+
:value => value,
|
61
|
+
:user_id => user.id,
|
62
|
+
:rateable_id => self.id,
|
63
|
+
:rateable_class => self.class.to_s,
|
64
|
+
:weight => weight
|
65
|
+
})
|
66
|
+
self.ratings << r
|
67
|
+
self.reload
|
68
|
+
r
|
69
|
+
end
|
70
|
+
|
71
|
+
# returns the Rating object found if user has rated this project, else returns nil
|
72
|
+
def rated_by_user?(user)
|
73
|
+
return false unless user
|
74
|
+
ratings.detect{ |r| r.user_id == user.id}
|
75
|
+
end
|
76
|
+
|
77
|
+
protected
|
78
|
+
|
79
|
+
def validate_rating!(value)
|
80
|
+
if (range = self.class::RATING_RANGE) and !range.include?(value.to_i)
|
81
|
+
raise ArgumentError, "Rating not in range #{range}. Rating provided was #{value}."
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.included(receiver)
|
88
|
+
receiver.class_eval do
|
89
|
+
many :ratings, :foreign_key => 'rateable_id', :dependent => :destroy
|
90
|
+
key :rating_stats, Hash, :default => {
|
91
|
+
:total => 0,
|
92
|
+
:count => 0,
|
93
|
+
:sum_of_weights => 0,
|
94
|
+
:average => nil
|
95
|
+
}
|
96
|
+
end
|
97
|
+
receiver.extend ClassMethods
|
98
|
+
receiver.send :include, InstanceMethods
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
%w{ models observers }.each do |dir|
|
105
|
+
path = File.join(File.dirname(__FILE__), 'app', dir)
|
106
|
+
$LOAD_PATH << path
|
107
|
+
ActiveSupport::Dependencies.load_paths << path
|
108
|
+
ActiveSupport::Dependencies.load_once_paths.delete(path)
|
109
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
class Rating
|
2
|
+
include MongoMapper::Document
|
3
|
+
|
4
|
+
key :user_id, ObjectId, :required => true
|
5
|
+
key :rateable_id, ObjectId, :required => true
|
6
|
+
key :rateable_class, String, :required => true
|
7
|
+
key :value, Integer, :required => true
|
8
|
+
key :previous_value, Integer, :default => 0
|
9
|
+
key :weight, Integer, :default => 1
|
10
|
+
timestamps!
|
11
|
+
|
12
|
+
ensure_index :user_id
|
13
|
+
ensure_index :rateable_id
|
14
|
+
ensure_index :rateable_class
|
15
|
+
ensure_index :created_at
|
16
|
+
|
17
|
+
belongs_to :user
|
18
|
+
|
19
|
+
after_create :set_rating_stats
|
20
|
+
after_update :update_rating_stats
|
21
|
+
before_update :set_previous_value
|
22
|
+
after_destroy :reduce_rating_stats
|
23
|
+
|
24
|
+
def set_rating_stats
|
25
|
+
doc = find_rated_document!
|
26
|
+
count = (doc.rating_stats[:count] += 1)
|
27
|
+
total = (doc.rating_stats[:total] += ( value * weight ))
|
28
|
+
sow = (doc.rating_stats[:sum_of_weights] += weight)
|
29
|
+
doc.rating_stats[:average] = total.to_f / sow
|
30
|
+
doc.save!
|
31
|
+
end
|
32
|
+
|
33
|
+
def set_previous_value
|
34
|
+
rating_from_db = Rating.find_by_id(id)
|
35
|
+
previous_value = (rating_from_db.value * weight) if rating_from_db
|
36
|
+
end
|
37
|
+
|
38
|
+
def update_rating_stats
|
39
|
+
doc = find_rated_document!
|
40
|
+
value_delta = (value * weight) - (previous_value * weight)
|
41
|
+
total = (doc.rating_stats[:total] += value_delta)
|
42
|
+
count = doc.rating_stats[:count]
|
43
|
+
doc.rating_stats[:average] = total.to_f / count
|
44
|
+
doc.save
|
45
|
+
end
|
46
|
+
|
47
|
+
def reduce_rating_stats
|
48
|
+
doc = find_rated_document
|
49
|
+
return if doc.nil?
|
50
|
+
count = (doc.rating_stats[:count] -= 1)
|
51
|
+
doc.rating_stats[:total] -= (value * weight)
|
52
|
+
doc.rating_stats[:sum_of_weights] -= weight
|
53
|
+
doc.rating_stats[:average] = nil if count == 0
|
54
|
+
doc.save
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
# == Various Instance Methods
|
59
|
+
def find_rated_document
|
60
|
+
klass = rateable_class.constantize
|
61
|
+
klass.find(rateable_id.to_s)
|
62
|
+
end
|
63
|
+
|
64
|
+
def find_rated_document!
|
65
|
+
doc = find_rated_document
|
66
|
+
raise "Associated document not found" if doc.nil?
|
67
|
+
doc
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'acts_as_mongo_rateable/acts_as_mongo_rateable'
|
data/rails/init.rb
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
|
3
|
+
class MongoRateableTest < ActiveSupport::TestCase
|
4
|
+
|
5
|
+
def create_user(name)
|
6
|
+
u = User.create({:name => name})
|
7
|
+
puts u.errors unless u.valid?
|
8
|
+
u
|
9
|
+
end
|
10
|
+
|
11
|
+
def load_multiple_raters
|
12
|
+
@m_rater_1 = create_user "m_rater_1"
|
13
|
+
@m_rater_2 = create_user "m_rater_2"
|
14
|
+
@m_rater_3 = create_user "m_rater_3"
|
15
|
+
[@m_rater_1, @m_rater_2, @m_rater_3]
|
16
|
+
end
|
17
|
+
|
18
|
+
def load_multiple_widgets
|
19
|
+
@widget = @owner.widgets.create({:name => "Test Widget"})
|
20
|
+
@widget_2 = @owner.widgets.create({:name => "Test Widget 2"})
|
21
|
+
@widget_3 = @owner.widgets.create({:name => "Test Widget 3"})
|
22
|
+
[@widget, @widget_2, @widget_3]
|
23
|
+
end
|
24
|
+
|
25
|
+
def load_multiple_dongles
|
26
|
+
@dongle = @owner.dongles.create({:name => "Test dongle"})
|
27
|
+
@dongle_2 = @owner.dongles.create({:name => "Test dongle 2"})
|
28
|
+
@dongle_3 = @owner.dongles.create({:name => "Test dongle 3"})
|
29
|
+
[@dongle, @dongle_2, @dongle_3]
|
30
|
+
end
|
31
|
+
|
32
|
+
def rate_this_many_times(obj, count, authority=1)
|
33
|
+
x = 0
|
34
|
+
count.times do
|
35
|
+
x += 1
|
36
|
+
rater = create_user "fake_rater_#{x}"
|
37
|
+
obj.rate(4, rater, authority)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def load_and_randomly_rate_multiple_widgets
|
42
|
+
raters = load_multiple_raters
|
43
|
+
widgets = load_multiple_widgets
|
44
|
+
widgets.each do |w|
|
45
|
+
raters.each {|r| w.rate(rand(5)+1, r, rand(3)+1) }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def load_and_randomly_rate_multiple_dongles
|
50
|
+
raters = load_multiple_raters
|
51
|
+
dongles = load_multiple_dongles
|
52
|
+
dongles.each do |d|
|
53
|
+
raters.each {|r| d.rate(rand(5)+1, r, rand(3)+1) }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def multi_rate(obj)
|
58
|
+
load_multiple_raters
|
59
|
+
obj.rate(4, @m_rater_1, 1)
|
60
|
+
obj.rate(2, @m_rater_2, 3)
|
61
|
+
obj.rate(5, @m_rater_3, 5)
|
62
|
+
end
|
63
|
+
|
64
|
+
def multi_rate_many_widgets
|
65
|
+
load_multiple_widgets
|
66
|
+
raters = load_multiple_raters
|
67
|
+
raters.each do |r|
|
68
|
+
@widget.rate(5, r, 2)
|
69
|
+
@widget_2.rate(1, r, 2)
|
70
|
+
@widget_3.rate(3, r, 2)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def setup
|
75
|
+
@owner = create_user 'owner'
|
76
|
+
@rater = create_user 'rater'
|
77
|
+
@widget = @owner.widgets.create({:name => "Test Widget"})
|
78
|
+
end
|
79
|
+
|
80
|
+
test "single rating on a widget produces the correct rating stats" do
|
81
|
+
@widget.rate(3, @rater, 2)
|
82
|
+
expected_stats = {"total" => 6, "count" => 1, "average" => 3.to_f, "sum_of_weights" => 2}
|
83
|
+
%w( total count average sum_of_weights).each do |method|
|
84
|
+
assert_equal expected_stats[method], @widget.rating_stats[method]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
test "multiple ratings on a widget produce the correct rating stats" do
|
89
|
+
multi_rate(@widget)
|
90
|
+
expected_stats = {"total" => 35, "count" => 3, "average" => 3.88888888888889, "sum_of_weights" => 9}
|
91
|
+
%w( total count sum_of_weights).each do |method|
|
92
|
+
assert_equal expected_stats[method], @widget.rating_stats[method]
|
93
|
+
end
|
94
|
+
# doing average seperately, since we don't need to test the whole long float
|
95
|
+
assert_equal sprintf('%.2f',expected_stats['average']), sprintf('%.2f',@widget.rating_stats['average'])
|
96
|
+
end
|
97
|
+
|
98
|
+
test "deleting all ratings for a specific widget works" do
|
99
|
+
load_and_randomly_rate_multiple_widgets
|
100
|
+
assert_equal 3, @widget.ratings.count
|
101
|
+
assert_equal 9, Rating.count
|
102
|
+
@widget.delete_all_ratings
|
103
|
+
assert_equal 0, @widget.ratings.count
|
104
|
+
assert_equal 6, Rating.count
|
105
|
+
end
|
106
|
+
|
107
|
+
test "deleting all ratings for a particular object doesn't delete ratings for other object" do
|
108
|
+
load_and_randomly_rate_multiple_widgets
|
109
|
+
load_and_randomly_rate_multiple_dongles
|
110
|
+
assert_equal 18, Rating.count
|
111
|
+
@widget.delete_all_ratings
|
112
|
+
assert_equal 0, @widget.ratings.count
|
113
|
+
assert_equal 3, @dongle.ratings.count
|
114
|
+
end
|
115
|
+
|
116
|
+
test "rated_by_user? works" do
|
117
|
+
multi_rate(@widget)
|
118
|
+
assert @widget.rated_by_user?(@m_rater_1)
|
119
|
+
end
|
120
|
+
|
121
|
+
test "delete by user only deletes ratings by that user for that object" do
|
122
|
+
@dongle = @owner.dongles.create({:name => "Test dongle"})
|
123
|
+
multi_rate(@widget)
|
124
|
+
widget_rater = @m_rater_1
|
125
|
+
multi_rate(@dongle)
|
126
|
+
assert_equal 6, Rating.count
|
127
|
+
assert_equal 3, @widget.ratings.count
|
128
|
+
assert @widget.rated_by_user?(widget_rater)
|
129
|
+
@widget.delete_ratings_by_user(widget_rater)
|
130
|
+
assert ! @widget.rated_by_user?(widget_rater)
|
131
|
+
assert_equal 2, @widget.ratings.count
|
132
|
+
assert_equal 5, Rating.count
|
133
|
+
end
|
134
|
+
|
135
|
+
test "bayesian_rating returns correct number" do
|
136
|
+
multi_rate_many_widgets
|
137
|
+
assert_equal sprintf('%.2f',3.82), sprintf('%.2f',@widget.bayesian_rating)
|
138
|
+
assert_equal sprintf('%.2f',1.54), sprintf('%.2f',@widget_2.bayesian_rating)
|
139
|
+
assert_equal sprintf('%.2f',2.68), sprintf('%.2f',@widget_3.bayesian_rating)
|
140
|
+
end
|
141
|
+
|
142
|
+
test "bayesian_rating for one class of objects not corrupted by other ratings for other classes" do
|
143
|
+
multi_rate_many_widgets
|
144
|
+
load_and_randomly_rate_multiple_dongles
|
145
|
+
assert_equal sprintf('%.2f',3.82), sprintf('%.2f',@widget.bayesian_rating)
|
146
|
+
assert_equal sprintf('%.2f',1.54), sprintf('%.2f',@widget_2.bayesian_rating)
|
147
|
+
assert_equal sprintf('%.2f',2.68), sprintf('%.2f',@widget_3.bayesian_rating)
|
148
|
+
end
|
149
|
+
|
150
|
+
test "highest_rated returns correct widgets in order" do
|
151
|
+
multi_rate_many_widgets
|
152
|
+
assert_equal [@widget, @widget_3, @widget_2], Widget.highest_rated(3)
|
153
|
+
end
|
154
|
+
|
155
|
+
test "most_rated returns correct widgets in order" do
|
156
|
+
load_multiple_widgets
|
157
|
+
rate_this_many_times(@widget, 6)
|
158
|
+
rate_this_many_times(@widget_2, 1)
|
159
|
+
rate_this_many_times(@widget_3, 3)
|
160
|
+
assert_equal [@widget, @widget_3, @widget_2], Widget.most_rated(3)
|
161
|
+
end
|
162
|
+
|
163
|
+
test "most_rated_by_authorities returns correct widgets in order" do
|
164
|
+
load_multiple_widgets
|
165
|
+
rate_this_many_times(@widget, 6, 2)
|
166
|
+
rate_this_many_times(@widget_2, 1, 5)
|
167
|
+
rate_this_many_times(@widget_3, 3, 1)
|
168
|
+
assert_equal [@widget, @widget_2, @widget_3], Widget.most_rated_by_authorities(3)
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'active_support'
|
3
|
+
require 'active_support/test_case'
|
4
|
+
|
5
|
+
ENV['RAILS_ENV'] = 'test'
|
6
|
+
ENV['RAILS_ROOT'] ||= File.dirname(__FILE__) + '/../../../..'
|
7
|
+
|
8
|
+
require 'test/unit'
|
9
|
+
require File.expand_path(File.join(ENV['RAILS_ROOT'], 'config/environment.rb'))
|
10
|
+
|
11
|
+
class ActiveSupport::TestCase
|
12
|
+
# Drop all columns after each test case.
|
13
|
+
def teardown
|
14
|
+
MongoMapper.database.collections.each do |coll|
|
15
|
+
coll.drop
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Make sure that each test case has a teardown
|
20
|
+
# method to clear the db after each test.
|
21
|
+
def inherited(base)
|
22
|
+
base.define_method teardown do
|
23
|
+
super
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# kinda weird, but we have to do this so we can ignore the app's User class and use our own for testing
|
29
|
+
Object.send(:remove_const, :User) if Object.const_defined?(:User)
|
30
|
+
|
31
|
+
class User
|
32
|
+
include MongoMapper::Document
|
33
|
+
key :name, String
|
34
|
+
has_many :widgets
|
35
|
+
has_many :dongles
|
36
|
+
end
|
37
|
+
|
38
|
+
class Widget
|
39
|
+
include MongoMapper::Document
|
40
|
+
include ActsAsMongoRateable
|
41
|
+
RATING_RANGE = (1..5)
|
42
|
+
|
43
|
+
belongs_to :user
|
44
|
+
|
45
|
+
key :user_id, ObjectId
|
46
|
+
key :name, String
|
47
|
+
end
|
48
|
+
|
49
|
+
class Dongle
|
50
|
+
include MongoMapper::Document
|
51
|
+
include ActsAsMongoRateable
|
52
|
+
RATING_RANGE = (1..5)
|
53
|
+
|
54
|
+
belongs_to :user
|
55
|
+
|
56
|
+
key :user_id, ObjectId
|
57
|
+
key :name, String
|
58
|
+
end
|
data/uninstall.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Uninstall hook code here
|
data.tar.gz.sig
ADDED
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: acts_as_mongo_rateable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 0.2.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- M. E. Patterson
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain:
|
17
|
+
- |
|
18
|
+
-----BEGIN CERTIFICATE-----
|
19
|
+
MIIDNDCCAhygAwIBAgIBADANBgkqhkiG9w0BAQUFADBAMRIwEAYDVQQDDAltYWRy
|
20
|
+
YXppZWwxFTATBgoJkiaJk/IsZAEZFgVnbWFpbDETMBEGCgmSJomT8ixkARkWA2Nv
|
21
|
+
bTAeFw0xMDA2MDUwNDUxMThaFw0xMTA2MDUwNDUxMThaMEAxEjAQBgNVBAMMCW1h
|
22
|
+
ZHJhemllbDEVMBMGCgmSJomT8ixkARkWBWdtYWlsMRMwEQYKCZImiZPyLGQBGRYD
|
23
|
+
Y29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2YYU8nBcWrz3Gls3
|
24
|
+
B4tKCLGU/v11vuLeDU51r0/mUL/ev4GAZOrlBRaIn4U3J53W4Wa3fHGNqDyB7dt4
|
25
|
+
eJS2a//eBv5MXtDxS0kuOeLen4sJiACN7WzoX/WaMiCdeLFebRFr6zDKtYMQZ1lM
|
26
|
+
x+DWXbFLBuWfihZ8TapkBBG8Yuw2J+OcJla7t0IR3KHJSpOe0e9D/rHydly2v7kH
|
27
|
+
6naqt5n4jtpTXrmgLeGUQeVR60BJrHIbHXjgUCkPz3b/prreaRl/a4Cufv0cTPMD
|
28
|
+
BFSOlmK6AKLii9gdaZMnLbF7b1Jc3rAcYNAsLHoUBGoq0Top8CTC732YrNZ7NmFp
|
29
|
+
/Jn0nQIDAQABozkwNzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQU
|
30
|
+
drmilB9ksqsW9hFPopqQEhcFdh8wDQYJKoZIhvcNAQEFBQADggEBAHPZLmxi4DYD
|
31
|
+
qdSsUv4oTlWoK5nCbEpnJRpJZXn1rn30Zaoc5v7kQfVwz/x/4uZmLdvfysZmuvKZ
|
32
|
+
GTMlYv/qSylO0SqbZeKhvsehQ+ENQdTon10zPtVvvnxiUE/As79eRmmxNGypJnwy
|
33
|
+
WZH2x1I+Hd25mRbt1UspT2vmuEwTgzkmxGHnl7cmiO4BYXlzHHk8KRSEdIDZUm5T
|
34
|
+
B+pavzaE15aDJrbpy+/awgxQEZb7O/mgzkFoTKlgV4VyDWSDjtU+aEI1sJk0DiSs
|
35
|
+
Zm3n8B2/BZXgeEcymjLxZGMrvkax12f02NF2l+C8f0/DzS+jlVsewUzux0WmYCR5
|
36
|
+
47YifzlLjIM=
|
37
|
+
-----END CERTIFICATE-----
|
38
|
+
|
39
|
+
date: 2010-06-04 00:00:00 -05:00
|
40
|
+
default_executable:
|
41
|
+
dependencies: []
|
42
|
+
|
43
|
+
description: A ratings system for Rails apps using MongoDB, with bayesian and straight averages, and weighting.
|
44
|
+
email: madraziel @nospam@ gmail.com
|
45
|
+
executables: []
|
46
|
+
|
47
|
+
extensions: []
|
48
|
+
|
49
|
+
extra_rdoc_files:
|
50
|
+
- README.markdown
|
51
|
+
- lib/acts_as_mongo_rateable/acts_as_mongo_rateable.rb
|
52
|
+
- lib/acts_as_mongo_rateable/app/models/rating.rb
|
53
|
+
- lib/mongo_rateable.rb
|
54
|
+
- tasks/mongo_rateable_tasks.rake
|
55
|
+
files:
|
56
|
+
- MIT-LICENSE
|
57
|
+
- README.markdown
|
58
|
+
- Rakefile
|
59
|
+
- install.rb
|
60
|
+
- lib/acts_as_mongo_rateable/acts_as_mongo_rateable.rb
|
61
|
+
- lib/acts_as_mongo_rateable/app/models/rating.rb
|
62
|
+
- lib/mongo_rateable.rb
|
63
|
+
- rails/init.rb
|
64
|
+
- tasks/mongo_rateable_tasks.rake
|
65
|
+
- test/mongo_rateable_test.rb
|
66
|
+
- test/test_helper.rb
|
67
|
+
- uninstall.rb
|
68
|
+
- Manifest
|
69
|
+
- acts_as_mongo_rateable.gemspec
|
70
|
+
has_rdoc: true
|
71
|
+
homepage: http://github.com/mepatterson/acts_as_mongo_rateable
|
72
|
+
licenses: []
|
73
|
+
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options:
|
76
|
+
- --line-numbers
|
77
|
+
- --inline-source
|
78
|
+
- --title
|
79
|
+
- Acts_as_mongo_rateable
|
80
|
+
- --main
|
81
|
+
- README.markdown
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
hash: 3
|
90
|
+
segments:
|
91
|
+
- 0
|
92
|
+
version: "0"
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
hash: 11
|
99
|
+
segments:
|
100
|
+
- 1
|
101
|
+
- 2
|
102
|
+
version: "1.2"
|
103
|
+
requirements: []
|
104
|
+
|
105
|
+
rubyforge_project: acts_as_mongo_rateable
|
106
|
+
rubygems_version: 1.3.7
|
107
|
+
signing_key:
|
108
|
+
specification_version: 3
|
109
|
+
summary: A ratings system for Rails apps using MongoDB, with bayesian and straight averages, and weighting.
|
110
|
+
test_files:
|
111
|
+
- test/mongo_rateable_test.rb
|
112
|
+
- test/test_helper.rb
|
metadata.gz.sig
ADDED
Binary file
|