rating 1.0.0 → 2.0.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +47 -4
- data/README.md +28 -21
- data/lib/generators/rating/templates/db/migrate/create_rate_table.rb +5 -5
- data/lib/generators/rating/templates/db/migrate/create_rating_table.rb +7 -7
- data/lib/rating/config.rb +9 -3
- data/lib/rating/models/rating/extension.rb +9 -8
- data/lib/rating/models/rating/rate.rb +13 -8
- data/lib/rating/models/rating/rating.rb +42 -50
- data/lib/rating/version.rb +1 -1
- metadata +5 -162
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 58836d050bdbccc7ed3cd471a8eebfe5bf3b6e16f3cfe6b5514b5e7ee724d59f
|
|
4
|
+
data.tar.gz: 5b92166c762349531174a1a04a2784eb826f7f5a37d12d9f607a464482aae0b6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3a3a39af0d990559f576a8a65e04f1d528bf89dd739e50b8127b43c3ec06b659e8cd2e62458337b903c6855fa6edad45ded96d5be94f67e5f5860d9904a5ae45
|
|
7
|
+
data.tar.gz: dacdd84b9eaed47bdc1ceec1eceae4218f2ad5bb26c6b2f0ad81f4bf58ee3764d04b4317b8073f1211c00d7ee33bd525a5d3716362d74b5db8001907389aa501
|
data/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,54 @@
|
|
|
1
|
-
##
|
|
1
|
+
## v2.0.0
|
|
2
|
+
|
|
3
|
+
### Break Changes
|
|
4
|
+
|
|
5
|
+
- Ruby 3.0, and 3.1 and 3.2 are no longer supported. The minimum Ruby version is now 3.3;
|
|
6
|
+
- The `estimate` field is now computed via Evan Miller's lower bound of the confidence interval
|
|
7
|
+
(https://www.evanmiller.org/ranking-items-with-star-ratings.html) instead of the previous weighted
|
|
8
|
+
Bayesian average. The new formula uses the full vote distribution (not just the mean), so it ranks
|
|
9
|
+
consistent ratings above polarized ones with the same mean, and items with few votes below items
|
|
10
|
+
with many votes;
|
|
11
|
+
- The `estimate` and `average` columns increased from `DECIMAL(11, 2)` to `DECIMAL(12, 8)`. Existing
|
|
12
|
+
apps must run a manual upgrade migration:
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
class UpgradeRatingPrecision < ActiveRecord::Migration[7.0]
|
|
16
|
+
def change
|
|
17
|
+
change_column :rating_ratings, :average, :decimal, precision: 12, scale: 8, default: 0, null: false
|
|
18
|
+
change_column :rating_ratings, :estimate, :decimal, precision: 12, scale: 8, default: 0, null: false
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
After running the migration, the new `estimate` values will be populated on the next vote per
|
|
24
|
+
resource. To force a recalculation now, iterate over your rated resources and call `Rating::Rating.update_rating(resource, scope)`;
|
|
25
|
+
- The `.round(2)` previously applied to `estimate` and `average` is removed. Values are stored at
|
|
26
|
+
full column precision. Round at the view layer if needed for display;
|
|
27
|
+
- The `Rating::Rate#value` validation changed from `1..100` to `1..Rating::Config.rating_levels`
|
|
28
|
+
(default 5) and now requires an integer. If your app uses a different scale, configure
|
|
29
|
+
`rating_levels` (see below);
|
|
30
|
+
- New configuration: `Rating::Config.rating_levels` (default `5`) defines the maximum value of your
|
|
31
|
+
rating scale. `Rating::Config.rating_z_score` (default `1.96`, i.e. 95% confidence) tunes how
|
|
32
|
+
aggressively low-confidence items are penalized in the estimate.
|
|
33
|
+
|
|
34
|
+
### Bug Fixes
|
|
35
|
+
|
|
36
|
+
- Fixes #8: `PG::NumericValueOutOfRange` overflow that occurred when a `resource_type` accumulated
|
|
37
|
+
1000+ rates. The new formula does not compute the `total_count / distinct_count` ratio that
|
|
38
|
+
caused the overflow.
|
|
2
39
|
|
|
3
|
-
###
|
|
40
|
+
### Updates
|
|
4
41
|
|
|
5
|
-
-
|
|
42
|
+
- Fixes a typo in the migration generator template (`mull: false` → `null: false`);
|
|
43
|
+
- The `mysql2` and `pg` gems are no longer transitive dev dependencies of the gem. Contributors
|
|
44
|
+
install only the adapter they need via the Gemfile groups (`bundle install --without mysql` or
|
|
45
|
+
`--without postgres`).
|
|
6
46
|
|
|
7
|
-
|
|
47
|
+
## v1.0.0
|
|
48
|
+
|
|
49
|
+
### Break Changes
|
|
8
50
|
|
|
51
|
+
- The attributes `estimate` and `average` now is rounded by two decimal numbers;
|
|
9
52
|
- The method `order_by_rating` now receives a hash parameter to avoid scope and so support Ruby 3.2;
|
|
10
53
|
|
|
11
54
|
## v0.12.0
|
data/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# Rating
|
|
2
2
|
|
|
3
|
-
[](https://github.com/wbotelhos/rating/actions)
|
|
4
4
|
[](https://badge.fury.io/rb/rating)
|
|
5
5
|
[](https://codeclimate.com/github/wbotelhos/rating/maintainability)
|
|
6
|
-
[](https://codecov.io/gh/wbotelhos/rating)
|
|
7
7
|
[](https://github.com/sponsors/wbotelhos)
|
|
8
8
|
|
|
9
|
-
A
|
|
9
|
+
A confidence-based rating system with scope and cache enabled.
|
|
10
10
|
|
|
11
11
|
## JS Rating?
|
|
12
12
|
|
|
@@ -14,35 +14,42 @@ This is **Raty**: https://github.com/wbotelhos/raty :star2:
|
|
|
14
14
|
|
|
15
15
|
## Description
|
|
16
16
|
|
|
17
|
-
Rating
|
|
17
|
+
Rating computes the `estimate` field using the **lower bound of the confidence interval** described
|
|
18
|
+
by Evan Miller in [Ranking Items With Star Ratings](https://www.evanmiller.org/ranking-items-with-star-ratings.html).
|
|
19
|
+
|
|
20
|
+
The formula uses the full distribution of votes — not just the mean — so it considers both the
|
|
21
|
+
average rating and the confidence we have in that average:
|
|
18
22
|
|
|
19
23
|
```
|
|
20
|
-
|
|
24
|
+
estimate = mean − z × √(variance / (N + K + 1))
|
|
21
25
|
```
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
`WR`: weighted rating
|
|
26
|
-
|
|
27
|
-
`R`: average for the movie (mean) = (Rating)
|
|
27
|
+
Where:
|
|
28
28
|
|
|
29
|
-
`
|
|
29
|
+
- `mean` and `variance` are computed over the smoothed vote distribution (Laplace prior of 1 vote per level);
|
|
30
|
+
- `N` is the total number of votes for the resource;
|
|
31
|
+
- `K` is the number of rating levels (e.g. 5 for a 1–5 star scale);
|
|
32
|
+
- `z` is the normal distribution quantile (1.96 for 95% confidence).
|
|
30
33
|
|
|
31
|
-
|
|
34
|
+
In practice, this means:
|
|
32
35
|
|
|
33
|
-
|
|
36
|
+
- An item with consistent votes (e.g. all 4-stars) ranks **above** an item with the same mean but polarized votes (half 1-stars, half 5-stars), because the latter has higher variance and thus more uncertainty;
|
|
37
|
+
- An item with 3 five-star votes ranks **below** an item with 200 votes averaging 4.7 stars, because the term `√(variance / (N + K + 1))` shrinks as `N` grows.
|
|
34
38
|
|
|
35
|
-
|
|
39
|
+
This is the same family of approach used by sites like Amazon and IMDb to avoid items with very few votes dominating top-rated lists.
|
|
36
40
|
|
|
37
|
-
|
|
41
|
+
### Configuration
|
|
38
42
|
|
|
39
|
-
|
|
43
|
+
You can tune the formula via `Rating::Config`:
|
|
40
44
|
|
|
41
|
-
`
|
|
45
|
+
- `rating_levels` (default `5`): the maximum value of your rating scale. Vote `value` must be an integer between `1` and `rating_levels`. Set this in `config/rating.yml`:
|
|
42
46
|
|
|
43
|
-
|
|
47
|
+
```yaml
|
|
48
|
+
rating:
|
|
49
|
+
rating_levels: 10
|
|
50
|
+
```
|
|
44
51
|
|
|
45
|
-
`
|
|
52
|
+
- `rating_z_score` (default `1.96`): controls confidence level. Use `2.576` for 99% confidence (more conservative, penalizes low-vote items harder), `1.645` for 90% confidence (more permissive).
|
|
46
53
|
|
|
47
54
|
## Install
|
|
48
55
|
|
|
@@ -101,7 +108,7 @@ It will return a `Rating` object that keeps:
|
|
|
101
108
|
|
|
102
109
|
`average`: the normal mean of votes;
|
|
103
110
|
|
|
104
|
-
`estimate`: the
|
|
111
|
+
`estimate`: the lower bound (95% confidence) of the rating, based on the vote distribution. Use this for ranking — it penalizes both polarized distributions and items with few votes;
|
|
105
112
|
|
|
106
113
|
`sum`: the sum of votes for this resource;
|
|
107
114
|
|
|
@@ -296,7 +303,7 @@ article.rates_records
|
|
|
296
303
|
### As
|
|
297
304
|
|
|
298
305
|
If you have a model that will only be able to rate but not to receive a rate, configure it as `author`.
|
|
299
|
-
An author model still can be rated, but won't
|
|
306
|
+
An author model still can be rated, but won't generate a Rating record with all values as zero to warm up the cache.
|
|
300
307
|
|
|
301
308
|
```ruby
|
|
302
309
|
rating as: :author
|
|
@@ -5,15 +5,15 @@ class CreateRateTable < ActiveRecord::Migration[5.0]
|
|
|
5
5
|
create_table :rating_rates do |t|
|
|
6
6
|
t.decimal :value, default: 0, precision: 11, scale: 2
|
|
7
7
|
|
|
8
|
-
t.references :author,
|
|
9
|
-
t.references :resource,
|
|
10
|
-
t.references :scopeable, index: true, null: true,
|
|
8
|
+
t.references :author, index: true, null: false, polymorphic: true
|
|
9
|
+
t.references :resource, index: true, null: false, polymorphic: true
|
|
10
|
+
t.references :scopeable, index: true, null: true, polymorphic: true
|
|
11
11
|
|
|
12
12
|
t.timestamps null: false
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
change_column :rating_rates, :author_type,
|
|
16
|
-
change_column :rating_rates, :resource_type,
|
|
15
|
+
change_column :rating_rates, :author_type, :string, limit: 10
|
|
16
|
+
change_column :rating_rates, :resource_type, :string, limit: 10
|
|
17
17
|
change_column :rating_rates, :scopeable_type, :string, limit: 10
|
|
18
18
|
|
|
19
19
|
add_index :rating_rates, %i[author_type author_id resource_type resource_id scopeable_type scopeable_id],
|
|
@@ -3,18 +3,18 @@
|
|
|
3
3
|
class CreateRatingTable < ActiveRecord::Migration[5.0]
|
|
4
4
|
def change
|
|
5
5
|
create_table :rating_ratings do |t|
|
|
6
|
-
t.decimal :average,
|
|
7
|
-
t.decimal :estimate, default: 0,
|
|
8
|
-
t.integer :sum,
|
|
9
|
-
t.integer :total,
|
|
6
|
+
t.decimal :average, default: 0, null: false, precision: 12, scale: 8
|
|
7
|
+
t.decimal :estimate, default: 0, null: false, precision: 12, scale: 8
|
|
8
|
+
t.integer :sum, default: 0, null: false
|
|
9
|
+
t.integer :total, default: 0, null: false
|
|
10
10
|
|
|
11
|
-
t.references :resource,
|
|
12
|
-
t.references :scopeable, index: true, null: true,
|
|
11
|
+
t.references :resource, index: true, null: false, polymorphic: true
|
|
12
|
+
t.references :scopeable, index: true, null: true, polymorphic: true
|
|
13
13
|
|
|
14
14
|
t.timestamps null: false
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
change_column :rating_ratings, :resource_type,
|
|
17
|
+
change_column :rating_ratings, :resource_type, :string, limit: 10
|
|
18
18
|
change_column :rating_ratings, :scopeable_type, :string, limit: 10
|
|
19
19
|
|
|
20
20
|
add_index :rating_ratings, %i[resource_type resource_id scopeable_type scopeable_id],
|
data/lib/rating/config.rb
CHANGED
|
@@ -8,9 +8,7 @@ module Rating
|
|
|
8
8
|
@config ||= begin
|
|
9
9
|
file_path = File.expand_path('config/rating.yml')
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
YAML.safe_load(File.read(file_path))['rating']
|
|
11
|
+
File.exist?(file_path) ? YAML.safe_load_file(file_path)['rating'] : {}
|
|
14
12
|
end
|
|
15
13
|
end
|
|
16
14
|
|
|
@@ -22,6 +20,14 @@ module Rating
|
|
|
22
20
|
@rating_table ||= config[__method__.to_s] || 'rating_ratings'
|
|
23
21
|
end
|
|
24
22
|
|
|
23
|
+
def rating_levels
|
|
24
|
+
@rating_levels ||= config[__method__.to_s] || 5
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def rating_z_score
|
|
28
|
+
@rating_z_score ||= config[__method__.to_s] || 1.96
|
|
29
|
+
end
|
|
30
|
+
|
|
25
31
|
def validations
|
|
26
32
|
@validations ||= begin
|
|
27
33
|
default_scope = %w[author_type resource_id resource_type scopeable_id scopeable_type]
|
|
@@ -7,21 +7,21 @@ module Rating
|
|
|
7
7
|
included do
|
|
8
8
|
def rate(resource, value, author: self, extra_scopes: {}, metadata: {}, scope: nil)
|
|
9
9
|
Rate.create(
|
|
10
|
-
author
|
|
11
|
-
extra_scopes
|
|
12
|
-
metadata
|
|
13
|
-
resource
|
|
10
|
+
author:,
|
|
11
|
+
extra_scopes:,
|
|
12
|
+
metadata:,
|
|
13
|
+
resource:,
|
|
14
14
|
scopeable: scope,
|
|
15
|
-
value:
|
|
15
|
+
value:
|
|
16
16
|
)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def rate_for(resource, extra_scopes: {}, scope: nil)
|
|
20
|
-
Rate.rate_for author: self, extra_scopes
|
|
20
|
+
Rate.rate_for author: self, extra_scopes:, resource:, scopeable: scope
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def rated?(resource, extra_scopes: {}, scope: nil)
|
|
24
|
-
Rate.exists?(extra_scopes.merge(author: self, resource
|
|
24
|
+
Rate.exists?(extra_scopes.merge(author: self, resource:, scopeable: scope))
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def rates(extra_scopes: {}, scope: nil)
|
|
@@ -76,9 +76,10 @@ module Rating
|
|
|
76
76
|
column = opts.fetch(:column, :estimate)
|
|
77
77
|
direction = opts.fetch(:direction, :desc)
|
|
78
78
|
scope = opts[:scope]
|
|
79
|
+
base_class = scope&.class&.base_class
|
|
79
80
|
|
|
80
81
|
includes(:rating_records)
|
|
81
|
-
.where(Rating.table_name => { scopeable_id: scope&.id, scopeable_type:
|
|
82
|
+
.where(Rating.table_name => { scopeable_id: scope&.id, scopeable_type: base_class&.name })
|
|
82
83
|
.order("#{Rating.table_name}.#{column} #{direction}")
|
|
83
84
|
}
|
|
84
85
|
|
|
@@ -3,16 +3,21 @@
|
|
|
3
3
|
module Rating
|
|
4
4
|
class Rate < ActiveRecord::Base
|
|
5
5
|
self.table_name_prefix = 'rating_'
|
|
6
|
-
self.table_name
|
|
6
|
+
self.table_name = ::Rating::Config.rate_table
|
|
7
7
|
|
|
8
8
|
after_save :update_rating
|
|
9
9
|
|
|
10
|
-
belongs_to :author,
|
|
11
|
-
belongs_to :resource,
|
|
10
|
+
belongs_to :author, polymorphic: true
|
|
11
|
+
belongs_to :resource, polymorphic: true
|
|
12
12
|
belongs_to :scopeable, polymorphic: true
|
|
13
13
|
|
|
14
|
-
validates :
|
|
15
|
-
|
|
14
|
+
validates :value, presence: true
|
|
15
|
+
|
|
16
|
+
validates :value, numericality: {
|
|
17
|
+
only_integer: true,
|
|
18
|
+
greater_than_or_equal_to: 1,
|
|
19
|
+
less_than_or_equal_to: ::Rating::Config.rating_levels,
|
|
20
|
+
}
|
|
16
21
|
|
|
17
22
|
validates :author_id, uniqueness: {
|
|
18
23
|
case_sensitive: ::Rating::Config.validations['rate']['case_sensitive'],
|
|
@@ -20,8 +25,8 @@ module Rating
|
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
def self.create(author:, extra_scopes:, metadata:, resource:, value:, scopeable: nil)
|
|
23
|
-
attributes = { author
|
|
24
|
-
record
|
|
28
|
+
attributes = { author:, resource:, scopeable: }.merge(extra_scopes)
|
|
29
|
+
record = find_or_initialize_by(attributes)
|
|
25
30
|
|
|
26
31
|
metadata.each { |k, v| record[k] = v } if metadata.present?
|
|
27
32
|
|
|
@@ -32,7 +37,7 @@ module Rating
|
|
|
32
37
|
end
|
|
33
38
|
|
|
34
39
|
def self.rate_for(author:, resource:, extra_scopes: {}, scopeable: nil)
|
|
35
|
-
find_by extra_scopes.merge(author
|
|
40
|
+
find_by extra_scopes.merge(author:, resource:, scopeable:)
|
|
36
41
|
end
|
|
37
42
|
|
|
38
43
|
private
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
module Rating
|
|
4
4
|
class Rating < ActiveRecord::Base
|
|
5
5
|
self.table_name_prefix = 'rating_'
|
|
6
|
-
self.table_name
|
|
6
|
+
self.table_name = ::Rating::Config.rating_table
|
|
7
7
|
|
|
8
|
-
belongs_to :resource,
|
|
8
|
+
belongs_to :resource, polymorphic: true
|
|
9
9
|
belongs_to :scopeable, polymorphic: true
|
|
10
10
|
|
|
11
|
-
validates :average, :estimate, :
|
|
11
|
+
validates :average, :estimate, :sum, :total, presence: true
|
|
12
12
|
validates :average, :estimate, :sum, :total, numericality: true
|
|
13
13
|
|
|
14
14
|
validates :resource_id, uniqueness: {
|
|
@@ -17,34 +17,35 @@ module Rating
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
class << self
|
|
20
|
-
def
|
|
21
|
-
total_count = how_many_resource_received_votes_sql(distinct: false, resource: resource, scopeable: scopeable)
|
|
22
|
-
distinct_count = how_many_resource_received_votes_sql(distinct: true, resource: resource, scopeable: scopeable)
|
|
23
|
-
values = { resource_type: resource.class.base_class.name }
|
|
24
|
-
|
|
25
|
-
values[:scopeable_type] = scopeable.class.base_class.name unless scopeable.nil?
|
|
26
|
-
|
|
20
|
+
def histogram_data(resource, scopeable)
|
|
27
21
|
sql = %(
|
|
28
22
|
SELECT
|
|
29
|
-
|
|
30
|
-
|
|
23
|
+
value,
|
|
24
|
+
COUNT(1) AS rating_count
|
|
31
25
|
FROM #{rate_table_name}
|
|
32
26
|
WHERE
|
|
33
|
-
resource_type =
|
|
34
|
-
|
|
27
|
+
resource_type = ?
|
|
28
|
+
AND resource_id = ?
|
|
29
|
+
#{scope_type_and_id_query(resource, scopeable)}
|
|
35
30
|
#{scope_where_query(resource)}
|
|
31
|
+
GROUP BY value
|
|
36
32
|
).squish
|
|
37
33
|
|
|
38
|
-
|
|
34
|
+
values = [sql, resource.class.base_class.name, resource.id]
|
|
35
|
+
values += [scopeable.class.base_class.name, scopeable.id] unless scopeable.nil? || unscoped_rating?(resource)
|
|
36
|
+
|
|
37
|
+
Rate.find_by_sql(values).to_h do |row|
|
|
38
|
+
[row.value.to_i, row.rating_count.to_i]
|
|
39
|
+
end
|
|
39
40
|
end
|
|
40
41
|
|
|
41
42
|
def data(resource, scopeable)
|
|
42
|
-
|
|
43
|
-
values
|
|
43
|
+
histogram = histogram_data(resource, scopeable)
|
|
44
|
+
values = values_data(resource, scopeable)
|
|
44
45
|
|
|
45
46
|
{
|
|
46
|
-
average: values.rating_avg
|
|
47
|
-
estimate:
|
|
47
|
+
average: values.rating_avg,
|
|
48
|
+
estimate: miller_lower_bound(histogram),
|
|
48
49
|
sum: values.rating_sum,
|
|
49
50
|
total: values.rating_count,
|
|
50
51
|
}
|
|
@@ -64,22 +65,22 @@ module Rating
|
|
|
64
65
|
#{scope_where_query(resource)}
|
|
65
66
|
).squish
|
|
66
67
|
|
|
67
|
-
values =
|
|
68
|
+
values = [sql, resource.class.base_class.name, resource.id]
|
|
68
69
|
values += [scopeable.class.base_class.name, scopeable.id] unless scopeable.nil? || unscoped_rating?(resource)
|
|
69
70
|
|
|
70
71
|
execute_sql values
|
|
71
72
|
end
|
|
72
73
|
|
|
73
74
|
def update_rating(resource, scopeable)
|
|
74
|
-
attributes
|
|
75
|
+
attributes = { resource: }
|
|
75
76
|
attributes[:scopeable] = unscoped_rating?(resource) ? nil : scopeable
|
|
76
77
|
|
|
77
78
|
record = find_or_initialize_by(attributes)
|
|
78
79
|
result = data(resource, scopeable)
|
|
79
80
|
|
|
80
|
-
record.average
|
|
81
|
-
record.sum
|
|
82
|
-
record.total
|
|
81
|
+
record.average = result[:average]
|
|
82
|
+
record.sum = result[:sum]
|
|
83
|
+
record.total = result[:total]
|
|
83
84
|
record.estimate = result[:estimate]
|
|
84
85
|
|
|
85
86
|
record.save
|
|
@@ -87,14 +88,24 @@ module Rating
|
|
|
87
88
|
|
|
88
89
|
private
|
|
89
90
|
|
|
90
|
-
def
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
def miller_lower_bound(histogram)
|
|
92
|
+
rating_levels = ::Rating::Config.rating_levels
|
|
93
|
+
z_score = ::Rating::Config.rating_z_score
|
|
94
|
+
total_votes = (1..rating_levels).sum { |level| histogram.fetch(level, 0) }
|
|
95
|
+
|
|
96
|
+
return BigDecimal('0') if total_votes.zero?
|
|
97
|
+
|
|
98
|
+
denominator = total_votes + rating_levels
|
|
99
|
+
|
|
100
|
+
smoothed_mean = (1..rating_levels).sum do |level|
|
|
101
|
+
level * (histogram.fetch(level, 0) + 1).to_d / denominator
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
smoothed_variance = (1..rating_levels).sum do |level|
|
|
105
|
+
(level**2) * (histogram.fetch(level, 0) + 1).to_d / denominator
|
|
106
|
+
end - (smoothed_mean**2)
|
|
95
107
|
|
|
96
|
-
((
|
|
97
|
-
((count_avg / (resource_rating_count + count_avg)) * resource_type_rating_avg)
|
|
108
|
+
smoothed_mean - (z_score.to_d * (smoothed_variance / (denominator + 1)).sqrt(16))
|
|
98
109
|
end
|
|
99
110
|
|
|
100
111
|
def execute_sql(sql)
|
|
@@ -105,29 +116,10 @@ module Rating
|
|
|
105
116
|
resource.rating_options[:unscoped_rating]
|
|
106
117
|
end
|
|
107
118
|
|
|
108
|
-
def how_many_resource_received_votes_sql(distinct:, resource:, scopeable:)
|
|
109
|
-
count = distinct ? 'COUNT(DISTINCT resource_id)' : 'COUNT(1)'
|
|
110
|
-
|
|
111
|
-
%((
|
|
112
|
-
SELECT GREATEST(#{count}, 1)
|
|
113
|
-
FROM #{rate_table_name}
|
|
114
|
-
WHERE
|
|
115
|
-
resource_type = :resource_type
|
|
116
|
-
#{scope_type_query(resource, scopeable)}
|
|
117
|
-
#{scope_where_query(resource)}
|
|
118
|
-
))
|
|
119
|
-
end
|
|
120
|
-
|
|
121
119
|
def rate_table_name
|
|
122
120
|
@rate_table_name ||= Rate.table_name
|
|
123
121
|
end
|
|
124
122
|
|
|
125
|
-
def scope_type_query(resource, scopeable)
|
|
126
|
-
return '' if unscoped_rating?(resource)
|
|
127
|
-
|
|
128
|
-
scopeable.nil? ? 'AND scopeable_type is NULL' : 'AND scopeable_type = :scopeable_type'
|
|
129
|
-
end
|
|
130
|
-
|
|
131
123
|
def scope_type_and_id_query(resource, scopeable)
|
|
132
124
|
return '' if unscoped_rating?(resource)
|
|
133
125
|
return 'AND scopeable_type is NULL AND scopeable_id is NULL' if scopeable.nil?
|
data/lib/rating/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rating
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Washington Botelho
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: activerecord
|
|
@@ -24,161 +23,7 @@ dependencies:
|
|
|
24
23
|
- - ">="
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
25
|
version: '0'
|
|
27
|
-
-
|
|
28
|
-
name: codecov
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - ">="
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: '0'
|
|
34
|
-
type: :development
|
|
35
|
-
prerelease: false
|
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
-
requirements:
|
|
38
|
-
- - ">="
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: '0'
|
|
41
|
-
- !ruby/object:Gem::Dependency
|
|
42
|
-
name: database_cleaner
|
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
|
44
|
-
requirements:
|
|
45
|
-
- - ">="
|
|
46
|
-
- !ruby/object:Gem::Version
|
|
47
|
-
version: '0'
|
|
48
|
-
type: :development
|
|
49
|
-
prerelease: false
|
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
-
requirements:
|
|
52
|
-
- - ">="
|
|
53
|
-
- !ruby/object:Gem::Version
|
|
54
|
-
version: '0'
|
|
55
|
-
- !ruby/object:Gem::Dependency
|
|
56
|
-
name: factory_bot_rails
|
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
|
58
|
-
requirements:
|
|
59
|
-
- - ">="
|
|
60
|
-
- !ruby/object:Gem::Version
|
|
61
|
-
version: '0'
|
|
62
|
-
type: :development
|
|
63
|
-
prerelease: false
|
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
-
requirements:
|
|
66
|
-
- - ">="
|
|
67
|
-
- !ruby/object:Gem::Version
|
|
68
|
-
version: '0'
|
|
69
|
-
- !ruby/object:Gem::Dependency
|
|
70
|
-
name: mysql2
|
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
|
72
|
-
requirements:
|
|
73
|
-
- - ">="
|
|
74
|
-
- !ruby/object:Gem::Version
|
|
75
|
-
version: '0'
|
|
76
|
-
type: :development
|
|
77
|
-
prerelease: false
|
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
-
requirements:
|
|
80
|
-
- - ">="
|
|
81
|
-
- !ruby/object:Gem::Version
|
|
82
|
-
version: '0'
|
|
83
|
-
- !ruby/object:Gem::Dependency
|
|
84
|
-
name: pg
|
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
|
86
|
-
requirements:
|
|
87
|
-
- - ">="
|
|
88
|
-
- !ruby/object:Gem::Version
|
|
89
|
-
version: '0'
|
|
90
|
-
type: :development
|
|
91
|
-
prerelease: false
|
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
-
requirements:
|
|
94
|
-
- - ">="
|
|
95
|
-
- !ruby/object:Gem::Version
|
|
96
|
-
version: '0'
|
|
97
|
-
- !ruby/object:Gem::Dependency
|
|
98
|
-
name: pry-byebug
|
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
|
100
|
-
requirements:
|
|
101
|
-
- - ">="
|
|
102
|
-
- !ruby/object:Gem::Version
|
|
103
|
-
version: '0'
|
|
104
|
-
type: :development
|
|
105
|
-
prerelease: false
|
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
-
requirements:
|
|
108
|
-
- - ">="
|
|
109
|
-
- !ruby/object:Gem::Version
|
|
110
|
-
version: '0'
|
|
111
|
-
- !ruby/object:Gem::Dependency
|
|
112
|
-
name: rspec-rails
|
|
113
|
-
requirement: !ruby/object:Gem::Requirement
|
|
114
|
-
requirements:
|
|
115
|
-
- - ">="
|
|
116
|
-
- !ruby/object:Gem::Version
|
|
117
|
-
version: '0'
|
|
118
|
-
type: :development
|
|
119
|
-
prerelease: false
|
|
120
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
-
requirements:
|
|
122
|
-
- - ">="
|
|
123
|
-
- !ruby/object:Gem::Version
|
|
124
|
-
version: '0'
|
|
125
|
-
- !ruby/object:Gem::Dependency
|
|
126
|
-
name: rubocop-performance
|
|
127
|
-
requirement: !ruby/object:Gem::Requirement
|
|
128
|
-
requirements:
|
|
129
|
-
- - ">="
|
|
130
|
-
- !ruby/object:Gem::Version
|
|
131
|
-
version: '0'
|
|
132
|
-
type: :development
|
|
133
|
-
prerelease: false
|
|
134
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
-
requirements:
|
|
136
|
-
- - ">="
|
|
137
|
-
- !ruby/object:Gem::Version
|
|
138
|
-
version: '0'
|
|
139
|
-
- !ruby/object:Gem::Dependency
|
|
140
|
-
name: rubocop-rails
|
|
141
|
-
requirement: !ruby/object:Gem::Requirement
|
|
142
|
-
requirements:
|
|
143
|
-
- - ">="
|
|
144
|
-
- !ruby/object:Gem::Version
|
|
145
|
-
version: '0'
|
|
146
|
-
type: :development
|
|
147
|
-
prerelease: false
|
|
148
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
149
|
-
requirements:
|
|
150
|
-
- - ">="
|
|
151
|
-
- !ruby/object:Gem::Version
|
|
152
|
-
version: '0'
|
|
153
|
-
- !ruby/object:Gem::Dependency
|
|
154
|
-
name: rubocop-rspec
|
|
155
|
-
requirement: !ruby/object:Gem::Requirement
|
|
156
|
-
requirements:
|
|
157
|
-
- - ">="
|
|
158
|
-
- !ruby/object:Gem::Version
|
|
159
|
-
version: '0'
|
|
160
|
-
type: :development
|
|
161
|
-
prerelease: false
|
|
162
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
163
|
-
requirements:
|
|
164
|
-
- - ">="
|
|
165
|
-
- !ruby/object:Gem::Version
|
|
166
|
-
version: '0'
|
|
167
|
-
- !ruby/object:Gem::Dependency
|
|
168
|
-
name: shoulda-matchers
|
|
169
|
-
requirement: !ruby/object:Gem::Requirement
|
|
170
|
-
requirements:
|
|
171
|
-
- - ">="
|
|
172
|
-
- !ruby/object:Gem::Version
|
|
173
|
-
version: '0'
|
|
174
|
-
type: :development
|
|
175
|
-
prerelease: false
|
|
176
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
177
|
-
requirements:
|
|
178
|
-
- - ">="
|
|
179
|
-
- !ruby/object:Gem::Version
|
|
180
|
-
version: '0'
|
|
181
|
-
description: A true Bayesian rating system with scope and cache enabled.
|
|
26
|
+
description: A confidence-based rating system with scope and cache enabled.
|
|
182
27
|
email: wbotelhos@gmail.com
|
|
183
28
|
executables: []
|
|
184
29
|
extensions: []
|
|
@@ -204,7 +49,6 @@ licenses:
|
|
|
204
49
|
- MIT
|
|
205
50
|
metadata:
|
|
206
51
|
rubygems_mfa_required: 'true'
|
|
207
|
-
post_install_message:
|
|
208
52
|
rdoc_options: []
|
|
209
53
|
require_paths:
|
|
210
54
|
- lib
|
|
@@ -212,15 +56,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
212
56
|
requirements:
|
|
213
57
|
- - ">="
|
|
214
58
|
- !ruby/object:Gem::Version
|
|
215
|
-
version: '
|
|
59
|
+
version: '3.3'
|
|
216
60
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
217
61
|
requirements:
|
|
218
62
|
- - ">="
|
|
219
63
|
- !ruby/object:Gem::Version
|
|
220
64
|
version: '0'
|
|
221
65
|
requirements: []
|
|
222
|
-
rubygems_version: 3.
|
|
223
|
-
signing_key:
|
|
66
|
+
rubygems_version: 3.6.9
|
|
224
67
|
specification_version: 4
|
|
225
68
|
summary: A true Bayesian rating system with scope and cache enabled.
|
|
226
69
|
test_files: []
|