Module: OccamsRecord::TypeCaster

Defined in:
lib/occams-record/type_caster.rb

Constant Summary collapse

CASTER =
case ActiveRecord::VERSION::MAJOR
when 6, 7, 8 then :deserialize
else raise "OccamsRecord::TypeCaster::CASTER does yet support this version of ActiveRecord"
end

Class Method Summary collapse

Class Method Details

.generate(column_names, column_types, model: nil) ⇒ Hash<Proc>

Returns a Hash containing type converters (a Proc) for each column. The Proc’s accept a value and return a converted value, mapping enum values from the model if necessary.

NOTE Some columns may have no Proc (particularly if you’re using SQLite and running a raw SQL query).



20
21
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
# File 'lib/occams-record/type_caster.rb', line 20

def self.generate(column_names, column_types, model: nil)
  column_names.each_with_object({}) { |col, memo|
    #
    # 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&.attributes_builder&.types&.[](col)

    #
    # NOTE is also some variation in when enum values are mapped in different AR versions.
    # In >=5.0, <=7.0, ActiveRecord::Result objects *usually* contain the human-readable values. In 4.2 and
    # pre-release versions of 7.1, they instead have the RAW values (e.g. integers) which we must map ourselves.
    #
    enum = model&.defined_enums&.[](col)
    inv_enum = enum&.invert

    memo[col] =
      case type&.type
      when nil
        if enum
          ->(val) { enum.has_key?(val) ? val : inv_enum[val] }
        end
      when :datetime
        ->(val) { type.send(CASTER, val)&.in_time_zone }
      else
        if enum
          ->(val) {
            val = type.send(CASTER, val)
            enum.has_key?(val) ? val : inv_enum[val]
          }
        else
          ->(val) { type.send(CASTER, val) }
        end
      end
  }
end