class Groupdate::Magic::Relation
Public Class Methods
Source
# File lib/groupdate/magic.rb, line 204 def self.generate_relation(relation, field:, **options) magic = Groupdate::Magic::Relation.new(**options) adapter_name = relation.connection_pool.with_connection { |c| c.adapter_name } adapter = Groupdate.adapters[adapter_name] raise Groupdate::Error, "Connection adapter not supported: #{adapter_name}" unless adapter # very important column = validate_column(field) column = resolve_column(relation, column) # generate ActiveRecord relation relation = adapter.new( relation, column: column, period: magic.period, time_zone: magic.time_zone, time_range: magic.time_range, week_start: magic.week_start, day_start: magic.day_start, n_seconds: magic.n_seconds, adapter_name: adapter_name ).generate # add Groupdate info magic.group_index = relation.group_values.size - 1 (relation.groupdate_values ||= []) << magic relation end
Source
# File lib/groupdate/magic.rb, line 122 def initialize(**options) super(**options.reject { |k, _| [:default_value, :carry_forward, :last, :current].include?(k) }) @options = options end
Calls superclass method
Groupdate::Magic::new
Source
# File lib/groupdate/magic.rb, line 261 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
allow any options to keep flexible for future
Source
# File lib/groupdate/magic.rb, line 253 def resolve_column(relation, column) node = relation.send(:relation).send(:arel_columns, [column]).first node = Arel::Nodes::SqlLiteral.new(node) if node.is_a?(String) relation.connection_pool.with_connection { |c| c.visitor.accept(node, Arel::Collectors::SQLString.new).value } end
resolves eagerly need to convert both where_clause (easy) and group_clause (not easy) if want to avoid this
Source
# File lib/groupdate/magic.rb, line 240 def validate_column(column) unless column.is_a?(Symbol) || column.is_a?(Arel::Nodes::SqlLiteral) column = column.to_s unless /\A\w+(\.\w+)?\z/i.match?(column) raise ActiveRecord::UnknownAttributeReference, "Query method called with non-attribute argument(s): #{column.inspect}. Use Arel.sql() for known-safe values." end end column end
basic version of Active Record disallow_raw_sql! symbol = column (safe), Arel node = SQL (safe), other = untrusted matches table.column and column
Public Instance Methods
Source
# File lib/groupdate/magic.rb, line 145 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 :day, :week, :month, :quarter, :year # TODO keep as date if day_start != 0 day_start_hour = day_start / 3600 day_start_min = (day_start % 3600) / 60 day_start_sec = (day_start % 3600) % 60 lambda { |k| k.in_time_zone(time_zone).change(hour: day_start_hour, min: day_start_min, sec: day_start_sec) } else lambda { |k| k.in_time_zone(time_zone) } end 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 end
Source
# File lib/groupdate/magic.rb, line 169 def cast_result(result, multiple_groups) new_result = {} result.each do |k, v| if multiple_groups k[group_index] = cast_method.call(k[group_index]) else k = cast_method.call(k) end new_result[k] = v end new_result end
Source
# File lib/groupdate/magic.rb, line 193 def check_nils(result, multiple_groups, relation) has_nils = multiple_groups ? (result.keys.first && result.keys.first[group_index].nil?) : result.key?(nil) if has_nils if time_zone_support?(relation) raise Groupdate::Error, "Invalid query - be sure to use a date or time column" else raise Groupdate::Error, "Database missing time zone support for #{time_zone.tzinfo.name} - see https://github.com/ankane/groupdate#for-mysql" end end end
Source
# File lib/groupdate/magic.rb, line 127 def perform(relation, result, default_value:) if defined?(ActiveRecord::Promise) && result.is_a?(ActiveRecord::Promise) return result.then { |r| perform(relation, r, default_value: default_value) } end multiple_groups = relation.group_values.size > 1 check_nils(result, multiple_groups, relation) result = cast_result(result, multiple_groups) series_builder.generate( result, default_value: options.key?(:default_value) ? options[:default_value] : default_value, multiple_groups: multiple_groups, group_index: group_index ) end
Source
# File lib/groupdate/magic.rb, line 182 def time_zone_support?(relation) relation.connection_pool.with_connection do |connection| if connection.adapter_name.match?(/mysql|trilogy/i) sql = relation.send(:sanitize_sql_array, ["SELECT CONVERT_TZ(NOW(), '+00:00', ?)", time_zone.tzinfo.name]) !connection.select_all(sql).to_a.first.values.first.nil? else true end end end