ActsAsReviewable
Reviews for any AR model with multi-dimensional ratings, review commentary, and info-graphics.
ActsAsReviewable - concept v0.0.1.
Rails: Make an ActiveRecord resource rateable/reviewable (rating + comment) across multiple dimensions with infographics.
Why another rating/review-plugin?
Existing plugins rate on one dimension and provide basic analytics and no charting.
- Don't assume that your rater/reviewer model is User. Use polymorphic associations, so your reviewer can be...anymodel.
- Don't make assumptions about the rating scale, how it should be divided, or rounding precision for stats. 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! =)
- Possible to submit additional custom attributes while rating, such as a title and body to make up a "review" instead of just a "rating". Feel free.
- Finders implemented using scopes, i.e.. less code smell.
- Information graphics provided as an optional extension to the plugin. Engine for charting would be useful, even if just for admins.
Installation
Gem:
gem install acts_as_reviewable
for rails 3, in your Gemfile:
gem 'acts_as_reviewable'
Usage
1. Generate migration:
$ rails generate acts_as_reviewable_migration
Generates db/migrations/timestamp_acts_as_reviewable_migration with
class ActsAsReviewableMigration < ActiveRecord::Migration
def self.up
create_table :reviews do |t|
t.references :reviewable, :polymorphic => true
t.references :reviewer, :polymorphic => true
t.float :rating
t.text :comment
#
# Custom fields go here...
#
# t.string :title
# t.string :intention
# ...
#
t.
end
add_index :reviews, :reviewer_id
add_index :reviews, :reviewer_type
add_index :reviews, [:reviewer_id, :reviewer_type]
add_index :reviews, [:reviewable_id, :reviewable_type]
end
def self.down
drop_table :reviews
end
end
2. Make your model reviewable:
class Post < ActiveRecord::Base
acts_as_reviewable :scale => 0..5
end
or, with explicit reviewer (or reviewers):
class Book < ActiveRecord::Base
# Setup associations for the reviewer class(es) automatically, and specify an explicit scale instead.
acts_as_reviewable :by => [:users, :authors], :scale => 0..5
end
3. API - a stable public api is scheduled for release 1.0.0 (plausibly June 2011)
Examples:
Review.destroy_all # just in case...
@post = Post.first
@user = User.first
@post.review!(:by => @user, :rating => 2) # new reviewer (type: object) => create
@post.review!(:by => @user, :rating => 5) # same reviewer (type: object) => update
@post.total_reviews # => 1
@post. # => 5.0
@post.total_reviews # => 2
@post. # => 3.5
@post.review!(:by => @user, :rating => nil, :body => "Lorem ipsum...") # same reviewer (type: IP) => update
@post.total_reviews # => 2
@post. # => 2.0, i.e. don't count nil (a.k.a. "no opinion")
@post.unreview!(:by => @user) # delete existing review (type: User) => destroy
@post.total_reviews # => 1
@post. # => 2.0
@post.reviews # => reviews on @post
@post.reviewers # => reviewers that reviewed @post
@user.reviews # => reviews by @user
@user.reviewables # => reviewable objects that got reviewed by @user
# TODO: A few more samples...
# etc...
Mixin Arguments
The acts_as_reviewable mixin takes the following hash arguments for customization:
Basic
- :by - the reviewer model(s), e.g. User, Account, etc. (accepts either symbol or class, i.e. User <=> :user <=> :users, or an array of such if there are more than one reviewer model). The reviewer model will be setup for you. Note: Polymorhic, so it accepts any model. Default: nil.
- :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]. =)
Advanced
- :total_precision - maximum number of digits for the average rating value. Default: 1.
- :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.
- :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.
Aliases
To make the usage of ActsAsReviewable a bit more generic (similar to other plugins you may use), there are two useful aliases for this purpose:
- Review#owner <=> Review#reviewer
- Review#object <=> Review#reviewable
Example:
@post.reviews.first.owner == post.reviews.first.reviewer # => true
@post.reviews.first.object == post.reviews.first.reviewable # => true
Finders (Named Scopes)
ActsAsReviewable has plenty of useful finders implemented using scopes. Here they are:
Review
Order:
- in_order - most recent reviews last (order by creation date).
- most_recent - most recent reviews first (opposite of in_order above).
- lowest_rating - reviews with lowest ratings first.
- highest_rating - reviews with highest ratings first.
Filter:
- limit(
) - maximumreviews. - since(
) - reviews created since. - recent(
) - if DateTime: reviews created since, else if Fixnum: pick last number of reviews. - between_dates(
, to_date) - reviews created between two datetimes. - with_rating(
) - reviews with(in) rating value (or range). - with_a_rating - reviews with a rating value, i.e. not nil.
- without_a_rating - opposite of with_a_rating (above).
- with_a_body - reviews with a body/comment, i.e. not nil/blank.
- without_a_body - opposite of with_a_body (above).
- complete - reviews with both rating and comments, i.e. "full reviews" where.
- of_reviewable_type(
) - reviews oftype of reviewable models. - by_reviewer_type(
) - reviews oftype of reviewer models. - on(
) - reviews on the reviewable object. - by(
) - reviews by thetype of reviewer models.
Reviewable
TODO: Documentation on scopes for Reviewable.
Reviewer
TODO: Documentation on scopes for Reviewer.
Examples using finders:
@user = User.first
@post = Post.first
@post.reviews.recent(10) # => [10 most recent reviews]
@post.reviews.recent(1.week.ago) # => [reviews created since 1 week ago]
@post.reviews.(3.5..4.0) # => [all reviews on @post with rating between 3.5 and 4.0]
@post.reviews.by_reviewer_type(:user) # => [all reviews on @post by User-objects]
# ...or:
@post.reviews.by_reviewer_type(:users) # => [all reviews on @post by User-objects]
# ...or:
@post.reviews.by_reviewer_type(User) # => [all reviews on @post by User-objects]
@user.reviews.on(@post) # => [all reviews by @user on @post]
@post.reviews.by(@user) # => [all reviews by @user on @post] (equivalent with above)
Review.on(@post) # => [all reviews on @user] <=> @post.reviews
Review.by(@user) # => [all reviews by @user] <=> @user.reviews
Additional Methods
Routes
config/routes.rb
resources :posts, :member => {:rate => :put}
Views
ActsAsReviewable comes with no view templates (etc.), but basic rating mechanism is trivial to implement (in this example, I'm using HAML because I despise ERB):
Example: app/views/posts/show.html.haml
h1
= @post.title
p
= @post.body
p
= "Your rating:"
#rating_wrapper= render '/reviews/rating', :resource => @post
Example: app/views/reviews/_rating.html.haml
.
- if resource.present? && resource.reviewable?
- if reviewer.present?
- = resource.review_by(reviewer).try(:rating)
- resource.reviewable_scale.each do ||
= link_to_remote "#{rating.to_i}", :url => rate_post_path(resource, :rating => .to_i), :method => :put, :html => {:class => "rate rated_#{rating.to_i}#{' current' if current_rating == rating}"}
= link_to_remote "no opinion", :url => rate_post_path(resource, :rating => nil), :method => :put, :html => {:class => "rate rated_none#{' current' unless current_rating}"}
- else # static rating
- = resource..round
- resource.reviewable_scale.each do ||
{:class => "rate rated_#{rating}#{' current' if current_rating == rating}"}
JavaScript/AJAX
Done! =)
Additional Use Cases
Like/Dislike
ActsAsReviewable 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 ActsAsReviewable:
Example:
class Post < ActiveRecord::Base
acts_as_reviewable :by => :users, :values => [0, 1]
end
Note: :values is an alias for :scale for semantical reasons in cases like these.
Dependencies
For testing: "rspec" and "sqlite3-ruby":http://gemcutter.org/gems/sqlite3-ruby.
Notes
- Tested with Ruby 1.9.2 and Rails 3.0.5.
- Let me know if you find any bugs; not used in production yet so consider this a concept version.
TODO
Priority:
- bug: Accept , etc..
- documentation: A few more README-examples.
- feature: Useful finders for Reviewable.
- feature: Useful finders for Reviewer.
- testing: More thorough tests, especially for scopes which is a bit tricky.
Maybe:
Related Links
...that might be of interest.
- "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.
Versioning
This project uses Semantic Versioning -- Please read and use to make the world a better place for software: http://semver.org/
License
Released under the MIT license. Copyright (c) "Eric Steen":http://github.com/rubycoder1
This project rocks and uses MIT-LICENSE.