jpie 4.1.5 → 4.1.6.pre.202611130310
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 +4 -4
- data/CHANGELOG.md +30 -0
- data/lib/json_api/controllers/concerns/resource_actions/include_preloading.rb +1 -2
- data/lib/json_api/controllers/concerns/resource_actions/serialization.rb +3 -0
- data/lib/json_api/resources/concerns/included_preloads.rb +22 -0
- data/lib/json_api/resources/resource.rb +2 -0
- data/lib/json_api/serialization/concerns/attributes_serialization.rb +7 -6
- data/lib/json_api/serialization/concerns/includes_serialization.rb +1 -0
- data/lib/json_api/serialization/include_filter_cache.rb +12 -1
- data/lib/json_api/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ecef6f3e7533e6112f4a1441139b94578bf3303a42454816453a179fdf4b1f37
|
|
4
|
+
data.tar.gz: 2537aadcda30e625af8a081bdcbafdb0aae20efbec328a7fb0243564ef1866fd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8cd573877f8cfa4453a31bbd6ec5737d0e3d467198b1bcea340cc50ef54427194db0ec51c439fdf703986415d510cd786aca1f418fa460351b3b306800d51bd9
|
|
7
|
+
data.tar.gz: 726fba1853629ba61b3f6223a87322ef834b8f59932adb2c764ef8460d38f5c7824dc505f18f1a99b80a5a941725b78ee7810cabcac79054e098085d3dfcb37c
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,36 @@ at release time.
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **Keep a primary record out of `included`.** A primary record that an include
|
|
13
|
+
reached again was serialized a second time, with its meta and computed
|
|
14
|
+
attributes, and sent in `included`. JSON:API allows one resource object for
|
|
15
|
+
each type and id in a document. Every primary now counts as seen before the
|
|
16
|
+
include walk starts. The walk still goes past it to the records behind it.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **`Resource.included_preloads` to give an included record lighter preloads.**
|
|
21
|
+
The include walk ran the full `records` preloads of a resource on every
|
|
22
|
+
record that another resource included. An included record serializes only
|
|
23
|
+
the relationships its include path names, so a deep preload for the
|
|
24
|
+
resource's own response (a conversation's messages, for example) loaded rows
|
|
25
|
+
the response never read. `included_preloads` returns the preloads of
|
|
26
|
+
`records` by default, so nothing changes until a resource overrides it.
|
|
27
|
+
Regression spec: `included_preloads_spec.rb`.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- **Compare the query values, not two SQL strings, to see if the hook narrowed
|
|
32
|
+
an include scope.** The include filter cache built the SQL of the resource
|
|
33
|
+
scope and of the authorized scope for each included class, in each request.
|
|
34
|
+
It now compares the values of the two relations and builds no SQL. A narrowed
|
|
35
|
+
scope still reads as narrowed. A scope the hook leaves unchanged still reads
|
|
36
|
+
as not narrowed. The documents do not change. On the Kiln test app, the
|
|
37
|
+
compare allocates 740 objects for each class before and 18 after.
|
|
38
|
+
- A sparse fieldset now limits which attributes the serializer reads. It read every attribute of every record and cut the fieldset out afterwards, so a resource method outside the fieldset ran for every row.
|
|
39
|
+
|
|
10
40
|
## [4.1.5] - 2026-11-11
|
|
11
41
|
|
|
12
42
|
### Fixed
|
|
@@ -103,8 +103,7 @@ module JSONAPI
|
|
|
103
103
|
end
|
|
104
104
|
|
|
105
105
|
def resource_preload_values(klass)
|
|
106
|
-
|
|
107
|
-
scope.preload_values + scope.includes_values
|
|
106
|
+
ResourceLoader.find_for_model(klass, namespace: jsonapi_namespace).included_preloads
|
|
108
107
|
end
|
|
109
108
|
end
|
|
110
109
|
end
|
|
@@ -41,6 +41,9 @@ module JSONAPI
|
|
|
41
41
|
shared_context = JSONAPI::Serialization::IncludeContext.new(
|
|
42
42
|
fields: fields, included_records: [], processed: Set.new, all_includes: nil,
|
|
43
43
|
)
|
|
44
|
+
# A primary that an include reaches again belongs in `data` alone. Mark every primary
|
|
45
|
+
# before the first walk, so no walk serializes it a second time.
|
|
46
|
+
resources.each { |r| shared_context.processed.add([r.class, r.id]) }
|
|
44
47
|
cache = build_include_filter_cache(resources, includes)
|
|
45
48
|
|
|
46
49
|
data = resources.map do |r|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSONAPI
|
|
4
|
+
module Resources
|
|
5
|
+
module IncludedPreloads
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
class_methods do
|
|
9
|
+
# The preloads the include walk runs on this resource's records when
|
|
10
|
+
# another resource reaches them through `?include=`. By default they
|
|
11
|
+
# are the preloads of `records`. Override it when `records` preloads
|
|
12
|
+
# more than an included record needs. An included record serializes
|
|
13
|
+
# only the relationships its include path names, so a deep preload
|
|
14
|
+
# that feeds the resource's own response is wasted there.
|
|
15
|
+
def included_preloads
|
|
16
|
+
scope = records
|
|
17
|
+
scope.preload_values + scope.includes_values
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -6,6 +6,7 @@ require_relative "concerns/sortable_fields_dsl"
|
|
|
6
6
|
require_relative "concerns/relationships_dsl"
|
|
7
7
|
require_relative "concerns/meta_dsl"
|
|
8
8
|
require_relative "concerns/model_class_helpers"
|
|
9
|
+
require_relative "concerns/included_preloads"
|
|
9
10
|
|
|
10
11
|
module JSONAPI
|
|
11
12
|
class Resource
|
|
@@ -15,6 +16,7 @@ module JSONAPI
|
|
|
15
16
|
include Resources::RelationshipsDsl
|
|
16
17
|
include Resources::MetaDsl
|
|
17
18
|
include Resources::ModelClassHelpers
|
|
19
|
+
include Resources::IncludedPreloads
|
|
18
20
|
|
|
19
21
|
class << self
|
|
20
22
|
attr_accessor :type_format
|
|
@@ -7,10 +7,7 @@ module JSONAPI
|
|
|
7
7
|
type_fields = extract_type_fields(fields)
|
|
8
8
|
return {} if type_fields.empty? && fields.any?
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
return attributes if type_fields.empty?
|
|
12
|
-
|
|
13
|
-
attributes.slice(*type_fields.map(&:to_sym))
|
|
10
|
+
build_attributes_hash(type_fields.map(&:to_sym))
|
|
14
11
|
end
|
|
15
12
|
|
|
16
13
|
private
|
|
@@ -19,11 +16,15 @@ module JSONAPI
|
|
|
19
16
|
fields[record_type.to_sym] || []
|
|
20
17
|
end
|
|
21
18
|
|
|
22
|
-
|
|
19
|
+
# A sparse fieldset names the attributes to send, so only those are read. Building
|
|
20
|
+
# every attribute and slicing afterwards paid for each unrequested resource method on
|
|
21
|
+
# every row. The keys keep the order of the fieldset, as the slice did.
|
|
22
|
+
def build_attributes_hash(requested)
|
|
23
23
|
permitted_attrs = definition.permitted_attributes
|
|
24
|
+
wanted = requested.empty? ? permitted_attrs : requested.select { |attr_sym| permitted_attrs.include?(attr_sym) }
|
|
24
25
|
attributes = {}
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
wanted.each do |attr_sym|
|
|
27
28
|
attributes[attr_sym] = get_attribute_value(definition_instance, attr_sym)
|
|
28
29
|
end
|
|
29
30
|
|
|
@@ -28,6 +28,7 @@ module JSONAPI
|
|
|
28
28
|
|
|
29
29
|
ctx = context || build_include_context(fields, all_includes)
|
|
30
30
|
ctx.all_includes ||= all_includes
|
|
31
|
+
ctx.processed.add(build_record_key(record))
|
|
31
32
|
all_includes.each { |path| serialize_include_path(record, path_walk_for(path, ctx), 0, ctx) }
|
|
32
33
|
ctx.included_records
|
|
33
34
|
end
|
|
@@ -75,12 +75,23 @@ module JSONAPI
|
|
|
75
75
|
Entry.new(
|
|
76
76
|
base_scope: base_scope,
|
|
77
77
|
authorized_scope: authorized_scope,
|
|
78
|
-
narrowed: authorized_scope
|
|
78
|
+
narrowed: narrowed?(authorized_scope, base_scope),
|
|
79
79
|
where_clause_empty: base_scope.where_clause.empty?,
|
|
80
80
|
where_hash: where_hash_for(base_scope, klass),
|
|
81
81
|
)
|
|
82
82
|
end
|
|
83
83
|
|
|
84
|
+
# A scope the hook leaves as it found it keeps the same query values, so the
|
|
85
|
+
# compare reads them and builds no SQL. Building both statements for every
|
|
86
|
+
# included class cost thousands of objects per request. Two scopes whose
|
|
87
|
+
# values differ but whose SQL matches read as narrowed. That only adds a
|
|
88
|
+
# vetting query: the verdicts come from the same SQL.
|
|
89
|
+
def narrowed?(authorized_scope, base_scope)
|
|
90
|
+
return false if authorized_scope.equal?(base_scope)
|
|
91
|
+
|
|
92
|
+
authorized_scope.values != base_scope.values
|
|
93
|
+
end
|
|
94
|
+
|
|
84
95
|
# Mirrors the serializer's include authorization: no context (serializer
|
|
85
96
|
# used outside a request) or no configured hook leaves the scope untouched.
|
|
86
97
|
def apply_authorization(scope, klass)
|
data/lib/json_api/version.rb
CHANGED
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.1.
|
|
4
|
+
version: 4.1.6.pre.202611130310
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Emil Kampp
|
|
@@ -135,6 +135,7 @@ files:
|
|
|
135
135
|
- lib/json_api/resources/active_storage_blob_resource.rb
|
|
136
136
|
- lib/json_api/resources/concerns/attributes_dsl.rb
|
|
137
137
|
- lib/json_api/resources/concerns/filters_dsl.rb
|
|
138
|
+
- lib/json_api/resources/concerns/included_preloads.rb
|
|
138
139
|
- lib/json_api/resources/concerns/meta_dsl.rb
|
|
139
140
|
- lib/json_api/resources/concerns/model_class_helpers.rb
|
|
140
141
|
- lib/json_api/resources/concerns/relationships_dsl.rb
|