This rule was added in rubocop 1.79. We were inconsistent about it, so we're adding it to get more consistency.
239 lines
5.5 KiB
Ruby
239 lines
5.5 KiB
Ruby
module Statisticable
|
|
extend ActiveSupport::Concern
|
|
|
|
PARTICIPATIONS = %w[gender age geozone].freeze
|
|
|
|
included do
|
|
attr_reader :resource, :cache
|
|
end
|
|
|
|
class_methods do
|
|
def stats_methods
|
|
base_stats_methods + gender_methods + age_methods + geozone_methods
|
|
end
|
|
|
|
def base_stats_methods
|
|
%i[total_participants participations] + participation_check_methods
|
|
end
|
|
|
|
def participation_check_methods
|
|
PARTICIPATIONS.map { |participation| :"#{participation}?" }
|
|
end
|
|
|
|
def gender_methods
|
|
%i[total_male_participants total_female_participants male_percentage female_percentage]
|
|
end
|
|
|
|
def age_methods
|
|
[:participants_by_age]
|
|
end
|
|
|
|
def geozone_methods
|
|
%i[participants_by_geozone total_no_demographic_data]
|
|
end
|
|
|
|
def stats_cache(*method_names)
|
|
method_names.each do |method_name|
|
|
alias_method :"raw_#{method_name}", method_name
|
|
|
|
define_method method_name do
|
|
stats_cache(method_name) { send(:"raw_#{method_name}") }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def initialize(resource, cache: true)
|
|
@resource = resource
|
|
@cache = cache
|
|
end
|
|
|
|
def generate
|
|
User.transaction do
|
|
begin
|
|
define_singleton_method :participants do
|
|
create_participants_table unless participants_table_created?
|
|
participants_class.all
|
|
end
|
|
|
|
stats_methods.each { |stat_name| send(stat_name) }
|
|
ensure
|
|
define_singleton_method :participants do
|
|
participants_from_original_table
|
|
end
|
|
end
|
|
|
|
drop_participants_table
|
|
end
|
|
end
|
|
|
|
def stats_methods
|
|
base_stats_methods + participation_methods
|
|
end
|
|
|
|
def participations
|
|
PARTICIPATIONS.select { |participation| send("#{participation}?") }
|
|
end
|
|
|
|
def gender?
|
|
participants.male.any? || participants.female.any?
|
|
end
|
|
|
|
def age?
|
|
participants.between_ages(
|
|
age_groups.flatten.min,
|
|
age_groups.flatten.max,
|
|
at_time: participation_date
|
|
).any?
|
|
end
|
|
|
|
def geozone?
|
|
participants.where(geozone: geozones).any?
|
|
end
|
|
|
|
def participants
|
|
@participants ||= User.unscoped.where(id: participant_ids)
|
|
end
|
|
alias_method :participants_from_original_table, :participants
|
|
|
|
def total_male_participants
|
|
participants.male.count
|
|
end
|
|
|
|
def total_female_participants
|
|
participants.female.count
|
|
end
|
|
|
|
def total_no_demographic_data
|
|
participants.where("gender IS NULL OR date_of_birth IS NULL OR geozone_id IS NULL").count
|
|
end
|
|
|
|
def male_percentage
|
|
calculate_percentage(total_male_participants, total_participants_with_gender)
|
|
end
|
|
|
|
def female_percentage
|
|
calculate_percentage(total_female_participants, total_participants_with_gender)
|
|
end
|
|
|
|
def participants_by_age
|
|
age_groups.to_h do |start, finish|
|
|
count = participants.between_ages(start, finish, at_time: participation_date).count
|
|
|
|
[
|
|
"#{start} - #{finish}",
|
|
{
|
|
range: range_description(start, finish),
|
|
count: count,
|
|
percentage: calculate_percentage(count, total_participants)
|
|
}
|
|
]
|
|
end
|
|
end
|
|
|
|
def participants_by_geozone
|
|
geozones.to_h do |geozone|
|
|
count = participants.where(geozone: geozone).count
|
|
|
|
[
|
|
geozone.name,
|
|
{
|
|
count: count,
|
|
percentage: calculate_percentage(count, total_participants)
|
|
}
|
|
]
|
|
end
|
|
end
|
|
|
|
def calculate_percentage(fraction, total)
|
|
return 0.0 if total.zero?
|
|
|
|
(fraction * 100.0 / total).round(3)
|
|
end
|
|
|
|
def advanced?
|
|
resource.advanced_stats_enabled?
|
|
end
|
|
|
|
private
|
|
|
|
def base_stats_methods
|
|
self.class.base_stats_methods
|
|
end
|
|
|
|
def participation_methods
|
|
participations.map { |participation| self.class.send("#{participation}_methods") }.flatten
|
|
end
|
|
|
|
def create_participants_table
|
|
User.connection.create_table(
|
|
participants_table_name,
|
|
temporary: true,
|
|
as: participants_from_original_table.to_sql
|
|
)
|
|
User.connection.add_index participants_table_name, :date_of_birth
|
|
User.connection.add_index participants_table_name, :geozone_id
|
|
@participants_table_created = true
|
|
end
|
|
|
|
def drop_participants_table
|
|
User.connection.drop_table(participants_table_name, if_exists: true, temporary: true)
|
|
@participants_table_created = false
|
|
end
|
|
|
|
def participants_table_name
|
|
@participants_table_name ||= "participants_#{resource.class.table_name}_#{resource.id}"
|
|
end
|
|
|
|
def participants_class
|
|
@participants_class ||= Class.new(User).tap { |klass| klass.table_name = participants_table_name }
|
|
end
|
|
|
|
def participants_table_created?
|
|
@participants_table_created.present?
|
|
end
|
|
|
|
def total_participants_with_gender
|
|
@total_participants_with_gender ||= participants.where.not(gender: nil).distinct.count
|
|
end
|
|
|
|
def age_groups
|
|
[[16, 19],
|
|
[20, 24],
|
|
[25, 29],
|
|
[30, 34],
|
|
[35, 39],
|
|
[40, 44],
|
|
[45, 49],
|
|
[50, 54],
|
|
[55, 59],
|
|
[60, 64],
|
|
[65, 69],
|
|
[70, 74],
|
|
[75, 79],
|
|
[80, 84],
|
|
[85, 89],
|
|
[90, 300]]
|
|
end
|
|
|
|
def geozones
|
|
Geozone.order(:name)
|
|
end
|
|
|
|
def range_description(start, finish)
|
|
if finish > 200
|
|
I18n.t("stats.age_more_than", start: start)
|
|
else
|
|
I18n.t("stats.age_range", start: start, finish: finish)
|
|
end
|
|
end
|
|
|
|
def stats_cache(key, &block)
|
|
if cache
|
|
Rails.cache.fetch(full_cache_key_for(key), expires_at: Date.current.end_of_day, &block)
|
|
else
|
|
block.call
|
|
end
|
|
end
|
|
end
|