lib/groupdate/scopes.rb in groupdate-1.0.2 vs lib/groupdate/scopes.rb in groupdate-1.0.3

- old
+ new

@@ -2,108 +2,79 @@ require "groupdate/series" require "active_record" module Groupdate module Scopes - extend ActiveSupport::Concern + time_fields = %w(second minute hour day week month year) + number_fields = %w(day_of_week hour_of_day) + (time_fields + number_fields).each do |field| + define_method :"group_by_#{field}" do |*args| + args = args.dup + options = args[-1].is_a?(Hash) ? args.pop : {} + column = connection.quote_table_name(args[0]) + time_zone = args[1] || Time.zone || "Etc/UTC" + if time_zone.is_a?(ActiveSupport::TimeZone) or time_zone = ActiveSupport::TimeZone[time_zone] + time_zone = time_zone.tzinfo.name + else + raise "Unrecognized time zone" + end - # Pattern from kaminari - # https://github.com/amatsuda/kaminari/blob/master/lib/kaminari/models/active_record_extension.rb - included do - # Future subclasses will pick up the model extension - class << self - def inherited_with_groupdate(kls) #:nodoc: - inherited_without_groupdate kls - kls.send(:include, ClassMethods) if kls.superclass == ActiveRecord::Base + # for week + week_start = [:mon, :tue, :wed, :thu, :fri, :sat, :sun].index((options[:start] || :sun).to_sym) + if field == "week" and !week_start + raise "Unrecognized :start option" end - alias_method_chain :inherited, :groupdate - end - # Existing subclasses pick up the model extension as well - self.descendants.each do |kls| - kls.send(:include, ClassMethods) if kls.superclass == ActiveRecord::Base - end - end - - module ClassMethods - extend ActiveSupport::Concern - - included do - # Field list from - # http://www.postgresql.org/docs/9.1/static/functions-datetime.html - time_fields = %w(second minute hour day week month year) - number_fields = %w(day_of_week hour_of_day) - (time_fields + number_fields).each do |field| - # no define_singleton_method in ruby 1.8 - (class << self; self end).send :define_method, :"group_by_#{field}" do |*args| - args = args.dup - options = args[-1].is_a?(Hash) ? args.pop : {} - column = connection.quote_table_name(args[0]) - time_zone = args[1] || Time.zone || "Etc/UTC" - if time_zone.is_a?(ActiveSupport::TimeZone) or time_zone = ActiveSupport::TimeZone[time_zone] - time_zone = time_zone.tzinfo.name + query = + case connection.adapter_name + when "MySQL", "Mysql2" + case field + when "day_of_week" # Sunday = 0, Monday = 1, etc + # use CONCAT for consistent return type (String) + ["DAYOFWEEK(CONVERT_TZ(#{column}, '+00:00', ?)) - 1", time_zone] + when "hour_of_day" + ["EXTRACT(HOUR from CONVERT_TZ(#{column}, '+00:00', ?))", time_zone] + when "week" + ["CONVERT_TZ(DATE_FORMAT(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL ((#{7 - week_start} + WEEKDAY(CONVERT_TZ(#{column}, '+00:00', ?))) % 7) DAY), '+00:00', ?), '%Y-%m-%d 00:00:00'), ?, '+00:00')", time_zone, time_zone, time_zone] else - raise "Unrecognized time zone" - end - - # for week - week_start = [:mon, :tue, :wed, :thu, :fri, :sat, :sun].index((options[:start] || :sun).to_sym) - if field == "week" and !week_start - raise "Unrecognized :start option" - end - - query = - case connection.adapter_name - when "MySQL", "Mysql2" + format = case field - when "day_of_week" # Sunday = 0, Monday = 1, etc - # use CONCAT for consistent return type (String) - ["DAYOFWEEK(CONVERT_TZ(#{column}, '+00:00', ?)) - 1", time_zone] - when "hour_of_day" - ["EXTRACT(HOUR from CONVERT_TZ(#{column}, '+00:00', ?))", time_zone] - when "week" - ["CONVERT_TZ(DATE_FORMAT(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL ((#{7 - week_start} + WEEKDAY(CONVERT_TZ(#{column}, '+00:00', ?))) % 7) DAY), '+00:00', ?), '%Y-%m-%d 00:00:00'), ?, '+00:00')", time_zone, time_zone, time_zone] - else - format = - case field - when "second" - "%Y-%m-%d %H:%i:%S" - when "minute" - "%Y-%m-%d %H:%i:00" - when "hour" - "%Y-%m-%d %H:00:00" - when "day" - "%Y-%m-%d 00:00:00" - when "month" - "%Y-%m-01 00:00:00" - else # year - "%Y-01-01 00:00:00" - end - - ["CONVERT_TZ(DATE_FORMAT(CONVERT_TZ(#{column}, '+00:00', ?), '#{format}'), ?, '+00:00')", time_zone, time_zone] + when "second" + "%Y-%m-%d %H:%i:%S" + when "minute" + "%Y-%m-%d %H:%i:00" + when "hour" + "%Y-%m-%d %H:00:00" + when "day" + "%Y-%m-%d 00:00:00" + when "month" + "%Y-%m-01 00:00:00" + else # year + "%Y-01-01 00:00:00" end - when "PostgreSQL" - case field - when "day_of_week" - ["EXTRACT(DOW from #{column}::timestamptz AT TIME ZONE ?)::integer", time_zone] - when "hour_of_day" - ["EXTRACT(HOUR from #{column}::timestamptz AT TIME ZONE ?)::integer", time_zone] - when "week" # start on Sunday, not PostgreSQL default Monday - ["(DATE_TRUNC('#{field}', (#{column}::timestamptz - INTERVAL '#{week_start} day') AT TIME ZONE ?) + INTERVAL '#{week_start} day') AT TIME ZONE ?", time_zone, time_zone] - else - ["DATE_TRUNC('#{field}', #{column}::timestamptz AT TIME ZONE ?) AT TIME ZONE ?", time_zone, time_zone] - end - else - raise "Connection adapter not supported: #{connection.adapter_name}" - end - group = group(Groupdate::OrderHack.new(sanitize_sql_array(query), field, time_zone)) - if args[2] - Series.new(group, field, column, time_zone, args[2], week_start) + ["CONVERT_TZ(DATE_FORMAT(CONVERT_TZ(#{column}, '+00:00', ?), '#{format}'), ?, '+00:00')", time_zone, time_zone] + end + when "PostgreSQL", "PostGIS" + case field + when "day_of_week" + ["EXTRACT(DOW from #{column}::timestamptz AT TIME ZONE ?)::integer", time_zone] + when "hour_of_day" + ["EXTRACT(HOUR from #{column}::timestamptz AT TIME ZONE ?)::integer", time_zone] + when "week" # start on Sunday, not PostgreSQL default Monday + ["(DATE_TRUNC('#{field}', (#{column}::timestamptz - INTERVAL '#{week_start} day') AT TIME ZONE ?) + INTERVAL '#{week_start} day') AT TIME ZONE ?", time_zone, time_zone] else - group + ["DATE_TRUNC('#{field}', #{column}::timestamptz AT TIME ZONE ?) AT TIME ZONE ?", time_zone, time_zone] end + else + raise "Connection adapter not supported: #{connection.adapter_name}" end + + group = group(Groupdate::OrderHack.new(sanitize_sql_array(query), field, time_zone)) + if args[2] + Series.new(group, field, column, time_zone, args[2], week_start) + else + group end end end end end