graphql-rails-api 0.9.3 → 0.9.6

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: dfe867c4f64d230748b3d8a9d1aece95a16c6692d96491fd67298ad522ddd5ce
4
- data.tar.gz: 0a407e15eff08e02e8a202da0d47d3fe6116af42eb591421e3b5a914964f7a48
3
+ metadata.gz: 1bb023539d96953816444d9bfb1c00a93143e7bf78bb6bd01fbdc90a2c8f9c52
4
+ data.tar.gz: fa272ef8fc02dd009221281694d1f567c4a3d3ef62a45d0129e5477cb145ba37
5
5
  SHA512:
6
- metadata.gz: e508676774b519da77b95483e1a0d28a91e1843925dc1669f674cb958d911ada161968923a385d86af51b86d47af56baecff3fe5a959cb1b727aed556834df6c
7
- data.tar.gz: 3e1aefb503057898ba28eb3008fd2e8b4c63f792fd530ac0a3bcfdf8754ed168a5b5f7f6c1dbaf76f96961494bd03da7f905fc57ba19ccaabd26af1eb8dddffc
6
+ metadata.gz: d72815cf15edf05a92ac0c49752acebb7f390999232caec1d7960b8d486f96b27e2a1cce616470e7627814a6f5161101a2c7f0c5af6d741d52186fe7371fe587
7
+ data.tar.gz: dc1a6d286bf8cfe6d6a2a2bb2398906f0ee860079d7acf46f88134dba05aa2775f227506fa978d05b696ac320357d9e8e653c313761a03cb55a9422e3b43b07d
@@ -8,13 +8,13 @@ class GraphqlAddFieldsGenerator < Rails::Generators::NamedBase
8
8
  end
9
9
 
10
10
  TYPES_MAPPING = {
11
- 'id' => 'types.String',
12
- 'uuid' => 'types.String',
13
- 'boolean' => 'types.Boolean',
14
- 'float' => 'types.Float',
15
- 'decimal' => 'types.Float',
16
- 'integer' => 'types.Int',
17
- 'bigint' => 'types.Int'
11
+ 'id' => 'String',
12
+ 'uuid' => 'String',
13
+ 'boolean' => 'Boolean',
14
+ 'float' => 'Float',
15
+ 'decimal' => 'Float',
16
+ 'integer' => 'Integer',
17
+ 'bigint' => 'Integer'
18
18
  }.freeze
19
19
 
20
20
  def create_graphql_files
@@ -35,30 +35,30 @@ class GraphqlAddFieldsGenerator < Rails::Generators::NamedBase
35
35
  add_has_many_to_models(@resource) if options.propagation?
36
36
  add_has_many_fields_to_types(@resource) if options.propagation?
37
37
 
38
- system('bundle exec rails db:migrate') if options.migrate?
38
+ # system('bundle exec rails db:migrate') if options.migrate?
39
39
  end
40
40
 
41
41
  private
42
42
 
43
43
  def types_mapping(type)
44
- TYPES_MAPPING[type] || 'types.String'
44
+ TYPES_MAPPING[type] || 'String'
45
45
  end
46
46
 
47
47
  def complete_graphql_input_type
48
48
  return if map_types(input_type: true).blank?
49
49
 
50
- write_at("#{@mutations_directory}/input_type.rb", 4, " #{map_types(input_type: true)}\n")
50
+ write_at("#{@mutations_directory}/input_type.rb", 7, " #{map_types(input_type: true)}\n")
51
51
  end
52
52
 
53
53
  def complete_graphql_type(resource)
54
54
  return if map_types(input_type: false).blank?
55
55
 
56
- write_at("#{graphql_resource_directory(resource)}/type.rb", 4, " #{map_types(input_type: false)}\n")
56
+ write_at("#{graphql_resource_directory(resource)}/type.rb", 6, " #{map_types(input_type: false)}\n")
57
57
  end
58
58
 
59
59
  def parse_args
60
60
  @id_db_type = 'uuid'
61
- @id_type = 'types.String'
61
+ @id_type = 'String'
62
62
 
63
63
  @resource = file_name.singularize
64
64
  @has_many = []
@@ -114,13 +114,8 @@ class GraphqlAddFieldsGenerator < Rails::Generators::NamedBase
114
114
  end
115
115
 
116
116
  write_at(
117
- file_name, 4,
118
- <<-STRING
119
- field :#{resource.singularize}_ids, types[#{@id_type}] do
120
- resolve CollectionIdsResolver
121
- end
122
- field :#{resource.pluralize}, types[#{resource.pluralize.camelize}::Type]
123
- STRING
117
+ file_name, 6,
118
+ " field :#{resource.pluralize}, [#{resource.pluralize.camelize}::Type], null: true\n"
124
119
  )
125
120
 
126
121
  input_type_file_name = "app/graphql/#{field.pluralize}/mutations/input_type.rb"
@@ -130,10 +125,8 @@ class GraphqlAddFieldsGenerator < Rails::Generators::NamedBase
130
125
  end
131
126
 
132
127
  write_at(
133
- input_type_file_name, 4,
134
- <<-STRING
135
- argument :#{resource.singularize}_ids, types[#{@id_type}]
136
- STRING
128
+ input_type_file_name, 7,
129
+ " argument :#{resource.singularize}_ids, [#{@id_type}], required: false\n"
137
130
  )
138
131
  end
139
132
 
@@ -145,11 +138,8 @@ class GraphqlAddFieldsGenerator < Rails::Generators::NamedBase
145
138
  end
146
139
 
147
140
  write_at(
148
- file_name, 4,
149
- <<-STRING
150
- field :#{field.singularize}_id, #{@id_type}
151
- field :#{field.singularize}, #{field.pluralize.camelize}::Type
152
- STRING
141
+ file_name, 6,
142
+ " field :#{field.singularize}_id, #{@id_type}, null: false\n field :#{field.singularize}, #{field.pluralize.camelize}::Type, null: false\n"
153
143
  )
154
144
  input_type_file_name = "app/graphql/#{resource.pluralize}/mutations/input_type.rb"
155
145
  if File.read(input_type_file_name).include?("argument :#{field.singularize}_id") ||
@@ -158,10 +148,8 @@ class GraphqlAddFieldsGenerator < Rails::Generators::NamedBase
158
148
  end
159
149
 
160
150
  write_at(
161
- input_type_file_name, 4,
162
- <<-STRING
163
- argument :#{field.singularize}_id, #{@id_type}
164
- STRING
151
+ input_type_file_name, 7,
152
+ " argument :#{field.singularize}_id, #{@id_type}, required: false\n"
165
153
  )
166
154
  end
167
155
 
@@ -225,14 +213,14 @@ t.#{@id_db_type} :#{resource.underscore.singularize}_id
225
213
  result = args&.map do |k, v|
226
214
  field_name = k
227
215
  field_type = types_mapping(v)
228
- res = "#{input_type ? 'argument' : 'field'} :#{field_name}, #{field_type}"
216
+ res = "#{input_type ? 'argument' : 'field'} :#{field_name}, #{field_type}, #{input_type ? "required: false" : "null: true"}"
229
217
  if !input_type && field_name.ends_with?('_id') && @belongs_to_fields.key?(field_name)
230
- res += "\n field :#{field_name.gsub('_id', '')}, " \
218
+ res += "\n field :#{field_name.gsub('_id', '')}, " \
231
219
  "#{field_name.gsub('_id', '').pluralize.camelize}::Type"
232
220
  end
233
221
  res
234
- end&.join("\n ")
235
- input_type ? result.gsub("field :id, #{@id_type}\n", '') : result
222
+ end&.join("\n " + (" " if input_type).to_s)
223
+ input_type ? result.gsub("field :id, #{@id_type}, null: false\n", '') : result
236
224
  end
237
225
 
238
226
  # Helpers methods
@@ -248,13 +236,13 @@ t.#{@id_db_type} :#{resource.underscore.singularize}_id
248
236
  file = open(file_name)
249
237
  line_count = file.readlines.size
250
238
  line_nb = 0
251
- file.each do |l|
239
+ IO.readlines(file).each do |l|
252
240
  line_nb += 1
253
241
  break if l.include?('ApplicationRecord')
254
242
  end
255
243
  raise 'Your model must inherit from ApplicationRecord to make it work' if line_nb >= line_count
256
244
 
257
- write_at(file_name, line_nb + 2, " #{line}\n")
245
+ write_at(file_name, line_nb + 1, " #{line}\n")
258
246
  end
259
247
 
260
248
  def generate_has_many_migration(resource, has_many:)
@@ -1,5 +1,4 @@
1
1
  class GraphqlMutationsGenerator < Rails::Generators::NamedBase
2
-
3
2
  def generate
4
3
  resource = file_name.underscore.singularize
5
4
  dir = "app/graphql/#{resource.pluralize}/mutations"
@@ -7,55 +6,30 @@ class GraphqlMutationsGenerator < Rails::Generators::NamedBase
7
6
  generate_create_mutation(dir, resource)
8
7
  generate_update_mutation(dir, resource)
9
8
  generate_destroy_mutation(dir, resource)
10
- generate_bulk_create_mutation(dir, resource)
11
- generate_bulk_update_mutation(dir, resource)
12
9
  end
13
10
 
14
11
  private
15
12
 
16
- def generate_bulk_create_mutation(dir, resource)
17
- File.write(
18
- "#{dir}/bulk_create.rb",
19
- <<~STRING
20
- #{resource_class(resource)}::Mutations::BulkCreate = GraphQL::Field.define do
21
- description 'creates some #{resource_class(resource).pluralize}'
22
- type types[#{resource_class(resource)}::Type]
23
-
24
- argument :#{resource}, !types[#{resource_class(resource)}::Mutations::InputType]
25
-
26
- resolve ApplicationService.call(:#{resource}, :bulk_create)
27
- end
28
- STRING
29
- )
30
- end
31
-
32
- def generate_bulk_update_mutation(dir, resource)
33
- File.write(
34
- "#{dir}/bulk_update.rb",
35
- <<~STRING
36
- #{resource_class(resource)}::Mutations::BulkUpdate = GraphQL::Field.define do
37
- description 'Updates some #{resource_class(resource).pluralize}'
38
- type types[#{resource_class(resource)}::Type]
39
-
40
- argument :#{resource}, !types[#{resource_class(resource)}::Mutations::InputType]
41
-
42
- resolve ApplicationService.call(:#{resource}, :bulk_update)
43
- end
44
- STRING
45
- )
46
- end
47
-
48
13
  def generate_create_mutation(dir, resource)
49
14
  File.write(
50
15
  "#{dir}/create.rb",
51
16
  <<~STRING
52
- #{resource_class(resource)}::Mutations::Create = GraphQL::Field.define do
53
- description 'Creates a #{resource_class(resource).singularize}'
54
- type #{resource_class(resource)}::Type
55
-
56
- argument :#{resource}, !#{resource_class(resource)}::Mutations::InputType
57
-
58
- resolve ApplicationService.call(:#{resource}, :create)
17
+ module #{resource_class(resource)}
18
+ module Mutations
19
+ class Create < GraphQL::Schema::Mutation
20
+ graphql_name "Create#{resource_class(resource)}"
21
+ description "Create a #{resource_class(resource).singularize}"
22
+
23
+ field :errors, [String], null: true
24
+ field :#{resource}, #{resource_class(resource)}::Type, null: true
25
+
26
+ argument :attributes, #{resource_class(resource)}::Mutations::InputType, required: false
27
+
28
+ def resolve(attributes:)
29
+ ApplicationService.call(:#{resource}, :create, context, attributes)
30
+ end
31
+ end
32
+ end
59
33
  end
60
34
  STRING
61
35
  )
@@ -65,14 +39,23 @@ class GraphqlMutationsGenerator < Rails::Generators::NamedBase
65
39
  File.write(
66
40
  "#{dir}/update.rb",
67
41
  <<~STRING
68
- #{resource_class(resource)}::Mutations::Update = GraphQL::Field.define do
69
- description 'Updates a #{resource_class(resource).singularize}'
70
- type #{resource_class(resource)}::Type
71
-
72
- argument :id, types.String
73
- argument :#{resource}, !#{resource_class(resource)}::Mutations::InputType
74
-
75
- resolve ApplicationService.call(:#{resource}, :update)
42
+ module #{resource_class(resource)}
43
+ module Mutations
44
+ class Update < GraphQL::Schema::Mutation
45
+ graphql_name "Update#{resource_class(resource)}"
46
+ description "Update a #{resource_class(resource).singularize}"
47
+
48
+ field :errors, [String], null: true
49
+ field :#{resource}, #{resource_class(resource)}::Type, null: true
50
+
51
+ argument :id, String, required: true
52
+ argument :attributes, #{resource_class(resource)}::Mutations::InputType, required: false
53
+
54
+ def resolve(id:, attributes:)
55
+ ApplicationService.call(:#{resource}, :update, context, id, attributes)
56
+ end
57
+ end
58
+ end
76
59
  end
77
60
  STRING
78
61
  )
@@ -82,13 +65,22 @@ class GraphqlMutationsGenerator < Rails::Generators::NamedBase
82
65
  File.write(
83
66
  "#{dir}/destroy.rb",
84
67
  <<~STRING
85
- #{resource_class(resource)}::Mutations::Destroy = GraphQL::Field.define do
86
- description 'Destroys a #{resource_class(resource).singularize}'
87
- type #{resource_class(resource)}::Type
88
-
89
- argument :id, !types.String
90
-
91
- resolve ApplicationService.call(:#{resource}, :destroy)
68
+ module #{resource_class(resource)}
69
+ module Mutations
70
+ class Destroy < GraphQL::Schema::Mutation
71
+ graphql_name "Destroy#{resource_class(resource)}"
72
+ description "Destroy a #{resource_class(resource).singularize}"
73
+
74
+ field :errors, [String], null: true
75
+ field :#{resource}, #{resource_class(resource)}::Type, null: true
76
+
77
+ argument :id, String, required: true
78
+
79
+ def resolve(id:)
80
+ ApplicationService.call(:#{resource}, :destroy, context, id)
81
+ end
82
+ end
83
+ end
92
84
  end
93
85
  STRING
94
86
  )
@@ -97,5 +89,4 @@ class GraphqlMutationsGenerator < Rails::Generators::NamedBase
97
89
  def resource_class(resource)
98
90
  @resource_class ||= resource.pluralize.camelize
99
91
  end
100
-
101
92
  end
@@ -3,7 +3,6 @@ require 'graphql/rails/api/config'
3
3
  module GraphqlRailsApi
4
4
  class InstallGenerator < Rails::Generators::Base
5
5
 
6
- class_option('apollo_compatibility', type: :boolean, default: true)
7
6
  class_option('generate_graphql_route', type: :boolean, default: true)
8
7
 
9
8
  def generate_files
@@ -165,18 +164,18 @@ end
165
164
  File.write(
166
165
  'app/graphql/mutation_type.rb',
167
166
  <<~'STRING'
168
- MutationType = GraphQL::ObjectType.define do
169
- name 'Mutation'
167
+ class MutationType < GraphQL::Schema::Object
168
+ graphql_name "Mutation"
169
+ description "The mutation root of this schema"
170
170
 
171
171
  Graphql::Rails::Api::Config.mutation_resources.each do |methd, resources|
172
172
  resources.each do |resource|
173
173
  field(
174
174
  "#{methd}_#{resource.singularize}".to_sym,
175
- "#{resource.camelize}::Mutations::#{methd.camelize}".constantize
175
+ mutation: "#{resource.camelize}::Mutations::#{methd.camelize}".constantize
176
176
  )
177
177
  end
178
178
  end
179
-
180
179
  end
181
180
  STRING
182
181
  )
@@ -186,59 +185,71 @@ end
186
185
  File.write(
187
186
  'app/graphql/query_type.rb',
188
187
  <<~'STRING'
189
- QueryType = GraphQL::ObjectType.define do
190
- name 'Query'
188
+ class QueryType < GraphQL::Schema::Object
189
+ description "The query root of this schema"
191
190
 
192
191
  Graphql::Rails::Api::Config.query_resources.each do |resource|
193
- field resource.singularize do
194
- description "Returns a #{resource.classify}"
195
- type !"#{resource.camelize}::Type".constantize
196
- argument :id, !types.String
197
- resolve ApplicationService.call(resource, :show)
192
+ klass = Class.new(GraphQL::Schema::Object) do
193
+ graphql_name "Paginated#{resource.classify}"
194
+
195
+ field :total_count, Integer, null: false
196
+ field :page, Integer, null: false
197
+ field :per_page, Integer, null: false
198
+ field :data, ["#{resource.pluralize.camelize}::Type".constantize], null: false
198
199
  end
199
200
 
200
- field resource.pluralize do
201
- description "Returns a #{resource.classify}"
202
- type !types[!"#{resource.camelize}::Type".constantize]
203
- argument :page, types.Int
204
- argument :per_page, types.Int
205
- argument :filter, types.String
206
- argument :order_by, types.String
207
- resolve ApplicationService.call(resource, :index)
201
+ resource.pluralize.camelize.constantize.const_set(
202
+ :PaginatedType,
203
+ klass
204
+ )
205
+
206
+ field "paginated_#{resource.pluralize}", "#{resource.camelize}::PaginatedType".constantize, null: false do
207
+ description "Return paginated #{resource.classify}"
208
+ argument :page, Integer, required: true
209
+ argument :per_page, Integer, required: true
210
+ argument :filter, String, required: false
211
+ argument :order_by, type: String, required: false
208
212
  end
209
213
 
210
- end
214
+ define_method("paginated_#{resource.pluralize}") do |page:, per_page:, filter: nil, order_by: nil|
215
+ arguments = {
216
+ page: page,
217
+ per_page: per_page,
218
+ filter: filter,
219
+ order_by: order_by
220
+ }
221
+ ApplicationService.call(resource, :paginated_index, context, arguments)
222
+ end
211
223
 
212
- field :me, Users::Type do
213
- description 'Returns the current user'
214
- resolve ->(_, _, ctx) { ctx[:current_user] }
215
- end
224
+ field resource.pluralize.to_sym, ["#{resource.camelize}::Type".constantize], null: false do
225
+ description "All #{resource.pluralize}"
226
+ argument :page, Integer, required: false
227
+ argument :per_page, Integer, required: false
228
+ argument :filter, String, required: false
229
+ argument :order_by, String, required: false
230
+ end
216
231
 
232
+ define_method(resource.pluralize) do |page: nil, per_page: nil, filter: nil, order_by: nil|
233
+ arguments = {page: page, per_page: per_page, filter: filter, order_by: order_by}
234
+ ApplicationService.call(resource, :index, context, arguments)
235
+ end
236
+
237
+ field resource.singularize.to_sym, "#{resource.camelize}::Type".constantize, null: false do
238
+ description "A #{resource.singularize}"
239
+ argument :id, String, required: true
240
+ end
241
+
242
+ define_method(resource.singularize) do |id:|
243
+ arguments = {id: id}
244
+ ApplicationService.call(resource, :show, context, arguments)
245
+ end
246
+ end
217
247
  end
248
+
218
249
  STRING
219
250
  )
220
251
  end
221
252
 
222
- def apollo_compat
223
- <<~'STRING'
224
- # /!\ do not remove /!\
225
- # Apollo Data compat.
226
- ClientDirective = GraphQL::Directive.define do
227
- name 'client'
228
- locations([GraphQL::Directive::FIELD])
229
- default_directive true
230
- end
231
- ConnectionDirective = GraphQL::Directive.define do
232
- name 'connection'
233
- locations([GraphQL::Directive::FIELD])
234
- argument :key, GraphQL::STRING_TYPE
235
- argument :filter, GraphQL::STRING_TYPE.to_list_type
236
- default_directive true
237
- end
238
- # end of Apollo Data compat.
239
- STRING
240
- end
241
-
242
253
  def write_schema
243
254
  logger = <<~'STRING'
244
255
  type_error_logger = Logger.new("#{Rails.root}/log/graphql_type_errors.log")
@@ -252,18 +263,20 @@ end
252
263
  File.write(
253
264
  "app/graphql/#{@app_name}_schema.rb",
254
265
  <<~STRING
255
- #{logger}
256
- #{apollo_compat if options.apollo_compatibility?}
257
- # Schema definition
258
- #{@app_name.camelize}Schema = GraphQL::Schema.define do
259
- mutation(MutationType)
260
- query(QueryType)
261
- #{'directives [ConnectionDirective, ClientDirective]' if options.apollo_compatibility?}
262
- type_error lambda { |err, query_ctx|
263
- #{error_handler}
264
- }
266
+ class #{@app_name.camelize}Schema < GraphQL::Schema
267
+ query QueryType
268
+ mutation MutationType
269
+ max_depth 15
270
+
271
+ def self.type_error(err, query_ctx)
272
+ type_error_logger = Logger.new("#{Rails.root}/log/graphql_type_errors.log")
273
+
274
+ type_error_logger.error(
275
+ "#{err} for #{query_ctx.query.query_string} with #{query_ctx.query.provided_variables}"
276
+ )
277
+ end
265
278
  end
266
- STRING
279
+ <<~STRING
267
280
  )
268
281
  end
269
282
 
@@ -317,41 +330,17 @@ end
317
330
  return not_allowed if not_allowed_to_create_resource(object)
318
331
 
319
332
  if object.save
320
- object
333
+ { singular_resource => Graphql::HydrateQuery.new(model.all, @context, user: user, id: object.id).run }
321
334
  else
322
335
  graphql_error(object.errors.full_messages.join(', '))
323
336
  end
324
337
  end
325
338
 
326
- def bulk_create
327
- result = model.import(params.map { |p| p.select { |param| model.new.respond_to?(param) } })
328
- result.each { |e| e.run_callbacks(:save) }
329
- hyd = Graphql::HydrateQuery.new(model.where(id: result.ids), @context).run.compact + result.failed_instances.map do |i|
330
- graphql_error(i.errors.full_messages)
331
- end
332
- return hyd.first if hyd.all? { |e| e.is_a?(GraphQL::ExecutionError) }
333
-
334
- hyd
335
- end
336
-
337
- def bulk_update
338
- visible_ids = model.where(id: params.map { |p| p[:id] }).pluck(:id)
339
- return not_allowed if (model.visible_for(user: user).pluck(:id) & visible_ids).size < visible_ids.size
340
-
341
- hash = params.each_with_object({}) { |p, h| h[p.delete(:id)] = p }
342
- failed_instances = []
343
- result = model.update(hash.keys, hash.values).map { |e| e.errors.blank? ? e : (failed_instances << e && nil) }
344
- hyd = Graphql::HydrateQuery.new(model.where(id: result.compact.map(&:id)), @context).run.compact + failed_instances.map do |i|
345
- graphql_error(i.errors.full_messages)
346
- end
347
- hyd.all? { |e| e.is_a?(GraphQL::ExecutionError) } ? hyd.first : hyd
348
- end
349
-
350
339
  def update
351
340
  return not_allowed if write_not_allowed
352
341
 
353
342
  if object.update_attributes(params)
354
- object
343
+ { singular_resource => Graphql::HydrateQuery.new(model.all, @context, user: user, id: object.id).run }
355
344
  else
356
345
  graphql_error(object.errors.full_messages.join(', '))
357
346
  end
@@ -362,7 +351,7 @@ end
362
351
  return not_allowed if write_not_allowed
363
352
 
364
353
  if object.destroy
365
- object
354
+ { singular_resource => object.attributes }
366
355
  else
367
356
  graphql_error(object.errors.full_messages.join(', '))
368
357
  end
@@ -1,20 +1,19 @@
1
1
  class GraphqlResourceGenerator < Rails::Generators::NamedBase
2
-
3
2
  %i[
4
3
  migration model mutations service graphql_input_type
5
- graphql_type propagation connection migrate
4
+ graphql_type propagation migrate
6
5
  ].each do |opt|
7
6
  class_option(opt, type: :boolean, default: true)
8
7
  end
9
8
 
10
9
  TYPES_MAPPING = {
11
- 'id' => 'types.String',
12
- 'uuid' => 'types.String',
13
- 'boolean' => 'types.Boolean',
14
- 'float' => 'types.Float',
15
- 'decimal' => 'types.Float',
16
- 'integer' => 'types.Int',
17
- 'bigint' => 'types.Int'
10
+ "id" => "String",
11
+ "uuid" => "String",
12
+ "boolean" => "Boolean",
13
+ "float" => "Float",
14
+ "decimal" => "Float",
15
+ "integer" => "Integer",
16
+ "bigint" => "Integer"
18
17
  }.freeze
19
18
 
20
19
  def create_graphql_files
@@ -42,18 +41,18 @@ class GraphqlResourceGenerator < Rails::Generators::NamedBase
42
41
  add_has_many_to_models(@resource) if options.propagation?
43
42
  add_has_many_fields_to_types(@resource) if options.propagation?
44
43
 
45
- system('bundle exec rails db:migrate') if options.migrate?
44
+ # system("bundle exec rails db:migrate") if options.migrate?
46
45
  end
47
46
 
48
47
  private
49
48
 
50
49
  def types_mapping(type)
51
- TYPES_MAPPING[type] || 'types.String'
50
+ TYPES_MAPPING[type] || "String"
52
51
  end
53
52
 
54
53
  def parse_args
55
- @id_db_type = 'uuid'
56
- @id_type = 'types.String'
54
+ @id_db_type = "uuid"
55
+ @id_type = "String"
57
56
 
58
57
  @resource = file_name.singularize
59
58
  @has_many = []
@@ -62,21 +61,21 @@ class GraphqlResourceGenerator < Rails::Generators::NamedBase
62
61
  @belongs_to_fields = {}
63
62
 
64
63
  @args = args.each_with_object({}) do |f, hash|
65
- next if f.split(':').count != 2
66
-
67
- case f.split(':').first
68
- when 'belongs_to' then
69
- hash["#{f.split(':').last.singularize}_id"] = @id_db_type
70
- @belongs_to_fields["#{f.split(':').last.singularize}_id"] = @id_db_type
71
- when 'has_many' then @has_many << f.split(':').last.pluralize
72
- when 'many_to_many' then @many_to_many << f.split(':').last.pluralize
64
+ next if f.split(":").count != 2
65
+
66
+ case f.split(":").first
67
+ when "belongs_to"
68
+ hash["#{f.split(":").last.singularize}_id"] = @id_db_type
69
+ @belongs_to_fields["#{f.split(":").last.singularize}_id"] = @id_db_type
70
+ when "has_many" then @has_many << f.split(":").last.pluralize
71
+ when "many_to_many" then @many_to_many << f.split(":").last.pluralize
73
72
  else
74
- hash[f.split(':').first] = f.split(':').last
73
+ hash[f.split(":").first] = f.split(":").last
75
74
  end
76
75
  end
77
76
 
78
77
  @fields_to_migration = @args.map do |f|
79
- "t.#{f.reverse.join(' :')}"
78
+ "t.#{f.reverse.join(" :")}"
80
79
  end.join("\n ")
81
80
  end
82
81
 
@@ -90,7 +89,7 @@ class GraphqlResourceGenerator < Rails::Generators::NamedBase
90
89
  File.write(
91
90
  migration_file,
92
91
  <<~STRING
93
- class Create#{resource.camelize} < ActiveRecord::Migration[5.2]
92
+ class Create#{resource.camelize} < ActiveRecord::Migration[7.0]
94
93
  def change
95
94
  create_table :#{resource.pluralize}, id: :uuid do |t|
96
95
  #{fields}
@@ -110,33 +109,20 @@ class GraphqlResourceGenerator < Rails::Generators::NamedBase
110
109
  generate_graphql_input_type(resource) if options.graphql_input_type?
111
110
  end
112
111
 
113
- def generate_graphql_connection(resource)
114
- File.write(
115
- "#{graphql_resource_directory(resource)}/connection.rb",
116
- <<~STRING
117
- #{resource_class(resource)}::Connection = #{resource.pluralize.camelize}::Type.define_connection do
118
- name '#{resource.camelize}Connection'
119
-
120
- field :total_count, types.Int do
121
- resolve ->(obj, _, _) { obj.nodes.count }
122
- end
123
- end
124
- STRING
125
- )
126
- end
127
-
128
112
  def generate_graphql_input_type(resource)
129
113
  FileUtils.mkdir_p(@mutations_directory) unless File.directory?(@mutations_directory)
130
114
 
131
115
  File.write(
132
116
  "#{@mutations_directory}/input_type.rb",
133
117
  <<~STRING
134
- #{resource_class(resource)}::Mutations::InputType = GraphQL::InputObjectType.define do
135
- name '#{resource_class(resource).singularize}InputType'
136
- description 'Properties for updating a #{resource_class(resource).singularize}'
137
-
138
- #{map_types(input_type: true)}
139
-
118
+ module #{resource_class(resource)}
119
+ module Mutations
120
+ class InputType < GraphQL::Schema::InputObject
121
+ graphql_name "#{resource.camelize}InputType"
122
+ description "Attributes for creating or updating a #{resource}"
123
+ #{map_types(input_type: true)}
124
+ end
125
+ end
140
126
  end
141
127
  STRING
142
128
  )
@@ -146,12 +132,16 @@ class GraphqlResourceGenerator < Rails::Generators::NamedBase
146
132
  File.write(
147
133
  "#{graphql_resource_directory(resource)}/type.rb",
148
134
  <<~STRING
149
- #{resource_class(resource)}::Type = GraphQL::ObjectType.define do
150
- name '#{resource_class(resource).singularize}'
151
- field :id, !#{@id_type}
152
- field :created_at, types.String
153
- field :updated_at, types.String
154
- #{map_types(input_type: false)}
135
+ module #{resource_class(resource)}
136
+ class Type < GraphQL::Schema::Object
137
+ graphql_name "#{resource.camelize}"
138
+ description "A #{resource}"
139
+
140
+ field :id, #{@id_type}, null: false
141
+ field :created_at, String, null: false
142
+ field :updated_at, String, null: false
143
+ #{map_types(input_type: false)}
144
+ end
155
145
  end
156
146
  STRING
157
147
  )
@@ -168,13 +158,7 @@ class GraphqlResourceGenerator < Rails::Generators::NamedBase
168
158
  return
169
159
  end
170
160
  write_at(
171
- file_name, 4,
172
- <<-STRING
173
- field :#{resource.singularize}_ids, types[#{@id_type}] do
174
- resolve CollectionIdsResolver
175
- end
176
- field :#{resource.pluralize}, types[#{resource.pluralize.camelize}::Type]
177
- STRING
161
+ file_name, 6," field :#{resource.pluralize}, [#{resource.pluralize.camelize}::Type], null: true\n"
178
162
  )
179
163
 
180
164
  input_type_file_name = "app/graphql/#{field.pluralize}/mutations/input_type.rb"
@@ -183,10 +167,8 @@ class GraphqlResourceGenerator < Rails::Generators::NamedBase
183
167
  return
184
168
  end
185
169
  write_at(
186
- input_type_file_name, 4,
187
- <<-STRING
188
- argument :#{resource.singularize}_ids, types[#{@id_type}]
189
- STRING
170
+ input_type_file_name, 7,
171
+ " argument :#{resource.singularize}_ids, [#{@id_type}], required: false\n"
190
172
  )
191
173
 
192
174
  end
@@ -199,11 +181,8 @@ class GraphqlResourceGenerator < Rails::Generators::NamedBase
199
181
  end
200
182
 
201
183
  write_at(
202
- file_name, 4,
203
- <<-STRING
204
- field :#{field.singularize}_id, #{@id_type}
205
- field :#{field.singularize}, #{field.pluralize.camelize}::Type
206
- STRING
184
+ file_name, 6,
185
+ " field :#{field.singularize}_id, #{@id_type}, null: false\n field :#{field.singularize}, #{field.pluralize.camelize}::Type, null: false\n"
207
186
  )
208
187
  input_type_file_name = "app/graphql/#{resource.pluralize}/mutations/input_type.rb"
209
188
  if File.read(input_type_file_name).include?("argument :#{field.singularize}_id") ||
@@ -212,10 +191,8 @@ class GraphqlResourceGenerator < Rails::Generators::NamedBase
212
191
  end
213
192
 
214
193
  write_at(
215
- input_type_file_name, 4,
216
- <<-STRING
217
- argument :#{field.singularize}_id, #{@id_type}
218
- STRING
194
+ input_type_file_name, 7,
195
+ " argument :#{field.singularize}_id, #{@id_type}, required: false\n"
219
196
  )
220
197
  end
221
198
 
@@ -225,8 +202,8 @@ class GraphqlResourceGenerator < Rails::Generators::NamedBase
225
202
  add_belongs_to_field_to_type(resource, f)
226
203
  end
227
204
  @belongs_to_fields.each do |f, _|
228
- add_has_many_fields_to_type(f.gsub('_id', ''), resource)
229
- add_belongs_to_field_to_type(f.gsub('_id', ''), resource)
205
+ add_has_many_fields_to_type(f.gsub("_id", ""), resource)
206
+ add_belongs_to_field_to_type(f.gsub("_id", ""), resource)
230
207
  end
231
208
  end
232
209
 
@@ -242,9 +219,8 @@ class GraphqlResourceGenerator < Rails::Generators::NamedBase
242
219
  end
243
220
 
244
221
  def generate_service(resource)
245
-
246
222
  FileUtils.mkdir_p("app/graphql/#{resource.pluralize}/") unless File.directory?("app/graphql/#{resource.pluralize}/")
247
-
223
+
248
224
  File.write(
249
225
  "app/graphql/#{resource.pluralize}/service.rb",
250
226
  <<~STRING
@@ -261,9 +237,9 @@ class GraphqlResourceGenerator < Rails::Generators::NamedBase
261
237
  @many_to_many.each do |field|
262
238
  generate_create_migration(
263
239
  "#{resource}_#{field}",
264
- <<-STRING
265
- t.#{@id_db_type} :#{resource.underscore.singularize}_id
266
- t.#{@id_db_type} :#{field.underscore.singularize}_id
240
+ <<~STRING
241
+ t.#{@id_db_type} :#{resource.underscore.singularize}_id
242
+ t.#{@id_db_type} :#{field.underscore.singularize}_id
267
243
  STRING
268
244
  )
269
245
  generate_empty_model("#{resource}_#{field.singularize}")
@@ -285,7 +261,7 @@ t.#{@id_db_type} :#{resource.underscore.singularize}_id
285
261
  add_to_model(field, "belongs_to :#{resource.singularize}")
286
262
  end
287
263
  @belongs_to_fields.each do |k, _|
288
- field = k.gsub('_id', '')
264
+ field = k.gsub("_id", "")
289
265
  add_to_model(field, "has_many :#{resource.pluralize}")
290
266
  add_to_model(resource, "belongs_to :#{field.singularize}")
291
267
  end
@@ -295,14 +271,15 @@ t.#{@id_db_type} :#{resource.underscore.singularize}_id
295
271
  result = args&.map do |k, v|
296
272
  field_name = k
297
273
  field_type = types_mapping(v)
298
- res = "#{input_type ? 'argument' : 'field'} :#{field_name}, #{field_type}"
299
- if !input_type && field_name.ends_with?('_id') && @belongs_to_fields.key?(field_name)
300
- res += "\n field :#{field_name.gsub('_id', '')}, " \
301
- "#{field_name.gsub('_id', '').pluralize.camelize}::Type"
274
+ res = "#{input_type ? "argument" : "field"} :#{field_name}, #{field_type}, #{input_type ? "required: false" : "null: true"}"
275
+
276
+ if !input_type && field_name.ends_with?("_id") && @belongs_to_fields.key?(field_name)
277
+ res += "\n field :#{field_name.gsub("_id", "")}, " \
278
+ "#{field_name.gsub("_id", "").pluralize.camelize}::Type, null: false"
302
279
  end
303
280
  res
304
- end&.join("\n ")
305
- input_type ? result.gsub("field :id, #{@id_type}\n", '') : result
281
+ end&.join("\n " + (" " if input_type).to_s)
282
+ input_type ? result.gsub("field :id, #{@id_type}, null: false\n", "") : result
306
283
  end
307
284
 
308
285
  # Helpers methods
@@ -312,19 +289,19 @@ t.#{@id_db_type} :#{resource.underscore.singularize}_id
312
289
  end
313
290
 
314
291
  def add_to_model(model, line)
315
- file_name = File.join("app","models","#{model.underscore.singularize}.rb")
292
+ file_name = File.join("app", "models", "#{model.underscore.singularize}.rb")
316
293
  return if !File.exist?(file_name) || File.read(file_name).include?(line)
317
294
 
318
295
  file = open(file_name)
319
296
  line_count = file.readlines.size
320
297
  line_nb = 0
321
- file.each do |l|
298
+ IO.readlines(file).each do |l|
322
299
  line_nb += 1
323
- break if l.include?('ApplicationRecord')
300
+ break if l.include?("ApplicationRecord")
324
301
  end
325
- raise 'Your model must inherit from ApplicationRecord to make it work' if line_nb >= line_count
302
+ raise "Your model must inherit from ApplicationRecord to make it work" if line_nb >= line_count
326
303
 
327
- write_at(file_name, line_nb + 2, " #{line}\n")
304
+ write_at(file_name, line_nb + 1, " #{line}\n")
328
305
  end
329
306
 
330
307
  def generate_has_many_migration(resource, has_many:)
@@ -335,7 +312,7 @@ t.#{@id_db_type} :#{resource.underscore.singularize}_id
335
312
  File.write(
336
313
  migration_file,
337
314
  <<~STRING
338
- class Add#{resource.singularize.camelize}IdTo#{has_many.camelize} < ActiveRecord::Migration[5.2]
315
+ class Add#{resource.singularize.camelize}IdTo#{has_many.camelize} < ActiveRecord::Migration[7.0]
339
316
  def change
340
317
  add_column :#{has_many.pluralize}, :#{resource.singularize}_id, :#{@id_db_type}
341
318
  end
@@ -349,7 +326,7 @@ t.#{@id_db_type} :#{resource.underscore.singularize}_id
349
326
  end
350
327
 
351
328
  def write_at(file_name, line, data)
352
- open(file_name, 'r+') do |f|
329
+ open(file_name, "r+") do |f|
353
330
  while (line -= 1).positive?
354
331
  f.readline
355
332
  end
@@ -360,5 +337,4 @@ t.#{@id_db_type} :#{resource.underscore.singularize}_id
360
337
  f.write(rest)
361
338
  end
362
339
  end
363
-
364
340
  end
@@ -24,7 +24,7 @@ module Graphql
24
24
  def run
25
25
  if @id
26
26
  @model = @model.where(id: @id)
27
- deep_pluck_to_structs(@context&.irep_node).first
27
+ deep_pluck_to_structs(irep_node(model_name.singularize)).first
28
28
  else
29
29
  @model = @model.limit(@per_page)
30
30
  @model = @model.offset(@per_page * (@page - 1))
@@ -32,7 +32,7 @@ module Graphql
32
32
  transform_filter if @filter.present?
33
33
  transform_order if @order_by.present?
34
34
 
35
- deep_pluck_to_structs(@context&.irep_node)
35
+ deep_pluck_to_structs(irep_node(model_name.pluralize))
36
36
  end
37
37
  end
38
38
 
@@ -45,7 +45,7 @@ module Graphql
45
45
  @model = @model.offset(@per_page * (@page - 1))
46
46
 
47
47
  OpenStruct.new(
48
- data: deep_pluck_to_structs(@context&.irep_node&.typed_children&.values&.first.try(:[], "data")),
48
+ data: deep_pluck_to_structs(irep_node(model_name.pluralize, paginated: true)),
49
49
  total_count: @total,
50
50
  per_page: @per_page,
51
51
  page: @page
@@ -58,7 +58,7 @@ module Graphql
58
58
  return if @order_by.blank?
59
59
 
60
60
  sign = @order_by.split(" ").last.downcase == "desc" ? "desc" : "asc"
61
- column = @order_by.split(" ").first.strip
61
+ column = @order_by.split(" ").first.strip.underscore
62
62
 
63
63
  if column.include?(".")
64
64
  associated_model = column.split(".").first
@@ -120,6 +120,8 @@ module Graphql
120
120
  handle_LessNode(node, model)
121
121
  elsif node.instance_of?(RKelly::Nodes::GreaterNode)
122
122
  handle_GreaterNode(node, model)
123
+ elsif node.instance_of?(RKelly::Nodes::ModulusNode)
124
+ handle_ModulusNode(node, model)
123
125
  else
124
126
  raise GraphQL::ExecutionError, "Invalid filter: #{node.class} unknown operator"
125
127
  end
@@ -174,9 +176,17 @@ module Graphql
174
176
  if node.instance_of?(RKelly::Nodes::StringNode)
175
177
  val = node.value.gsub(/^'|'$|^"|"$/, "")
176
178
  if sym_type == :datetime
177
- DateTime.parse(val)
179
+ begin
180
+ DateTime.parse(val)
181
+ rescue
182
+ val
183
+ end
178
184
  elsif sym_type == :date
179
- Date.parse(val)
185
+ begin
186
+ DateTime.parse(val)
187
+ rescue
188
+ val
189
+ end
180
190
  elsif sym_type == :integer
181
191
  # Enums are handled here : We are about to compare a string with an integer column
182
192
  # If the symbol and the value correspond to an existing enum into the model
@@ -208,6 +218,7 @@ module Graphql
208
218
 
209
219
  def handle_NotEqualNode(node, model)
210
220
  arel_field, model, type, value = handle_operator_node(node, model)
221
+ arel_field.name = arel_field.name.underscore
211
222
 
212
223
  if value.nil?
213
224
  model.where.not(arel_field.eq(nil))
@@ -220,6 +231,7 @@ module Graphql
220
231
 
221
232
  def handle_NotStrictEqualNode(node, model)
222
233
  arel_field, model, type, value = handle_operator_node(node, model)
234
+ arel_field.name = arel_field.name.underscore
223
235
 
224
236
  if value.nil?
225
237
  model.where.not(arel_field.eq(nil))
@@ -232,6 +244,7 @@ module Graphql
232
244
 
233
245
  def handle_EqualNode(node, model)
234
246
  arel_field, model, type, value = handle_operator_node(node, model)
247
+ arel_field.name = arel_field.name.underscore
235
248
 
236
249
  if value.nil?
237
250
  model.where(arel_field.eq(nil))
@@ -244,6 +257,7 @@ module Graphql
244
257
 
245
258
  def handle_StrictEqualNode(node, model)
246
259
  arel_field, model, type, value = handle_operator_node(node, model)
260
+ arel_field.name = arel_field.name.underscore
247
261
 
248
262
  if value.nil?
249
263
  model.where(arel_field.eq(nil))
@@ -256,24 +270,68 @@ module Graphql
256
270
 
257
271
  def handle_GreaterOrEqualNode(node, model)
258
272
  arel_field, model, type, value = handle_operator_node(node, model)
273
+ arel_field.name = arel_field.name.underscore
274
+
259
275
  model.where(arel_field.gteq(value))
260
276
  end
261
277
 
262
278
  def handle_LessOrEqualNode(node, model)
263
279
  arel_field, model, type, value = handle_operator_node(node, model)
280
+ arel_field.name = arel_field.name.underscore
281
+
264
282
  model.where(arel_field.lteq(value))
265
283
  end
266
284
 
267
285
  def handle_LessNode(node, model)
268
286
  arel_field, model, type, value = handle_operator_node(node, model)
287
+ arel_field.name = arel_field.name.underscore
288
+
269
289
  model.where(arel_field.lt(value))
270
290
  end
271
291
 
272
292
  def handle_GreaterNode(node, model)
273
293
  arel_field, model, type, value = handle_operator_node(node, model)
294
+ arel_field.name = arel_field.name.underscore
295
+
274
296
  model.where(arel_field.gt(value))
275
297
  end
276
298
 
299
+ # Example: "US" should match "US" and "USA" and "AUS"
300
+ def handle_ModulusNode(node, model)
301
+ arel_field, model, type, value = handle_operator_node(node, model)
302
+ arel_field.name = arel_field.name.underscore
303
+
304
+ if value.nil?
305
+ model.where(arel_field.eq(nil))
306
+ elsif type == :text || type == :string
307
+ model.where(arel_field.matches("%" + sanitize_sql_like(value) + "%"))
308
+ elsif type == :datetime
309
+ if value.instance_of?(DateTime)
310
+ model.where(arel_field.gteq(value).and(arel_field.lteq(value + 1.day)))
311
+ else
312
+ model.where(
313
+ Arel::Nodes::NamedFunction.new(
314
+ "CAST",
315
+ [Arel::Nodes::As.new(arel_field, Arel::Nodes::SqlLiteral.new("text"))]
316
+ ).matches("%" + sanitize_sql_like(value) + "%")
317
+ )
318
+ end
319
+ elsif type == :uuid
320
+ model.where(
321
+ Arel::Nodes::NamedFunction.new(
322
+ "CAST",
323
+ [Arel::Nodes::As.new(arel_field, Arel::Nodes::SqlLiteral.new("text"))]
324
+ ).matches("%" + sanitize_sql_like(value) + "%")
325
+ )
326
+ else
327
+ model.where(arel_field.eq(value))
328
+ end
329
+ end
330
+
331
+ def uuid_to_string(uuid)
332
+ uuid.delete("-")
333
+ end
334
+
277
335
  def valid_id?(id)
278
336
  valid_uuid?(id) || id.is_a?(Integer)
279
337
  end
@@ -291,7 +349,7 @@ module Graphql
291
349
  end
292
350
 
293
351
  def get_field_type!(model, field_name)
294
- field = model.column_for_attribute(field_name.to_sym)
352
+ field = model.column_for_attribute(field_name.underscore.to_sym)
295
353
  unless field.present?
296
354
  raise GraphQL::ExecutionError, "Invalid filter: #{field_name} is not a field of #{model}"
297
355
  end
@@ -371,13 +429,29 @@ module Graphql
371
429
  parent_class_name.constantize.reflections[child.to_s.underscore]&.klass
372
430
  end
373
431
 
374
- def parse_fields(irep_node)
375
- fields = irep_node&.scoped_children&.values&.first
376
- fields = fields["edges"].scoped_children.values.first["node"]&.scoped_children&.values&.first if fields&.key?("edges")
432
+ def irep_node(name, paginated: false)
433
+ child = @context.query.lookahead.ast_nodes.first.children[@context[:query_index]]
434
+ result = if paginated
435
+ child.children.find { |n| n.name == "data" }.children
436
+ else
437
+ child.children
438
+ end
439
+ @context[:query_index] += 1
440
+ result
441
+ end
442
+
443
+ def parse_fields(global_fields)
444
+ return if global_fields.blank?
445
+
446
+ fields = global_fields.map do |field|
447
+ next if field.class != GraphQL::Language::Nodes::Field
448
+ field
449
+ end.compact
450
+
377
451
  return if fields.blank?
378
452
 
379
- fields.each_with_object({}) do |(k, v), h|
380
- h[k] = v.scoped_children == {} ? v.definition.name : parse_fields(v)
453
+ fields.each_with_object({}) do |node, h|
454
+ h[node.name.underscore] = node.children.blank? ? nil : parse_fields(node.children)
381
455
  end
382
456
  end
383
457
 
@@ -1,7 +1,7 @@
1
1
  module Graphql
2
2
  module Rails
3
3
  module Api
4
- VERSION = "0.9.3".freeze
4
+ VERSION = "0.9.6".freeze
5
5
  end
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql-rails-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.3
4
+ version: 0.9.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - poilon
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-09-06 00:00:00.000000000 Z
11
+ date: 2022-11-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: graphql
@@ -16,20 +16,20 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.12'
19
+ version: 2.0.15
20
20
  - - "<="
21
21
  - !ruby/object:Gem::Version
22
- version: 1.12.13
22
+ version: 2.0.15
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - "~>"
28
28
  - !ruby/object:Gem::Version
29
- version: '1.12'
29
+ version: 2.0.15
30
30
  - - "<="
31
31
  - !ruby/object:Gem::Version
32
- version: 1.12.13
32
+ version: 2.0.15
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: deep_pluck_with_authorization
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -50,14 +50,14 @@ dependencies:
50
50
  requirements:
51
51
  - - ">="
52
52
  - !ruby/object:Gem::Version
53
- version: 5.1.4
53
+ version: '0'
54
54
  type: :runtime
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - ">="
59
59
  - !ruby/object:Gem::Version
60
- version: 5.1.4
60
+ version: '0'
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: rkelly-remix
63
63
  requirement: !ruby/object:Gem::Requirement