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 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: