is_reviewable 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Jonas Grimfelt
|
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/README.textile
ADDED
@@ -0,0 +1,421 @@
|
|
1
|
+
h1. IS_REVIEWABLE ~ concept version ~
|
2
|
+
|
3
|
+
_Rails: Make an ActiveRecord resource ratable/reviewable (rating + comment), without the usual extra code-smell._
|
4
|
+
|
5
|
+
h2. Why another rating-plugin?
|
6
|
+
|
7
|
+
Many reasons; I felt the existing plugins all suck in one way or another (sorry, no hating). This is why IsReviweable is better if you ask me:
|
8
|
+
|
9
|
+
* Don't do assumptions that your rater/reviewer model is @User@. Relying on polymorphic assocation completely, so your reviewer can be...@DonaldDuck@.
|
10
|
+
* ...and optionally even accepts an IP as rater/reviewer. Disabled by default though.
|
11
|
+
* Don't make assumptions about your Review model - you can extend it without meta-programming, good 'ol subclassing folks.
|
12
|
+
* Don't make assumptions about what rating scale you wanna have, how the rating scale should be divided, or average rating rounding precision. The 1-5 scale is 80% of the cases, but there's no real reason or overhead to support any scale. To sum it up: Scale can consist negative and/or positive range or explicit integer/float values...and you won't even notice the difference on the outside. See the examples! =)
|
13
|
+
* Possible to submit additional custom attributes while rating, such as @title@ and @body@ to make up a "review" instead of just a "rating". Feel free.
|
14
|
+
* Very sassy finders implemented using named scopes, i.e. less code smell.
|
15
|
+
* No lame out-of-the-box views/javascripts/images that you probably won't use in the final product anyway. That should be an extension to the plugin.
|
16
|
+
* Transparently supports column-caching expensive calculations for the reviewable model. Will simply be turned on if these fields exists - otherwise fallback with an optimized DB hit instead.
|
17
|
+
* If I can say it myself...this code should be very solid, optimized, and DRY. Shoot the messenger! o<;)
|
18
|
+
|
19
|
+
If you got suggestions how it could be even better, just hit me up. I appreciate critics for the cause of a rock solid plugin for rating/reviewing. Such a common "problem" as rating/reviewing should have had a great solution by now!
|
20
|
+
|
21
|
+
h2. Installation
|
22
|
+
|
23
|
+
*Gem:*
|
24
|
+
|
25
|
+
<pre>sudo gem install grimen-is_reviewable</pre>
|
26
|
+
|
27
|
+
and in @config/environment.rb@:
|
28
|
+
|
29
|
+
<pre>config.gem 'grimen-is_reviewable', :lib => 'is_reviewable'</pre>
|
30
|
+
|
31
|
+
*Plugin:*
|
32
|
+
|
33
|
+
<pre>./script/plugin install git://github.com/grimen/is_reviewable.git</pre>
|
34
|
+
|
35
|
+
h2. Usage
|
36
|
+
|
37
|
+
h3. 1. Generate migration:
|
38
|
+
|
39
|
+
<pre>$ ./script/generate is_reviewable_migration</pre>
|
40
|
+
|
41
|
+
Generates @db/migrations/{timestamp}_is_reviewable_migration@ with:
|
42
|
+
|
43
|
+
<pre>
|
44
|
+
class IsReviewableMigration < ActiveRecord::Migration
|
45
|
+
def self.up
|
46
|
+
create_table :reviews do |t|
|
47
|
+
t.references :reviewable, :polymorphic => true
|
48
|
+
|
49
|
+
t.references :reviewer, :polymorphic => true
|
50
|
+
t.string :ip, :limit => 24
|
51
|
+
|
52
|
+
t.float :rating
|
53
|
+
t.text :body
|
54
|
+
|
55
|
+
#
|
56
|
+
# Custom fields goes here...
|
57
|
+
#
|
58
|
+
# t.string :title
|
59
|
+
# t.string :mood
|
60
|
+
# ...
|
61
|
+
#
|
62
|
+
|
63
|
+
t.timestamps
|
64
|
+
end
|
65
|
+
|
66
|
+
add_index :reviews, :reviewer_id
|
67
|
+
add_index :reviews, :reviewer_type
|
68
|
+
add_index :reviews, [:reviewer_id, :reviewer_type]
|
69
|
+
add_index :reviews, [:reviewable_id, :reviewable_type]
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.down
|
73
|
+
drop_table :reviews
|
74
|
+
end
|
75
|
+
end
|
76
|
+
</pre>
|
77
|
+
|
78
|
+
h3. 2. Make your model reviewable:
|
79
|
+
|
80
|
+
<pre>
|
81
|
+
class Post < ActiveRecord::Base
|
82
|
+
is_reviewable :scale => 0..5
|
83
|
+
end
|
84
|
+
</pre>
|
85
|
+
|
86
|
+
or, with explicit reviewer (or reviewers):
|
87
|
+
|
88
|
+
<pre>
|
89
|
+
class Book < ActiveRecord::Base
|
90
|
+
# Setup associations for the reviewer class(es) automatically, and specify an explicit scale instead.
|
91
|
+
is_reviewable :by => [:users, :journalists], :scale => 0..5
|
92
|
+
end
|
93
|
+
</pre>
|
94
|
+
|
95
|
+
*Note:* @:by@ is optional if you choose any of @User@ or @Account@ as reviewer classes.
|
96
|
+
|
97
|
+
h3. 3. ...and here we go:
|
98
|
+
|
99
|
+
Examples:
|
100
|
+
|
101
|
+
<pre>
|
102
|
+
Review.destroy_all # just in case...
|
103
|
+
@post = Post.first
|
104
|
+
@user = User.first
|
105
|
+
|
106
|
+
@post.review!(:reviewer => @user, :rating => 2) # new reviewer (type: object) => create
|
107
|
+
@post.review!(:reviewer => @user, :rating => 5) # same reviewer (type: object) => update
|
108
|
+
|
109
|
+
@post.total_reviews # => 1
|
110
|
+
@post.average_rating # => 5.0
|
111
|
+
|
112
|
+
@post.review!(:reviewer => '128.0.0.0', :rating => 4) # new reviewer (type: IP) => create
|
113
|
+
@post.review!(:reviewer => '128.0.0.0', :rating => 2) # same reviewer (type: IP) => update
|
114
|
+
|
115
|
+
@post.total_reviews # => 2
|
116
|
+
@post.average_rating # => 3.5
|
117
|
+
|
118
|
+
@post.review!(:reviewer => @user, :rating => nil, :body => "Lorem ipsum...") # same reviewer (type: IP) => update
|
119
|
+
|
120
|
+
@post.total_reviews # => 2
|
121
|
+
@post.average_rating # => 2.0, i.e. don't count nil (a.k.a. "no opinion")
|
122
|
+
|
123
|
+
@post.unreview!(:reviewer => '128.0.0.0') # delete existing review (type: IP) => destroy
|
124
|
+
|
125
|
+
@post.total_reviews # => 1
|
126
|
+
@post.average_rating # => 2.0
|
127
|
+
|
128
|
+
@post.reviews # => reviews on @post
|
129
|
+
@post.reviewers # => reviewers that reviewed @post
|
130
|
+
@user.reviews # => reviews by @user
|
131
|
+
@user.reviewables # => reviewable objects that got reviewed by @user
|
132
|
+
|
133
|
+
# TODO: A few more samples...
|
134
|
+
|
135
|
+
# etc...
|
136
|
+
</pre>
|
137
|
+
|
138
|
+
h2. Mixin Arguments
|
139
|
+
|
140
|
+
The @is_reviewable@ mixin takes some hash arguments for customization:
|
141
|
+
|
142
|
+
*Basic*
|
143
|
+
|
144
|
+
* @:by@ - the reviewer model, e.g. User, Account, etc. (accepts either symbol or class, i.e. @User@ <=> @:user@ <=> @:users@). The reviewer model will be setup for you. Note: Polymorhic, so it accepts any model. Default: @User@, or auto-detection fails.
|
145
|
+
* @:scale@/@:range@/@:values@ - range, or array, of valid rating values. Default: @1..5@. Note: Negative values are allowed too, and a range of values are not required, i.e. [-1, 1] is valid as well as [1,3,5]. =)
|
146
|
+
* @:accept_ip@ - accept anonymous users uniquely identified by IP (well...you handle the bots =D). See examples below how to use this in comparison to reviewer model. Default: @false@.
|
147
|
+
|
148
|
+
*Advanced*
|
149
|
+
|
150
|
+
* @:total_precision@ - maximum number of digits for the average rating value. Default: @1@.
|
151
|
+
* @:step@ - useful if you want to specify a custom step for each scale value within a range of values. Default: @1@ for range of fixnum, auto-detected based on first value in range of float.
|
152
|
+
* @:steps@ - similar to @:step@ (they relate to each other), but instead of specifying a step you can specify how many steps you want. Default: auto-detected based on custom or default value @:step@.
|
153
|
+
|
154
|
+
h2. Finders
|
155
|
+
|
156
|
+
IsReviewable has plenty of useful finders implemented using named scopes. Here they are:
|
157
|
+
|
158
|
+
h3. @Review@
|
159
|
+
|
160
|
+
*Order:*
|
161
|
+
|
162
|
+
* @in_order@ - most recent reviews last (order by creation date).
|
163
|
+
* @most_recent@ - most recent reviews first (opposite of @in_order@ above).
|
164
|
+
* @lowest_rating@ - reviews with lowest ratings first.
|
165
|
+
* @highest_rating@ - reviews with lowest ratings first.
|
166
|
+
|
167
|
+
*Filter:*
|
168
|
+
|
169
|
+
* @limit(<number_of_items>)@ - maximum @<number_of_items>@ reviews.
|
170
|
+
* @since(<created_at_datetime>)@ - reviews created since @<created_at_datetime>@.
|
171
|
+
* @recent(<datetime_or_size>)@ - if DateTime: reviews created since @<datetime_or_size>@, else if Fixnum: pick last @<datetime_or_size>@ number of reviews.
|
172
|
+
* @between_dates(<from_date>, to_date)@ - reviews created between two datetimes.
|
173
|
+
* @with_rating(<rating_value_or_range>)@ - reviews with(in) rating value (or range) @<rating_value_or_range>@.
|
174
|
+
* @with_a_rating@ - reviews with a rating value, i.e. not nil.
|
175
|
+
* @without_a_rating@ - opposite of @with_a_rating@ (above).
|
176
|
+
* @with_a_body@ - reviews with a body/comment, i.e. not nil/blank.
|
177
|
+
* @without_a_body@ - opposite of @with_a_body@ (above).
|
178
|
+
* @complete@ - reviews with both rating and comments, i.e. "full reviews" where.
|
179
|
+
* @of_reviewable_type(<reviewable_type>)@ - reviews of @<reviewable_type>@ type of reviewable models.
|
180
|
+
* @by_reviewer_type(<reviewer_type>)@ - reviews of @<reviewer_type>@ type of reviewer models.
|
181
|
+
* @on(<reviewable_object>)@ - reviews on the reviewable object @<reviewable_object>@ .
|
182
|
+
* @by(<reviewer_object>)@ - reviews by the @<reviewer_object>@ type of reviewer models.
|
183
|
+
|
184
|
+
h3. @Reviewable@
|
185
|
+
|
186
|
+
*Order:*
|
187
|
+
|
188
|
+
_TODO: Documentation on named scopes for reviewable._
|
189
|
+
|
190
|
+
*Filter:*
|
191
|
+
|
192
|
+
_TODO: Documentation on named scopes for reviewable._
|
193
|
+
|
194
|
+
h3. @Reviewer@
|
195
|
+
|
196
|
+
*Order:*
|
197
|
+
|
198
|
+
_TODO: Documentation on named scopes for reviewable._
|
199
|
+
|
200
|
+
*Filter:*
|
201
|
+
|
202
|
+
_TODO: Documentation on named scopes for reviewable._
|
203
|
+
|
204
|
+
h3. Examples using finders:
|
205
|
+
|
206
|
+
<pre>
|
207
|
+
@user = User.first
|
208
|
+
@post = Post.first
|
209
|
+
|
210
|
+
@post.reviews.recent(10) # => [10 most recent reviews]
|
211
|
+
@post.reviews.recent(1.week.ago) # => [reviews created since 1 week ago]
|
212
|
+
|
213
|
+
@post.reviews.with_rating(3.5..4.0) # => [all reviews on @post with rating between 3.5 and 4.0]
|
214
|
+
|
215
|
+
@post.reviews.by_reviewer_type(:user) # => [all reviews on @post by User-objects]
|
216
|
+
# ...or:
|
217
|
+
@post.reviews.by_reviewer_type(:users) # => [all reviews on @post by User-objects]
|
218
|
+
# ...or:
|
219
|
+
@post.reviews.by_reviewer_type(User) # => [all reviews on @post by User-objects]
|
220
|
+
|
221
|
+
@user.reviews.on(@post) # => [all reviews by @user on @post]
|
222
|
+
@post.reviews.by(@user) # => [all reviews by @user on @post] (equivalent with above)
|
223
|
+
|
224
|
+
Review.on(@post) # => [all reviews on @user] <=> @post.reviews
|
225
|
+
Review.by(@user) # => [all reviews by @user] <=> @user.reviews
|
226
|
+
|
227
|
+
# etc, etc. It's all named scopes, so it's really no new hokus-pokus you have to learn.
|
228
|
+
</pre>
|
229
|
+
|
230
|
+
h2. Additional Methods
|
231
|
+
|
232
|
+
*Note:* See documentation (RDoc).
|
233
|
+
|
234
|
+
h2. Extend the Review model
|
235
|
+
|
236
|
+
This is optional, but if you wanna be in control of your models (in this case @Review@) you can take control like this:
|
237
|
+
|
238
|
+
<pre>
|
239
|
+
class Review < IsReviewable::Review
|
240
|
+
|
241
|
+
# Do what you do best here... (stating the obvious: core IsReviewable associations, named scopes, etc. will be inherited)
|
242
|
+
|
243
|
+
end
|
244
|
+
</pre>
|
245
|
+
|
246
|
+
h2. Caching
|
247
|
+
|
248
|
+
If the visitable class table - in the sample above @Post@ - contains a columns @reviews_count@ and @average_rating@, then a cached value will be maintained within it for the number of reviews and the average rating the object have got.
|
249
|
+
|
250
|
+
Additional caching fields (to a reviewable model table):
|
251
|
+
|
252
|
+
<pre>
|
253
|
+
class AddIsReviewableToPostsMigration < ActiveRecord::Migration
|
254
|
+
def self.up
|
255
|
+
# Enable is_reviewable-caching.
|
256
|
+
add_column :posts, :cached_total_reviews, :integer
|
257
|
+
add_column :posts, :cached_average_rating, :integer
|
258
|
+
end
|
259
|
+
|
260
|
+
def self.down
|
261
|
+
remove_column :posts, :cached_total_reviews
|
262
|
+
remove_column :posts, :cached_average_rating
|
263
|
+
end
|
264
|
+
end
|
265
|
+
</pre>
|
266
|
+
|
267
|
+
h2. Example
|
268
|
+
|
269
|
+
h3. Controller
|
270
|
+
|
271
|
+
Depending on your implementation: You might - or might not - need a Controller, but for most cases where you only want to allow rating of something, a controller most probably is overkill. In the case of a review, this is how one cold look like (in this example, I'm using the excellent the "InheritedResources":http://github.com/josevalim/inherited_resources):
|
272
|
+
|
273
|
+
Example: @app/controllers/reviews_controller.rb@:
|
274
|
+
|
275
|
+
<pre>
|
276
|
+
class ReviewsController < InheritedResources::Base
|
277
|
+
|
278
|
+
actions :create, :update, :destroy
|
279
|
+
respond_to :js
|
280
|
+
layout false
|
281
|
+
|
282
|
+
end
|
283
|
+
</pre>
|
284
|
+
|
285
|
+
..or in the more basic rating case - @app/controllers/posts_controller.rb@:
|
286
|
+
|
287
|
+
<pre>
|
288
|
+
class PostsController < InheritedResources::Base
|
289
|
+
|
290
|
+
actions :all
|
291
|
+
respond_to :html, :js
|
292
|
+
layout false if request.format == :js
|
293
|
+
|
294
|
+
def rate
|
295
|
+
begin
|
296
|
+
@post.review! :reviewer => current_user, params.slice(:rating, :body)
|
297
|
+
rescue
|
298
|
+
flash[:error] = 'Sad panda...could not rate for some reason. O_o'
|
299
|
+
end
|
300
|
+
respond_to do |format|
|
301
|
+
format.html { redirect_to @post }
|
302
|
+
format.js # app/views/posts/rate.js.rjs
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
end
|
307
|
+
</pre>
|
308
|
+
|
309
|
+
h3. Routes
|
310
|
+
|
311
|
+
@config/routes.rb@
|
312
|
+
|
313
|
+
<pre>
|
314
|
+
map.resources :posts, :member => {:rate => :put}
|
315
|
+
</pre>
|
316
|
+
|
317
|
+
h3. Views
|
318
|
+
|
319
|
+
IsReviewable comes with no view templates (etc.) because of already stated reasons, but basic rating mechanism is trivial to implement (in this example, I'm using HAML because I despise ERB):
|
320
|
+
|
321
|
+
Example: @app/views/posts/show.html.haml@
|
322
|
+
|
323
|
+
<pre>
|
324
|
+
%h1
|
325
|
+
= @post.title
|
326
|
+
%p
|
327
|
+
= @post.body
|
328
|
+
%p
|
329
|
+
= "Your rating:"
|
330
|
+
#rating_wrapper= render '/reviews/rating', :resource => @post
|
331
|
+
</pre>
|
332
|
+
|
333
|
+
Example: @app/views/reviews/_rating.html.haml@
|
334
|
+
|
335
|
+
<pre>
|
336
|
+
.rating
|
337
|
+
- if resource.present? && resource.reviewable?
|
338
|
+
- if reviewer.present?
|
339
|
+
- current_rating = resource.review_by(reviewer).try(:rating)
|
340
|
+
- resource.reviewable_scale.each do |rating|
|
341
|
+
= link_to_remote "#{rating.to_i}", :url => rate_post_path(resource, :rating => rating.to_i), :method => :put, :html => {:class => "rate rated_#{rating.to_i}#{' current' if current_rating == rating}"}
|
342
|
+
= link_to_remote "no opinion", :url => rate_post_path(resource, :rating => nil), :method => :put, :html => {:class => "rate rated_none#{' current' unless current_rating}"}
|
343
|
+
- else # static rating
|
344
|
+
- current_rating = resource.average_rating.round
|
345
|
+
- resource.reviewable_scale.each do |rating|
|
346
|
+
{:class => "rate rated_#{rating}#{' current' if current_rating == rating}"}
|
347
|
+
</pre>
|
348
|
+
|
349
|
+
*Note:* Skipping review-body (etc.) in this view sample, but that is straightforward to implement anyways.
|
350
|
+
|
351
|
+
h3. JavaScript/AJAX
|
352
|
+
|
353
|
+
...and finally - as we in this example using AJAX - the javascript response (in this example, I'm using RJS + jQuery - don't get me started on why I don't use Prototype for UI...).
|
354
|
+
|
355
|
+
Example: @app/views/reviews/rate.js.rjs@
|
356
|
+
|
357
|
+
<pre>
|
358
|
+
page.replace_html '#rating_wrapper', render('/reviews/rating', :reviewable => @post, :reviewer => current_user)
|
359
|
+
page.visual_effect :highlight, '.rating .current'
|
360
|
+
</pre>
|
361
|
+
|
362
|
+
Done! =)
|
363
|
+
|
364
|
+
h2. Design Implications: Additional Use Cases
|
365
|
+
|
366
|
+
h3. Like/Dislike
|
367
|
+
|
368
|
+
IsReviewable is designed in such way that you as a developer are not locked to how traditional rating works. As an example, this is how you could implement like/dislike (like VoteFu) pattern using IsReviewable:
|
369
|
+
|
370
|
+
Example:
|
371
|
+
|
372
|
+
<pre>
|
373
|
+
class Post < ActiveRecord::Base
|
374
|
+
is_reviewable :by => :users, :values => [-1, 1]
|
375
|
+
end
|
376
|
+
</pre>
|
377
|
+
|
378
|
+
*Note:* @:values@ is an alias for @:scale@ for semantical reasons in cases like these.
|
379
|
+
|
380
|
+
h2. Dependencies
|
381
|
+
|
382
|
+
Basic usage:
|
383
|
+
|
384
|
+
* "rails":http://github.com/rails/rails (well...)
|
385
|
+
|
386
|
+
For running tests:
|
387
|
+
|
388
|
+
* sqlite3-ruby
|
389
|
+
* "thoughtbot-shoulda":http://github.com/thoughtbot/shoulda
|
390
|
+
* "nakajima-acts_as_fu":http://github.com/nakajima/acts_as_fu
|
391
|
+
* "jgre-monkeyspecdoc":http://github.com/jgre/monkeyspecdoc (optional)
|
392
|
+
|
393
|
+
h2. Notes
|
394
|
+
|
395
|
+
* Tested with Ruby 1.9.1.
|
396
|
+
* Let me know if you find any bugs; not used in production yet so consider this a concept version.
|
397
|
+
|
398
|
+
h2. TODO
|
399
|
+
|
400
|
+
h3. Priority:
|
401
|
+
|
402
|
+
* bug: Accept multiple reviewers (of different types): @average_rating_by@, etc..
|
403
|
+
* documentation: A few more README-examples.
|
404
|
+
* feature: Reviewable on multiple contexts, e.g. @is_reviewable :on => [:acting, :writing, ...]@. Alias method @is_reviewable_on@.
|
405
|
+
* testing: More thorough tests, especially for named scopes which is a bit tricky.
|
406
|
+
* feature: Useful finders for @Reviewable@.
|
407
|
+
* feature: Useful finders for @Reviewer@.
|
408
|
+
|
409
|
+
h3. Maybe:
|
410
|
+
|
411
|
+
* feature: Make alias helpers to implement functionality like VoteFu (http://github.com/peteonrails/vote_fu), simply just aliasing methods with existing ones with hardcoded parameters. Not required because this is supported already, it's all about semantics and sassy code.
|
412
|
+
|
413
|
+
h2. Related Links
|
414
|
+
|
415
|
+
...that might be of interest.
|
416
|
+
|
417
|
+
* "jQuery Star Rating":http://github.com/rathgar/jquery-star-rating/ - javascript star rating plugin for Rails on jQuery, if you don't want to do the rating widget on your own. It should be quite straightforward to customize the appearance of it for your needs too.
|
418
|
+
|
419
|
+
h2. License
|
420
|
+
|
421
|
+
Copyright (c) 2009 Jonas Grimfelt, released under the MIT-license.
|
data/Rakefile
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rake/rdoctask'
|
6
|
+
|
7
|
+
NAME = "is_reviewable"
|
8
|
+
SUMMARY = %Q{Rails: Make an ActiveRecord resource ratable/reviewable (rate + text), without the usual extra code-smell.}
|
9
|
+
HOMEPAGE = "http://github.com/grimen/#{NAME}"
|
10
|
+
AUTHOR = "Jonas Grimfelt"
|
11
|
+
EMAIL = "grimen@gmail.com"
|
12
|
+
SUPPORT_FILES = %w(README.textile)
|
13
|
+
|
14
|
+
begin
|
15
|
+
gem 'technicalpickles-jeweler', '>= 1.2.1'
|
16
|
+
require 'jeweler'
|
17
|
+
Jeweler::Tasks.new do |gem|
|
18
|
+
gem.name = NAME
|
19
|
+
gem.summary = SUMMARY
|
20
|
+
gem.description = SUMMARY
|
21
|
+
gem.homepage = HOMEPAGE
|
22
|
+
gem.author = AUTHOR
|
23
|
+
gem.email = EMAIL
|
24
|
+
|
25
|
+
gem.require_paths = %w{lib}
|
26
|
+
gem.files = SUPPORT_FILES << %w(MIT-LICENSE Rakefile) << Dir.glob(File.join('{generators,lib,test,rails}', '**', '*'))
|
27
|
+
gem.executables = %w()
|
28
|
+
gem.extra_rdoc_files = SUPPORT_FILES
|
29
|
+
end
|
30
|
+
rescue LoadError
|
31
|
+
puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
32
|
+
end
|
33
|
+
|
34
|
+
desc %Q{Default: Run unit tests for "#{NAME}".}
|
35
|
+
task :default => :test
|
36
|
+
|
37
|
+
desc %Q{Run unit tests for "#{NAME}".}
|
38
|
+
Rake::TestTask.new(:test) do |test|
|
39
|
+
test.libs << %w(lib test)
|
40
|
+
test.pattern = File.join('test', '**', '*_test.rb')
|
41
|
+
test.verbose = true
|
42
|
+
end
|
43
|
+
|
44
|
+
desc %Q{Generate documentation for "#{NAME}".}
|
45
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
46
|
+
rdoc.rdoc_dir = 'rdoc'
|
47
|
+
rdoc.title = NAME
|
48
|
+
rdoc.options << '--line-numbers' << '--inline-source' << '--charset=UTF-8'
|
49
|
+
rdoc.rdoc_files.include(SUPPORT_FILES)
|
50
|
+
rdoc.rdoc_files.include(File.join('lib', '**', '*.rb'))
|
51
|
+
end
|