diff --git a/app/models/concerns/statisticable.rb b/app/models/concerns/statisticable.rb index c3ee15139..826d7dc68 100644 --- a/app/models/concerns/statisticable.rb +++ b/app/models/concerns/statisticable.rb @@ -47,7 +47,23 @@ module Statisticable end def generate - stats_methods.each { |stat_name| send(stat_name) } + User.transaction do + create_participants_table + + begin + define_singleton_method :participants do + 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 @@ -77,6 +93,7 @@ module Statisticable def participants @participants ||= User.unscoped.where(id: participant_ids) end + alias_method :participants_from_original_table, :participants def total_male_participants participants.male.count @@ -151,6 +168,26 @@ module Statisticable 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 + ) + end + + def drop_participants_table + User.connection.drop_table(participants_table_name, if_exists: true, temporary: true) + 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 total_participants_with_gender @total_participants_with_gender ||= participants.where.not(gender: nil).distinct.count end diff --git a/spec/models/statisticable_spec.rb b/spec/models/statisticable_spec.rb index 9768b39ee..f1817fbbd 100644 --- a/spec/models/statisticable_spec.rb +++ b/spec/models/statisticable_spec.rb @@ -8,6 +8,11 @@ describe Statisticable do def participants User.all end + alias_method :participants_from_original_table, :participants + + def total_participants + User.count + end def participation_date Time.current @@ -17,7 +22,7 @@ describe Statisticable do stub_const("DummyStats", dummy_stats) end - let(:stats) { DummyStats.new(nil) } + let(:stats) { DummyStats.new(double(id: 1, class: double(table_name: ""))) } describe "#gender?" do context "No participants" do @@ -194,4 +199,26 @@ describe Statisticable do end end end + + describe "#generate" do + it "drops the temporary table after finishing" do + stats.generate + + expect { stats.send(:participants_class).first }.to raise_exception(ActiveRecord::StatementInvalid) + end + + it "can be executed twice without errors" do + stats.generate + + expect { stats.generate }.not_to raise_exception + end + + it "can be executed twice in parallel since it uses a transaction" do + other_stats = DummyStats.new(stats.resource) + + [stats, other_stats].map do |stat| + Thread.new { stat.generate } + end.each(&:join) + end + end end