Display only existing stats
So if we don't have information regarding gender, age or geozone, stats regarding those topics will not be shown. Note we're using `spec/models/statisticable_spec.rb` because having the same file in `spec/models/concerns` caused the tests to be executed twice. Also note the implementation behind the `gender?`, `age?` and `geozone?` methods is a bit primitive. We might need to make it more robust in the future.
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
module Statisticable
|
module Statisticable
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
PARTICIPATIONS = %w[gender age geozone]
|
||||||
|
|
||||||
included do
|
included do
|
||||||
attr_reader :resource
|
attr_reader :resource
|
||||||
@@ -9,7 +10,27 @@ module Statisticable
|
|||||||
end
|
end
|
||||||
|
|
||||||
def generate
|
def generate
|
||||||
self.class.stats_methods.each { |stat_name| send(stat_name) }
|
stats_methods.each { |stat_name| send(stat_name) }
|
||||||
|
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).any?
|
||||||
|
end
|
||||||
|
|
||||||
|
def geozone?
|
||||||
|
participants.where(geozone: geozones).any?
|
||||||
end
|
end
|
||||||
|
|
||||||
def total_male_participants
|
def total_male_participants
|
||||||
@@ -66,6 +87,14 @@ module Statisticable
|
|||||||
|
|
||||||
private
|
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 total_participants_with_gender
|
def total_participants_with_gender
|
||||||
participants.where.not(gender: nil).distinct.count
|
participants.where.not(gender: nil).distinct.count
|
||||||
end
|
end
|
||||||
@@ -115,10 +144,28 @@ module Statisticable
|
|||||||
|
|
||||||
class_methods do
|
class_methods do
|
||||||
def stats_methods
|
def stats_methods
|
||||||
%i[total_participants
|
base_stats_methods + gender_methods + age_methods + geozone_methods
|
||||||
total_male_participants total_female_participants total_unknown_gender_or_age
|
end
|
||||||
male_percentage female_percentage
|
|
||||||
participants_by_age participants_by_geozone]
|
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 total_unknown_gender_or_age
|
||||||
|
male_percentage female_percentage]
|
||||||
|
end
|
||||||
|
|
||||||
|
def age_methods
|
||||||
|
[:participants_by_age]
|
||||||
|
end
|
||||||
|
|
||||||
|
def geozone_methods
|
||||||
|
[:participants_by_geozone]
|
||||||
end
|
end
|
||||||
|
|
||||||
def stats_cache(*method_names)
|
def stats_cache(*method_names)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
|
|
||||||
<div class="row margin">
|
<div class="row margin">
|
||||||
<div class="small-12 medium-3 column sidebar">
|
<div class="small-12 medium-3 column sidebar">
|
||||||
<%= render "shared/stats/links" %>
|
<%= render "shared/stats/links", stats: @stats %>
|
||||||
|
|
||||||
<p><strong><%= link_to t("stats.advanced"), "#advanced_statistics" %></strong></p>
|
<p><strong><%= link_to t("stats.advanced"), "#advanced_statistics" %></strong></p>
|
||||||
<ul class="menu vertical">
|
<ul class="menu vertical">
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<div class="row margin">
|
<div class="row margin">
|
||||||
<div class="small-12 medium-3 column sidebar">
|
<div class="small-12 medium-3 column sidebar">
|
||||||
<%= render "shared/stats/links" %>
|
<%= render "shared/stats/links", stats: @stats %>
|
||||||
|
|
||||||
<p><strong><%= link_to t("stats.advanced"), "#advanced_statistics" %></strong></p>
|
<p><strong><%= link_to t("stats.advanced"), "#advanced_statistics" %></strong></p>
|
||||||
<ul class="menu vertical">
|
<ul class="menu vertical">
|
||||||
|
|||||||
28
app/views/shared/stats/_age.html.erb
Normal file
28
app/views/shared/stats/_age.html.erb
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<div id="participants_by_age" class="stats-group">
|
||||||
|
<h4><%= t("stats.by_age") %></h4>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="small-4"><%= t("stats.age") %></th>
|
||||||
|
<th><%= t("stats.total") %></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
<% stats.participants_by_age.values.each do |group| %>
|
||||||
|
<tr>
|
||||||
|
<td><%= group[:range] %></td>
|
||||||
|
<td>
|
||||||
|
<strong>
|
||||||
|
<%= "#{group[:count]} (#{number_to_stats_percentage(group[:percentage])})" %>
|
||||||
|
</strong>
|
||||||
|
<div class="progress" tabindex="0">
|
||||||
|
<span class="progress-meter" style="width: <%= group[:percentage] %>%"></span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
15
app/views/shared/stats/_gender.html.erb
Normal file
15
app/views/shared/stats/_gender.html.erb
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<div id="participants_by_gender" class="stats-group">
|
||||||
|
<h4><%= t("stats.by_gender") %></h4>
|
||||||
|
|
||||||
|
<%= number_with_info_tags(
|
||||||
|
stats.total_male_participants,
|
||||||
|
t("stats.men_percentage", percentage: number_to_stats_percentage(stats.male_percentage)),
|
||||||
|
html_class: "participants male"
|
||||||
|
) %>
|
||||||
|
|
||||||
|
<%= number_with_info_tags(
|
||||||
|
stats.total_female_participants,
|
||||||
|
t("stats.women_percentage", percentage: number_to_stats_percentage(stats.female_percentage)),
|
||||||
|
html_class: "participants female"
|
||||||
|
) %>
|
||||||
|
</div>
|
||||||
23
app/views/shared/stats/_geozone.html.erb
Normal file
23
app/views/shared/stats/_geozone.html.erb
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<div id="participants_by_geozone" class="stats-group">
|
||||||
|
<h4><%= t("stats.by_geozone") %></h4>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><%= t("stats.geozone") %></th>
|
||||||
|
<th><%= t("stats.total") %></th>
|
||||||
|
<th><%= t("stats.geozone_participation") %></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
<% stats.participants_by_geozone.each do |geozone, participants| %>
|
||||||
|
<tr>
|
||||||
|
<td><%= geozone %></td>
|
||||||
|
<td><%= "#{participants[:total][:count]} (#{number_to_stats_percentage(participants[:total][:percentage])})" %></td>
|
||||||
|
<td><%= number_to_stats_percentage(participants[:percentage]) %></td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
@@ -3,13 +3,10 @@
|
|||||||
<li>
|
<li>
|
||||||
<%= link_to t("stats.total_participants"), "#total_participants" %>
|
<%= link_to t("stats.total_participants"), "#total_participants" %>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<%= link_to t("stats.by_gender"), "#participants_by_gender" %>
|
<% stats.participations.each do |stat| %>
|
||||||
</li>
|
<li>
|
||||||
<li>
|
<%= link_to t("stats.by_#{stat}"), "#participants_by_#{stat}" %>
|
||||||
<%= link_to t("stats.by_age"), "#participants_by_age" %>
|
</li>
|
||||||
</li>
|
<% end %>
|
||||||
<li>
|
|
||||||
<%= link_to t("stats.by_geozone"), "#participants_by_geozone" %>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -11,73 +11,7 @@
|
|||||||
) %>
|
) %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="participants_by_gender" class="stats-group">
|
<% stats.participations.each do |participation| %>
|
||||||
<h4><%= t("stats.by_gender") %></h4>
|
<%= render "shared/stats/#{participation}", stats: stats %>
|
||||||
|
<% end %>
|
||||||
<%= number_with_info_tags(
|
|
||||||
stats.total_male_participants,
|
|
||||||
t("stats.men_percentage", percentage: number_to_stats_percentage(stats.male_percentage)),
|
|
||||||
html_class: "participants male"
|
|
||||||
) %>
|
|
||||||
|
|
||||||
<%= number_with_info_tags(
|
|
||||||
stats.total_female_participants,
|
|
||||||
t("stats.women_percentage", percentage: number_to_stats_percentage(stats.female_percentage)),
|
|
||||||
html_class: "participants female"
|
|
||||||
) %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="participants_by_age" class="stats-group">
|
|
||||||
<h4><%= t("stats.by_age") %></h4>
|
|
||||||
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class="small-4"><%= t("stats.age") %></th>
|
|
||||||
<th><%= t("stats.total") %></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
<% stats.participants_by_age.values.each do |group| %>
|
|
||||||
<tr>
|
|
||||||
<td><%= group[:range] %></td>
|
|
||||||
<td>
|
|
||||||
<strong>
|
|
||||||
<%= "#{group[:count]} (#{number_to_stats_percentage(group[:percentage])})" %>
|
|
||||||
</strong>
|
|
||||||
<div class="progress" tabindex="0">
|
|
||||||
<span class="progress-meter" style="width: <%= group[:percentage] %>%">
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<% end %>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="participants_by_geozone" class="stats-group">
|
|
||||||
<h4><%= t("stats.by_geozone") %></h4>
|
|
||||||
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th><%= t("stats.geozone") %></th>
|
|
||||||
<th><%= t("stats.total") %></th>
|
|
||||||
<th><%= t("stats.geozone_participation") %></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
<% stats.participants_by_geozone.each do |geozone, participants| %>
|
|
||||||
<tr>
|
|
||||||
<td><%= geozone %></td>
|
|
||||||
<td><%= "#{participants[:total][:count]} (#{number_to_stats_percentage(participants[:total][:percentage])})" %></td>
|
|
||||||
<td><%= number_to_stats_percentage(participants[:percentage]) %></td>
|
|
||||||
</tr>
|
|
||||||
<% end %>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
148
spec/models/statisticable_spec.rb
Normal file
148
spec/models/statisticable_spec.rb
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
require "rails_helper"
|
||||||
|
|
||||||
|
describe Statisticable do
|
||||||
|
class DummyStats
|
||||||
|
include Statisticable
|
||||||
|
|
||||||
|
def participants
|
||||||
|
User.all
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:stats) { DummyStats.new(nil) }
|
||||||
|
|
||||||
|
describe "#gender?" do
|
||||||
|
context "No participants" do
|
||||||
|
it "is false" do
|
||||||
|
expect(stats.gender?).to be false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "All participants have no defined gender" do
|
||||||
|
before { create(:user, gender: nil) }
|
||||||
|
|
||||||
|
it "is false" do
|
||||||
|
expect(stats.gender?).to be false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "There's a male participant" do
|
||||||
|
before { create(:user, gender: "male") }
|
||||||
|
|
||||||
|
it "is true" do
|
||||||
|
expect(stats.gender?).to be true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "There's a female participant" do
|
||||||
|
before { create(:user, gender: "female") }
|
||||||
|
|
||||||
|
it "is true" do
|
||||||
|
expect(stats.gender?).to be true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#age?" do
|
||||||
|
context "No participants" do
|
||||||
|
it "is false" do
|
||||||
|
expect(stats.age?).to be false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "All participants have no defined age" do
|
||||||
|
before { create(:user, date_of_birth: nil) }
|
||||||
|
|
||||||
|
it "is false" do
|
||||||
|
expect(stats.age?).to be false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "All participants have impossible ages" do
|
||||||
|
before do
|
||||||
|
create(:user, date_of_birth: 3.seconds.ago)
|
||||||
|
create(:user, date_of_birth: 3000.years.ago)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is false" do
|
||||||
|
expect(stats.age?).to be false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "There's a participant with a defined age" do
|
||||||
|
before { create(:user, date_of_birth: 30.years.ago) }
|
||||||
|
|
||||||
|
it "is true" do
|
||||||
|
expect(stats.age?).to be true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#geozone?" do
|
||||||
|
context "No participants" do
|
||||||
|
it "is false" do
|
||||||
|
expect(stats.geozone?).to be false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "All participants have no defined geozone" do
|
||||||
|
before { create(:user, geozone: nil) }
|
||||||
|
|
||||||
|
it "is false" do
|
||||||
|
expect(stats.geozone?).to be false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "There's a participant with a defined geozone" do
|
||||||
|
before { create(:user, geozone: create(:geozone)) }
|
||||||
|
|
||||||
|
it "is true" do
|
||||||
|
expect(stats.geozone?).to be true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#stats_methods" do
|
||||||
|
it "includes total participants" do
|
||||||
|
expect(stats.stats_methods).to include(:total_participants)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "no gender stats" do
|
||||||
|
before { allow(stats).to receive(:gender?).and_return(false) }
|
||||||
|
|
||||||
|
it "doesn't include gender methods" do
|
||||||
|
expect(stats.stats_methods).not_to include(:total_male_participants)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "no age stats" do
|
||||||
|
before { allow(stats).to receive(:age?).and_return(false) }
|
||||||
|
|
||||||
|
it "doesn't include age methods" do
|
||||||
|
expect(stats.stats_methods).not_to include(:participants_by_age)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "no geozone stats" do
|
||||||
|
before { allow(stats).to receive(:geozone?).and_return(false) }
|
||||||
|
|
||||||
|
it "doesn't include age methods" do
|
||||||
|
expect(stats.stats_methods).not_to include(:participants_by_geozone)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "all gender, age and geozone stats" do
|
||||||
|
before do
|
||||||
|
allow(stats).to receive(:gender?).and_return(true)
|
||||||
|
allow(stats).to receive(:age?).and_return(true)
|
||||||
|
allow(stats).to receive(:geozone?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "includes all stats methods" do
|
||||||
|
expect(stats.stats_methods).to include(:total_male_participants)
|
||||||
|
expect(stats.stats_methods).to include(:participants_by_age)
|
||||||
|
expect(stats.stats_methods).to include(:participants_by_geozone)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user