graphiti 2.1.1 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 873ef782c20fd4c861ccc48b7d1be6ff79943a22a89bc823d1c929cd64a05e13
4
- data.tar.gz: 32aab62c054dfc0cf73647fb018adf40efec5862bebddc727e16797857b2987b
3
+ metadata.gz: af05146072fd78f20acdaa5b02efb3853936050991fdbec6b95c877e5d2135b5
4
+ data.tar.gz: 5edcc4716bb8e514aecd912e7ad5bc0c1e3db599b7048109060d3aee655ada9a
5
5
  SHA512:
6
- metadata.gz: fac4855f042d4214bdc196609776efb739de5676d8c464dcffaeeda6f233639609bfb3c450608b394c81aee6470d04013a4989cead074b61a71cc645f3a9eb3e
7
- data.tar.gz: 8119467fe8a1da3a711f7b597a19f2540f7170d2fa2fe6116d19086d029f8713fdeb976e090aea8a5662d54e7af1df576dc9f29b8c138c3367ba337fc2a7e02c
6
+ metadata.gz: 9a343df743086251a11b6349f9a1a79b1cdcf370c57505c5fc65640e0a316524a98221c4b4dc34bbe51fca3357fff23a5fa03e9f3c26e37c478dd21e03669aec
7
+ data.tar.gz: 042a48e0dbf92f54d2b2cb2292bec09fbc4914ef7e2df4d2422401e0b93ee6b1078a15ec14d580f090c2aaf7fdaabb9b2b49ce623ea43480497598912ef9eb04
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,13 @@
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
+
3
11
  ## [2.1.1](https://github.com/graphiti-api/graphiti/compare/v2.1.0...v2.1.1) (2026-09-07)
4
12
 
5
13
 
@@ -3,6 +3,7 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "rails", "~> 7.1.0"
6
+ gem "json", "< 3"
6
7
  gem "rspec-rails"
7
8
  gem "responders"
8
9
  gem "sqlite3", "~> 1.4"
@@ -3,6 +3,7 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "rails", "~> 7.2.0"
6
+ gem "json", "< 3"
6
7
  gem "rspec-rails"
7
8
  gem "responders"
8
9
  gem "sqlite3", "~> 2.1"
@@ -3,6 +3,7 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "rails", "~> 8.0.0"
6
+ gem "json", "< 3"
6
7
  gem "rspec-rails"
7
8
  gem "responders"
8
9
  gem "sqlite3", "~> 2.1"
@@ -20,7 +20,6 @@ module Graphiti
20
20
  def method_missing(name, *args, &blk)
21
21
  @procs[name] = blk
22
22
  end
23
- # rubocop: enable Style/MethodMissingSuper
24
23
 
25
24
  def respond_to_missing?(*args)
26
25
  true
@@ -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]
@@ -174,7 +174,7 @@ module Graphiti
174
174
 
175
175
  # The hidden filter only ever sees keys Graphiti read off models, so its type is never used to cast anything.
176
176
  def public_id_proc
177
- proc { @resource.class.public_id_for(@object) }
177
+ proc { (self.class.resource_class || @resource.class).public_id_for(@object) }
178
178
  end
179
179
 
180
180
  def apply_public_id
@@ -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]]
@@ -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
- Graphiti.public_id_sources.register(resource_class, inverse_filter, parent_resource_class)
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
@@ -10,7 +10,6 @@ class Graphiti::Sideload::PolymorphicBelongsTo < Graphiti::Sideload::BelongsTo
10
10
  def method_missing(name, *args, &blk)
11
11
  @calls << [name, args, blk]
12
12
  end
13
- # rubocop: enable Style/MethodMissingSuper
14
13
 
15
14
  def respond_to_missing?(*args)
16
15
  true
@@ -49,7 +49,6 @@ module Graphiti
49
49
  def method_missing(meth, *args, &blk)
50
50
  @calculations[meth] = blk
51
51
  end
52
- # rubocop: enable Style/MethodMissingSuper
53
52
 
54
53
  def respond_to_missing?(*args)
55
54
  true
@@ -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
- def register(child_resource_class, filter_name, parent_resource_class)
9
- key = [child_resource_class.name, filter_name.to_sym]
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
- klass = child_resource_class
19
- while klass
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
@@ -1,3 +1,3 @@
1
1
  module Graphiti
2
- VERSION = "2.1.1"
2
+ VERSION = "2.1.2"
3
3
  end
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.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.16
442
+ rubygems_version: 4.0.20
443
443
  specification_version: 4
444
444
  summary: Easily build jsonapi.org-compatible APIs
445
445
  test_files: []