graphql-fragment_cache 1.20.5 → 1.21.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 241af11e5f9aa966c8c544a72a4dc2a92f63267e503636962b198b658727c9d8
4
- data.tar.gz: 4e4abd9fbc612dcdbd534466e3ea86f72f990117a98970573b1ffc66d4305bfc
3
+ metadata.gz: a13fb7ec2904be7d61b8f2ff020cc131d794f77cda87f9ea0cca5a71850055d8
4
+ data.tar.gz: 4c04a9f8aed82998a96c28334b6fc0a3dc06de61d8c0d9e6eb7287dc8b378076
5
5
  SHA512:
6
- metadata.gz: 994673b1241a0de72655671fc7b3de19fa182191ed41263c184138a5a79ef338e8d167d51d54193131bbd3fb4af281caa72df3308748cf98a2f8478fb40b7029
7
- data.tar.gz: a2d13a3eb6f124cac0e5820afeac651c29e7b087649a350fe92dc29ba719b3bd5257fed7896a15a3e5332ba6502275096f36cd991b2aad0696846bcb52acb840
6
+ metadata.gz: 4347bed71acb7657604b0ede1bccf075fffc00429697dc6c05cd26fa169ed19e58325f542a94b964f2129b4b25c3656c74d4e567440cfd848f5514e3ed1dd9c0
7
+ data.tar.gz: 42f0f52fe8d48d386c1bbc4a931eb3ff08d63164f2fd18f10d77830f044df496c27702f2af383117f685ae3eda1df7717f948d978ba42cb5fa812931a827147d
data/CHANGELOG.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 1.21.0 (2025-02-01)
6
+
7
+ - [PR#130](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache/pull/130) Dataloader support ([@DmitryTsepelev][])
8
+ - [PR#125](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache/pull/125) Introduce cache lookup instrumentation hook ([@danielhartnell][])
9
+
5
10
  ## 1.20.5 (2024-11-02)
6
11
 
7
12
  - [PR#120](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache/pull/120) Fix warning on ActiveSupport::Cache.format_version ([@Drowze][])
@@ -211,3 +216,4 @@
211
216
  [@diegofigueroa]: https://github.com/diegofigueroa
212
217
  [@noma4i]: https://github.com/noma4i
213
218
  [@Drowze]: https://github.com/Drowze
219
+ [@danielhartnell]: https://github.com/danielhartnell
data/README.md CHANGED
@@ -381,6 +381,34 @@ class QueryType < BaseObject
381
381
  end
382
382
  ```
383
383
 
384
+ ## Dataloader
385
+
386
+ If you are using [Dataloader](https://graphql-ruby.org/dataloader/overview.html), you will need to let the gem know using `dataloader: true`:
387
+
388
+ ```ruby
389
+ class PostType < BaseObject
390
+ field :author, User, null: false
391
+
392
+ def author
393
+ cache_fragment(dataloader: true) do
394
+ dataloader.with(AuthorDataloaderSource).load(object.id)
395
+ end
396
+ end
397
+ end
398
+
399
+ # or
400
+
401
+ class PostType < BaseObject
402
+ field :author, User, null: false, cache_fragment: {dataloader: true}
403
+
404
+ def author
405
+ dataloader.with(AuthorDataloaderSource).load(object.id)
406
+ end
407
+ end
408
+ ```
409
+
410
+ The problem is that I didn't find a way to detect that dataloader (and, therefore, Fiber) is used, and the block is forced to resolve, causing the N+1 inside the Dataloader Source class.
411
+
384
412
  ## How to use `#cache_fragment` in extensions (and other places where context is not available)
385
413
 
386
414
  If you want to call `#cache_fragment` from places other that fields or resolvers, you'll need to pass `context` explicitly and turn on `raw_value` support. For instance, let's take a look at this extension:
@@ -446,6 +474,30 @@ Cache processing can be disabled if needed. For example:
446
474
  GraphQL::FragmentCache.enabled = false if Rails.env.test?
447
475
  ```
448
476
 
477
+ ## Cache lookup monitoring
478
+
479
+ It may be useful to capture cache lookup events. When monitoring is enabled, the `cache_key`, `operation_name`, `path` and a boolean indicating a cache hit or miss will be sent to a `cache_lookup_event` method. This method can be implemented in your application to handle the event.
480
+
481
+ Example handler defined in a Rails initializer:
482
+
483
+ ```ruby
484
+ module GraphQL
485
+ module FragmentCache
486
+ class Fragment
487
+ def self.cache_lookup_event(**args)
488
+ # Monitoring such as incrementing a cache hit counter metric
489
+ end
490
+ end
491
+ end
492
+ end
493
+ ```
494
+
495
+ Like managing caching itself, monitoring can be enabled if needed. It is disabled by default. For example:
496
+
497
+ ```ruby
498
+ GraphQL::FragmentCache.monitoring_enabled = true
499
+ ```
500
+
449
501
  ## Limitations
450
502
 
451
503
  1. `Schema#execute`, [graphql-batch](https://github.com/Shopify/graphql-batch) and _graphql-ruby-fragment_cache_ do not [play well](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache/issues/45) together. The problem appears when `cache_fragment` is _inside_ the `.then` block:
@@ -477,7 +529,7 @@ end
477
529
  field :cached_avatar_url, String, null: false
478
530
 
479
531
  def cached_avatar_url
480
- cache_fragment(query_cache_key: "post_avatar_url(#{object.id})") { object.avatar_url }
532
+ cache_fragment(path_cache_key: "post_avatar_url(#{object.id})") { object.avatar_url }
481
533
  end
482
534
  ```
483
535
 
@@ -14,7 +14,7 @@ module GraphQL
14
14
  def traverse_argument(argument)
15
15
  return argument unless argument.is_a?(GraphQL::Schema::InputObject)
16
16
 
17
- "{#{argument.map { |_1, _2| "#{_1}:#{traverse_argument(_2)}" }.sort.join(",")}}"
17
+ "{#{argument.map { |name, arg| "#{name}:#{traverse_argument(arg)}" }.sort.join(",")}}"
18
18
  end
19
19
 
20
20
  def to_selections_key
@@ -26,7 +26,7 @@ module GraphQL
26
26
  field_name = "#{field_alias}:#{field_name}" unless field_alias.empty?
27
27
 
28
28
  unless val.arguments.empty?
29
- args = val.arguments.map { |_1, _2| "#{_1}:#{traverse_argument(_2)}" }.sort.join(",")
29
+ args = val.arguments.map { |name, arg| "#{name}:#{traverse_argument(arg)}" }.sort.join(",")
30
30
  field_name += "(#{args})"
31
31
  end
32
32
 
@@ -53,64 +53,66 @@ module GraphQL
53
53
  alias_selection(name, **kwargs)
54
54
  end
55
55
 
56
- def alias_selection(name, selected_type: @selected_type, arguments: nil)
57
- return alias_selections[name] if alias_selections.key?(name)
56
+ if GraphRubyVersion.before_2_1_4?
57
+ def alias_selection(name, selected_type: @selected_type, arguments: nil)
58
+ return alias_selections[name] if alias_selections.key?(name)
58
59
 
59
- alias_node = lookup_alias_node(ast_nodes, name)
60
- return ::GraphQL::Execution::Lookahead::NULL_LOOKAHEAD unless alias_node
60
+ alias_node = lookup_alias_node(ast_nodes, name)
61
+ return ::GraphQL::Execution::Lookahead::NULL_LOOKAHEAD unless alias_node
61
62
 
62
- next_field_name = alias_node.name
63
+ next_field_name = alias_node.name
63
64
 
64
- # From https://github.com/rmosolgo/graphql-ruby/blob/1a9a20f3da629e63ea8e5ee8400be82218f9edc3/lib/graphql/execution/lookahead.rb#L91
65
- next_field_defn =
66
- if GraphQL::FragmentCache.graphql_ruby_before_2_0?
67
- get_class_based_field(selected_type, next_field_name)
68
- else
69
- @query.get_field(selected_type, next_field_name)
70
- end
65
+ # From https://github.com/rmosolgo/graphql-ruby/blob/1a9a20f3da629e63ea8e5ee8400be82218f9edc3/lib/graphql/execution/lookahead.rb#L91
66
+ next_field_defn =
67
+ if GraphRubyVersion.before_2_0?
68
+ get_class_based_field(selected_type, next_field_name)
69
+ else
70
+ @query.get_field(selected_type, next_field_name)
71
+ end
71
72
 
72
- alias_selections[name] =
73
- if next_field_defn
74
- next_nodes = []
75
- arguments = @query.arguments_for(alias_node, next_field_defn)
76
- arguments = arguments.is_a?(::GraphQL::Execution::Interpreter::Arguments) ? arguments.keyword_arguments : arguments
77
- @ast_nodes.each do |ast_node|
78
- ast_node.selections.each do |selection|
79
- if GraphQL::FragmentCache.graphql_ruby_after_2_0_13? && GraphQL::FragmentCache.graphql_ruby_before_2_1_4?
80
- find_selected_nodes(selection, next_field_defn, arguments: arguments, matches: next_nodes)
81
- else
82
- find_selected_nodes(selection, next_field_name, next_field_defn, arguments: arguments, matches: next_nodes)
73
+ alias_selections[name] =
74
+ if next_field_defn
75
+ next_nodes = []
76
+ arguments = @query.arguments_for(alias_node, next_field_defn)
77
+ arguments = arguments.is_a?(::GraphQL::Execution::Interpreter::Arguments) ? arguments.keyword_arguments : arguments
78
+ @ast_nodes.each do |ast_node|
79
+ ast_node.selections.each do |selection|
80
+ if GraphRubyVersion.after_2_0_13? && GraphRubyVersion.before_2_1_4?
81
+ find_selected_nodes(selection, next_field_defn, arguments: arguments, matches: next_nodes)
82
+ else
83
+ find_selected_nodes(selection, next_field_name, next_field_defn, arguments: arguments, matches: next_nodes)
84
+ end
83
85
  end
84
86
  end
85
- end
86
87
 
87
- if next_nodes.any?
88
- ::GraphQL::Execution::Lookahead.new(query: @query, ast_nodes: next_nodes, field: next_field_defn, owner_type: selected_type)
88
+ if next_nodes.any?
89
+ ::GraphQL::Execution::Lookahead.new(query: @query, ast_nodes: next_nodes, field: next_field_defn, owner_type: selected_type)
90
+ else
91
+ ::GraphQL::Execution::Lookahead::NULL_LOOKAHEAD
92
+ end
89
93
  else
90
94
  ::GraphQL::Execution::Lookahead::NULL_LOOKAHEAD
91
95
  end
92
- else
93
- ::GraphQL::Execution::Lookahead::NULL_LOOKAHEAD
94
- end
95
- end
96
+ end
96
97
 
97
- def alias_selections
98
- return @alias_selections if defined?(@alias_selections)
99
- @alias_selections ||= {}
100
- end
98
+ def alias_selections
99
+ return @alias_selections if defined?(@alias_selections)
100
+ @alias_selections ||= {}
101
+ end
101
102
 
102
- def lookup_alias_node(nodes, name)
103
- return if nodes.empty?
103
+ def lookup_alias_node(nodes, name)
104
+ return if nodes.empty?
104
105
 
105
- nodes.find do |node|
106
- if node.is_a?(GraphQL::Language::Nodes::FragmentSpread)
107
- node = @query.fragments[node.name]
108
- raise("Invariant: Can't look ahead to nonexistent fragment #{node.name} (found: #{@query.fragments.keys})") unless node
109
- end
106
+ nodes.find do |node|
107
+ if node.is_a?(GraphQL::Language::Nodes::FragmentSpread)
108
+ node = @query.fragments[node.name]
109
+ raise("Invariant: Can't look ahead to nonexistent fragment #{node.name} (found: #{@query.fragments.keys})") unless node
110
+ end
110
111
 
111
- return node if node.alias?(name)
112
- child = lookup_alias_node(node.children, name)
113
- return child if child
112
+ return node if node.alias?(name)
113
+ child = lookup_alias_node(node.children, name)
114
+ return child if child
115
+ end
114
116
  end
115
117
  end
116
118
  end
@@ -195,7 +197,7 @@ module GraphQL
195
197
 
196
198
  next lookahead.field.name if lookahead.arguments.empty?
197
199
 
198
- args = lookahead.arguments.map { |_1, _2| "#{_1}:#{traverse_argument(_2)}" }.sort.join(",")
200
+ args = lookahead.arguments.map { |name, arg| "#{name}:#{traverse_argument(arg)}" }.sort.join(",")
199
201
  "#{lookahead.field.name}(#{args})"
200
202
  }.join("/")
201
203
  end
@@ -204,7 +206,7 @@ module GraphQL
204
206
  def traverse_argument(argument)
205
207
  return argument unless argument.is_a?(GraphQL::Schema::InputObject)
206
208
 
207
- "{#{argument.map { |_1, _2| "#{_1}:#{traverse_argument(_2)}" }.sort.join(",")}}"
209
+ "{#{argument.map { |name, arg| "#{name}:#{traverse_argument(arg)}" }.sort.join(",")}}"
208
210
  end
209
211
 
210
212
  def object_cache_key
@@ -3,8 +3,6 @@
3
3
  require "json"
4
4
  require "digest"
5
5
 
6
- using RubyNext
7
-
8
6
  module GraphQL
9
7
  module FragmentCache
10
8
  using Ext
@@ -52,76 +50,11 @@ module GraphQL
52
50
  return selection(name, **kwargs) if selects?(name, **kwargs)
53
51
  alias_selection(name, **kwargs)
54
52
  end
55
-
56
- if GraphRubyVersion.before_2_1_4?
57
- def alias_selection(name, selected_type: @selected_type, arguments: nil)
58
- return alias_selections[name] if alias_selections.key?(name)
59
-
60
- alias_node = lookup_alias_node(ast_nodes, name)
61
- return ::GraphQL::Execution::Lookahead::NULL_LOOKAHEAD unless alias_node
62
-
63
- next_field_name = alias_node.name
64
-
65
- # From https://github.com/rmosolgo/graphql-ruby/blob/1a9a20f3da629e63ea8e5ee8400be82218f9edc3/lib/graphql/execution/lookahead.rb#L91
66
- next_field_defn =
67
- if GraphRubyVersion.before_2_0?
68
- get_class_based_field(selected_type, next_field_name)
69
- else
70
- @query.get_field(selected_type, next_field_name)
71
- end
72
-
73
- alias_selections[name] =
74
- if next_field_defn
75
- next_nodes = []
76
- arguments = @query.arguments_for(alias_node, next_field_defn)
77
- arguments = arguments.is_a?(::GraphQL::Execution::Interpreter::Arguments) ? arguments.keyword_arguments : arguments
78
- @ast_nodes.each do |ast_node|
79
- ast_node.selections.each do |selection|
80
- if GraphRubyVersion.after_2_0_13? && GraphRubyVersion.before_2_1_4?
81
- find_selected_nodes(selection, next_field_defn, arguments: arguments, matches: next_nodes)
82
- else
83
- find_selected_nodes(selection, next_field_name, next_field_defn, arguments: arguments, matches: next_nodes)
84
- end
85
- end
86
- end
87
-
88
- if next_nodes.any?
89
- ::GraphQL::Execution::Lookahead.new(query: @query, ast_nodes: next_nodes, field: next_field_defn, owner_type: selected_type)
90
- else
91
- ::GraphQL::Execution::Lookahead::NULL_LOOKAHEAD
92
- end
93
- else
94
- ::GraphQL::Execution::Lookahead::NULL_LOOKAHEAD
95
- end
96
- end
97
-
98
- def alias_selections
99
- return @alias_selections if defined?(@alias_selections)
100
- @alias_selections ||= {}
101
- end
102
-
103
- def lookup_alias_node(nodes, name)
104
- return if nodes.empty?
105
-
106
- nodes.find do |node|
107
- if node.is_a?(GraphQL::Language::Nodes::FragmentSpread)
108
- node = @query.fragments[node.name]
109
- raise("Invariant: Can't look ahead to nonexistent fragment #{node.name} (found: #{@query.fragments.keys})") unless node
110
- end
111
-
112
- return node if node.alias?(name)
113
- child = lookup_alias_node(node.children, name)
114
- return child if child
115
- end
116
- end
117
- end
118
53
  end
119
54
  })
120
55
 
121
56
  # Builds cache key for fragment
122
57
  class CacheKeyBuilder
123
- using RubyNext
124
-
125
58
  class << self
126
59
  def call(**options)
127
60
  new(**options).build
@@ -16,12 +16,10 @@ module GraphQL
16
16
  return fragments.map { |f| [f, f.read] }.to_h
17
17
  end
18
18
 
19
- fragments_to_cache_keys = fragments
20
- .map { |f| [f, f.cache_key] }.to_h
19
+ fragments_to_cache_keys = fragments.map { |f| [f, f.cache_key] }.to_h
21
20
 
22
21
  # Filter out all the cache_keys for fragments with renew_cache: true in their context
23
- cache_keys = fragments_to_cache_keys
24
- .reject { |k, _v| k.context[:renew_cache] == true }.values
22
+ cache_keys = fragments_to_cache_keys.reject { |k, _v| k.context[:renew_cache] == true }.values
25
23
 
26
24
  # If there are cache_keys look up values with read_multi otherwise return an empty hash
27
25
  cache_keys_to_values = if cache_keys.empty?
@@ -30,9 +28,23 @@ module GraphQL
30
28
  FragmentCache.cache_store.read_multi(*cache_keys)
31
29
  end
32
30
 
31
+ if GraphQL::FragmentCache.monitoring_enabled
32
+ begin
33
+ fragments.map do |fragment|
34
+ cache_lookup_event(
35
+ cache_key: fragment.cache_key,
36
+ operation_name: fragment.context.query.operation_name,
37
+ path: fragment.path,
38
+ cache_hit: cache_keys_to_values.key?(fragment.cache_key)
39
+ )
40
+ end
41
+ rescue
42
+ # Allow cache_lookup_event to fail when we do not have all of the requested attributes
43
+ end
44
+ end
45
+
33
46
  # Fragmenst without values or with renew_cache: true in their context will have nil values like the read method
34
- fragments_to_cache_keys
35
- .map { |fragment, cache_key| [fragment, cache_keys_to_values[cache_key]] }.to_h
47
+ fragments_to_cache_keys.map { |fragment, cache_key| [fragment, cache_keys_to_values[cache_key]] }.to_h
36
48
  end
37
49
  end
38
50
 
@@ -87,6 +99,11 @@ module GraphQL
87
99
  def final_value
88
100
  @final_value ||= context.query.result["data"]
89
101
  end
102
+
103
+ def cache_lookup_event(**args)
104
+ # This method can be implemented in your application
105
+ # This provides a mechanism to monitor cache hits for a fragment
106
+ end
90
107
  end
91
108
  end
92
109
  end
@@ -1,24 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- using RubyNext
4
-
5
3
  module GraphQL
6
4
  module FragmentCache
7
5
  module GraphRubyVersion
8
6
  module_function
9
7
 
10
- def before_2_0?
11
- check_graphql_version "< 2.0.0"
12
- end
13
-
14
- def after_2_0_13?
15
- check_graphql_version "> 2.0.13"
16
- end
17
-
18
- def before_2_1_4?
19
- check_graphql_version "< 2.1.4"
20
- end
21
-
22
8
  def after_2_2_5?
23
9
  check_graphql_version "> 2.2.5"
24
10
  end
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- using RubyNext
4
-
5
3
  module GraphQL
6
4
  module FragmentCache
7
5
  # Memory adapter for storing cached fragments
8
6
  class MemoryStore
9
- using RubyNext
10
-
11
7
  class Entry < Struct.new(:value, :expires_at, keyword_init: true)
12
8
  def expired?
13
9
  expires_at && expires_at < Time.now
@@ -26,7 +26,9 @@ module GraphQL
26
26
 
27
27
  if ENV["RACK_ENV"] == "test" || ENV["RAILS_ENV"] == "test"
28
28
  initializer "graphql-fragment_cache" do
29
- config.graphql_fragment_cache.store = if Rails.version.to_f >= 7.0
29
+ config.graphql_fragment_cache.store = if Rails.version.to_f >= 8.0
30
+ [:null_store]
31
+ elsif Rails.version.to_f >= 7.0
30
32
  [:null_store, serializer: :marshal_7_0]
31
33
  else
32
34
  :null_store
@@ -16,6 +16,8 @@ module GraphQL
16
16
  @block = block
17
17
 
18
18
  @lazy_state[:pending_fragments] << @fragment
19
+
20
+ ensure_dataloader_resulution! if @fragment.options[:dataloader]
19
21
  end
20
22
 
21
23
  def resolve
@@ -35,6 +37,15 @@ module GraphQL
35
37
  @query_ctx.fragments << @fragment
36
38
  end
37
39
  end
40
+
41
+ private
42
+
43
+ def ensure_dataloader_resulution!
44
+ return if FragmentCache.cache_store.exist?(@fragment.cache_key)
45
+
46
+ @object_to_cache = @block.call
47
+ @block = nil
48
+ end
38
49
  end
39
50
  end
40
51
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GraphQL
4
4
  module FragmentCache
5
- VERSION = "1.20.5"
5
+ VERSION = "1.21.0"
6
6
  end
7
7
  end
@@ -25,14 +25,13 @@ module GraphQL
25
25
  class << self
26
26
  attr_reader :cache_store
27
27
  attr_accessor :enabled
28
+ attr_accessor :monitoring_enabled
28
29
  attr_accessor :namespace
29
30
  attr_accessor :default_options
30
31
 
31
32
  attr_accessor :skip_cache_when_query_has_errors
32
33
 
33
34
  def use(schema_defn, options = {})
34
- verify_interpreter_and_analysis!(schema_defn)
35
-
36
35
  if GraphRubyVersion.after_2_2_5?
37
36
  schema_defn.trace_with(Schema::Instrumentation::Tracer)
38
37
  else
@@ -69,24 +68,11 @@ module GraphQL
69
68
  def check_graphql_version(predicate)
70
69
  Gem::Dependency.new("graphql", predicate).match?("graphql", GraphQL::VERSION)
71
70
  end
72
-
73
- def verify_interpreter_and_analysis!(schema_defn)
74
- if GraphRubyVersion.before_2_0?
75
- unless schema_defn.interpreter?
76
- raise StandardError,
77
- "GraphQL::Execution::Interpreter should be enabled for fragment caching"
78
- end
79
-
80
- unless schema_defn.analysis_engine == GraphQL::Analysis::AST
81
- raise StandardError,
82
- "GraphQL::Analysis::AST should be enabled for fragment caching"
83
- end
84
- end
85
- end
86
71
  end
87
72
 
88
73
  self.cache_store = MemoryStore.new
89
74
  self.enabled = true
75
+ self.monitoring_enabled = false
90
76
  self.namespace = "graphql"
91
77
  self.default_options = {}
92
78
  self.skip_cache_when_query_has_errors = false
@@ -1,6 +1 @@
1
- require "ruby-next"
2
-
3
- require "ruby-next/language/setup"
4
- RubyNext::Language.setup_gem_load_path(transpile: true)
5
-
6
1
  require "graphql/fragment_cache"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql-fragment_cache
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.20.5
4
+ version: 1.21.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - DmitryTsepelev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-11-02 00:00:00.000000000 Z
11
+ date: 2025-02-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: graphql
@@ -16,28 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 2.0.0
19
+ version: 2.1.4
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 2.0.0
27
- - !ruby/object:Gem::Dependency
28
- name: ruby-next
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: 0.15.0
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: 0.15.0
26
+ version: 2.1.4
41
27
  - !ruby/object:Gem::Dependency
42
28
  name: combustion
43
29
  requirement: !ruby/object:Gem::Requirement
@@ -70,16 +56,16 @@ dependencies:
70
56
  name: sqlite3
71
57
  requirement: !ruby/object:Gem::Requirement
72
58
  requirements:
73
- - - "~>"
59
+ - - ">="
74
60
  - !ruby/object:Gem::Version
75
- version: '1.4'
61
+ version: '0'
76
62
  type: :development
77
63
  prerelease: false
78
64
  version_requirements: !ruby/object:Gem::Requirement
79
65
  requirements:
80
- - - "~>"
66
+ - - ">="
81
67
  - !ruby/object:Gem::Version
82
- version: '1.4'
68
+ version: '0'
83
69
  - !ruby/object:Gem::Dependency
84
70
  name: rake
85
71
  requirement: !ruby/object:Gem::Requirement
@@ -122,20 +108,6 @@ dependencies:
122
108
  - - ">="
123
109
  - !ruby/object:Gem::Version
124
110
  version: '0'
125
- - !ruby/object:Gem::Dependency
126
- name: ruby-next
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - ">="
130
- - !ruby/object:Gem::Version
131
- version: '0.10'
132
- type: :development
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - ">="
137
- - !ruby/object:Gem::Version
138
- version: '0.10'
139
111
  - !ruby/object:Gem::Dependency
140
112
  name: unparser
141
113
  requirement: !ruby/object:Gem::Requirement
@@ -193,8 +165,6 @@ files:
193
165
  - lib/.rbnext/2.3/graphql/fragment_cache/cache_key_builder.rb
194
166
  - lib/.rbnext/2.3/graphql/fragment_cache/memory_store.rb
195
167
  - lib/.rbnext/2.5/graphql/fragment_cache/cacher.rb
196
- - lib/.rbnext/2.7/graphql/fragment_cache/cache_key_builder.rb
197
- - lib/.rbnext/2.7/graphql/fragment_cache/ext/graphql_cache_key.rb
198
168
  - lib/graphql-fragment_cache.rb
199
169
  - lib/graphql/fragment_cache.rb
200
170
  - lib/graphql/fragment_cache/cache_key_builder.rb
@@ -230,14 +200,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
230
200
  requirements:
231
201
  - - ">="
232
202
  - !ruby/object:Gem::Version
233
- version: '3.0'
203
+ version: '3.1'
234
204
  required_rubygems_version: !ruby/object:Gem::Requirement
235
205
  requirements:
236
206
  - - ">="
237
207
  - !ruby/object:Gem::Version
238
208
  version: '0'
239
209
  requirements: []
240
- rubygems_version: 3.4.6
210
+ rubygems_version: 3.5.22
241
211
  signing_key:
242
212
  specification_version: 4
243
213
  summary: Fragment cache for graphql-ruby
@@ -1,219 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "json"
4
- require "digest"
5
-
6
- using RubyNext
7
-
8
- module GraphQL
9
- module FragmentCache
10
- using Ext
11
-
12
- using(Module.new {
13
- refine Array do
14
- def traverse_argument(argument)
15
- return argument unless argument.is_a?(GraphQL::Schema::InputObject)
16
-
17
- "{#{argument.map { |_1, _2| "#{_1}:#{traverse_argument(_2)}" }.sort.join(",")}}"
18
- end
19
-
20
- def to_selections_key
21
- map { |val|
22
- children = val.selections.empty? ? "" : "[#{val.selections.to_selections_key}]"
23
-
24
- field_name = val.field.name
25
- field_alias = val.ast_nodes.map(&:alias).join
26
- field_name = "#{field_alias}:#{field_name}" unless field_alias.empty?
27
-
28
- unless val.arguments.empty?
29
- args = val.arguments.map { |_1, _2| "#{_1}:#{traverse_argument(_2)}" }.sort.join(",")
30
- field_name += "(#{args})"
31
- end
32
-
33
- "#{field_name}#{children}"
34
- }.join(".")
35
- end
36
- end
37
-
38
- refine ::GraphQL::Language::Nodes::AbstractNode do
39
- def alias?(_)
40
- false
41
- end
42
- end
43
-
44
- refine ::GraphQL::Language::Nodes::Field do
45
- def alias?(val)
46
- self.alias == val
47
- end
48
- end
49
-
50
- refine ::GraphQL::Execution::Lookahead do
51
- def selection_with_alias(name, **kwargs)
52
- return selection(name, **kwargs) if selects?(name, **kwargs)
53
- alias_selection(name, **kwargs)
54
- end
55
-
56
- def alias_selection(name, selected_type: @selected_type, arguments: nil)
57
- return alias_selections[name] if alias_selections.key?(name)
58
-
59
- alias_node = lookup_alias_node(ast_nodes, name)
60
- return ::GraphQL::Execution::Lookahead::NULL_LOOKAHEAD unless alias_node
61
-
62
- next_field_name = alias_node.name
63
-
64
- # From https://github.com/rmosolgo/graphql-ruby/blob/1a9a20f3da629e63ea8e5ee8400be82218f9edc3/lib/graphql/execution/lookahead.rb#L91
65
- next_field_defn =
66
- if GraphQL::FragmentCache.graphql_ruby_before_2_0?
67
- get_class_based_field(selected_type, next_field_name)
68
- else
69
- @query.get_field(selected_type, next_field_name)
70
- end
71
-
72
- alias_selections[name] =
73
- if next_field_defn
74
- next_nodes = []
75
- arguments = @query.arguments_for(alias_node, next_field_defn)
76
- arguments = arguments.is_a?(::GraphQL::Execution::Interpreter::Arguments) ? arguments.keyword_arguments : arguments
77
- @ast_nodes.each do |ast_node|
78
- ast_node.selections.each do |selection|
79
- if GraphQL::FragmentCache.graphql_ruby_after_2_0_13? && GraphQL::FragmentCache.graphql_ruby_before_2_1_4?
80
- find_selected_nodes(selection, next_field_defn, arguments: arguments, matches: next_nodes)
81
- else
82
- find_selected_nodes(selection, next_field_name, next_field_defn, arguments: arguments, matches: next_nodes)
83
- end
84
- end
85
- end
86
-
87
- if next_nodes.any?
88
- ::GraphQL::Execution::Lookahead.new(query: @query, ast_nodes: next_nodes, field: next_field_defn, owner_type: selected_type)
89
- else
90
- ::GraphQL::Execution::Lookahead::NULL_LOOKAHEAD
91
- end
92
- else
93
- ::GraphQL::Execution::Lookahead::NULL_LOOKAHEAD
94
- end
95
- end
96
-
97
- def alias_selections
98
- return @alias_selections if defined?(@alias_selections)
99
- @alias_selections ||= {}
100
- end
101
-
102
- def lookup_alias_node(nodes, name)
103
- return if nodes.empty?
104
-
105
- nodes.find do |node|
106
- if node.is_a?(GraphQL::Language::Nodes::FragmentSpread)
107
- node = @query.fragments[node.name]
108
- raise("Invariant: Can't look ahead to nonexistent fragment #{node.name} (found: #{@query.fragments.keys})") unless node
109
- end
110
-
111
- return node if node.alias?(name)
112
- child = lookup_alias_node(node.children, name)
113
- return child if child
114
- end
115
- end
116
- end
117
- })
118
-
119
- # Builds cache key for fragment
120
- class CacheKeyBuilder
121
- using RubyNext
122
-
123
- class << self
124
- def call(**options)
125
- new(**options).build
126
- end
127
- end
128
-
129
- attr_reader :query, :path, :object, :schema
130
-
131
- def initialize(query:, path:, object: nil, **options)
132
- @object = object
133
- @query = query
134
- @schema = query.schema
135
- @path = path
136
- @options = options
137
- end
138
-
139
- def build
140
- key_parts = [
141
- GraphQL::FragmentCache.namespace,
142
- simple_path_cache_key,
143
- implicit_cache_key,
144
- object_cache_key
145
- ]
146
-
147
- key_parts
148
- .compact
149
- .map { |key_part| key_part.tr("/", "-") }
150
- .join("/")
151
- end
152
-
153
- private
154
-
155
- def implicit_cache_key
156
- Digest::SHA1.hexdigest("#{schema_cache_key}/#{query_cache_key}")
157
- end
158
-
159
- def schema_cache_key
160
- @options.fetch(:schema_cache_key) { schema.schema_cache_key }
161
- end
162
-
163
- def query_cache_key
164
- @options.fetch(:query_cache_key) { "#{path_cache_key}[#{selections_cache_key}]" }
165
- end
166
-
167
- def selections_cache_key
168
- current_root =
169
- path.reduce(query.lookahead) { |lkhd, field_name|
170
- # Handle cached fields inside collections:
171
- next lkhd if field_name.is_a?(Integer)
172
-
173
- lkhd.selection_with_alias(field_name)
174
- }
175
-
176
- current_root.selections.to_selections_key
177
- end
178
-
179
- def simple_path_cache_key
180
- return if path_cache_key.nil?
181
-
182
- path_cache_key.split("(").first
183
- end
184
-
185
- def path_cache_key
186
- @path_cache_key ||= @options.fetch(:path_cache_key) do
187
- lookahead = query.lookahead
188
-
189
- path.map { |field_name|
190
- # Handle cached fields inside collections:
191
- next field_name if field_name.is_a?(Integer)
192
-
193
- lookahead = lookahead.selection_with_alias(field_name)
194
- raise "Failed to look ahead the field: #{field_name}" if lookahead.is_a?(::GraphQL::Execution::Lookahead::NullLookahead)
195
-
196
- next lookahead.field.name if lookahead.arguments.empty?
197
-
198
- args = lookahead.arguments.map { |_1, _2| "#{_1}:#{traverse_argument(_2)}" }.sort.join(",")
199
- "#{lookahead.field.name}(#{args})"
200
- }.join("/")
201
- end
202
- end
203
-
204
- def traverse_argument(argument)
205
- return argument unless argument.is_a?(GraphQL::Schema::InputObject)
206
-
207
- "{#{argument.map { |_1, _2| "#{_1}:#{traverse_argument(_2)}" }.sort.join(",")}}"
208
- end
209
-
210
- def object_cache_key
211
- @options[:object_cache_key] || object_key(object)
212
- end
213
-
214
- def object_key(obj)
215
- obj&._graphql_cache_key
216
- end
217
- end
218
- end
219
- end
@@ -1,93 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module GraphQL
4
- module FragmentCache
5
- module Ext
6
- # Adds #_graphql_cache_key method to Object,
7
- # which just call #graphql_cache_key or #cache_key.
8
- #
9
- # For other core classes returns string representation.
10
- #
11
- # Raises ArgumentError otherwise.
12
- #
13
- # We use a refinement to avoid case/if statements for type checking
14
- refine Object do
15
- def _graphql_cache_key
16
- return graphql_cache_key if respond_to?(:graphql_cache_key)
17
- return cache_key if respond_to?(:cache_key)
18
- return to_a._graphql_cache_key if respond_to?(:to_a)
19
-
20
- to_s
21
- end
22
- end
23
-
24
- refine Array do
25
- def _graphql_cache_key
26
- map { |_1| _1._graphql_cache_key }.join("/")
27
- end
28
- end
29
-
30
- refine NilClass do
31
- def _graphql_cache_key
32
- ""
33
- end
34
- end
35
-
36
- refine TrueClass do
37
- def _graphql_cache_key
38
- "t"
39
- end
40
- end
41
-
42
- refine FalseClass do
43
- def _graphql_cache_key
44
- "f"
45
- end
46
- end
47
-
48
- refine String do
49
- def _graphql_cache_key
50
- self
51
- end
52
- end
53
-
54
- refine Symbol do
55
- def _graphql_cache_key
56
- to_s
57
- end
58
- end
59
-
60
- if RUBY_PLATFORM.match?(/java/i)
61
- refine Integer do
62
- def _graphql_cache_key
63
- to_s
64
- end
65
- end
66
-
67
- refine Float do
68
- def _graphql_cache_key
69
- to_s
70
- end
71
- end
72
- else
73
- refine Numeric do
74
- def _graphql_cache_key
75
- to_s
76
- end
77
- end
78
- end
79
-
80
- refine Time do
81
- def _graphql_cache_key
82
- to_s
83
- end
84
- end
85
-
86
- refine Module do
87
- def _graphql_cache_key
88
- name
89
- end
90
- end
91
- end
92
- end
93
- end