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
|
||||
extend ActiveSupport::Concern
|
||||
PARTICIPATIONS = %w[gender age geozone]
|
||||
|
||||
included do
|
||||
attr_reader :resource
|
||||
@@ -9,7 +10,27 @@ module Statisticable
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
def total_male_participants
|
||||
@@ -66,6 +87,14 @@ module Statisticable
|
||||
|
||||
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
|
||||
participants.where.not(gender: nil).distinct.count
|
||||
end
|
||||
@@ -115,10 +144,28 @@ module Statisticable
|
||||
|
||||
class_methods do
|
||||
def stats_methods
|
||||
%i[total_participants
|
||||
total_male_participants total_female_participants total_unknown_gender_or_age
|
||||
male_percentage female_percentage
|
||||
participants_by_age participants_by_geozone]
|
||||
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 total_unknown_gender_or_age
|
||||
male_percentage female_percentage]
|
||||
end
|
||||
|
||||
def age_methods
|
||||
[:participants_by_age]
|
||||
end
|
||||
|
||||
def geozone_methods
|
||||
[:participants_by_geozone]
|
||||
end
|
||||
|
||||
def stats_cache(*method_names)
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
|
||||
<div class="row margin">
|
||||
<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>
|
||||
<ul class="menu vertical">
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
<div class="row margin">
|
||||
<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>
|
||||
<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>
|
||||
<%= link_to t("stats.total_participants"), "#total_participants" %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to t("stats.by_gender"), "#participants_by_gender" %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to t("stats.by_age"), "#participants_by_age" %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to t("stats.by_geozone"), "#participants_by_geozone" %>
|
||||
</li>
|
||||
|
||||
<% stats.participations.each do |stat| %>
|
||||
<li>
|
||||
<%= link_to t("stats.by_#{stat}"), "#participants_by_#{stat}" %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
||||
@@ -11,73 +11,7 @@
|
||||
) %>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<% stats.participations.each do |participation| %>
|
||||
<%= render "shared/stats/#{participation}", stats: stats %>
|
||||
<% end %>
|
||||
</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