diff --git a/Gemfile b/Gemfile index 758a0be39..d33c750ed 100644 --- a/Gemfile +++ b/Gemfile @@ -136,6 +136,7 @@ end group :test do gem 'codeclimate-test-reporter', require: false + gem 'timecop' end group :travis do diff --git a/Gemfile.lock b/Gemfile.lock index e431570d4..952c4afe1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -482,6 +482,7 @@ GEM thread (0.2.2) thread_safe (0.3.6) tilt (2.0.7) + timecop (0.8.1) tins (1.14.0) trollop (1.16.2) tzinfo (1.2.3) @@ -589,12 +590,12 @@ DEPENDENCIES selenium-webdriver sidekiq sparkpost_rails + timecop uglifier (~> 2.7.2) unicorn webrat will_paginate (~> 3.0) - RUBY VERSION ruby 2.3.4p301 diff --git a/app/models/planting.rb b/app/models/planting.rb index 21b655d6a..082f2348a 100644 --- a/app/models/planting.rb +++ b/app/models/planting.rb @@ -91,59 +91,27 @@ class Planting < ActiveRecord::Base photos.first end - def planted?(current_date = Date.current) - planted_at.present? && current_date.to_date >= planted_at + def planted? + planted_at.present? && planted_at < Date.current end def days_until_finished - return 0 if finished? - days = (finished_at - Date.current).to_i - days.positive? ? days : 0 + PlantingPredictions.new(self).days_until_finished end def days_until_mature - days = ((planted_at + days_before_maturity) - Date.current).to_i - days.positive? ? days : 0 + PlantingPredictions.new(self).days_until_mature end - def percentage_grown(current_date = Date.current) - return nil unless days_before_maturity && planted?(current_date) - - days = (current_date.to_date - planted_at.to_date).to_i - - return 0 if current_date < planted_at - return 100 if days > days_before_maturity - percent = (days / days_before_maturity * 100).to_i - - if percent >= 100 - percent = 100 - end - - percent + def percentage_grown + PlantingPredictions.new(self).percentage_grown end def start_to_finish_diff - (finished_at - planted_at).to_i if finished_at && planted_at - end - - def self.mean_days_until_maturity(plantings) - ## Given a set of finished plantings, calculate the average/mean time from start to finish - differences = plantings.collect(&:start_to_finish_diff) - differences.compact.sum / differences.compact.size unless differences.compact.empty? + PlantingPredictions.new(self).start_to_finish_diff end def calc_and_set_days_before_maturity - # calculate the number of days, from planted_at, until maturity - if planted_at && finished_at - self.days_before_maturity = start_to_finish_diff - else - self.days_before_maturity = Planting.mean_days_until_maturity other_finished_plantings_same_crop - end - end - - private - - def other_finished_plantings_same_crop - Planting.where(crop_id: crop).where.not(id: id).where.not(finished_at: nil) + PlantingPredictions.new(self).calc_and_set_days_before_maturity end end diff --git a/app/services/planting_predictions.rb b/app/services/planting_predictions.rb new file mode 100644 index 000000000..f6291998b --- /dev/null +++ b/app/services/planting_predictions.rb @@ -0,0 +1,59 @@ +class PlantingPredictions + def initialize(planting) + @planting = planting + end + + def days_until_finished + return 0 if @planting.finished? + days = (@planting.finished_at - Date.current).to_i + days.positive? ? days : 0 + end + + def days_until_mature + days = ((@planting.planted_at + @planting.days_before_maturity) - Date.current).to_i + days.positive? ? days : 0 + end + + def percentage_grown + current_date = Date.current + return nil unless @planting.days_before_maturity && @planting.planted? + + days = (current_date.to_date - @planting.planted_at.to_date).to_i + + return 0 if current_date < @planting.planted_at + return 100 if days > @planting.days_before_maturity + percent = (days / @planting.days_before_maturity * 100).to_i + + percent = 100 if percent >= 100 + + percent + end + + def start_to_finish_diff + (@planting.finished_at - @planting.planted_at).to_i if @planting.finished_at && @planting.planted_at + end + + def calc_and_set_days_before_maturity + # calculate the number of days, from planted_at, until maturity + if @planting.planted_at && @planting.finished_at + @planting.days_before_maturity = start_to_finish_diff + else + plantings = other_finished_plantings_same_crop + @planting.days_before_maturity = PlantingPredictions.mean_days_until_maturity(plantings) + end + end + + def self.mean_days_until_maturity(plantings) + ## Given a set of finished plantings, calculate the average/mean time from start to finish + differences = plantings.collect(&:start_to_finish_diff) + differences.compact.sum / differences.compact.size unless differences.compact.empty? + end + + private + + def other_finished_plantings_same_crop + Planting.where(crop_id: @planting.crop.id) + .where.not(id: @planting.id) + .where.not(finished_at: nil) + end +end diff --git a/spec/models/planting_spec.rb b/spec/models/planting_spec.rb index aee1a3463..29f02629f 100644 --- a/spec/models/planting_spec.rb +++ b/spec/models/planting_spec.rb @@ -20,9 +20,13 @@ describe Planting do 8.times { FactoryGirl.create :planting, crop: crop, finished_at: nil } end let!(:planting_with_diff_crop) { FactoryGirl.create :planting, planted_at: 10.days.ago, finished_at: 2.days.ago } - it { expect(planting.send(:other_finished_plantings_same_crop).size).to eq(8) } - it { expect(planting.send(:other_finished_plantings_same_crop)).not_to include(planting) } - it { expect(planting.send(:other_finished_plantings_same_crop)).not_to include(planting_with_diff_crop) } + let(:planting_predictions) { PlantingPredictions.new(planting) } + it { expect(planting_predictions.send(:other_finished_plantings_same_crop).size).to eq(8) } + it { expect(planting_predictions.send(:other_finished_plantings_same_crop)).not_to include(planting) } + it do + expect(planting_predictions.send(:other_finished_plantings_same_crop)) + .not_to include(planting_with_diff_crop) + end end describe 'mean_days_until_maturity' do @@ -30,7 +34,7 @@ describe Planting do FactoryGirl.create_list(:planting, 10, crop: crop, planted_at: 12.days.ago, finished_at: 2.days.ago) end it { expect(plantings.size).to eq(10) } - it { expect(Planting.mean_days_until_maturity(plantings)).to eq(10) } + it { expect(PlantingPredictions.mean_days_until_maturity(plantings)).to eq(10) } end describe 'saving planting calculates days_before_maturity' do @@ -88,23 +92,22 @@ describe Planting do it 'should not be more than 100%' do @planting = FactoryGirl.build(:planting, days_before_maturity: 1, planted_at: 1.day.ago) - now_later_than_planting = 2.days.from_now - - @planting.percentage_grown(now_later_than_planting).should be 100 + Timecop.freeze(2.days.from_now) do + @planting.percentage_grown.should be 100 + end end it 'should not be less than 0%' do @planting = FactoryGirl.build(:planting, days_before_maturity: 1, planted_at: 1.day.ago) - now_earlier_than_planting = 2.days.ago - - @planting.percentage_grown(now_earlier_than_planting).should be nil + Timecop.freeze(2.days.ago) do + @planting.percentage_grown.should be nil + end end it 'should reflect the current growth' do @planting = FactoryGirl.build(:planting, days_before_maturity: 10, planted_at: 4.days.ago) - - expect(@planting.percentage_grown(Date.current)).to eq 40 + expect(@planting.percentage_grown).to eq 40 end it 'should not be calculated for unplanted plantings' do @@ -116,7 +119,6 @@ describe Planting do it 'should not be calculated for plantings with an unknown days before maturity' do @planting = FactoryGirl.build(:planting, days_before_maturity: nil) - @planting.percentage_grown.should be nil end end