occams-record 1.8.0 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 846af974c8bc267fe8e901995d0aa3341b018d135eeb267c11d9add447cd0155
4
- data.tar.gz: 763f8a038908cb9a990bcf305644661736dd9133e51a9d8fb8ece2b8ff018fd2
3
+ metadata.gz: 2778b16aee6b725c967c4877b9905eed75b37b64bf8c5fca7077918ea2a4a53a
4
+ data.tar.gz: b8948ddd57573efcbeb90e96235626e482cc557e9b92d4e5fdf0237c182e39df
5
5
  SHA512:
6
- metadata.gz: 24e1c5dfb09cb35b2b7116cb40aafcb1eb215ac96e3c8bab7ff6558598b31e8abb77b49c2b711392567862b182e1aed705888e939f8b66f15285f2ca352dbc89
7
- data.tar.gz: 04f78d14765b6ae4d9ccee5f01633132ed1e39662071dfc39f6e33583e9cd3e6214841225eb35ce2f678472ce34bf45e27e23660114f28cede6a27cdb38613b0
6
+ metadata.gz: e436f30e97f0a256d146539902b3f6fe9b54c241d7b49a51da63242f59505793c14f6ceef601343660750d0030ddf21c196e40a4aec41b1b5f1490b71b255ca6
7
+ data.tar.gz: 14095b912856efe2a097471046e518c0d3040320e72198ae28cf8b76c84040ca3319f16916ef2c70c357d8661e6465a36d7b80c49109896c828a2cc36dd777ee
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Occams Record [![Build Status](https://travis-ci.org/jhollinger/occams-record.svg?branch=master)](https://travis-ci.org/jhollinger/occams-record)
1
+ # Occams Record
2
2
 
3
3
  > Do not multiply entities beyond necessity. -- Occam's Razor
4
4
 
@@ -298,32 +298,40 @@ On the other hand, Active Record makes it *very* easy to forget to eager load as
298
298
 
299
299
  # Testing
300
300
 
301
- ```bash
302
- bundle install
303
-
304
- # test against SQLite
305
- bundle exec rake test
306
-
307
- # test against Postgres
308
- TEST_DATABASE_URL=postgresql://postgres@localhost:5432/occams_record bundle exec rake test
309
-
310
- # test against MySQL
311
- TEST_DATABASE_URL=mysql2://root:@127.0.0.1:3306/occams_record bundle exec rake test
312
- ```
313
-
314
- **Test against all supported ActiveRecord versions**
301
+ Tests are run with `appraisal` in Docker Compose using the `bin/test` or `bin/testall` scripts.
315
302
 
316
303
  ```bash
317
- bundle exec appraisal install
318
-
319
- # test against all supported AR versions (defaults to SQLite)
320
- bundle exec appraisal rake test
321
-
322
- # test against a specific AR version
323
- bundle exec appraisal ar-7.0 rake test
324
-
325
- # test against Postgres
326
- TEST_DATABASE_URL=postgresql://postgres@localhost:5432/occams_record bundle exec appraisal rake test
304
+ # Run tests against all supported ActiveRecord versions, Ruby versions, and databases
305
+ bin/testall
306
+
307
+ # Run tests for Ruby vX only
308
+ bin/testall ruby-3.1
309
+
310
+ # Run tests for ActiveRecord vX only
311
+ bin/testall ar-6.1
312
+
313
+ # Run tests against a specific database
314
+ bin/testall sqlite|postgres-14|mysql-8
315
+
316
+ # Run exactly one set of tests
317
+ bin/test ruby-3.1 ar-7.0 postgres-14
318
+
319
+ # If all tests complete successfully, you'll be rewarded by an ASCII Nyancat!
320
+
321
+ + o + o
322
+ + o + +
323
+ o +
324
+ o + + +
325
+ + o o + o
326
+ -_-_-_-_-_-_-_,------, o
327
+ _-_-_-_-_-_-_-| /\_/\
328
+ -_-_-_-_-_-_-~|__( ^ .^) + +
329
+ _-_-_-_-_-_-_-"" ""
330
+ + o o + o
331
+ + +
332
+ o o o o +
333
+ o +
334
+ + + o o +
327
335
  ```
328
336
 
329
337
  # License
@@ -12,45 +12,24 @@ module OccamsRecord
12
12
 
13
13
  # returns an array of values
14
14
  def pluck_results_single(results, col, model: nil)
15
- enum = model&.defined_enums&.[](col)
16
- inv_enum = enum&.invert
17
- if enum
18
- results.map { |r|
19
- val = r[col]
20
- enum.has_key?(val) ? val : inv_enum[val]
21
- }
22
- else
23
- # micro-optimization for when there are no enums
24
- results.map { |r| r[col] }
25
- end
15
+ casters = TypeCaster.generate(results.columns, results.column_types, model: model)
16
+ col = results.columns[0]
17
+ caster = casters[col]
18
+ results.map { |row|
19
+ val = row[col]
20
+ caster ? caster.(val) : val
21
+ }
26
22
  end
27
23
 
28
24
  # returns an array of arrays
29
25
  def pluck_results_multi(results, cols, model: nil)
30
- any_enums = false
31
- cols_with_enums = cols.map { |col|
32
- enum = model&.defined_enums&.[](col)
33
- any_enums ||= !!enum
34
- [col, enum, enum&.invert]
35
- }
36
-
37
- if any_enums
38
- results.map { |row|
39
- cols_with_enums.map { |(col, enum, inv_enum)|
40
- if enum
41
- val = row[col]
42
- enum.has_key?(val) ? val : inv_enum[val]
43
- else
44
- row[col]
45
- end
46
- }
47
- }
48
- else
49
- # micro-optimization for when there are no enums
50
- results.map { |row|
51
- cols.map { |col| row[col] }
26
+ casters = TypeCaster.generate(results.columns, results.column_types, model: model)
27
+ results.map { |row|
28
+ row.map { |col, val|
29
+ caster = casters[col]
30
+ caster ? caster.(val) : val
52
31
  }
53
- end
32
+ }
54
33
  end
55
34
  end
56
35
  end
@@ -1,13 +1,6 @@
1
1
  module OccamsRecord
2
2
  # Classes and methods for handing query results.
3
3
  module Results
4
- # ActiveRecord's internal type casting API changes from version to version.
5
- CASTER = case ActiveRecord::VERSION::MAJOR
6
- when 4 then :type_cast_from_database
7
- when 5, 6, 7 then :deserialize
8
- else raise "OccamsRecord::Results::CASTER does yet support this version of ActiveRecord"
9
- end
10
-
11
4
  #
12
5
  # Dynamically build a class for a specific set of result rows. It inherits from OccamsRecord::Results::Row, and optionall prepends
13
6
  # user-defined modules.
@@ -43,53 +36,22 @@ module OccamsRecord
43
36
  attr_accessor(*association_names)
44
37
 
45
38
  # Build a getter for each attribute returned by the query. The values will be type converted on demand.
46
- model_column_types = model ? model.attributes_builder.types : nil
39
+ casters = TypeCaster.generate(column_names, column_types, model: model)
47
40
  self.columns.each_with_index do |col, idx|
48
- #
49
- # NOTE there's lots of variation between DB adapters and AR versions here. Some notes:
50
- # * Postgres AR < 6.1 `column_types` will contain entries for every column.
51
- # * Postgres AR >= 6.1 `column_types` only contains entries for "exotic" types. Columns with "common" types have already been converted by the PG adapter.
52
- # * SQLite `column_types` will always be empty. Some types will have already been convered by the SQLite adapter, but others will depend on
53
- # `model_column_types` for converstion. See test/raw_query_test.rb#test_common_types for examples.
54
- # * MySQL ?
55
- #
56
- type = column_types[col] || model_column_types&.[](col)
57
-
58
- #
59
- # NOTE is also some variation in when enum values are mapped in different AR versions.
60
- # In >=5.0, <=7.0, ActiveRecord::Result objects contain the human-readable values. In 4.2 and
61
- # pre-release versions of 7.1, they instead have the RAW values (e.g. integers) which we must map ourselves.
62
- #
63
- enum = model&.defined_enums&.[](col)
64
- inv_enum = enum&.invert
41
+ caster = casters[col]
65
42
 
66
- case type&.type
67
- when nil
68
- if enum
69
- define_method(col) {
70
- val = @raw_values[idx]
71
- enum.has_key?(val) ? val : inv_enum[val]
72
- }
73
- else
74
- define_method(col) { @raw_values[idx] }
75
- end
76
- when :datetime
77
- define_method(col) { @cast_values[idx] ||= type.send(CASTER, @raw_values[idx])&.in_time_zone }
78
- when :boolean
79
- define_method(col) { @cast_values[idx] ||= type.send(CASTER, @raw_values[idx]) }
80
- define_method("#{col}?") { !!send(col) }
43
+ if caster
44
+ define_method(col) {
45
+ @cast_values[idx] = caster.(@raw_values[idx]) if @cast_values[idx].nil?
46
+ @cast_values[idx]
47
+ }
81
48
  else
82
- if enum
83
- define_method(col) {
84
- @cast_values[idx] ||= (
85
- val = type.send(CASTER, @raw_values[idx])
86
- enum.has_key?(val) ? val : inv_enum[val]
87
- )
88
- }
89
- else
90
- define_method(col) { @cast_values[idx] ||= type.send(CASTER, @raw_values[idx]) }
91
- end
49
+ define_method(col) {
50
+ @raw_values[idx]
51
+ }
92
52
  end
53
+
54
+ define_method("#{col}?") { !!send(col) }
93
55
  end
94
56
  end
95
57
  end
@@ -0,0 +1,60 @@
1
+ module OccamsRecord
2
+ # @private
3
+ module TypeCaster
4
+ CASTER =
5
+ case ActiveRecord::VERSION::MAJOR
6
+ when 4 then :type_cast_from_database
7
+ when 5, 6, 7 then :deserialize
8
+ else raise "OccamsRecord::TypeCaster::CASTER does yet support this version of ActiveRecord"
9
+ end
10
+
11
+ #
12
+ # @param column_names [Array<String>] the column names in the result set. The order MUST match the order returned by the query.
13
+ # @param column_types [Hash] Column name => type from an ActiveRecord::Result
14
+ # @param model [ActiveRecord::Base] the AR model representing the table (it holds column & type info).
15
+ # @return [Hash<Proc>] a Hash of casting Proc's keyed by column
16
+ #
17
+ def self.generate(column_names, column_types, model: nil)
18
+ column_names.each_with_object({}) { |col, memo|
19
+ #
20
+ # NOTE there's lots of variation between DB adapters and AR versions here. Some notes:
21
+ # * Postgres AR < 6.1 `column_types` will contain entries for every column.
22
+ # * Postgres AR >= 6.1 `column_types` only contains entries for "exotic" types. Columns with "common" types have already been converted by the PG adapter.
23
+ # * SQLite `column_types` will always be empty. Some types will have already been convered by the SQLite adapter, but others will depend on
24
+ # `model_column_types` for converstion. See test/raw_query_test.rb#test_common_types for examples.
25
+ # * MySQL ?
26
+ #
27
+ type = column_types[col] || model&.attributes_builder&.types&.[](col)
28
+
29
+ #
30
+ # NOTE is also some variation in when enum values are mapped in different AR versions.
31
+ # In >=5.0, <=7.0, ActiveRecord::Result objects *usually* contain the human-readable values. In 4.2 and
32
+ # pre-release versions of 7.1, they instead have the RAW values (e.g. integers) which we must map ourselves.
33
+ #
34
+ enum = model&.defined_enums&.[](col)
35
+ inv_enum = enum&.invert
36
+
37
+ memo[col] =
38
+ case type&.type
39
+ when nil
40
+ if enum
41
+ ->(val) { enum.has_key?(val) ? val : inv_enum[val] }
42
+ end
43
+ when :datetime
44
+ ->(val) { type.send(CASTER, val)&.in_time_zone }
45
+ when :boolean
46
+ ->(val) { type.send(CASTER, val) }
47
+ else
48
+ if enum
49
+ ->(val) {
50
+ val = type.send(CASTER, val)
51
+ enum.has_key?(val) ? val : inv_enum[val]
52
+ }
53
+ else
54
+ ->(val) { type.send(CASTER, val) }
55
+ end
56
+ end
57
+ }
58
+ end
59
+ end
60
+ end
@@ -3,5 +3,5 @@
3
3
  #
4
4
  module OccamsRecord
5
5
  # @private
6
- VERSION = "1.8.0".freeze
6
+ VERSION = "1.8.1".freeze
7
7
  end
data/lib/occams-record.rb CHANGED
@@ -3,6 +3,7 @@ require 'occams-record/version'
3
3
  require 'occams-record/merge'
4
4
  require 'occams-record/measureable'
5
5
  require 'occams-record/eager_loaders/eager_loaders'
6
+ require 'occams-record/type_caster'
6
7
  require 'occams-record/results/results'
7
8
  require 'occams-record/results/row'
8
9
  require 'occams-record/pluck'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: occams-record
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.0
4
+ version: 1.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jordan Hollinger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-07-01 00:00:00.000000000 Z
11
+ date: 2023-07-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -30,20 +30,6 @@ dependencies:
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
32
  version: '7.1'
33
- - !ruby/object:Gem::Dependency
34
- name: appraisal
35
- requirement: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - ">="
38
- - !ruby/object:Gem::Version
39
- version: '0'
40
- type: :development
41
- prerelease: false
42
- version_requirements: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - ">="
45
- - !ruby/object:Gem::Version
46
- version: '0'
47
33
  description: A faster, lower-memory, fuller-featured querying API for ActiveRecord
48
34
  that returns results as unadorned, read-only objects.
49
35
  email: jordan.hollinger@gmail.com
@@ -79,6 +65,7 @@ files:
79
65
  - lib/occams-record/raw_query.rb
80
66
  - lib/occams-record/results/results.rb
81
67
  - lib/occams-record/results/row.rb
68
+ - lib/occams-record/type_caster.rb
82
69
  - lib/occams-record/ugly.rb
83
70
  - lib/occams-record/version.rb
84
71
  homepage: https://jhollinger.github.io/occams-record/