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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 48063ae9a3d3e0ac0573471477dddae7834219fd
4
- data.tar.gz: 20df48c7bb6af87d89ceddb6a5c2b029b7c751fe
3
+ metadata.gz: c384b931b0e023652ca3ae1025de72e2371f5c21
4
+ data.tar.gz: 01cefd2f71309f2a9a1dc09f01f058cc00322619
5
5
  SHA512:
6
- metadata.gz: 2a4ed2ace00a53bc4076942fad4fa3c6fb402c24867ab4b80ea550d27b6b4874464bc2a7a68d8f009a3ef4afc42e1c79286906958a986f20d14eca3327490d31
7
- data.tar.gz: '09c238d2ceed932e4f40a8304d3bfa5bc5ad276082a91be4c0e4f2b3addd89f1ed45ee188f976d811fc3dcdcd1a2e33c129e08d06ee7f6c2f7fd3c513ed3a031'
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: 'reviews'
344
- rating_table: 'review_ratings'
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
- ## Validation
374
+ #### Rate Uniqueness
352
375
 
353
- It is recommended that you add a validation to ensure that the author do not vote multiple times on the same resource and its scope.
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
- ```ruby
357
- validates :author_id, uniqueness: {
358
- case_sensitive: false,
359
- scope: %i[author_type resource_id resource_type scopeable_id scopeable_type]
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 author: author, metadata: metadata, resource: resource, scopeable: scope, value: value
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
- def rated?(resource, scope: nil)
17
- !rate_for(resource, scope: scope).nil?
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
- rates_records.where scopeable: scope
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
- rated_records.where scopeable: scope
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 => scope_values)
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
- def self.create(author:, metadata:, resource:, scopeable: nil, value:)
18
- record = find_or_initialize_by(author: author, resource: resource, scopeable: scopeable)
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
- find_by author: author, resource: resource, scopeable: scopeable
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rating
4
- VERSION = '0.6.0'
4
+ VERSION = '0.7.0'
5
5
  end
@@ -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) { create :author }
7
- let!(:article) { create :article }
6
+ let!(:author) { create :author }
7
+ let!(:resource) { create :article }
8
8
 
9
9
  context 'with no scopeable' do
10
- context 'when has no rate for the given resource' do
11
- before { allow(author).to receive(:rate_for).with(article, scope: nil).and_return nil }
10
+ before { author.rate resource, 1 }
12
11
 
13
- specify { expect(author.rated?(article)).to eq false }
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
- before { allow(author).to receive(:rate_for).with(article, scope: nil).and_return double }
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) { build :category }
22
+ let!(:category) { create :category }
25
23
 
26
- context 'when has no rate for the given resource' do
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
- specify { expect(author.rated?(article, scope: category)).to eq false }
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
- before { allow(author).to receive(:rate_for).with(article, scope: category).and_return double }
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 { described_class.create author: author, metadata: {}, resource: article, scopeable: category, value: 3 }
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 { described_class.create author: author_2, metadata: {}, resource: article, scopeable: category, value: 4 }
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 author: author, metadata: { comment: 'comment' }, resource: article, value: 3
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
- specify { expect(described_class.rate_for(author: author, resource: article)).to eq nil }
11
+ it { expect(described_class.rate_for(author: author, resource: article)).to eq nil }
12
12
  end
13
13
 
14
- context 'when rate does not exist' do
15
- before { described_class.create author: author, metadata: {}, resource: article, value: 3 }
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 described_class.last
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
- specify { expect(described_class.rate_for(author: author, resource: article, scopeable: category)).to eq nil }
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 does not exist' do
31
- before { described_class.create author: author, metadata: {}, resource: article, scopeable: category, value: 3 }
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 described_class.last
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
@@ -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
@@ -14,3 +14,4 @@ CreateRatingTable.new.change
14
14
  CreateReviewRatingsTable.new.change
15
15
  CreateReviewsTable.new.change
16
16
  AddCommentOnRatingRatesTable.new.change
17
+ AddExtraScopesOnRatingRatesTable.new.change
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.6.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-26 00:00:00.000000000 Z
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