lib/groupdate/magic.rb in groupdate2-4.1.5 vs lib/groupdate/magic.rb in groupdate2-5.0.0
- old
+ new
@@ -1,21 +1,45 @@
require "i18n"
module Groupdate
class Magic
+ DAYS = [:monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :sunday]
+
attr_accessor :period, :options, :group_index
def initialize(period:, **options)
@period = period
@options = options
- unknown_keywords = options.keys - [:day_start, :time_zone, :dates, :series, :week_start, :format, :locale, :range, :reverse]
+ validate_keywords
+ validate_arguments
+ end
+
+ def validate_keywords
+ known_keywords = [:time_zone, :dates, :series, :format, :locale, :range, :reverse, :series_label]
+
+ if %i[week day_of_week].include?(period)
+ known_keywords << :week_start
+ end
+
+ if %i[day week month quarter year day_of_week hour_of_day day_of_month day_of_year month_of_year].include?(period)
+ known_keywords << :day_start
+ else
+ # prevent Groupdate.day_start from applying
+ @day_start = 0
+ end
+
+ unknown_keywords = options.keys - known_keywords
raise ArgumentError, "unknown keywords: #{unknown_keywords.join(", ")}" if unknown_keywords.any?
+ end
- raise Groupdate::Error, "Unrecognized time zone" unless time_zone
- raise Groupdate::Error, "Unrecognized :week_start option" if period == :week && !week_start
- raise Groupdate::Error, "Cannot use endless range for :range option" if options[:range].is_a?(Range) && !options[:range].end
+ def validate_arguments
+ # TODO better messages
+ raise ArgumentError, "Unrecognized time zone" unless time_zone
+ raise ArgumentError, "Unrecognized :week_start option" unless week_start
+ raise ArgumentError, "Cannot use endless range for :range option" if options[:range].is_a?(Range) && !options[:range].end
+ raise ArgumentError, ":day_start must be between 0 and 24" if (day_start / 3600) < 0 || (day_start / 3600) >= 24
end
def time_zone
@time_zone ||= begin
time_zone = "Etc/UTC" if options[:time_zone] == false
@@ -23,17 +47,24 @@
time_zone.is_a?(ActiveSupport::TimeZone) ? time_zone : ActiveSupport::TimeZone[time_zone]
end
end
def week_start
- @week_start ||= [:mon, :tue, :wed, :thu, :fri, :sat, :sun].index((options[:week_start] || options[:start] || Groupdate.week_start).to_sym)
+ @week_start ||= begin
+ v = (options[:week_start] || Groupdate.week_start).to_sym
+ DAYS.index(v) || [:mon, :tue, :wed, :thu, :fri, :sat, :sun].index(v)
+ end
end
def day_start
@day_start ||= ((options[:day_start] || Groupdate.day_start).to_f * 3600).round
end
+ def series_label
+ @series_label ||= (options[:series_label].present? ? options[:series_label] : nil)
+ end
+
def series_builder
@series_builder ||=
SeriesBuilder.new(
**options,
period: period,
@@ -45,10 +76,15 @@
def time_range
series_builder.time_range
end
+ def self.validate_period(period, permit)
+ permitted_periods = ((permit || Groupdate::PERIODS).map(&:to_sym) & Groupdate::PERIODS).map(&:to_s)
+ raise ArgumentError, "Unpermitted period" unless permitted_periods.include?(period.to_s)
+ end
+
class Enumerable < Magic
def group_by(enum, &_block)
group = enum.group_by do |v|
v = yield(v)
raise ArgumentError, "Not a time" unless v.respond_to?(:to_time)
@@ -83,14 +119,14 @@
end
def cast_method
@cast_method ||= begin
case period
+ when :minute_of_hour, :hour_of_day, :day_of_month, :day_of_year, :month_of_year
+ lambda { |k| k.to_i }
when :day_of_week
lambda { |k| (k.to_i - 1 - week_start) % 7 }
- when :hour_of_day, :day_of_month, :month_of_year, :minute_of_hour
- lambda { |k| k.to_i }
else
utc = ActiveSupport::TimeZone["UTC"]
lambda { |k| (k.is_a?(String) || !k.respond_to?(:to_time) ? utc.parse(k.to_s) : k.to_time).in_time_zone(time_zone) }
end
end
@@ -128,10 +164,24 @@
raise Groupdate::Error, "Database missing time zone support for #{time_zone.tzinfo.name} - see https://github.com/ankane/groupdate#for-mysql"
end
end
end
+ def perform_series_label(relation, result)
+ label = options[:series_label]
+ return result unless label.present?
+
+ result.map do |r|
+ r.send("#{label}=", cast_series_label(r.send(label)))
+ r
+ end
+ end
+
+ def cast_series_label(original_label)
+ series_builder.format_series_label(cast_method.call(original_label))
+ end
+
def self.generate_relation(relation, field:, **options)
magic = Groupdate::Magic::Relation.new(**options)
# generate ActiveRecord relation
relation =
@@ -140,11 +190,12 @@
column: field,
period: magic.period,
time_zone: magic.time_zone,
time_range: magic.time_range,
week_start: magic.week_start,
- day_start: magic.day_start
+ day_start: magic.day_start,
+ series_label: magic.series_label
).generate
# add Groupdate info
magic.group_index = relation.group_values.size - 1
(relation.groupdate_values ||= []) << magic
@@ -154,9 +205,16 @@
# allow any options to keep flexible for future
def self.process_result(relation, result, **options)
relation.groupdate_values.reverse.each do |gv|
result = gv.perform(relation, result, default_value: options[:default_value])
+ end
+ result
+ end
+
+ def self.process_series_label(relation, result)
+ relation.groupdate_values.reverse.each do |gv|
+ result = gv.perform_series_label(relation, result)
end
result
end
end
end