is_reviewable 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/MIT-LICENSE +20 -0
- data/README.textile +421 -0
- data/Rakefile +51 -0
- data/generators/is_reviewable_migration/is_reviewable_migration_generator.rb +12 -0
- data/generators/is_reviewable_migration/templates/reviews_migration.rb +34 -0
- data/generators/is_reviewable_model/is_reviewable_model_generator.rb +11 -0
- data/generators/is_reviewable_model/templates/review_model.rb +5 -0
- data/lib/is_reviewable.rb +34 -0
- data/lib/is_reviewable/review.rb +38 -0
- data/lib/is_reviewable/reviewable.rb +430 -0
- data/lib/is_reviewable/reviewer.rb +17 -0
- data/lib/is_reviewable/support.rb +49 -0
- data/rails/init.rb +1 -0
- data/test/is_reviewable_test.rb +234 -0
- data/test/test_helper.rb +59 -0
- metadata +84 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
module IsReviewable
|
2
|
+
module Support
|
3
|
+
|
4
|
+
extend self
|
5
|
+
|
6
|
+
# Shortcut method for generating conditions hash for polymorphic belongs_to-associations.
|
7
|
+
#
|
8
|
+
def polymorphic_conditions_for(object_or_type, field, *match)
|
9
|
+
match = [:id, :type] if match.blank?
|
10
|
+
# Note: {} is equivalent to Hash.new which takes a block, so we must do: ({}) or (Hash.new)
|
11
|
+
returning({}) do |conditions|
|
12
|
+
conditions.merge!(:"#{field}_id" => object_or_type.id) if object_or_type.is_a?(::ActiveRecord::Base) && match.include?(:id)
|
13
|
+
|
14
|
+
if match.include?(:type)
|
15
|
+
type = case object_or_type
|
16
|
+
when ::Class
|
17
|
+
object_or_type.name
|
18
|
+
when ::Symbol, ::String
|
19
|
+
object_or_type.to_s.singularize.classify
|
20
|
+
else # Object - or raise NameError as usual
|
21
|
+
object_or_type.class.name
|
22
|
+
end
|
23
|
+
|
24
|
+
conditions.merge!(:"#{field}_type" => type)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Check if object is a valid activerecord object.
|
30
|
+
#
|
31
|
+
def is_active_record?(object)
|
32
|
+
object.present? && object.is_a?(::ActiveRecord::Base) # TODO: ::ActiveModel if Rails 3?
|
33
|
+
end
|
34
|
+
|
35
|
+
# Check if input is a valid format of IP, i.e. "#.#.#.#". Note: Just basic validation.
|
36
|
+
#
|
37
|
+
def is_ip?(object)
|
38
|
+
(object =~ /^\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}$/) rescue false
|
39
|
+
end
|
40
|
+
|
41
|
+
# Hash conditions to array conditions converter,
|
42
|
+
# e.g. {:key => value} will be turned to: ['key = :key', {:key => value}]
|
43
|
+
#
|
44
|
+
def hash_conditions_as_array(conditions)
|
45
|
+
[conditions.keys.collect { |key| "#{key} = :#{key}" }.join(' AND '), conditions]
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'is_reviewable'))
|
@@ -0,0 +1,234 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
class IsReviewableTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@review = ::Review.new
|
8
|
+
@user = ::User.create
|
9
|
+
@user_2 = ::User.create
|
10
|
+
@user_3 = ::User.create
|
11
|
+
@guest = ::Guest.create
|
12
|
+
@account = ::Account.create
|
13
|
+
@regular_post = ::Post.create
|
14
|
+
@reviewable_post = ::ReviewablePost.create
|
15
|
+
@reviewable_article = ::ReviewableArticle.create
|
16
|
+
end
|
17
|
+
|
18
|
+
context "initialization" do
|
19
|
+
|
20
|
+
should "extend ActiveRecord::Base" do
|
21
|
+
assert_respond_to ::ActiveRecord::Base, :is_reviewable
|
22
|
+
assert_respond_to ::ActiveRecord::Base, :is_reviewable?
|
23
|
+
end
|
24
|
+
|
25
|
+
should "extend with instance methods only for reviewable models" do
|
26
|
+
public_instance_methods = [
|
27
|
+
[:is_reviewable?, :reviewable?],
|
28
|
+
[:rating_scale, :reviewable_scale],
|
29
|
+
[:rating_precision, :reviewable_precision],
|
30
|
+
:reviewed_at,
|
31
|
+
:average_rating,
|
32
|
+
:average_rating_by,
|
33
|
+
[:number_of_reviews, :total_reviews],
|
34
|
+
[:is_reviewed?, :reviewed?],
|
35
|
+
[:is_reviewed_by?, :reviewed_by?],
|
36
|
+
:review_by,
|
37
|
+
:review!,
|
38
|
+
:unreview!,
|
39
|
+
:reviews
|
40
|
+
].flatten
|
41
|
+
|
42
|
+
assert public_instance_methods.all? { |m| @reviewable_post.respond_to?(m.to_sym) }
|
43
|
+
assert !public_instance_methods.all? { |m| @regular_post.respond_to?(m) }
|
44
|
+
end
|
45
|
+
|
46
|
+
should "be enabled only for specified models" do
|
47
|
+
assert @reviewable_post.reviewable?
|
48
|
+
assert @reviewable_article.reviewable?
|
49
|
+
assert !@regular_post.reviewable?
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
context "reviewable" do
|
55
|
+
|
56
|
+
should "have many reviews" do
|
57
|
+
assert @reviewable_post.respond_to?(:reviews)
|
58
|
+
assert @reviewable_article.respond_to?(:reviews)
|
59
|
+
|
60
|
+
@reviewable_post.review!(:reviewer => @user, :rating => 2.5)
|
61
|
+
@reviewable_post.review!(:reviewer => @user_2, :rating => 2.5)
|
62
|
+
|
63
|
+
assert_equal 2, @reviewable_post.reviews.size
|
64
|
+
end
|
65
|
+
|
66
|
+
should "have many reviewers" do
|
67
|
+
assert @reviewable_post.respond_to?(:reviewers)
|
68
|
+
assert @reviewable_article.respond_to?(:reviewers)
|
69
|
+
|
70
|
+
@reviewable_post.review!(:reviewer => @user, :rating => 2.5)
|
71
|
+
@reviewable_post.review!(:reviewer => @user_2, :rating => 2.5)
|
72
|
+
@reviewable_post.review!(:reviewer => @user_3, :rating => 2.5)
|
73
|
+
|
74
|
+
assert_equal 3, @reviewable_post.reviewers.size
|
75
|
+
end
|
76
|
+
|
77
|
+
should "have no reviews from the beginning" do
|
78
|
+
assert_equal(@reviewable_post.reviews.size, 0)
|
79
|
+
end
|
80
|
+
|
81
|
+
should "count reviews and ratings based on IP correctly" do
|
82
|
+
@reviewable_post.review!(:reviewer => '128.0.0.0', :rating => 1)
|
83
|
+
@reviewable_post.review!(:reviewer => '128.0.0.1', :rating => 2.5)
|
84
|
+
|
85
|
+
assert_equal 2, @reviewable_post.total_reviews
|
86
|
+
assert_equal 1.75, @reviewable_post.average_rating # with precision set to 2
|
87
|
+
|
88
|
+
# should not count as new, but update values
|
89
|
+
@reviewable_post.review!(:reviewer => '128.0.0.1', :rating => 3)
|
90
|
+
|
91
|
+
assert_equal 2, @reviewable_post.total_reviews
|
92
|
+
assert_equal 2.0, @reviewable_post.average_rating
|
93
|
+
|
94
|
+
# should not count in the end
|
95
|
+
@reviewable_post.review!(:reviewer => '128.0.0.3', :rating => 1)
|
96
|
+
@reviewable_post.unreview!(:reviewer => '128.0.0.3', :rating => 1)
|
97
|
+
|
98
|
+
assert_equal 2, @reviewable_post.total_reviews
|
99
|
+
assert_equal 2.0, @reviewable_post.average_rating
|
100
|
+
end
|
101
|
+
|
102
|
+
should "not accept any reviews on IP if disabled" do
|
103
|
+
assert_raise ::IsReviewable::InvalidReviewerError do
|
104
|
+
@reviewable_article.review!(:reviewer => '128.0.0.0', :rating => 1)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
should "count reviews based on reviewer object (user/account) correctly" do
|
109
|
+
@reviewable_post.review!(:reviewer => @user, :rating => 1)
|
110
|
+
@reviewable_post.review!(:reviewer => @user_2, :rating => 2.5)
|
111
|
+
|
112
|
+
assert_equal 2, @reviewable_post.total_reviews
|
113
|
+
assert_equal 1.75, @reviewable_post.average_rating # with precision set to 2
|
114
|
+
|
115
|
+
# should not count as new, but update values
|
116
|
+
@reviewable_post.review!(:reviewer => @user_2, :rating => 3)
|
117
|
+
|
118
|
+
assert_equal 2, @reviewable_post.total_reviews
|
119
|
+
assert_equal 2.0, @reviewable_post.average_rating
|
120
|
+
|
121
|
+
# should not count in the end
|
122
|
+
@reviewable_post.review!(:reviewer => @user_3, :rating => 1)
|
123
|
+
@reviewable_post.unreview!(:reviewer => @user_3, :rating => 1)
|
124
|
+
|
125
|
+
assert_equal 2, @reviewable_post.total_reviews
|
126
|
+
assert_equal 2.0, @reviewable_post.average_rating
|
127
|
+
end
|
128
|
+
|
129
|
+
should "count reviews based on both IP and reviewer object (user/account) correctly" do
|
130
|
+
@reviewable_post.review!(:reviewer => @user, :rating => 1)
|
131
|
+
@reviewable_post.review!(:reviewer => '128.0.0.2', :rating => 2.5)
|
132
|
+
|
133
|
+
assert_equal 2, @reviewable_post.total_reviews
|
134
|
+
assert_equal 1.75, @reviewable_post.average_rating # with precision set to 2
|
135
|
+
|
136
|
+
# should not count as new, but update values
|
137
|
+
@reviewable_post.review!(:reviewer => '128.0.0.2', :rating => 3)
|
138
|
+
|
139
|
+
assert_equal 2, @reviewable_post.total_reviews
|
140
|
+
assert_equal 2.0, @reviewable_post.average_rating
|
141
|
+
end
|
142
|
+
|
143
|
+
should "not count NULL-ratings, e.g. reviews skipping rating value" do
|
144
|
+
@reviewable_post.review!(:reviewer => @user, :rating => nil)
|
145
|
+
|
146
|
+
assert_equal 1, @reviewable_post.total_reviews
|
147
|
+
assert_equal 0.0, @reviewable_post.average_rating
|
148
|
+
end
|
149
|
+
|
150
|
+
should "not accept ratings out of rating scale range" do
|
151
|
+
assert_raise ::IsReviewable::InvalidReviewValueError do
|
152
|
+
@reviewable_post.review!(:reviewer => @user, :rating => 6)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
should "save review body" do
|
157
|
+
review_body = "Lorem ipsum dolor sit amet, consectetur adipisicing elit..."
|
158
|
+
|
159
|
+
# just body
|
160
|
+
review_1 = @reviewable_post.review!(:reviewer => @user, :body => review_body)
|
161
|
+
assert_equal(review_body, review_1.body)
|
162
|
+
|
163
|
+
# body + rating
|
164
|
+
review_2 = @reviewable_post.review!(:reviewer => @user_2, :rating => 4, :body => review_body)
|
165
|
+
assert_equal(review_body, review_2.body)
|
166
|
+
end
|
167
|
+
|
168
|
+
should "save any additional non-reserved attribute values" do
|
169
|
+
review = @reviewable_post.review!(:reviewer => @user, :rating => 4, :title => "My title")
|
170
|
+
assert_equal "My title", review.title
|
171
|
+
|
172
|
+
# don't allow update of reserved fields
|
173
|
+
review = @reviewable_post.review!(:reviewer => @user_2, :reviewable_id => 666)
|
174
|
+
assert_not_equal 666, review.reviewable_id
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
context "reviewer" do
|
179
|
+
|
180
|
+
should "have many reviews" do
|
181
|
+
assert @user.respond_to?(:reviews)
|
182
|
+
assert @account.respond_to?(:reviews)
|
183
|
+
assert !@guest.respond_to?(:reviews)
|
184
|
+
|
185
|
+
ReviewablePost.create.review!(:reviewer => @user, :rating => 2.5)
|
186
|
+
ReviewablePost.create.review!(:reviewer => @user, :rating => 2.5)
|
187
|
+
|
188
|
+
assert_equal 2, @user.reviews.size
|
189
|
+
end
|
190
|
+
|
191
|
+
should "have many reviewables" do
|
192
|
+
assert @user.respond_to?(:reviewables)
|
193
|
+
assert @account.respond_to?(:reviewables)
|
194
|
+
assert !@guest.respond_to?(:reviewables)
|
195
|
+
|
196
|
+
ReviewablePost.create.review!(:reviewer => @user, :rating => 2.5)
|
197
|
+
ReviewablePost.create.review!(:reviewer => @user, :rating => 2.5)
|
198
|
+
ReviewablePost.create.review!(:reviewer => @user, :rating => 2.5)
|
199
|
+
|
200
|
+
assert_equal 3, @user.reviewables.size
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|
204
|
+
|
205
|
+
context "review" do
|
206
|
+
|
207
|
+
# TODO: Test named scopes á la:
|
208
|
+
# * http://www.simonecarletti.com/blog/2009/06/how-to-test-rails-activerecord-named-scopes/
|
209
|
+
# * http://blog.confabulus.com/2008/11/24/testing-named-scopes, or similar.
|
210
|
+
|
211
|
+
should "define named scopes" do
|
212
|
+
named_scopes = [
|
213
|
+
:between_dates
|
214
|
+
]
|
215
|
+
|
216
|
+
# Example: Review.complete.proxy_options # => :conditions=>["rating IS NOT NULL AND body IS NOT NULL AND LENGTH(body) > 0"]}
|
217
|
+
|
218
|
+
# Old: This won't work...
|
219
|
+
# assert named_scopes.all? { |named_scope| Review.respond_to?(named_scope, true) }
|
220
|
+
#assert named_scopes.all? { |named_scope| @reviewable_post.reviews.respond_to?(named_scope, true) }
|
221
|
+
end
|
222
|
+
|
223
|
+
should "return reviews by creation date with named scope :in_order" do
|
224
|
+
@reviewable_post.review!(:reviewer => @user, :rating => 1)
|
225
|
+
@reviewable_post.review!(:reviewer => @user_2, :rating => 2)
|
226
|
+
|
227
|
+
# Old: This won't work...
|
228
|
+
#assert_equal @user, @reviewable_post.reviews.in_order.first.reviewer
|
229
|
+
#assert_equal @user_2, @reviewable_post.reviews.in_order.last.reviewer
|
230
|
+
end
|
231
|
+
|
232
|
+
end
|
233
|
+
|
234
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'rubygems'
|
3
|
+
|
4
|
+
gem 'test-unit', '= 1.2.3'
|
5
|
+
gem 'thoughtbot-shoulda', '>= 2.10.2'
|
6
|
+
gem 'sqlite3-ruby', '>= 1.2.0'
|
7
|
+
gem 'nakajima-acts_as_fu', '>= 0.0.5'
|
8
|
+
|
9
|
+
# Optional dependency
|
10
|
+
begin
|
11
|
+
require 'monkeyspecdoc'
|
12
|
+
rescue LoadError
|
13
|
+
begin
|
14
|
+
gem 'jgre-monkeyspecdoc', '>= 0.9.5'
|
15
|
+
require 'monkeyspecdoc'
|
16
|
+
rescue LoadError
|
17
|
+
# skip
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'test/unit'
|
22
|
+
require 'shoulda'
|
23
|
+
require 'acts_as_fu'
|
24
|
+
|
25
|
+
require 'test_helper'
|
26
|
+
|
27
|
+
require 'is_reviewable'
|
28
|
+
|
29
|
+
build_model :reviews do
|
30
|
+
references :reviewable, :polymorphic => true
|
31
|
+
|
32
|
+
references :reviewer, :polymorphic => true
|
33
|
+
string :ip, :limit => 24
|
34
|
+
|
35
|
+
float :rating
|
36
|
+
text :body
|
37
|
+
|
38
|
+
string :title
|
39
|
+
|
40
|
+
timestamps
|
41
|
+
end
|
42
|
+
|
43
|
+
build_model :guests
|
44
|
+
build_model :users
|
45
|
+
build_model :accounts
|
46
|
+
build_model :posts
|
47
|
+
|
48
|
+
build_model :reviewable_posts do
|
49
|
+
is_reviewable :by => :users, :scale => 1.0..5.0, :step => 0.5, :average_precision => 2, :accept_ip => true
|
50
|
+
end
|
51
|
+
|
52
|
+
build_model :reviewable_articles do
|
53
|
+
is_reviewable :by => [:accounts, :users], :scale => [1,2,3], :accept_ip => false
|
54
|
+
end
|
55
|
+
|
56
|
+
build_model :cached_reviewable_posts do
|
57
|
+
integer :reviews_count
|
58
|
+
integer :average_rating
|
59
|
+
end
|
metadata
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: is_reviewable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jonas Grimfelt
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-11-02 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: "Rails: Make an ActiveRecord resource ratable/reviewable (rate + text), without the usual extra code-smell."
|
17
|
+
email: grimen@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- MIT-LICENSE
|
24
|
+
- README.textile
|
25
|
+
- Rakefile
|
26
|
+
- generators/is_reviewable_migration/is_reviewable_migration_generator.rb
|
27
|
+
- generators/is_reviewable_migration/templates/reviews_migration.rb
|
28
|
+
- generators/is_reviewable_model/is_reviewable_model_generator.rb
|
29
|
+
- generators/is_reviewable_model/templates/review_model.rb
|
30
|
+
- lib/is_reviewable.rb
|
31
|
+
- lib/is_reviewable/review.rb
|
32
|
+
- lib/is_reviewable/reviewable.rb
|
33
|
+
- lib/is_reviewable/reviewer.rb
|
34
|
+
- lib/is_reviewable/support.rb
|
35
|
+
- rails/init.rb
|
36
|
+
- test/is_reviewable_test.rb
|
37
|
+
- test/test_helper.rb
|
38
|
+
files:
|
39
|
+
- MIT-LICENSE
|
40
|
+
- README.textile
|
41
|
+
- Rakefile
|
42
|
+
- generators/is_reviewable_migration/is_reviewable_migration_generator.rb
|
43
|
+
- generators/is_reviewable_migration/templates/reviews_migration.rb
|
44
|
+
- generators/is_reviewable_model/is_reviewable_model_generator.rb
|
45
|
+
- generators/is_reviewable_model/templates/review_model.rb
|
46
|
+
- lib/is_reviewable.rb
|
47
|
+
- lib/is_reviewable/review.rb
|
48
|
+
- lib/is_reviewable/reviewable.rb
|
49
|
+
- lib/is_reviewable/reviewer.rb
|
50
|
+
- lib/is_reviewable/support.rb
|
51
|
+
- rails/init.rb
|
52
|
+
- test/is_reviewable_test.rb
|
53
|
+
- test/test_helper.rb
|
54
|
+
has_rdoc: true
|
55
|
+
homepage: http://github.com/grimen/is_reviewable
|
56
|
+
licenses: []
|
57
|
+
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options:
|
60
|
+
- --charset=UTF-8
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: "0"
|
68
|
+
version:
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: "0"
|
74
|
+
version:
|
75
|
+
requirements: []
|
76
|
+
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 1.3.5
|
79
|
+
signing_key:
|
80
|
+
specification_version: 3
|
81
|
+
summary: "Rails: Make an ActiveRecord resource ratable/reviewable (rate + text), without the usual extra code-smell."
|
82
|
+
test_files:
|
83
|
+
- test/is_reviewable_test.rb
|
84
|
+
- test/test_helper.rb
|