From a3e0a7b52fff3cfa9a8669aa550a30cea057a744 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Tue, 13 Oct 2015 10:56:11 -0500 Subject: [PATCH 01/51] Draft changes (temporary save). --- app/controllers/traits_controller.rb | 1 + app/helpers/application_helper.rb | 5 ++ app/models/trait.rb | 88 ++++++++++++++++++++++++++++ app/views/traits/new.html.erb | 13 ++-- 4 files changed, 101 insertions(+), 6 deletions(-) diff --git a/app/controllers/traits_controller.rb b/app/controllers/traits_controller.rb index 6b873e369..23d7ef5de 100644 --- a/app/controllers/traits_controller.rb +++ b/app/controllers/traits_controller.rb @@ -126,6 +126,7 @@ def new @citation = Citation.find_by_id(session["citation"]) @new_covariates = [Covariate.new] + @utc_offsets = utc_offsets respond_to do |format| format.html # new.html.erb format.xml { render :xml => @trait } diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index c34dbe52e..611631ee0 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -42,6 +42,11 @@ def treatment_check(ty) $statname_list = ["","SD", "SE", "MSE", "95%CI", "LSD", "MSD"] + offsets_query = "SELECT DISTINCT utc_offset FROM pg_timezone_names ORDER BY utc_offset" + $utc_offsets = ActiveRecord::Base.connection.execute(offsets_query).to_a.collect do |item| + item['utc_offset'] + end + # Simple Search def sortable(column, title = nil) diff --git a/app/models/trait.rb b/app/models/trait.rb index 97d896271..495f19469 100644 --- a/app/models/trait.rb +++ b/app/models/trait.rb @@ -2,6 +2,10 @@ class Trait < ActiveRecord::Base # Passed from controller for validation of ability attr_accessor :current_user + attr_accessor :timezone_offset + + before_save :apply_offset + include Overrides extend DataAccess # provides all_limited @@ -23,6 +27,43 @@ class Trait < ActiveRecord::Base belongs_to :entity belongs_to :ebi_method, :class_name => 'Methods', :foreign_key => 'method_id' + + + # VALIDATION + + ## Validation methods + + def consistent_date_and_time_fields + case dateloc + when 9 + if !(date_year.nil? && date_month.nil? && date_day.nil?) + errors.add(:base, "When dateloc = 9, year, month and day should be blank") + else + date_year = 2000 + date_month = 1 + date_day = 1 + end + end + + Rails.logger.info("date_year = #{date_year} and has type #{date_year.class}") + + begin + t = DateTime.new(date_year, date_month, date_day, time_hour, time_minute, 0, timezone_offset) + Rails.logger.info("Time is #{t}") + rescue => e + e.backtrace.each do |m| + Rails.logger.info(m) + end + errors.add(:base, e.message) + end + end + + ## Validation callback methods + + ## Validation callbacks + + ## Validations + validates_presence_of :mean, :access_level validates_numericality_of :mean validates_presence_of :variable @@ -33,6 +74,8 @@ class Trait < ActiveRecord::Base validates_format_of :date_day, :with => /\A\d{1,2}\z/, :allow_blank => true validates_format_of :time_hour, :with => /\A\d{1,2}\z/, :allow_blank => true validates_format_of :time_minute, :with => /\A\d{1,2}\z/, :allow_blank => true + validates_format_of :timezone_offset, :with => /\A *[+-]?([01]?[0-9]|2[0-3]):(00|15|30|45) *\z/ + validate :consistent_date_and_time_fields validate :can_change_checked validate :mean_in_range @@ -176,4 +219,49 @@ def self.search_columns return ["traits.id"] end + + + private + + def apply_offset +=begin + sign, hour, minutes = /\A *([+-]?)(\d\d?):(\d\d) *\z/.match(self.timezone_offset).captures + + Rails.logger.info("self.timezone_offset: #{self.timezone_offset}") + Rails.logger.info("sign, hour, minutes: #{sign}, #{hour}, #{minutes}") + + if sign == "-" + sign = -1 + else + sign = 1 + end + + hour = hour.to_i + minutes = minutes.to_i + + # convert time to UTC : UTC time = local time - local time offset + Rails.logger.info("time_hour: #{time_hour}") + Rails.logger.info("sign * hour: #{sign * hour}") + Rails.logger.info("time_hour - sign * hour: #{time_hour - sign * hour}") + self.time_hour -= sign * hour + self.time_minute -= sign * minutes + + self.notes = "blah" + + Rails.logger.info("self = #{self}") +=end + + +=begin + t_utc = DateTime.new(date_year, date_month, date_day, time_hour, time_minute, 0, self.timezone_offset).utc.to_s(:db) + Rails.logger.info("t_utc = #{t_utc}") + #self.date_year, self.date_month, self.date_day, self.time_hour, self.time_minute = /\A(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):\d\d\z/.match(t_utc).captures + + self.date = t_utc + +=end + + end + + end diff --git a/app/views/traits/new.html.erb b/app/views/traits/new.html.erb index 3d606a3e9..e04edc8e9 100644 --- a/app/views/traits/new.html.erb +++ b/app/views/traits/new.html.erb @@ -72,17 +72,18 @@ <%= f.select :date_day, [''] + (1..31).to_a %> -
- <%= f.label :dateloc, "Date Level of Confidence" %> - <%= f.select :dateloc, options_for_select($dateloc_drop.sort, f.object.dateloc || $dateloc_drop_default) %> -
Time - <%= f.select :time_hour, [''] + (0..23).to_a %> - <%= f.select :time_minute, [''] + (0..59).to_a %> + <%= f.select :time_hour, [''] + (0..23).to_a.collect { |i| '% 2i' % i } %> + <%= f.select :time_minute, [''] + (0..59).to_a.collect { |i| '%02i' % i } %> + <%= f.select :timezone_offset, [''] + $utc_offsets %>
+
+ <%= f.label :dateloc, "Date Level of Confidence" %> + <%= f.select :dateloc, options_for_select($dateloc_drop.sort, f.object.dateloc || $dateloc_drop_default) %> +
<%= f.label :timeloc, "Time Level of Confidence" %> <%= f.select :timeloc, options_for_select($timeloc_drop.sort, f.object.timeloc || $timeloc_drop_default),{},:class => "input-full" %> From f72f4c22a2fbbbc18e12bf261de07beb0c592b30 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Tue, 13 Oct 2015 12:42:49 -0500 Subject: [PATCH 02/51] tmp save --- app/models/trait.rb | 83 ++++++++++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 34 deletions(-) diff --git a/app/models/trait.rb b/app/models/trait.rb index 495f19469..f69f50bd3 100644 --- a/app/models/trait.rb +++ b/app/models/trait.rb @@ -2,7 +2,24 @@ class Trait < ActiveRecord::Base # Passed from controller for validation of ability attr_accessor :current_user - attr_accessor :timezone_offset + attr_accessor :timezone_offset, :d_year, :d_month, :d_day, :t_hour, :t_minute + + def d_year + date.year + end + def d_month + date.month + end + def d_day + date.day + end + def t_hour + Rails.logger.info("In t_hour, date = #{self.date}") + date.hour + end + def t_minute + date.min + end before_save :apply_offset @@ -34,21 +51,20 @@ class Trait < ActiveRecord::Base ## Validation methods def consistent_date_and_time_fields + + Rails.logger.info("Traits is now #{self.attributes}") + case dateloc when 9 - if !(date_year.nil? && date_month.nil? && date_day.nil?) + if !(d_year.nil? && d_month.nil? && d_day.nil?) errors.add(:base, "When dateloc = 9, year, month and day should be blank") - else - date_year = 2000 - date_month = 1 - date_day = 1 end end - Rails.logger.info("date_year = #{date_year} and has type #{date_year.class}") + Rails.logger.info("d_year = #{d_year} and has type #{d_year.class}") begin - t = DateTime.new(date_year, date_month, date_day, time_hour, time_minute, 0, timezone_offset) + t = DateTime.new(d_year.to_i, d_month.to_i, d_day.to_i, t_hour.to_i, t_minute.to_i, 0, timezone_offset) Rails.logger.info("Time is #{t}") rescue => e e.backtrace.each do |m| @@ -64,20 +80,22 @@ def consistent_date_and_time_fields ## Validations +=begin validates_presence_of :mean, :access_level validates_numericality_of :mean validates_presence_of :variable validates_inclusion_of :access_level, in: 1..4, message: "You must select an access level" validates_presence_of :statname, :if => Proc.new { |trait| !trait.stat.blank? } - validates_format_of :date_year, :with => /\A(\d{2}|\d{4})\z/, :allow_blank => true - validates_format_of :date_month, :with => /\A\d{1,2}\z/, :allow_blank => true - validates_format_of :date_day, :with => /\A\d{1,2}\z/, :allow_blank => true - validates_format_of :time_hour, :with => /\A\d{1,2}\z/, :allow_blank => true - validates_format_of :time_minute, :with => /\A\d{1,2}\z/, :allow_blank => true - validates_format_of :timezone_offset, :with => /\A *[+-]?([01]?[0-9]|2[0-3]):(00|15|30|45) *\z/ + validates_format_of :d_year, :with => /\A(\d{2}|\d{4})\z/, :allow_blank => true + validates_format_of :d_month, :with => /\A\d{1,2}\z/, :allow_blank => true + validates_format_of :d_day, :with => /\A\d{1,2}\z/, :allow_blank => true + validates_format_of :t_hour, :with => /\A\d{1,2}\z/, :allow_blank => true + validates_format_of :t_minute, :with => /\A\d{1,2}\z/, :allow_blank => true + #validates_format_of :timezone_offset, :with => /\A *[+-]?([01]?[0-9]|2[0-3]):(00|15|30|45):00 *\z/ validate :consistent_date_and_time_fields validate :can_change_checked validate :mean_in_range +=end # Only allow admins/managers to change traits marked as failed. def can_change_checked @@ -116,12 +134,12 @@ def mean_in_range cultivar_id treatment_id entity_id - date_year - date_month - date_day + d_year + d_month + d_day dateloc - time_hour - time_minute + t_hour + t_minute timeloc mean n @@ -174,9 +192,9 @@ def mean_in_range def pretty_date date_string = "" - date_string += "#{date_year} " unless date_year.nil? - date_string += "#{Date::ABBR_MONTHNAMES[date_month]} " unless date_month.nil? - date_string += "#{date_day} " unless date_day.nil? + date_string += "#{d_year} " unless d_year.nil? + date_string += "#{Date::ABBR_MONTHNAMES[d_month]} " unless d_month.nil? + date_string += "#{d_day} " unless d_day.nil? end def format_statname @@ -197,8 +215,8 @@ def format_stat def pretty_time time_string = "" - time_string += "#{time_hour}" unless time_hour.nil? - time_string += ":#{time_minute}" unless time_hour.nil? or time_minute.nil? + time_string += "#{t_hour}" unless t_hour.nil? + time_string += ":#{t_minute}" unless t_hour.nil? or t_minute.nil? end def specie_treat_cultivar @@ -240,26 +258,23 @@ def apply_offset minutes = minutes.to_i # convert time to UTC : UTC time = local time - local time offset - Rails.logger.info("time_hour: #{time_hour}") + Rails.logger.info("t_hour: #{t_hour}") Rails.logger.info("sign * hour: #{sign * hour}") - Rails.logger.info("time_hour - sign * hour: #{time_hour - sign * hour}") - self.time_hour -= sign * hour - self.time_minute -= sign * minutes + Rails.logger.info("t_hour - sign * hour: #{t_hour - sign * hour}") + self.t_hour -= sign * hour + self.t_minute -= sign * minutes self.notes = "blah" Rails.logger.info("self = #{self}") =end - -=begin - t_utc = DateTime.new(date_year, date_month, date_day, time_hour, time_minute, 0, self.timezone_offset).utc.to_s(:db) + t_utc = DateTime.new(d_year, d_month, d_day, t_hour, t_minute, 0).utc Rails.logger.info("t_utc = #{t_utc}") - #self.date_year, self.date_month, self.date_day, self.time_hour, self.time_minute = /\A(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):\d\d\z/.match(t_utc).captures +# self.d_year, self.d_month, self.d_day, self.t_hour, self.t_minute = /\A(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):\d\d\z/.match(t_utc.to_s(:db)).captures - self.date = t_utc +# self.date = t_utc -=end end From c3edd691b596042d9896a5399f99a1529b161cf4 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Wed, 14 Oct 2015 10:18:49 -0500 Subject: [PATCH 03/51] tmp save --- app/controllers/traits_controller.rb | 1 - app/helpers/application_helper.rb | 8 ++- app/models/trait.rb | 100 ++++++++++++++++++--------- app/views/traits/edit.html.erb | 19 ++--- app/views/traits/new.html.erb | 10 +-- 5 files changed, 90 insertions(+), 48 deletions(-) diff --git a/app/controllers/traits_controller.rb b/app/controllers/traits_controller.rb index 23d7ef5de..6b873e369 100644 --- a/app/controllers/traits_controller.rb +++ b/app/controllers/traits_controller.rb @@ -126,7 +126,6 @@ def new @citation = Citation.find_by_id(session["citation"]) @new_covariates = [Covariate.new] - @utc_offsets = utc_offsets respond_to do |format| format.html # new.html.erb format.xml { render :xml => @trait } diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 611631ee0..4c260e296 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -42,9 +42,15 @@ def treatment_check(ty) $statname_list = ["","SD", "SE", "MSE", "95%CI", "LSD", "MSD"] + + # Query the database to get all possible timezone offsets in current use: offsets_query = "SELECT DISTINCT utc_offset FROM pg_timezone_names ORDER BY utc_offset" $utc_offsets = ActiveRecord::Base.connection.execute(offsets_query).to_a.collect do |item| - item['utc_offset'] + offset = item['utc_offset'] + if !offset.match(/\+|-/) + offset = "+#{offset}" + end + offset end diff --git a/app/models/trait.rb b/app/models/trait.rb index f69f50bd3..8d80457d1 100644 --- a/app/models/trait.rb +++ b/app/models/trait.rb @@ -5,20 +5,25 @@ class Trait < ActiveRecord::Base attr_accessor :timezone_offset, :d_year, :d_month, :d_day, :t_hour, :t_minute def d_year - date.year + Rails.logger.info("In method d_year, @d_year = #{@d_year}") + @d_year || (date.nil? ? '' : date.utc.year) end def d_month - date.month + @d_month || (date.nil? ? '' : date.utc.month) end def d_day - date.day + @d_day || (date.nil? ? '' : date.utc.day) end def t_hour - Rails.logger.info("In t_hour, date = #{self.date}") - date.hour + @t_hour || (date.nil? ? '' : date.utc.hour) end def t_minute - date.min + # use formatting to make sure value matches dropdown values: + @t_minute || (date.nil? ? '' : date.utc.min) + end + def timezone_offset + @timezone_offset.nil? ? "+00:00:00" : (@timezone_offset.match(/\+|-/) ? @timezone_offset : "+#{@timezone_offset}") + #Rails.logger.info("In t_hour, date.utc = #{self.date.utc} and date.utc.hour = #{date.utc.hour}") end before_save :apply_offset @@ -52,24 +57,34 @@ def t_minute def consistent_date_and_time_fields - Rails.logger.info("Traits is now #{self.attributes}") + # Require a month if there is a day: + if @d_month.blank? && !@d_day.blank? + errors.add(:base, "If you have a date day, you must specify the month.") + end - case dateloc - when 9 - if !(d_year.nil? && d_month.nil? && d_day.nil?) - errors.add(:base, "When dateloc = 9, year, month and day should be blank") - end + # Require an hour if minutes are specified: + if @t_hour.blank? && !@t_minute.blank? + errors.add(:base, "If you specify minutes, you must specify the hour.") end - Rails.logger.info("d_year = #{d_year} and has type #{d_year.class}") + # Require a timezone offset if the hour is specified: + if timezone_offset.blank? && !@t_hour.blank? + errors.add(:base, "If you specify the hour, you must specify a timezone offset.") + end + + if !timezone_offset.blank? && !site_id.blank? + # estimate time zone from site longitude + appx_offset = (Site.find site_id).lon / 15.0 + hour_offset = (timezone_offset.sub(/:.*/, '')).to_i + if(appx_offset - hour_offset).abs > 2 + errors.add(:base, "The UTC offset value you have selected seems inconsistent with the site location.") + end + end + begin - t = DateTime.new(d_year.to_i, d_month.to_i, d_day.to_i, t_hour.to_i, t_minute.to_i, 0, timezone_offset) - Rails.logger.info("Time is #{t}") + t = DateTime.new(@d_year.to_i, @d_month.to_i, @d_day.to_i, @t_hour.to_i, @t_minute.to_i, 0, timezone_offset) rescue => e - e.backtrace.each do |m| - Rails.logger.info(m) - end errors.add(:base, e.message) end end @@ -80,7 +95,6 @@ def consistent_date_and_time_fields ## Validations -=begin validates_presence_of :mean, :access_level validates_numericality_of :mean validates_presence_of :variable @@ -89,13 +103,13 @@ def consistent_date_and_time_fields validates_format_of :d_year, :with => /\A(\d{2}|\d{4})\z/, :allow_blank => true validates_format_of :d_month, :with => /\A\d{1,2}\z/, :allow_blank => true validates_format_of :d_day, :with => /\A\d{1,2}\z/, :allow_blank => true - validates_format_of :t_hour, :with => /\A\d{1,2}\z/, :allow_blank => true + #validates_format_of :t_hour, :with => /\A\d{1,2}\z/, :allow_blank => true validates_format_of :t_minute, :with => /\A\d{1,2}\z/, :allow_blank => true #validates_format_of :timezone_offset, :with => /\A *[+-]?([01]?[0-9]|2[0-3]):(00|15|30|45):00 *\z/ validate :consistent_date_and_time_fields validate :can_change_checked validate :mean_in_range -=end + # Only allow admins/managers to change traits marked as failed. def can_change_checked @@ -192,9 +206,12 @@ def mean_in_range def pretty_date date_string = "" - date_string += "#{d_year} " unless d_year.nil? + date_string += "#{d_year} " unless d_year == 9996 date_string += "#{Date::ABBR_MONTHNAMES[d_month]} " unless d_month.nil? date_string += "#{d_day} " unless d_day.nil? + date_string += " (year unspecified)" if d_year == 9996 + + date_string end def format_statname @@ -215,8 +232,8 @@ def format_stat def pretty_time time_string = "" - time_string += "#{t_hour}" unless t_hour.nil? - time_string += ":#{t_minute}" unless t_hour.nil? or t_minute.nil? + time_string += "#{"%02i" % t_hour}" unless t_hour.nil? + time_string += ":#{"%02i" % t_minute}" unless t_hour.nil? or t_minute.nil? end def specie_treat_cultivar @@ -242,6 +259,16 @@ def self.search_columns private def apply_offset + + + # Supply missing year if needed: + + if @d_year.blank? + @d_year = 9996 + self.dateloc = 95 + end + + =begin sign, hour, minutes = /\A *([+-]?)(\d\d?):(\d\d) *\z/.match(self.timezone_offset).captures @@ -258,22 +285,31 @@ def apply_offset minutes = minutes.to_i # convert time to UTC : UTC time = local time - local time offset - Rails.logger.info("t_hour: #{t_hour}") + Rails.logger.info("@t_hour: #{@t_hour}") Rails.logger.info("sign * hour: #{sign * hour}") - Rails.logger.info("t_hour - sign * hour: #{t_hour - sign * hour}") - self.t_hour -= sign * hour - self.t_minute -= sign * minutes + Rails.logger.info("@t_hour - sign * hour: #{@t_hour - sign * hour}") + self.@t_hour -= sign * hour + self.@t_minute -= sign * minutes self.notes = "blah" Rails.logger.info("self = #{self}") =end - - t_utc = DateTime.new(d_year, d_month, d_day, t_hour, t_minute, 0).utc + begin + t_utc = DateTime.new(@d_year.to_i, @d_month.to_i, @d_day.to_i, @t_hour.to_i, @t_minute.to_i, 0, timezone_offset).utc + rescue => e + Rails.logger.info("in apply offset, got this error: #{e.message}") + Rails.logger.info("values of @d_year, @d_month, @d_day, @t_hour, @t_minute are #{@d_year}, #{@d_month}, #{@d_day}, #{@t_hour}, #{@t_minute} with types #{@d_year.class}, #{@d_month.class}, #{@d_day.class}, #{@t_hour.class}, #{@t_minute.class}") + return false + end + Rails.logger.info("values of @d_year, @d_month, @d_day, @t_hour, @t_minute are #{@d_year}, #{@d_month}, #{@d_day}, #{@t_hour}, #{@t_minute} with types #{@d_year.class}, #{@d_month.class}, #{@d_day.class}, #{@t_hour.class}, #{@t_minute.class}") Rails.logger.info("t_utc = #{t_utc}") -# self.d_year, self.d_month, self.d_day, self.t_hour, self.t_minute = /\A(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):\d\d\z/.match(t_utc.to_s(:db)).captures -# self.date = t_utc + if t_utc.year == 9995 || t_utc.year == 9997 + t_utc.change(year: 9996) + end + + self.date = t_utc end diff --git a/app/views/traits/edit.html.erb b/app/views/traits/edit.html.erb index 9ce1846f0..867c2bd79 100644 --- a/app/views/traits/edit.html.erb +++ b/app/views/traits/edit.html.erb @@ -58,22 +58,23 @@
Date - <%= f.select :date_year, [''] + (1800..Time.now.year).to_a %> - <%= f.select :date_month, [''] + (1..12).to_a %> - <%= f.select :date_day, [''] + (1..31).to_a %> + <%= f.select :d_year, [''] + (1800..Time.now.year).to_a %> + <%= f.select :d_month, [''] + (1..12).to_a %> + <%= f.select :d_day, [''] + (1..31).to_a %>
-
- <%= f.label :dateloc, "Date Level of Confidence" %> - <%= f.select :dateloc, options_for_select($dateloc_drop.sort, f.object.dateloc || $dateloc_drop_default) %> -
Time - <%= f.select :time_hour, [''] + (0..23).to_a %> - <%= f.select :time_minute, [''] + (0..59).to_a %> + <%= f.select :t_hour, [''] + (0..23).to_a %> + <%= f.select :t_minute, [''] + (0..59).to_a.collect { |i| '%02i' % i } %> + <%= f.select :timezone_offset, [''] + $utc_offsets %>
+
+ <%= f.label :dateloc, "Date Level of Confidence" %> + <%= f.select :dateloc, options_for_select($dateloc_drop.sort, f.object.dateloc || $dateloc_drop_default) %> +
<%= f.label :timeloc, "Time Level of Confidence" %> <%= f.select :timeloc, options_for_select($timeloc_drop.sort, f.object.timeloc || $timeloc_drop_default),{},:class => "input-full" %> diff --git a/app/views/traits/new.html.erb b/app/views/traits/new.html.erb index e04edc8e9..9c97a3915 100644 --- a/app/views/traits/new.html.erb +++ b/app/views/traits/new.html.erb @@ -67,16 +67,16 @@
Date - <%= f.select :date_year, [''] + (1800..Time.now.year).to_a %> - <%= f.select :date_month, [''] + (1..12).to_a %> - <%= f.select :date_day, [''] + (1..31).to_a %> + <%= f.select :d_year, [''] + (1800..Time.now.year).to_a %> + <%= f.select :d_month, [''] + (1..12).to_a %> + <%= f.select :d_day, [''] + (1..31).to_a %>
Time - <%= f.select :time_hour, [''] + (0..23).to_a.collect { |i| '% 2i' % i } %> - <%= f.select :time_minute, [''] + (0..59).to_a.collect { |i| '%02i' % i } %> + <%= f.select :t_hour, [''] + (0..23).to_a.collect { |i| '% 2i' % i } %> + <%= f.select :t_minute, [''] + (0..59).to_a.collect { |i| '%02i' % i } %> <%= f.select :timezone_offset, [''] + $utc_offsets %>
From 29e6da6ad93924899ab6a78673b28b7fe5bfb578 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Fri, 16 Oct 2015 17:32:17 -0500 Subject: [PATCH 04/51] Draft of new display format for dates and times on View Trait pages. --- app/models/trait.rb | 67 ++++++++++++++++---- config/initializers/time_formats.rb | 97 +++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+), 11 deletions(-) create mode 100644 config/initializers/time_formats.rb diff --git a/app/models/trait.rb b/app/models/trait.rb index 8d80457d1..c498678ce 100644 --- a/app/models/trait.rb +++ b/app/models/trait.rb @@ -83,7 +83,7 @@ def consistent_date_and_time_fields begin - t = DateTime.new(@d_year.to_i, @d_month.to_i, @d_day.to_i, @t_hour.to_i, @t_minute.to_i, 0, timezone_offset) + #t = DateTime.new(@d_year.to_i, @d_month.to_i, @d_day.to_i, @t_hour.to_i, @t_minute.to_i, 0, timezone_offset) rescue => e errors.add(:base, e.message) end @@ -205,13 +205,7 @@ def mean_in_range end def pretty_date - date_string = "" - date_string += "#{d_year} " unless d_year == 9996 - date_string += "#{Date::ABBR_MONTHNAMES[d_month]} " unless d_month.nil? - date_string += "#{d_day} " unless d_day.nil? - date_string += " (year unspecified)" if d_year == 9996 - - date_string + date.to_formatted_s(date_format) end def format_statname @@ -231,9 +225,7 @@ def format_stat end def pretty_time - time_string = "" - time_string += "#{"%02i" % t_hour}" unless t_hour.nil? - time_string += ":#{"%02i" % t_minute}" unless t_hour.nil? or t_minute.nil? + time.nil? ? '[unspecified]' : time.to_s(time_format) end def specie_treat_cultivar @@ -313,6 +305,59 @@ def apply_offset end + + + private + + def date_format + case dateloc + when 9 + :no_date_data + when 8 + :year_only + when 7 + :season_and_year + when 6 + :month_and_year + when 5.5 + :week_of_year + when 5 + :year_month_day + when 97 + :season_only + when 96 + :month_only + when 95 + :month_day + when nil + :unspecified_dateloc + else + :unrecognized_dateloc + end + end + + def time_format + case timeloc + when 9 + :no_time_data + when 4 + :time_of_day + when 3 + :hour_only + when 2 + :hour_minutes + when 1 + :hour_minutes_seconds + when nil + :unspecified_timeloc + else + :unrecognized_timeloc + end + end + + + + end diff --git a/config/initializers/time_formats.rb b/config/initializers/time_formats.rb new file mode 100644 index 000000000..8e487ffab --- /dev/null +++ b/config/initializers/time_formats.rb @@ -0,0 +1,97 @@ + +NEW_FORMATS = { + + # Date Formats + + # dateloc 9 + no_date_data: '[date unspecified or unknown]', + + # dateloc 8 + year_only: '%Y', + + # dateloc 7 + season_and_year: ->(date) do + date.strftime("#{date.to_s(:season_only)} %Y") + end, + + # dateloc 6 + month_and_year: '%B %Y', + + # dateloc 5.5: + week_of_year: 'Week of %b %-d, %Y', + + # dateloc 5: + year_month_day: '%Y %b %-d', + + # dateloc 97: + season_only: ->(date) do + case date.month + when 1 + 'Winter' + when 4 + 'Spring' + when 7 + 'Summer' + when 10 + 'Autumn' + else + '[Invalid season designation]' + end + end, + + # dateloc 96: + month_only: '%B', + + # dateloc 95: + month_day: ->(date) { date.strftime("%B #{time.day.ordinalize} %Y") }, + + unspecified_dateloc: 'Date Level of Confidence Unknown', + + unrecognized_dateloc: 'Unrecognized Value for Date Level of Confidence', + + + # Time Formats + + # timeloc 9: + no_time_data: '[time unspecified or unknown]', + + # timeloc 4: + time_of_day: ->(time) do + case time.hour + when 1 + 'night' + when 9 + 'morning' + when 12 + 'mid-day' + when 15 + 'afternoon' + when 18 + 'evening' + when 23 + 'late evening' + else + '[Invalid time-of-day designation]' + end + end, + + # timeloc 3: + hour_only: '%l %p', + + # timeloc 2: + hour_minutes: '%H:%M', + + # timeloc 1: + hour_minutes_seconds: '%H:%M:%S', + + unspecified_timeloc: 'Time Level of Confidence Unknown', + + unrecognized_timeloc: 'Unrecognized Value for Time Level of Confidence' + + + +} + + +Time::DATE_FORMATS.merge!(NEW_FORMATS) + From 5fd62841ecaa8719c37d4dbbae27d22e699c8ce0 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Mon, 19 Oct 2015 13:05:35 -0500 Subject: [PATCH 05/51] models/trait.rb: Changed name apply_offset -> process_datetime_input. During validation: Don't flag timezone offset '+00:00:00'. Allow season names in month field. Fixed pretty_time to use date attribute instead of obsolete time attribute. In process_datetime_input, set the correct @d_month value and dateloc value when @d_month is a season name. --- app/models/trait.rb | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/app/models/trait.rb b/app/models/trait.rb index c498678ce..9a167004b 100644 --- a/app/models/trait.rb +++ b/app/models/trait.rb @@ -26,7 +26,7 @@ def timezone_offset #Rails.logger.info("In t_hour, date.utc = #{self.date.utc} and date.utc.hour = #{date.utc.hour}") end - before_save :apply_offset + before_save :process_datetime_input include Overrides @@ -76,7 +76,7 @@ def consistent_date_and_time_fields # estimate time zone from site longitude appx_offset = (Site.find site_id).lon / 15.0 hour_offset = (timezone_offset.sub(/:.*/, '')).to_i - if(appx_offset - hour_offset).abs > 2 + if timezone_offset != '+00:00:00' && (appx_offset - hour_offset).abs > 2 errors.add(:base, "The UTC offset value you have selected seems inconsistent with the site location.") end end @@ -101,7 +101,7 @@ def consistent_date_and_time_fields validates_inclusion_of :access_level, in: 1..4, message: "You must select an access level" validates_presence_of :statname, :if => Proc.new { |trait| !trait.stat.blank? } validates_format_of :d_year, :with => /\A(\d{2}|\d{4})\z/, :allow_blank => true - validates_format_of :d_month, :with => /\A\d{1,2}\z/, :allow_blank => true + validates_format_of :d_month, :with => /\A\d{1,2}|Spring|Summer|Winter|Autumn\z/, :allow_blank => true validates_format_of :d_day, :with => /\A\d{1,2}\z/, :allow_blank => true #validates_format_of :t_hour, :with => /\A\d{1,2}\z/, :allow_blank => true validates_format_of :t_minute, :with => /\A\d{1,2}\z/, :allow_blank => true @@ -225,7 +225,7 @@ def format_stat end def pretty_time - time.nil? ? '[unspecified]' : time.to_s(time_format) + date.nil? ? '[unspecified]' : date.to_s(time_format) end def specie_treat_cultivar @@ -250,7 +250,7 @@ def self.search_columns private - def apply_offset + def process_datetime_input # Supply missing year if needed: @@ -260,6 +260,11 @@ def apply_offset self.dateloc = 95 end + if ['Spring', 'Summer', 'Autumn', 'Winter'].include? @d_month + @d_month = { 'Spring' => '4', 'Summer' => '7', 'Autumn' => '10', 'Winter' => '1' }[@d_month] + dateloc = 7 + end + =begin sign, hour, minutes = /\A *([+-]?)(\d\d?):(\d\d) *\z/.match(self.timezone_offset).captures @@ -288,7 +293,7 @@ def apply_offset Rails.logger.info("self = #{self}") =end begin - t_utc = DateTime.new(@d_year.to_i, @d_month.to_i, @d_day.to_i, @t_hour.to_i, @t_minute.to_i, 0, timezone_offset).utc + t_utc = DateTime.now#new(@d_year.to_i, @d_month.to_i, @d_day.to_i, @t_hour.to_i, @t_minute.to_i, 0, timezone_offset).utc rescue => e Rails.logger.info("in apply offset, got this error: #{e.message}") Rails.logger.info("values of @d_year, @d_month, @d_day, @t_hour, @t_minute are #{@d_year}, #{@d_month}, #{@d_day}, #{@t_hour}, #{@t_minute} with types #{@d_year.class}, #{@d_month.class}, #{@d_day.class}, #{@t_hour.class}, #{@t_minute.class}") From e30a3eb23402939181ed2c806339a3a97d22a167 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Mon, 19 Oct 2015 17:32:16 -0500 Subject: [PATCH 06/51] Added to $dateloc_drop and $timeloc_drop. --- app/helpers/application_helper.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 4c260e296..2dd07dcc6 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -31,12 +31,16 @@ def treatment_check(ty) "7 season" => "7.0" , "7.5 " => "7.5" , "8 year" => "8.0" , - "9 no data" => "9.0" } + "9 no data" => "9.0", + "95 day (year unspecified)" => "95.0", + "96 month (year unspecified)" => "96.0", + "97 season (year unspecified)" => "97.0" } $dateloc_drop_default = "9.0" $timeloc_drop = { "1 second" => "1.0" , "2 minute" => "2.0" , "2.5 quarter-hour" => "2.5" , "3 hour" => "3.0" , + "4 time of day, e.g. morning, afternoon" => "4.0" , "9 no data" => "9.0" } $timeloc_drop_default = "9.0" From c15c67cf6165fcb2a73cce424b07eb4743977d43 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Mon, 19 Oct 2015 17:33:56 -0500 Subject: [PATCH 07/51] Added seasons to d_month dropdown. --- app/views/traits/edit.html.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/traits/edit.html.erb b/app/views/traits/edit.html.erb index 867c2bd79..e697f768a 100644 --- a/app/views/traits/edit.html.erb +++ b/app/views/traits/edit.html.erb @@ -59,7 +59,7 @@
Date <%= f.select :d_year, [''] + (1800..Time.now.year).to_a %> - <%= f.select :d_month, [''] + (1..12).to_a %> + <%= f.select :d_month, ['Spring', 'Summer', 'Autumn', 'Winter'] + (1..12).to_a, include_blank: true %> <%= f.select :d_day, [''] + (1..31).to_a %>
@@ -68,7 +68,7 @@ Time <%= f.select :t_hour, [''] + (0..23).to_a %> <%= f.select :t_minute, [''] + (0..59).to_a.collect { |i| '%02i' % i } %> - <%= f.select :timezone_offset, [''] + $utc_offsets %> + <%= f.select :timezone_offset, $utc_offsets, include_blank: true %>
From 9bbdcd83e6368dc7ae7d6deea71904378b816caa Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Mon, 19 Oct 2015 17:35:54 -0500 Subject: [PATCH 08/51] In New Trait form, changed default utc_offset to nil and eliminated LOC fields. --- app/views/traits/new.html.erb | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/app/views/traits/new.html.erb b/app/views/traits/new.html.erb index 9c97a3915..f2034d5b1 100644 --- a/app/views/traits/new.html.erb +++ b/app/views/traits/new.html.erb @@ -77,17 +77,9 @@ Time <%= f.select :t_hour, [''] + (0..23).to_a.collect { |i| '% 2i' % i } %> <%= f.select :t_minute, [''] + (0..59).to_a.collect { |i| '%02i' % i } %> - <%= f.select :timezone_offset, [''] + $utc_offsets %> + <%= f.select :timezone_offset, $utc_offsets, include_blank: true, selected: nil %>
-
- <%= f.label :dateloc, "Date Level of Confidence" %> - <%= f.select :dateloc, options_for_select($dateloc_drop.sort, f.object.dateloc || $dateloc_drop_default) %> -
-
- <%= f.label :timeloc, "Time Level of Confidence" %> - <%= f.select :timeloc, options_for_select($timeloc_drop.sort, f.object.timeloc || $timeloc_drop_default),{},:class => "input-full" %> -
From 10407aeaeeef547d0e4e2319372d4c1f6df18bce Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Mon, 19 Oct 2015 17:36:49 -0500 Subject: [PATCH 09/51] Eliminated LOC display. --- app/views/traits/show.html.erb | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/views/traits/show.html.erb b/app/views/traits/show.html.erb index 2b8e66213..7a532f2aa 100644 --- a/app/views/traits/show.html.erb +++ b/app/views/traits/show.html.erb @@ -35,15 +35,9 @@
Date:
<%= @trait.pretty_date %>
-
Date Level of Confidence:
-
<%= $dateloc_drop.invert[@trait.dateloc.to_s] %>
-
Time:
<%= @trait.pretty_time %>
-
Time Level of Confidence:
-
<%= @trait.timeloc %>
-
Mean:
<%= @trait.mean %>
From efac6e95565a5d1c97fd2d7ae990c1d0922db398 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Mon, 19 Oct 2015 17:38:33 -0500 Subject: [PATCH 10/51] Corrected month_day time format. --- config/initializers/time_formats.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/time_formats.rb b/config/initializers/time_formats.rb index 8e487ffab..9f9481449 100644 --- a/config/initializers/time_formats.rb +++ b/config/initializers/time_formats.rb @@ -43,7 +43,7 @@ month_only: '%B', # dateloc 95: - month_day: ->(date) { date.strftime("%B #{time.day.ordinalize} %Y") }, + month_day: ->(date) { date.strftime("%B #{date.day.ordinalize}") }, unspecified_dateloc: 'Date Level of Confidence Unknown', From 871964f12a8568254c7c5a72ad73cff59072616d Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Mon, 19 Oct 2015 17:39:53 -0500 Subject: [PATCH 11/51] Lot's of changes to Trait model. Needs cleanup. --- app/models/trait.rb | 205 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 163 insertions(+), 42 deletions(-) diff --git a/app/models/trait.rb b/app/models/trait.rb index 9a167004b..7c1c864ab 100644 --- a/app/models/trait.rb +++ b/app/models/trait.rb @@ -2,24 +2,88 @@ class Trait < ActiveRecord::Base # Passed from controller for validation of ability attr_accessor :current_user - attr_accessor :timezone_offset, :d_year, :d_month, :d_day, :t_hour, :t_minute + attr_writer :timezone_offset, :d_year, :d_month, :d_day, :t_hour, :t_minute def d_year - Rails.logger.info("In method d_year, @d_year = #{@d_year}") - @d_year || (date.nil? ? '' : date.utc.year) + if !@d_year.nil? + return @d_year + end + + case dateloc + when 95, 96, 97, 9 + '' + when 5, 5.5, 6, 7, 8 + date.nil? ? '' : date.utc.year + else + raise + end end def d_month - @d_month || (date.nil? ? '' : date.utc.month) + if !@d_month.nil? + return @d_month + end + + case dateloc + when 8, 9 + nil + when 7, 97 + case date.utc.month + when 1 + 'Winter' + when 4 + 'Spring' + when 7 + 'Summer' + when 10 + 'Autumn' + else + raise + end + when 5, 5.5, 6, 95, 96 + date.nil? ? '' : date.utc.month + else + raise + end end def d_day - @d_day || (date.nil? ? '' : date.utc.day) + if !@d_day.nil? + return @d_day + end + + case dateloc + when 6, 7, 8, 9, 96, 97 + nil + when 5, 5.5 + date.nil? ? '' : date.utc.day + end end def t_hour - @t_hour || (date.nil? ? '' : date.utc.hour) + if !@t_hour.nil? + return @t_hour + end + + case timeloc + when 9 + nil + when 4 + 'morning' + when 1, 2, 3 + date.nil? ? '' : date.utc.hour + else + raise + end end def t_minute - # use formatting to make sure value matches dropdown values: - @t_minute || (date.nil? ? '' : date.utc.min) + if !@t_minute.nil? + @t_minute + end + + case timeloc + when 3, 4, 5 + nil + when 1, 2 + date.nil? ? '' : date.utc.strftime('%M') + end end def timezone_offset @timezone_offset.nil? ? "+00:00:00" : (@timezone_offset.match(/\+|-/) ? @timezone_offset : "+#{@timezone_offset}") @@ -205,7 +269,7 @@ def mean_in_range end def pretty_date - date.to_formatted_s(date_format) + date.nil? ? '[unspecified]' : date.utc.to_formatted_s(date_format) + ([7, 9, 97].include?(dateloc) ? '' : ' (UTC)') end def format_statname @@ -225,7 +289,8 @@ def format_stat end def pretty_time - date.nil? ? '[unspecified]' : date.to_s(time_format) + Rails.logger.info("date = #{date.to_s}; date.to_s(time_format) = #{date.to_s(time_format)}") + date.nil? ? '[unspecified]' : date.utc.to_s(time_format) + (timeloc == 9 ? '' : ' UTC') end def specie_treat_cultivar @@ -252,51 +317,108 @@ def self.search_columns def process_datetime_input + # Use local variables year, month, day, hour, minute for instantiating + # DateTime object; initialize them from form fields: + year = d_year.to_i + month = d_month.to_i + day = d_day.to_i + hour = t_hour.to_i + minute = @t_minute.to_i - # Supply missing year if needed: - if @d_year.blank? - @d_year = 9996 - self.dateloc = 95 - end + Rails.logger.info("values of @d_year, @d_month, @d_day, @t_hour, @t_minute are #{@d_year}, #{@d_month}, #{@d_day}, #{@t_hour}, #{@t_minute} with types #{@d_year.class}, #{@d_month.class}, #{@d_day.class}, #{@t_hour.class}, #{@t_minute.class}") - if ['Spring', 'Summer', 'Autumn', 'Winter'].include? @d_month - @d_month = { 'Spring' => '4', 'Summer' => '7', 'Autumn' => '10', 'Winter' => '1' }[@d_month] - dateloc = 7 - end + Rails.logger.info("d_year = #{d_year} and @d_year = #{@d_year}") + Rails.logger.info("d_month = #{d_month} and @d_month = #{@d_month}") + Rails.logger.info("d_day = #{d_day} and @d_day = #{@d_day}") + Rails.logger.info("t_hour = #{t_hour} and @t_hour = #{@t_hour}") + Rails.logger.info("t_minute = #{t_minute} and @t_minute = #{@t_minute}") -=begin - sign, hour, minutes = /\A *([+-]?)(\d\d?):(\d\d) *\z/.match(self.timezone_offset).captures - Rails.logger.info("self.timezone_offset: #{self.timezone_offset}") - Rails.logger.info("sign, hour, minutes: #{sign}, #{hour}, #{minutes}") + # Supply missing year if needed: - if sign == "-" - sign = -1 - else - sign = 1 + if @d_year.blank? + if @d_month.blank? + if !@d_day.blank? + # shouldn't ever get here + raise "If you set a day, you must also set a month." + end + year = 9999 + month = 1 + day = 1 + self.dateloc = 9 + else + year = 9996 + if ['Spring', 'Summer', 'Autumn', 'Winter'].include? @d_month + if !@d_day.blank? + # shouldn't ever get here + raise "If you set a season, you must leave day blank." + end + month = { 'Spring' => 4, 'Summer' => 7, 'Autumn' => 10, 'Winter' => 1 }[@d_month] + day = 1 + self.dateloc = 97 + else # month is an integer + if @d_day.blank? + day = 1 + self.dateloc = 96 + else + self.dateloc = 95 + end + end + end + else # year is given + if @d_month.blank? + if !@d_day.blank? + # shouldn't ever get here + raise "If you set a day, you must also set a month." + end + month = 1 + day = 1 + self.dateloc = 8 + else + if ['Spring', 'Summer', 'Autumn', 'Winter'].include? @d_month + if !@d_day.blank? + # shouldn't ever get here + raise "If you set a season, you must leave day blank." + end + month = { 'Spring' => 4, 'Summer' => 7, 'Autumn' => 10, 'Winter' => 1 }[@d_month] + day = 1 + self.dateloc = 7 + else # month is an integer + if @d_day.blank? + day = 1 + self.dateloc = 6 + else + self.dateloc = 5 + end + end + end end - hour = hour.to_i - minutes = minutes.to_i - # convert time to UTC : UTC time = local time - local time offset - Rails.logger.info("@t_hour: #{@t_hour}") - Rails.logger.info("sign * hour: #{sign * hour}") - Rails.logger.info("@t_hour - sign * hour: #{@t_hour - sign * hour}") - self.@t_hour -= sign * hour - self.@t_minute -= sign * minutes - - self.notes = "blah" + if @t_hour.blank? + if !@t_minute.blank? + # shouldn't ever get here + raise "If you set a minute, you must set the hour." + end + hour = 0 + minute = 0 + self.timeloc = 9 + else # hour is given + if @t_minute.blank? + minute = 0 + self.timeloc = 3 + else + self.timeloc = 2 + end + end - Rails.logger.info("self = #{self}") -=end begin - t_utc = DateTime.now#new(@d_year.to_i, @d_month.to_i, @d_day.to_i, @t_hour.to_i, @t_minute.to_i, 0, timezone_offset).utc + Rails.logger.info("values of year, month, day, hour, minute are #{year}, #{month}, #{day}, #{hour}, #{minute} with types #{year.class}, #{month.class}, #{day.class}, #{hour.class}, #{minute.class}") + t_utc = DateTime.new(year, month, day, hour, minute, 0, timezone_offset).utc rescue => e Rails.logger.info("in apply offset, got this error: #{e.message}") - Rails.logger.info("values of @d_year, @d_month, @d_day, @t_hour, @t_minute are #{@d_year}, #{@d_month}, #{@d_day}, #{@t_hour}, #{@t_minute} with types #{@d_year.class}, #{@d_month.class}, #{@d_day.class}, #{@t_hour.class}, #{@t_minute.class}") return false end Rails.logger.info("values of @d_year, @d_month, @d_day, @t_hour, @t_minute are #{@d_year}, #{@d_month}, #{@d_day}, #{@t_hour}, #{@t_minute} with types #{@d_year.class}, #{@d_month.class}, #{@d_day.class}, #{@t_hour.class}, #{@t_minute.class}") @@ -308,7 +430,6 @@ def process_datetime_input self.date = t_utc - end From bae4681a10d4972e08bdd292d357e4674044a12c Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Wed, 21 Oct 2015 14:32:15 -0500 Subject: [PATCH 12/51] Altered Trait model d_* accessors to accomodate the case where dateloc and/or timeloc are nil (such as when the Trait is new). --- app/models/trait.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/models/trait.rb b/app/models/trait.rb index 7c1c864ab..1b61ebd69 100644 --- a/app/models/trait.rb +++ b/app/models/trait.rb @@ -14,6 +14,8 @@ def d_year '' when 5, 5.5, 6, 7, 8 date.nil? ? '' : date.utc.year + when nil + nil else raise end @@ -41,6 +43,8 @@ def d_month end when 5, 5.5, 6, 95, 96 date.nil? ? '' : date.utc.month + when nil + nil else raise end @@ -55,6 +59,10 @@ def d_day nil when 5, 5.5 date.nil? ? '' : date.utc.day + when nil + nil + else + raise end end def t_hour @@ -69,6 +77,8 @@ def t_hour 'morning' when 1, 2, 3 date.nil? ? '' : date.utc.hour + when nil + nil else raise end @@ -83,6 +93,10 @@ def t_minute nil when 1, 2 date.nil? ? '' : date.utc.strftime('%M') + when nil + nil + else + raise end end def timezone_offset From fc0d4d0af76778952c1302b23b306ec971f38c47 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Wed, 21 Oct 2015 14:40:14 -0500 Subject: [PATCH 13/51] Forgot to include timeloc value 9 in t_minute's case statement. --- app/models/trait.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/trait.rb b/app/models/trait.rb index 1b61ebd69..91ac4f3e4 100644 --- a/app/models/trait.rb +++ b/app/models/trait.rb @@ -89,7 +89,7 @@ def t_minute end case timeloc - when 3, 4, 5 + when 3, 4, 5, 9 nil when 1, 2 date.nil? ? '' : date.utc.strftime('%M') From 68f460ba96d6f268d468e31359602dd2ef9d57b2 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Wed, 21 Oct 2015 15:20:56 -0500 Subject: [PATCH 14/51] In Trait validation method "consistent_date_and_time_fields", use instance variable @timezone_offset instead of virtual attribute accessor timezone_offset. --- app/models/trait.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/models/trait.rb b/app/models/trait.rb index 91ac4f3e4..6579283cd 100644 --- a/app/models/trait.rb +++ b/app/models/trait.rb @@ -146,15 +146,15 @@ def consistent_date_and_time_fields end # Require a timezone offset if the hour is specified: - if timezone_offset.blank? && !@t_hour.blank? + if @timezone_offset.blank? && !@t_hour.blank? errors.add(:base, "If you specify the hour, you must specify a timezone offset.") end - if !timezone_offset.blank? && !site_id.blank? + if !@timezone_offset.blank? && !site_id.blank? # estimate time zone from site longitude appx_offset = (Site.find site_id).lon / 15.0 - hour_offset = (timezone_offset.sub(/:.*/, '')).to_i - if timezone_offset != '+00:00:00' && (appx_offset - hour_offset).abs > 2 + hour_offset = (@timezone_offset.sub(/:.*/, '')).to_i + if @timezone_offset != '+00:00:00' && (appx_offset - hour_offset).abs > 2 errors.add(:base, "The UTC offset value you have selected seems inconsistent with the site location.") end end From a89d5cd455af70da335b50ae819bf54e8cbb5334 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Wed, 21 Oct 2015 15:58:00 -0500 Subject: [PATCH 15/51] Forgot to handle dateloc value 95 in Trait accessor method d_day. --- app/models/trait.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/trait.rb b/app/models/trait.rb index 6579283cd..d84e87030 100644 --- a/app/models/trait.rb +++ b/app/models/trait.rb @@ -57,7 +57,7 @@ def d_day case dateloc when 6, 7, 8, 9, 96, 97 nil - when 5, 5.5 + when 5, 5.5, 95 date.nil? ? '' : date.utc.day when nil nil From b3e7b9ca702fc16315d214e5c7d64c7665c5d70b Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Mon, 26 Oct 2015 12:18:32 -0500 Subject: [PATCH 16/51] Removed time zone offset input element and made date/time layout roomier. Always display hours as two digits (this connotes 24-hour clock). Added blank to site selections so traits that don't yet have an associated site don't automatically get some arbitrary default when they are edited. --- app/views/traits/edit.html.erb | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/app/views/traits/edit.html.erb b/app/views/traits/edit.html.erb index e697f768a..43ffc6e8b 100644 --- a/app/views/traits/edit.html.erb +++ b/app/views/traits/edit.html.erb @@ -55,7 +55,7 @@
-
+
Date <%= f.select :d_year, [''] + (1800..Time.now.year).to_a %> @@ -63,15 +63,17 @@ <%= f.select :d_day, [''] + (1..31).to_a %>
-
+
- Time - <%= f.select :t_hour, [''] + (0..23).to_a %> + Time (at site) + <%= f.select :t_hour, [''] + (0..23).to_a.collect { |i| '%02i' % i } %> <%= f.select :t_minute, [''] + (0..59).to_a.collect { |i| '%02i' % i } %> - <%= f.select :timezone_offset, $utc_offsets, include_blank: true %>
-
+
+ +
+
<%= f.label :dateloc, "Date Level of Confidence" %> <%= f.select :dateloc, options_for_select($dateloc_drop.sort, f.object.dateloc || $dateloc_drop_default) %>
@@ -79,12 +81,15 @@ <%= f.label :timeloc, "Time Level of Confidence" %> <%= f.select :timeloc, options_for_select($timeloc_drop.sort, f.object.timeloc || $timeloc_drop_default),{},:class => "input-full" %>
+
+ <--- This will be removed! +
<%= f.label :site_id %> - <%= f.select :site_id, @citation.sites.collect { |p| [ p.select_default, p.id ] }, {}, :class => "input-full" %> + <%= f.select :site_id, @citation.sites.collect { |p| [ p.select_default, p.id ] }, {include_blank: true}, :class => "input-full" %>
<%= f.label :treatment_id %> From 77bc18fd95b4ec6ddbeb283095abfdd1dd8fc22b Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Mon, 26 Oct 2015 12:21:01 -0500 Subject: [PATCH 17/51] Added seasons to month dropdown. Removed time zone offset input element. Added blank to site selection dropdown and made it the default so that a new trait doesn't inadvertently get assigned to some arbitrary site. --- app/views/traits/new.html.erb | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/app/views/traits/new.html.erb b/app/views/traits/new.html.erb index f2034d5b1..a3284224a 100644 --- a/app/views/traits/new.html.erb +++ b/app/views/traits/new.html.erb @@ -64,20 +64,19 @@
-
+
Date <%= f.select :d_year, [''] + (1800..Time.now.year).to_a %> - <%= f.select :d_month, [''] + (1..12).to_a %> + <%= f.select :d_month, ['Spring', 'Summer', 'Autumn', 'Winter'] + (1..12).to_a, include_blank: true %> <%= f.select :d_day, [''] + (1..31).to_a %>
-
+
- Time + Time (at site) <%= f.select :t_hour, [''] + (0..23).to_a.collect { |i| '% 2i' % i } %> <%= f.select :t_minute, [''] + (0..59).to_a.collect { |i| '%02i' % i } %> - <%= f.select :timezone_offset, $utc_offsets, include_blank: true, selected: nil %>
@@ -85,7 +84,7 @@
<%= f.label :site_id %> - <%= f.select :site_id, @citation.sites.collect { |p| [ p.select_default, p.id ] }, {}, :class => "input-full" %> + <%= f.select :site_id, @citation.sites.collect { |p| [ p.select_default, p.id ] }, {include_blank: true}, :class => "input-full" %>
<%= f.label :treatment_id %> From c05c2d717b2feb1176dc2081744dd7ffa896f889 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Mon, 26 Oct 2015 12:54:40 -0500 Subject: [PATCH 18/51] Removed timezone_offset getter and setter. Added Seasons constant. Made d_* and t_* accessors return site-timezone-adjusted date/time values. Made pretty_date and pretty_time methods show site-timezone-adjusted date and time values. Fixed t_minute accessor (it didn't return @t_minute when it should). Added compute_dateloc convenience method. Made consistent_date_and_time_fields validation method check that input comprises a valid timestamp value. For incomplete input, made sure that standard default values together with input comprise a valid timestamp value. Added validation to ensure a site is selected. Added site_timezone method. Added utctime_from_sitetime method, and use it for generating UTC-based timestamp values for storage in the database from local-site-time-based date and time input values. --- app/models/trait.rb | 166 +++++++++++++++++++++++++++++++++----------- 1 file changed, 124 insertions(+), 42 deletions(-) diff --git a/app/models/trait.rb b/app/models/trait.rb index d84e87030..1928ad4ea 100644 --- a/app/models/trait.rb +++ b/app/models/trait.rb @@ -2,7 +2,9 @@ class Trait < ActiveRecord::Base # Passed from controller for validation of ability attr_accessor :current_user - attr_writer :timezone_offset, :d_year, :d_month, :d_day, :t_hour, :t_minute + attr_writer :d_year, :d_month, :d_day, :t_hour, :t_minute + + Seasons = ['Spring', 'Summer', 'Autumn', 'Winter'] def d_year if !@d_year.nil? @@ -13,7 +15,7 @@ def d_year when 95, 96, 97, 9 '' when 5, 5.5, 6, 7, 8 - date.nil? ? '' : date.utc.year + date.nil? ? '' : date_in_site_timezone.year when nil nil else @@ -29,7 +31,7 @@ def d_month when 8, 9 nil when 7, 97 - case date.utc.month + case date_in_site_timezone.month when 1 'Winter' when 4 @@ -42,7 +44,7 @@ def d_month raise end when 5, 5.5, 6, 95, 96 - date.nil? ? '' : date.utc.month + date.nil? ? '' : date_in_site_timezone.month when nil nil else @@ -58,7 +60,7 @@ def d_day when 6, 7, 8, 9, 96, 97 nil when 5, 5.5, 95 - date.nil? ? '' : date.utc.day + date.nil? ? '' : date_in_site_timezone.day when nil nil else @@ -76,7 +78,7 @@ def t_hour when 4 'morning' when 1, 2, 3 - date.nil? ? '' : date.utc.hour + date.nil? ? '' : date_in_site_timezone.strftime('%H') when nil nil else @@ -85,24 +87,20 @@ def t_hour end def t_minute if !@t_minute.nil? - @t_minute + return @t_minute end case timeloc when 3, 4, 5, 9 nil when 1, 2 - date.nil? ? '' : date.utc.strftime('%M') + date.nil? ? '' : date_in_site_timezone.strftime('%M') when nil nil else raise end end - def timezone_offset - @timezone_offset.nil? ? "+00:00:00" : (@timezone_offset.match(/\+|-/) ? @timezone_offset : "+#{@timezone_offset}") - #Rails.logger.info("In t_hour, date.utc = #{self.date.utc} and date.utc.hour = #{date.utc.hour}") - end before_save :process_datetime_input @@ -135,35 +133,71 @@ def timezone_offset def consistent_date_and_time_fields - # Require a month if there is a day: - if @d_month.blank? && !@d_day.blank? - errors.add(:base, "If you have a date day, you must specify the month.") + # use local copies of instance variables + d_year = @d_year + d_month = @d_month + d_day = @d_day + t_hour = @t_hour + t_minute = @t_minute + + + begin + dateloc = compute_dateloc(d_year, d_month, d_day) + rescue => e + errors.add(:base, e.message) end # Require an hour if minutes are specified: - if @t_hour.blank? && !@t_minute.blank? + if t_hour.blank? && !t_minute.blank? errors.add(:base, "If you specify minutes, you must specify the hour.") end - - # Require a timezone offset if the hour is specified: - if @timezone_offset.blank? && !@t_hour.blank? - errors.add(:base, "If you specify the hour, you must specify a timezone offset.") - end - - if !@timezone_offset.blank? && !site_id.blank? - # estimate time zone from site longitude - appx_offset = (Site.find site_id).lon / 15.0 - hour_offset = (@timezone_offset.sub(/:.*/, '')).to_i - if @timezone_offset != '+00:00:00' && (appx_offset - hour_offset).abs > 2 - errors.add(:base, "The UTC offset value you have selected seems inconsistent with the site location.") + + # set defaults if needed + case dateloc + when 9 + d_year, d_month, d_day = 9999, 1, 1 + when 8 + d_month, d_day = 1, 1 + when 7 + d_day = 1 + case d_month + when 'Spring' + d_month = 4 + when 'Summer' + d_month = 7 + when 'Autumn' + d_month = 10 + when 'Winter' + d_month = 1 + end + when 6 + d_day = 1 + when 5 + # nothing to do + when 97 + d_year = 9996 + d_day = 1 + case d_month + when 'Spring' + d_month = 4 + when 'Summer' + d_month = 7 + when 'Autumn' + d_month = 10 + when 'Winter' + d_month = 1 end + when 6 + d_year = 9996 + d_day = 1 + when 5 + d_year = 9996 end - begin - #t = DateTime.new(@d_year.to_i, @d_month.to_i, @d_day.to_i, @t_hour.to_i, @t_minute.to_i, 0, timezone_offset) + t = utctime_from_sitetime(d_year.to_i, d_month.to_i, d_day.to_i, t_hour.to_i, t_minute.to_i) #DateTime.new(d_year.to_i, d_month.to_i, d_day.to_i, t_hour.to_i, t_minute.to_i, 0, timezone_offset) rescue => e - errors.add(:base, e.message) + errors.add(:base, "With dateloc = #{dateloc}, can't make DateTime from #{d_year}, #{d_month}, #{d_day}, #{t_hour}, #{t_minute}") end end @@ -173,7 +207,7 @@ def consistent_date_and_time_fields ## Validations - validates_presence_of :mean, :access_level + validates_presence_of :mean, :access_level, :site validates_numericality_of :mean validates_presence_of :variable validates_inclusion_of :access_level, in: 1..4, message: "You must select an access level" @@ -283,7 +317,7 @@ def mean_in_range end def pretty_date - date.nil? ? '[unspecified]' : date.utc.to_formatted_s(date_format) + ([7, 9, 97].include?(dateloc) ? '' : ' (UTC)') + date.nil? ? '[unspecified]' : date_in_site_timezone.to_formatted_s(date_format) + ([7, 9, 97].include?(dateloc) ? '' : ' (UTC)') end def format_statname @@ -303,8 +337,8 @@ def format_stat end def pretty_time - Rails.logger.info("date = #{date.to_s}; date.to_s(time_format) = #{date.to_s(time_format)}") - date.nil? ? '[unspecified]' : date.utc.to_s(time_format) + (timeloc == 9 ? '' : ' UTC') +# Rails.logger.info("date = #{date.to_s}; date.to_s(time_format) = #{date.to_s(time_format)}") + date.nil? ? '[unspecified]' : date_in_site_timezone.to_s(time_format) + (timeloc == 9 ? '' : ' ' + site_timezone) end def specie_treat_cultivar @@ -325,6 +359,14 @@ def self.search_columns return ["traits.id"] end + def site_timezone + begin + zone = (Site.find site_id).time_zone + rescue + zone = 'UTC' + end + end + private @@ -430,7 +472,7 @@ def process_datetime_input begin Rails.logger.info("values of year, month, day, hour, minute are #{year}, #{month}, #{day}, #{hour}, #{minute} with types #{year.class}, #{month.class}, #{day.class}, #{hour.class}, #{minute.class}") - t_utc = DateTime.new(year, month, day, hour, minute, 0, timezone_offset).utc + t_utc = utctime_from_sitetime(year, month, day, hour, minute) # DateTime.new(year, month, day, hour, minute, 0, timezone_offset).utc rescue => e Rails.logger.info("in apply offset, got this error: #{e.message}") return false @@ -446,8 +488,41 @@ def process_datetime_input end - - private + def compute_dateloc(y, m, d) + if !d.blank? + if m.blank? + raise "If month is blank, day must be also." + elsif Seasons.include?(m) + raise "If you select a season, day must be blank." + else + if y.blank? + 95 + else + 5 + end + end + else # d is blank + if m.blank? + if y.blank? + 9 + else + 8 + end + elsif Seasons.include?(m) + if y.blank? + 97 + else + 7 + end + else # month is a number (a real month) + if y.blank? + 96 + else + 6 + end + end + end + end def date_format case dateloc @@ -495,9 +570,16 @@ def time_format end end - - - - + def utctime_from_sitetime(y, m, d, hr, min) + utctime = nil + Time.use_zone site_timezone do + utctime = Time.zone.local(y, m, d, hr, min, 0).utc + end + return utctime.to_datetime + end + def date_in_site_timezone + date.in_time_zone(site_timezone) + end + end From 14144a0700b9d688aeffa08d0c4dfbe61941e6b0 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Mon, 26 Oct 2015 13:09:00 -0500 Subject: [PATCH 19/51] Tweaked display of timezone on Show page. Use Trait::Seasons constant in views. --- app/models/trait.rb | 4 ++-- app/views/traits/edit.html.erb | 2 +- app/views/traits/new.html.erb | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/models/trait.rb b/app/models/trait.rb index 1928ad4ea..b69e9d35b 100644 --- a/app/models/trait.rb +++ b/app/models/trait.rb @@ -317,7 +317,7 @@ def mean_in_range end def pretty_date - date.nil? ? '[unspecified]' : date_in_site_timezone.to_formatted_s(date_format) + ([7, 9, 97].include?(dateloc) ? '' : ' (UTC)') + date.nil? ? '[unspecified]' : date_in_site_timezone.to_formatted_s(date_format) + ([7, 9, 97].include?(dateloc) || timeloc != 9 ? '' : " (#{site_timezone})") end def format_statname @@ -338,7 +338,7 @@ def format_stat def pretty_time # Rails.logger.info("date = #{date.to_s}; date.to_s(time_format) = #{date.to_s(time_format)}") - date.nil? ? '[unspecified]' : date_in_site_timezone.to_s(time_format) + (timeloc == 9 ? '' : ' ' + site_timezone) + date.nil? ? '[unspecified]' : date_in_site_timezone.to_s(time_format) + (timeloc == 9 ? '' : " (#{site_timezone})") end def specie_treat_cultivar diff --git a/app/views/traits/edit.html.erb b/app/views/traits/edit.html.erb index 43ffc6e8b..cba077e7f 100644 --- a/app/views/traits/edit.html.erb +++ b/app/views/traits/edit.html.erb @@ -59,7 +59,7 @@
Date <%= f.select :d_year, [''] + (1800..Time.now.year).to_a %> - <%= f.select :d_month, ['Spring', 'Summer', 'Autumn', 'Winter'] + (1..12).to_a, include_blank: true %> + <%= f.select :d_month, Trait::Seasons + (1..12).to_a, include_blank: true %> <%= f.select :d_day, [''] + (1..31).to_a %>
diff --git a/app/views/traits/new.html.erb b/app/views/traits/new.html.erb index a3284224a..d24ae05fa 100644 --- a/app/views/traits/new.html.erb +++ b/app/views/traits/new.html.erb @@ -68,7 +68,7 @@
Date <%= f.select :d_year, [''] + (1800..Time.now.year).to_a %> - <%= f.select :d_month, ['Spring', 'Summer', 'Autumn', 'Winter'] + (1..12).to_a, include_blank: true %> + <%= f.select :d_month, Trait::Seasons + (1..12).to_a, include_blank: true %> <%= f.select :d_day, [''] + (1..31).to_a %>
From 2dccffb26931c4e2fc7c28cbe60bccd7598ec163 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Mon, 26 Oct 2015 17:17:13 -0500 Subject: [PATCH 20/51] Added migration to add time_zone to sites. --- ...0151023163648_replace_local_time_with_time_zone.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 db/migrate/20151023163648_replace_local_time_with_time_zone.rb diff --git a/db/migrate/20151023163648_replace_local_time_with_time_zone.rb b/db/migrate/20151023163648_replace_local_time_with_time_zone.rb new file mode 100644 index 000000000..76d7c6e5c --- /dev/null +++ b/db/migrate/20151023163648_replace_local_time_with_time_zone.rb @@ -0,0 +1,11 @@ +class ReplaceLocalTimeWithTimeZone < ActiveRecord::Migration + def up + add_column :sites, :time_zone, :text + remove_column :sites, :local_time + end + + def down + add_column :sites, :local_time, :integer + remove_column :sites, :time_zone + end +end From e258e92273a99d875cb46690a496b850c89697e1 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Wed, 28 Oct 2015 16:53:52 -0500 Subject: [PATCH 21/51] Added Javascript to Trait/Edit page to make site changes adjust the timezone informatin display. Updated fixtures and tests to go with trait date/time-related changes. --- app/controllers/sites_controller.rb | 23 +++++++++++++ app/models/trait.rb | 10 ++++-- app/views/sites/show.html.erb | 2 -- app/views/traits/edit.html.erb | 46 ++++++++++++++++++++++--- config/routes.rb | 1 + public/stylesheets/base.css | 4 +++ spec/features/trait_integration_spec.rb | 1 + spec/models/trait_spec.rb | 4 +-- test/fixtures/sites.yml | 2 -- 9 files changed, 81 insertions(+), 12 deletions(-) diff --git a/app/controllers/sites_controller.rb b/app/controllers/sites_controller.rb index a7c3f33ec..1e66b24b2 100644 --- a/app/controllers/sites_controller.rb +++ b/app/controllers/sites_controller.rb @@ -7,6 +7,29 @@ class SitesController < ApplicationController require 'csv' #AJAX Calls + + # Returns the name of the time zone for the site whose id is passed in the + # HTTP parameter "id"; or returns 'UTC' if no site having that id is found or + # if the time_zone attribute of the site found is NULL. + def get_timezone + begin + site = Site.find(params[:id]) + if site.time_zone.nil? + tz = 'UTC - site timezone unknown' + else + tz = site.time_zone + end + rescue ActiveRecord::RecordNotFound + tz = 'UTC' + end + + respond_to do |format| + format.text { + render(text: tz) + } + end + end + def linked @citation = Citation.find(session["citation"]) @site = Site.find(params[:id]) diff --git a/app/models/trait.rb b/app/models/trait.rb index b69e9d35b..e611b804d 100644 --- a/app/models/trait.rb +++ b/app/models/trait.rb @@ -359,12 +359,18 @@ def self.search_columns return ["traits.id"] end + # Returns the time zone of the associated site or "UTC" if no there is no + # associated site or if its time_zone attribute is blank. def site_timezone begin - zone = (Site.find site_id).time_zone + zone = site.time_zone + if zone.blank? + zone = 'UTC' + end rescue - zone = 'UTC' + zone = 'UTC' # site not found end + return zone end diff --git a/app/views/sites/show.html.erb b/app/views/sites/show.html.erb index 3b8e98d28..c5a455f1a 100644 --- a/app/views/sites/show.html.erb +++ b/app/views/sites/show.html.erb @@ -42,8 +42,6 @@
<%= @site.masl %>
Soil:
<%= @site.soil %> -
Local Time:
-
<%= @site.local_time %>
Espg:
<%= @site.espg %>
% Clay:
diff --git a/app/views/traits/edit.html.erb b/app/views/traits/edit.html.erb index cba077e7f..79ee064b8 100644 --- a/app/views/traits/edit.html.erb +++ b/app/views/traits/edit.html.erb @@ -9,6 +9,36 @@ }; <%= javascript_include_tag 'lazy/autocomplete.js' %> + + <% end %>
@@ -63,16 +93,24 @@ <%= f.select :d_day, [''] + (1..31).to_a %>
-
+
- Time (at site) + Time <%= @trait.site.nil? || !@trait.site.time_zone.blank? ? "at site" : "(UTC)" %> <%= f.select :t_hour, [''] + (0..23).to_a.collect { |i| '%02i' % i } %> <%= f.select :t_minute, [''] + (0..59).to_a.collect { |i| '%02i' % i } %>
+
+
+ Time Zone + <%= !@trait.site.nil? + ? (!@trait.site.time_zone.blank? ? @trait.site.time_zone : "UTC - site time zone unknown") + : "(no site selected)" %> +
+
-
+
<%= f.label :dateloc, "Date Level of Confidence" %> <%= f.select :dateloc, options_for_select($dateloc_drop.sort, f.object.dateloc || $dateloc_drop_default) %> @@ -82,7 +120,7 @@ <%= f.select :timeloc, options_for_select($timeloc_drop.sort, f.object.timeloc || $timeloc_drop_default),{},:class => "input-full" %>
- <--- This will be removed! + <--- These will be removed!
diff --git a/config/routes.rb b/config/routes.rb index 6ac279b46..f1b73a972 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -165,6 +165,7 @@ get :linked get :rem_citations_sites get :add_citations_sites + get :get_timezone end end diff --git a/public/stylesheets/base.css b/public/stylesheets/base.css index e49c46b98..e2662173d 100755 --- a/public/stylesheets/base.css +++ b/public/stylesheets/base.css @@ -223,6 +223,10 @@ font-weight: normal; font-size: 13px; color: #444; } + legend span.inherit { + font-weight: inherit; + font-size: inherit; + color: inherit; } /* #Misc ================================================== */ diff --git a/spec/features/trait_integration_spec.rb b/spec/features/trait_integration_spec.rb index dad244258..3b3c00220 100644 --- a/spec/features/trait_integration_spec.rb +++ b/spec/features/trait_integration_spec.rb @@ -50,6 +50,7 @@ fill_in 'trait_stat', :with => '7.76' fill_in 'trait_n', :with => '3' fill_in 'trait_notes', :with => 'Research Interwebs Papers Research Interwebs PapersResearch Interwebs PapersResearch Interwebs Papers' + select('1: Aliartos, Greece - Aliartos, Greece', :from => 'trait[site_id]') click_button 'Create' diff --git a/spec/models/trait_spec.rb b/spec/models/trait_spec.rb index ec3518a8e..fc2426b8a 100644 --- a/spec/models/trait_spec.rb +++ b/spec/models/trait_spec.rb @@ -7,8 +7,8 @@ t.invalid?.should == true end - it 'should be valid if a valid mean, variable_id, and access_level are given' do - t = Trait.new mean: 6, access_level: 1, variable_id: 1 + it 'should be valid if a valid mean, variable_id, site_id, and access_level are given' do + t = Trait.new mean: 6, access_level: 1, variable_id: 1, site_id: 1 t.invalid?.should == false end diff --git a/test/fixtures/sites.yml b/test/fixtures/sites.yml index 5d0c405a3..a3a0eacd3 100644 --- a/test/fixtures/sites.yml +++ b/test/fixtures/sites.yml @@ -14,7 +14,6 @@ test_site: sitename: "Aliartos, Greece" greenhouse: false user_id: 1 - local_time: NULL sand_pct: NULL clay_pct: NULL geometry: # @@ -35,7 +34,6 @@ test_trait_site: sitename: "" greenhouse: false user_id: 1 - local_time: NULL sand_pct: NULL clay_pct: NULL From d187ad064b7014532c1719924401913ab1acf857 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Wed, 4 Nov 2015 11:05:39 -0600 Subject: [PATCH 22/51] Added TimesOfDay constant. Reformatting. Replaced Rails.logger with just logger. --- app/models/trait.rb | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/app/models/trait.rb b/app/models/trait.rb index e611b804d..881c12234 100644 --- a/app/models/trait.rb +++ b/app/models/trait.rb @@ -5,7 +5,9 @@ class Trait < ActiveRecord::Base attr_writer :d_year, :d_month, :d_day, :t_hour, :t_minute Seasons = ['Spring', 'Summer', 'Autumn', 'Winter'] + TimesOfDay = ['morning', 'mid-day', 'afternoon', 'night'] + # Custom accessors for virtual attributes. def d_year if !@d_year.nil? return @d_year @@ -163,11 +165,11 @@ def consistent_date_and_time_fields case d_month when 'Spring' d_month = 4 - when 'Summer' + when 'Summer' d_month = 7 - when 'Autumn' + when 'Autumn' d_month = 10 - when 'Winter' + when 'Winter' d_month = 1 end when 6 @@ -180,11 +182,11 @@ def consistent_date_and_time_fields case d_month when 'Spring' d_month = 4 - when 'Summer' + when 'Summer' d_month = 7 - when 'Autumn' + when 'Autumn' d_month = 10 - when 'Winter' + when 'Winter' d_month = 1 end when 6 @@ -388,14 +390,14 @@ def process_datetime_input minute = @t_minute.to_i - Rails.logger.info("values of @d_year, @d_month, @d_day, @t_hour, @t_minute are #{@d_year}, #{@d_month}, #{@d_day}, #{@t_hour}, #{@t_minute} with types #{@d_year.class}, #{@d_month.class}, #{@d_day.class}, #{@t_hour.class}, #{@t_minute.class}") + logger.info("values of @d_year, @d_month, @d_day, @t_hour, @t_minute are #{@d_year}, #{@d_month}, #{@d_day}, #{@t_hour}, #{@t_minute} with types #{@d_year.class}, #{@d_month.class}, #{@d_day.class}, #{@t_hour.class}, #{@t_minute.class}") - Rails.logger.info("d_year = #{d_year} and @d_year = #{@d_year}") - Rails.logger.info("d_month = #{d_month} and @d_month = #{@d_month}") - Rails.logger.info("d_day = #{d_day} and @d_day = #{@d_day}") - Rails.logger.info("t_hour = #{t_hour} and @t_hour = #{@t_hour}") - Rails.logger.info("t_minute = #{t_minute} and @t_minute = #{@t_minute}") + logger.info("d_year = #{d_year} and @d_year = #{@d_year}") + logger.info("d_month = #{d_month} and @d_month = #{@d_month}") + logger.info("d_day = #{d_day} and @d_day = #{@d_day}") + logger.info("t_hour = #{t_hour} and @t_hour = #{@t_hour}") + logger.info("t_minute = #{t_minute} and @t_minute = #{@t_minute}") # Supply missing year if needed: @@ -477,14 +479,14 @@ def process_datetime_input end begin - Rails.logger.info("values of year, month, day, hour, minute are #{year}, #{month}, #{day}, #{hour}, #{minute} with types #{year.class}, #{month.class}, #{day.class}, #{hour.class}, #{minute.class}") + logger.info("values of year, month, day, hour, minute are #{year}, #{month}, #{day}, #{hour}, #{minute} with types #{year.class}, #{month.class}, #{day.class}, #{hour.class}, #{minute.class}") t_utc = utctime_from_sitetime(year, month, day, hour, minute) # DateTime.new(year, month, day, hour, minute, 0, timezone_offset).utc rescue => e - Rails.logger.info("in apply offset, got this error: #{e.message}") + logger.info("in apply offset, got this error: #{e.message}") return false end - Rails.logger.info("values of @d_year, @d_month, @d_day, @t_hour, @t_minute are #{@d_year}, #{@d_month}, #{@d_day}, #{@t_hour}, #{@t_minute} with types #{@d_year.class}, #{@d_month.class}, #{@d_day.class}, #{@t_hour.class}, #{@t_minute.class}") - Rails.logger.info("t_utc = #{t_utc}") + logger.info("values of @d_year, @d_month, @d_day, @t_hour, @t_minute are #{@d_year}, #{@d_month}, #{@d_day}, #{@t_hour}, #{@t_minute} with types #{@d_year.class}, #{@d_month.class}, #{@d_day.class}, #{@t_hour.class}, #{@t_minute.class}") + logger.info("t_utc = #{t_utc}") if t_utc.year == 9995 || t_utc.year == 9997 t_utc.change(year: 9996) From 9ea7a97a1ed46c7b4e07b4a8e2ccce9830b2e7ca Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Wed, 4 Nov 2015 14:24:51 -0600 Subject: [PATCH 23/51] Re-arranged code into sections and added additional comments. --- app/models/trait.rb | 254 +++++++++++++++++++++++++++++--------------- 1 file changed, 167 insertions(+), 87 deletions(-) diff --git a/app/models/trait.rb b/app/models/trait.rb index 881c12234..b5dd9db43 100644 --- a/app/models/trait.rb +++ b/app/models/trait.rb @@ -1,13 +1,88 @@ class Trait < ActiveRecord::Base - # Passed from controller for validation of ability - attr_accessor :current_user - attr_writer :d_year, :d_month, :d_day, :t_hour, :t_minute + #-- + ### Module Usage ### + + include Overrides + + extend DataAccess # provides all_limited + + extend SimpleSearch + SEARCH_INCLUDES = %w{ citation variable specie site treatment } + SEARCH_FIELDS = %w{ traits.id traits.mean traits.n traits.stat traits.statname variables.name species.genus citations.author sites.sitename treatments.name } + + + + #-- + ### Associations ### + + has_many :covariates + has_many :variables, :through => :covariates + + belongs_to :variable + belongs_to :site + belongs_to :specie + belongs_to :citation + belongs_to :treatment + belongs_to :cultivar + belongs_to :user + belongs_to :entity + belongs_to :ebi_method, :class_name => 'Methods', :foreign_key => 'method_id' + + + + #-- + ### Scopes ### + + scope :sorted_order, lambda { |order| order(order).includes(SEARCH_INCLUDES) } + scope :search, lambda { |search| where(simple_search(search)) } + scope :exclude_api, where("checked != ?","-1") + scope :citation, lambda { |citation| + if citation.nil? + {} + else + where("citation_id = ?", citation) + end + } + + + + #-- + ### Callbacks ### + + before_save :process_datetime_input + + + + #-- + ### Constants ### Seasons = ['Spring', 'Summer', 'Autumn', 'Winter'] TimesOfDay = ['morning', 'mid-day', 'afternoon', 'night'] - # Custom accessors for virtual attributes. + + + #-- + ### Virtual Attributes ### + + # Passed from controller for validation of ability + attr_accessor :current_user + + attr_writer :d_year + attr_writer :d_month + attr_writer :d_day + attr_writer :t_hour + attr_writer :t_minute + + + #-- + ## Custom accessors for virtual attributes. ## + + # A getter for the +d_year+ virtual attribute. If the @+d_year+ instance + # variable has been set, it is used. (This occurs, for example, when + # returning to a form that failed validation.) Otherwise, the value is + # computed from these persistent (i.e., database-backed) attributes: date, + # dateloc, and site.time_zone. def d_year if !@d_year.nil? return @d_year @@ -24,6 +99,12 @@ def d_year raise end end + + # A getter for the +d_month+ virtual attribute. If the @+d_month+ instance + # variable has been set, it is used. (This occurs, for example, when + # returning to a form that failed validation.) Otherwise, the value is + # computed from these persistent (i.e., database-backed) attributes: date, + # dateloc, and site.time_zone. def d_month if !@d_month.nil? return @d_month @@ -53,6 +134,12 @@ def d_month raise end end + + # A getter for the +d_day+ virtual attribute. If the @+d_day+ instance + # variable has been set, it is used. (This occurs, for example, when + # returning to a form that failed validation.) Otherwise, the value is + # computed from these persistent (i.e., database-backed) attributes: date, + # dateloc, and site.time_zone. def d_day if !@d_day.nil? return @d_day @@ -69,6 +156,12 @@ def d_day raise end end + + # A getter for the +t_hour+ virtual attribute. If the @+t_hour+ instance + # variable has been set, it is used. (This occurs, for example, when + # returning to a form that failed validation.) Otherwise, the value is + # computed from these persistent (i.e., database-backed) attributes: date, + # dateloc, and site.time_zone. def t_hour if !@t_hour.nil? return @t_hour @@ -87,6 +180,12 @@ def t_hour raise end end + + # A getter for the +t_minute+ virtual attribute. If the @+t_minute+ instance + # variable has been set, it is used. (This occurs, for example, when + # returning to a form that failed validation.) Otherwise, the value is + # computed from these persistent (i.e., database-backed) attributes: date, + # dateloc, and site.time_zone. def t_minute if !@t_minute.nil? return @t_minute @@ -104,35 +203,31 @@ def t_minute end end - before_save :process_datetime_input - - include Overrides - - extend DataAccess # provides all_limited - - extend SimpleSearch - SEARCH_INCLUDES = %w{ citation variable specie site treatment } - SEARCH_FIELDS = %w{ traits.id traits.mean traits.n traits.stat traits.statname variables.name species.genus citations.author sites.sitename treatments.name } - - has_many :covariates - has_many :variables, :through => :covariates - - belongs_to :variable - belongs_to :site - belongs_to :specie - belongs_to :citation - belongs_to :treatment - belongs_to :cultivar - belongs_to :user - belongs_to :entity - belongs_to :ebi_method, :class_name => 'Methods', :foreign_key => 'method_id' + # Returns the time zone of the associated site or "UTC" if no there is no + # associated site or if its time_zone attribute is blank. + def site_timezone + begin + zone = site.time_zone + if zone.blank? + zone = 'UTC' + end + rescue + zone = 'UTC' # site not found + end + return zone + end - # VALIDATION + #-- + ### VALIDATION ### - ## Validation methods + #-- + ## Validation methods ## + # Validation Method: Check that the five time/date fields represent a valid + # date and time and are consistent with our conventions for allowable partial + # information about dates and times. def consistent_date_and_time_fields # use local copies of instance variables @@ -203,11 +298,28 @@ def consistent_date_and_time_fields end end - ## Validation callback methods + # Validation Method: Only allow admins/managers to change traits marked as failed. + def can_change_checked + errors.add(:checked, "You do not have permission to change") if + checked == -1 and current_user.page_access_level > 2 + end - ## Validation callbacks + # Validation Method: Check that the mean is in the range stipulated for the variable. + # To do: change the database type of min and max and constrain to be + # non-null so that these tests can be simplified. + def mean_in_range + return if mean.blank? || variable.blank? # validates_presence_of should handle this error + if variable.min != "-Infinity" and mean < variable.min.to_f + errors.add(:mean, "The value of mean for the #{variable.name} trait must be at least #{variable.min}.") + end + if variable.max != "Infinity" and mean > variable.max.to_f + errors.add(:mean, "The value of mean for the #{variable.name} trait must be at most #{variable.max}.") + end + end - ## Validations + + #-- + ## Validations ## validates_presence_of :mean, :access_level, :site validates_numericality_of :mean @@ -225,34 +337,9 @@ def consistent_date_and_time_fields validate :mean_in_range - # Only allow admins/managers to change traits marked as failed. - def can_change_checked - errors.add(:checked, "You do not have permission to change") if - checked == -1 and current_user.page_access_level > 2 - end - # To do: change the database type of min and max and constrain to be - # non-null so that these tests can be simplified. - def mean_in_range - return if mean.blank? || variable.blank? # validates_presence_of should handle this error - if variable.min != "-Infinity" and mean < variable.min.to_f - errors.add(:mean, "The value of mean for the #{variable.name} trait must be at least #{variable.min}.") - end - if variable.max != "Infinity" and mean > variable.max.to_f - errors.add(:mean, "The value of mean for the #{variable.name} trait must be at most #{variable.max}.") - end - end - - scope :sorted_order, lambda { |order| order(order).includes(SEARCH_INCLUDES) } - scope :search, lambda { |search| where(simple_search(search)) } - scope :exclude_api, where("checked != ?","-1") - scope :citation, lambda { |citation| - if citation.nil? - {} - else - where("citation_id = ?", citation) - end - } + #-- + ### CSV Formats ### comma do id @@ -318,6 +405,11 @@ def mean_in_range site :lat => 'Latitude', :lon => 'Longitude' end + + + #-- + ### Presentation Methods ### + def pretty_date date.nil? ? '[unspecified]' : date_in_site_timezone.to_formatted_s(date_format) + ([7, 9, 97].include?(dateloc) || timeloc != 9 ? '' : " (#{site_timezone})") end @@ -361,24 +453,12 @@ def self.search_columns return ["traits.id"] end - # Returns the time zone of the associated site or "UTC" if no there is no - # associated site or if its time_zone attribute is blank. - def site_timezone - begin - zone = site.time_zone - if zone.blank? - zone = 'UTC' - end - rescue - zone = 'UTC' # site not found - end - return zone - end - private + # Computes the values to store for date, dateloc, and timeloc based on the + # values of the virtual attributes. def process_datetime_input # Use local variables year, month, day, hour, minute for instantiating @@ -387,7 +467,7 @@ def process_datetime_input month = d_month.to_i day = d_day.to_i hour = t_hour.to_i - minute = @t_minute.to_i + minute = t_minute.to_i logger.info("values of @d_year, @d_month, @d_day, @t_hour, @t_minute are #{@d_year}, #{@d_month}, #{@d_day}, #{@t_hour}, #{@t_minute} with types #{@d_year.class}, #{@d_month.class}, #{@d_day.class}, #{@t_hour.class}, #{@t_minute.class}") @@ -534,23 +614,23 @@ def compute_dateloc(y, m, d) def date_format case dateloc - when 9 + when 9 :no_date_data - when 8 + when 8 :year_only - when 7 + when 7 :season_and_year - when 6 + when 6 :month_and_year - when 5.5 + when 5.5 :week_of_year - when 5 + when 5 :year_month_day - when 97 + when 97 :season_only - when 96 + when 96 :month_only - when 95 + when 95 :month_day when nil :unspecified_dateloc @@ -561,15 +641,15 @@ def date_format def time_format case timeloc - when 9 + when 9 :no_time_data - when 4 + when 4 :time_of_day - when 3 + when 3 :hour_only - when 2 + when 2 :hour_minutes - when 1 + when 1 :hour_minutes_seconds when nil :unspecified_timeloc From e1c1718b0ced822ce2b0b046264f0f63543e061a Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Wed, 4 Nov 2015 17:14:01 -0600 Subject: [PATCH 24/51] Simplified code, especially the process_datetime_input method, which now simply relies on computations in methods process_datetime_input, computed_dateloc, and computed_timeloc. --- app/models/trait.rb | 222 ++++++++++++-------------------------------- 1 file changed, 61 insertions(+), 161 deletions(-) diff --git a/app/models/trait.rb b/app/models/trait.rb index b5dd9db43..a6897532c 100644 --- a/app/models/trait.rb +++ b/app/models/trait.rb @@ -230,72 +230,55 @@ def site_timezone # information about dates and times. def consistent_date_and_time_fields - # use local copies of instance variables - d_year = @d_year - d_month = @d_month - d_day = @d_day - t_hour = @t_hour - t_minute = @t_minute - - - begin - dateloc = compute_dateloc(d_year, d_month, d_day) - rescue => e - errors.add(:base, e.message) - end - - # Require an hour if minutes are specified: - if t_hour.blank? && !t_minute.blank? - errors.add(:base, "If you specify minutes, you must specify the hour.") - end - - # set defaults if needed - case dateloc + # Set defaults for unspecified components and convert the supplied ones to + # integers. + case computed_dateloc when 9 - d_year, d_month, d_day = 9999, 1, 1 + year, month, day = 9996, 1, 1 when 8 - d_month, d_day = 1, 1 + year, month, day = d_year.to_i, 1, 1 when 7 - d_day = 1 case d_month when 'Spring' - d_month = 4 + month = 4 when 'Summer' - d_month = 7 + month = 7 when 'Autumn' - d_month = 10 + month = 10 when 'Winter' - d_month = 1 + month = 1 end + year, day = d_year.to_i, 1 when 6 - d_day = 1 + year, month, day = d_year.to_i, d_month.to_i, 1 when 5 - # nothing to do + year, month, day = d_year.to_i, d_month.to_i, d_day.to_i when 97 - d_year = 9996 - d_day = 1 case d_month when 'Spring' - d_month = 4 + month = 4 when 'Summer' - d_month = 7 + month = 7 when 'Autumn' - d_month = 10 + month = 10 when 'Winter' - d_month = 1 + month = 1 end - when 6 - d_year = 9996 - d_day = 1 - when 5 - d_year = 9996 + year, day = 9996, 1 + when 96 + year, month, day = 9996, d_month.to_i, 1 + when 95 + year, month, day = 9996, d_month.to_i, d_day.to_i end - begin - t = utctime_from_sitetime(d_year.to_i, d_month.to_i, d_day.to_i, t_hour.to_i, t_minute.to_i) #DateTime.new(d_year.to_i, d_month.to_i, d_day.to_i, t_hour.to_i, t_minute.to_i, 0, timezone_offset) - rescue => e - errors.add(:base, "With dateloc = #{dateloc}, can't make DateTime from #{d_year}, #{d_month}, #{d_day}, #{t_hour}, #{t_minute}") - end + hour, minute = t_hour.to_i, t_minute.to_i + # This will catch some illegal dates (1900-02-29, for example) that Time.new + # will silently covert to an acceptible date (1900-03-01, in this example). + DateTime.new(year, month, day, hour, minute) + + @computed_date = utctime_from_sitetime(year, month, day, hour, minute) + rescue => e + errors.add(:base, e.message) end # Validation Method: Only allow admins/managers to change traits marked as failed. @@ -460,126 +443,23 @@ def self.search_columns # Computes the values to store for date, dateloc, and timeloc based on the # values of the virtual attributes. def process_datetime_input + self.dateloc = computed_dateloc + self.timeloc = computed_timeloc + self.date = @computed_date # this is set in the consistent_date_and_time_fields validate method + end - # Use local variables year, month, day, hour, minute for instantiating - # DateTime object; initialize them from form fields: - year = d_year.to_i - month = d_month.to_i - day = d_day.to_i - hour = t_hour.to_i - minute = t_minute.to_i - - - logger.info("values of @d_year, @d_month, @d_day, @t_hour, @t_minute are #{@d_year}, #{@d_month}, #{@d_day}, #{@t_hour}, #{@t_minute} with types #{@d_year.class}, #{@d_month.class}, #{@d_day.class}, #{@t_hour.class}, #{@t_minute.class}") - - - logger.info("d_year = #{d_year} and @d_year = #{@d_year}") - logger.info("d_month = #{d_month} and @d_month = #{@d_month}") - logger.info("d_day = #{d_day} and @d_day = #{@d_day}") - logger.info("t_hour = #{t_hour} and @t_hour = #{@t_hour}") - logger.info("t_minute = #{t_minute} and @t_minute = #{@t_minute}") - - - # Supply missing year if needed: - - if @d_year.blank? - if @d_month.blank? - if !@d_day.blank? - # shouldn't ever get here - raise "If you set a day, you must also set a month." - end - year = 9999 - month = 1 - day = 1 - self.dateloc = 9 - else - year = 9996 - if ['Spring', 'Summer', 'Autumn', 'Winter'].include? @d_month - if !@d_day.blank? - # shouldn't ever get here - raise "If you set a season, you must leave day blank." - end - month = { 'Spring' => 4, 'Summer' => 7, 'Autumn' => 10, 'Winter' => 1 }[@d_month] - day = 1 - self.dateloc = 97 - else # month is an integer - if @d_day.blank? - day = 1 - self.dateloc = 96 - else - self.dateloc = 95 - end - end - end - else # year is given - if @d_month.blank? - if !@d_day.blank? - # shouldn't ever get here - raise "If you set a day, you must also set a month." - end - month = 1 - day = 1 - self.dateloc = 8 - else - if ['Spring', 'Summer', 'Autumn', 'Winter'].include? @d_month - if !@d_day.blank? - # shouldn't ever get here - raise "If you set a season, you must leave day blank." - end - month = { 'Spring' => 4, 'Summer' => 7, 'Autumn' => 10, 'Winter' => 1 }[@d_month] - day = 1 - self.dateloc = 7 - else # month is an integer - if @d_day.blank? - day = 1 - self.dateloc = 6 - else - self.dateloc = 5 - end - end - end - end - - - if @t_hour.blank? - if !@t_minute.blank? - # shouldn't ever get here - raise "If you set a minute, you must set the hour." - end - hour = 0 - minute = 0 - self.timeloc = 9 - else # hour is given - if @t_minute.blank? - minute = 0 - self.timeloc = 3 - else - self.timeloc = 2 - end - end - - begin - logger.info("values of year, month, day, hour, minute are #{year}, #{month}, #{day}, #{hour}, #{minute} with types #{year.class}, #{month.class}, #{day.class}, #{hour.class}, #{minute.class}") - t_utc = utctime_from_sitetime(year, month, day, hour, minute) # DateTime.new(year, month, day, hour, minute, 0, timezone_offset).utc - rescue => e - logger.info("in apply offset, got this error: #{e.message}") - return false - end - logger.info("values of @d_year, @d_month, @d_day, @t_hour, @t_minute are #{@d_year}, #{@d_month}, #{@d_day}, #{@t_hour}, #{@t_minute} with types #{@d_year.class}, #{@d_month.class}, #{@d_day.class}, #{@t_hour.class}, #{@t_minute.class}") - logger.info("t_utc = #{t_utc}") - - if t_utc.year == 9995 || t_utc.year == 9997 - t_utc.change(year: 9996) - end - - self.date = t_utc - end + def computed_dateloc + # Convenience variables; since we only use this method in cases where the + # accessor returns exactly the same value as the instance variable, we may + # as well use the latter since it's faster. + y = @d_year + m = @d_month + d = @d_day - def compute_dateloc(y, m, d) if !d.blank? if m.blank? - raise "If month is blank, day must be also." + raise "If you set a day, you must also set a month." elsif Seasons.include?(m) raise "If you select a season, day must be blank." else @@ -612,6 +492,22 @@ def compute_dateloc(y, m, d) end end + def computed_timeloc + if @t_hour.blank? + if !@t_minute.blank? + # shouldn't ever get here + raise "If you specify minutes, you must specify the hour.!" + end + 9 + else # hour is given + if @t_minute.blank? + 3 + else + 2 + end + end + end + def date_format case dateloc when 9 @@ -666,6 +562,10 @@ def utctime_from_sitetime(y, m, d, hr, min) return utctime.to_datetime end + # Convert the Trait date attribute (which is an ActiveSupport::TimeWithZone + # object) to a new TimeWithZone object representing the time in site_timezone. + # This is used in various methods used for presenting the date and time to the + # user, which is always done in local (site) time. def date_in_site_timezone date.in_time_zone(site_timezone) end From 905dabf175bbc76b82a3300d3f5c0753483c66c3 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Thu, 5 Nov 2015 10:27:34 -0600 Subject: [PATCH 25/51] Updated Trait model and time_formats initializer to support time-of-day selection and display. --- app/models/trait.rb | 56 +++++++++++++++++++++++------ config/initializers/time_formats.rb | 6 +--- 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/app/models/trait.rb b/app/models/trait.rb index a6897532c..3e69632ac 100644 --- a/app/models/trait.rb +++ b/app/models/trait.rb @@ -171,7 +171,18 @@ def t_hour when 9 nil when 4 - 'morning' + case date_in_site_timezone.hour + when 9 + 'morning' + when 12 + 'mid-day' + when 15 + 'afternoon' + when 0 + 'night' + else + raise + end when 1, 2, 3 date.nil? ? '' : date_in_site_timezone.strftime('%H') when nil @@ -271,7 +282,28 @@ def consistent_date_and_time_fields year, month, day = 9996, d_month.to_i, d_day.to_i end - hour, minute = t_hour.to_i, t_minute.to_i + case computed_timeloc + when 9 + hour, minute = 0, 0 + when 4 + case t_hour + when 'morning' + hour, minute = 9, 0 + when 'mid-day' + hour, minute = 12, 0 + when 'afternoon' + hour, minute = 15, 0 + when 'night' + hour, minute = 0, 0 + end + when 3 + hour, minute = t_hour.to_i, 0 + when 2 + hour, minute = t_hour.to_id, t_minute.to_i + else + raise + end + # This will catch some illegal dates (1900-02-29, for example) that Time.new # will silently covert to an acceptible date (1900-03-01, in this example). DateTime.new(year, month, day, hour, minute) @@ -493,18 +525,22 @@ def computed_dateloc end def computed_timeloc - if @t_hour.blank? - if !@t_minute.blank? - # shouldn't ever get here + if !@t_minute.blank? + if @t_hour.blank? raise "If you specify minutes, you must specify the hour.!" - end - 9 - else # hour is given - if @t_minute.blank? - 3 + elsif TimesOfDay.include?(@t_hour) + raise "If you select a time of day, minutes must be blank." else 2 end + else # @t_minute is blank + if @t_hour.blank? + 9 + elsif TimesOfDay.include?(@t_hour) + 4 + else + 3 + end end end diff --git a/config/initializers/time_formats.rb b/config/initializers/time_formats.rb index 9f9481449..25acbf02a 100644 --- a/config/initializers/time_formats.rb +++ b/config/initializers/time_formats.rb @@ -58,7 +58,7 @@ # timeloc 4: time_of_day: ->(time) do case time.hour - when 1 + when 0 'night' when 9 'morning' @@ -66,10 +66,6 @@ 'mid-day' when 15 'afternoon' - when 18 - 'evening' - when 23 - 'late evening' else '[Invalid time-of-day designation]' end From e6551884f2acd6f32ac16d111cb667ce68b83bb1 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Thu, 5 Nov 2015 14:47:48 -0600 Subject: [PATCH 26/51] Added constants for Months, Days, Hours, and Minutes to the Trait model and make use of them in the traits views. Typo fix in Trait model: to_id --> to_i Added JavaScript-enabled timezone display to New Trait view. --- app/models/trait.rb | 8 +++-- app/views/traits/edit.html.erb | 17 ++++++----- app/views/traits/new.html.erb | 53 +++++++++++++++++++++++++++++----- 3 files changed, 61 insertions(+), 17 deletions(-) diff --git a/app/models/trait.rb b/app/models/trait.rb index 3e69632ac..2b173b9bb 100644 --- a/app/models/trait.rb +++ b/app/models/trait.rb @@ -60,6 +60,10 @@ class Trait < ActiveRecord::Base Seasons = ['Spring', 'Summer', 'Autumn', 'Winter'] TimesOfDay = ['morning', 'mid-day', 'afternoon', 'night'] + Months = 1..12 + Days = 1..31 + Hours = 0..23 + Minutes = 0..59 #-- @@ -299,7 +303,7 @@ def consistent_date_and_time_fields when 3 hour, minute = t_hour.to_i, 0 when 2 - hour, minute = t_hour.to_id, t_minute.to_i + hour, minute = t_hour.to_i, t_minute.to_i else raise end @@ -527,7 +531,7 @@ def computed_dateloc def computed_timeloc if !@t_minute.blank? if @t_hour.blank? - raise "If you specify minutes, you must specify the hour.!" + raise "If you specify minutes, you must specify the hour." elsif TimesOfDay.include?(@t_hour) raise "If you select a time of day, minutes must be blank." else diff --git a/app/views/traits/edit.html.erb b/app/views/traits/edit.html.erb index 79ee064b8..04d4b9079 100644 --- a/app/views/traits/edit.html.erb +++ b/app/views/traits/edit.html.erb @@ -11,6 +11,7 @@ <%= javascript_include_tag 'lazy/autocomplete.js' %> <%= javascript_include_tag 'lazy/autocomplete.js' %> + + <% end %>
@@ -67,16 +98,24 @@
Date - <%= f.select :d_year, [''] + (1800..Time.now.year).to_a %> - <%= f.select :d_month, Trait::Seasons + (1..12).to_a, include_blank: true %> - <%= f.select :d_day, [''] + (1..31).to_a %> + <%= f.select :d_year, (1800..Time.now.year).to_a, include_blank: true %> + <%= f.select :d_month, Trait::Seasons + Trait::Months.to_a, include_blank: true %> + <%= f.select :d_day, Trait::Days.to_a, include_blank: true %>
-
+
+
+ Time <%= @trait.site.nil? || !@trait.site.time_zone.blank? ? "at site" : "(UTC)" %> + <%= f.select :t_hour, Trait::TimesOfDay + Trait::Hours.to_a.collect { |i| '%02i' % i }, include_blank: true %> + <%= f.select :t_minute, Trait::Minutes.to_a.collect { |i| '%02i' % i }, include_blank: true %> +
+
+
- Time (at site) - <%= f.select :t_hour, [''] + (0..23).to_a.collect { |i| '% 2i' % i } %> - <%= f.select :t_minute, [''] + (0..59).to_a.collect { |i| '%02i' % i } %> + Time Zone + <%= @trait.site.nil? ? "(no site selected)" + : @trait.site.time_zone.blank? ? "UTC - site time zone unknown" + : @trait.site.time_zone %>
From 50e038385cfbd8a56e36525d30833312a8cfd8f4 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Thu, 5 Nov 2015 16:18:58 -0600 Subject: [PATCH 27/51] Better error handling, especially: For validation errors, add to the errors hash rather than raising a separate exception; that way we see all validation errors, not just the first one we happen to hit. --- app/models/trait.rb | 48 ++++++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/app/models/trait.rb b/app/models/trait.rb index 2b173b9bb..37433de91 100644 --- a/app/models/trait.rb +++ b/app/models/trait.rb @@ -100,7 +100,7 @@ def d_year when nil nil else - raise + raise "In d_year, unrecognized dateloc value." end end @@ -128,14 +128,14 @@ def d_month when 10 'Autumn' else - raise + raise "In d_month, month value is not appropriate for representing a season." end when 5, 5.5, 6, 95, 96 date.nil? ? '' : date_in_site_timezone.month when nil nil else - raise + raise "In d_month, unrecognized dateloc value." end end @@ -157,7 +157,7 @@ def d_day when nil nil else - raise + raise "In d_day, unrecognized dateloc value." end end @@ -185,14 +185,14 @@ def t_hour when 0 'night' else - raise + raise "In t_hour, hour is not appropriate for representing a time of day." end when 1, 2, 3 date.nil? ? '' : date_in_site_timezone.strftime('%H') when nil nil else - raise + raise "In t_hour, unrecognized timeloc value." end end @@ -214,7 +214,7 @@ def t_minute when nil nil else - raise + raise "In t_minute, unrecognized timeloc value." end end @@ -284,6 +284,8 @@ def consistent_date_and_time_fields year, month, day = 9996, d_month.to_i, 1 when 95 year, month, day = 9996, d_month.to_i, d_day.to_i + else + raise "Unexpected computed_dateloc value in Trait#consistent_date_and_time_fields." end case computed_timeloc @@ -305,16 +307,22 @@ def consistent_date_and_time_fields when 2 hour, minute = t_hour.to_i, t_minute.to_i else - raise + raise "Unexpected computed_timeloc value in Trait#consistent_date_and_time_fields." end - # This will catch some illegal dates (1900-02-29, for example) that Time.new - # will silently covert to an acceptible date (1900-03-01, in this example). - DateTime.new(year, month, day, hour, minute) + begin + + # This will catch some illegal dates (1900-02-29, for example) that Time.new + # will silently covert to an acceptible date (1900-03-01, in this example). + DateTime.new(year, month, day, hour, minute) + + # Store the computed date for use by the before_save call-back "process_datetime_input". + @computed_date = utctime_from_sitetime(year, month, day, hour, minute) + + rescue ArgumentError => e + errors.add(:date, "is invalid") + end - @computed_date = utctime_from_sitetime(year, month, day, hour, minute) - rescue => e - errors.add(:base, e.message) end # Validation Method: Only allow admins/managers to change traits marked as failed. @@ -495,9 +503,11 @@ def computed_dateloc if !d.blank? if m.blank? - raise "If you set a day, you must also set a month." + errors.add(:base, "If you set a day, you must also set a month.") + 9 elsif Seasons.include?(m) - raise "If you select a season, day must be blank." + errors.add(:base, "If you select a season, day must be blank.") + 9 else if y.blank? 95 @@ -531,9 +541,11 @@ def computed_dateloc def computed_timeloc if !@t_minute.blank? if @t_hour.blank? - raise "If you specify minutes, you must specify the hour." + errors.add(:base, "If you specify minutes, you must specify the hour.") + 9 elsif TimesOfDay.include?(@t_hour) - raise "If you select a time of day, minutes must be blank." + errors.add(:base, "If you select a time of day, minutes must be blank.") + 9 else 2 end From 717b93c530f99bd77d4c031585088e4183f43ff7 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Fri, 6 Nov 2015 16:06:01 -0600 Subject: [PATCH 28/51] Updated date presentation in Advanced Search results table in accordance with new date storage conventions. Dried up a lot of code: Moved Date-Time constants to Module defined in initializer. Use hash to convert Season to representative month number and vice-versa. Use hash to convert TimeOfDay to representative hour number and vice-versa. Added validation of hour. Tweaked validation of minutes. Reformated code. --- app/models/trait.rb | 153 +++++++++----------------- app/models/traits_and_yields_view.rb | 84 +++++++++++++- app/views/search/_table_body.html.erb | 4 +- config/initializers/time_formats.rb | 23 ++-- 4 files changed, 145 insertions(+), 119 deletions(-) diff --git a/app/models/trait.rb b/app/models/trait.rb index 37433de91..c831c3cf5 100644 --- a/app/models/trait.rb +++ b/app/models/trait.rb @@ -11,7 +11,7 @@ class Trait < ActiveRecord::Base SEARCH_INCLUDES = %w{ citation variable specie site treatment } SEARCH_FIELDS = %w{ traits.id traits.mean traits.n traits.stat traits.statname variables.name species.genus citations.author sites.sitename treatments.name } - + include DateTimeConstants #-- ### Associations ### @@ -29,7 +29,7 @@ class Trait < ActiveRecord::Base belongs_to :entity belongs_to :ebi_method, :class_name => 'Methods', :foreign_key => 'method_id' - + #-- ### Scopes ### @@ -54,18 +54,6 @@ class Trait < ActiveRecord::Base - #-- - ### Constants ### - - Seasons = ['Spring', 'Summer', 'Autumn', 'Winter'] - TimesOfDay = ['morning', 'mid-day', 'afternoon', 'night'] - - Months = 1..12 - Days = 1..31 - Hours = 0..23 - Minutes = 0..59 - - #-- ### Virtual Attributes ### @@ -118,18 +106,8 @@ def d_month when 8, 9 nil when 7, 97 - case date_in_site_timezone.month - when 1 - 'Winter' - when 4 - 'Spring' - when 7 - 'Summer' - when 10 - 'Autumn' - else + SeasonRepresentativeMonths.key(date_in_site_timezone.month) or raise "In d_month, month value is not appropriate for representing a season." - end when 5, 5.5, 6, 95, 96 date.nil? ? '' : date_in_site_timezone.month when nil @@ -175,18 +153,8 @@ def t_hour when 9 nil when 4 - case date_in_site_timezone.hour - when 9 - 'morning' - when 12 - 'mid-day' - when 15 - 'afternoon' - when 0 - 'night' - else + TimesOfDayRepresentativeHours.key(date_in_site_timezone.hour) or raise "In t_hour, hour is not appropriate for representing a time of day." - end when 1, 2, 3 date.nil? ? '' : date_in_site_timezone.strftime('%H') when nil @@ -218,20 +186,6 @@ def t_minute end end - # Returns the time zone of the associated site or "UTC" if no there is no - # associated site or if its time_zone attribute is blank. - def site_timezone - begin - zone = site.time_zone - if zone.blank? - zone = 'UTC' - end - rescue - zone = 'UTC' # site not found - end - return zone - end - #-- @@ -253,32 +207,14 @@ def consistent_date_and_time_fields when 8 year, month, day = d_year.to_i, 1, 1 when 7 - case d_month - when 'Spring' - month = 4 - when 'Summer' - month = 7 - when 'Autumn' - month = 10 - when 'Winter' - month = 1 - end + month = SeasonRepresentativeMonths[d_month] year, day = d_year.to_i, 1 when 6 year, month, day = d_year.to_i, d_month.to_i, 1 when 5 year, month, day = d_year.to_i, d_month.to_i, d_day.to_i when 97 - case d_month - when 'Spring' - month = 4 - when 'Summer' - month = 7 - when 'Autumn' - month = 10 - when 'Winter' - month = 1 - end + month = SeasonRepresentativeMonths[d_month] year, day = 9996, 1 when 96 year, month, day = 9996, d_month.to_i, 1 @@ -292,16 +228,8 @@ def consistent_date_and_time_fields when 9 hour, minute = 0, 0 when 4 - case t_hour - when 'morning' - hour, minute = 9, 0 - when 'mid-day' - hour, minute = 12, 0 - when 'afternoon' - hour, minute = 15, 0 - when 'night' - hour, minute = 0, 0 - end + hour = TimesOfDayRepresentativeHours[t_hour] + minute = 0 when 3 hour, minute = t_hour.to_i, 0 when 2 @@ -354,11 +282,10 @@ def mean_in_range validates_inclusion_of :access_level, in: 1..4, message: "You must select an access level" validates_presence_of :statname, :if => Proc.new { |trait| !trait.stat.blank? } validates_format_of :d_year, :with => /\A(\d{2}|\d{4})\z/, :allow_blank => true - validates_format_of :d_month, :with => /\A\d{1,2}|Spring|Summer|Winter|Autumn\z/, :allow_blank => true + validates_format_of :d_month, :with => /\A(\d{1,2}|#{Seasons.join("|")})\z/, :allow_blank => true validates_format_of :d_day, :with => /\A\d{1,2}\z/, :allow_blank => true - #validates_format_of :t_hour, :with => /\A\d{1,2}\z/, :allow_blank => true - validates_format_of :t_minute, :with => /\A\d{1,2}\z/, :allow_blank => true - #validates_format_of :timezone_offset, :with => /\A *[+-]?([01]?[0-9]|2[0-3]):(00|15|30|45):00 *\z/ + validates_format_of :t_hour, :with => /\A(\d{2}|#{TimesOfDay.join('|')})\z/, :allow_blank => true + validates_format_of :t_minute, :with => /\A\d{2}\z/, :allow_blank => true validate :consistent_date_and_time_fields validate :can_change_checked validate :mean_in_range @@ -428,17 +355,23 @@ def mean_in_range end site :sitename_state_country => 'Site Name' treatment :name_definition => 'Treatment' - citation :author_year_title => 'Author Year Title' + citation :author_year_title => 'Author Year Title' site :lat => 'Latitude', :lon => 'Longitude' end - + #-- ### Presentation Methods ### def pretty_date - date.nil? ? '[unspecified]' : date_in_site_timezone.to_formatted_s(date_format) + ([7, 9, 97].include?(dateloc) || timeloc != 9 ? '' : " (#{site_timezone})") + if date.nil? + '[unspecified]' + else + date_in_site_timezone.to_formatted_s(date_format) + + # show the site timezone next to the date only if the time is unspecified or unknown + (timeloc == 9 && [5, 6, 8, 95, 96].include?(dateloc) ? " (#{site_timezone})" : "") + end end def format_statname @@ -562,23 +495,23 @@ def computed_timeloc def date_format case dateloc - when 9 + when 9 :no_date_data - when 8 + when 8 :year_only - when 7 + when 7 :season_and_year - when 6 + when 6 :month_and_year - when 5.5 + when 5.5 :week_of_year - when 5 + when 5 :year_month_day - when 97 + when 97 :season_only - when 96 + when 96 :month_only - when 95 + when 95 :month_day when nil :unspecified_dateloc @@ -589,15 +522,15 @@ def date_format def time_format case timeloc - when 9 + when 9 :no_time_data - when 4 + when 4 :time_of_day - when 3 + when 3 :hour_only - when 2 + when 2 :hour_minutes - when 1 + when 1 :hour_minutes_seconds when nil :unspecified_timeloc @@ -605,7 +538,7 @@ def time_format :unrecognized_timeloc end end - + def utctime_from_sitetime(y, m, d, hr, min) utctime = nil Time.use_zone site_timezone do @@ -621,5 +554,19 @@ def utctime_from_sitetime(y, m, d, hr, min) def date_in_site_timezone date.in_time_zone(site_timezone) end - + + # Returns the time zone of the associated site or "UTC" if no there is no + # associated site or if its time_zone attribute is blank. + def site_timezone + begin + zone = site.time_zone + if zone.blank? + zone = 'UTC' + end + rescue + zone = 'UTC' # site not found + end + return zone + end + end diff --git a/app/models/traits_and_yields_view.rb b/app/models/traits_and_yields_view.rb index 1c1acca74..ade241b65 100644 --- a/app/models/traits_and_yields_view.rb +++ b/app/models/traits_and_yields_view.rb @@ -14,6 +14,10 @@ class TraitsAndYieldsView < ActiveRecord::Base # attr_accessor :current_user self.table_name = 'traits_and_yields_view' + + #-- + ### Module Usage ### + include ActiveModel::Serialization extend CoordinateSearch # provides coordinate_search @@ -28,6 +32,12 @@ class TraitsAndYieldsView < ActiveRecord::Base trait_description city sitename author citation_year } + + + + #-- + ### Scopes ### + scope :sorted_order, lambda { |order| order(order).includes(SEARCH_INCLUDES).order("id asc") } scope :search, lambda { |search| where(advanced_search(search)) } scope :checked, lambda { |checked_minimum| where("checked >= #{checked_minimum}") } @@ -37,6 +47,12 @@ class TraitsAndYieldsView < ActiveRecord::Base # make NumberHelper available to use inside comma block: extend ActionView::Helpers::NumberHelper + + + + #-- + ### CSV Format ### + comma do checked 'checked' do |num| if num == 1 then @@ -82,11 +98,75 @@ class TraitsAndYieldsView < ActiveRecord::Base stat 'stat' do |num| if num.nil? then "[missing]" - else + else TraitsAndYieldsView.number_with_precision(num, precision: 3)#, significant: true) - end + end end notes 'notes' end + + + #-- + ### Presentation Methods ### + + def pretty_date + if date.nil? + '[unspecified]' + else + date_in_site_timezone.to_formatted_s(date_format) + " (#{site_timezone})" + end + end + + private + + # Convert the Trait date attribute (which is an ActiveSupport::TimeWithZone + # object) to a new TimeWithZone object representing the time in site_timezone. + # This is used in various methods used for presenting the date and time to the + # user, which is always done in local (site) time. + def date_in_site_timezone + date.in_time_zone(site_timezone) + end + + # Returns the time zone of the associated site or "UTC" if no there is no + # associated site or if its time_zone attribute is blank. + def site_timezone + begin + zone = (Site.find(site_id)).time_zone + if zone.blank? + zone = 'UTC' + end + rescue + zone = 'UTC' # site not found + end + return zone + end + + def date_format + case dateloc + when 9 + :no_date_data + when 8 + :year_only + when 7 + :season_and_year + when 6 + :month_and_year + when 5.5 + :week_of_year + when 5 + :year_month_day + when 97 + :season_only + when 96 + :month_only + when 95 + :month_day + when nil + :unspecified_dateloc + else + :unrecognized_dateloc + end + end + end diff --git a/app/views/search/_table_body.html.erb b/app/views/search/_table_body.html.erb index 555ef0a27..db993352c 100644 --- a/app/views/search/_table_body.html.erb +++ b/app/views/search/_table_body.html.erb @@ -36,9 +36,7 @@ --> <%= t.lat ? t.lat.round(2) : nil %> <%= t.lon ? t.lon.round(2) : nil %> - <%= # convert timestamp to UTC and then truncate to show only the date portion: - t.date.nil? ? "" : # display the empty string if date is null - t.date.utc.to_s[0, 10] %> + <%= t.pretty_date %> <%= link_to species_path(t.species_id.to_i) do %> <%= t.scientificname.to_s %> <%= if t.commonname && !t.commonname.strip.blank? then raw("
(" + t.commonname.strip + ")") end %> diff --git a/config/initializers/time_formats.rb b/config/initializers/time_formats.rb index 25acbf02a..7047ada4c 100644 --- a/config/initializers/time_formats.rb +++ b/config/initializers/time_formats.rb @@ -1,3 +1,14 @@ +module DateTimeConstants + Seasons = ['Season: MAN', 'Season: JJA', 'Season: SON', 'Season: DJF'] + SeasonRepresentativeMonths = { 'Season: MAN' => 4, 'Season: JJA' => 7, 'Season: SON' => 10, 'Season: DJF' => 1 } + TimesOfDay = ['morning', 'mid-day', 'afternoon', 'night'] + TimesOfDayRepresentativeHours = { 'morning' => 9, 'mid-day' => 12, 'afternoon' => 15, 'night' => 0 } + + Months = 1..12 + Days = 1..31 + Hours = 0..23 + Minutes = 0..59 +end NEW_FORMATS = { @@ -25,18 +36,8 @@ # dateloc 97: season_only: ->(date) do - case date.month - when 1 - 'Winter' - when 4 - 'Spring' - when 7 - 'Summer' - when 10 - 'Autumn' - else + DateTimeConstants::SeasonRepresentativeMonths.key(date.month) || '[Invalid season designation]' - end end, # dateloc 96: From 029d5ebe9ee5ec0083382c74d7cfd2907d50fcbf Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Fri, 6 Nov 2015 17:01:35 -0600 Subject: [PATCH 29/51] Refactored code: Moved methods from Trait and TraitsAndYieldsView classes into a shared module in an intializer. --- app/models/trait.rb | 50 +---------------------- app/models/traits_and_yields_view.rb | 52 +----------------------- config/initializers/time_formats.rb | 60 ++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 98 deletions(-) diff --git a/app/models/trait.rb b/app/models/trait.rb index c831c3cf5..98f319ce4 100644 --- a/app/models/trait.rb +++ b/app/models/trait.rb @@ -493,33 +493,6 @@ def computed_timeloc end end - def date_format - case dateloc - when 9 - :no_date_data - when 8 - :year_only - when 7 - :season_and_year - when 6 - :month_and_year - when 5.5 - :week_of_year - when 5 - :year_month_day - when 97 - :season_only - when 96 - :month_only - when 95 - :month_day - when nil - :unspecified_dateloc - else - :unrecognized_dateloc - end - end - def time_format case timeloc when 9 @@ -547,26 +520,7 @@ def utctime_from_sitetime(y, m, d, hr, min) return utctime.to_datetime end - # Convert the Trait date attribute (which is an ActiveSupport::TimeWithZone - # object) to a new TimeWithZone object representing the time in site_timezone. - # This is used in various methods used for presenting the date and time to the - # user, which is always done in local (site) time. - def date_in_site_timezone - date.in_time_zone(site_timezone) - end - - # Returns the time zone of the associated site or "UTC" if no there is no - # associated site or if its time_zone attribute is blank. - def site_timezone - begin - zone = site.time_zone - if zone.blank? - zone = 'UTC' - end - rescue - zone = 'UTC' # site not found - end - return zone - end + # provides date_in_site_timezone, date_format, and site_timezone + include DateTimeUtilityMethods end diff --git a/app/models/traits_and_yields_view.rb b/app/models/traits_and_yields_view.rb index ade241b65..68b61f36f 100644 --- a/app/models/traits_and_yields_view.rb +++ b/app/models/traits_and_yields_view.rb @@ -118,55 +118,7 @@ def pretty_date end end - private - - # Convert the Trait date attribute (which is an ActiveSupport::TimeWithZone - # object) to a new TimeWithZone object representing the time in site_timezone. - # This is used in various methods used for presenting the date and time to the - # user, which is always done in local (site) time. - def date_in_site_timezone - date.in_time_zone(site_timezone) - end - - # Returns the time zone of the associated site or "UTC" if no there is no - # associated site or if its time_zone attribute is blank. - def site_timezone - begin - zone = (Site.find(site_id)).time_zone - if zone.blank? - zone = 'UTC' - end - rescue - zone = 'UTC' # site not found - end - return zone - end - - def date_format - case dateloc - when 9 - :no_date_data - when 8 - :year_only - when 7 - :season_and_year - when 6 - :month_and_year - when 5.5 - :week_of_year - when 5 - :year_month_day - when 97 - :season_only - when 96 - :month_only - when 95 - :month_day - when nil - :unspecified_dateloc - else - :unrecognized_dateloc - end - end + # provides date_in_site_timezone, date_format, and site_timezone + include DateTimeUtilityMethods end diff --git a/config/initializers/time_formats.rb b/config/initializers/time_formats.rb index 7047ada4c..5149def00 100644 --- a/config/initializers/time_formats.rb +++ b/config/initializers/time_formats.rb @@ -10,6 +10,66 @@ module DateTimeConstants Minutes = 0..59 end +module DateTimeUtilityMethods + + private + + # Convert the Trait date attribute (which is an ActiveSupport::TimeWithZone + # object) to a new TimeWithZone object representing the time in site_timezone. + # This is used in various methods used for presenting the date and time to the + # user, which is always done in local (site) time. + def date_in_site_timezone + date.in_time_zone(site_timezone) + end + + # Returns the time zone of the associated site or "UTC" if no there is no + # associated site or if its time_zone attribute is blank. + def site_timezone + begin + if respond_to? :site + zone = site.time_zone + else + zone = (Site.find(site_id)).time_zone + end + + if zone.blank? + zone = 'UTC' + end + rescue + zone = 'UTC' # site not found + end + return zone + end + + def date_format + case dateloc + when 9 + :no_date_data + when 8 + :year_only + when 7 + :season_and_year + when 6 + :month_and_year + when 5.5 + :week_of_year + when 5 + :year_month_day + when 97 + :season_only + when 96 + :month_only + when 95 + :month_day + when nil + :unspecified_dateloc + else + :unrecognized_dateloc + end + end + +end + NEW_FORMATS = { # Date Formats From cb5057bb489d44a0452c55ceb5b278a2ec124859 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Tue, 10 Nov 2015 13:27:18 -0600 Subject: [PATCH 30/51] Converting Yields pages and Advanced Pages to the use of new date/dateloc logic. --- app/controllers/yields_controller.rb | 43 ----- app/models/traits_and_yields_view.rb | 4 +- app/models/yield.rb | 265 ++++++++++++++++++++++++++- app/views/yields/edit.html.erb | 17 +- app/views/yields/new.html.erb | 17 +- app/views/yields/show.html.erb | 2 +- 6 files changed, 288 insertions(+), 60 deletions(-) diff --git a/app/controllers/yields_controller.rb b/app/controllers/yields_controller.rb index 96f722d5e..e9df93fc2 100644 --- a/app/controllers/yields_controller.rb +++ b/app/controllers/yields_controller.rb @@ -117,12 +117,8 @@ def edit # POST /yields.xml def create - set_default_date_fields(params) - @yield = Yield.new(params[:yield]) - maybe_set_from_julian_date(params) - @yield.user_id = current_user.id logger.info "Current user: #{current_user.id}" @@ -148,12 +144,8 @@ def create def update @yield = Yield.all_limited(current_user).find(params[:id]) - set_default_date_fields(params) - @yield.update_attributes(params[:yield]) - maybe_set_from_julian_date(params) - respond_to do |format| # This save is a no-op unless Julian date was set: if @yield.save @@ -185,39 +177,4 @@ def destroy end end - private - - # If any piece of the date was given in the parameters, set any other pieces - # that may have been missing to default values. - def set_default_date_fields(params) - # If at least on of the date fields is non-blank, assign defaults to the others if they are blank: - if !(params[:yield]['date(1i)'].blank? and params[:yield]['date(2i)'].blank? and params[:yield]['date(3i)'].blank?) - - # Default the empty fields. In Rails 3.2, it seems we must do this explicitly. - params[:yield]['date(1i)'] = "9999" if params[:yield]['date(1i)'].blank? # year default - params[:yield]['date(2i)'] = "1"if params[:yield]['date(2i)'].blank? # month default - params[:yield]['date(3i)'] = "1"if params[:yield]['date(3i)'].blank? # day default - - end - end - - # If either parameter julianyear or julianday was set, update the yield date - # using these parameter values. - def maybe_set_from_julian_date(params) - - # They can also enter the date in julian format, so if they do, overwrite - # the other date field: - if !(params[:julianyear].blank? and params[:julianday].blank?) - params[:julianyear] = "9999" if params[:julianyear].blank? # year default - params[:julianday] = "1" if params[:julianday].blank? # day default - - begin - @yield.date = Date.ordinal(params[:julianyear].to_f, params[:julianday].to_f) - rescue - flash[:error] = "Invalid values in Julian date fields were ignored" - end - end - - end - end diff --git a/app/models/traits_and_yields_view.rb b/app/models/traits_and_yields_view.rb index 68b61f36f..4db148c74 100644 --- a/app/models/traits_and_yields_view.rb +++ b/app/models/traits_and_yields_view.rb @@ -113,8 +113,10 @@ class TraitsAndYieldsView < ActiveRecord::Base def pretty_date if date.nil? '[unspecified]' - else + elsif result_type =~ /traits/ date_in_site_timezone.to_formatted_s(date_format) + " (#{site_timezone})" + else # for yields, don't mess with time zones + date.to_time.to_formatted_s(date_format) end end diff --git a/app/models/yield.rb b/app/models/yield.rb index 9fe2e897f..549336599 100644 --- a/app/models/yield.rb +++ b/app/models/yield.rb @@ -1,5 +1,8 @@ class Yield < ActiveRecord::Base + #-- + ### Module Usage ### + include Overrides extend DataAccess # provides all_limited @@ -8,6 +11,11 @@ class Yield < ActiveRecord::Base SEARCH_INCLUDES = %w{ citation specie site treatment cultivar } SEARCH_FIELDS = %w{ species.genus cultivars.name yields.mean yields.n yields.stat yields.statname citations.author sites.sitename treatments.name } + include DateTimeConstants + + #-- + ### Associations ### + belongs_to :citation belongs_to :site belongs_to :specie @@ -16,6 +24,60 @@ class Yield < ActiveRecord::Base belongs_to :user belongs_to :ebi_method, :class_name => 'Methods', :foreign_key => 'method_id' + + + #-- + ### VALIDATION ### + + #-- + ## Validation methods ## + + # Validation Method: Check that the three date fields represent a valid date + # and are consistent with our conventions for allowable partial information + # about dates. + def consistent_date_and_time_fields + + # Set defaults for unspecified components and convert the supplied ones to + # integers. + case computed_dateloc + when 9 + year, month, day = 9996, 1, 1 + when 8 + year, month, day = (julianyear || d_year).to_i, 1, 1 + when 7 + month = SeasonRepresentativeMonths[d_month] + year, day = d_year.to_i, 1 + when 6 + year, month, day = d_year.to_i, d_month.to_i, 1 + when 5 + if !julianday.blank? + @computed_date = Date.ordinal(julianyear.to_i, julianday.to_i) + return + end + year, month, day = d_year.to_i, d_month.to_i, d_day.to_i + when 97 + month = SeasonRepresentativeMonths[d_month] + year, day = 9996, 1 + when 96 + year, month, day = 9996, d_month.to_i, 1 + when 95 + if !julianday.blank? + @computed_date = Date.ordinal(9996, julianday) + return + end + year, month, day = 9996, d_month.to_i, d_day.to_i + else + raise "Unexpected computed_dateloc value in Trait#consistent_date_and_time_fields." + end + + # Store the computed date for use by the before_save call-back "process_datetime_input". + @computed_date = Date.new(year, month, day) + + rescue ArgumentError => e + errors.add(:date, "is invalid") + end + + validates_presence_of :mean validates_numericality_of :mean, :greater_than_or_equal_to => 0.0 validates_presence_of :statname, :if => Proc.new { |y| !y.stat.blank? } @@ -26,7 +88,12 @@ class Yield < ActiveRecord::Base validates_presence_of :treatment_id validates_presence_of :user_id validates_presence_of :access_level - validates_presence_of :date + validate :consistent_date_and_time_fields + + + + #-- + ### Scopes ### scope :all_order, includes(:specie).order('species.genus, species.species') scope :sorted_order, lambda { |order| order(order).includes(SEARCH_INCLUDES) } @@ -39,6 +106,106 @@ class Yield < ActiveRecord::Base end } + + + #-- + ### Callbacks ### + + before_save :process_date_input + + + + #-- + ### Virtual Attributes ### + + attr_writer :d_year + attr_writer :d_month + attr_writer :d_day + + # We don't need special getter methods for these because we always populate + # the d_* fields, not these, when freshly populating the page form from the + # database. + attr_accessor :julianyear + attr_accessor :julianday + + + #-- + ## Custom accessors for virtual attributes. ## + + # A getter for the +d_year+ virtual attribute. If the @+d_year+ instance + # variable has been set, it is used. (This occurs, for example, when + # returning to a form that failed validation.) Otherwise, the value is + # computed from these persistent (i.e., database-backed) attributes: date, + # dateloc, and site.time_zone. + def d_year + if !@d_year.nil? + return @d_year + end + + case dateloc + when 95, 96, 97, 9 + '' + when 5, 5.5, 6, 7, 8 + date.nil? ? '' : date.year + when nil + nil + else + raise "In d_year, unrecognized dateloc value." + end + end + + # A getter for the +d_month+ virtual attribute. If the @+d_month+ instance + # variable has been set, it is used. (This occurs, for example, when + # returning to a form that failed validation.) Otherwise, the value is + # computed from these persistent (i.e., database-backed) attributes: date, + # dateloc, and site.time_zone. + def d_month + if !@d_month.nil? + return @d_month + end + + case dateloc + when 8, 9 + nil + when 7, 97 + SeasonRepresentativeMonths.key(date.month) or + raise "In d_month, month value is not appropriate for representing a season." + when 5, 5.5, 6, 95, 96 + date.nil? ? '' : date.month + when nil + nil + else + raise "In d_month, unrecognized dateloc value." + end + end + + # A getter for the +d_day+ virtual attribute. If the @+d_day+ instance + # variable has been set, it is used. (This occurs, for example, when + # returning to a form that failed validation.) Otherwise, the value is + # computed from these persistent (i.e., database-backed) attributes: date, + # dateloc, and site.time_zone. + def d_day + if !@d_day.nil? + return @d_day + end + + case dateloc + when 6, 7, 8, 9, 96, 97 + nil + when 5, 5.5, 95 + date.nil? ? '' : date.day + when nil + nil + else + raise "In d_day, unrecognized dateloc value." + end + end + + + + #-- + ### CSV Formats ### + comma do id citation_id @@ -73,6 +240,25 @@ class Yield < ActiveRecord::Base end + + + #-- + ### Presentation Methods ### + + def pretty_date + if date.nil? + '[unspecified]' + else + date.to_time.to_formatted_s(date_format) + end + end + + + + + + + # Now that the access_level column of "yields" has user-defined (domain) type # "level_of_access", we have to ensure it maps to a Ruby Fixnum because Rails # seems to map unknown SQL types to strings by default: @@ -90,4 +276,81 @@ def self.search_columns return ["yields.id"] end + + + + private + def process_date_input + logger.info("computed_dateloc = #{computed_dateloc}") + self.dateloc = computed_dateloc + self.date = @computed_date # this is set in the consistent_date_and_time_fields validate method + end + + + def computed_dateloc + # Convenience variables; since we only use this method in cases where the + # accessor returns exactly the same value as the instance variable, we may + # as well use the latter since it's faster. + y = @d_year + m = @d_month + d = @d_day + + jy = @julianyear + jd = @julianday + + if (!jy.blank? || !jd.blank?) + if (!y.blank? || !m.blank? || !d.blank?) + errors.add(:base, "If you set the Julian year or day, you must leave the other date fields blank.") + return 9 + elsif jy.blank? + return 95 + elsif jd.blank? + return 8 + else + return 5 + end + end + + # We only get here if the Julian date fields are blank. + + if !d.blank? + if m.blank? + errors.add(:base, "If you set a day, you must also set a month.") + 9 + elsif Seasons.include?(m) + errors.add(:base, "If you select a season, day must be blank.") + 9 + else + if y.blank? + 95 + else + 5 + end + end + else # d is blank + if m.blank? + if y.blank? + 9 + else + 8 + end + elsif Seasons.include?(m) + if y.blank? + 97 + else + 7 + end + else # month is a number (a real month) + if y.blank? + 96 + else + 6 + end + end + end + end + + # provides date_format, and site_timezone + include DateTimeUtilityMethods + end diff --git a/app/views/yields/edit.html.erb b/app/views/yields/edit.html.erb index 11ad4f280..de725cff5 100644 --- a/app/views/yields/edit.html.erb +++ b/app/views/yields/edit.html.erb @@ -34,15 +34,21 @@
- <%= f.label :date %> - <%= f.date_select :date,:defualt => nil, :order=> [:day, :month, :year], :add_month_numbers=> true, :include_blank=> true, :start_year=> 1800, :end_year=> Time.now.year %> +
+ Date + <%= f.select :d_year, (1800..Time.now.year).to_a, include_blank: true %> + <%= f.select :d_month, Trait::Seasons + Trait::Months.to_a, include_blank: true %> + <%= f.select :d_day, Trait::Days.to_a, include_blank: true %> +
... OR ...
- <%= f.label "julian day (1-365)/year" %> - <%= text_field_tag "julianday" %> / <%= text_field_tag "julianyear" %> +
+ Julian Day (1–366) / Year + <%= f.text_field :julianday %> / <%= f.text_field :julianyear %> +
@@ -74,9 +80,6 @@ <%= f.label :cultivar_id %> <%= f.select :cultivar_id, Cultivar.joins(:specie).order('species.scientificname').collect { |p| [ p.select_default, p.id ] }, { :include_blank => true}, :class => "input-full" %>
- <%= f.label :dateloc, "Date Level of Confidence" %> - <%= f.select :dateloc, options_for_select($dateloc_drop.sort, f.object.dateloc || $dateloc_drop_default), :class => "input-full" %> -
<%= f.label :access_level %> <%= f.select :access_level, [["1.Restricted",1],['2.EBI',2],['3.External',3],['4.Public',4]], :class => "input-full" %>
diff --git a/app/views/yields/new.html.erb b/app/views/yields/new.html.erb index f4f06dd73..93013c5c5 100644 --- a/app/views/yields/new.html.erb +++ b/app/views/yields/new.html.erb @@ -33,15 +33,21 @@
- <%= f.label :date %> - <%= f.date_select :date,:defualt => nil, :order=> [:day, :month, :year], :add_month_numbers=> true, :include_blank=> true, :start_year=> 1800, :end_year=> Time.now.year %> +
+ Date + <%= f.select :d_year, (1800..Time.now.year).to_a, include_blank: true %> + <%= f.select :d_month, Trait::Seasons + Trait::Months.to_a, include_blank: true %> + <%= f.select :d_day, Trait::Days.to_a, include_blank: true %> +
... OR ...
- <%= f.label "julian day (1-365)/year" %> - <%= text_field_tag "julianday" %> / <%= text_field_tag "julianyear" %> +
+ Julian Day (1–366) / Year + <%= f.text_field :julianday %> / <%= f.text_field :julianyear %> +
@@ -73,9 +79,6 @@ <%= f.label :cultivar_id %> <%= f.select :cultivar_id, Cultivar.joins(:specie).order('species.scientificname').collect { |p| [ p.select_default, p.id ] }, { :include_blank => true}, :class => "input-full" %>
- <%= f.label :dateloc, "Date Level of Confidence" %> - <%= f.select :dateloc, options_for_select($dateloc_drop.sort, f.object.dateloc || $dateloc_drop_default), :class => "input-full" %> -
<%= f.label :access_level %> <%= f.select :access_level, [["1.Restricted",1],['2.EBI',2],['3.External',3],['4.Public',4]], :selected => current_user.access_level, :class => "input-full" %>
diff --git a/app/views/yields/show.html.erb b/app/views/yields/show.html.erb index 400835363..9b04e67d9 100644 --- a/app/views/yields/show.html.erb +++ b/app/views/yields/show.html.erb @@ -34,7 +34,7 @@
<%= user_for_view(@yield.user) %>
Date:
-
<%= @yield.date %>
+
<%= @yield.pretty_date %>
Dateloc:
<%= @yield.dateloc %>
From 9cdfa322ca590cb5d1592853a6dcc63df29630e5 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Tue, 10 Nov 2015 13:40:50 -0600 Subject: [PATCH 31/51] Use named constants instead of integer literals for dummy date/time values. --- app/models/trait.rb | 22 +++++++++++----------- app/models/yield.rb | 16 ++++++++-------- config/initializers/time_formats.rb | 8 ++++++++ 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/app/models/trait.rb b/app/models/trait.rb index 98f319ce4..ac00c31fc 100644 --- a/app/models/trait.rb +++ b/app/models/trait.rb @@ -203,35 +203,35 @@ def consistent_date_and_time_fields # integers. case computed_dateloc when 9 - year, month, day = 9996, 1, 1 + year, month, day = DummyYear, DummyMonth, DummyDay when 8 - year, month, day = d_year.to_i, 1, 1 + year, month, day = d_year.to_i, DummyMonth, DummyDay when 7 month = SeasonRepresentativeMonths[d_month] - year, day = d_year.to_i, 1 + year, day = d_year.to_i, DummyDay when 6 - year, month, day = d_year.to_i, d_month.to_i, 1 + year, month, day = d_year.to_i, d_month.to_i, DummyDay when 5 year, month, day = d_year.to_i, d_month.to_i, d_day.to_i when 97 month = SeasonRepresentativeMonths[d_month] - year, day = 9996, 1 + year, day = DummyYear, DummyDay when 96 - year, month, day = 9996, d_month.to_i, 1 + year, month, day = DummyYear, d_month.to_i, DummyDay when 95 - year, month, day = 9996, d_month.to_i, d_day.to_i + year, month, day = DummyYear, d_month.to_i, d_day.to_i else raise "Unexpected computed_dateloc value in Trait#consistent_date_and_time_fields." end case computed_timeloc when 9 - hour, minute = 0, 0 + hour, minute = DummyHour, DummyMinute when 4 hour = TimesOfDayRepresentativeHours[t_hour] - minute = 0 + minute = DummyMinute when 3 - hour, minute = t_hour.to_i, 0 + hour, minute = t_hour.to_i, DummyMinute when 2 hour, minute = t_hour.to_i, t_minute.to_i else @@ -515,7 +515,7 @@ def time_format def utctime_from_sitetime(y, m, d, hr, min) utctime = nil Time.use_zone site_timezone do - utctime = Time.zone.local(y, m, d, hr, min, 0).utc + utctime = Time.zone.local(y, m, d, hr, min, DummySecond).utc end return utctime.to_datetime end diff --git a/app/models/yield.rb b/app/models/yield.rb index 549336599..c537b7b1b 100644 --- a/app/models/yield.rb +++ b/app/models/yield.rb @@ -41,14 +41,14 @@ def consistent_date_and_time_fields # integers. case computed_dateloc when 9 - year, month, day = 9996, 1, 1 + year, month, day = DummyYear, DummyMonth, DummyDay when 8 - year, month, day = (julianyear || d_year).to_i, 1, 1 + year, month, day = (julianyear || d_year).to_i, DummyMonth, DummyDay when 7 month = SeasonRepresentativeMonths[d_month] - year, day = d_year.to_i, 1 + year, day = d_year.to_i, DummyDay when 6 - year, month, day = d_year.to_i, d_month.to_i, 1 + year, month, day = d_year.to_i, d_month.to_i, DummyDay when 5 if !julianday.blank? @computed_date = Date.ordinal(julianyear.to_i, julianday.to_i) @@ -57,15 +57,15 @@ def consistent_date_and_time_fields year, month, day = d_year.to_i, d_month.to_i, d_day.to_i when 97 month = SeasonRepresentativeMonths[d_month] - year, day = 9996, 1 + year, day = DummyYear, DummyDay when 96 - year, month, day = 9996, d_month.to_i, 1 + year, month, day = DummyYear, d_month.to_i, DummyDay when 95 if !julianday.blank? - @computed_date = Date.ordinal(9996, julianday) + @computed_date = Date.ordinal(DummyYear, julianday) return end - year, month, day = 9996, d_month.to_i, d_day.to_i + year, month, day = DummyYear, d_month.to_i, d_day.to_i else raise "Unexpected computed_dateloc value in Trait#consistent_date_and_time_fields." end diff --git a/config/initializers/time_formats.rb b/config/initializers/time_formats.rb index 5149def00..ccc92dec7 100644 --- a/config/initializers/time_formats.rb +++ b/config/initializers/time_formats.rb @@ -4,6 +4,14 @@ module DateTimeConstants TimesOfDay = ['morning', 'mid-day', 'afternoon', 'night'] TimesOfDayRepresentativeHours = { 'morning' => 9, 'mid-day' => 12, 'afternoon' => 15, 'night' => 0 } + # dummy values to use when date/times are not fully explicit + DummyYear = 9996 + DummyMonth = 1 + DummyDay = 1 + DummyHour = 0 + DummyMinute = 0 + DummySecond = 0 + Months = 1..12 Days = 1..31 Hours = 0..23 From b8a298c6a30525a6c84e8494d2821829c71e601c Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Tue, 10 Nov 2015 16:02:53 -0600 Subject: [PATCH 32/51] Fixed bug in Yield#consistent_date_and_time_fields for the case where computed_dateloc = 8 and julianyear is blank. --- app/models/yield.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/yield.rb b/app/models/yield.rb index c537b7b1b..aedbaec00 100644 --- a/app/models/yield.rb +++ b/app/models/yield.rb @@ -43,7 +43,8 @@ def consistent_date_and_time_fields when 9 year, month, day = DummyYear, DummyMonth, DummyDay when 8 - year, month, day = (julianyear || d_year).to_i, DummyMonth, DummyDay + year = julianyear.blank? ? d_year.to_i : julianyear.to_i + month, day = DummyMonth, DummyDay when 7 month = SeasonRepresentativeMonths[d_month] year, day = d_year.to_i, DummyDay From f546975fa4555495e79b210ab175b606b00d829f Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Tue, 10 Nov 2015 16:28:16 -0600 Subject: [PATCH 33/51] In Yield#update, use assign_attributes instead of update_attributes since the call to save will be done later (so update_attributes is redundant). --- app/controllers/yields_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/yields_controller.rb b/app/controllers/yields_controller.rb index e9df93fc2..50dfae156 100644 --- a/app/controllers/yields_controller.rb +++ b/app/controllers/yields_controller.rb @@ -144,7 +144,7 @@ def create def update @yield = Yield.all_limited(current_user).find(params[:id]) - @yield.update_attributes(params[:yield]) + @yield.assign_attributes(params[:yield]) respond_to do |format| # This save is a no-op unless Julian date was set: From 9f90309b606f3b1392a71976e4593b4a600ff6b4 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Wed, 11 Nov 2015 11:25:54 -0600 Subject: [PATCH 34/51] Changed d_day accessor methods to return nil when dateloc is 5.5. --- app/models/trait.rb | 4 ++-- app/models/yield.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/models/trait.rb b/app/models/trait.rb index ac00c31fc..452decfc8 100644 --- a/app/models/trait.rb +++ b/app/models/trait.rb @@ -128,9 +128,9 @@ def d_day end case dateloc - when 6, 7, 8, 9, 96, 97 + when 5.5, 6, 7, 8, 9, 96, 97 nil - when 5, 5.5, 95 + when 5, 95 date.nil? ? '' : date_in_site_timezone.day when nil nil diff --git a/app/models/yield.rb b/app/models/yield.rb index aedbaec00..0bcd16ce7 100644 --- a/app/models/yield.rb +++ b/app/models/yield.rb @@ -191,9 +191,9 @@ def d_day end case dateloc - when 6, 7, 8, 9, 96, 97 + when 5.5, 6, 7, 8, 9, 96, 97 nil - when 5, 5.5, 95 + when 5, 95 date.nil? ? '' : date.day when nil nil From 17bd764b1be002ea2ba206ecbc2b64b41fbe99e7 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Wed, 11 Nov 2015 15:12:57 -0600 Subject: [PATCH 35/51] Changed CSV output for traits and yields to use LOC-aware date/time values. Be more lenient about representative month for seasons--don't raise an exception, just display a warning. --- app/models/trait.rb | 5 ++++- app/models/traits_and_yields_view.rb | 4 +--- app/models/yield.rb | 5 +++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/models/trait.rb b/app/models/trait.rb index 452decfc8..fb05a0c6b 100644 --- a/app/models/trait.rb +++ b/app/models/trait.rb @@ -107,7 +107,7 @@ def d_month nil when 7, 97 SeasonRepresentativeMonths.key(date_in_site_timezone.month) or - raise "In d_month, month value is not appropriate for representing a season." + "[bad month value #{date_in_site_timezone.month} doesn't correspond to a season]" when 5, 5.5, 6, 95, 96 date.nil? ? '' : date_in_site_timezone.month when nil @@ -303,10 +303,13 @@ def mean_in_range cultivar_id treatment_id entity_id + date 'raw date' do |date| date.utc end + pretty_date d_year d_month d_day dateloc + pretty_time t_hour t_minute timeloc diff --git a/app/models/traits_and_yields_view.rb b/app/models/traits_and_yields_view.rb index 4db148c74..797984455 100644 --- a/app/models/traits_and_yields_view.rb +++ b/app/models/traits_and_yields_view.rb @@ -80,9 +80,7 @@ class TraitsAndYieldsView < ActiveRecord::Base author 'author' citation_year 'citation_year' treatment 'treatment' - date 'date' - month 'month' - year 'year' + pretty_date 'date' dateloc 'dateloc' trait 'trait' mean 'mean' do |num| diff --git a/app/models/yield.rb b/app/models/yield.rb index 0bcd16ce7..eb186513a 100644 --- a/app/models/yield.rb +++ b/app/models/yield.rb @@ -170,7 +170,7 @@ def d_month nil when 7, 97 SeasonRepresentativeMonths.key(date.month) or - raise "In d_month, month value is not appropriate for representing a season." + "[bad month value #{date_in_site_timezone.month} doesn't correspond to a season]" when 5, 5.5, 6, 95, 96 date.nil? ? '' : date.month when nil @@ -214,7 +214,8 @@ def d_day specie_id treatment_id cultivar_id - date + date 'raw date' + pretty_date dateloc statname stat From 5402cbcd7d1ad52b254fcbeaf68106e20c53bc92 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Wed, 11 Nov 2015 16:40:51 -0600 Subject: [PATCH 36/51] Updated yield integration test to work with new date fields. --- spec/features/yield_integration_spec.rb | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/spec/features/yield_integration_spec.rb b/spec/features/yield_integration_spec.rb index e9b5a60e1..45dfa5296 100644 --- a/spec/features/yield_integration_spec.rb +++ b/spec/features/yield_integration_spec.rb @@ -32,9 +32,9 @@ fill_in 'yield[stat]', :with => '98736.0' fill_in 'yield[n]', :with => '100' fill_in 'Notes', :with => 'In some technical publications, appendices are so long and important as part of the book that they are a creative endeavour of the author' - select('10', :from => 'yield[date(3i)]') - select('10', :from => 'yield[date(2i)]') - select('1800', :from => 'yield[date(1i)]') + select('10', :from => 'yield[d_day]') + select('10', :from => 'yield[d_month]') + select('1800', :from => 'yield[d_year]') fill_in 'species_query', :with => 'sacc' select('3', :from => 'yield[specie_id]') @@ -60,9 +60,9 @@ visit '/yields/new' fill_in 'yield[mean]', :with => 'asdf' fill_in 'yield[stat]', :with => '98736.0' - select('1', :from => 'yield[date(3i)]') - select('1 - January', :from => 'yield[date(2i)]') - select('1800', :from => 'yield[date(1i)]') + select('1', :from => 'yield[d_day]') + select('1', :from => 'yield[d_month]') + select('1800', :from => 'yield[d_year]') fill_in 'species_query', :with => 'sacc' select('3', :from => 'yield[specie_id]') @@ -80,9 +80,9 @@ # Required attributes: fill_in 'yield[mean]', :with => '10.0' - select('10', :from => 'yield[date(3i)]') - select('10', :from => 'yield[date(2i)]') - select('1800', :from => 'yield[date(1i)]') + select('10', :from => 'yield[d_day]') + select('10', :from => 'yield[d_month]') + select('1800', :from => 'yield[d_year]') fill_in 'species_query', :with => 'Abar' select('2', :from => 'yield[specie_id]') @@ -159,9 +159,9 @@ # Required attributes: fill_in 'yield[mean]', :with => '10.0' - select('10', :from => 'yield[date(3i)]') - select('10', :from => 'yield[date(2i)]') - select('1800', :from => 'yield[date(1i)]') + select('10', :from => 'yield[d_day]') + select('10', :from => 'yield[d_month]') + select('1800', :from => 'yield[d_year]') fill_in 'species_query', :with => 'Abar' select('2', :from => 'yield[specie_id]') From 943a1ef5c52c44bb0d892e90e430c7d0f18f24c5 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Fri, 20 Nov 2015 14:24:59 -0600 Subject: [PATCH 37/51] Added a migration that defines the normative interpretation of trait dates by means of a PL/pgSQL function. Added tests to ensure Rails trait date-related functions are consistent with this interpretation. --- ...trait_and_yield_date_and_time_functions.rb | 106 ++++++++++++++ spec/models/trait_spec.rb | 138 ++++++++++++++++-- 2 files changed, 228 insertions(+), 16 deletions(-) create mode 100644 db/migrate/20151120201053_add_trait_and_yield_date_and_time_functions.rb diff --git a/db/migrate/20151120201053_add_trait_and_yield_date_and_time_functions.rb b/db/migrate/20151120201053_add_trait_and_yield_date_and_time_functions.rb new file mode 100644 index 000000000..d64f8ad43 --- /dev/null +++ b/db/migrate/20151120201053_add_trait_and_yield_date_and_time_functions.rb @@ -0,0 +1,106 @@ +class AddTraitAndYieldDateAndTimeFunctions < ActiveRecord::Migration + def up + + # Use "%q" so that backspashes are taken literally (except when doubled). + execute %q{ + +CREATE OR REPLACE FUNCTION pretty_date( + date timestamp, + dateloc numeric(4,2), + timeloc numeric(4,2), + site_id bigint +) RETURNS text AS $body$ +DECLARE + FORMAT text; + SEASON text; + SITE_OR_UTC_TIMEZONE text; + TIMEZONE_DESIGNATION text; + SITE_OR_UTC_DATE timestamp; +BEGIN + + SELECT COALESCE(time_zone, 'UTC') FROM sites WHERE id = site_id INTO SITE_OR_UTC_TIMEZONE; + + TIMEZONE_DESIGNATION := ''; + IF timeloc = 9 AND dateloc IN (5, 6, 8, 95, 96) THEN + TIMEZONE_DESIGNATION := FORMAT(' (%s)', SITE_OR_UTC_TIMEZONE); + END IF; + + /* Interpret the date column as being UTC (not server time!), then convert it site time (if determined) or UTC. + Note that "date || ' UTC'" is NULL if date is NULL (unlike CONCAT(date, ' UTC)', which is ' UTC' if date is NULL. + This is what we want. */ + SELECT CAST((date::text || ' UTC') AS timestamp with time zone) AT TIME ZONE SITE_OR_UTC_TIMEZONE INTO SITE_OR_UTC_DATE; + + CASE extract(month FROM SITE_OR_UTC_DATE) + WHEN 1 THEN + SEASON := '"DJF"'; + WHEN 4 THEN + SEASON := '"MAM"'; + WHEN 7 THEN + SEASON := '"JJA"'; + WHEN 10 THEN + SEASON := '"SON"'; + ELSE + SEASON := '"[UNRECOGNIZED SEASON MONTH]"'; + END CASE; + + + CASE COALESCE(dateloc, -1) + + WHEN 9 THEN + FORMAT := '"[date unspecified or unknown]"'; + + WHEN 8 THEN + FORMAT := 'YYYY'; + + WHEN 7 THEN + FORMAT := CONCAT('Season: ', SEASON, ' YYYY'); + + WHEN 6 THEN + FORMAT := 'FMMonth YYYY'; + + WHEN 5.5 THEN + FORMAT := '"Week of" Mon FMDD, YYYY'; + + WHEN 5 THEN + FORMAT := 'YYYY Mon FMDD'; + + WHEN 97 THEN + FORMAT := CONCAT('Season: ', SEASON); + + WHEN 96 THEN + FORMAT := 'FMMonth'; + + WHEN 95 THEN + FORMAT := 'FMMonth FMDDth'; + + WHEN -1 THEN + FORMAT := '"Date Level of Confidence Unknown"'; + + ELSE + FORMAT := '"Unrecognized Value for Date Level of Confidence"'; + END CASE; + + RETURN CONCAT(to_char(SITE_OR_UTC_DATE, FORMAT), TIMEZONE_DESIGNATION); + +END; +$body$ LANGUAGE plpgsql; + + } + + end + + def down + + execute %q{ + +DROP FUNCTION pretty_date( + date timestamp, + dateloc numeric(4,2), + timeloc numeric(4,2), + site_id bigint +); + + } + + end +end diff --git a/spec/models/trait_spec.rb b/spec/models/trait_spec.rb index fc2426b8a..df3594431 100644 --- a/spec/models/trait_spec.rb +++ b/spec/models/trait_spec.rb @@ -1,20 +1,126 @@ require 'spec_helper' -describe Trait do - it 'should be invalid if no attributes are given' do - - t = Trait.new - t.invalid?.should == true - end - - it 'should be valid if a valid mean, variable_id, site_id, and access_level are given' do - t = Trait.new mean: 6, access_level: 1, variable_id: 1, site_id: 1 - t.invalid?.should == false - end - - it 'should be invalid if date_year has the wrong format' do - t = Trait.new mean: 6, access_level: 1, variable_id: 1, date_year: "783" - t.invalid?.should == true - end +### SAMPLE DATA ### + +# samples for all possible dateloc values +TEST_DATE_SAMPLES = [ + { dateloc: 9, date_attributes: {}, description: "no date information is given" }, + { dateloc: 8, date_attributes: { d_year: 2001 }, description: "only a year is given" }, + { dateloc: 7, date_attributes: { d_year: 2001, d_month: "Season: SON" }, description: "a year and season are given" }, + { dateloc: 6, date_attributes: { d_year: 2001, d_month: 7 }, description: "a year and month are given" }, + { dateloc: 5, date_attributes: { d_year: 2001, d_month: 7, d_day: 23 }, description: "a year, month, and day are given" }, + { dateloc: 97, date_attributes: { d_month: "Season: DJF" }, description: "only a season is given" }, + { dateloc: 96, date_attributes: { d_month: 11 }, description: "only a month is given" }, + { dateloc: 95, date_attributes: { d_month: 11, d_day: 9 }, description: "only a month and day are given" } + ] + +TEST_SITES = [ + { site_id: 1 }, + { site_id: 2 }, + { site_id: 3 }, + { site_id: 4 } +] + +ABERRANT_DATA = [ + { dateloc: nil, description: "no dateloc is given" }, + { dateloc: 5.5, description: "the obsolescent week dateloc is given" }, + { dateloc: 10, description: "an invalid dateloc is given" } +] + +### CONVENIENCE METHODS ### + +# Compare result of calling Trait#pretty_print with calling the PL/pgSQL +# function pretty_print. (The latter comprises the normative interpretation of +# trait date/dateloc information.) +def rails_pp_output_agrees_with_sql_pp_output(t) + sql_call = "SELECT pretty_date(date, dateloc, timeloc, site_id) FROM traits WHERE id = #{t.id}" + + sql_text = ActiveRecord::Base.connection.select_all(sql_call).first.fetch("pretty_date") + rails_text = t.pretty_date + expect(sql_text).to eq(rails_text), < { self.raw_update } end + + +### TESTS ### + + +describe "Trait" do + describe "basic validity constraints" do + it 'should be invalid if no attributes are given' do + + t = Trait.new + t.invalid?.should == true + end + + it 'should be valid if a valid mean, variable_id, site_id, and access_level are given' do + t = Trait.new mean: 6, access_level: 1, variable_id: 1, site_id: 1 + t.invalid?.should == false + end + end # describe "basic validity constraints" + + describe "date and time semantics" do + let(:sample_trait) do + Trait.create mean: 1, variable_id: 1, access_level: 1, site_id: 1 + end + + describe "automatic dateloc setting" do + TEST_DATE_SAMPLES.each do |sample| + it "should set dateloc to #{sample.fetch(:dateloc)} if #{sample.fetch(:description)}" do + sample_trait.update_attributes(sample.fetch(:date_attributes)) + expect(sample_trait.dateloc).to eq sample.fetch(:dateloc) + end + end + end + + describe "pretty-printing date should pretty print the date and time according to the normative SQL function" do + + TEST_DATE_SAMPLES.each do |sample_date| + TEST_SITES.each do |sample_site| + it "when #{sample_date.fetch(:description)} and site timezone is #{Site.find(sample_site.fetch(:site_id)).time_zone || 'nil'}" do + + # set the site first so that timezone is taken into consideration when date is set + sample_trait.update_attributes(sample_site) + + sample_trait.update_attributes(sample_date.fetch(:date_attributes)) + + rails_pp_output_agrees_with_sql_pp_output(sample_trait) + + end # end it + end # inner each loop + end # outer each loop + + describe "pretty_date should display aberrant date information according to the normative SQL function" do + let(:sample_trait_with_date) do + t = Trait.create(mean: 1, variable_id: 1, access_level: 1, site_id: 1, d_year: 2001, d_month: 7, d_day: 23) + t.raw_update = true # circumvent before_save callback so we can set dateloc at will + return t + end # let + + ABERRANT_DATA.each do |facet| + it "when #{facet.fetch(:description)}" do + sample_trait_with_date.update_attributes(dateloc: facet.fetch(:dateloc)) + + rails_pp_output_agrees_with_sql_pp_output(sample_trait_with_date) + end + end # ABERRANT_DATA.each + end # describe "aberrant cases" + + end # describe "pretty-printing date" + + end # describe "date and time semantics" +end # describe "Trait" From 11da0ceed3724f9bd0adc085a1330e06807434f2 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Fri, 20 Nov 2015 15:31:20 -0600 Subject: [PATCH 38/51] Added a PL/pgSQL function that defines the normative interpretation of the time portion of trait dates to migration. Added tests to ensure Rails trait time-related functions are consistent with this interpretation and modified trait and site fixtures to facilitate these tests. Modified format used for "hour-only" time display. --- config/initializers/time_formats.rb | 2 +- ...trait_and_yield_date_and_time_functions.rb | 82 +++++++++++++++++++ spec/models/trait_spec.rb | 67 +++++++++++++++ test/fixtures/sites.yml | 3 + test/fixtures/traits.yml | 12 +-- 5 files changed, 159 insertions(+), 7 deletions(-) diff --git a/config/initializers/time_formats.rb b/config/initializers/time_formats.rb index ccc92dec7..3db9bf6fd 100644 --- a/config/initializers/time_formats.rb +++ b/config/initializers/time_formats.rb @@ -141,7 +141,7 @@ def date_format end, # timeloc 3: - hour_only: '%l %p', + hour_only: '%-l %p', # timeloc 2: hour_minutes: '%H:%M', diff --git a/db/migrate/20151120201053_add_trait_and_yield_date_and_time_functions.rb b/db/migrate/20151120201053_add_trait_and_yield_date_and_time_functions.rb index d64f8ad43..5b71ac500 100644 --- a/db/migrate/20151120201053_add_trait_and_yield_date_and_time_functions.rb +++ b/db/migrate/20151120201053_add_trait_and_yield_date_and_time_functions.rb @@ -85,6 +85,82 @@ def up END; $body$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION pretty_time( + date timestamp, + timeloc numeric(4,2), + site_id bigint +) RETURNS text AS $body$ +DECLARE + FORMAT text; + TIME_OF_DAY text; + SITE_OR_UTC_TIMEZONE text; + TIMEZONE_DESIGNATION text; + SITE_OR_UTC_DATE timestamp; +BEGIN + + + SELECT COALESCE(time_zone, 'UTC') FROM sites WHERE id = site_id INTO SITE_OR_UTC_TIMEZONE; + + TIMEZONE_DESIGNATION := ''; + IF timeloc != 9 THEN + TIMEZONE_DESIGNATION := FORMAT(' (%s)', SITE_OR_UTC_TIMEZONE); + END IF; + + /* Interpret the date column as being UTC (not server time!), then convert it site time (if determined) or UTC. + Note that "date || ' UTC'" is NULL if date is NULL (unlike CONCAT(date, ' UTC)', which is ' UTC' if date is NULL. + This is what we want. */ + SELECT CAST((date::text || ' UTC') AS timestamp with time zone) AT TIME ZONE SITE_OR_UTC_TIMEZONE INTO SITE_OR_UTC_DATE; + + + CASE extract(hour FROM SITE_OR_UTC_DATE) + WHEN 0 THEN + TIME_OF_DAY := '"night"'; + WHEN 9 THEN + TIME_OF_DAY := '"morning"'; + WHEN 12 THEN + TIME_OF_DAY := '"mid-day"'; + WHEN 15 THEN + TIME_OF_DAY := '"afternoon"'; + ELSE + TIME_OF_DAY := '"[Invalid time-of-day designation]"'; + END CASE; + + + CASE COALESCE(timeloc, -1) + + + WHEN 9 THEN + FORMAT := '"[time unspecified or unknown]"'; + + WHEN 4 THEN + FORMAT := TIME_OF_DAY; + + WHEN 3 THEN + FORMAT := 'FMHH AM'; + + WHEN 2 THEN + FORMAT := 'HH24:MI'; + + WHEN 1 THEN + FORMAT := 'HH24:MI:SS'; + + WHEN -1 THEN + FORMAT := '"Time Level of Confidence Unknown"'; + + ELSE + FORMAT := '"Unrecognized Value for Time Level of Confidence"'; + + END CASE; + + RETURN CONCAT(to_char(SITE_OR_UTC_DATE, FORMAT), TIMEZONE_DESIGNATION); + +END; +$body$ LANGUAGE plpgsql; + + + + } end @@ -100,6 +176,12 @@ def down site_id bigint ); +DROP FUNCTION pretty_time( + date timestamp, + timeloc numeric(4,2), + site_id bigint +) + } end diff --git a/spec/models/trait_spec.rb b/spec/models/trait_spec.rb index df3594431..7d52f8078 100644 --- a/spec/models/trait_spec.rb +++ b/spec/models/trait_spec.rb @@ -14,6 +14,14 @@ { dateloc: 95, date_attributes: { d_month: 11, d_day: 9 }, description: "only a month and day are given" } ] +# samples for all possible timeloc values +TEST_TIME_SAMPLES = [ + { timeloc: 9, time_attributes: {}, description: "no time information is given" }, + { timeloc: 4, time_attributes: { t_hour: 'afternoon' }, description: "only the approximate time of day is given" }, + { timeloc: 3, time_attributes: { t_hour: 17 }, description: "only the hour is given" }, + { timeloc: 2, time_attributes: { t_hour: 19, t_minute: 25 }, description: "both hour and minutes are given" } + ] + TEST_SITES = [ { site_id: 1 }, { site_id: 2 }, @@ -49,6 +57,26 @@ def rails_pp_output_agrees_with_sql_pp_output(t) end +# Compare result of calling Trait#pretty_print with calling the PL/pgSQL +# function pretty_print. (The latter comprises the normative interpretation of +# trait date/dateloc information.) +def rails_pretty_time_output_agrees_with_sql_pretty_time_output(t) + sql_call = "SELECT pretty_time(date, timeloc, site_id) FROM traits WHERE id = #{t.id}" + + sql_text = ActiveRecord::Base.connection.select_all(sql_call).first.fetch("pretty_time") + rails_text = t.pretty_time + + expect(sql_text).to eq(rails_text), < + time_zone: 'Europe/Athens' test_trait_site: id: 4 @@ -44,6 +45,7 @@ test_site_2: state: "Nevada" country: US geometry: # + time_zone: 'America/Los_Angeles' test_site_3: id: 3 @@ -52,3 +54,4 @@ test_site_3: state: "xxx" country: xxx geometry: # + time_zone: 'Australia/Brisbane' diff --git a/test/fixtures/traits.yml b/test/fixtures/traits.yml index f83af00a9..28444761f 100644 --- a/test/fixtures/traits.yml +++ b/test/fixtures/traits.yml @@ -4,10 +4,10 @@ test_trait: citation_id: 1 cultivar_id: 1 treatment_id: 1 - date: NULL - dateloc: NULL + date: "2005-07-25 09:31:00" + dateloc: 5 time: NULL - timeloc: NULL + timeloc: 2 mean: NULL n: NULL stat: NULL @@ -32,10 +32,10 @@ test_edit_trait: citation_id: 1 cultivar_id: 417 treatment_id: 1 - date: NULL - dateloc: 9.00 + date: "2015-10-01 04:00:00" + dateloc: 7 time: NULL - timeloc: 9.00 + timeloc: 3 mean: '5' n: NULL statname: "" From 2844dc0d7d8f4668ca33ac761640725aa0692cbb Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Wed, 20 Jul 2016 14:18:41 -0500 Subject: [PATCH 39/51] Removed redundant migration and changed timestamp on another. --- ...0151023163648_replace_local_time_with_time_zone.rb | 11 ----------- ...33_add_trait_and_yield_date_and_time_functions.rb} | 0 2 files changed, 11 deletions(-) delete mode 100644 db/migrate/20151023163648_replace_local_time_with_time_zone.rb rename db/migrate/{20151120201053_add_trait_and_yield_date_and_time_functions.rb => 20160720182233_add_trait_and_yield_date_and_time_functions.rb} (100%) diff --git a/db/migrate/20151023163648_replace_local_time_with_time_zone.rb b/db/migrate/20151023163648_replace_local_time_with_time_zone.rb deleted file mode 100644 index 76d7c6e5c..000000000 --- a/db/migrate/20151023163648_replace_local_time_with_time_zone.rb +++ /dev/null @@ -1,11 +0,0 @@ -class ReplaceLocalTimeWithTimeZone < ActiveRecord::Migration - def up - add_column :sites, :time_zone, :text - remove_column :sites, :local_time - end - - def down - add_column :sites, :local_time, :integer - remove_column :sites, :time_zone - end -end diff --git a/db/migrate/20151120201053_add_trait_and_yield_date_and_time_functions.rb b/db/migrate/20160720182233_add_trait_and_yield_date_and_time_functions.rb similarity index 100% rename from db/migrate/20151120201053_add_trait_and_yield_date_and_time_functions.rb rename to db/migrate/20160720182233_add_trait_and_yield_date_and_time_functions.rb From 2086b5fd44e70dc2666f00b293920c2185236364 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Thu, 21 Jul 2016 14:50:10 -0500 Subject: [PATCH 40/51] Add more testing of the Trait model. --- spec/models/trait_interface_spec.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 spec/models/trait_interface_spec.rb diff --git a/spec/models/trait_interface_spec.rb b/spec/models/trait_interface_spec.rb new file mode 100644 index 000000000..a3e928a70 --- /dev/null +++ b/spec/models/trait_interface_spec.rb @@ -0,0 +1,21 @@ +# Override the default path "#{::Rails.root}/spec/fixtures" +RSpec.configure do |config| + config.fixture_path = "#{::Rails.root}/test/fixtures" +end + + + +describe "Trait" do + + fixtures :traits + + specify "Saving an unchanged trait shouldn't change it, even if date attributes are inconsistent." do + + t = traits(:trait_with_inconsistent_date_attributes) + + # A bug having to do with method nsec is averted by using reload here: + expect { t.save; t.reload }.not_to change { t.date } + + end + +end From 9faed8bb425b84438622db6e703352535e28c98f Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Thu, 21 Jul 2016 15:58:03 -0500 Subject: [PATCH 41/51] Add more tests. --- spec/models/trait_interface_spec.rb | 42 +++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/spec/models/trait_interface_spec.rb b/spec/models/trait_interface_spec.rb index a3e928a70..fbb436eab 100644 --- a/spec/models/trait_interface_spec.rb +++ b/spec/models/trait_interface_spec.rb @@ -9,12 +9,50 @@ fixtures :traits - specify "Saving an unchanged trait shouldn't change it, even if date attributes are inconsistent." do + xspecify "Saving an unchanged trait shouldn't change it." do + + t = traits(:test_trait) + + # A bug having to do with method nsec is averted by using reload here: + expect { t.save }.not_to change { t.updated_at } + + end + + xspecify "Saving an unchanged trait shouldn't change it, even if date attributes are inconsistent." do t = traits(:trait_with_inconsistent_date_attributes) # A bug having to do with method nsec is averted by using reload here: - expect { t.save; t.reload }.not_to change { t.date } + expect { t.save }.not_to change { t.updated_at } + + end + + context "Updating a non-trait attribute of a trait having consistent date attributes" do + + before(:example) do + @trait = traits(:test_trait) + @trait.notes = "New note" + end + + it "should save the change to the changed attribute" do + @trait.save + @trait.reload # ensure we get the saved version + expect(@trait.notes).to eq("New note") + end + + it "shouldn't change the date attribute" do + # A bug having to do with method nsec is averted by using reload here: + expect { @trait.save; @trait.reload }.not_to change { @trait.date } + end + + it "shouldn't change the dateloc attribute" do + # A bug having to do with method nsec is averted by using reload here: + expect { @trait.save }.not_to change { @trait.dateloc } + end + + it "shouldn't change the timeloc attribute" do + expect { @trait.save }.not_to change { @trait.timeloc } + end end From 6f5db31084dc51363ba32d3bd04d5cc0ec5b9085 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Sat, 23 Jul 2016 15:59:38 -0500 Subject: [PATCH 42/51] Added more trait date tests. --- spec/models/trait_interface_spec.rb | 57 +++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/spec/models/trait_interface_spec.rb b/spec/models/trait_interface_spec.rb index fbb436eab..cb5b8c54b 100644 --- a/spec/models/trait_interface_spec.rb +++ b/spec/models/trait_interface_spec.rb @@ -9,7 +9,7 @@ fixtures :traits - xspecify "Saving an unchanged trait shouldn't change it." do + specify "Saving an unchanged trait shouldn't change it." do t = traits(:test_trait) @@ -18,7 +18,7 @@ end - xspecify "Saving an unchanged trait shouldn't change it, even if date attributes are inconsistent." do + specify "Saving an unchanged trait shouldn't change it, even if date attributes are inconsistent." do t = traits(:trait_with_inconsistent_date_attributes) @@ -27,7 +27,7 @@ end - context "Updating a non-trait attribute of a trait having consistent date attributes" do + context "Updating a non-date attribute of a trait having consistent date attributes" do before(:example) do @trait = traits(:test_trait) @@ -56,4 +56,55 @@ end + context "Adding 1 to the d_year virtual attribute of a valid date should" do + before(:example) do + @trait = traits(:test_trait) + @starting_d_year = @trait.d_year + @trait.d_year += 1 + end + + it "increase the year by one" do + expect { @trait.save; @trait.reload }.to change { @trait.date.year }.by(1) + end + + it "not change the month" do + expect { @trait.save; @trait.reload }.not_to change { @trait.date.month } + end + + it "not change the day" do + expect { @trait.save; @trait.reload }.not_to change { @trait.date.day } + end + + it "not change the hour" do + expect { @trait.save; @trait.reload }.not_to change { @trait.date.hour } + end + + it "not change the minute" do + expect { @trait.save; @trait.reload }.not_to change { @trait.date.min } + end + + + it "increase the year virtual attribute by one" do + @trait.save + @trait.reload + expect(@trait.d_year - @starting_d_year).to eq(1) + end + + it "not change the month virtual attribute" do + expect { @trait.save; @trait.reload }.not_to change { @trait.d_month } + end + + it "not change the day virtual attribute" do + expect { @trait.save; @trait.reload }.not_to change { @trait.d_day } + end + + it "not change the hour virtual attribute" do + expect { @trait.save; @trait.reload }.not_to change { @trait.t_hour } + end + + it "not change the minute virtual attribute" do + expect { @trait.save; @trait.reload }.not_to change { @trait.t_minute } + end + + end end From 848f6b691a557cd552e984d3c085b4d45c7ede21 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Sat, 23 Jul 2016 16:09:17 -0500 Subject: [PATCH 43/51] When computing time and date LOC, use accessors to get the year, month, day, hour, and minute instead of using instance variables so that unspecified values default to those gleaned from the 'date' column instead of to nil. Only call process_datetime_input before saving if there IS some datetime input. --- app/models/trait.rb | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/app/models/trait.rb b/app/models/trait.rb index fb05a0c6b..38d1ceb43 100644 --- a/app/models/trait.rb +++ b/app/models/trait.rb @@ -50,8 +50,13 @@ class Trait < ActiveRecord::Base #-- ### Callbacks ### - before_save :process_datetime_input - + before_save :process_datetime_input, if: lambda { |obj| + !obj.instance_variable_get(:@d_year).nil? || + !obj.instance_variable_get(:@d_month).nil? || + !obj.instance_variable_get(:@d_day).nil? || + !obj.instance_variable_get(:@d_hour).nil? || + !obj.instance_variable_get(:@d_minute).nil? + } #-- @@ -430,12 +435,9 @@ def process_datetime_input def computed_dateloc - # Convenience variables; since we only use this method in cases where the - # accessor returns exactly the same value as the instance variable, we may - # as well use the latter since it's faster. - y = @d_year - m = @d_month - d = @d_day + y = d_year + m = d_month + d = d_day if !d.blank? if m.blank? @@ -475,20 +477,20 @@ def computed_dateloc end def computed_timeloc - if !@t_minute.blank? - if @t_hour.blank? + if !t_minute.blank? + if t_hour.blank? errors.add(:base, "If you specify minutes, you must specify the hour.") 9 - elsif TimesOfDay.include?(@t_hour) + elsif TimesOfDay.include?(t_hour) errors.add(:base, "If you select a time of day, minutes must be blank.") 9 else 2 end - else # @t_minute is blank - if @t_hour.blank? + else # t_minute is blank + if t_hour.blank? 9 - elsif TimesOfDay.include?(@t_hour) + elsif TimesOfDay.include?(t_hour) 4 else 3 From 8671d1c47fa1a63b7958118c14e850693011f9dd Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Sat, 23 Jul 2016 16:12:12 -0500 Subject: [PATCH 44/51] Tweaked one trait fixture and added one new one. --- test/fixtures/traits.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/test/fixtures/traits.yml b/test/fixtures/traits.yml index 2f2a6ba4d..8737ff418 100644 --- a/test/fixtures/traits.yml +++ b/test/fixtures/traits.yml @@ -8,7 +8,7 @@ test_trait: dateloc: 5 time: NULL timeloc: 2 - mean: NULL + mean: 1 n: NULL stat: NULL created_at: NULL @@ -25,6 +25,17 @@ test_trait: time_hour: NULL time_minute: NULL +trait_with_inconsistent_date_attributes: + site_id: 1 + specie_id: 1 + date: "2005-07-25 09:31:00" + dateloc: 5 + time: NULL + timeloc: 9 + mean: 1 + variable_id: 1 + access_level: 4 + test_edit_trait: id: 2 site_id: 1 From 7ce90b1d0408a1fc5c7498a79954e6b539a9ab65 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Tue, 26 Jul 2016 10:32:21 -0500 Subject: [PATCH 45/51] Started incorporating Factory Girl into RSpec tests. --- Gemfile | 1 + Gemfile.lock | 6 ++++++ spec/factories/sites.rb | 10 ++++++++++ spec/factories/traits.rb | 15 +++++++++++++++ spec/factories/variables.rb | 5 +++++ spec/models/trait_interface_spec.rb | 5 ++--- spec/support/factory_girl.rb | 3 +++ 7 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 spec/factories/sites.rb create mode 100644 spec/factories/traits.rb create mode 100644 spec/factories/variables.rb create mode 100644 spec/support/factory_girl.rb diff --git a/Gemfile b/Gemfile index 2a6b05969..2a2c29ae0 100644 --- a/Gemfile +++ b/Gemfile @@ -55,6 +55,7 @@ group :test do # gem "webrat", "0.7.1" gem "capybara" gem "database_cleaner" + gem "factory_girl_rails", "~> 4.0" end # If you have difficulty installing or don't wish to install capybara-webkit, diff --git a/Gemfile.lock b/Gemfile.lock index 12232d93d..438ffd1ad 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -63,6 +63,11 @@ GEM diff-lcs (1.2.5) dynamic_form (1.1.4) erubis (2.7.0) + factory_girl (4.7.0) + activesupport (>= 3.0.0) + factory_girl_rails (4.7.0) + factory_girl (~> 4.7.0) + railties (>= 3.0.0) ffi (1.9.8) hike (1.2.3) i18n (0.7.0) @@ -201,6 +206,7 @@ DEPENDENCIES comma (= 3.0.4) database_cleaner dynamic_form + factory_girl_rails (~> 4.0) json json-schema memoist diff --git a/spec/factories/sites.rb b/spec/factories/sites.rb new file mode 100644 index 000000000..c260fa4b8 --- /dev/null +++ b/spec/factories/sites.rb @@ -0,0 +1,10 @@ +FactoryGirl.define do + factory :site do + + sitename "Mordor" + lat 0 + lon 0 + masl 0 + + end +end diff --git a/spec/factories/traits.rb b/spec/factories/traits.rb new file mode 100644 index 000000000..ce1debfff --- /dev/null +++ b/spec/factories/traits.rb @@ -0,0 +1,15 @@ +FactoryGirl.define do + factory :trait do + trait :test_trait do + + site +# date "2005-07-25 09:31:00" +# dateloc 5 +# timeloc 2 + mean 1 + variable + access_level 4 + + end + end +end diff --git a/spec/factories/variables.rb b/spec/factories/variables.rb new file mode 100644 index 000000000..a05d7260a --- /dev/null +++ b/spec/factories/variables.rb @@ -0,0 +1,5 @@ +FactoryGirl.define do + factory :variable do + name "congeniality" + end +end diff --git a/spec/models/trait_interface_spec.rb b/spec/models/trait_interface_spec.rb index cb5b8c54b..469db1c98 100644 --- a/spec/models/trait_interface_spec.rb +++ b/spec/models/trait_interface_spec.rb @@ -3,15 +3,14 @@ config.fixture_path = "#{::Rails.root}/test/fixtures" end +require 'support/factory_girl' describe "Trait" do - fixtures :traits - specify "Saving an unchanged trait shouldn't change it." do - t = traits(:test_trait) + t = create :trait, :minimal_trait # A bug having to do with method nsec is averted by using reload here: expect { t.save }.not_to change { t.updated_at } diff --git a/spec/support/factory_girl.rb b/spec/support/factory_girl.rb new file mode 100644 index 000000000..eec437fb3 --- /dev/null +++ b/spec/support/factory_girl.rb @@ -0,0 +1,3 @@ +RSpec.configure do |config| + config.include FactoryGirl::Syntax::Methods +end From af02ee05f4934d29ac99ee6703cb140dfa91fa05 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Tue, 26 Jul 2016 10:42:25 -0500 Subject: [PATCH 46/51] Fixed first test in trait_interface_spec.rb. --- spec/factories/traits.rb | 10 ++++------ spec/models/trait_interface_spec.rb | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/spec/factories/traits.rb b/spec/factories/traits.rb index ce1debfff..8bc727be7 100644 --- a/spec/factories/traits.rb +++ b/spec/factories/traits.rb @@ -1,15 +1,13 @@ FactoryGirl.define do factory :trait do - trait :test_trait do + site + mean 1 + variable + access_level 4 - site # date "2005-07-25 09:31:00" # dateloc 5 # timeloc 2 - mean 1 - variable - access_level 4 - end end end diff --git a/spec/models/trait_interface_spec.rb b/spec/models/trait_interface_spec.rb index 469db1c98..2daa9a65d 100644 --- a/spec/models/trait_interface_spec.rb +++ b/spec/models/trait_interface_spec.rb @@ -10,7 +10,7 @@ specify "Saving an unchanged trait shouldn't change it." do - t = create :trait, :minimal_trait + t = create :trait # A bug having to do with method nsec is averted by using reload here: expect { t.save }.not_to change { t.updated_at } From 21d43f0d851b282b3f95cba1bd232baf03a78400 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Tue, 26 Jul 2016 13:57:02 -0500 Subject: [PATCH 47/51] Converted trait_interface_spec over to using Factory Girl. --- spec/factories/traits.rb | 18 +++++++++++++++--- spec/models/trait_interface_spec.rb | 11 +++++------ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/spec/factories/traits.rb b/spec/factories/traits.rb index 8bc727be7..6481b9a8d 100644 --- a/spec/factories/traits.rb +++ b/spec/factories/traits.rb @@ -5,9 +5,21 @@ variable access_level 4 -# date "2005-07-25 09:31:00" -# dateloc 5 -# timeloc 2 + factory :trait_with_inconsistent_date_attributes do + + date "2005-07-25 09:31:00" + dateloc 9 + timeloc 9 + + end + + factory :trait_with_consistent_date_attributes do + + date "2005-07-25 09:31:35" + dateloc 5 + timeloc 1 + + end end end diff --git a/spec/models/trait_interface_spec.rb b/spec/models/trait_interface_spec.rb index 2daa9a65d..b0cd8ec4e 100644 --- a/spec/models/trait_interface_spec.rb +++ b/spec/models/trait_interface_spec.rb @@ -17,19 +17,18 @@ end - specify "Saving an unchanged trait shouldn't change it, even if date attributes are inconsistent." do - - t = traits(:trait_with_inconsistent_date_attributes) + specify "Saving an unchanged trait shouldn't change the date, even if date attributes are inconsistent." do + t = create :trait_with_inconsistent_date_attributes # A bug having to do with method nsec is averted by using reload here: - expect { t.save }.not_to change { t.updated_at } + expect { t.save; t.reload }.not_to change { t.date } end context "Updating a non-date attribute of a trait having consistent date attributes" do before(:example) do - @trait = traits(:test_trait) + @trait = create(:trait) @trait.notes = "New note" end @@ -57,7 +56,7 @@ context "Adding 1 to the d_year virtual attribute of a valid date should" do before(:example) do - @trait = traits(:test_trait) + @trait = create(:trait_with_consistent_date_attributes) @starting_d_year = @trait.d_year @trait.d_year += 1 end From b3332891fa8460887f3fc77bad2df5c606cfb0c6 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Mon, 1 Aug 2016 16:57:35 -0500 Subject: [PATCH 48/51] Fixed bugs in pretty_date and pretty_time functions. --- ...dd_trait_and_yield_date_and_time_functions.rb | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/db/migrate/20160720182233_add_trait_and_yield_date_and_time_functions.rb b/db/migrate/20160720182233_add_trait_and_yield_date_and_time_functions.rb index 5b71ac500..6a0b08e66 100644 --- a/db/migrate/20160720182233_add_trait_and_yield_date_and_time_functions.rb +++ b/db/migrate/20160720182233_add_trait_and_yield_date_and_time_functions.rb @@ -18,15 +18,19 @@ def up SITE_OR_UTC_DATE timestamp; BEGIN - SELECT COALESCE(time_zone, 'UTC') FROM sites WHERE id = site_id INTO SITE_OR_UTC_TIMEZONE; + IF site_id IS NULL THEN + SITE_OR_UTC_TIMEZONE := 'UTC'; + ELSE + SELECT COALESCE(time_zone, 'UTC') FROM sites WHERE id = site_id INTO SITE_OR_UTC_TIMEZONE; + END IF; TIMEZONE_DESIGNATION := ''; IF timeloc = 9 AND dateloc IN (5, 6, 8, 95, 96) THEN TIMEZONE_DESIGNATION := FORMAT(' (%s)', SITE_OR_UTC_TIMEZONE); END IF; - /* Interpret the date column as being UTC (not server time!), then convert it site time (if determined) or UTC. - Note that "date || ' UTC'" is NULL if date is NULL (unlike CONCAT(date, ' UTC)', which is ' UTC' if date is NULL. + /* Interpret the date column as being UTC (not server time!), then convert it site time (if determined) or UTC. + Note that "date || ' UTC'" is NULL if date is NULL (unlike CONCAT(date, ' UTC'), which is ' UTC' if date is NULL). This is what we want. */ SELECT CAST((date::text || ' UTC') AS timestamp with time zone) AT TIME ZONE SITE_OR_UTC_TIMEZONE INTO SITE_OR_UTC_DATE; @@ -100,7 +104,11 @@ def up BEGIN - SELECT COALESCE(time_zone, 'UTC') FROM sites WHERE id = site_id INTO SITE_OR_UTC_TIMEZONE; + IF site_id IS NULL THEN + SITE_OR_UTC_TIMEZONE := 'UTC'; + ELSE + SELECT COALESCE(time_zone, 'UTC') FROM sites WHERE id = site_id INTO SITE_OR_UTC_TIMEZONE; + END IF; TIMEZONE_DESIGNATION := ''; IF timeloc != 9 THEN From f6225a9385f448767988bc1e9fb1da14a9e79a61 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Tue, 18 Jul 2017 23:34:01 -0500 Subject: [PATCH 49/51] Fixed pretty_date to show site timezone when timeloc is 9 and dateloc is 5.5. --- app/models/trait.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/trait.rb b/app/models/trait.rb index 38d1ceb43..53a9d2ef6 100644 --- a/app/models/trait.rb +++ b/app/models/trait.rb @@ -378,7 +378,7 @@ def pretty_date else date_in_site_timezone.to_formatted_s(date_format) + # show the site timezone next to the date only if the time is unspecified or unknown - (timeloc == 9 && [5, 6, 8, 95, 96].include?(dateloc) ? " (#{site_timezone})" : "") + (timeloc == 9 && [5, 5.5, 6, 8, 95, 96].include?(dateloc) ? " (#{site_timezone})" : "") end end From 2fd4f9a8367fede037661b29fad8516fa6a92424 Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Tue, 18 Jul 2017 23:47:18 -0500 Subject: [PATCH 50/51] Fixed trait model test to be consistent with the July 2016 update to the trait model use accessors instead of instance variables to get the year, month, day, hour, and minute. --- spec/models/trait_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/models/trait_spec.rb b/spec/models/trait_spec.rb index 5ae9edc4f..d9c08467d 100644 --- a/spec/models/trait_spec.rb +++ b/spec/models/trait_spec.rb @@ -108,7 +108,8 @@ class Trait describe "date and time semantics" do let(:sample_trait) do - Trait.create mean: 1, variable_id: 1, access_level: 1, site_id: 1 + Trait.create mean: 1, variable_id: 1, access_level: 1, site_id:1, + d_year: '', d_month: '', d_day: '', t_hour: '', t_minute: '' end describe "automatic dateloc setting" do From 3d5f09687700940fca3ceb424764a300034e921c Mon Sep 17 00:00:00 2001 From: Scott Rohde Date: Tue, 26 Mar 2019 15:14:59 -0500 Subject: [PATCH 51/51] Fix typo in abbreviation for season representing March-April-May. --- config/initializers/time_formats.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/initializers/time_formats.rb b/config/initializers/time_formats.rb index 3db9bf6fd..f5636aa0b 100644 --- a/config/initializers/time_formats.rb +++ b/config/initializers/time_formats.rb @@ -1,6 +1,6 @@ module DateTimeConstants - Seasons = ['Season: MAN', 'Season: JJA', 'Season: SON', 'Season: DJF'] - SeasonRepresentativeMonths = { 'Season: MAN' => 4, 'Season: JJA' => 7, 'Season: SON' => 10, 'Season: DJF' => 1 } + Seasons = ['Season: MAM', 'Season: JJA', 'Season: SON', 'Season: DJF'] + SeasonRepresentativeMonths = { 'Season: MAM' => 4, 'Season: JJA' => 7, 'Season: SON' => 10, 'Season: DJF' => 1 } TimesOfDay = ['morning', 'mid-day', 'afternoon', 'night'] TimesOfDayRepresentativeHours = { 'morning' => 9, 'mid-day' => 12, 'afternoon' => 15, 'night' => 0 }