occams-record 1.7.1 → 1.8.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0dc125a06b3fa732e1064483f13e5abbe2a7c64b4e6ac7f1b2d37337d8e233cb
4
- data.tar.gz: 5c47a9a644b757463d5dd1961c1b86ed1de50cf247a286e8d1d0c3dd5daa99b8
3
+ metadata.gz: 2778b16aee6b725c967c4877b9905eed75b37b64bf8c5fca7077918ea2a4a53a
4
+ data.tar.gz: b8948ddd57573efcbeb90e96235626e482cc557e9b92d4e5fdf0237c182e39df
5
5
  SHA512:
6
- metadata.gz: 7fb55ec18a98e4d2bb4b3fab8d3f609a378bee578d1b405d56a43b1f035f40d53149fc8dabc321bbaf17b180eb9e3b1dda282cc2fd8675a3e10c8bc59a9f7a59
7
- data.tar.gz: f9f28ad1537cc297a0b5b1fe8a5209d4f4dfa4529148a603f715da884fa9d9f8188836aca84c8e5dcc36f674bb6abe7211de57c014e90a4539d892da0192c28d
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
 
@@ -284,7 +284,6 @@ The following ActiveRecord features are under consideration, but not high priori
284
284
  The following ActiveRecord features are not supported, and likely never will be. Pull requests are still welcome, though.
285
285
 
286
286
  * Eager loading `through` associations that involve a polymorphic association.
287
- * ActiveRecord enum types
288
287
  * ActiveRecord serialized types
289
288
 
290
289
  ---
@@ -299,32 +298,40 @@ On the other hand, Active Record makes it *very* easy to forget to eager load as
299
298
 
300
299
  # Testing
301
300
 
302
- ```bash
303
- bundle install
304
-
305
- # test against SQLite
306
- bundle exec rake test
307
-
308
- # test against Postgres
309
- TEST_DATABASE_URL=postgresql://postgres@localhost:5432/occams_record bundle exec rake test
310
-
311
- # test against MySQL
312
- TEST_DATABASE_URL=mysql2://root:@127.0.0.1:3306/occams_record bundle exec rake test
313
- ```
314
-
315
- **Test against all supported ActiveRecord versions**
301
+ Tests are run with `appraisal` in Docker Compose using the `bin/test` or `bin/testall` scripts.
316
302
 
317
303
  ```bash
318
- bundle exec appraisal install
319
-
320
- # test against all supported AR versions (defaults to SQLite)
321
- bundle exec appraisal rake test
322
-
323
- # test against a specific AR version
324
- bundle exec appraisal ar-7.0 rake test
325
-
326
- # test against Postgres
327
- 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 +
328
335
  ```
329
336
 
330
337
  # License
@@ -2,14 +2,34 @@ module OccamsRecord
2
2
  module Pluck
3
3
  private
4
4
 
5
- def pluck_results(results, cols)
5
+ def pluck_results(results, cols, model: nil)
6
6
  if cols.size == 1
7
- col = cols[0].to_s
8
- results.map { |r| r[col] }
7
+ pluck_results_single(results, cols[0].to_s, model: model)
9
8
  else
10
- cols = cols.map(&:to_s)
11
- results.map { |r| r.values_at(*cols) }
9
+ pluck_results_multi(results, cols.map(&:to_s), model: model)
12
10
  end
13
11
  end
12
+
13
+ # returns an array of values
14
+ def pluck_results_single(results, col, model: nil)
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
+ }
22
+ end
23
+
24
+ # returns an array of arrays
25
+ def pluck_results_multi(results, cols, model: nil)
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
31
+ }
32
+ }
33
+ end
14
34
  end
15
35
  end
@@ -250,7 +250,7 @@ module OccamsRecord
250
250
  else
251
251
  model.connection.exec_query sql
252
252
  end
253
- pluck_results result, cols
253
+ pluck_results result, cols, model: @model
254
254
  end
255
255
  end
256
256
  end
@@ -229,7 +229,7 @@ module OccamsRecord
229
229
  else
230
230
  conn.exec_query _escaped_sql
231
231
  end
232
- pluck_results result, cols
232
+ pluck_results result, cols, model: @eager_loaders.model
233
233
  end
234
234
 
235
235
  private
@@ -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,28 +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
- case type&.type
58
- when nil
59
- define_method(col) { @raw_values[idx] }
60
- when :datetime
61
- define_method(col) { @cast_values[idx] ||= type.send(CASTER, @raw_values[idx])&.in_time_zone }
62
- when :boolean
63
- define_method(col) { @cast_values[idx] ||= type.send(CASTER, @raw_values[idx]) }
64
- define_method("#{col}?") { !!send(col) }
41
+ caster = casters[col]
42
+
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
+ }
65
48
  else
66
- define_method(col) { @cast_values[idx] ||= type.send(CASTER, @raw_values[idx]) }
49
+ define_method(col) {
50
+ @raw_values[idx]
51
+ }
67
52
  end
53
+
54
+ define_method("#{col}?") { !!send(col) }
68
55
  end
69
56
  end
70
57
  end
@@ -40,7 +40,7 @@ module OccamsRecord
40
40
  #
41
41
  # Hash-like accessor for attributes and associations.
42
42
  #
43
- # @param attr [String|Symbol\
43
+ # @param attr [String|Symbol]
44
44
  # @return [Object]
45
45
  #
46
46
  def [](attr)
@@ -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.7.1".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.7.1
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-05-30 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/