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.
@@ -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