rating 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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