first commit
commit
598c58af7e
|
@ -0,0 +1,2 @@
|
|||
.byebug_history
|
||||
*.gem
|
|
@ -0,0 +1,63 @@
|
|||
require: rubocop-rspec
|
||||
|
||||
inherit_from: .rubocop_todo.yml
|
||||
|
||||
AllCops:
|
||||
Exclude:
|
||||
- generators/rating/templates/**/*
|
||||
|
||||
Rails:
|
||||
Enabled: true
|
||||
|
||||
Lint/Debugger:
|
||||
Enabled: false
|
||||
|
||||
Metrics/AbcSize:
|
||||
Enabled: false
|
||||
|
||||
Metrics/ClassLength:
|
||||
Enabled: false
|
||||
|
||||
Metrics/CyclomaticComplexity:
|
||||
Enabled: false
|
||||
|
||||
Metrics/LineLength:
|
||||
Enabled: false
|
||||
|
||||
Metrics/MethodLength:
|
||||
Enabled: false
|
||||
|
||||
Metrics/PerceivedComplexity:
|
||||
Enabled: false
|
||||
|
||||
Rails/HasAndBelongsToMany:
|
||||
Enabled: false
|
||||
|
||||
Rails/HttpPositionalArguments:
|
||||
Enabled: false
|
||||
|
||||
Style/IfUnlessModifier:
|
||||
MaxLineLength: 0
|
||||
|
||||
Style/PercentLiteralDelimiters:
|
||||
PreferredDelimiters:
|
||||
'%i': '[]'
|
||||
'%I': '[]'
|
||||
'%r': ()
|
||||
'%w': '[]'
|
||||
'%W': '[]'
|
||||
|
||||
Style/AlignParameters:
|
||||
EnforcedStyle: with_fixed_indentation
|
||||
|
||||
Style/Documentation:
|
||||
Enabled: false
|
||||
|
||||
Style/Encoding:
|
||||
Enabled: false
|
||||
|
||||
Style/SpaceBeforeComma:
|
||||
Enabled: false
|
||||
|
||||
Style/SpaceBeforeFirstArg:
|
||||
Enabled: false
|
|
@ -0,0 +1 @@
|
|||
rating
|
|
@ -0,0 +1 @@
|
|||
2.4.2
|
|
@ -0,0 +1 @@
|
|||
language: ruby
|
|
@ -0,0 +1,3 @@
|
|||
## v0.1.0
|
||||
|
||||
- First release.
|
|
@ -0,0 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gemspec
|
|
@ -0,0 +1,187 @@
|
|||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
rating (0.1.0)
|
||||
rails (>= 4.2, < 6)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (5.1.4)
|
||||
actionpack (= 5.1.4)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (~> 0.6.1)
|
||||
actionmailer (5.1.4)
|
||||
actionpack (= 5.1.4)
|
||||
actionview (= 5.1.4)
|
||||
activejob (= 5.1.4)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (5.1.4)
|
||||
actionview (= 5.1.4)
|
||||
activesupport (= 5.1.4)
|
||||
rack (~> 2.0)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
actionview (5.1.4)
|
||||
activesupport (= 5.1.4)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
||||
activejob (5.1.4)
|
||||
activesupport (= 5.1.4)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (5.1.4)
|
||||
activesupport (= 5.1.4)
|
||||
activerecord (5.1.4)
|
||||
activemodel (= 5.1.4)
|
||||
activesupport (= 5.1.4)
|
||||
arel (~> 8.0)
|
||||
activesupport (5.1.4)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (~> 0.7)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
arel (8.0.0)
|
||||
ast (2.3.0)
|
||||
builder (3.2.3)
|
||||
byebug (9.1.0)
|
||||
coderay (1.1.2)
|
||||
concurrent-ruby (1.0.5)
|
||||
crass (1.0.2)
|
||||
database_cleaner (1.6.2)
|
||||
diff-lcs (1.3)
|
||||
erubi (1.7.0)
|
||||
factory_bot (4.8.2)
|
||||
activesupport (>= 3.0.0)
|
||||
factory_bot_rails (4.8.2)
|
||||
factory_bot (~> 4.8.2)
|
||||
railties (>= 3.0.0)
|
||||
globalid (0.4.1)
|
||||
activesupport (>= 4.2.0)
|
||||
i18n (0.9.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
loofah (2.1.1)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.6.6)
|
||||
mime-types (>= 1.16, < 4)
|
||||
method_source (0.9.0)
|
||||
mime-types (3.1)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2016.0521)
|
||||
mini_portile2 (2.3.0)
|
||||
minitest (5.10.3)
|
||||
nio4r (2.1.0)
|
||||
nokogiri (1.8.1)
|
||||
mini_portile2 (~> 2.3.0)
|
||||
parallel (1.12.0)
|
||||
parser (2.4.0.0)
|
||||
ast (~> 2.2)
|
||||
powerpack (0.1.1)
|
||||
pry (0.11.2)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.9.0)
|
||||
pry-byebug (3.5.0)
|
||||
byebug (~> 9.1)
|
||||
pry (~> 0.10)
|
||||
rack (2.0.3)
|
||||
rack-test (0.7.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rails (5.1.4)
|
||||
actioncable (= 5.1.4)
|
||||
actionmailer (= 5.1.4)
|
||||
actionpack (= 5.1.4)
|
||||
actionview (= 5.1.4)
|
||||
activejob (= 5.1.4)
|
||||
activemodel (= 5.1.4)
|
||||
activerecord (= 5.1.4)
|
||||
activesupport (= 5.1.4)
|
||||
bundler (>= 1.3.0)
|
||||
railties (= 5.1.4)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.0.3)
|
||||
loofah (~> 2.0)
|
||||
railties (5.1.4)
|
||||
actionpack (= 5.1.4)
|
||||
activesupport (= 5.1.4)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
rainbow (2.2.2)
|
||||
rake
|
||||
rake (12.2.1)
|
||||
rspec (3.7.0)
|
||||
rspec-core (~> 3.7.0)
|
||||
rspec-expectations (~> 3.7.0)
|
||||
rspec-mocks (~> 3.7.0)
|
||||
rspec-core (3.7.0)
|
||||
rspec-support (~> 3.7.0)
|
||||
rspec-expectations (3.7.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.7.0)
|
||||
rspec-html-matchers (0.9.1)
|
||||
nokogiri (~> 1)
|
||||
rspec (>= 3.0.0.a, < 4)
|
||||
rspec-mocks (3.7.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.7.0)
|
||||
rspec-rails (3.7.1)
|
||||
actionpack (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
railties (>= 3.0)
|
||||
rspec-core (~> 3.7.0)
|
||||
rspec-expectations (~> 3.7.0)
|
||||
rspec-mocks (~> 3.7.0)
|
||||
rspec-support (~> 3.7.0)
|
||||
rspec-support (3.7.0)
|
||||
rubocop (0.51.0)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.3.3.1, < 3.0)
|
||||
powerpack (~> 0.1)
|
||||
rainbow (>= 2.2.2, < 3.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (~> 1.0, >= 1.0.1)
|
||||
rubocop-rspec (1.19.0)
|
||||
rubocop (>= 0.51.0)
|
||||
ruby-progressbar (1.9.0)
|
||||
shoulda-matchers (3.1.2)
|
||||
activesupport (>= 4.0.0)
|
||||
sprockets (3.7.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
sprockets-rails (3.2.1)
|
||||
actionpack (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
sprockets (>= 3.0.0)
|
||||
sqlite3 (1.3.13)
|
||||
thor (0.20.0)
|
||||
thread_safe (0.3.6)
|
||||
tzinfo (1.2.4)
|
||||
thread_safe (~> 0.1)
|
||||
unicode-display_width (1.3.0)
|
||||
websocket-driver (0.6.5)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.2)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
database_cleaner
|
||||
factory_bot_rails
|
||||
pry-byebug
|
||||
rating!
|
||||
rspec-html-matchers
|
||||
rspec-rails
|
||||
rubocop-rspec
|
||||
shoulda-matchers
|
||||
sqlite3
|
||||
|
||||
BUNDLED WITH
|
||||
1.15.4
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Washington Botelho
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,180 @@
|
|||
# Rating
|
||||
|
||||
[![Build Status](https://travis-ci.org/wbotelhos/rating.svg)](https://travis-ci.org/wbotelhos/rating)
|
||||
[![Gem Version](https://badge.fury.io/rb/rating.svg)](https://badge.fury.io/rb/rating)
|
||||
|
||||
A true Bayesian rating system with cache enabled.
|
||||
|
||||
## JS Rating?
|
||||
|
||||
This is **Raty**: https://github.com/wbotelhos/raty :star2:
|
||||
|
||||
## Description
|
||||
|
||||
Rating uses the know as "True Bayesian Estimate" inspired on [IMDb rating](http://www.imdb.com/help/show_leaf?votestopfaq) 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 (mean) = (Rating)
|
||||
|
||||
`v`: number of votes for the movie = (votes)
|
||||
|
||||
`m`: average of the number of votes
|
||||
|
||||
`C`: the mean vote across the whole report
|
||||
|
||||
## Install
|
||||
|
||||
Add the following code on your `Gemfile` and run `bundle install`:
|
||||
|
||||
```ruby
|
||||
gem 'rating'
|
||||
```
|
||||
|
||||
Run the following task to create a Rating migration:
|
||||
|
||||
```bash
|
||||
rails g rating:install
|
||||
```
|
||||
|
||||
Then execute the migrations to create the to create tables `rating_rates` and `rating_ratings`:
|
||||
|
||||
```bash
|
||||
rake db:migrate
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Just add the callback `rating` to your model:
|
||||
|
||||
```ruby
|
||||
class User < ApplicationRecord
|
||||
rating
|
||||
end
|
||||
```
|
||||
|
||||
Now this model can vote or be voted.
|
||||
|
||||
### rate
|
||||
|
||||
You can vote on some resource:
|
||||
|
||||
```ruby
|
||||
author = User.last
|
||||
resource = Article.last
|
||||
|
||||
author.rate(resource, 3)
|
||||
```
|
||||
|
||||
### rating
|
||||
|
||||
A voted resource exposes a cached data about it state:
|
||||
|
||||
```ruby
|
||||
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:
|
||||
|
||||
```ruby
|
||||
author = User.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`:
|
||||
|
||||
```ruby
|
||||
author = User.last
|
||||
resource = Article.last
|
||||
|
||||
author.rated? resource
|
||||
```
|
||||
|
||||
### rates
|
||||
|
||||
You can retrieve all rates made by some author:
|
||||
|
||||
```ruby
|
||||
author = User.last
|
||||
|
||||
author.rates
|
||||
```
|
||||
|
||||
It will return a collection of `Rate` object.
|
||||
|
||||
### rated
|
||||
|
||||
In the same way you can retrieve all rates that some author received:
|
||||
|
||||
```ruby
|
||||
author = User.last
|
||||
|
||||
author.rated
|
||||
```
|
||||
|
||||
It will return a collection of `Rate` object.
|
||||
|
||||
### order_by_rating
|
||||
|
||||
You can list resource ordered by rating data:
|
||||
|
||||
```ruby
|
||||
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:
|
||||
|
||||
```ruby
|
||||
Article.order_by_rating :average, :asc
|
||||
```
|
||||
|
||||
It will return a collection of resource ordered by `Rating` table data.
|
||||
|
||||
## Love it!
|
||||
|
||||
Via [PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=X8HEP2878NDEG&item_name=rating) or [Gratipay](https://gratipay.com/rating). Thanks! (:
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rspec/core/rake_task'
|
||||
|
||||
RSpec::Core::RakeTask.new
|
||||
|
||||
task default: :spec
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Rating
|
||||
class InstallGenerator < Rails::Generators::Base
|
||||
source_root File.expand_path('../templates', __FILE__)
|
||||
|
||||
desc 'configure Rating'
|
||||
|
||||
def create_migration
|
||||
version = Time.current.strftime('%Y%m%d%H%M%S')
|
||||
|
||||
template 'db/migrate/create_rating_tables.rb', "db/migrate/#{version}_create_rating_tables.rb"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateRatingTables < ActiveRecord::Migration[5.0]
|
||||
def change
|
||||
create_table :rating_rates do |t|
|
||||
t.decimal :value, default: 0, precision: 17, scale: 14
|
||||
|
||||
t.references :author , index: true, null: false, polymorphic: true
|
||||
t.references :resource, index: true, null: false, polymorphic: true
|
||||
|
||||
t.timestamps null: false
|
||||
end
|
||||
|
||||
add_index :rating_rates, %i[author_id author_type resource_id resource_type],
|
||||
name: :index_rating_rates_on_author_and_resource,
|
||||
unique: true
|
||||
|
||||
create_table :rating_ratings do |t|
|
||||
t.decimal :average , default: 0, mull: false, precision: 17, scale: 14
|
||||
t.decimal :estimate, default: 0, mull: false, precision: 17, scale: 14
|
||||
t.integer :sum , default: 0, mull: false
|
||||
t.integer :total , default: 0, mull: false
|
||||
|
||||
t.references :resource, index: true, null: false, polymorphic: true
|
||||
|
||||
t.timestamps null: false
|
||||
end
|
||||
|
||||
add_index :rating_ratings, %i[resource_id resource_type], unique: true
|
||||
end
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Rating
|
||||
end
|
||||
|
||||
require 'rating/models/rating/extension'
|
||||
require 'rating/models/rating/rate'
|
||||
require 'rating/models/rating/rating'
|
||||
|
||||
ActiveRecord::Base.include Rating::Extension
|
|
@ -0,0 +1,46 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Rating
|
||||
module Extension
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
def rate(resource, value, author: self)
|
||||
Rate.create author: author, resource: resource, value: value
|
||||
end
|
||||
|
||||
def rate_for(resource)
|
||||
Rate.rate_for author: self, resource: resource
|
||||
end
|
||||
|
||||
def rated?(resource)
|
||||
!rate_for(resource).nil?
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def rating
|
||||
after_create { Rating.find_or_create_by resource: self }
|
||||
|
||||
has_one :rating,
|
||||
as: :resource,
|
||||
class_name: '::Rating::Rating',
|
||||
dependent: :destroy
|
||||
|
||||
has_many :rates,
|
||||
as: :resource,
|
||||
class_name: '::Rating::Rate',
|
||||
dependent: :destroy
|
||||
|
||||
has_many :rated,
|
||||
as: :author,
|
||||
class_name: '::Rating::Rate',
|
||||
dependent: :destroy
|
||||
|
||||
scope :order_by_rating, ->(column = :estimate, direction = :desc) {
|
||||
includes(:rating).order("#{Rating.table_name}.#{column} #{direction}")
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,41 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Rating
|
||||
class Rate < ActiveRecord::Base
|
||||
self.table_name = 'rating_rates'
|
||||
|
||||
after_save :update_rating
|
||||
|
||||
belongs_to :author , polymorphic: true
|
||||
belongs_to :resource, polymorphic: true
|
||||
|
||||
validates :author, :resource, :value, presence: true
|
||||
validates :value, numericality: { greater_than_or_equal_to: 1, less_than_or_equal_to: 100 }
|
||||
|
||||
validates :author_id, uniqueness: {
|
||||
case_sensitive: false,
|
||||
scope: %i[author_type resource_id resource_type]
|
||||
}
|
||||
|
||||
def self.create(author:, resource:, value:)
|
||||
record = find_or_initialize_by(author: author, resource: resource)
|
||||
|
||||
return record if record.persisted? && value == record.value
|
||||
|
||||
record.value = value
|
||||
record.save
|
||||
|
||||
record
|
||||
end
|
||||
|
||||
def self.rate_for(author:, resource:)
|
||||
find_by author: author, resource: resource
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_rating
|
||||
::Rating::Rating.update_rating resource
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,96 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Rating
|
||||
class Rating < ActiveRecord::Base
|
||||
self.table_name = 'rating_ratings'
|
||||
|
||||
belongs_to :resource, polymorphic: true
|
||||
|
||||
validates :average, :estimate, :resource, :sum, :total, presence: true
|
||||
validates :average, :estimate, :sum, :total, numericality: true
|
||||
|
||||
class << self
|
||||
def averager_data(resource)
|
||||
total_count = how_many_resource_received_votes_sql?
|
||||
distinct_count = how_many_resource_received_votes_sql?(distinct: true)
|
||||
|
||||
sql = %(
|
||||
SELECT
|
||||
(#{total_count} / CAST(#{distinct_count} AS float)) count_avg,
|
||||
COALESCE(AVG(value), 0) rating_avg
|
||||
FROM #{rate_table_name}
|
||||
WHERE resource_type = :resource_type
|
||||
).squish
|
||||
|
||||
execute_sql [sql, resource_type: resource.class.base_class.name]
|
||||
end
|
||||
|
||||
def data(resource)
|
||||
averager = averager_data(resource)
|
||||
values = values_data(resource)
|
||||
|
||||
{
|
||||
average: values.rating_avg,
|
||||
estimate: estimate(averager, values),
|
||||
sum: values.rating_sum,
|
||||
total: values.rating_count
|
||||
}
|
||||
end
|
||||
|
||||
def values_data(resource)
|
||||
sql = %(
|
||||
SELECT
|
||||
COALESCE(AVG(value), 0) rating_avg,
|
||||
COALESCE(SUM(value), 0) rating_sum,
|
||||
COUNT(1) rating_count
|
||||
FROM #{rate_table_name}
|
||||
WHERE resource_type = ? and resource_id = ?
|
||||
).squish
|
||||
|
||||
execute_sql [sql, resource.class.base_class.name, resource.id]
|
||||
end
|
||||
|
||||
def update_rating(resource)
|
||||
record = find_or_initialize_by(resource: resource)
|
||||
result = data(resource)
|
||||
|
||||
record.average = result[:average]
|
||||
record.sum = result[:sum]
|
||||
record.total = result[:total]
|
||||
record.estimate = result[:estimate]
|
||||
|
||||
record.save
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def estimate(averager, values)
|
||||
resource_type_rating_avg = averager.rating_avg
|
||||
count_avg = averager.count_avg
|
||||
resource_rating_avg = values.rating_avg
|
||||
resource_rating_count = values.rating_count.to_f
|
||||
|
||||
(resource_rating_count / (resource_rating_count + count_avg)) * resource_rating_avg +
|
||||
(count_avg / (resource_rating_count + count_avg)) * resource_type_rating_avg
|
||||
end
|
||||
|
||||
def execute_sql(sql)
|
||||
Rate.find_by_sql(sql).first
|
||||
end
|
||||
|
||||
def how_many_resource_received_votes_sql?(distinct: false)
|
||||
count = distinct ? 'COUNT(DISTINCT resource_id)' : 'COUNT(1)'
|
||||
|
||||
%((
|
||||
SELECT #{count}
|
||||
FROM #{rate_table_name}
|
||||
WHERE resource_type = :resource_type
|
||||
))
|
||||
end
|
||||
|
||||
def rate_table_name
|
||||
@rate_table_name ||= Rate.table_name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Rating
|
||||
VERSION = '0.1.0'
|
||||
end
|
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
lib = File.expand_path('../lib', __FILE__)
|
||||
|
||||
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
||||
|
||||
require 'rating/version'
|
||||
|
||||
Gem::Specification.new do |spec|
|
||||
spec.author = 'Washington Botelho'
|
||||
spec.description = 'A true Bayesian rating system with cache enabled.'
|
||||
spec.email = 'wbotelhos@gmail.com'
|
||||
spec.files = Dir['lib/**/*'] + %w[CHANGELOG.md LICENSE README.md]
|
||||
spec.homepage = 'https://github.com/wbotelhos/rating'
|
||||
spec.license = 'MIT'
|
||||
spec.name = 'rating'
|
||||
spec.platform = Gem::Platform::RUBY
|
||||
spec.summary = 'A true Bayesian rating system with cache enabled.'
|
||||
spec.test_files = Dir['spec/**/*']
|
||||
spec.version = Rating::VERSION
|
||||
|
||||
spec.add_dependency 'rails', '>= 4.2', '< 6'
|
||||
|
||||
spec.add_development_dependency 'database_cleaner'
|
||||
spec.add_development_dependency 'factory_bot_rails'
|
||||
spec.add_development_dependency 'pry-byebug'
|
||||
spec.add_development_dependency 'rspec-html-matchers'
|
||||
spec.add_development_dependency 'rspec-rails'
|
||||
spec.add_development_dependency 'rubocop-rspec'
|
||||
spec.add_development_dependency 'shoulda-matchers'
|
||||
spec.add_development_dependency 'sqlite3'
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :article do
|
||||
sequence(:name) { |i| "Name #{i}" }
|
||||
end
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :rating_rate, class: Rating::Rate do
|
||||
value 100
|
||||
|
||||
author { create :user }
|
||||
resource { create :article }
|
||||
end
|
||||
end
|
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :rating_rating, class: Rating::Rating do
|
||||
average 100
|
||||
estimate 100
|
||||
sum 100
|
||||
total 1
|
||||
|
||||
resource { create :article }
|
||||
end
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :user do
|
||||
sequence(:name) { |i| "Name #{i}" }
|
||||
end
|
||||
end
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Rating::Extension, ':after_create' do
|
||||
context 'when creates object' do
|
||||
let!(:user) { create :user }
|
||||
|
||||
it 'creates a record with zero values just to be easy to make the count' do
|
||||
rating = Rating::Rating.find_by(resource: user)
|
||||
|
||||
expect(rating.average).to eq 0
|
||||
expect(rating.estimate).to eq 0
|
||||
expect(rating.resource).to eq user
|
||||
expect(rating.sum).to eq 0
|
||||
expect(rating.total).to eq 0
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,121 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Rating::Extension, ':order_by_rating' do
|
||||
let!(:user_1) { create :user }
|
||||
let!(:user_2) { create :user }
|
||||
|
||||
let!(:article_1) { create :article }
|
||||
let!(:article_2) { create :article }
|
||||
let!(:article_3) { create :article }
|
||||
|
||||
before do
|
||||
create :rating_rate, author: user_1, resource: article_1, value: 100
|
||||
create :rating_rate, author: user_1, resource: article_2, value: 11
|
||||
create :rating_rate, author: user_1, resource: article_3, value: 10
|
||||
create :rating_rate, author: user_2, resource: article_1, value: 1
|
||||
end
|
||||
|
||||
context 'with default filters' do
|
||||
it 'sorts by :estimate :desc' do
|
||||
expect(Article.order_by_rating).to eq [
|
||||
article_1,
|
||||
article_2,
|
||||
article_3
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
context 'filtering by :average' do
|
||||
context 'as asc' do
|
||||
it 'works' do
|
||||
expect(Article.order_by_rating(:average, :asc)).to eq [
|
||||
article_3,
|
||||
article_2,
|
||||
article_1
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
context 'as desc' do
|
||||
it 'works' do
|
||||
expect(Article.order_by_rating(:average, :desc)).to eq [
|
||||
article_1,
|
||||
article_2,
|
||||
article_3
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'filtering by :estimate' do
|
||||
context 'as asc' do
|
||||
it 'works' do
|
||||
expect(Article.order_by_rating(:estimate, :asc)).to eq [
|
||||
article_3,
|
||||
article_2,
|
||||
article_1
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
context 'as desc' do
|
||||
it 'works' do
|
||||
expect(Article.order_by_rating(:estimate, :desc)).to eq [
|
||||
article_1,
|
||||
article_2,
|
||||
article_3
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'filtering by :sum' do
|
||||
context 'as asc' do
|
||||
it 'works' do
|
||||
expect(Article.order_by_rating(:sum, :asc)).to eq [
|
||||
article_3,
|
||||
article_2,
|
||||
article_1
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
context 'as desc' do
|
||||
it 'works' do
|
||||
expect(Article.order_by_rating(:sum, :desc)).to eq [
|
||||
article_1,
|
||||
article_2,
|
||||
article_3
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
context 'filtering by :total' do
|
||||
context 'as asc' do
|
||||
it 'works' do
|
||||
result = Article.order_by_rating(:total, :asc)
|
||||
|
||||
expect(result[0..1]).to match_array [article_2, article_3]
|
||||
expect(result.last).to eq article_1
|
||||
end
|
||||
end
|
||||
|
||||
context 'as desc' do
|
||||
it 'works' do
|
||||
result = Article.order_by_rating(:total, :desc)
|
||||
|
||||
expect(result.first).to eq article_1
|
||||
expect(result[1..2]).to match_array [article_2, article_3]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with other resource' do
|
||||
it 'works' do
|
||||
expect(User.order_by_rating(:total, :desc)).to match_array [user_1, user_2]
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Rating::Extension, ':rate_for' do
|
||||
let!(:user) { create :user }
|
||||
let!(:article) { create :article }
|
||||
|
||||
it 'delegates to rate object' do
|
||||
expect(Rating::Rate).to receive(:rate_for).with author: user, resource: article
|
||||
|
||||
user.rate_for article
|
||||
end
|
||||
end
|
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Rating::Extension, ':rate' do
|
||||
let!(:user) { create :user }
|
||||
let!(:article) { create :article }
|
||||
|
||||
it 'delegates to rate object' do
|
||||
expect(Rating::Rate).to receive(:create).with author: user, resource: article, value: 3
|
||||
|
||||
user.rate article, 3
|
||||
end
|
||||
end
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Rating::Extension, ':rated?' do
|
||||
let!(:user) { create :user }
|
||||
let!(:article) { create :article }
|
||||
|
||||
context 'when has no rate for the given resource' do
|
||||
before { allow(user).to receive(:rate_for).with(article) { nil } }
|
||||
|
||||
specify { expect(user.rated?(article)).to eq false }
|
||||
end
|
||||
|
||||
context 'when has rate for the given resource' do
|
||||
before { allow(user).to receive(:rate_for).with(article) { double } }
|
||||
|
||||
specify { expect(user.rated?(article)).to eq true }
|
||||
end
|
||||
end
|
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Rating::Extension, ':rated' do
|
||||
let!(:user) { create :user }
|
||||
let!(:article) { create :article }
|
||||
|
||||
before { user.rate article, 3 }
|
||||
|
||||
it 'returns rates made by the caller' do
|
||||
expect(user.rated).to eq [Rating::Rate.find_by(resource: article)]
|
||||
end
|
||||
|
||||
context 'when destroy author' do
|
||||
before do
|
||||
expect(Rating::Rate.where(author: user).count).to eq 1
|
||||
|
||||
user.destroy!
|
||||
end
|
||||
|
||||
it 'destroys rates of this author' do
|
||||
expect(Rating::Rate.where(author: user).count).to eq 0
|
||||
end
|
||||
end
|
||||
|
||||
context 'when destroy resource rated by author' do
|
||||
before do
|
||||
expect(Rating::Rate.where(resource: article).count).to eq 1
|
||||
|
||||
article.destroy!
|
||||
end
|
||||
|
||||
it 'destroys rates for that resource' do
|
||||
expect(Rating::Rate.where(resource: article).count).to eq 0
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Rating::Extension, ':rates' do
|
||||
let!(:user) { create :user }
|
||||
let!(:article) { create :article }
|
||||
|
||||
before { user.rate article, 3 }
|
||||
|
||||
it 'returns rates record' do
|
||||
expect(article.rates).to eq [Rating::Rate.last]
|
||||
end
|
||||
|
||||
context 'when destroy author' do
|
||||
before do
|
||||
expect(Rating::Rate.where(resource: article).count).to eq 1
|
||||
|
||||
user.destroy!
|
||||
end
|
||||
|
||||
it 'destroys rates of that resource' do
|
||||
expect(Rating::Rate.where(resource: article).count).to eq 0
|
||||
end
|
||||
end
|
||||
|
||||
context 'when destroy resource' do
|
||||
before do
|
||||
expect(Rating::Rate.where(resource: article).count).to eq 1
|
||||
|
||||
article.destroy!
|
||||
end
|
||||
|
||||
it 'destroys rates of that resource' do
|
||||
expect(Rating::Rate.where(resource: article).count).to eq 0
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Rating::Extension, ':rating' do
|
||||
let!(:user_1) { create :user }
|
||||
let!(:article) { create :article }
|
||||
|
||||
before { user_1.rate article, 1 }
|
||||
|
||||
it 'returns rating record' do
|
||||
expect(article.rating).to eq Rating::Rating.last
|
||||
end
|
||||
|
||||
context 'when destroy author' do
|
||||
let!(:user_2) { create :user }
|
||||
|
||||
before { user_2.rate article, 2 }
|
||||
|
||||
it 'does not destroy resource rating' do
|
||||
expect(Rating::Rating.where(resource: article).count).to eq 1
|
||||
|
||||
user_1.destroy!
|
||||
|
||||
expect(Rating::Rating.where(resource: article).count).to eq 1
|
||||
end
|
||||
end
|
||||
|
||||
context 'when destroy resource' do
|
||||
it 'destroys resource rating too' do
|
||||
expect(Rating::Rating.where(resource: article).count).to eq 1
|
||||
|
||||
article.destroy!
|
||||
|
||||
expect(Rating::Rating.where(resource: article).count).to eq 0
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,64 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Rating::Rate, ':create' do
|
||||
let!(:user) { create :user }
|
||||
let!(:article) { create :article }
|
||||
|
||||
before { create :rating_rate, author: user, resource: article, value: 3 }
|
||||
|
||||
context 'when rate does not exist yet' do
|
||||
it 'creates a rate entry' do
|
||||
rate = described_class.last
|
||||
|
||||
expect(rate.author).to eq user
|
||||
expect(rate.resource).to eq article
|
||||
expect(rate.value).to eq 3
|
||||
end
|
||||
|
||||
it 'creates a rating entry' do
|
||||
rating = Rating::Rating.last
|
||||
|
||||
expect(rating.average).to eq 3
|
||||
expect(rating.estimate).to eq 3
|
||||
expect(rating.resource).to eq article
|
||||
expect(rating.sum).to eq 3
|
||||
expect(rating.total).to eq 1
|
||||
end
|
||||
end
|
||||
|
||||
context 'when rate already exists' do
|
||||
let!(:user_2) { create :user }
|
||||
|
||||
before { create :rating_rate, author: user_2, resource: article, value: 4 }
|
||||
|
||||
it 'creates one more rate entry' do
|
||||
rates = described_class.where(author: [user, user_2]).order('created_at asc')
|
||||
|
||||
expect(rates.size).to eq 2
|
||||
|
||||
rate = rates[0]
|
||||
|
||||
expect(rate.author).to eq user
|
||||
expect(rate.resource).to eq article
|
||||
expect(rate.value).to eq 3
|
||||
|
||||
rate = rates[1]
|
||||
|
||||
expect(rate.author).to eq user_2
|
||||
expect(rate.resource).to eq article
|
||||
expect(rate.value).to eq 4
|
||||
end
|
||||
|
||||
it 'updates the unique rating entry' do
|
||||
rating = Rating::Rating.find_by(resource: article)
|
||||
|
||||
expect(rating.average).to eq 3.5
|
||||
expect(rating.estimate).to eq 3.5
|
||||
expect(rating.resource).to eq article
|
||||
expect(rating.sum).to eq 7
|
||||
expect(rating.total).to eq 2
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Rating::Rate, ':rate_for' do
|
||||
let!(:user) { create :user }
|
||||
let!(:article) { create :article }
|
||||
|
||||
context 'when rate does not exist' do
|
||||
specify { expect(described_class.rate_for(author: user, resource: article)).to eq nil }
|
||||
end
|
||||
|
||||
context 'when rate does not exist' do
|
||||
before { described_class.create author: user, resource: article, value: 3 }
|
||||
|
||||
it 'returns the record' do
|
||||
expect(described_class.rate_for(author: user, resource: article)).to eq described_class.last
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Rating::Rate do
|
||||
let!(:object) { build :rating_rate }
|
||||
|
||||
it { expect(object).to be_valid }
|
||||
|
||||
it { is_expected.to belong_to :author }
|
||||
it { is_expected.to belong_to :resource }
|
||||
|
||||
it { is_expected.to validate_presence_of :author }
|
||||
it { is_expected.to validate_presence_of :resource }
|
||||
it { is_expected.to validate_presence_of :value }
|
||||
|
||||
specify do
|
||||
is_expected.to validate_numericality_of(:value).is_less_than_or_equal_to(100).is_less_than_or_equal_to 100
|
||||
end
|
||||
|
||||
specify do
|
||||
expect(object).to validate_uniqueness_of(:author_id)
|
||||
.scoped_to(%i[author_type resource_id resource_type])
|
||||
.case_insensitive
|
||||
end
|
||||
end
|
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Rating::Rating, ':averager_data' do
|
||||
subject { described_class.averager_data article_1 }
|
||||
|
||||
let!(:user_1) { create :user }
|
||||
let!(:user_2) { create :user }
|
||||
|
||||
let!(:article_1) { create :article }
|
||||
let!(:article_2) { create :article }
|
||||
let!(:article_3) { create :article }
|
||||
|
||||
before do
|
||||
create :rating_rate, author: user_1, resource: article_1, value: 100
|
||||
create :rating_rate, author: user_1, resource: article_2, value: 11
|
||||
create :rating_rate, author: user_1, resource: article_3, value: 10
|
||||
create :rating_rate, author: user_2, resource: article_1, value: 1
|
||||
end
|
||||
|
||||
it 'returns the values average of given resource type' do
|
||||
expect(subject.as_json['rating_avg']).to eq 30.5
|
||||
end
|
||||
|
||||
it 'returns the average of number of records for the given resource type' do
|
||||
expect(subject.as_json['count_avg']).to eq 1.3333333333333333
|
||||
end
|
||||
end
|
|
@ -0,0 +1,37 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Rating::Rating, ':data' do
|
||||
subject { described_class.data article_1 }
|
||||
|
||||
let!(:user_1) { create :user }
|
||||
let!(:user_2) { create :user }
|
||||
|
||||
let!(:article_1) { create :article }
|
||||
let!(:article_2) { create :article }
|
||||
let!(:article_3) { create :article }
|
||||
|
||||
before do
|
||||
create :rating_rate, author: user_1, resource: article_1, value: 100
|
||||
create :rating_rate, author: user_1, resource: article_2, value: 11
|
||||
create :rating_rate, author: user_1, resource: article_3, value: 10
|
||||
create :rating_rate, author: user_2, resource: article_1, value: 1
|
||||
end
|
||||
|
||||
it 'returns the average of value for a resource' do
|
||||
expect(subject[:average]).to eq 50.5
|
||||
end
|
||||
|
||||
it 'returns the sum of values for a resource' do
|
||||
expect(subject[:sum]).to eq 101
|
||||
end
|
||||
|
||||
it 'returns the count of votes for a resource' do
|
||||
expect(subject[:total]).to eq 2
|
||||
end
|
||||
|
||||
it 'returns the estimate for a resource' do
|
||||
expect(subject[:estimate]).to eq 42.50000000000001
|
||||
end
|
||||
end
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Rating::Rating, ':update_rating' do
|
||||
let!(:user_1) { create :user }
|
||||
let!(:user_2) { create :user }
|
||||
|
||||
let!(:article_1) { create :article }
|
||||
let!(:article_2) { create :article }
|
||||
let!(:article_3) { create :article }
|
||||
|
||||
before do
|
||||
create :rating_rate, author: user_1, resource: article_1, value: 100
|
||||
create :rating_rate, author: user_1, resource: article_2, value: 11
|
||||
create :rating_rate, author: user_1, resource: article_3, value: 10
|
||||
create :rating_rate, author: user_2, resource: article_1, value: 1
|
||||
end
|
||||
|
||||
it 'updates the rating data of the given resource' do
|
||||
record = described_class.find_by(resource: article_1)
|
||||
|
||||
expect(record.average).to eq 50.50000000000001
|
||||
expect(record.estimate).to eq 42.50000000000001
|
||||
expect(record.sum).to eq 101
|
||||
expect(record.total).to eq 2
|
||||
end
|
||||
end
|
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Rating::Rating, ':values_data' do
|
||||
subject { described_class.values_data article_1 }
|
||||
|
||||
let!(:user_1) { create :user }
|
||||
let!(:user_2) { create :user }
|
||||
|
||||
let!(:article_1) { create :article }
|
||||
let!(:article_2) { create :article }
|
||||
let!(:article_3) { create :article }
|
||||
|
||||
before do
|
||||
create :rating_rate, author: user_1, resource: article_1, value: 100
|
||||
create :rating_rate, author: user_1, resource: article_2, value: 11
|
||||
create :rating_rate, author: user_1, resource: article_3, value: 10
|
||||
create :rating_rate, author: user_2, resource: article_1, value: 4
|
||||
end
|
||||
|
||||
it 'returns the average of value for a resource' do
|
||||
expect(subject.as_json['rating_avg']).to eq 52.0
|
||||
end
|
||||
|
||||
it 'returns the sum of values for a resource' do
|
||||
expect(subject.as_json['rating_sum']).to eq 104
|
||||
end
|
||||
|
||||
it 'returns the count of votes for a resource' do
|
||||
expect(subject.as_json['rating_count']).to eq 2
|
||||
end
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Rating::Rating do
|
||||
let!(:object) { build :rating_rating }
|
||||
|
||||
it { expect(object).to be_valid }
|
||||
|
||||
it { is_expected.to belong_to :resource }
|
||||
|
||||
it { is_expected.to validate_presence_of :average }
|
||||
it { is_expected.to validate_presence_of :estimate }
|
||||
it { is_expected.to validate_presence_of :resource }
|
||||
it { is_expected.to validate_presence_of :sum }
|
||||
it { is_expected.to validate_presence_of :total }
|
||||
end
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
ENV['RAILS_ENV'] ||= 'test'
|
||||
|
||||
require 'active_record/railtie'
|
||||
require 'pry-byebug'
|
||||
require 'rating'
|
||||
|
||||
ActiveRecord::Base.establish_connection adapter: :sqlite3, database: ':memory:'
|
||||
|
||||
Dir[File.expand_path('support/**/*.rb', __dir__)].each { |file| require file }
|
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rspec/rails'
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.filter_run_when_matching :focus
|
||||
|
||||
config.disable_monkey_patching!
|
||||
config.infer_spec_type_from_file_location!
|
||||
|
||||
config.expect_with :rspec do |expectations|
|
||||
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
||||
end
|
||||
|
||||
config.mock_with :rspec do |mocks|
|
||||
mocks.verify_partial_doubles = true
|
||||
end
|
||||
|
||||
config.infer_base_class_for_anonymous_controllers = false
|
||||
config.order = :random
|
||||
config.profile_examples = true
|
||||
end
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'database_cleaner'
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.before :suite do
|
||||
DatabaseCleaner.strategy = :transaction
|
||||
|
||||
DatabaseCleaner.clean_with :truncation
|
||||
end
|
||||
|
||||
config.before { DatabaseCleaner.start }
|
||||
|
||||
config.around do |spec|
|
||||
DatabaseCleaner.cleaning do
|
||||
spec.run
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateArticlesTable < ActiveRecord::Migration[5.0]
|
||||
def change
|
||||
create_table :articles do |t|
|
||||
t.string :name, null: false
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateUsersTable < ActiveRecord::Migration[5.0]
|
||||
def change
|
||||
create_table :users do |t|
|
||||
t.string :name, null: false
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'factory_bot'
|
||||
|
||||
Dir[File.expand_path('../factories/**/*.rb', __dir__)].each { |file| require file }
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.include FactoryBot::Syntax::Methods
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rspec-html-matchers'
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.include RSpecHtmlMatchers
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require File.expand_path('../../lib/generators/rating/templates/db/migrate/create_rating_tables.rb', __dir__)
|
||||
|
||||
CreateRatingTables.new.change
|
||||
CreateUsersTable.new.change
|
||||
CreateArticlesTable.new.change
|
|
@ -0,0 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Article < ::ActiveRecord::Base
|
||||
rating
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class User < ::ActiveRecord::Base
|
||||
rating
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'shoulda-matchers'
|
||||
|
||||
Shoulda::Matchers.configure do |config|
|
||||
config.integrate do |with|
|
||||
with.library :rails
|
||||
with.test_framework :rspec
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue