graphql-fragment_cache 1.20.5 → 1.21.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: 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