rating 0.6.0 → 0.7.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 +11 -0
- data/README.md +42 -10
- data/lib/rating/config.rb +13 -0
- data/lib/rating/models/rating/extension.rb +23 -16
- data/lib/rating/models/rating/rate.rb +12 -4
- data/lib/rating/version.rb +1 -1
- data/spec/config/validations_spec.rb +27 -0
- data/spec/models/extension/rate_for_spec.rb +17 -2
- data/spec/models/extension/rate_spec.rb +14 -6
- data/spec/models/extension/rated_question_spec.rb +23 -15
- data/spec/models/extension/rated_spec.rb +8 -0
- data/spec/models/extension/rates_spec.rb +8 -0
- data/spec/models/rate/create_spec.rb +197 -7
- data/spec/models/rate/rate_for_spec.rb +73 -8
- data/spec/models/rate_spec.rb +7 -0
- data/spec/support/db/migrate/add_extra_fields_on_rating_rates_table.rb +10 -0
- data/spec/support/migrate.rb +1 -0
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c384b931b0e023652ca3ae1025de72e2371f5c21
|
|
4
|
+
data.tar.gz: 01cefd2f71309f2a9a1dc09f01f058cc00322619
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 98af2a274b9fc2cca342546016ba431d58e718463ac773ec02f99bdb7830634e08a8fd8cadffba68ac3e047eba14407c29787dd913192b730b3d98a4b2bffd50
|
|
7
|
+
data.tar.gz: 4f8a8ae57dc1e14719739b3defbf850abb606c4d37d36d71922b11d91776ccd65866c9f2f17819ba5b9d046f91551ef70e216b9e28fd8b1fdd0cc437af36e12d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
## v0.7.0
|
|
2
|
+
|
|
3
|
+
### News
|
|
4
|
+
|
|
5
|
+
- Support to configure `uniqueness` validation via YAML into Rating::Rate model;
|
|
6
|
+
- Support to multiple scopes via `extra_scopes` option.
|
|
7
|
+
|
|
8
|
+
### Updates
|
|
9
|
+
|
|
10
|
+
- Reverts v0.6.0, since we need this validation because we cannot edit the Rate model by ourself.
|
|
11
|
+
|
|
1
12
|
## v0.6.0
|
|
2
13
|
|
|
3
14
|
### Updates
|
data/README.md
CHANGED
|
@@ -248,6 +248,28 @@ To order the rating you do the same thing:
|
|
|
248
248
|
Article.order_by_rating scope: category_1
|
|
249
249
|
```
|
|
250
250
|
|
|
251
|
+
### Extra Scopes
|
|
252
|
+
|
|
253
|
+
Maybe you need to use more than one scope to make a rate, so you can use the `extra_scopes` options.
|
|
254
|
+
This feature is enable **only** to restrict the rate, the rating calculation will **ignore** it.
|
|
255
|
+
|
|
256
|
+
Example situation: I have a Profile (resource) that belongs to some Category (scope) and the Client (author) will rate
|
|
257
|
+
this Profile based on each Lead (extra scope) this Profile made. The Client can vote just one time on each lead, but many
|
|
258
|
+
times to that Profile. The Profile has a rating score based on all leads made on that Category.
|
|
259
|
+
|
|
260
|
+
```ruby
|
|
261
|
+
scope = Category.first
|
|
262
|
+
author = Client.last
|
|
263
|
+
resource = Profile.last
|
|
264
|
+
lead = Lead.last
|
|
265
|
+
|
|
266
|
+
author.rate resource, 5, extra_scopes: { lead_id: lead.id }, scope: scope
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
* The extra scopes fields is not present into gem, so you cannot use `{ lead: lead }`, for example.
|
|
270
|
+
|
|
271
|
+
All methods listed on [Scope](#scope) session allows `extra_scopes` as additional condition too.
|
|
272
|
+
|
|
251
273
|
### Records
|
|
252
274
|
|
|
253
275
|
Maybe you want to recover all records with or without scope, so you can add the suffix `_records` on relations:
|
|
@@ -340,24 +362,34 @@ You should just to provide a `config/rating.yml` file with the following content
|
|
|
340
362
|
|
|
341
363
|
```yml
|
|
342
364
|
rating:
|
|
343
|
-
rate_table:
|
|
344
|
-
rating_table:
|
|
365
|
+
rate_table: reviews
|
|
366
|
+
rating_table: review_ratings
|
|
345
367
|
```
|
|
346
368
|
|
|
347
369
|
Now the rates will be written on `reviews` table over `rating_rates` and calculation will be on `review_ratings` over `rating_ratings`.
|
|
348
370
|
You can change one table o both of them.
|
|
349
371
|
|
|
372
|
+
### Validations
|
|
350
373
|
|
|
351
|
-
|
|
374
|
+
#### Rate Uniqueness
|
|
352
375
|
|
|
353
|
-
|
|
354
|
-
This validation is not inside the gem since you could want make your own custom validation.
|
|
376
|
+
Since you can to use [Extra Scopes](#extra_scopes) to restrict rates and the original model `Rating::Rate` is inside gem, you can configure the uniqueness validation, from outside, to include this extra scopes.
|
|
355
377
|
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
378
|
+
```yml
|
|
379
|
+
rating:
|
|
380
|
+
validations:
|
|
381
|
+
rate:
|
|
382
|
+
uniqueness:
|
|
383
|
+
case_sensitive: false
|
|
384
|
+
|
|
385
|
+
scope:
|
|
386
|
+
- author_type
|
|
387
|
+
- resource_id
|
|
388
|
+
- resource_type
|
|
389
|
+
- scopeable_id
|
|
390
|
+
- scopeable_type
|
|
391
|
+
- scope_1
|
|
392
|
+
- scope_2
|
|
361
393
|
```
|
|
362
394
|
|
|
363
395
|
## Love it!
|
data/lib/rating/config.rb
CHANGED
|
@@ -21,5 +21,18 @@ module Rating
|
|
|
21
21
|
def rating_table
|
|
22
22
|
@rating_table ||= config[__method__.to_s] || 'rating_ratings'
|
|
23
23
|
end
|
|
24
|
+
|
|
25
|
+
def validations
|
|
26
|
+
@validations ||= begin
|
|
27
|
+
default_scope = %w[author_type resource_id resource_type scopeable_id scopeable_type]
|
|
28
|
+
|
|
29
|
+
{
|
|
30
|
+
rate: {
|
|
31
|
+
case_sensitive: config.dig('validations', 'rate', 'case_sensitive') || false,
|
|
32
|
+
scope: config.dig('validations', 'rate', 'scope') || default_scope
|
|
33
|
+
}
|
|
34
|
+
}.deep_stringify_keys
|
|
35
|
+
end
|
|
36
|
+
end
|
|
24
37
|
end
|
|
25
38
|
end
|
|
@@ -5,24 +5,36 @@ module Rating
|
|
|
5
5
|
extend ActiveSupport::Concern
|
|
6
6
|
|
|
7
7
|
included do
|
|
8
|
-
def rate(resource, value, author: self, metadata: {}, scope: nil)
|
|
9
|
-
Rate.create
|
|
8
|
+
def rate(resource, value, author: self, extra_scopes: {}, metadata: {}, scope: nil)
|
|
9
|
+
Rate.create(
|
|
10
|
+
author: author,
|
|
11
|
+
extra_scopes: extra_scopes,
|
|
12
|
+
metadata: metadata,
|
|
13
|
+
resource: resource,
|
|
14
|
+
scopeable: scope,
|
|
15
|
+
value: value
|
|
16
|
+
)
|
|
10
17
|
end
|
|
11
18
|
|
|
12
|
-
def rate_for(resource, scope: nil)
|
|
13
|
-
Rate.rate_for author: self, resource: resource, scopeable: scope
|
|
19
|
+
def rate_for(resource, extra_scopes: {}, scope: nil)
|
|
20
|
+
Rate.rate_for author: self, extra_scopes: extra_scopes, resource: resource, scopeable: scope
|
|
14
21
|
end
|
|
15
22
|
|
|
16
|
-
|
|
17
|
-
|
|
23
|
+
# TODO: use exists for performance
|
|
24
|
+
def rated?(resource, extra_scopes: {}, scope: nil)
|
|
25
|
+
!rate_for(resource, extra_scopes: extra_scopes, scope: scope).nil?
|
|
18
26
|
end
|
|
19
27
|
|
|
20
|
-
def rates(scope: nil)
|
|
21
|
-
|
|
28
|
+
def rates(extra_scopes: {}, scope: nil)
|
|
29
|
+
attributes = { scopeable: scope }.merge(extra_scopes)
|
|
30
|
+
|
|
31
|
+
rates_records.where attributes
|
|
22
32
|
end
|
|
23
33
|
|
|
24
|
-
def rated(scope: nil)
|
|
25
|
-
|
|
34
|
+
def rated(extra_scopes: {}, scope: nil)
|
|
35
|
+
attributes = { scopeable: scope }.merge(extra_scopes)
|
|
36
|
+
|
|
37
|
+
rated_records.where attributes
|
|
26
38
|
end
|
|
27
39
|
|
|
28
40
|
def rating(scope: nil)
|
|
@@ -62,13 +74,8 @@ module Rating
|
|
|
62
74
|
dependent: :destroy
|
|
63
75
|
|
|
64
76
|
scope :order_by_rating, ->(column = :estimate, direction = :desc, scope: nil) {
|
|
65
|
-
scope_values = {
|
|
66
|
-
scopeable_id: scope&.id,
|
|
67
|
-
scopeable_type: scope&.class&.base_class&.name
|
|
68
|
-
}
|
|
69
|
-
|
|
70
77
|
includes(:rating_records)
|
|
71
|
-
.where(Rating.table_name =>
|
|
78
|
+
.where(Rating.table_name => { scopeable_id: scope&.id, scopeable_type: scope&.class&.base_class&.name })
|
|
72
79
|
.order("#{Rating.table_name}.#{column} #{direction}")
|
|
73
80
|
}
|
|
74
81
|
end
|
|
@@ -14,8 +14,14 @@ module Rating
|
|
|
14
14
|
validates :author, :resource, :value, presence: true
|
|
15
15
|
validates :value, numericality: { greater_than_or_equal_to: 1, less_than_or_equal_to: 100 }
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
validates :author_id, uniqueness: {
|
|
18
|
+
case_sensitive: ::Rating::Config.validations['rate']['case_sensitive'],
|
|
19
|
+
scope: ::Rating::Config.validations['rate']['scope'].map(&:to_sym)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
def self.create(author:, extra_scopes:, metadata:, resource:, scopeable: nil, value:)
|
|
23
|
+
attributes = { author: author, resource: resource, scopeable: scopeable }.merge(extra_scopes)
|
|
24
|
+
record = find_or_initialize_by(attributes)
|
|
19
25
|
|
|
20
26
|
return record if record.persisted? && value == record.value
|
|
21
27
|
|
|
@@ -27,8 +33,10 @@ module Rating
|
|
|
27
33
|
record
|
|
28
34
|
end
|
|
29
35
|
|
|
30
|
-
def self.rate_for(author:, resource:, scopeable: nil)
|
|
31
|
-
|
|
36
|
+
def self.rate_for(author:, extra_scopes: {}, resource:, scopeable: nil)
|
|
37
|
+
attributes = { author: author, resource: resource, scopeable: scopeable }.merge(extra_scopes)
|
|
38
|
+
|
|
39
|
+
find_by attributes
|
|
32
40
|
end
|
|
33
41
|
|
|
34
42
|
private
|
data/lib/rating/version.rb
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Rating::Config, '.validations' do
|
|
6
|
+
context 'when rating.yml does not exist' do
|
|
7
|
+
it do
|
|
8
|
+
expect(subject.validations).to eq({
|
|
9
|
+
rate: {
|
|
10
|
+
case_sensitive: false,
|
|
11
|
+
scope: %w[author_type resource_id resource_type scopeable_id scopeable_type]
|
|
12
|
+
}
|
|
13
|
+
}.deep_stringify_keys)
|
|
14
|
+
end
|
|
15
|
+
end if ENV['CONFIG_ENABLED_WITH_EXTRA_SCOPES'] != 'true'
|
|
16
|
+
|
|
17
|
+
context 'when rating.yml exists' do
|
|
18
|
+
it do
|
|
19
|
+
expect(subject.validations).to eq({
|
|
20
|
+
rate: {
|
|
21
|
+
case_sensitive: false,
|
|
22
|
+
scope: %w[author_type resource_id resource_type scopeable_id scopeable_type scope_1 scope_2]
|
|
23
|
+
}
|
|
24
|
+
}.deep_stringify_keys)
|
|
25
|
+
end
|
|
26
|
+
end if ENV['CONFIG_ENABLED_WITH_EXTRA_SCOPES'] == 'true'
|
|
27
|
+
end
|
|
@@ -8,7 +8,7 @@ RSpec.describe Rating::Extension, ':rate_for' do
|
|
|
8
8
|
|
|
9
9
|
context 'with no scopeable' do
|
|
10
10
|
it 'delegates to rate object' do
|
|
11
|
-
expect(Rating::Rate).to receive(:rate_for).with author: author, resource: article, scopeable: nil
|
|
11
|
+
expect(Rating::Rate).to receive(:rate_for).with author: author, extra_scopes: {}, resource: article, scopeable: nil
|
|
12
12
|
|
|
13
13
|
author.rate_for article
|
|
14
14
|
end
|
|
@@ -18,9 +18,24 @@ RSpec.describe Rating::Extension, ':rate_for' do
|
|
|
18
18
|
let!(:category) { build :category }
|
|
19
19
|
|
|
20
20
|
it 'delegates to rate object' do
|
|
21
|
-
expect(Rating::Rate).to receive(:rate_for).with author: author, resource: article, scopeable: category
|
|
21
|
+
expect(Rating::Rate).to receive(:rate_for).with author: author, extra_scopes: {}, resource: article, scopeable: category
|
|
22
22
|
|
|
23
23
|
author.rate_for article, scope: category
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
|
+
|
|
27
|
+
context 'with extra_scopes' do
|
|
28
|
+
let!(:category) { build :category }
|
|
29
|
+
|
|
30
|
+
it 'delegates to rate object' do
|
|
31
|
+
expect(Rating::Rate).to receive(:rate_for).with(
|
|
32
|
+
author: author,
|
|
33
|
+
extra_scopes: { scope_1: 'scope_1' },
|
|
34
|
+
resource: article,
|
|
35
|
+
scopeable: category
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
author.rate_for article, extra_scopes: { scope_1: 'scope_1' }, scope: category
|
|
39
|
+
end
|
|
40
|
+
end
|
|
26
41
|
end
|
|
@@ -9,7 +9,7 @@ RSpec.describe Rating::Extension, ':rate' do
|
|
|
9
9
|
context 'with no scopeable' do
|
|
10
10
|
it 'delegates to rate object' do
|
|
11
11
|
expect(Rating::Rate).to receive(:create).with(
|
|
12
|
-
author: author, metadata: {}, resource: article, scopeable: nil, value: 3
|
|
12
|
+
author: author, extra_scopes: {}, metadata: {}, resource: article, scopeable: nil, value: 3
|
|
13
13
|
)
|
|
14
14
|
|
|
15
15
|
author.rate article, 3
|
|
@@ -21,7 +21,7 @@ RSpec.describe Rating::Extension, ':rate' do
|
|
|
21
21
|
|
|
22
22
|
it 'delegates to rate object' do
|
|
23
23
|
expect(Rating::Rate).to receive(:create).with(
|
|
24
|
-
author: author, metadata: {}, resource: article, scopeable: category, value: 3
|
|
24
|
+
author: author, extra_scopes: {}, metadata: {}, resource: article, scopeable: category, value: 3
|
|
25
25
|
)
|
|
26
26
|
|
|
27
27
|
author.rate article, 3, scope: category
|
|
@@ -31,7 +31,7 @@ RSpec.describe Rating::Extension, ':rate' do
|
|
|
31
31
|
context 'with no metadata' do
|
|
32
32
|
it 'delegates an empty hash to rate object' do
|
|
33
33
|
expect(Rating::Rate).to receive(:create).with(
|
|
34
|
-
author: author, resource: article, metadata: {}, scopeable: nil, value: 3
|
|
34
|
+
author: author, extra_scopes: {}, resource: article, metadata: {}, scopeable: nil, value: 3
|
|
35
35
|
)
|
|
36
36
|
|
|
37
37
|
author.rate article, 3
|
|
@@ -39,14 +39,22 @@ RSpec.describe Rating::Extension, ':rate' do
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
context 'with metadata' do
|
|
42
|
-
let!(:category) { build :category }
|
|
43
|
-
|
|
44
42
|
it 'delegates to rate object' do
|
|
45
43
|
expect(Rating::Rate).to receive(:create).with(
|
|
46
|
-
author: author, metadata: { comment: 'comment' }, resource: article, scopeable: nil, value: 3
|
|
44
|
+
author: author, extra_scopes: {}, metadata: { comment: 'comment' }, resource: article, scopeable: nil, value: 3
|
|
47
45
|
)
|
|
48
46
|
|
|
49
47
|
author.rate article, 3, metadata: { comment: 'comment' }
|
|
50
48
|
end
|
|
51
49
|
end
|
|
50
|
+
|
|
51
|
+
context 'with extra_scopes' do
|
|
52
|
+
it 'delegates to rate object' do
|
|
53
|
+
expect(Rating::Rate).to receive(:create).with(
|
|
54
|
+
author: author, extra_scopes: { scope_1: 'scope_1' }, metadata: { comment: 'comment' }, resource: article, scopeable: nil, value: 3
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
author.rate article, 3, extra_scopes: { scope_1: 'scope_1' }, metadata: { comment: 'comment' }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
52
60
|
end
|
|
@@ -3,36 +3,44 @@
|
|
|
3
3
|
require 'rails_helper'
|
|
4
4
|
|
|
5
5
|
RSpec.describe Rating::Extension, ':rated?' do
|
|
6
|
-
let!(:author)
|
|
7
|
-
let!(:
|
|
6
|
+
let!(:author) { create :author }
|
|
7
|
+
let!(:resource) { create :article }
|
|
8
8
|
|
|
9
9
|
context 'with no scopeable' do
|
|
10
|
-
|
|
11
|
-
before { allow(author).to receive(:rate_for).with(article, scope: nil).and_return nil }
|
|
10
|
+
before { author.rate resource, 1 }
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
context 'when has no rate for the given resource' do
|
|
13
|
+
specify { expect(author.rated?(create(:article))).to eq false }
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
context 'when has rate for the given resource' do
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
specify { expect(author.rated?(article)).to eq true }
|
|
17
|
+
specify { expect(author.rated?(resource)).to eq true }
|
|
20
18
|
end
|
|
21
19
|
end
|
|
22
20
|
|
|
23
21
|
context 'with scopeable' do
|
|
24
|
-
let!(:category) {
|
|
22
|
+
let!(:category) { create :category }
|
|
25
23
|
|
|
26
|
-
|
|
27
|
-
before { allow(author).to receive(:rate_for).with(article, scope: category).and_return nil }
|
|
24
|
+
before { author.rate resource, 1, scope: category }
|
|
28
25
|
|
|
29
|
-
|
|
26
|
+
context 'when has no rate for the given resource' do
|
|
27
|
+
specify { expect(author.rated?(resource, scope: create(:category))).to eq false }
|
|
30
28
|
end
|
|
31
29
|
|
|
32
30
|
context 'when has rate for the given resource' do
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
specify { expect(author.rated?(article, scope: category)).to eq true }
|
|
31
|
+
specify { expect(author.rated?(resource, scope: category)).to eq true }
|
|
36
32
|
end
|
|
37
33
|
end
|
|
34
|
+
|
|
35
|
+
context 'with extra scopes' do
|
|
36
|
+
before { author.rate resource, 1, extra_scopes: { scope_1: 'scope_1' } }
|
|
37
|
+
|
|
38
|
+
context 'when has no rate for the given resource with given extra scopes' do
|
|
39
|
+
specify { expect(author.rated?(resource, extra_scopes: { scope_1: 'missing' })).to eq false }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
context 'when has rate for the given resource with given extra scopes' do
|
|
43
|
+
specify { expect(author.rated?(resource, extra_scopes: { scope_1: 'scope_1' })).to eq true }
|
|
44
|
+
end
|
|
45
|
+
end if ENV['CONFIG_ENABLED_WITH_EXTRA_SCOPES'] == 'true'
|
|
38
46
|
end
|
|
@@ -37,4 +37,12 @@ RSpec.describe Rating::Extension, ':rated' do
|
|
|
37
37
|
expect(Rating::Rate.where(resource: article_1).count).to eq 0
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
|
+
|
|
41
|
+
context 'with extra scopes' do
|
|
42
|
+
let!(:extra_scopes_rate) { author_1.rate article_1, 1, extra_scopes: { scope_1: 'scope_1' } }
|
|
43
|
+
|
|
44
|
+
it 'returns records considering the extra scopes' do
|
|
45
|
+
expect(author_1.rated(extra_scopes: { scope_1: 'scope_1' })).to eq [extra_scopes_rate]
|
|
46
|
+
end
|
|
47
|
+
end if ENV['CONFIG_ENABLED_WITH_EXTRA_SCOPES'] == 'true'
|
|
40
48
|
end
|
|
@@ -37,4 +37,12 @@ RSpec.describe Rating::Extension, ':rates' do
|
|
|
37
37
|
expect(Rating::Rate.where(resource: article_1).count).to eq 0
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
|
+
|
|
41
|
+
context 'with extra scopes' do
|
|
42
|
+
let!(:extra_scopes_rate) { author_1.rate article_1, 1, extra_scopes: { scope_1: 'scope_1' } }
|
|
43
|
+
|
|
44
|
+
it 'returns records considering the extra scopes' do
|
|
45
|
+
expect(article_1.rates(extra_scopes: { scope_1: 'scope_1' })).to eq [extra_scopes_rate]
|
|
46
|
+
end
|
|
47
|
+
end if ENV['CONFIG_ENABLED_WITH_EXTRA_SCOPES'] == 'true'
|
|
40
48
|
end
|
|
@@ -7,7 +7,7 @@ RSpec.describe Rating::Rate, ':create' do
|
|
|
7
7
|
let!(:article) { create :article }
|
|
8
8
|
|
|
9
9
|
context 'with no scopeable' do
|
|
10
|
-
before { described_class.create author: author, metadata: {}, resource: article, value: 3 }
|
|
10
|
+
before { described_class.create author: author, extra_scopes: {}, metadata: {}, resource: article, value: 3 }
|
|
11
11
|
|
|
12
12
|
context 'when rate does not exist yet' do
|
|
13
13
|
it 'creates a rate entry' do
|
|
@@ -32,7 +32,7 @@ RSpec.describe Rating::Rate, ':create' do
|
|
|
32
32
|
context 'when rate already exists' do
|
|
33
33
|
let!(:author_2) { create :author }
|
|
34
34
|
|
|
35
|
-
before { described_class.create author: author_2, metadata: {}, resource: article, value: 4 }
|
|
35
|
+
before { described_class.create author: author_2, extra_scopes: {}, metadata: {}, resource: article, value: 4 }
|
|
36
36
|
|
|
37
37
|
it 'creates one more rate entry' do
|
|
38
38
|
rates = described_class.where(author: [author, author_2]).order('created_at asc')
|
|
@@ -67,7 +67,16 @@ RSpec.describe Rating::Rate, ':create' do
|
|
|
67
67
|
context 'with scopeable' do
|
|
68
68
|
let!(:category) { create :category }
|
|
69
69
|
|
|
70
|
-
before
|
|
70
|
+
before do
|
|
71
|
+
described_class.create(
|
|
72
|
+
author: author,
|
|
73
|
+
extra_scopes: {},
|
|
74
|
+
metadata: {},
|
|
75
|
+
resource: article,
|
|
76
|
+
scopeable: category,
|
|
77
|
+
value: 3
|
|
78
|
+
)
|
|
79
|
+
end
|
|
71
80
|
|
|
72
81
|
context 'when rate does not exist yet' do
|
|
73
82
|
it 'creates a rate entry' do
|
|
@@ -94,7 +103,16 @@ RSpec.describe Rating::Rate, ':create' do
|
|
|
94
103
|
context 'when rate already exists' do
|
|
95
104
|
let!(:author_2) { create :author }
|
|
96
105
|
|
|
97
|
-
before
|
|
106
|
+
before do
|
|
107
|
+
described_class.create(
|
|
108
|
+
author: author_2,
|
|
109
|
+
extra_scopes: {},
|
|
110
|
+
metadata: {},
|
|
111
|
+
resource: article,
|
|
112
|
+
scopeable: category,
|
|
113
|
+
value: 4
|
|
114
|
+
)
|
|
115
|
+
end
|
|
98
116
|
|
|
99
117
|
it 'creates one more rate entry' do
|
|
100
118
|
rates = described_class.where(author: [author, author_2]).order('created_at asc')
|
|
@@ -132,7 +150,7 @@ RSpec.describe Rating::Rate, ':create' do
|
|
|
132
150
|
context 'with metadata' do
|
|
133
151
|
context 'with nil value' do
|
|
134
152
|
it 'creates a rate entry ignoring metadata' do
|
|
135
|
-
described_class.create author: author, metadata: nil, resource: article, value: 3
|
|
153
|
+
described_class.create author: author, extra_scopes: {}, metadata: nil, resource: article, value: 3
|
|
136
154
|
|
|
137
155
|
rate = described_class.last
|
|
138
156
|
|
|
@@ -145,7 +163,7 @@ RSpec.describe Rating::Rate, ':create' do
|
|
|
145
163
|
|
|
146
164
|
context 'with empty value' do
|
|
147
165
|
it 'creates a rate entry ignoring metadata' do
|
|
148
|
-
described_class.create author: author, metadata: '', resource: article, value: 3
|
|
166
|
+
described_class.create author: author, extra_scopes: {}, metadata: '', resource: article, value: 3
|
|
149
167
|
|
|
150
168
|
rate = described_class.last
|
|
151
169
|
|
|
@@ -158,7 +176,13 @@ RSpec.describe Rating::Rate, ':create' do
|
|
|
158
176
|
|
|
159
177
|
context 'with hash value' do
|
|
160
178
|
it 'creates a rate entry with metadata included' do
|
|
161
|
-
described_class.create
|
|
179
|
+
described_class.create(
|
|
180
|
+
author: author,
|
|
181
|
+
extra_scopes: {},
|
|
182
|
+
metadata: { comment: 'comment' },
|
|
183
|
+
resource: article,
|
|
184
|
+
value: 3
|
|
185
|
+
)
|
|
162
186
|
|
|
163
187
|
rate = described_class.last
|
|
164
188
|
|
|
@@ -169,4 +193,170 @@ RSpec.describe Rating::Rate, ':create' do
|
|
|
169
193
|
end
|
|
170
194
|
end
|
|
171
195
|
end
|
|
196
|
+
|
|
197
|
+
context 'with extra scopes' do
|
|
198
|
+
let!(:category) { create :category }
|
|
199
|
+
|
|
200
|
+
it 'creates a rate entry' do
|
|
201
|
+
described_class.create(
|
|
202
|
+
author: author,
|
|
203
|
+
extra_scopes: { scope_1: 'scope_1', scope_2: 'scope_2' },
|
|
204
|
+
metadata: {},
|
|
205
|
+
resource: article,
|
|
206
|
+
scopeable: category,
|
|
207
|
+
value: 1
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
rate = described_class.last
|
|
211
|
+
|
|
212
|
+
expect(rate.author).to eq author
|
|
213
|
+
expect(rate.resource).to eq article
|
|
214
|
+
expect(rate.scope_1).to eq 'scope_1'
|
|
215
|
+
expect(rate.scope_2).to eq 'scope_2'
|
|
216
|
+
expect(rate.scopeable).to eq category
|
|
217
|
+
expect(rate.value).to eq 1
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
it 'creates a rating entry' do
|
|
221
|
+
described_class.create(
|
|
222
|
+
author: author,
|
|
223
|
+
extra_scopes: { scope_1: 'scope_1', scope_2: 'scope_2' },
|
|
224
|
+
metadata: {},
|
|
225
|
+
resource: article,
|
|
226
|
+
scopeable: category,
|
|
227
|
+
value: 1
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
rating = Rating::Rating.last
|
|
231
|
+
|
|
232
|
+
expect(rating.average).to eq 1
|
|
233
|
+
expect(rating.estimate).to eq 1
|
|
234
|
+
expect(rating.resource).to eq article
|
|
235
|
+
expect(rating.scopeable).to eq category
|
|
236
|
+
expect(rating.sum).to eq 1
|
|
237
|
+
expect(rating.total).to eq 1
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
context 'when rate already exists' do
|
|
241
|
+
before do
|
|
242
|
+
described_class.create(
|
|
243
|
+
author: author,
|
|
244
|
+
extra_scopes: { scope_1: 'scope_1', scope_2: 'scope_2' },
|
|
245
|
+
metadata: {},
|
|
246
|
+
resource: article,
|
|
247
|
+
scopeable: category,
|
|
248
|
+
value: 1
|
|
249
|
+
)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
it 'updates the rate entry' do
|
|
253
|
+
described_class.create(
|
|
254
|
+
author: author,
|
|
255
|
+
extra_scopes: { scope_1: 'scope_1', scope_2: 'scope_2' },
|
|
256
|
+
metadata: {},
|
|
257
|
+
resource: article,
|
|
258
|
+
scopeable: category,
|
|
259
|
+
value: 2
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
rates = described_class.all
|
|
263
|
+
|
|
264
|
+
expect(rates.size).to eq 1
|
|
265
|
+
|
|
266
|
+
rate = rates[0]
|
|
267
|
+
|
|
268
|
+
expect(rate.author).to eq author
|
|
269
|
+
expect(rate.resource).to eq article
|
|
270
|
+
expect(rate.scope_1).to eq 'scope_1'
|
|
271
|
+
expect(rate.scope_2).to eq 'scope_2'
|
|
272
|
+
expect(rate.scopeable).to eq category
|
|
273
|
+
expect(rate.value).to eq 2
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
it 'updates the unique rating entry' do
|
|
277
|
+
described_class.create(
|
|
278
|
+
author: author,
|
|
279
|
+
extra_scopes: { scope_1: 'scope_1', scope_2: 'scope_2' },
|
|
280
|
+
metadata: {},
|
|
281
|
+
resource: article,
|
|
282
|
+
scopeable: category,
|
|
283
|
+
value: 2
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
ratings = Rating::Rating.all
|
|
287
|
+
|
|
288
|
+
expect(ratings.size).to eq 1
|
|
289
|
+
|
|
290
|
+
rating = ratings[0]
|
|
291
|
+
|
|
292
|
+
expect(rating.average).to eq 2
|
|
293
|
+
expect(rating.estimate).to eq 2
|
|
294
|
+
expect(rating.resource).to eq article
|
|
295
|
+
expect(rating.scopeable).to eq category
|
|
296
|
+
expect(rating.sum).to eq 2
|
|
297
|
+
expect(rating.total).to eq 1
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
context 'when rate already exists but with at least one extra scope different' do
|
|
302
|
+
before do
|
|
303
|
+
described_class.create(
|
|
304
|
+
author: author,
|
|
305
|
+
extra_scopes: { scope_1: 'scope_1', scope_2: 'scope_2' },
|
|
306
|
+
metadata: {},
|
|
307
|
+
resource: article,
|
|
308
|
+
scopeable: category,
|
|
309
|
+
value: 1
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
described_class.create(
|
|
313
|
+
author: author,
|
|
314
|
+
extra_scopes: { scope_1: 'scope_1', scope_2: 'scope_missing' },
|
|
315
|
+
metadata: {},
|
|
316
|
+
resource: article,
|
|
317
|
+
scopeable: category,
|
|
318
|
+
value: 2
|
|
319
|
+
)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
it 'creates a new rate entry' do
|
|
323
|
+
rates = described_class.all
|
|
324
|
+
|
|
325
|
+
expect(rates.size).to eq 2
|
|
326
|
+
|
|
327
|
+
rate = rates[0]
|
|
328
|
+
|
|
329
|
+
expect(rate.author).to eq author
|
|
330
|
+
expect(rate.resource).to eq article
|
|
331
|
+
expect(rate.scope_1).to eq 'scope_1'
|
|
332
|
+
expect(rate.scope_2).to eq 'scope_2'
|
|
333
|
+
expect(rate.scopeable).to eq category
|
|
334
|
+
expect(rate.value).to eq 1
|
|
335
|
+
|
|
336
|
+
rate = rates[1]
|
|
337
|
+
|
|
338
|
+
expect(rate.author).to eq author
|
|
339
|
+
expect(rate.resource).to eq article
|
|
340
|
+
expect(rate.scope_1).to eq 'scope_1'
|
|
341
|
+
expect(rate.scope_2).to eq 'scope_missing'
|
|
342
|
+
expect(rate.scopeable).to eq category
|
|
343
|
+
expect(rate.value).to eq 2
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
it 'updates the unique rating entry' do
|
|
347
|
+
ratings = Rating::Rating.all
|
|
348
|
+
|
|
349
|
+
expect(ratings.size).to eq 1
|
|
350
|
+
|
|
351
|
+
rating = ratings[0]
|
|
352
|
+
|
|
353
|
+
expect(rating.average).to eq 1.5
|
|
354
|
+
expect(rating.estimate).to eq 1.5
|
|
355
|
+
expect(rating.resource).to eq article
|
|
356
|
+
expect(rating.scopeable).to eq category
|
|
357
|
+
expect(rating.sum).to eq 3
|
|
358
|
+
expect(rating.total).to eq 2
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
end if ENV['CONFIG_ENABLED_WITH_EXTRA_SCOPES'] == 'true'
|
|
172
362
|
end
|
|
@@ -8,14 +8,16 @@ RSpec.describe Rating::Rate, ':rate_for' do
|
|
|
8
8
|
|
|
9
9
|
context 'with no scopeable' do
|
|
10
10
|
context 'when rate does not exist' do
|
|
11
|
-
|
|
11
|
+
it { expect(described_class.rate_for(author: author, resource: article)).to eq nil }
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
context 'when rate
|
|
15
|
-
|
|
14
|
+
context 'when rate exists' do
|
|
15
|
+
let!(:record) do
|
|
16
|
+
described_class.create author: author, extra_scopes: {}, metadata: {}, resource: article, value: 3
|
|
17
|
+
end
|
|
16
18
|
|
|
17
19
|
it 'returns the record' do
|
|
18
|
-
expect(described_class.rate_for(author: author, resource: article)).to eq
|
|
20
|
+
expect(described_class.rate_for(author: author, resource: article)).to eq record
|
|
19
21
|
end
|
|
20
22
|
end
|
|
21
23
|
end
|
|
@@ -24,17 +26,80 @@ RSpec.describe Rating::Rate, ':rate_for' do
|
|
|
24
26
|
let!(:category) { create :category }
|
|
25
27
|
|
|
26
28
|
context 'when rate does not exist' do
|
|
27
|
-
|
|
29
|
+
it do
|
|
30
|
+
expect(described_class.rate_for(author: author, resource: article, scopeable: category)).to eq nil
|
|
31
|
+
end
|
|
28
32
|
end
|
|
29
33
|
|
|
30
|
-
context 'when rate
|
|
31
|
-
|
|
34
|
+
context 'when rate exists' do
|
|
35
|
+
let!(:record) do
|
|
36
|
+
described_class.create(
|
|
37
|
+
author: author,
|
|
38
|
+
extra_scopes: {},
|
|
39
|
+
metadata: {},
|
|
40
|
+
resource: article,
|
|
41
|
+
scopeable: category,
|
|
42
|
+
value: 3
|
|
43
|
+
)
|
|
44
|
+
end
|
|
32
45
|
|
|
33
46
|
it 'returns the record' do
|
|
34
47
|
query = described_class.rate_for(author: author, resource: article, scopeable: category)
|
|
35
48
|
|
|
36
|
-
expect(query).to eq
|
|
49
|
+
expect(query).to eq record
|
|
37
50
|
end
|
|
38
51
|
end
|
|
39
52
|
end
|
|
53
|
+
|
|
54
|
+
context 'with extra scopes' do
|
|
55
|
+
let!(:category) { create :category }
|
|
56
|
+
|
|
57
|
+
context 'when matches all attributes including the extra scopes' do
|
|
58
|
+
let!(:record) do
|
|
59
|
+
described_class.create(
|
|
60
|
+
author: author,
|
|
61
|
+
extra_scopes: { scope_1: 'scope_1', scope_2: 'scope_2' },
|
|
62
|
+
metadata: {},
|
|
63
|
+
resource: article,
|
|
64
|
+
scopeable: category,
|
|
65
|
+
value: 1
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'returns the record' do
|
|
70
|
+
result = described_class.rate_for(
|
|
71
|
+
author: author,
|
|
72
|
+
extra_scopes: { scope_1: 'scope_1', scope_2: 'scope_2' },
|
|
73
|
+
resource: article,
|
|
74
|
+
scopeable: category
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
expect(result).to eq record
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
context 'when matches all attributes but at least one extra scopes' do
|
|
82
|
+
before do
|
|
83
|
+
described_class.create(
|
|
84
|
+
author: author,
|
|
85
|
+
extra_scopes: { scope_1: 'scope_1', scope_2: 'scope_2' },
|
|
86
|
+
metadata: {},
|
|
87
|
+
resource: article,
|
|
88
|
+
scopeable: category,
|
|
89
|
+
value: 1
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it 'does not return the record' do
|
|
94
|
+
result = described_class.rate_for(
|
|
95
|
+
author: author,
|
|
96
|
+
extra_scopes: { scope_1: 'scope_1', scope_2: 'scope_missing' },
|
|
97
|
+
resource: article,
|
|
98
|
+
scopeable: category
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
expect(result).to eq nil
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end if ENV['CONFIG_ENABLED_WITH_EXTRA_SCOPES'] == 'true'
|
|
40
105
|
end
|
data/spec/models/rate_spec.rb
CHANGED
|
@@ -18,4 +18,11 @@ RSpec.describe Rating::Rate do
|
|
|
18
18
|
it do
|
|
19
19
|
is_expected.to validate_numericality_of(:value).is_less_than_or_equal_to(100).is_less_than_or_equal_to 100
|
|
20
20
|
end
|
|
21
|
+
|
|
22
|
+
it do
|
|
23
|
+
scopes = %i[author_type resource_id resource_type scopeable_id scopeable_type]
|
|
24
|
+
scopes += %i[scope_1 scope_2] if ENV['CONFIG_ENABLED_WITH_EXTRA_SCOPES'] == 'true'
|
|
25
|
+
|
|
26
|
+
expect(object).to validate_uniqueness_of(:author_id).scoped_to(scopes).case_insensitive
|
|
27
|
+
end
|
|
21
28
|
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddExtraScopesOnRatingRatesTable < ActiveRecord::Migration[5.0]
|
|
4
|
+
def change
|
|
5
|
+
add_column :rating_rates, :scope_1, :string
|
|
6
|
+
add_column :rating_rates, :scope_2, :string
|
|
7
|
+
|
|
8
|
+
remove_index :rating_rates, %i[author_id author_type resource_id resource_type scopeable_id scopeable_type]
|
|
9
|
+
end
|
|
10
|
+
end
|
data/spec/support/migrate.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rating
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Washington Botelho
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2018-02
|
|
11
|
+
date: 2018-03-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|
|
@@ -148,6 +148,7 @@ files:
|
|
|
148
148
|
- lib/rating/version.rb
|
|
149
149
|
- spec/config/rate_table_spec.rb
|
|
150
150
|
- spec/config/rating_table_spec.rb
|
|
151
|
+
- spec/config/validations_spec.rb
|
|
151
152
|
- spec/factories/article.rb
|
|
152
153
|
- spec/factories/author.rb
|
|
153
154
|
- spec/factories/category.rb
|
|
@@ -178,6 +179,7 @@ files:
|
|
|
178
179
|
- spec/support/common.rb
|
|
179
180
|
- spec/support/database_cleaner.rb
|
|
180
181
|
- spec/support/db/migrate/add_comment_on_rating_rates_table.rb
|
|
182
|
+
- spec/support/db/migrate/add_extra_fields_on_rating_rates_table.rb
|
|
181
183
|
- spec/support/db/migrate/create_articles_table.rb
|
|
182
184
|
- spec/support/db/migrate/create_authors_table.rb
|
|
183
185
|
- spec/support/db/migrate/create_categories_table.rb
|
|
@@ -220,6 +222,7 @@ specification_version: 4
|
|
|
220
222
|
summary: A true Bayesian rating system with scope and cache enabled.
|
|
221
223
|
test_files:
|
|
222
224
|
- spec/config/rate_table_spec.rb
|
|
225
|
+
- spec/config/validations_spec.rb
|
|
223
226
|
- spec/config/rating_table_spec.rb
|
|
224
227
|
- spec/models/rate/rate_for_spec.rb
|
|
225
228
|
- spec/models/rate/create_spec.rb
|
|
@@ -256,6 +259,7 @@ test_files:
|
|
|
256
259
|
- spec/support/db/migrate/create_review_ratings_table.rb
|
|
257
260
|
- spec/support/db/migrate/create_articles_table.rb
|
|
258
261
|
- spec/support/db/migrate/add_comment_on_rating_rates_table.rb
|
|
262
|
+
- spec/support/db/migrate/add_extra_fields_on_rating_rates_table.rb
|
|
259
263
|
- spec/support/db/migrate/create_reviews_table.rb
|
|
260
264
|
- spec/support/db/migrate/create_comments_table.rb
|
|
261
265
|
- spec/support/database_cleaner.rb
|