graphiti 1.12.2 → 1.13.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: de4409a60f63682b5bb513238e1101ca1997943a18b136c3d9c507f1eeda9651
4
- data.tar.gz: 62b1019afe5fbb095b1323f17c06f7c97f7c15aa0b9dfff3afded57b428ff326
3
+ metadata.gz: 5b0c2234b78e4616fea8ab95f8b6f044d18aa4feafb103cb4eb7d0fed35f8dd6
4
+ data.tar.gz: 2b276253a3b5b802dbf143ccd6fdc7d17287ee542c60f352ab939a33f16da233
5
5
  SHA512:
6
- metadata.gz: b4da6af791178d1a3229dd0c9515792cecacd2bc60ee96274dfc8723af232531a6190e04697f5a1f6e2f9f2b6562a950cfe916657198969e33b0a5251b0854ab
7
- data.tar.gz: 1f6561906d4b70bc148563be8c9d20b2dc028006a238993449d59a51fc125efd7c5110d9564a983ed09d749fcd53f46740667aab737e1e7e13d47977569b4246
6
+ metadata.gz: 01ac700f8481142c1842488db436df2bedc508acabf3bde7fcd4d9e0e1a9882ab0df8c23ac5473b68a8ab1717b6041a34fde6b18125d269ccad88f23d8a3f3aa
7
+ data.tar.gz: be85a9c17b7b1df85c5ff755b4aebfeb6bd814e4a88a87b926f05056d776f5b77ec643ab1c8800e155cf541149c30d9c343471ef9427b0dcc69ae6c2e79b3295
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  graphiti changelog
2
2
 
3
+ # [1.13.0](https://github.com/graphiti-api/graphiti/compare/v1.12.2...v1.13.0) (2026-07-30)
4
+
5
+
6
+ ### Features
7
+
8
+ * add Resource.wrap as an easy way to use graphiti serialization on models fetched via other means (and not via graphiti's finders) ([#513](https://github.com/graphiti-api/graphiti/issues/513)) ([fcd19e2](https://github.com/graphiti-api/graphiti/commit/fcd19e2d023091d85947dbdd89259b587e0f582b))
9
+ * deprecate mutating attributes in around_persistence hooks ([#514](https://github.com/graphiti-api/graphiti/issues/514)) [skip ci] ([8410ab0](https://github.com/graphiti-api/graphiti/commit/8410ab0a916077601cf85d8260a05ef9098149eb))
10
+
3
11
  ## [1.12.2](https://github.com/graphiti-api/graphiti/compare/v1.12.1...v1.12.2) (2026-07-29)
4
12
 
5
13
 
@@ -467,6 +467,19 @@ module Graphiti
467
467
  end
468
468
  end
469
469
 
470
+ class InvalidWrapModel < Base
471
+ def initialize(resource_class, model)
472
+ @resource_class = resource_class
473
+ @model = model
474
+ end
475
+
476
+ def message
477
+ <<~MSG
478
+ #{@resource_class.name}.wrap was given a #{@model.class}, but this resource is for #{@resource_class.model} (subclasses are fine).
479
+ MSG
480
+ end
481
+ end
482
+
470
483
  class ResourceEndpointConflict < Base
471
484
  def initialize(path, action, resource_a, resource_b)
472
485
  @path = path
@@ -53,8 +53,28 @@ module Graphiti
53
53
  runner.proxy(base_scope, single: true, raise_on_missing: true)
54
54
  end
55
55
 
56
+ # Wrap models fetched outside graphiti so they render like any other proxy
57
+ def wrap(models, base_scope = nil)
58
+ validate_wrap_models!(models)
59
+ runner = Runner.new(self, {}, nil, :find)
60
+ runner.proxy(base_scope, bypass_required_filters: true).tap do |proxy|
61
+ proxy.data = models
62
+ end
63
+ end
64
+
56
65
  private
57
66
 
67
+ # Skip polymorphic parents - resource_for_model raises a better error for unknown children
68
+ def validate_wrap_models!(models)
69
+ return if abstract_class? || (polymorphic? && !polymorphic_child?)
70
+
71
+ [models].flatten.compact.each do |model|
72
+ unless model.is_a?(self.model)
73
+ raise Errors::InvalidWrapModel.new(self, model)
74
+ end
75
+ end
76
+ end
77
+
58
78
  def caching_options
59
79
  {cache: @cache_resource, cache_expires_in: @cache_expires_in, cache_tag: @cache_tag}
60
80
  end
@@ -71,8 +71,11 @@ module Graphiti
71
71
 
72
72
  def create(create_params, meta = nil)
73
73
  model_instance = nil
74
+ snapshot = attributes_snapshot(:create, create_params)
74
75
 
75
76
  run_callbacks :persistence, :create, create_params, meta do
77
+ warn_attributes_mutated_in_around_persistence(:create, snapshot, create_params)
78
+
76
79
  run_callbacks :attributes, :create, create_params, meta do |params|
77
80
  model_instance = call_with_meta(:build, model, meta)
78
81
  call_with_meta(:assign_attributes, model_instance, params, meta)
@@ -92,7 +95,11 @@ module Graphiti
92
95
  id = update_params[:id]
93
96
  update_params = update_params.except(:id)
94
97
 
98
+ snapshot = attributes_snapshot(:update, update_params)
99
+
95
100
  run_callbacks :persistence, :update, update_params, meta do
101
+ warn_attributes_mutated_in_around_persistence(:update, snapshot, update_params)
102
+
96
103
  run_callbacks :attributes, :update, update_params, meta do |params|
97
104
  model_instance = self.class._find(id: id).data
98
105
  call_with_meta(:assign_attributes, model_instance, params, meta)
@@ -133,6 +140,37 @@ module Graphiti
133
140
 
134
141
  private
135
142
 
143
+ # In Graphiti 2.0, attributes are assigned to the model before the
144
+ # persistence hooks fire, so hash modifications made by an
145
+ # around_persistence hook before its yield will no longer be applied.
146
+ # Snapshot-and-compare detects exactly those hooks: the comparison
147
+ # happens before any attributes-phase callback has run, so hooks that
148
+ # only wrap their yield never trigger the warning.
149
+ def attributes_snapshot(action, params)
150
+ return unless around_persistence_hooks?(action)
151
+ Util::Hash.deep_dup(params)
152
+ end
153
+
154
+ def around_persistence_hooks?(action)
155
+ hooks = self.class.config[:callbacks][:persistence].try(:[], :around) || []
156
+ hooks.any? { |hook| hook[:only].include?(action) }
157
+ end
158
+
159
+ def warn_attributes_mutated_in_around_persistence(action, snapshot, params)
160
+ return if snapshot.nil? || snapshot == params
161
+
162
+ changed_keys = (snapshot.keys | params.keys).reject { |key| snapshot[key] == params[key] }
163
+ Graphiti::DEPRECATOR.warn(<<~MSG)
164
+ #{self.class}'s around_persistence hook modified the attributes hash before yield (changed keys: #{changed_keys.map(&:inspect).join(", ")}). In Graphiti 2.0, attributes are assigned to the model before persistence hooks run, and these modifications will be silently ignored. Move the modification to a hook that runs before assignment:
165
+
166
+ before_attributes do |attributes|
167
+ attributes[#{changed_keys.first.inspect}] = ...
168
+ end
169
+
170
+ or set the value on the model itself in a before_save hook. See UPGRADING.md in the Graphiti 2.0 release.
171
+ MSG
172
+ end
173
+
136
174
  def run_callbacks(kind, action, *args)
137
175
  fire_around_callbacks(kind, action, *args) do |*yieldargs|
138
176
  fire_callbacks(kind, :before, action, *yieldargs)
@@ -80,6 +80,12 @@ module Graphiti
80
80
  Renderer.new(self, options).as_graphql
81
81
  end
82
82
 
83
+ # Records supplied directly, no scope resolution
84
+ def data=(models)
85
+ @data = models
86
+ [@data].flatten.compact.each { |record| @resource.decorate_record(record) }
87
+ end
88
+
83
89
  def data
84
90
  @data ||= begin
85
91
  records = @scope.resolve
@@ -1,3 +1,3 @@
1
1
  module Graphiti
2
- VERSION = "1.12.2"
2
+ VERSION = "1.13.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphiti
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.12.2
4
+ version: 1.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lee Richmond
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-07-29 00:00:00.000000000 Z
11
+ date: 2026-07-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jsonapi-serializable