diff --git a/lib/rating/models/rating/rate.rb b/lib/rating/models/rating/rate.rb index d7bbf75..af365df 100644 --- a/lib/rating/models/rating/rate.rb +++ b/lib/rating/models/rating/rate.rb @@ -19,8 +19,9 @@ module Rating scope: %i[author_type resource_id resource_type scopeable_id scopeable_type] } - def self.create(author:, metadata:, resource:, scopeable: nil, value:) - record = find_or_initialize_by(author: author, resource: resource, scopeable: scopeable) + def self.create(author:, extra_scopes:, metadata:, resource:, scopeable: nil, value:) + attributes = { author: author, resource: resource, scopeable: scopeable }.merge(extra_scopes) + record = find_or_initialize_by(attributes) return record if record.persisted? && value == record.value @@ -32,8 +33,10 @@ module Rating record end - def self.rate_for(author:, resource:, scopeable: nil) - find_by author: author, resource: resource, scopeable: scopeable + def self.rate_for(author:, extra_scopes: {}, resource:, scopeable: nil) + attributes = { author: author, resource: resource, scopeable: scopeable }.merge(extra_scopes) + + find_by attributes end private diff --git a/spec/models/rate/create_spec.rb b/spec/models/rate/create_spec.rb index bb326bc..924b097 100644 --- a/spec/models/rate/create_spec.rb +++ b/spec/models/rate/create_spec.rb @@ -6,8 +6,10 @@ RSpec.describe Rating::Rate, ':create' do let!(:author) { create :author } let!(:article) { create :article } + before(:all) { AddExtraScopesOnRatingRatesTable.new.change } + context 'with no scopeable' do - before { described_class.create author: author, metadata: {}, resource: article, value: 3 } + before { described_class.create author: author, extra_scopes: {}, metadata: {}, resource: article, value: 3 } context 'when rate does not exist yet' do it 'creates a rate entry' do @@ -32,7 +34,7 @@ RSpec.describe Rating::Rate, ':create' do context 'when rate already exists' do let!(:author_2) { create :author } - before { described_class.create author: author_2, metadata: {}, resource: article, value: 4 } + before { described_class.create author: author_2, extra_scopes: {}, metadata: {}, resource: article, value: 4 } it 'creates one more rate entry' do rates = described_class.where(author: [author, author_2]).order('created_at asc') @@ -67,7 +69,16 @@ RSpec.describe Rating::Rate, ':create' do context 'with scopeable' do let!(:category) { create :category } - before { described_class.create author: author, metadata: {}, resource: article, scopeable: category, value: 3 } + before do + described_class.create( + author: author, + extra_scopes: {}, + metadata: {}, + resource: article, + scopeable: category, + value: 3 + ) + end context 'when rate does not exist yet' do it 'creates a rate entry' do @@ -94,7 +105,16 @@ RSpec.describe Rating::Rate, ':create' do context 'when rate already exists' do let!(:author_2) { create :author } - before { described_class.create author: author_2, metadata: {}, resource: article, scopeable: category, value: 4 } + before do + described_class.create( + author: author_2, + extra_scopes: {}, + metadata: {}, + resource: article, + scopeable: category, + value: 4 + ) + end it 'creates one more rate entry' do rates = described_class.where(author: [author, author_2]).order('created_at asc') @@ -132,7 +152,7 @@ RSpec.describe Rating::Rate, ':create' do context 'with metadata' do context 'with nil value' do it 'creates a rate entry ignoring metadata' do - described_class.create author: author, metadata: nil, resource: article, value: 3 + described_class.create author: author, extra_scopes: {}, metadata: nil, resource: article, value: 3 rate = described_class.last @@ -145,7 +165,7 @@ RSpec.describe Rating::Rate, ':create' do context 'with empty value' do it 'creates a rate entry ignoring metadata' do - described_class.create author: author, metadata: '', resource: article, value: 3 + described_class.create author: author, extra_scopes: {}, metadata: '', resource: article, value: 3 rate = described_class.last @@ -158,7 +178,13 @@ RSpec.describe Rating::Rate, ':create' do context 'with hash value' do it 'creates a rate entry with metadata included' do - described_class.create author: author, metadata: { comment: 'comment' }, resource: article, value: 3 + described_class.create( + author: author, + extra_scopes: {}, + metadata: { comment: 'comment' }, + resource: article, + value: 3 + ) rate = described_class.last @@ -169,4 +195,170 @@ RSpec.describe Rating::Rate, ':create' do end end end + + context 'with extra scopes' do + let!(:category) { create :category } + + it 'creates a rate entry' do + described_class.create( + author: author, + extra_scopes: { scope_1: 'scope_1', scope_2: 'scope_2' }, + metadata: {}, + resource: article, + scopeable: category, + value: 1 + ) + + rate = described_class.last + + expect(rate.author).to eq author + expect(rate.resource).to eq article + expect(rate.scope_1).to eq 'scope_1' + expect(rate.scope_2).to eq 'scope_2' + expect(rate.scopeable).to eq category + expect(rate.value).to eq 1 + end + + it 'creates a rating entry' do + described_class.create( + author: author, + extra_scopes: { scope_1: 'scope_1', scope_2: 'scope_2' }, + metadata: {}, + resource: article, + scopeable: category, + value: 1 + ) + + rating = Rating::Rating.last + + expect(rating.average).to eq 1 + expect(rating.estimate).to eq 1 + expect(rating.resource).to eq article + expect(rating.scopeable).to eq category + expect(rating.sum).to eq 1 + expect(rating.total).to eq 1 + end + + context 'when rate already exists' do + before do + described_class.create( + author: author, + extra_scopes: { scope_1: 'scope_1', scope_2: 'scope_2' }, + metadata: {}, + resource: article, + scopeable: category, + value: 1 + ) + end + + it 'updates the rate entry' do + described_class.create( + author: author, + extra_scopes: { scope_1: 'scope_1', scope_2: 'scope_2' }, + metadata: {}, + resource: article, + scopeable: category, + value: 2 + ) + + rates = described_class.all + + expect(rates.size).to eq 1 + + rate = rates[0] + + expect(rate.author).to eq author + expect(rate.resource).to eq article + expect(rate.scope_1).to eq 'scope_1' + expect(rate.scope_2).to eq 'scope_2' + expect(rate.scopeable).to eq category + expect(rate.value).to eq 2 + end + + it 'updates the unique rating entry' do + described_class.create( + author: author, + extra_scopes: { scope_1: 'scope_1', scope_2: 'scope_2' }, + metadata: {}, + resource: article, + scopeable: category, + value: 2 + ) + + ratings = Rating::Rating.all + + expect(ratings.size).to eq 1 + + rating = ratings[0] + + expect(rating.average).to eq 2 + expect(rating.estimate).to eq 2 + expect(rating.resource).to eq article + expect(rating.scopeable).to eq category + expect(rating.sum).to eq 2 + expect(rating.total).to eq 1 + end + end + + context 'when rate already exists but with at least one extra scope different' do + before do + described_class.create( + author: author, + extra_scopes: { scope_1: 'scope_1', scope_2: 'scope_2' }, + metadata: {}, + resource: article, + scopeable: category, + value: 1 + ) + + described_class.create( + author: author, + extra_scopes: { scope_1: 'scope_1', scope_2: 'scope_missing' }, + metadata: {}, + resource: article, + scopeable: category, + value: 2 + ) + end + + it 'creates a new rate entry' do + rates = described_class.all + + expect(rates.size).to eq 2 + + rate = rates[0] + + expect(rate.author).to eq author + expect(rate.resource).to eq article + expect(rate.scope_1).to eq 'scope_1' + expect(rate.scope_2).to eq 'scope_2' + expect(rate.scopeable).to eq category + expect(rate.value).to eq 1 + + rate = rates[1] + + expect(rate.author).to eq author + expect(rate.resource).to eq article + expect(rate.scope_1).to eq 'scope_1' + expect(rate.scope_2).to eq 'scope_missing' + expect(rate.scopeable).to eq category + expect(rate.value).to eq 2 + end + + it 'updates the unique rating entry' do + ratings = Rating::Rating.all + + expect(ratings.size).to eq 1 + + rating = ratings[0] + + expect(rating.average).to eq 1.5 + expect(rating.estimate).to eq 1.5 + expect(rating.resource).to eq article + expect(rating.scopeable).to eq category + expect(rating.sum).to eq 3 + expect(rating.total).to eq 2 + end + end + end end diff --git a/spec/models/rate/rate_for_spec.rb b/spec/models/rate/rate_for_spec.rb index ca8c6d3..ac738aa 100644 --- a/spec/models/rate/rate_for_spec.rb +++ b/spec/models/rate/rate_for_spec.rb @@ -6,16 +6,20 @@ RSpec.describe Rating::Rate, ':rate_for' do let!(:author) { create :author } let!(:article) { create :article } + before(:all) { AddExtraScopesOnRatingRatesTable.new.change } + context 'with no scopeable' do context 'when rate does not exist' do - specify { expect(described_class.rate_for(author: author, resource: article)).to eq nil } + it { expect(described_class.rate_for(author: author, resource: article)).to eq nil } end - context 'when rate does not exist' do - before { described_class.create author: author, metadata: {}, resource: article, value: 3 } + context 'when rate exists' do + let!(:record) do + described_class.create author: author, extra_scopes: {}, metadata: {}, resource: article, value: 3 + end it 'returns the record' do - expect(described_class.rate_for(author: author, resource: article)).to eq described_class.last + expect(described_class.rate_for(author: author, resource: article)).to eq record end end end @@ -24,16 +28,79 @@ RSpec.describe Rating::Rate, ':rate_for' do let!(:category) { create :category } context 'when rate does not exist' do - specify { expect(described_class.rate_for(author: author, resource: article, scopeable: category)).to eq nil } + it do + expect(described_class.rate_for(author: author, resource: article, scopeable: category)).to eq nil + end end - context 'when rate does not exist' do - before { described_class.create author: author, metadata: {}, resource: article, scopeable: category, value: 3 } + context 'when rate exists' do + let!(:record) do + described_class.create( + author: author, + extra_scopes: {}, + metadata: {}, + resource: article, + scopeable: category, + value: 3 + ) + end it 'returns the record' do query = described_class.rate_for(author: author, resource: article, scopeable: category) - expect(query).to eq described_class.last + expect(query).to eq record + end + end + end + + context 'with extra scopes' do + let!(:category) { create :category } + + context 'when matches all attributes including the extra scopes' do + let!(:record) do + described_class.create( + author: author, + extra_scopes: { scope_1: 'scope_1', scope_2: 'scope_2' }, + metadata: {}, + resource: article, + scopeable: category, + value: 1 + ) + end + + it 'returns the record' do + result = described_class.rate_for( + author: author, + extra_scopes: { scope_1: 'scope_1', scope_2: 'scope_2' }, + resource: article, + scopeable: category + ) + + expect(result).to eq record + end + end + + context 'when matches all attributes but at least one extra scopes' do + let!(:record) do + described_class.create( + author: author, + extra_scopes: { scope_1: 'scope_1', scope_2: 'scope_2' }, + metadata: {}, + resource: article, + scopeable: category, + value: 1 + ) + end + + it 'does not return the record' do + result = described_class.rate_for( + author: author, + extra_scopes: { scope_1: 'scope_1', scope_2: 'scope_missing' }, + resource: article, + scopeable: category + ) + + expect(result).to eq nil end end end diff --git a/spec/support/db/migrate/add_extra_fields_on_rating_rates_table.rb b/spec/support/db/migrate/add_extra_fields_on_rating_rates_table.rb new file mode 100644 index 0000000..0be328d --- /dev/null +++ b/spec/support/db/migrate/add_extra_fields_on_rating_rates_table.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class AddExtraScopesOnRatingRatesTable < ActiveRecord::Migration[5.0] + def change + add_column :rating_rates, :scope_1, :string + add_column :rating_rates, :scope_2, :string + + remove_index :rating_rates, %i[author_id author_type resource_id resource_type scopeable_id scopeable_type] + end +end