96a5f67221 | ||
---|---|---|
lib | ||
spec | ||
.gitignore | ||
.rubocop.yml | ||
.rubocop_todo.yml | ||
.ruby-gemset | ||
.ruby-version | ||
.travis.yml | ||
CHANGELOG.md | ||
Gemfile | ||
Gemfile.lock | ||
LICENSE | ||
README.md | ||
Rakefile | ||
rating.gemspec |
README.md
Rating
A true Bayesian rating system with scope and cache enabled.
JS Rating?
This is Raty: https://github.com/wbotelhos/raty 🌟
Description
Rating uses the know as "True Bayesian Estimate" inspired on IMDb rating with the following formula:
(WR) = (v ÷ (v + m)) × R + (m ÷ (v + m)) × C
IMDb Implementation:
WR
: weighted rating
R
: average for the movie (mean) = (Rating)
v
: number of votes for the movie = (votes)
m
: minimum votes required to be listed in the Top 250
C
: the mean vote across the whole report
Rating Implementation:
WR
: weighted rating
R
: average for the resource
v
: number of votes for the resource
m
: average of the number of votes
C
: the average rating based on all resources
Install
Add the following code on your Gemfile
and run bundle install
:
gem 'rating'
Run the following task to create a Rating migration:
rails g rating:install
Then execute the migrations to create the to create tables rating_rates
and rating_ratings
:
rake db:migrate
Usage
Just add the callback rating
to your model:
class Author < ApplicationRecord
rating
end
Now this model can vote or receive votes.
rate
You can vote on some resource:
author = Author.last
resource = Article.last
author.rate resource, 3
rating
A voted resource exposes a cached data about it state:
resource = Article.last
resource.rating
It will return a Rating
object that keeps:
average
: the normal mean of votes;
estimate
: the true Bayesian estimate mean value (you should use this over average);
sum
: the sum of votes for this resource;
total
: the total of votes for this resource.
rate_for
You can retrieve the rate of some author gave to some resource:
author = Author.last
resource = Article.last
author.rate_for resource
It will return a Rate
object that keeps:
author
: the author of vote;
resource
: the resource that received the vote;
value
: the value of the vote.
rated?
Maybe you want just to know if some author already rated some resource and receive true
or false
:
author = Author.last
resource = Article.last
author.rated? resource
rates
You can retrieve all rates received by some resource:
resource = Article.last
resource.rates
It will return a collection of Rate
object.
rated
In the same way you can retrieve all rates that some author received:
author = Author.last
author.rated
It will return a collection of Rate
object.
order_by_rating
You can list resource ordered by rating data:
Article.order_by_rating
It will return a collection of resource ordered by estimate desc
as default.
The order column and direction can be changed:
Article.order_by_rating :average, :asc
It will return a collection of resource ordered by Rating
table data.
Scope
All methods support scope query, since you may want to vote on items of a resource instead the resource itself. Let's say an article belongs to one or more categories and you want to vote on some categories of this article.
category_1 = Category.first
category_2 = Category.second
author = Author.last
resource = Article.last
In this situation you should scope the vote of article with some category:
rate
author.rate resource, 3, scope: category_1
author.rate resource, 5, scope: category_2
Now resource
has a rating for category_1
and another one for category_2
.
rating
Recovering the rating values for resource, we have:
resource.rating
# nil
But using the scope to make the right query:
resource.rating scope: category_1
# { average: 3, estimate: 3, sum: 3, total: 1 }
resource.rating scope: category_2
# { average: 5, estimate: 5, sum: 5, total: 1 }
rated
On the same way you can find your rates with a scoped query:
author.rated scope: category_1
# { value: 3, scopeable: category_1 }
rates
The resource still have the power to consult its rates:
article.rates scope: category_1
# { value: 3, scopeable: category_1 }
article.rates scope: category_2
# { value: 3, scopeable: category_2 }
order_by_rating
To order the rating you do the same thing:
Article.order_by_rating scope: category_1
Records
Maybe you want to recover all records with or without scope, so you can add the suffix _records
on relations:
category_1 = Category.first
category_2 = Category.second
author = Author.last
resource = Article.last
author.rate resource, 1
author.rate resource, 3, scope: category_1
author.rate resource, 5, scope: category_2
author.rating_records
# { average: 1, estimate: 1, scopeable: nil , sum: 1, total: 1 },
# { average: 3, estimate: 3, scopeable: category_1, sum: 3, total: 1 },
# { average: 5, estimate: 5, scopeable: category_2, sum: 5, total: 1 }
author.rated_records
# { value: 1 }, { value: 3, scopeable: category_1 }, { value: 5, scopeable: category_2 }
article.rates_records
# { value: 1 }, { value: 3, scopeable: category_1 }, { value: 5, scopeable: category_2 }
As
If you have a model that will only be able to rate but not to receive a rate, configure it as author
.
An author model still can be rated, but won't genarate a Rating record with all values as zero to warm up the cache.
rating as: :author
Metadata
Maybe you want include a comment
together your rating or even a fingerprint
field to make your rating more secure.
So, first you will need to add more fields to the Rating::Rate
table:
class AddCommentAndFingerprintOnRatingRates < ActiveRecord::Migration
def change
add_column :rating_rates, :comment, :text
add_reference :rating_rates, :fingerprint, foreign_key: true, index: true, null: false
end
end
As you can seed, we can add any kind of field we want. Now we just provide this values when we make the rate:
author = Author.last
resource = Article.last
comment = 'This is a very nice rating. s2'
fingerprint = Fingerprint.new(ip: '127.0.0.1')
author.rate resource, 3, metadata: { comment: comment, fingerprint: fingerprint }
Now you can have this data into your model normally:
author = Author.last
rate = author.rates.last
rate.comment # 'This is a very nice rating. s2'
rate.fingerprint # <Fingerprint id:...>
rate.value # 3
Scoping
If you need to warm up a record with scope, you need to setup the scoping
relation.
class Resource < ApplicationRecord
voting scoping: :categories
end
Now, when a resource is created, the cache will be generated for each related category
as scopeable
.
Table Name
You can choose the table where Rating will write the data via YAML config.
You should just to provide a config/rating.yml
file with the following content:
rating:
rate_table: 'reviews'
rating_table: 'review_ratings'
Now the rates will be written on reviews
table over rating_rates
and calculation will be on review_ratings
over rating_ratings
.
You can change one table o both of them.