graphql_lazy_load 0.2.0 → 0.3.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: 724af5e1b3fe1f2fbff97284fe54f2b20d9ddd9e3dddd3932c2f5b913c678873
4
- data.tar.gz: adfcdb624948134a38390d7470898d8d038982dd917d76dda82eb898f3ffaadd
3
+ metadata.gz: 30a2e056b22a38d09ee95c801c2b40b5ca5ca84b5fc6515dfa3561bf0e1457a8
4
+ data.tar.gz: 6ab1efe390d966b27dabe05b813c6fb3efcab08456fdcd2a7f699477e05ed53b
5
5
  SHA512:
6
- metadata.gz: 81e56ea916ed8ae3173bd6629b037960f56f0edcb5031b64d7ad12a6c38ea1a293d98bca839955311adee539f300b1a4a7ab20a8a07ea3cc7394aaa0e2005705
7
- data.tar.gz: 6062acf99ebc505293eb69bd5d917048f89927aa5be346cb8d86440fd70c05dbe370b193c007f1d00f3c6ef1a78d393a63955c3d393402432074316c2c776310
6
+ metadata.gz: f1af93639f764829f3f7b452865e6e3e8a37bc3197ae0f367349aa71dec04fa939ef7f71c6b31c775f07226766cdc26e9a35bcac0abf06e4d9a44bad4392fb84
7
+ data.tar.gz: 9d208fdccddd90fa56a273531f6d4c639eabf640ab8d84ab478f036cc84c69263f9ae6d69798c66812d67f7abdc2a4194d2c5e5a539fef861c183019677dee26
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- graphql_lazy_load (0.2.0)
4
+ graphql_lazy_load (0.3.0)
5
5
  activerecord (>= 4.1.16)
6
6
  graphql (>= 1.3.0)
7
7
 
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.2.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
- ### ActiveRecordRelation
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! Wherever you have an association, the syntax is:
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
- field :field_name, Types::AssociationType, null: false
32
- def field_name
33
- GraphqlLazyLoad::ActiveRecordRelation.new(self, :association_name)
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
- lazy_resolve(GraphqlLazyLoad::Custom, :result)
44
- end
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
- Now you can start using it! Wherever you have a something to lazy load, the syntax is:
47
+ ### Custom Syntax
48
48
  ```ruby
49
49
  field :field_name, Types::AssociationType, null: false
50
- def field_name
51
- GraphqlLazyLoad::Custom.new(self, :association_name, object.id) do |ids|
52
- # return a hash with key matching object.id
53
- AssociationName.where(id: ids).reduce({}) do |acc, value|
54
- acc[value.id] = value
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
- Values passed to the `Custom` initializer are `self`, `unique_identifier`, `unique_id` (gets passed to block, and is used to retrieve the values), you can also pass an optional value `params` (gets passed as second argument to block). It is important to note that data is grouped by `unique_identifier` and `params`.
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
- def players
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
- def team
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
- def players
94
- GraphqlLazyLoad::Custom.new(self, :players, object.id) do |team_ids|
95
- # return a hash with key matching object.id
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
- def team
112
- GraphqlLazyLoad::Custom.new(self, :team, object.team_id) do |team_ids|
113
- # return a hash with key matching object.team_id
114
- Team.where(id: team_ids).reduce({}) do |acc, team|
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 scopes/params! So for the example above if you want to allow sorting (or query, paging, etc) do the following.
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
- def players(order: nil)
121
+ lazy_load_association(:players) do |order: nil|
135
122
  scope = Player.all
136
123
  scope = scope.sort(order.underscore) if order
137
- GraphqlLazyLoad::ActiveRecordRelation.new(self, :players, scope: scope)
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
- def players(order: nil)
152
- GraphqlLazyLoad::Custom.new(self, :players, object.id, {order: order}) do |team_ids, params|
153
- query = Player.where(team_id: team_ids)
154
- query = query.order(params[:order].underscore) if params[:order]
155
- query.reduce({}) do |acc, player|
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GraphqlLazyLoad
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
@@ -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.2.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-14 00:00:00.000000000 Z
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: