feat: unscoped rating
parent
09010f5ee2
commit
fd954f677f
|
@ -55,8 +55,8 @@ module Rating
|
|||
end
|
||||
|
||||
module ClassMethods
|
||||
def rating(as: nil, scoping: nil)
|
||||
after_create -> { rating_warm_up scoping: scoping }, unless: -> { as == :author }
|
||||
def rating(options = {})
|
||||
after_create -> { rating_warm_up scoping: options[:scoping] }, unless: -> { options[:as] == :author }
|
||||
|
||||
has_many :rating_records,
|
||||
as: :resource,
|
||||
|
@ -78,6 +78,10 @@ module Rating
|
|||
.where(Rating.table_name => { scopeable_id: scope&.id, scopeable_type: scope&.class&.base_class&.name })
|
||||
.order("#{Rating.table_name}.#{column} #{direction}")
|
||||
}
|
||||
|
||||
define_method :rating_options do
|
||||
options
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -18,8 +18,8 @@ module Rating
|
|||
|
||||
class << self
|
||||
def averager_data(resource, scopeable)
|
||||
total_count = how_many_resource_received_votes_sql?(distinct: false, scopeable: scopeable)
|
||||
distinct_count = how_many_resource_received_votes_sql?(distinct: true, scopeable: scopeable)
|
||||
total_count = how_many_resource_received_votes_sql(distinct: false, resource: resource, scopeable: scopeable)
|
||||
distinct_count = how_many_resource_received_votes_sql(distinct: true, resource: resource, scopeable: scopeable)
|
||||
values = { resource_type: resource.class.base_class.name }
|
||||
|
||||
values[:scopeable_type] = scopeable.class.base_class.name unless scopeable.nil?
|
||||
|
@ -29,7 +29,7 @@ module Rating
|
|||
(CAST(#{total_count} AS DECIMAL(17, 14)) / #{distinct_count}) count_avg,
|
||||
COALESCE(AVG(value), 0) rating_avg
|
||||
FROM #{rate_table_name}
|
||||
WHERE resource_type = :resource_type AND #{scope_type_query(scopeable)}
|
||||
WHERE resource_type = :resource_type #{scope_type_query(resource, scopeable)}
|
||||
).squish
|
||||
|
||||
execute_sql [sql, values]
|
||||
|
@ -48,29 +48,26 @@ module Rating
|
|||
end
|
||||
|
||||
def values_data(resource, scopeable)
|
||||
scope_query = if scopeable.nil?
|
||||
'scopeable_type is NULL AND scopeable_id is NULL'
|
||||
else
|
||||
'scopeable_type = ? AND scopeable_id = ?'
|
||||
end
|
||||
|
||||
sql = %(
|
||||
SELECT
|
||||
COALESCE(AVG(value), 0) rating_avg,
|
||||
COALESCE(SUM(value), 0) rating_sum,
|
||||
COUNT(1) rating_count
|
||||
FROM #{rate_table_name}
|
||||
WHERE resource_type = ? AND resource_id = ? AND #{scope_query}
|
||||
WHERE resource_type = ? AND resource_id = ? #{scope_type_and_id_query(resource, scopeable)}
|
||||
).squish
|
||||
|
||||
values = [sql, resource.class.base_class.name, resource.id]
|
||||
values += [scopeable.class.base_class.name, scopeable.id] unless scopeable.nil?
|
||||
values += [scopeable.class.base_class.name, scopeable.id] unless scopeable.nil? || unscoped_rating?(resource)
|
||||
|
||||
execute_sql values
|
||||
end
|
||||
|
||||
def update_rating(resource, scopeable)
|
||||
record = find_or_initialize_by(resource: resource, scopeable: scopeable)
|
||||
attributes = { resource: resource }
|
||||
attributes[:scopeable] = unscoped_rating?(resource) ? nil : scopeable
|
||||
|
||||
record = find_or_initialize_by(attributes)
|
||||
result = data(resource, scopeable)
|
||||
|
||||
record.average = result[:average]
|
||||
|
@ -97,13 +94,17 @@ module Rating
|
|||
Rate.find_by_sql(sql).first
|
||||
end
|
||||
|
||||
def how_many_resource_received_votes_sql?(distinct:, scopeable:)
|
||||
def unscoped_rating?(resource)
|
||||
resource.rating_options[:unscoped_rating]
|
||||
end
|
||||
|
||||
def how_many_resource_received_votes_sql(distinct:, resource:, scopeable:)
|
||||
count = distinct ? 'COUNT(DISTINCT resource_id)' : 'COUNT(1)'
|
||||
|
||||
%((
|
||||
SELECT GREATEST(#{count}, 1)
|
||||
FROM #{rate_table_name}
|
||||
WHERE resource_type = :resource_type AND #{scope_type_query(scopeable)}
|
||||
WHERE resource_type = :resource_type #{scope_type_query(resource, scopeable)}
|
||||
))
|
||||
end
|
||||
|
||||
|
@ -111,8 +112,17 @@ module Rating
|
|||
@rate_table_name ||= Rate.table_name
|
||||
end
|
||||
|
||||
def scope_type_query(scopeable)
|
||||
scopeable.nil? ? 'scopeable_type is NULL' : 'scopeable_type = :scopeable_type'
|
||||
def scope_type_query(resource, scopeable)
|
||||
return '' if unscoped_rating?(resource)
|
||||
|
||||
scopeable.nil? ? 'AND scopeable_type is NULL' : 'AND scopeable_type = :scopeable_type'
|
||||
end
|
||||
|
||||
def scope_type_and_id_query(resource, scopeable)
|
||||
return '' if unscoped_rating?(resource)
|
||||
return 'AND scopeable_type is NULL AND scopeable_id is NULL' if scopeable.nil?
|
||||
|
||||
'AND scopeable_type = ? AND scopeable_id = ?'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :global do
|
||||
end
|
||||
end
|
|
@ -0,0 +1,121 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Rating::Extension, 'unscoped_rating' do
|
||||
let!(:author_1) { create :author }
|
||||
let!(:author_2) { create :author }
|
||||
let!(:scope) { create :category }
|
||||
|
||||
context 'when is false' do
|
||||
let!(:resource) { create :article }
|
||||
|
||||
it 'groups in different line record' do
|
||||
author_1.rate resource, 1, scope: scope
|
||||
author_2.rate resource, 2, scope: scope
|
||||
author_1.rate resource, 5
|
||||
|
||||
ratings = Rating::Rating.all.order('id')
|
||||
|
||||
expect(ratings.size).to eq 2
|
||||
|
||||
rating = ratings[0]
|
||||
|
||||
expect(rating.average.to_s).to eq '1.5'
|
||||
expect(rating.estimate.to_s).to eq '1.5'
|
||||
expect(rating.resource).to eq resource
|
||||
expect(rating.scopeable).to eq scope
|
||||
expect(rating.sum).to eq 3
|
||||
expect(rating.total).to eq 2
|
||||
|
||||
rating = ratings[1]
|
||||
|
||||
expect(rating.average.to_s).to eq '5.0'
|
||||
expect(rating.estimate.to_s).to eq '5.0'
|
||||
expect(rating.resource).to eq resource
|
||||
expect(rating.scopeable).to eq nil
|
||||
expect(rating.sum).to eq 5
|
||||
expect(rating.total).to eq 1
|
||||
end
|
||||
end
|
||||
|
||||
context 'when is true' do
|
||||
let!(:resource) { create :global }
|
||||
|
||||
it 'groups in the same line record' do
|
||||
author_1.rate resource, 1, scope: scope
|
||||
author_2.rate resource, 2, scope: scope
|
||||
author_1.rate resource, 5
|
||||
|
||||
ratings = Rating::Rating.all.order('id')
|
||||
|
||||
expect(ratings.size).to eq 1
|
||||
|
||||
rating = ratings[0]
|
||||
|
||||
expect(rating.average.to_s).to eq '2.6666666666666667'
|
||||
expect(rating.estimate.to_s).to eq '2.6666666666666667'
|
||||
expect(rating.resource).to eq resource
|
||||
expect(rating.scopeable).to eq nil
|
||||
expect(rating.sum).to eq 8
|
||||
expect(rating.total).to eq 3
|
||||
end
|
||||
end
|
||||
|
||||
context 'when is true' do
|
||||
let!(:resource) { create :global }
|
||||
|
||||
it 'groups in the same line record' do
|
||||
author_1.rate resource, 1, scope: scope
|
||||
author_2.rate resource, 2, scope: scope
|
||||
author_1.rate resource, 5
|
||||
|
||||
ratings = Rating::Rating.all.order('id')
|
||||
|
||||
expect(ratings.size).to eq 1
|
||||
|
||||
rating = ratings[0]
|
||||
|
||||
expect(rating.average.to_s).to eq '2.6666666666666667'
|
||||
expect(rating.estimate.to_s).to eq '2.6666666666666667'
|
||||
expect(rating.resource).to eq resource
|
||||
expect(rating.scopeable).to eq nil
|
||||
expect(rating.sum).to eq 8
|
||||
expect(rating.total).to eq 3
|
||||
end
|
||||
end
|
||||
|
||||
context 'when is true and have a non scopeable record first on dabase' do
|
||||
let!(:resource) { create :global }
|
||||
|
||||
before { ::Rating::Rating.create resource: resource, scopeable: scope }
|
||||
|
||||
it 'groups in the line with no scope' do
|
||||
author_1.rate resource, 1, scope: scope
|
||||
author_2.rate resource, 2, scope: scope
|
||||
author_1.rate resource, 5
|
||||
|
||||
ratings = Rating::Rating.all.order('id')
|
||||
|
||||
expect(ratings.size).to eq 2
|
||||
|
||||
rating = ratings[0]
|
||||
|
||||
expect(rating.average.to_s).to eq '0.0'
|
||||
expect(rating.estimate.to_s).to eq '0.0'
|
||||
expect(rating.resource).to eq resource
|
||||
expect(rating.scopeable).to eq scope
|
||||
expect(rating.sum).to eq 0
|
||||
expect(rating.total).to eq 0
|
||||
|
||||
rating = ratings[1]
|
||||
|
||||
expect(rating.average.to_s).to eq '2.6666666666666667'
|
||||
expect(rating.estimate.to_s).to eq '2.6666666666666667'
|
||||
expect(rating.resource).to eq resource
|
||||
expect(rating.scopeable).to eq nil
|
||||
expect(rating.sum).to eq 8
|
||||
expect(rating.total).to eq 3
|
||||
end
|
||||
end
|
||||
end
|
|
@ -6,6 +6,7 @@ class CreateCategoriesTable < ActiveRecord::Migration[5.0]
|
|||
t.string :name, null: false
|
||||
|
||||
t.references :article, foreign_key: true, index: true
|
||||
t.references :global, foreign_key: true, index: true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateGlobalsTable < ActiveRecord::Migration[5.0]
|
||||
def change
|
||||
create_table :globals do |t|
|
||||
end
|
||||
end
|
||||
end
|
|
@ -7,7 +7,10 @@ Dir[File.expand_path('db/migrate/*.rb', __dir__)].each { |file| require file }
|
|||
|
||||
CreateArticlesTable.new.change
|
||||
CreateAuthorsTable.new.change
|
||||
|
||||
CreateGlobalsTable.new.change
|
||||
CreateCategoriesTable.new.change
|
||||
|
||||
CreateCommentsTable.new.change
|
||||
CreateRateTable.new.change
|
||||
CreateRatingTable.new.change
|
||||
|
|
|
@ -2,4 +2,5 @@
|
|||
|
||||
class Category < ::ActiveRecord::Base
|
||||
belongs_to :article
|
||||
belongs_to :global
|
||||
end
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Global < ::ActiveRecord::Base
|
||||
rating scoping: :categories, unscoped_rating: true
|
||||
|
||||
has_many :categories
|
||||
end
|
Loading…
Reference in New Issue