Module: OccamsRecord::Results

Defined in:
lib/occams-record/results/results.rb,
lib/occams-record/results/row.rb

Overview

Classes and methods for handing query results.

Defined Under Namespace

Classes: Row

Constant Summary collapse

CASTER =

ActiveRecord’s internal type casting API changes from version to version.

case ActiveRecord::VERSION::MAJOR
when 4 then :type_cast_from_database
when 5, 6 then :deserialize
else raise "OccamsRecord::Results::CASTER does yet support this version of ActiveRecord"
end

Class Method Summary collapse

Class Method Details

.klass(column_names, column_types, association_names = [], model: nil, modules: nil) ⇒ OccamsRecord::Results::Row

Dynamically build a class for a specific set of result rows. It inherits from OccamsRecord::Results::Row, and optionall prepends user-defined modules.



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/occams-record/results/results.rb', line 22

def self.klass(column_names, column_types, association_names = [], model: nil, modules: nil)
  Class.new(Results::Row) do
    Array(modules).each { |mod| prepend mod } if modules

    self.columns = column_names.map(&:to_s)
    self.associations = association_names.map(&:to_s)
    self._model = model
    self.model_name = model ? model.name : nil
    self.table_name = model ? model.table_name : nil
    self.primary_key = if model&.primary_key and (pkey = model.primary_key.to_s) and columns.include?(pkey)
                         pkey
                       end

    # Build getters & setters for associations. (We need setters b/c they're set AFTER the row is initialized
    attr_accessor(*association_names)

    # Build a getter for each attribute returned by the query. The values will be type converted on demand.
    model_column_types = model ? model.attributes_builder.types : nil
    self.columns.each_with_index do |col, idx|
      #
      # NOTE there's lots of variation between DB adapters and AR versions here. Some notes:
      # * Postgres AR < 6.1 `column_types` will contain entries for every column.
      # * Postgres AR >= 6.1 `column_types` only contains entries for "exotic" types. Columns with "common" types have already been converted by the PG adapter.
      # * SQLite `column_types` will always be empty. Some types will have already been convered by the SQLite adapter, but others will depend on
      #   `model_column_types` for converstion. See test/raw_query_test.rb#test_common_types for examples.
      # * MySQL ?
      #
      type = column_types[col] || model_column_types&.[](col)
      case type&.type
      when nil
        define_method(col) { @raw_values[idx] }
      when :datetime
        define_method(col) { @cast_values[idx] ||= type.send(CASTER, @raw_values[idx])&.in_time_zone }
      when :boolean
        define_method(col) { @cast_values[idx] ||= type.send(CASTER, @raw_values[idx]) }
        define_method("#{col}?") { !!send(col) }
      else
        define_method(col) { @cast_values[idx] ||= type.send(CASTER, @raw_values[idx]) }
      end
    end
  end
end