From 62336f94cd1081921d645c1a955920141e194659 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Sat, 2 Mar 2013 12:04:45 -0500 Subject: [PATCH] Move time to complete stats into separate class This separates out the calculations from the queries so we can get decent tests around them. --- app/models/stats/actions.rb | 52 ++----------------- app/models/stats/time_to_complete.rb | 59 ++++++++++++++++++++++ app/views/stats/_actions.html.erb | 6 +-- test/unit/time_to_complete_test.rb | 75 ++++++++++++++++++++++++++++ 4 files changed, 142 insertions(+), 50 deletions(-) create mode 100644 app/models/stats/time_to_complete.rb create mode 100644 test/unit/time_to_complete_test.rb diff --git a/app/models/stats/actions.rb b/app/models/stats/actions.rb index 9383e49b..212aa374 100644 --- a/app/models/stats/actions.rb +++ b/app/models/stats/actions.rb @@ -8,23 +8,8 @@ module Stats @user = user end - def avg_ttc - @avg_ttc ||= (sum/count)/SECONDS_PER_DAY - end - - def max_ttc - @max_ttc ||= max/SECONDS_PER_DAY - end - - def min_ttc - @min_ttc ||= min/SECONDS_PER_DAY - end - - def min_ttc_sec - min_ttc_sec = arbitrary_day + min # convert to a datetime - @actions_min_ttc_sec = (min_ttc_sec).strftime("%H:%M:%S") - @actions_min_ttc_sec = (min / SECONDS_PER_DAY).round.to_s + " days " + @actions_min_ttc_sec if min > SECONDS_PER_DAY - @actions_min_ttc_sec + def ttc + @ttc ||= TimeToComplete.new(completed) end def done_last30days @@ -69,10 +54,6 @@ module Stats private - def arbitrary_day - @arbitrary_day ||= Time.utc(2000,1,1,0,0) - end - def one_year @one_year ||= 12.months.ago.beginning_of_day end @@ -81,32 +62,6 @@ module Stats @one_month ||= 1.month.ago.beginning_of_day end - def completed - @completed ||= user.todos.completed.select("completed_at, created_at") - end - - def durations - @durations ||= completed.map do |r| - (r.completed_at - r.created_at) - end - end - - def sum - @sum ||= durations.inject(0) {|sum, d| sum + d} - end - - def min - @min ||= durations.min || 0 - end - - def max - @max ||= durations.max || 0 - end - - def count - completed.empty? ? 1 : completed.size - end - def new_since(cutoff) user.todos.created_after(cutoff).count end @@ -115,5 +70,8 @@ module Stats user.todos.completed.completed_after(cutoff).count end + def completed + @completed ||= user.todos.completed.select("completed_at, created_at") + end end end diff --git a/app/models/stats/time_to_complete.rb b/app/models/stats/time_to_complete.rb new file mode 100644 index 00000000..3407e773 --- /dev/null +++ b/app/models/stats/time_to_complete.rb @@ -0,0 +1,59 @@ +module Stats + class TimeToComplete + + SECONDS_PER_DAY = 86400; + + attr_reader :actions + def initialize(actions) + @actions = actions + end + + def avg + @avg ||= (sum / count) / SECONDS_PER_DAY + end + + def max + @max ||= max_in_seconds / SECONDS_PER_DAY + end + + def min + @min ||= min_in_seconds / SECONDS_PER_DAY + end + + def min_sec + min_sec = arbitrary_day + min_in_seconds # convert to a datetime + @min_sec = min_sec.strftime("%H:%M:%S") + @min_sec = min.round.to_s + " days " + @min_sec if min >= 1 + @min_sec + end + + private + + def min_in_seconds + @min_in_seconds ||= durations.min || 0 + end + + def max_in_seconds + @max_in_seconds ||= durations.max || 0 + end + + def count + actions.empty? ? 1 : actions.size + end + + def durations + @durations ||= actions.map do |r| + (r.completed_at - r.created_at) + end + end + + def sum + @sum ||= durations.inject(0) {|sum, d| sum + d} + end + + def arbitrary_day + @arbitrary_day ||= Time.utc(2000, 1, 1, 0, 0) + end + + end +end diff --git a/app/views/stats/_actions.html.erb b/app/views/stats/_actions.html.erb index 6b467533..f59431ca 100755 --- a/app/views/stats/_actions.html.erb +++ b/app/views/stats/_actions.html.erb @@ -1,6 +1,6 @@ -

<%= t('stats.actions_avg_completion_time', :count => (actions.avg_ttc*10).round/10.0) %> -<%= t('stats.actions_min_max_completion_days', :max => (actions.max_ttc*10).round/10.0, :min => (actions.min_ttc*10).round/10.0) %> -<%= t('stats.actions_min_completion_time', :time => actions.min_ttc_sec) %>

+

<%= t('stats.actions_avg_completion_time', :count => (actions.ttc.avg*10).round/10.0) %> +<%= t('stats.actions_min_max_completion_days', :max => (actions.ttc.max*10).round/10.0, :min => (actions.ttc.min*10).round/10.0) %> +<%= t('stats.actions_min_completion_time', :time => actions.ttc.min_sec) %>

<%= t('stats.actions_actions_avg_created_30days', :count => (actions.created_last30days*10.0/30.0).round/10.0 )%> <%= t('stats.actions_avg_completed_30days', :count => (actions.done_last30days*10.0/30.0).round/10.0 )%> diff --git a/test/unit/time_to_complete_test.rb b/test/unit/time_to_complete_test.rb new file mode 100644 index 00000000..f1cce5d5 --- /dev/null +++ b/test/unit/time_to_complete_test.rb @@ -0,0 +1,75 @@ +require 'date' +require 'time' +require './test/minimal_test_helper' +require 'app/models/stats/time_to_complete' + +FakeTask = Struct.new(:created_at, :completed_at) + +class TimeToCompleteTest < Test::Unit::TestCase + + def now + @now ||= Time.utc(2013, 1, 2, 3, 4, 5) + end + + def day0 + @day0 ||= Time.utc(2013, 1, 2, 0, 0, 0) + end + + def day2 + @day2 ||= Time.utc(2012, 12, 31, 0, 0, 0) + end + + def day4 + @day4 ||= Time.utc(2012, 12, 29, 0, 0, 0) + end + + def day6 + @day6 ||= Time.utc(2012, 12, 27, 0, 0, 0) + end + + def fake_tasks + @fake_tasks ||= [ + FakeTask.new(day2, now), + FakeTask.new(day4, now), + FakeTask.new(day6, now) + ] + end + + def test_empty_minimum + assert_equal 0, Stats::TimeToComplete.new([]).min + end + + def test_empty_maximum + assert_equal 0, Stats::TimeToComplete.new([]).max + end + + def test_empty_avg + assert_equal 0, Stats::TimeToComplete.new([]).avg + end + + def test_empty_min_sec + assert_equal '00:00:00', Stats::TimeToComplete.new([]).min_sec + end + + def test_minimum + assert_equal 2.127835648148148, Stats::TimeToComplete.new(fake_tasks).min + end + + def test_maximum + assert_equal 6.127835648148148, Stats::TimeToComplete.new(fake_tasks).max + end + + def test_avg + assert_equal 4.127835648148148, Stats::TimeToComplete.new(fake_tasks).avg + end + + def test_min_sec + assert_equal '2 days 03:04:05', Stats::TimeToComplete.new(fake_tasks).min_sec + end + + def test_min_sec_with_less_than_a_day + task = FakeTask.new(day0, now) + assert_equal '03:04:05', Stats::TimeToComplete.new([task]).min_sec + end +end +