graphiti 1.6.4 → 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/lib/graphiti/configuration.rb +12 -0
- data/lib/graphiti/debugger.rb +24 -1
- data/lib/graphiti/query.rb +16 -0
- data/lib/graphiti/renderer.rb +8 -1
- data/lib/graphiti/resource/interface.rb +15 -2
- data/lib/graphiti/resource_proxy.rb +29 -2
- data/lib/graphiti/runner.rb +3 -1
- data/lib/graphiti/scope.rb +56 -0
- data/lib/graphiti/serializer.rb +1 -1
- data/lib/graphiti/sideload.rb +10 -3
- data/lib/graphiti/util/cache_debug.rb +88 -0
- data/lib/graphiti/version.rb +1 -1
- data/lib/graphiti.rb +9 -0
- 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: 48e9df9f78bfe2d9b9303f304541894222fa4eb3d925f2ac68a01e3b0567fbb4
|
4
|
+
data.tar.gz: d8d010bca5894bc11cc0bfb76f9d88deaf6d95d7b413d937765b58d87fefe1cc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 56ec9608ea8de6b8a8be491e27a8dc0ef83b297a3178e617495d8a63098d8df2d408b944e4f314eacd98e8e87e278e01fa287b68e07961228086ffb21259d509
|
7
|
+
data.tar.gz: f2077122a2b0a4996a0326f21fcae529724dd6438303f42822eb73605b58801c1c8466d012d8f3edf5899cb82ed234373fcc989f4630b012d7e045c32e40894d
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
graphiti changelog
|
2
2
|
|
3
|
+
# [1.7.0](https://github.com/graphiti-api/graphiti/compare/v1.6.4...v1.7.0) (2024-03-27)
|
4
|
+
|
5
|
+
|
6
|
+
### Features
|
7
|
+
|
8
|
+
* Add support for caching renders in Graphiti, and better support using etags and stale? in the controller ([#424](https://github.com/graphiti-api/graphiti/issues/424)) ([8bae50a](https://github.com/graphiti-api/graphiti/commit/8bae50ab82559e2644d506e16a4f715effd89317))
|
9
|
+
|
3
10
|
## [1.6.4](https://github.com/graphiti-api/graphiti/compare/v1.6.3...v1.6.4) (2024-03-27)
|
4
11
|
|
5
12
|
## [1.6.3](https://github.com/graphiti-api/graphiti/compare/v1.6.2...v1.6.3) (2024-03-26)
|
@@ -20,6 +20,7 @@ module Graphiti
|
|
20
20
|
attr_reader :debug, :debug_models
|
21
21
|
|
22
22
|
attr_writer :schema_path
|
23
|
+
attr_writer :cache_rendering
|
23
24
|
|
24
25
|
# Set defaults
|
25
26
|
# @api private
|
@@ -32,6 +33,7 @@ module Graphiti
|
|
32
33
|
@pagination_links = false
|
33
34
|
@typecast_reads = true
|
34
35
|
@raise_on_missing_sidepost = true
|
36
|
+
@cache_rendering = false
|
35
37
|
self.debug = ENV.fetch("GRAPHITI_DEBUG", true)
|
36
38
|
self.debug_models = ENV.fetch("GRAPHITI_DEBUG_MODELS", false)
|
37
39
|
|
@@ -52,6 +54,16 @@ module Graphiti
|
|
52
54
|
end
|
53
55
|
end
|
54
56
|
|
57
|
+
def cache_rendering?
|
58
|
+
use_caching = @cache_rendering && Graphiti.cache.respond_to?(:fetch)
|
59
|
+
|
60
|
+
use_caching.tap do |use|
|
61
|
+
if @cache_rendering && !Graphiti.cache&.respond_to?(:fetch)
|
62
|
+
raise "You must configure a cache store in order to use cache_rendering. Set Graphiti.cache = Rails.cache, for example."
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
55
67
|
def schema_path
|
56
68
|
@schema_path ||= raise("No schema_path defined! Set Graphiti.config.schema_path to save your schema.")
|
57
69
|
end
|
data/lib/graphiti/debugger.rb
CHANGED
@@ -98,7 +98,30 @@ module Graphiti
|
|
98
98
|
took = ((stop - start) * 1000.0).round(2)
|
99
99
|
logs << [""]
|
100
100
|
logs << ["=== Graphiti Debug", :green, true]
|
101
|
-
|
101
|
+
if payload[:proxy]&.cached? && Graphiti.config.cache_rendering?
|
102
|
+
logs << ["Rendering (cached):", :green, true]
|
103
|
+
|
104
|
+
Graphiti::Util::CacheDebug.new(payload[:proxy]).analyze do |cache_debug|
|
105
|
+
logs << ["Cache key for #{cache_debug.name}", :blue, true]
|
106
|
+
logs << if cache_debug.volatile?
|
107
|
+
[" \\_ volatile | Request count: #{cache_debug.request_count} | Hit count: #{cache_debug.hit_count}", :red, true]
|
108
|
+
else
|
109
|
+
[" \\_ stable | Request count: #{cache_debug.request_count} | Hit count: #{cache_debug.hit_count}", :blue, true]
|
110
|
+
end
|
111
|
+
|
112
|
+
if cache_debug.changed_key?
|
113
|
+
logs << [" [x] cache key changed #{cache_debug.last_version[:etag]} -> #{cache_debug.current_version[:etag]}", :red]
|
114
|
+
logs << [" removed: #{cache_debug.removed_segments}", :red]
|
115
|
+
logs << [" added: #{cache_debug.added_segments}", :red]
|
116
|
+
elsif cache_debug.new_key?
|
117
|
+
logs << [" [+] cache key added #{cache_debug.current_version[:etag]}", :red, true]
|
118
|
+
else
|
119
|
+
logs << [" [✓] #{cache_debug.current_version[:etag]}", :green, true]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
else
|
123
|
+
logs << ["Rendering:", :green, true]
|
124
|
+
end
|
102
125
|
logs << ["Took: #{took}ms", :magenta, true]
|
103
126
|
end
|
104
127
|
end
|
data/lib/graphiti/query.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require "digest"
|
2
|
+
|
1
3
|
module Graphiti
|
2
4
|
class Query
|
3
5
|
attr_reader :resource, :association_name, :params, :action
|
@@ -232,8 +234,22 @@ module Graphiti
|
|
232
234
|
![false, "false"].include?(@params[:paginate])
|
233
235
|
end
|
234
236
|
|
237
|
+
def cache_key
|
238
|
+
"args-#{query_cache_key}"
|
239
|
+
end
|
240
|
+
|
235
241
|
private
|
236
242
|
|
243
|
+
def query_cache_key
|
244
|
+
attrs = {extra_fields: extra_fields,
|
245
|
+
fields: fields,
|
246
|
+
links: links?,
|
247
|
+
pagination_links: pagination_links?,
|
248
|
+
format: params[:format]}
|
249
|
+
|
250
|
+
Digest::SHA1.hexdigest(attrs.to_s)
|
251
|
+
end
|
252
|
+
|
237
253
|
def cast_page_param(name, value)
|
238
254
|
if [:before, :after].include?(name)
|
239
255
|
decode_cursor(value)
|
data/lib/graphiti/renderer.rb
CHANGED
@@ -68,7 +68,14 @@ module Graphiti
|
|
68
68
|
options[:meta][:debug] = Debugger.to_a if debug_json?
|
69
69
|
options[:proxy] = proxy
|
70
70
|
|
71
|
-
|
71
|
+
if proxy.cache? && Graphiti.config.cache_rendering?
|
72
|
+
Graphiti.cache.fetch("graphiti:render/#{proxy.cache_key}", version: proxy.updated_at, expires_in: proxy.cache_expires_in) do
|
73
|
+
options.delete(:cache) # ensure that we don't use JSONAPI-Resources's built-in caching logic
|
74
|
+
renderer.render(records, options)
|
75
|
+
end
|
76
|
+
else
|
77
|
+
renderer.render(records, options)
|
78
|
+
end
|
72
79
|
end
|
73
80
|
end
|
74
81
|
|
@@ -4,6 +4,11 @@ module Graphiti
|
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
6
|
class_methods do
|
7
|
+
def cache_resource(expires_in: false)
|
8
|
+
@cache_resource = true
|
9
|
+
@cache_expires_in = expires_in
|
10
|
+
end
|
11
|
+
|
7
12
|
def all(params = {}, base_scope = nil)
|
8
13
|
validate_request!(params)
|
9
14
|
_all(params, {}, base_scope)
|
@@ -13,7 +18,7 @@ module Graphiti
|
|
13
18
|
def _all(params, opts, base_scope)
|
14
19
|
runner = Runner.new(self, params, opts.delete(:query), :all)
|
15
20
|
opts[:params] = params
|
16
|
-
runner.proxy(base_scope, opts)
|
21
|
+
runner.proxy(base_scope, opts.merge(caching_options))
|
17
22
|
end
|
18
23
|
|
19
24
|
def find(params = {}, base_scope = nil)
|
@@ -31,10 +36,14 @@ module Graphiti
|
|
31
36
|
params[:filter][:id] = id if id
|
32
37
|
|
33
38
|
runner = Runner.new(self, params, nil, :find)
|
34
|
-
|
39
|
+
|
40
|
+
find_options = {
|
35
41
|
single: true,
|
36
42
|
raise_on_missing: true,
|
37
43
|
bypass_required_filters: true
|
44
|
+
}.merge(caching_options)
|
45
|
+
|
46
|
+
runner.proxy base_scope, find_options
|
38
47
|
end
|
39
48
|
|
40
49
|
def build(params, base_scope = nil)
|
@@ -45,6 +54,10 @@ module Graphiti
|
|
45
54
|
|
46
55
|
private
|
47
56
|
|
57
|
+
def caching_options
|
58
|
+
{cache: @cache_resource, cache_expires_in: @cache_expires_in}
|
59
|
+
end
|
60
|
+
|
48
61
|
def validate_request!(params)
|
49
62
|
return if Graphiti.context[:graphql] || !validate_endpoints?
|
50
63
|
|
@@ -2,20 +2,31 @@ module Graphiti
|
|
2
2
|
class ResourceProxy
|
3
3
|
include Enumerable
|
4
4
|
|
5
|
-
attr_reader :resource, :query, :scope, :payload
|
5
|
+
attr_reader :resource, :query, :scope, :payload, :cache_expires_in, :cache
|
6
6
|
|
7
7
|
def initialize(resource, scope, query,
|
8
8
|
payload: nil,
|
9
9
|
single: false,
|
10
|
-
raise_on_missing: false
|
10
|
+
raise_on_missing: false,
|
11
|
+
cache: nil,
|
12
|
+
cache_expires_in: nil)
|
13
|
+
|
11
14
|
@resource = resource
|
12
15
|
@scope = scope
|
13
16
|
@query = query
|
14
17
|
@payload = payload
|
15
18
|
@single = single
|
16
19
|
@raise_on_missing = raise_on_missing
|
20
|
+
@cache = cache
|
21
|
+
@cache_expires_in = cache_expires_in
|
22
|
+
end
|
23
|
+
|
24
|
+
def cache?
|
25
|
+
!!@cache
|
17
26
|
end
|
18
27
|
|
28
|
+
alias_method :cached?, :cache?
|
29
|
+
|
19
30
|
def single?
|
20
31
|
!!@single
|
21
32
|
end
|
@@ -180,6 +191,22 @@ module Graphiti
|
|
180
191
|
query.debug_requested?
|
181
192
|
end
|
182
193
|
|
194
|
+
def updated_at
|
195
|
+
@scope.updated_at
|
196
|
+
end
|
197
|
+
|
198
|
+
def etag
|
199
|
+
"W/#{ActiveSupport::Digest.hexdigest(cache_key_with_version.to_s)}"
|
200
|
+
end
|
201
|
+
|
202
|
+
def cache_key
|
203
|
+
ActiveSupport::Cache.expand_cache_key([@scope.cache_key, @query.cache_key])
|
204
|
+
end
|
205
|
+
|
206
|
+
def cache_key_with_version
|
207
|
+
ActiveSupport::Cache.expand_cache_key([@scope.cache_key_with_version, @query.cache_key])
|
208
|
+
end
|
209
|
+
|
183
210
|
private
|
184
211
|
|
185
212
|
def persist
|
data/lib/graphiti/runner.rb
CHANGED
@@ -71,7 +71,9 @@ module Graphiti
|
|
71
71
|
query,
|
72
72
|
payload: deserialized_payload,
|
73
73
|
single: opts[:single],
|
74
|
-
raise_on_missing: opts[:raise_on_missing]
|
74
|
+
raise_on_missing: opts[:raise_on_missing],
|
75
|
+
cache: opts[:cache],
|
76
|
+
cache_expires_in: opts[:cache_expires_in]
|
75
77
|
end
|
76
78
|
end
|
77
79
|
end
|
data/lib/graphiti/scope.rb
CHANGED
@@ -67,8 +67,64 @@ module Graphiti
|
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
70
|
+
def parent_resource
|
71
|
+
@resource
|
72
|
+
end
|
73
|
+
|
74
|
+
def cache_key
|
75
|
+
# This is the combined cache key for the base query and the query for all sideloads
|
76
|
+
# Changing the query will yield a different cache key
|
77
|
+
|
78
|
+
cache_keys = sideload_resource_proxies.map { |proxy| proxy.try(:cache_key) }
|
79
|
+
|
80
|
+
cache_keys << @object.try(:cache_key) # this is what calls into the ORM (ActiveRecord, most likely)
|
81
|
+
ActiveSupport::Cache.expand_cache_key(cache_keys.flatten.compact)
|
82
|
+
end
|
83
|
+
|
84
|
+
def cache_key_with_version
|
85
|
+
# This is the combined and versioned cache key for the base query and the query for all sideloads
|
86
|
+
# If any returned model's updated_at changes, this key will change
|
87
|
+
|
88
|
+
cache_keys = sideload_resource_proxies.map { |proxy| proxy.try(:cache_key_with_version) }
|
89
|
+
|
90
|
+
cache_keys << @object.try(:cache_key_with_version) # this is what calls into ORM (ActiveRecord, most likely)
|
91
|
+
ActiveSupport::Cache.expand_cache_key(cache_keys.flatten.compact)
|
92
|
+
end
|
93
|
+
|
94
|
+
def updated_at
|
95
|
+
updated_ats = sideload_resource_proxies.map(&:updated_at)
|
96
|
+
|
97
|
+
begin
|
98
|
+
updated_ats << @object.maximum(:updated_at)
|
99
|
+
rescue => e
|
100
|
+
Graphiti.log("error calculating last_modified_at for #{@resource.class}")
|
101
|
+
Graphiti.log(e)
|
102
|
+
end
|
103
|
+
|
104
|
+
updated_ats.compact.max
|
105
|
+
end
|
106
|
+
alias_method :last_modified_at, :updated_at
|
107
|
+
|
70
108
|
private
|
71
109
|
|
110
|
+
def sideload_resource_proxies
|
111
|
+
@sideload_resource_proxies ||= begin
|
112
|
+
@object = @resource.before_resolve(@object, @query)
|
113
|
+
results = @resource.resolve(@object)
|
114
|
+
|
115
|
+
[].tap do |proxies|
|
116
|
+
unless @query.sideloads.empty?
|
117
|
+
@query.sideloads.each_pair do |name, q|
|
118
|
+
sideload = @resource.class.sideload(name)
|
119
|
+
next if sideload.nil? || sideload.shared_remote?
|
120
|
+
|
121
|
+
proxies << sideload.build_resource_proxy(results, q, parent_resource)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end.flatten
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
72
128
|
def broadcast_data
|
73
129
|
opts = {
|
74
130
|
resource: @resource,
|
data/lib/graphiti/serializer.rb
CHANGED
@@ -99,7 +99,7 @@ module Graphiti
|
|
99
99
|
|
100
100
|
def strip_relationships?
|
101
101
|
return false unless Graphiti.config.links_on_demand
|
102
|
-
params = Graphiti.context[:object]
|
102
|
+
params = Graphiti.context[:object]&.params || {}
|
103
103
|
[false, nil, "false"].include?(params[:links])
|
104
104
|
end
|
105
105
|
end
|
data/lib/graphiti/sideload.rb
CHANGED
@@ -209,13 +209,16 @@ module Graphiti
|
|
209
209
|
end
|
210
210
|
end
|
211
211
|
|
212
|
-
def
|
213
|
-
params
|
212
|
+
def build_resource_proxy(parents, query, graph_parent)
|
213
|
+
params = nil
|
214
|
+
opts = nil
|
215
|
+
proxy = nil
|
214
216
|
|
215
217
|
with_error_handling Errors::SideloadParamsError do
|
216
218
|
params = load_params(parents, query)
|
217
219
|
params_proc&.call(params, parents, context)
|
218
220
|
return [] if blank_query?(params)
|
221
|
+
|
219
222
|
opts = load_options(parents, query)
|
220
223
|
opts[:sideload] = self
|
221
224
|
opts[:parent] = graph_parent
|
@@ -228,7 +231,11 @@ module Graphiti
|
|
228
231
|
pre_load_proc&.call(proxy, parents)
|
229
232
|
end
|
230
233
|
|
231
|
-
proxy
|
234
|
+
proxy
|
235
|
+
end
|
236
|
+
|
237
|
+
def load(parents, query, graph_parent)
|
238
|
+
build_resource_proxy(parents, query, graph_parent).to_a
|
232
239
|
end
|
233
240
|
|
234
241
|
# Override in subclass
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Graphiti
|
2
|
+
module Util
|
3
|
+
class CacheDebug
|
4
|
+
attr_reader :proxy
|
5
|
+
|
6
|
+
def initialize(proxy)
|
7
|
+
@proxy = proxy
|
8
|
+
end
|
9
|
+
|
10
|
+
def last_version
|
11
|
+
@last_version ||= Graphiti.cache.read(key) || {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def name
|
15
|
+
"#{Graphiti.context[:object].request.method} #{Graphiti.context[:object].request.url}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def key
|
19
|
+
"graphiti:debug/#{name}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def current_version
|
23
|
+
@current_version ||= {
|
24
|
+
cache_key: proxy.cache_key_with_version,
|
25
|
+
version: proxy.updated_at,
|
26
|
+
expires_in: proxy.cache_expires_in,
|
27
|
+
etag: proxy.etag,
|
28
|
+
miss_count: last_version[:miss_count].to_i + (changed_key? ? 1 : 0),
|
29
|
+
hit_count: last_version[:hit_count].to_i + (!changed_key? && !new_key? ? 1 : 0),
|
30
|
+
request_count: last_version[:request_count].to_i + (last_version.present? ? 1 : 0)
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def analyze
|
35
|
+
yield self
|
36
|
+
save
|
37
|
+
end
|
38
|
+
|
39
|
+
def request_count
|
40
|
+
current_version[:request_count]
|
41
|
+
end
|
42
|
+
|
43
|
+
def miss_count
|
44
|
+
current_version[:miss_count]
|
45
|
+
end
|
46
|
+
|
47
|
+
def hit_count
|
48
|
+
current_version[:hit_count]
|
49
|
+
end
|
50
|
+
|
51
|
+
def change_percentage
|
52
|
+
return 0 if request_count == 0
|
53
|
+
(miss_count.to_i / request_count.to_f * 100).round(1)
|
54
|
+
end
|
55
|
+
|
56
|
+
def volatile?
|
57
|
+
change_percentage > 50
|
58
|
+
end
|
59
|
+
|
60
|
+
def new_key?
|
61
|
+
last_version[:cache_key].blank? && proxy.cache_key_with_version
|
62
|
+
end
|
63
|
+
|
64
|
+
def changed_key?
|
65
|
+
last_version[:cache_key] != proxy.cache_key_with_version && !new_key?
|
66
|
+
end
|
67
|
+
|
68
|
+
def removed_segments
|
69
|
+
changes[1] - changes[0]
|
70
|
+
end
|
71
|
+
|
72
|
+
def added_segments
|
73
|
+
changes[0] - changes[1]
|
74
|
+
end
|
75
|
+
|
76
|
+
def changes
|
77
|
+
sub_keys_old = last_version[:cache_key]&.scan(/\w+\/query-[a-z0-9-]+\/args-[a-z0-9-]+/).to_a || []
|
78
|
+
sub_keys_new = current_version[:cache_key]&.scan(/\w+\/query-[a-z0-9-]+\/args-[a-z0-9-]+/).to_a || []
|
79
|
+
|
80
|
+
[sub_keys_old, sub_keys_new]
|
81
|
+
end
|
82
|
+
|
83
|
+
def save
|
84
|
+
Graphiti.cache.write(key, current_version)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/lib/graphiti/version.rb
CHANGED
data/lib/graphiti.rb
CHANGED
@@ -106,6 +106,14 @@ module Graphiti
|
|
106
106
|
r.apply_sideloads_to_serializer
|
107
107
|
end
|
108
108
|
end
|
109
|
+
|
110
|
+
def self.cache=(val)
|
111
|
+
@cache = val
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.cache
|
115
|
+
@cache
|
116
|
+
end
|
109
117
|
end
|
110
118
|
|
111
119
|
require "graphiti/version"
|
@@ -177,6 +185,7 @@ require "graphiti/extensions/temp_id"
|
|
177
185
|
require "graphiti/serializer"
|
178
186
|
require "graphiti/query"
|
179
187
|
require "graphiti/debugger"
|
188
|
+
require "graphiti/util/cache_debug"
|
180
189
|
|
181
190
|
if defined?(ActiveRecord)
|
182
191
|
require "graphiti/adapters/active_record"
|
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: 1.
|
4
|
+
version: 1.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lee Richmond
|
@@ -324,6 +324,7 @@ files:
|
|
324
324
|
- lib/graphiti/stats/payload.rb
|
325
325
|
- lib/graphiti/types.rb
|
326
326
|
- lib/graphiti/util/attribute_check.rb
|
327
|
+
- lib/graphiti/util/cache_debug.rb
|
327
328
|
- lib/graphiti/util/class.rb
|
328
329
|
- lib/graphiti/util/field_params.rb
|
329
330
|
- lib/graphiti/util/hash.rb
|