jpie 4.1.2 → 4.1.3
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 +21 -0
- data/lib/json_api/support/concerns/nested_filters.rb +19 -14
- data/lib/json_api/support/concerns/polymorphic_filters.rb +19 -11
- data/lib/json_api/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e210d214edcde88211f1fad82af9fa2dfcc2bb67053e8592d7a7d271995d99b3
|
|
4
|
+
data.tar.gz: e8e81eacff5269a4f66cadc93ee496b9b7746f7016f9a68a48cdffb21fc3d64e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1725977eb3a852b9b131453bbca28d25ab5495faf06862b439bf65572455be2d1a97cbf8de18ad65e855d3e51c349e6ac06af3644201436fc7d00b52919ae6bd
|
|
7
|
+
data.tar.gz: f17962764401488d7986f9da451d26bc3a68a037135713169ad76b2b7be9ec86533b66e17c2d0c3e0fa36d290d67575b03818675fbb3e07f22ac89c8d2fc3916
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,27 @@ at release time.
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [4.1.3] - 2026-10-10
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **Filter a polymorphic hop on the model that owns it.** A path such as
|
|
15
|
+
`filter[user.profile.id_eq]` on `/posts` passed validation and answered 500.
|
|
16
|
+
`PolymorphicFilters` read the foreign key and type columns from the root
|
|
17
|
+
table of the query, and `posts` holds no `profile_id`. A root table that
|
|
18
|
+
holds a column of the same name answered 200 with rows filtered on the wrong
|
|
19
|
+
table. The filter now joins the hops before the polymorphic one and reads
|
|
20
|
+
the columns of the owner's table. A polymorphic hop on the root model
|
|
21
|
+
behaves as before. Regression spec:
|
|
22
|
+
`nested_filter_polymorphic_hop_below_root_spec.rb`.
|
|
23
|
+
- **Ignore an empty nested filter value.** `filter[comments.author.email_eq]=`
|
|
24
|
+
added the inner join before it checked the value, so the answer held only
|
|
25
|
+
the posts that have a comment. `filter[profile.type]=` matched the empty
|
|
26
|
+
string and answered no rows. An empty nested value now adds no join and no
|
|
27
|
+
condition, as an empty top-level filter does. A nested filter that adds no
|
|
28
|
+
condition adds no join either. Regression spec:
|
|
29
|
+
`nested_filter_empty_value_spec.rb`.
|
|
30
|
+
|
|
10
31
|
## [4.1.2] - 2026-10-10
|
|
11
32
|
|
|
12
33
|
### Fixed
|
|
@@ -19,23 +19,25 @@ module JSONAPI
|
|
|
19
19
|
def apply_filter_for_path(scope, filter_name, filter_value)
|
|
20
20
|
*relationship_chain, leaf_filter = filter_name.split(".")
|
|
21
21
|
return scope if relationship_chain.empty?
|
|
22
|
+
# An empty value filters nothing, so it adds no join. An inner join
|
|
23
|
+
# drops each row that has no related record.
|
|
24
|
+
return scope if empty_filter_value?(filter_value)
|
|
22
25
|
|
|
23
26
|
# The walk filter validation takes. A path it refuses filters nothing.
|
|
24
27
|
target = filter_path.resolve(filter_name)
|
|
25
28
|
return scope unless target
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
# The foreign key and type columns of a polymorphic hop sit on the model
|
|
31
|
+
# that owns the association. Only the hops before it need a join.
|
|
32
|
+
joined = join_chain(scope, target.association ? relationship_chain[0..-2] : relationship_chain)
|
|
33
|
+
filtered = apply_filter_on_target(joined, target, leaf_filter, filter_value)
|
|
34
|
+
# A filter that adds no condition adds no join either.
|
|
35
|
+
filtered.equal?(joined) ? scope : filtered
|
|
33
36
|
end
|
|
34
37
|
|
|
35
|
-
def
|
|
36
|
-
join_hash = build_join_hash_for_chain(
|
|
37
|
-
|
|
38
|
-
apply_filter_on_model(scope, target_model, leaf_filter, filter_value)
|
|
38
|
+
def join_chain(scope, chain)
|
|
39
|
+
join_hash = build_join_hash_for_chain(chain)
|
|
40
|
+
join_hash.present? ? scope.joins(join_hash) : scope
|
|
39
41
|
end
|
|
40
42
|
|
|
41
43
|
def build_join_hash_for_chain(chain)
|
|
@@ -50,11 +52,14 @@ module JSONAPI
|
|
|
50
52
|
end
|
|
51
53
|
end
|
|
52
54
|
|
|
53
|
-
def
|
|
54
|
-
|
|
55
|
+
def apply_filter_on_target(scope, target, filter_name, filter_value)
|
|
56
|
+
if target.association
|
|
57
|
+
attributes = { filter_name => filter_value }
|
|
58
|
+
return apply_polymorphic_nested_filters(scope, target.association, target.model, attributes)
|
|
59
|
+
end
|
|
55
60
|
|
|
56
|
-
apply_column_filter(scope,
|
|
57
|
-
apply_scope_method_filter(scope,
|
|
61
|
+
apply_column_filter(scope, target.model, filter_name, filter_value) ||
|
|
62
|
+
apply_scope_method_filter(scope, target.model, filter_name, filter_value) ||
|
|
58
63
|
scope
|
|
59
64
|
end
|
|
60
65
|
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
module JSONAPI
|
|
4
4
|
module Support
|
|
5
5
|
module PolymorphicFilters
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
# `owner` is the model that holds the association. Its table carries the
|
|
7
|
+
# foreign key and type columns, and the scope must already join it.
|
|
8
|
+
def apply_polymorphic_nested_filters(scope, association, owner, attributes)
|
|
9
|
+
context = build_polymorphic_filter_context(association, owner, attributes)
|
|
8
10
|
|
|
9
11
|
attributes.each do |attr_name, attr_value|
|
|
10
12
|
scope = apply_polymorphic_attribute_filter(scope, attr_name, attr_value, context)
|
|
@@ -15,18 +17,18 @@ module JSONAPI
|
|
|
15
17
|
|
|
16
18
|
private
|
|
17
19
|
|
|
18
|
-
def build_polymorphic_filter_context(association, attributes)
|
|
20
|
+
def build_polymorphic_filter_context(association, owner, attributes)
|
|
19
21
|
{
|
|
22
|
+
owner:,
|
|
20
23
|
foreign_key: association.foreign_key,
|
|
21
24
|
foreign_type: association.foreign_type,
|
|
22
|
-
fk_column:
|
|
25
|
+
fk_column: owner.column_for_attribute(association.foreign_key),
|
|
23
26
|
type_value: attributes["type"] || attributes["type_eq"],
|
|
24
27
|
class_name: association.options[:class_name],
|
|
25
28
|
}
|
|
26
29
|
end
|
|
27
30
|
|
|
28
31
|
def apply_polymorphic_attribute_filter(scope, attr_name, attr_value, context)
|
|
29
|
-
return scope if empty_filter_value?(attr_value)
|
|
30
32
|
return scope if attr_name == "type"
|
|
31
33
|
|
|
32
34
|
scope = apply_polymorphic_id_filter(scope, attr_name, attr_value, context)
|
|
@@ -38,7 +40,7 @@ module JSONAPI
|
|
|
38
40
|
return apply_polymorphic_fk_filter(scope, attr_value, column_filter, context) if polymorphic_fk_filter?(
|
|
39
41
|
column_filter, context,
|
|
40
42
|
)
|
|
41
|
-
return scope
|
|
43
|
+
return where_owner_column(scope, context, :foreign_key, attr_value) if attr_name == "id"
|
|
42
44
|
|
|
43
45
|
scope
|
|
44
46
|
end
|
|
@@ -48,17 +50,17 @@ module JSONAPI
|
|
|
48
50
|
end
|
|
49
51
|
|
|
50
52
|
def apply_polymorphic_fk_filter(scope, attr_value, column_filter, context)
|
|
51
|
-
value = normalize_filter_value_for_model(
|
|
53
|
+
value = normalize_filter_value_for_model(context[:owner], context[:fk_column], attr_value)
|
|
52
54
|
return scope unless value
|
|
53
55
|
|
|
54
|
-
condition = build_condition(
|
|
56
|
+
condition = build_condition(context[:owner], context[:fk_column], value, column_filter[:operator])
|
|
55
57
|
condition ? apply_condition(scope, condition) : scope
|
|
56
58
|
end
|
|
57
59
|
|
|
58
60
|
def apply_polymorphic_type_filter(scope, context)
|
|
59
61
|
return scope unless context[:foreign_type]
|
|
60
|
-
return scope
|
|
61
|
-
return scope
|
|
62
|
+
return where_owner_column(scope, context, :foreign_type, context[:class_name]) if context[:class_name]
|
|
63
|
+
return where_owner_column(scope, context, :foreign_type, context[:type_value]) if context[:type_value]
|
|
62
64
|
|
|
63
65
|
scope
|
|
64
66
|
end
|
|
@@ -68,7 +70,13 @@ module JSONAPI
|
|
|
68
70
|
return scope if context[:class_name]
|
|
69
71
|
return scope unless context[:type_value]
|
|
70
72
|
|
|
71
|
-
scope
|
|
73
|
+
where_owner_column(scope, context, :foreign_type, context[:type_value])
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Names the owner's table, so the condition reads the joined table when
|
|
77
|
+
# the owner sits below the root of the query.
|
|
78
|
+
def where_owner_column(scope, context, column_key, value)
|
|
79
|
+
scope.where(context[:owner].table_name => { context[column_key] => value })
|
|
72
80
|
end
|
|
73
81
|
end
|
|
74
82
|
end
|
data/lib/json_api/version.rb
CHANGED