graphql-sources 0.2.0 → 0.5.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: 1d3f8fb917c9bbe477e31a14294750e1f7bca546bd8321f1e802eb654b083888
4
- data.tar.gz: 8dfe477f1139b1c2fd44cf5627c6c2d0415cb4f62c97f45c6a924102a3594661
3
+ metadata.gz: e99e1d718952f3ee55b0a0b37c6f4f9f99789f4740243a6dd7995bee2b5fa6a4
4
+ data.tar.gz: 4f082d61109e02db43b06b2ff345dd2fdc697f8a6dc30336b7e6226e352a5e79
5
5
  SHA512:
6
- metadata.gz: 83f0d540b272203c2c7ec315472ca27310683961e71857127a16f4574fcdadb0dab40cf385d0c6f92f80c14523e116c839f57acdd080474c5fa2171a4420833e
7
- data.tar.gz: ae31236645fc987acfd875d3056f2aa32e35925afbf7e7e95544cb3014cbcc90bcfda2a4fefa796ce1a02f3ac67b0cfaef7b85a331f8045ffe29cc20114379b2
6
+ metadata.gz: 58202b58608b54a184ed5831672ebafbd39b216cee19908139b4c9ee6e2874197ed5609fcb2bcd592074e101e77ddce8b1e250db5777c55427a37e7f19abc7be
7
+ data.tar.gz: ee4949a68c77cf7ea0b945f4f79d4a5ba6de9e491c64abb472f8091b01c8181512beafbaa16ca66bbecd8cc64b33c8891d2b3d2589659610ad0149c29dfdf6e0
data/README.md CHANGED
@@ -78,6 +78,46 @@ WHERE "comments"."user_id" IN (...)
78
78
  ORDER BY "comments"."id"
79
79
  ```
80
80
 
81
+ ### Loading `has_one_attached` Associations
82
+
83
+ ```ruby
84
+ class User
85
+ has_one_attached :avatar
86
+ end
87
+ ```
88
+
89
+ ```ruby
90
+ class UserType < GraphQL::Schema::Object
91
+ field :avatar, AttachedType, null: false
92
+
93
+ def avatar
94
+ dataloader
95
+ .with(GraphQL::Sources::ActiveStorageHasOneAttached, :avatar)
96
+ .load(object)
97
+ end
98
+ end
99
+ ```
100
+
101
+ ### Loading `has_many_attached` Associations
102
+
103
+ ```ruby
104
+ class User
105
+ has_many_attached :photos
106
+ end
107
+ ```
108
+
109
+ ```ruby
110
+ class UserType < GraphQL::Schema::Object
111
+ field :photos, [AttachedType], null: false
112
+
113
+ def photos
114
+ dataloader
115
+ .with(GraphQL::Sources::ActiveStorageHasOneAttached, :photos)
116
+ .load(object)
117
+ end
118
+ end
119
+ ```
120
+
81
121
  ### Loading Counts
82
122
 
83
123
  ```ruby
@@ -111,6 +151,26 @@ WHERE "likes"."post_id" IN (1, 2, 3, ...)
111
151
  GROUP BY "likes"."post_id"
112
152
  ```
113
153
 
154
+ ### Loading with `Rails.cache`
155
+
156
+ ```ruby
157
+ class UserType < GraphQL::Schema::Object
158
+ field :location, String, null: false
159
+
160
+ def location
161
+ dataloader
162
+ .with(GraphQL::Sources::RailsCache)
163
+ .load(key: "geocode:#{object.latest_ip}", fallback: -> { Geocode.for(object.latest_ip) })
164
+ end
165
+ end
166
+ ```
167
+
168
+ ## Status
169
+
170
+ [![CircleCI](https://circleci.com/gh/ksylvest/graphql-sources.svg?style=svg)](https://circleci.com/gh/ksylvest/graphql-sources)
171
+ [![Maintainability](https://api.codeclimate.com/v1/badges/bc301cb72712637e67dd/maintainability)](https://codeclimate.com/github/ksylvest/graphql-sources/maintainability)
172
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/bc301cb72712637e67dd/test_coverage)](https://codeclimate.com/github/ksylvest/graphql-sources/test_coverage)
173
+
114
174
  ## License
115
175
 
116
176
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -31,6 +31,8 @@ module GraphQL
31
31
  # WHERE "comments"."user_id" IN (...)
32
32
  # ORDER BY "comments"."id"
33
33
  class ActiveRecordCollection < ActiveRecordBase
34
+ # @param keys [Array] an array of keys
35
+ # @return [Array] grouped records mirroring the keys
34
36
  def fetch(keys)
35
37
  models = models(keys: keys).order(:id).load_async
36
38
  dataloader.yield
@@ -31,6 +31,8 @@ module GraphQL
31
31
  # WHERE "likes"."post_id" IN (1, 2, 3, ...)
32
32
  # GROUP BY "likes"."post_id"
33
33
  class ActiveRecordCount < ActiveRecordBase
34
+ # @param keys [Array] an array of keys
35
+ # @return [Array] grouped counts for the keys
34
36
  def fetch(keys)
35
37
  map = models(keys: keys).group(@key).count
36
38
  keys.map { |key| map[key] || 0 }
@@ -31,6 +31,8 @@ module GraphQL
31
31
  # WHERE "profiles"."user_id" IN (1, 2, 3, ...)
32
32
  # ORDER BY "profiles"."id"
33
33
  class ActiveRecordObject < ActiveRecordBase
34
+ # @param keys [Array] an array of keys
35
+ # @return [Array] indexed records mirroring the keys
34
36
  def fetch(keys)
35
37
  models = models(keys: keys).order(:id).load_async
36
38
  dataloader.yield
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Sources
5
+ # An abstract class for interacting with active storage.
6
+ class ActiveStorageBase < GraphQL::Dataloader::Source
7
+ # @param name [String] the association name
8
+ def initialize(name)
9
+ super()
10
+ @name = name
11
+ end
12
+
13
+ protected
14
+
15
+ # @param records [Array<ActiveRecord::Base>] a collection of records to load attachments for
16
+ # @return [Array<ActiveStorage::Attachment>] the associated attachments with preloaded blobs
17
+ def attachments(records:)
18
+ ActiveStorage::Attachment
19
+ .preload(:blob)
20
+ .where(record: records)
21
+ .where(name: @name)
22
+ .order(:id)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './active_storage_base'
4
+
5
+ module GraphQL
6
+ module Sources
7
+ # A class for loading `has_many_attached` style associations.
8
+ #
9
+ # class User
10
+ # has_many_attached :photos
11
+ # end
12
+ #
13
+ # class UserType < GraphQL::Schema::Object
14
+ # field :photos, [AttachedType], null: false
15
+ #
16
+ # def photos
17
+ # dataloader
18
+ # .with(GraphQL::Sources::ActiveStorageHasManyAttached, :photos)
19
+ # .load(object)
20
+ # end
21
+ # end
22
+ #
23
+ # The resulting SQL query is:
24
+ #
25
+ # SELECT "active_storage_attachments".*
26
+ # FROM "active_storage_attachments"
27
+ # WHERE "active_storage_attachments"."name" = 'photos'
28
+ # AND "active_storage_attachments"."record_type" = 'User'
29
+ # AND "active_storage_attachments"."record_id" IN (...)
30
+ class ActiveStorageHasManyAttached < ActiveStorageBase
31
+ # @param records [Array<ActiveRecord::Base>] an array of records
32
+ # @return [Array] grouped attachments mirroring the keys
33
+ def fetch(records)
34
+ attachments = attachments(records: records).load_async
35
+ dataloader.yield
36
+
37
+ map = attachments.group_by { |attachment| [attachment.record_type, attachment.record_id] }
38
+ records.map { |record| map[[record.class.name, record.id]] || [] }
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './active_storage_base'
4
+
5
+ module GraphQL
6
+ module Sources
7
+ # A class for loading `has_one_attached` style associations.
8
+ #
9
+ # class User
10
+ # has_one_attached :photo
11
+ # end
12
+ #
13
+ # class UserType < GraphQL::Schema::Object
14
+ # field :avatar, AttachedType, null: false
15
+ #
16
+ # def avatar
17
+ # dataloader
18
+ # .with(GraphQL::Sources::ActiveStorageHasOneAttached, :avatar)
19
+ # .load(object)
20
+ # end
21
+ # end
22
+ #
23
+ # The resulting SQL query is:
24
+ #
25
+ # SELECT "active_storage_attachments".*
26
+ # FROM "active_storage_attachments"
27
+ # WHERE "active_storage_attachments"."name" = 'avatar'
28
+ # AND "active_storage_attachments"."record_type" = 'User'
29
+ # AND "active_storage_attachments"."record_id" IN (...)
30
+ class ActiveStorageHasOneAttached < ActiveStorageBase
31
+ # @param records [Array<ActiveRecord::Base>] an array of records
32
+ # @return [Array] indexed attachments mirroring the keys
33
+ def fetch(records)
34
+ attachments = attachments(records: records).load_async
35
+ dataloader.yield
36
+
37
+ map = attachments.index_by { |attachment| [attachment.record_type, attachment.record_id] }
38
+ records.map { |record| map[[record.class.name, record.id]] }
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Sources
5
+ # A class for loading with Rails.cache.
6
+ #
7
+ # class UserType < GraphQL::Schema::Object
8
+ # field :location, String, null: false
9
+ #
10
+ # def location
11
+ # dataloader
12
+ # .with(GraphQL::Sources::RailsCache)
13
+ # .load(key: "geocode:#{object.latest_ip}", fallback: -> { Geocode.for(object.latest_ip) })
14
+ # end
15
+ # end
16
+ class RailsCache < GraphQL::Dataloader::Source
17
+ # @param operations [Array<Hash>] an array of key and fallback hashes
18
+ def fetch(operations)
19
+ keys = operations.pluck(:key)
20
+ fallbacks = operations.to_h { |operation| [operation[:key], operation[:fallback]] }
21
+ results = Rails.cache.fetch_multi(*keys) { |key| fallbacks[key].call }
22
+ keys.map { |key| results[key] }
23
+ end
24
+ end
25
+ end
26
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GraphQL
4
4
  module Sources
5
- VERSION = '0.2.0'
5
+ VERSION = '0.5.0'
6
6
  end
7
7
  end
@@ -5,6 +5,9 @@ require 'graphql'
5
5
  require_relative './sources/active_record_count'
6
6
  require_relative './sources/active_record_collection'
7
7
  require_relative './sources/active_record_object'
8
+ require_relative './sources/active_storage_has_many_attached'
9
+ require_relative './sources/active_storage_has_one_attached'
10
+ require_relative './sources/rails_cache'
8
11
 
9
12
  module GraphQL
10
13
  # A collection of common GraphQL dataloader classes.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql-sources
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Sylvestre
@@ -150,6 +150,20 @@ dependencies:
150
150
  - - ">="
151
151
  - !ruby/object:Gem::Version
152
152
  version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: simplecov
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
153
167
  description: Common loaders for various database or cache operations.
154
168
  email:
155
169
  - kevin@ksylvest.com
@@ -165,6 +179,10 @@ files:
165
179
  - lib/graphql/sources/active_record_collection.rb
166
180
  - lib/graphql/sources/active_record_count.rb
167
181
  - lib/graphql/sources/active_record_object.rb
182
+ - lib/graphql/sources/active_storage_base.rb
183
+ - lib/graphql/sources/active_storage_has_many_attached.rb
184
+ - lib/graphql/sources/active_storage_has_one_attached.rb
185
+ - lib/graphql/sources/rails_cache.rb
168
186
  - lib/graphql/sources/version.rb
169
187
  homepage: https://github.com/ksylvest/graphql-sources
170
188
  licenses: