is_reviewable 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ # coding: utf-8
2
+
3
+ module IsReviewable
4
+ module Reviewer
5
+
6
+ DEFAULT_CLASS_NAME = begin
7
+ if defined?(Account)
8
+ :account
9
+ else
10
+ :user
11
+ end
12
+ rescue
13
+ :user
14
+ end
15
+
16
+ end
17
+ end
@@ -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
@@ -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
@@ -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