graphql_lazy_load 0.2.0 → 0.3.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 +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +40 -57
- data/gems/graphql_lazy_load-0.2.0.gem +0 -0
- data/lib/graphql_lazy_load/active_record_relation.rb +80 -0
- data/lib/graphql_lazy_load/custom.rb +69 -0
- data/lib/graphql_lazy_load/object_helper.rb +22 -0
- data/lib/graphql_lazy_load/version.rb +1 -1
- data/lib/graphql_lazy_load.rb +3 -135
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 30a2e056b22a38d09ee95c801c2b40b5ca5ca84b5fc6515dfa3561bf0e1457a8
|
4
|
+
data.tar.gz: 6ab1efe390d966b27dabe05b813c6fb3efcab08456fdcd2a7f699477e05ed53b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f1af93639f764829f3f7b452865e6e3e8a37bc3197ae0f367349aa71dec04fa939ef7f71c6b31c775f07226766cdc26e9a35bcac0abf06e4d9a44bad4392fb84
|
7
|
+
data.tar.gz: 9d208fdccddd90fa56a273531f6d4c639eabf640ab8d84ab478f036cc84c69263f9ae6d69798c66812d67f7abdc2a4194d2c5e5a539fef861c183019677dee26
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -6,7 +6,7 @@ Lazy executor for activerecord associations and graphql gem.
|
|
6
6
|
|
7
7
|
GraphqlLazyLoad requires ActiveRecord >= 4.1.16 and Graphql >= 1.3.0. To use add this line to your application's Gemfile:
|
8
8
|
```ruby
|
9
|
-
gem 'graphql_lazy_load', '~> 0.
|
9
|
+
gem 'graphql_lazy_load', '~> 0.3.0'
|
10
10
|
```
|
11
11
|
Then run `bundle install`.
|
12
12
|
|
@@ -15,60 +15,57 @@ Or install it yourself as:
|
|
15
15
|
$ gem install graphql_lazy_load
|
16
16
|
|
17
17
|
## Usage
|
18
|
-
|
19
|
-
To use, first add the executor (`GraphqlLazyLoad::ActiveRecordRelation`) to graphqls schema:
|
18
|
+
To use, first add the executor (`GraphqlLazyLoad::ActiveRecordRelation` and/or `GraphqlLazyLoad::Custom`) to graphqls schema:
|
20
19
|
```ruby
|
21
20
|
class MySchema < GraphQL::Schema
|
22
21
|
mutation(Types::MutationType)
|
23
22
|
query(Types::QueryType)
|
24
23
|
|
25
24
|
lazy_resolve(GraphqlLazyLoad::ActiveRecordRelation, :result)
|
25
|
+
lazy_resolve(GraphqlLazyLoad::Custom, :result)
|
26
26
|
end
|
27
27
|
```
|
28
28
|
|
29
|
-
Now you can start using it!
|
29
|
+
Now you can start using it!
|
30
|
+
|
31
|
+
The easiest thing to do is to extend the object helper method in the `Types::BaseObject` (or where you want to use the methods):
|
30
32
|
```ruby
|
31
|
-
|
32
|
-
|
33
|
-
|
33
|
+
module Types
|
34
|
+
class BaseObject < GraphQL::Schema::Object
|
35
|
+
extend GraphqlLazyLoad::ObjectHelper
|
36
|
+
end
|
34
37
|
end
|
35
38
|
```
|
36
|
-
### Custom
|
37
|
-
If you want to lazy load a non active record association you can use the `Custom` loader, first add the executor (`GraphqlLazyLoad::Custom`) to graphqls schema (both can be in the schema together):
|
38
|
-
```ruby
|
39
|
-
class MySchema < GraphQL::Schema
|
40
|
-
mutation(Types::MutationType)
|
41
|
-
query(Types::QueryType)
|
42
39
|
|
43
|
-
|
44
|
-
|
40
|
+
### ActiveRecordRelation Syntax
|
41
|
+
```ruby
|
42
|
+
field :field_name, Types::AssociationType, null: false
|
43
|
+
lazy_load_association(:field_name)
|
45
44
|
```
|
45
|
+
If the association does not match the field name you can pass it `lazy_load_association(:field_name, association: :association_name)`
|
46
46
|
|
47
|
-
|
47
|
+
### Custom Syntax
|
48
48
|
```ruby
|
49
49
|
field :field_name, Types::AssociationType, null: false
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
acc
|
56
|
-
end
|
50
|
+
lazy_load_custom(:field_name, :id) do |field_name_ids|
|
51
|
+
# return hash of field_name_id => association_name, ...
|
52
|
+
AssociationName.where(id: ids).reduce({}) do |acc, value|
|
53
|
+
acc[value.id] = value
|
54
|
+
acc
|
57
55
|
end
|
58
56
|
end
|
59
57
|
```
|
60
|
-
|
58
|
+
|
61
59
|
## Examples
|
62
60
|
If you have two models `Team` which can have many `Player`s. To lazy load players from teams do the following:
|
63
61
|
### ActiveRecordRelation
|
64
62
|
```ruby
|
65
63
|
module Types
|
66
64
|
class TeamType < Types::BaseObject
|
65
|
+
# extend GraphqlLazyLoad::ObjectHelper # uncomment if not extended in Types::BaseObject
|
67
66
|
...
|
68
67
|
field :players, [Types::PlayerType], null: false
|
69
|
-
|
70
|
-
GraphqlLazyLoad::ActiveRecordRelation.new(self, :players)
|
71
|
-
end
|
68
|
+
lazy_load_association(:players)
|
72
69
|
end
|
73
70
|
end
|
74
71
|
```
|
@@ -78,9 +75,7 @@ module Types
|
|
78
75
|
class PlayerType < Types::BaseObject
|
79
76
|
...
|
80
77
|
field :team, Types::TeamType, null: false
|
81
|
-
|
82
|
-
GraphqlLazyLoad::ActiveRecordRelation.new(self, :team)
|
83
|
-
end
|
78
|
+
lazy_load_association(:team)
|
84
79
|
end
|
85
80
|
end
|
86
81
|
```
|
@@ -90,14 +85,9 @@ module Types
|
|
90
85
|
class TeamType < Types::BaseObject
|
91
86
|
...
|
92
87
|
field :players, [Types::PlayerType], null: false
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
Player.where(team_id: team_ids).reduce({}) do |acc, player|
|
97
|
-
(acc[player.team_id] ||= []).push(player)
|
98
|
-
acc
|
99
|
-
end
|
100
|
-
end
|
88
|
+
lazy_load_custom(:players, :id) do |team_ids|
|
89
|
+
# return a hash with key team_id => [player,...]
|
90
|
+
Player.where(team_id: team_ids).group_by(&:team_id)
|
101
91
|
end
|
102
92
|
end
|
103
93
|
end
|
@@ -108,13 +98,10 @@ module Types
|
|
108
98
|
class PlayerType < Types::BaseObject
|
109
99
|
...
|
110
100
|
field :team, Types::TeamType, null: false
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
acc[team.id] = team
|
116
|
-
acc
|
117
|
-
end
|
101
|
+
lazy_load_custom(:field_name, :team_id) do |team_ids|
|
102
|
+
# return a hash with key team_id => team
|
103
|
+
Team.where(id: team_ids).each_with_object({}) do |team, acc|
|
104
|
+
acc[team.id] = team
|
118
105
|
end
|
119
106
|
end
|
120
107
|
end
|
@@ -122,7 +109,7 @@ end
|
|
122
109
|
```
|
123
110
|
|
124
111
|
### Scoping
|
125
|
-
The great thing is you can pass
|
112
|
+
The great thing is you can pass params. So for the example above if you want to allow sorting (or query, paging, etc) on player do the following.
|
126
113
|
### ActiveRecordRelation
|
127
114
|
```ruby
|
128
115
|
module Types
|
@@ -131,10 +118,10 @@ module Types
|
|
131
118
|
field :players, [Types::PlayerType], null: false do
|
132
119
|
argument :order, String, required: false
|
133
120
|
end
|
134
|
-
|
121
|
+
lazy_load_association(:players) do |order: nil|
|
135
122
|
scope = Player.all
|
136
123
|
scope = scope.sort(order.underscore) if order
|
137
|
-
|
124
|
+
scope
|
138
125
|
end
|
139
126
|
end
|
140
127
|
end
|
@@ -148,15 +135,11 @@ module Types
|
|
148
135
|
field :players, [Types::PlayerType], null: false do
|
149
136
|
argument :order, String, required: false
|
150
137
|
end
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
(acc[player.team_id] ||= []).push(player)
|
157
|
-
acc
|
158
|
-
end
|
159
|
-
end
|
138
|
+
lazy_load_custom(:players, :id) do |team_ids, order: nil|
|
139
|
+
# return a hash with key team_id => [player,...]
|
140
|
+
query = Player.where(team_id: team_ids)
|
141
|
+
query = query.order(params[:order].underscore) if params[:order]
|
142
|
+
query.group_by(&:team_id)
|
160
143
|
end
|
161
144
|
end
|
162
145
|
end
|
Binary file
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphqlLazyLoad
|
4
|
+
class ActiveRecordRelation
|
5
|
+
def initialize(type, association, **params, &block)
|
6
|
+
object_class = type.object.class
|
7
|
+
context_key = [type.class, object_class, association]
|
8
|
+
@object_id = type.object.id
|
9
|
+
@association = association
|
10
|
+
|
11
|
+
# Initialize the loading state for this query,
|
12
|
+
# or get the previously-initiated state
|
13
|
+
@lazy = type.context[context_key] ||= {
|
14
|
+
objects_to_load: Set.new,
|
15
|
+
ids: Set.new, # use ids cause cant compare objects
|
16
|
+
results: {},
|
17
|
+
params: params,
|
18
|
+
block: block,
|
19
|
+
}
|
20
|
+
# Register this to be loaded later unless we've already queued or loaded it
|
21
|
+
return if already_loaded_or_queued?
|
22
|
+
# use copy of object so it doesnt add preload to associations.
|
23
|
+
# this is so associations dont get loaded to object passed so different scopes get reloaded
|
24
|
+
lazy_objects.add(object_class.new(type.object.attributes))
|
25
|
+
lazy_ids.add(object_id)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Return the loaded record, hitting the database if needed
|
29
|
+
def result
|
30
|
+
if !already_loaded? && any_to_load?
|
31
|
+
ActiveRecord::Associations::Preloader.new.preload(lazy_objects.to_a, association, scope)
|
32
|
+
lazy_objects.each do |object|
|
33
|
+
lazy_results[object.id] = object.send(association)
|
34
|
+
end
|
35
|
+
lazy_objects.clear
|
36
|
+
end
|
37
|
+
lazy_results[object_id]
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
attr_reader :object_id, :association
|
42
|
+
|
43
|
+
def scope
|
44
|
+
return nil unless lazy_block
|
45
|
+
lazy_block.call(lazy_params)
|
46
|
+
end
|
47
|
+
|
48
|
+
def lazy_params
|
49
|
+
@lazy[:params]
|
50
|
+
end
|
51
|
+
|
52
|
+
def lazy_block
|
53
|
+
@lazy[:block]
|
54
|
+
end
|
55
|
+
|
56
|
+
def already_loaded_or_queued?
|
57
|
+
lazy_ids.include?(object_id)
|
58
|
+
end
|
59
|
+
|
60
|
+
def already_loaded?
|
61
|
+
lazy_results.key?(object_id)
|
62
|
+
end
|
63
|
+
|
64
|
+
def any_to_load?
|
65
|
+
lazy_objects.any?
|
66
|
+
end
|
67
|
+
|
68
|
+
def lazy_ids
|
69
|
+
@lazy[:ids]
|
70
|
+
end
|
71
|
+
|
72
|
+
def lazy_objects
|
73
|
+
@lazy[:objects_to_load]
|
74
|
+
end
|
75
|
+
|
76
|
+
def lazy_results
|
77
|
+
@lazy[:results]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphqlLazyLoad
|
4
|
+
class Custom
|
5
|
+
def initialize(type, unique_identifier, value, default_value: nil, **params, &block)
|
6
|
+
context_key = [type.class, unique_identifier, params]
|
7
|
+
@value = value
|
8
|
+
# Initialize the loading state for this query,
|
9
|
+
# or get the previously-initiated state
|
10
|
+
@lazy = type.context[context_key] ||= {
|
11
|
+
values: Set.new,
|
12
|
+
results: Hash.new(default_value),
|
13
|
+
params: params,
|
14
|
+
block: block,
|
15
|
+
}
|
16
|
+
# Register this to be loaded later unless we've already queued or loaded it
|
17
|
+
return if already_loaded_or_queued?
|
18
|
+
lazy_values.add(value)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Return the loaded record, hitting the database if needed
|
22
|
+
def result
|
23
|
+
if not_already_loaded? && any_to_load?
|
24
|
+
lazy_results.merge!(block_results)
|
25
|
+
lazy_values.clear
|
26
|
+
end
|
27
|
+
lazy_results[value]
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
attr_reader :value
|
32
|
+
|
33
|
+
def block_results
|
34
|
+
lazy_block.call(lazy_values.to_a, lazy_params)
|
35
|
+
end
|
36
|
+
|
37
|
+
def lazy_params
|
38
|
+
@lazy[:params]
|
39
|
+
end
|
40
|
+
|
41
|
+
def lazy_block
|
42
|
+
@lazy[:block]
|
43
|
+
end
|
44
|
+
|
45
|
+
def already_loaded_or_queued?
|
46
|
+
lazy_values.include?(value) || lazy_results.key?(value)
|
47
|
+
end
|
48
|
+
|
49
|
+
def not_already_loaded?
|
50
|
+
!lazy_results.key?(value)
|
51
|
+
end
|
52
|
+
|
53
|
+
def any_to_load?
|
54
|
+
lazy_values.any?
|
55
|
+
end
|
56
|
+
|
57
|
+
def lazy_values_array
|
58
|
+
lazy_values.to_a
|
59
|
+
end
|
60
|
+
|
61
|
+
def lazy_values
|
62
|
+
@lazy[:values]
|
63
|
+
end
|
64
|
+
|
65
|
+
def lazy_results
|
66
|
+
@lazy[:results]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphqlLazyLoad
|
4
|
+
module ObjectHelper
|
5
|
+
def lazy_load_custom(method, value, default_value: nil, &block)
|
6
|
+
define_method(method) do |**params|
|
7
|
+
Custom.new(self, method, object.send(value), params.merge(default_value: default_value)) do |*options|
|
8
|
+
instance_exec(*options, &block)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def lazy_load_association(method, association: method, &block)
|
14
|
+
define_method(method) do |**params|
|
15
|
+
return ActiveRecordRelation.new(self, association, **params) unless block
|
16
|
+
ActiveRecordRelation.new(self, association, **params) do |*options|
|
17
|
+
instance_exec(*options, &block)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/graphql_lazy_load.rb
CHANGED
@@ -1,143 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "graphql_lazy_load/version"
|
4
|
+
require "graphql_lazy_load/object_helper"
|
5
|
+
require "graphql_lazy_load/custom"
|
6
|
+
require "graphql_lazy_load/active_record_relation"
|
4
7
|
require "active_record"
|
5
8
|
|
6
9
|
module GraphqlLazyLoad
|
7
|
-
class Custom
|
8
|
-
def initialize(type, unique_identifier, value, **params, &block)
|
9
|
-
@unique_identifier = unique_identifier
|
10
|
-
@params = params
|
11
|
-
@block = block
|
12
|
-
@value = value
|
13
|
-
# Initialize the loading state for this query,
|
14
|
-
# or get the previously-initiated state
|
15
|
-
# scope cant be used as a hash key because when .hash is called on diff
|
16
|
-
# for ~same~ scopes its diff every time but scope == scope will return true if ~same~
|
17
|
-
@lazy = type.context[context_key] ||= {
|
18
|
-
values_to_load: Set.new,
|
19
|
-
ids: Set.new,
|
20
|
-
results: {}
|
21
|
-
}
|
22
|
-
# Register this to be loaded later unless we've already queued or loaded it
|
23
|
-
return if already_loaded_or_queued?
|
24
|
-
lazy_values.add(value)
|
25
|
-
lazy_ids.add(value)
|
26
|
-
end
|
27
10
|
|
28
|
-
# Return the loaded record, hitting the database if needed
|
29
|
-
def result
|
30
|
-
if !already_loaded? && any_to_load?
|
31
|
-
lazy_results.merge!(block_results)
|
32
|
-
lazy_values.clear
|
33
|
-
end
|
34
|
-
lazy_results[value]
|
35
|
-
end
|
36
|
-
|
37
|
-
private
|
38
|
-
attr_reader :unique_identifier, :params, :value
|
39
|
-
|
40
|
-
def block_results
|
41
|
-
@block.call(lazy_values, params)
|
42
|
-
end
|
43
|
-
|
44
|
-
def context_key
|
45
|
-
[unique_identifier, params]
|
46
|
-
end
|
47
|
-
|
48
|
-
def already_loaded_or_queued?
|
49
|
-
lazy_ids.include?(object_id)
|
50
|
-
end
|
51
|
-
|
52
|
-
def already_loaded?
|
53
|
-
lazy_results.key?(object_id)
|
54
|
-
end
|
55
|
-
|
56
|
-
def any_to_load?
|
57
|
-
lazy_values.any?
|
58
|
-
end
|
59
|
-
|
60
|
-
def lazy_ids
|
61
|
-
@lazy[:ids]
|
62
|
-
end
|
63
|
-
|
64
|
-
def lazy_values
|
65
|
-
@lazy[:values_to_load]
|
66
|
-
end
|
67
|
-
|
68
|
-
def lazy_results
|
69
|
-
@lazy[:results]
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
class ActiveRecordRelation
|
74
|
-
def initialize(type, association, scope: nil)
|
75
|
-
@object_class = type.object.class
|
76
|
-
@object_id = type.object.id
|
77
|
-
@association = association
|
78
|
-
@scope = scope
|
79
|
-
# Initialize the loading state for this query,
|
80
|
-
# or get the previously-initiated state
|
81
|
-
# scope cant be used as a hash key because when .hash is called on diff
|
82
|
-
# for ~same~ scopes its diff every time but scope == scope will return true if ~same~
|
83
|
-
@lazy = (type.context[context_key] ||= []).find { |c| c[:scope] == scope }
|
84
|
-
unless @lazy
|
85
|
-
@lazy = {
|
86
|
-
objects_to_load: Set.new,
|
87
|
-
ids: Set.new,
|
88
|
-
results: {}
|
89
|
-
}
|
90
|
-
type.context[context_key].push(@lazy)
|
91
|
-
end
|
92
|
-
# Register this to be loaded later unless we've already queued or loaded it
|
93
|
-
return if already_loaded_or_queued?
|
94
|
-
# use copy of object so it doesnt add preload to associations.
|
95
|
-
# this is so associations dont get loaded to object passed so different scopes get reloaded
|
96
|
-
lazy_objects.add(object_class.new(type.object.attributes))
|
97
|
-
lazy_ids.add(object_id)
|
98
|
-
end
|
99
|
-
|
100
|
-
# Return the loaded record, hitting the database if needed
|
101
|
-
def result
|
102
|
-
if !already_loaded? && any_to_load?
|
103
|
-
ActiveRecord::Associations::Preloader.new.preload(lazy_objects.to_a, association, scope)
|
104
|
-
lazy_objects.each do |object|
|
105
|
-
lazy_results[object.id] = object.send(association)
|
106
|
-
end
|
107
|
-
lazy_objects.clear
|
108
|
-
end
|
109
|
-
lazy_results[object_id]
|
110
|
-
end
|
111
|
-
|
112
|
-
private
|
113
|
-
attr_reader :object_class, :object_id, :association, :scope
|
114
|
-
|
115
|
-
def context_key
|
116
|
-
[object_class, association]
|
117
|
-
end
|
118
|
-
|
119
|
-
def already_loaded_or_queued?
|
120
|
-
lazy_ids.include?(object_id)
|
121
|
-
end
|
122
|
-
|
123
|
-
def already_loaded?
|
124
|
-
lazy_results.key?(object_id)
|
125
|
-
end
|
126
|
-
|
127
|
-
def any_to_load?
|
128
|
-
lazy_objects.any?
|
129
|
-
end
|
130
|
-
|
131
|
-
def lazy_ids
|
132
|
-
@lazy[:ids]
|
133
|
-
end
|
134
|
-
|
135
|
-
def lazy_objects
|
136
|
-
@lazy[:objects_to_load]
|
137
|
-
end
|
138
|
-
|
139
|
-
def lazy_results
|
140
|
-
@lazy[:results]
|
141
|
-
end
|
142
|
-
end
|
143
11
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql_lazy_load
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonathon Gardner
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-09-
|
11
|
+
date: 2019-09-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -141,8 +141,12 @@ files:
|
|
141
141
|
- bin/console
|
142
142
|
- bin/setup
|
143
143
|
- gems/graphql_lazy_load-0.1.0.gem
|
144
|
+
- gems/graphql_lazy_load-0.2.0.gem
|
144
145
|
- graphql_lazy_load.gemspec
|
145
146
|
- lib/graphql_lazy_load.rb
|
147
|
+
- lib/graphql_lazy_load/active_record_relation.rb
|
148
|
+
- lib/graphql_lazy_load/custom.rb
|
149
|
+
- lib/graphql_lazy_load/object_helper.rb
|
146
150
|
- lib/graphql_lazy_load/version.rb
|
147
151
|
homepage: http://github.com/jonathongardner/graphql_lazy_load
|
148
152
|
licenses:
|