jpie 4.0.0 → 4.1.0

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: fd3d3cf0f731b0a5d47d2e0ae3b4fc405d7b49ae775da39baf04a3f862b06d3b
4
- data.tar.gz: 2d305cd5e41bb20cc5ef36fdf0321407425e503fee2a7424f40035cb9fda0059
3
+ metadata.gz: 5c04100490b218fa6dbef0035d581acd97ec7ebae03d045d0966a633bdcfb567
4
+ data.tar.gz: 116d6e4ae7f51f03a47f8222771f2fb508435fce759014eadd3a02642a4f7b85
5
5
  SHA512:
6
- metadata.gz: beeb386f1743ca01b78a5532925d16115b66ca94ae00f31b1b94118c7b0bdf63367e00242593b78e62cd891c7e497cc00b722ee17d6fa37b724f2de5aa40d178
7
- data.tar.gz: e6362ed9996bec7dedabf0891e9a50a4ff80f240829cf6f7e08decf4b4ddf5d29c139f6e71eb88de1a53648449c416907a81c7f73dee1134e49a4b8704ca5063
6
+ metadata.gz: e86bd604abba1b02e6148d857a2967a05b90a35c796503f2517d25368cbb95a8c102afddc2208a8f77961085bc041a55e27e6b6d0f304b37ed9404362fbf64eb
7
+ data.tar.gz: 3ec3bda5b8ce2e07fb494f790cbc20391b37fd12a9f57c6a352b3129a9037246116d68440f70cb86b17e3ca424ad6bd4efdbba174b3af8825cb1149bfc59fbb5
data/CHANGELOG.md CHANGED
@@ -7,6 +7,36 @@ at release time.
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [4.1.0] - 2026-09-15
11
+
12
+ ### Fixed
13
+
14
+ - **Preload the include tree, never join it.** `scope_with_includes` handed the
15
+ tree to `includes`, and Rails answers `includes` with one LEFT OUTER JOIN
16
+ across every path as soon as the scope answers `eager_loading?` with true. A
17
+ single condition on a table that no `joins` names is enough to cause that: a
18
+ resource default such as
19
+ `super.includes(:archival).where(archivals: { archived_at: nil })` does it.
20
+ Every collection path in the tree then multiplied row for row. A reported
21
+ page of 25 records joined 75,712 rows and allocated 796.1MB to return an
22
+ 822kB document, and 98% of those bytes were the rows, not the serializer. The
23
+ tree now preloads in every case: the same document costs 18.1MB in 12
24
+ queries. Serialization filters on none of the included tables, so the tree
25
+ never needed the join, and a scope's own `includes` is untouched — it still
26
+ supplies whatever join its own conditions need. Regression spec:
27
+ `include_preload_never_joins_spec.rb`.
28
+
29
+ **One behaviour change comes with it.** A query that puts a condition or an
30
+ order on an included table, and does not join that table itself, now raises.
31
+ Before, it worked only when the client's `?include=` named that table. Look
32
+ for such a query in three places:
33
+
34
+ - the `records` scope of a resource;
35
+ - a model scope that `sort` calls, such as `order(authors: { name: dir })`;
36
+ - a model scope that `filter` calls, such as `where(authors: { name: v })`.
37
+
38
+ Add `joins` or `left_joins` for the table to fix it.
39
+
10
40
  ## [4.0.0] - 2026-09-09
11
41
 
12
42
  ### Changed
@@ -25,12 +25,16 @@ module JSONAPI
25
25
  return scope unless includes.any?
26
26
  return scope if scope.is_a?(Array)
27
27
 
28
- inc_hash = includes_to_hash(includes)
29
- # `includes` may resolve to a JOIN when the base scope already references an
30
- # associated table (e.g. a `records` override that sorts by one). Rails cannot
31
- # eager-load polymorphic or instance-dependent associations in a JOIN, so those
32
- # must go through `preload` (a separate query), which loads them without raising.
33
- preload_required?(inc_hash, model_class) ? scope.preload(inc_hash) : scope.includes(inc_hash)
28
+ # `preload`, never `includes`. `includes` resolves to one LEFT OUTER JOIN
29
+ # across the whole tree whenever the scope already answers
30
+ # `eager_loading?` with true, which one condition on an unjoined table is
31
+ # enough to cause. Every collection path in the tree then multiplies row
32
+ # for row: a measured page of 25 records loaded 75,712 rows and allocated
33
+ # 800MB, against 18MB for the same document through `preload`.
34
+ # Serialization filters on none of the included tables, so the tree never
35
+ # needs the join. A scope's own `includes` is untouched and still supplies
36
+ # whatever join its own conditions need.
37
+ scope.preload(includes_to_hash(includes))
34
38
  end
35
39
 
36
40
  def filter_includable(hash, klass)
@@ -57,35 +61,6 @@ module JSONAPI
57
61
  reflection.macro == :has_one_attached ? :"#{key}_attachment" : :"#{key}_attachments"
58
62
  end
59
63
 
60
- def preload_required?(hash, klass)
61
- hash.any? do |key, value|
62
- assoc = klass.reflect_on_association(key)
63
- next false unless assoc
64
-
65
- assoc.polymorphic? || instance_dependent_scope?(assoc) ||
66
- (value.present? && preload_required?(value, assoc.klass))
67
- end
68
- end
69
-
70
- # Rails refuses to eager-load (JOIN) an association whose scope takes the owner
71
- # instance (`->(owner) { ... }`), raising "instance dependent scopes are not
72
- # supported". Its arity is non-zero; such associations must be preloaded instead.
73
- def instance_dependent_scope?(assoc)
74
- scope = assoc.scope
75
- !scope.nil? && scope.arity != 0
76
- end
77
-
78
- def hash_contains_polymorphic?(hash, klass)
79
- hash.any? { |key, value| polymorphic_in_hash_entry?(key, value, klass) }
80
- end
81
-
82
- def polymorphic_in_hash_entry?(key, value, klass)
83
- assoc = klass.reflect_on_association(key)
84
- return false unless assoc
85
-
86
- assoc.polymorphic? || (value.present? && hash_contains_polymorphic?(value, assoc.klass))
87
- end
88
-
89
64
  # Applies each included resource's own `records` preloads to the records
90
65
  # of that resource's class. Walks the whole include tree: a resource
91
66
  # reached at depth 3 gets its preloads just as one reached at depth 1.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JSONAPI
4
- VERSION = "4.0.0"
4
+ VERSION = "4.1.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jpie
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0
4
+ version: 4.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emil Kampp