graphiti 2.1.0 → 2.1.2
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/.github/workflows/docs.yml +0 -2
- data/.github/workflows/release.yml +5 -0
- data/Appraisals +5 -0
- data/CHANGELOG.md +15 -0
- data/gemfiles/rails_7_1.gemfile +1 -0
- data/gemfiles/rails_7_2.gemfile +1 -0
- data/gemfiles/rails_8_0.gemfile +1 -0
- data/lib/graphiti/filter_operators.rb +0 -1
- data/lib/graphiti/resource/configuration.rb +4 -2
- data/lib/graphiti/resource/dsl.rb +8 -1
- data/lib/graphiti/scoping/filter.rb +13 -1
- data/lib/graphiti/serializer.rb +1 -0
- data/lib/graphiti/sideload/has_many.rb +8 -3
- data/lib/graphiti/sideload/polymorphic_belongs_to.rb +0 -1
- data/lib/graphiti/stats/dsl.rb +0 -1
- data/lib/graphiti/util/public_id_sources.rb +31 -6
- data/lib/graphiti/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: af05146072fd78f20acdaa5b02efb3853936050991fdbec6b95c877e5d2135b5
|
|
4
|
+
data.tar.gz: 5edcc4716bb8e514aecd912e7ad5bc0c1e3db599b7048109060d3aee655ada9a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9a343df743086251a11b6349f9a1a79b1cdcf370c57505c5fc65640e0a316524a98221c4b4dc34bbe51fca3357fff23a5fa03e9f3c26e37c478dd21e03669aec
|
|
7
|
+
data.tar.gz: 042a48e0dbf92f54d2b2cb2292bec09fbc4914ef7e2df4d2422401e0b93ee6b1078a15ec14d580f090c2aaf7fdaabb9b2b49ce623ea43480497598912ef9eb04
|
data/.github/workflows/docs.yml
CHANGED
|
@@ -36,3 +36,8 @@ jobs:
|
|
|
36
36
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
37
37
|
GEM_HOST_API_KEY: ${{ secrets.GEM_HOST_API_KEY }}
|
|
38
38
|
run: npx semantic-release
|
|
39
|
+
# A release published with GITHUB_TOKEN never fires other workflows' release triggers, so the docs are rebuilt by hand.
|
|
40
|
+
- name: Rebuild docs with the new version label
|
|
41
|
+
env:
|
|
42
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
43
|
+
run: gh workflow run docs.yml --ref "$GITHUB_REF_NAME"
|
data/Appraisals
CHANGED
|
@@ -16,9 +16,12 @@
|
|
|
16
16
|
# Rails 8.0 / 8.1 -> gem "sqlite3", ">= 2.1"
|
|
17
17
|
# "~> 1.4" rather than "~> 1.4.0" is deliberate: it resolves to 1.7.x, which
|
|
18
18
|
# still builds on modern Rubies, where 1.4.x no longer does.
|
|
19
|
+
#
|
|
20
|
+
# Rails 7.1 through 8.0 pass quirks_mode to JSON.generate, which json 3 rejects.
|
|
19
21
|
|
|
20
22
|
appraise "rails-7-1" do
|
|
21
23
|
gem "rails", "~> 7.1.0"
|
|
24
|
+
gem "json", "< 3"
|
|
22
25
|
gem "rspec-rails"
|
|
23
26
|
gem "responders"
|
|
24
27
|
gem "sqlite3", "~> 1.4"
|
|
@@ -26,6 +29,7 @@ end
|
|
|
26
29
|
|
|
27
30
|
appraise "rails-7-2" do
|
|
28
31
|
gem "rails", "~> 7.2.0"
|
|
32
|
+
gem "json", "< 3"
|
|
29
33
|
gem "rspec-rails"
|
|
30
34
|
gem "responders"
|
|
31
35
|
gem "sqlite3", "~> 2.1"
|
|
@@ -33,6 +37,7 @@ end
|
|
|
33
37
|
|
|
34
38
|
appraise "rails-8-0" do
|
|
35
39
|
gem "rails", "~> 8.0.0"
|
|
40
|
+
gem "json", "< 3"
|
|
36
41
|
gem "rspec-rails"
|
|
37
42
|
gem "responders"
|
|
38
43
|
gem "sqlite3", "~> 2.1"
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
graphiti changelog
|
|
2
2
|
|
|
3
|
+
## [2.1.2](https://github.com/graphiti-api/graphiti/compare/v2.1.1...v2.1.2) (2026-10-12)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* a polymorphic resource renders each child that publishes a public id by that id instead of raising ([8d5e0fb](https://github.com/graphiti-api/graphiti/commit/8d5e0fbbdd71d706c3a57b11c866fc36727d6ef8))
|
|
9
|
+
* several resources can declare the same polymorphic_has_many while publishing public ids ([d6e332c](https://github.com/graphiti-api/graphiti/commit/d6e332c73e4ef7ed969e10c3444e802701f46232))
|
|
10
|
+
|
|
11
|
+
## [2.1.1](https://github.com/graphiti-api/graphiti/compare/v2.1.0...v2.1.1) (2026-09-07)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
* a filter you declare by hand always beats the one an attribute generates ([955875b](https://github.com/graphiti-api/graphiti/commit/955875b2a28ef67980e240399deb70620f44b935))
|
|
17
|
+
|
|
3
18
|
# [2.1.0](https://github.com/graphiti-api/graphiti/compare/v2.0.2...v2.1.0) (2026-09-04)
|
|
4
19
|
|
|
5
20
|
|
data/gemfiles/rails_7_1.gemfile
CHANGED
data/gemfiles/rails_7_2.gemfile
CHANGED
data/gemfiles/rails_8_0.gemfile
CHANGED
|
@@ -99,6 +99,7 @@ module Graphiti
|
|
|
99
99
|
def serializer=(val)
|
|
100
100
|
if val
|
|
101
101
|
if super(Class.new(val))
|
|
102
|
+
serializer.resource_class = self if serializer.respond_to?(:resource_class=)
|
|
102
103
|
apply_attributes_to_serializer
|
|
103
104
|
end
|
|
104
105
|
else
|
|
@@ -336,7 +337,7 @@ module Graphiti
|
|
|
336
337
|
attribute = attributes[attribute_name.to_sym]
|
|
337
338
|
return unless attribute && attribute[:readable] && !attribute[:proc]
|
|
338
339
|
|
|
339
|
-
source = public_id_source_for(attribute_name)
|
|
340
|
+
source = public_id_source_for(attribute_name) || (Graphiti.public_ids_declared? && Graphiti.public_id_sources.any(self, attribute_name))
|
|
340
341
|
raise Errors::PublicIdLeak.new(self, attribute_name, source) if source
|
|
341
342
|
end
|
|
342
343
|
|
|
@@ -348,10 +349,11 @@ module Graphiti
|
|
|
348
349
|
filter[:operators][:eq].nil? || Array(filter[:operators_taking_primary_keys]).include?(:eq)
|
|
349
350
|
end
|
|
350
351
|
|
|
351
|
-
def public_id_source_for(filter_name)
|
|
352
|
+
def public_id_source_for(filter_name, polymorphic_type = nil)
|
|
352
353
|
return unless Graphiti.public_ids_declared?
|
|
353
354
|
|
|
354
355
|
name = filter_name.to_sym
|
|
356
|
+
return Graphiti.public_id_sources[self, name, polymorphic_type] if polymorphic_type
|
|
355
357
|
return self if name == :id && config[:public_id_decode]
|
|
356
358
|
|
|
357
359
|
source = Graphiti.public_id_sources[self, name]
|
|
@@ -16,6 +16,7 @@ module Graphiti
|
|
|
16
16
|
|
|
17
17
|
aliases = [name, opts[:aliases]].flatten.compact
|
|
18
18
|
operators = FilterOperators.build(self, att[:type], opts, &blk)
|
|
19
|
+
operators = operators.merge(custom_operators(name)) if opts[:via_attribute_dsl]
|
|
19
20
|
|
|
20
21
|
case Graphiti::Types[att[:type]][:canonical_name]
|
|
21
22
|
when :boolean
|
|
@@ -173,7 +174,7 @@ module Graphiti
|
|
|
173
174
|
|
|
174
175
|
# The hidden filter only ever sees keys Graphiti read off models, so its type is never used to cast anything.
|
|
175
176
|
def public_id_proc
|
|
176
|
-
proc { @resource.class.public_id_for(@object) }
|
|
177
|
+
proc { (self.class.resource_class || @resource.class).public_id_for(@object) }
|
|
177
178
|
end
|
|
178
179
|
|
|
179
180
|
def apply_public_id
|
|
@@ -253,6 +254,12 @@ module Graphiti
|
|
|
253
254
|
end
|
|
254
255
|
private :relationship_option
|
|
255
256
|
|
|
257
|
+
# Declaring an attribute regenerates its filter, which must not silently outrank a filter declared by hand.
|
|
258
|
+
def custom_operators(name)
|
|
259
|
+
(config[:filters][name]&.[](:operators) || {}).compact
|
|
260
|
+
end
|
|
261
|
+
private :custom_operators
|
|
262
|
+
|
|
256
263
|
def blanks_for(name, opts)
|
|
257
264
|
if opts.key?(:deny_empty)
|
|
258
265
|
DEPRECATOR.warn("The deny_empty: filter option is deprecated. Use blanks: :rejected.")
|
|
@@ -65,7 +65,7 @@ module Graphiti
|
|
|
65
65
|
elsif filter.values[0][:internal]
|
|
66
66
|
raise Errors::InvalidAttributeAccess.new(resource, param_name, :filterable, request: true)
|
|
67
67
|
else
|
|
68
|
-
source = resource.class.public_id_source_for(filter.keys[0])
|
|
68
|
+
source = resource.class.public_id_source_for(filter.keys[0], requested_polymorphic_type(filter.keys[0]))
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
normalize_param(filter, param_value).each do |operator, value|
|
|
@@ -84,6 +84,18 @@ module Graphiti
|
|
|
84
84
|
end
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
+
def requested_polymorphic_type(filter_name)
|
|
88
|
+
return unless Graphiti.public_ids_declared?
|
|
89
|
+
|
|
90
|
+
type_filter = Graphiti.public_id_sources.type_filter(resource.class, filter_name)
|
|
91
|
+
return unless type_filter
|
|
92
|
+
|
|
93
|
+
requested = filter_param[type_filter]
|
|
94
|
+
requested = requested.values.first if requested.is_a?(Hash)
|
|
95
|
+
types = requested.is_a?(String) ? requested.split(",") : Array(requested)
|
|
96
|
+
types.first if types.one?
|
|
97
|
+
end
|
|
98
|
+
|
|
87
99
|
# A block asking for primary_keys: was sent public ids, which cast like the source's id rather than the key.
|
|
88
100
|
def validated_and_cast(filter, param_name, value, public_id_source)
|
|
89
101
|
type = Types[filter.values[0][:type]]
|
data/lib/graphiti/serializer.rb
CHANGED
|
@@ -18,6 +18,7 @@ module Graphiti
|
|
|
18
18
|
# relationship is told apart from one already applied, and from one the
|
|
19
19
|
# application wrote by hand.
|
|
20
20
|
class_attribute :relationship_sideloads
|
|
21
|
+
class_attribute :resource_class
|
|
21
22
|
self.attributes_applied_via_resource = []
|
|
22
23
|
self.extra_attributes_applied_via_resource = []
|
|
23
24
|
# See #requested_relationships
|
|
@@ -31,10 +31,14 @@ class Graphiti::Sideload::HasMany < Graphiti::Sideload
|
|
|
31
31
|
def link_hides_primary_key?
|
|
32
32
|
return true unless links_by_public_id?
|
|
33
33
|
|
|
34
|
-
resource_class.public_id_source_for(inverse_filter) == parent_resource_class &&
|
|
34
|
+
resource_class.public_id_source_for(inverse_filter, polymorphic_type) == parent_resource_class &&
|
|
35
35
|
resource_class.filter_accepts_public_ids?(inverse_filter)
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
+
def polymorphic_type
|
|
39
|
+
parent_resource_class.model.name if polymorphic_has_many?
|
|
40
|
+
end
|
|
41
|
+
|
|
38
42
|
def links_by_public_id?
|
|
39
43
|
@links_by_public_id ||= Graphiti.public_ids_declared? && primary_key == :id && !!parent_resource_class&.publishes_public_id?
|
|
40
44
|
end
|
|
@@ -43,7 +47,8 @@ class Graphiti::Sideload::HasMany < Graphiti::Sideload
|
|
|
43
47
|
return unless links_by_public_id? && resource_class_loaded?
|
|
44
48
|
return if parent_resource_class.abstract_class? || !parent_resource_class.model_loaded?
|
|
45
49
|
|
|
46
|
-
|
|
50
|
+
type_filter = :"#{polymorphic_as}_type" if polymorphic_has_many?
|
|
51
|
+
Graphiti.public_id_sources.register(resource_class, inverse_filter, parent_resource_class, polymorphic_type: polymorphic_type, type_filter: type_filter)
|
|
47
52
|
resource_class.guard_public_id_leak!(inverse_filter)
|
|
48
53
|
end
|
|
49
54
|
|
|
@@ -54,7 +59,7 @@ class Graphiti::Sideload::HasMany < Graphiti::Sideload
|
|
|
54
59
|
end
|
|
55
60
|
|
|
56
61
|
def internal_parent_filter(parents, filter_name)
|
|
57
|
-
return parent_filter(parents) unless resource_class.public_id_source_for(filter_name)
|
|
62
|
+
return parent_filter(parents) unless resource_class.public_id_source_for(filter_name, polymorphic_type)
|
|
58
63
|
|
|
59
64
|
Graphiti::Util::InternalParam.new(ids_for_parents(parents))
|
|
60
65
|
end
|
data/lib/graphiti/stats/dsl.rb
CHANGED
|
@@ -3,24 +3,49 @@ module Graphiti
|
|
|
3
3
|
class PublicIdSources
|
|
4
4
|
def initialize
|
|
5
5
|
@sources = {}
|
|
6
|
+
@type_filters = {}
|
|
6
7
|
end
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
# Every parent of a polymorphic has_many shares one inverse filter, so each registers under the type its type filter names.
|
|
10
|
+
def register(child_resource_class, filter_name, parent_resource_class, polymorphic_type: nil, type_filter: nil)
|
|
11
|
+
key = [child_resource_class.name, filter_name.to_sym, polymorphic_type]
|
|
10
12
|
registered = @sources[key]
|
|
11
13
|
if registered && registered.name != parent_resource_class.name
|
|
12
14
|
raise Errors::ConflictingPublicIdSource.new(child_resource_class, filter_name, registered, parent_resource_class)
|
|
13
15
|
end
|
|
14
16
|
@sources[key] = parent_resource_class
|
|
17
|
+
@type_filters[[child_resource_class.name, filter_name.to_sym]] = type_filter.to_sym if type_filter
|
|
15
18
|
end
|
|
16
19
|
|
|
17
|
-
def [](child_resource_class, filter_name)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
source = @sources[[klass.name, filter_name.to_sym]]
|
|
20
|
+
def [](child_resource_class, filter_name, polymorphic_type = nil)
|
|
21
|
+
each_ancestor_name(child_resource_class) do |name|
|
|
22
|
+
source = @sources[[name, filter_name.to_sym, polymorphic_type]]
|
|
21
23
|
return source if source
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def type_filter(child_resource_class, filter_name)
|
|
28
|
+
each_ancestor_name(child_resource_class) do |name|
|
|
29
|
+
type_filter = @type_filters[[name, filter_name.to_sym]]
|
|
30
|
+
return type_filter if type_filter
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def any(child_resource_class, filter_name)
|
|
35
|
+
each_ancestor_name(child_resource_class) do |name|
|
|
36
|
+
entry = @sources.find { |(child, filter, _type), _source| child == name && filter == filter_name.to_sym }
|
|
37
|
+
return entry.last if entry
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def each_ancestor_name(klass)
|
|
44
|
+
while klass
|
|
45
|
+
yield klass.name
|
|
22
46
|
klass = klass.superclass
|
|
23
47
|
end
|
|
48
|
+
nil
|
|
24
49
|
end
|
|
25
50
|
end
|
|
26
51
|
end
|
data/lib/graphiti/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: graphiti
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.1.
|
|
4
|
+
version: 2.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Lee Richmond
|
|
@@ -439,7 +439,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
439
439
|
- !ruby/object:Gem::Version
|
|
440
440
|
version: '0'
|
|
441
441
|
requirements: []
|
|
442
|
-
rubygems_version: 4.0.
|
|
442
|
+
rubygems_version: 4.0.20
|
|
443
443
|
specification_version: 4
|
|
444
444
|
summary: Easily build jsonapi.org-compatible APIs
|
|
445
445
|
test_files: []
|