graphql-rails-api 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +37 -0
- data/Rakefile +33 -0
- data/lib/generators/graphql_mutations/graphql_mutations_generator.rb +68 -0
- data/lib/generators/graphql_rails_api/install_generator.rb +424 -0
- data/lib/generators/graphql_resource/USAGE +8 -0
- data/lib/generators/graphql_resource/graphql_resource_generator.rb +330 -0
- data/lib/graphql/hydrate_query.rb +136 -0
- data/lib/graphql/rails/api/config.rb +30 -0
- data/lib/graphql/rails/api/version.rb +7 -0
- data/lib/tasks/graphql/rails/api_tasks.rake +4 -0
- metadata +84 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 106d6fbb14638d87cf01f586da3eac910f9287e7
|
4
|
+
data.tar.gz: 4ce1326fb1e76cb6ca8d9ea265fc6f817f08edae
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 233e224a5adbdbfe1e40c7cb1e21e892a1846c4df5dbd7fe25665573fb6c0c78167fdf9f71807a5cfa61609c46df8d7daacfb9637252f8bd642fca9af11e1b0f
|
7
|
+
data.tar.gz: 2a5134db639e3659c46859603f9aabf912385e14d713369db9da1ee24c6955fe13e6a90b8620f409e22d708eab551f4dc9913406135070d5bc166cc2af40ed85
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2018 poilon
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# GraphqlRailsApi
|
2
|
+
|
3
|
+
`graphql-rails-api` is a gem that provide generators to describe easily your graphql API.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
Add these lines to your application's Gemfile:
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
gem 'graphql'
|
10
|
+
gem 'graphql-rails-api'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
```bash
|
15
|
+
$ bundle
|
16
|
+
$ rails generate graphql_rails_api:install
|
17
|
+
```
|
18
|
+
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
```bash
|
23
|
+
$ rails generate graphql_resource account base_email:string auth_id:string
|
24
|
+
$ rails generate graphql_resource user email:string first_name:string last_name:string has_many:users
|
25
|
+
$ rails generate graphql_resource computer ref:string description:text belongs_to:user
|
26
|
+
$ rails generate graphql_resource motherboard ref:string many_to_many:computers
|
27
|
+
|
28
|
+
```
|
29
|
+
|
30
|
+
|
31
|
+
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
Contribution directions go here.
|
35
|
+
|
36
|
+
## License
|
37
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'Graphql::Rails::Api'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.md')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
require 'bundler/gem_tasks'
|
23
|
+
|
24
|
+
require 'rake/testtask'
|
25
|
+
|
26
|
+
Rake::TestTask.new(:test) do |t|
|
27
|
+
t.libs << 'test'
|
28
|
+
t.pattern = 'test/**/*_test.rb'
|
29
|
+
t.verbose = false
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
task default: :test
|
@@ -0,0 +1,68 @@
|
|
1
|
+
class GraphqlMutationsGenerator < Rails::Generators::NamedBase
|
2
|
+
|
3
|
+
def generate
|
4
|
+
@id = Graphql::Rails::Api::Config.instance.id_type == :uuid ? '!types.String' : '!types.ID'
|
5
|
+
resource = file_name.underscore.singularize
|
6
|
+
dir = "app/graphql/#{resource.pluralize}/mutations"
|
7
|
+
system("mkdir -p #{dir}")
|
8
|
+
generate_create_mutation(dir, resource)
|
9
|
+
generate_update_mutation(dir, resource)
|
10
|
+
generate_destroy_mutation(dir, resource)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def generate_create_mutation(dir, resource)
|
16
|
+
File.write(
|
17
|
+
"#{dir}/create.rb",
|
18
|
+
<<~STRING
|
19
|
+
#{resource_class(resource)}::Mutations::Create = GraphQL::Field.define do
|
20
|
+
description 'Creates a #{resource_class(resource).singularize}'
|
21
|
+
type #{resource_class(resource)}::Type
|
22
|
+
|
23
|
+
argument :#{resource}, #{resource_class(resource)}::Mutations::InputType
|
24
|
+
|
25
|
+
resolve ApplicationService.call(:#{resource}, :create)
|
26
|
+
end
|
27
|
+
STRING
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
def generate_update_mutation(dir, resource)
|
32
|
+
File.write(
|
33
|
+
"#{dir}/update.rb",
|
34
|
+
<<~STRING
|
35
|
+
#{resource_class(resource)}::Mutations::Update = GraphQL::Field.define do
|
36
|
+
description 'Updates a #{resource_class(resource).singularize}'
|
37
|
+
type #{resource_class(resource)}::Type
|
38
|
+
|
39
|
+
argument :id, #{@id}
|
40
|
+
argument :#{resource}, #{resource_class(resource)}::Mutations::InputType
|
41
|
+
|
42
|
+
resolve ApplicationService.call(:#{resource}, :update)
|
43
|
+
end
|
44
|
+
STRING
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
def generate_destroy_mutation(dir, resource)
|
49
|
+
File.write(
|
50
|
+
"#{dir}/destroy.rb",
|
51
|
+
<<~STRING
|
52
|
+
#{resource_class(resource)}::Mutations::Destroy = GraphQL::Field.define do
|
53
|
+
description 'Destroys a #{resource_class(resource).singularize}'
|
54
|
+
type #{resource_class(resource)}::Type
|
55
|
+
|
56
|
+
argument :id, #{@id}
|
57
|
+
|
58
|
+
resolve ApplicationService.call(:#{resource}, :destroy)
|
59
|
+
end
|
60
|
+
STRING
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
def resource_class(resource)
|
65
|
+
@resource_class ||= resource.pluralize.camelize
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,424 @@
|
|
1
|
+
require 'graphql/rails/api/config'
|
2
|
+
|
3
|
+
module GraphqlRailsApi
|
4
|
+
class InstallGenerator < Rails::Generators::Base
|
5
|
+
|
6
|
+
class_option('apollo_compatibility', type: :boolean, default: true)
|
7
|
+
class_option('action_cable_subs', type: :boolean, default: true)
|
8
|
+
class_option('pg_uuid', type: :boolean, default: true)
|
9
|
+
|
10
|
+
def generate_files
|
11
|
+
@app_name = File.basename(Rails.root.to_s).underscore
|
12
|
+
system('mkdir -p app/graphql/')
|
13
|
+
|
14
|
+
write_service
|
15
|
+
write_application_record_methods
|
16
|
+
write_schema
|
17
|
+
write_query_type
|
18
|
+
write_mutation_type
|
19
|
+
write_subscription_type
|
20
|
+
write_controller
|
21
|
+
write_channel if options.action_cable_subs?
|
22
|
+
write_initializer
|
23
|
+
write_require_application_rb
|
24
|
+
write_uuid_extensions_migration if options.pg_uuid?
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def write_application_record_methods
|
30
|
+
lines_count = File.read('app/models/application_record.rb').lines.count
|
31
|
+
|
32
|
+
return if File.read('app/models/application_record.rb').include?('def self.visible_for')
|
33
|
+
write_at(
|
34
|
+
'app/models/application_record.rb',
|
35
|
+
lines_count,
|
36
|
+
<<-STRING
|
37
|
+
def self.visible_for(*)
|
38
|
+
all
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.writable_by(*)
|
42
|
+
all
|
43
|
+
end
|
44
|
+
|
45
|
+
STRING
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
def write_require_application_rb
|
50
|
+
File.write(
|
51
|
+
'config/application.rb',
|
52
|
+
File.read('config/application.rb').gsub(
|
53
|
+
"require 'rails/all'",
|
54
|
+
"require 'rails/all'\nrequire 'graphql/hydrate_query'\n"
|
55
|
+
)
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
def write_uuid_extensions_migration
|
60
|
+
system('bundle exec rails generate migration uuid_pg_extensions --skip')
|
61
|
+
migration_file = Dir.glob('db/migrate/*uuid_pg_extensions*').last
|
62
|
+
File.write(
|
63
|
+
migration_file,
|
64
|
+
<<~STRING
|
65
|
+
class UuidPgExtensions < ActiveRecord::Migration[5.1]
|
66
|
+
|
67
|
+
def change
|
68
|
+
execute 'CREATE EXTENSION "pgcrypto" SCHEMA pg_catalog;'
|
69
|
+
execute 'CREATE EXTENSION "uuid-ossp" SCHEMA pg_catalog;'
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
STRING
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
77
|
+
def write_initializer
|
78
|
+
File.write(
|
79
|
+
'config/initializers/graphql_rails_api_config.rb',
|
80
|
+
<<~STRING
|
81
|
+
require 'graphql/rails/api/config'
|
82
|
+
|
83
|
+
config = Graphql::Rails::Api::Config.instance
|
84
|
+
|
85
|
+
config.id_type = #{options.pg_uuid? ? ':uuid' : ':id'} # :id or :uuid
|
86
|
+
|
87
|
+
# Possibilites are :create, :update or :destroy
|
88
|
+
config.basic_mutations = %i[create update destroy]
|
89
|
+
STRING
|
90
|
+
)
|
91
|
+
end
|
92
|
+
|
93
|
+
def write_channel
|
94
|
+
File.write(
|
95
|
+
'app/channels/graphql_channel.rb',
|
96
|
+
<<~STRING
|
97
|
+
class GraphqlChannel < ApplicationCable::Channel
|
98
|
+
|
99
|
+
def subscribed
|
100
|
+
@subscription_ids = []
|
101
|
+
end
|
102
|
+
|
103
|
+
# see graphql-ruby from details
|
104
|
+
def execute(data)
|
105
|
+
query, context, variables, operation_name = options_for_execute(data)
|
106
|
+
result = #{@app_name.camelize}Schema.execute(query: query, context: context,
|
107
|
+
variables: variables, operation_name: operation_name)
|
108
|
+
payload = { result: result.subscription? ? nil : result.to_h, more: result.subscription?,
|
109
|
+
errors: result ? result.to_h[:errors] : nil }
|
110
|
+
@subscription_ids << result.context[:subscription_id] if result.context[:subscription_id]
|
111
|
+
transmit(payload)
|
112
|
+
end
|
113
|
+
|
114
|
+
def unsubscribed
|
115
|
+
@subscription_ids.each do |sid|
|
116
|
+
#{@app_name.camelize}Schema.subscriptions.delete_subscription(sid)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def options_for_execute(data)
|
121
|
+
query = data['query']
|
122
|
+
variables = ensure_hash(data['variables'])
|
123
|
+
operation_name = data['operationName']
|
124
|
+
context = { current_user: current_user, channel: self }.
|
125
|
+
merge(ensure_hash(data['context']).symbolize_keys). # ensure context is filled
|
126
|
+
merge(variables) # include variables in context too
|
127
|
+
[query, context, variables, operation_name]
|
128
|
+
end
|
129
|
+
|
130
|
+
# Handle form data, JSON body, or a blank value
|
131
|
+
def ensure_hash(ambiguous_param)
|
132
|
+
case ambiguous_param
|
133
|
+
when String
|
134
|
+
ambiguous_param.present? ? ensure_hash(JSON.parse(ambiguous_param)) : {}
|
135
|
+
when Hash, ActionController::Parameters
|
136
|
+
ambiguous_param
|
137
|
+
when nil
|
138
|
+
{}
|
139
|
+
else
|
140
|
+
raise ArgumentError, 'Unexpected parameter: ' + ambiguous_param
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
STRING
|
146
|
+
)
|
147
|
+
end
|
148
|
+
|
149
|
+
def write_controller
|
150
|
+
File.write(
|
151
|
+
'app/controllers/graphql_controller.rb',
|
152
|
+
<<~STRING
|
153
|
+
class GraphqlController < ApplicationController
|
154
|
+
|
155
|
+
# GraphQL endpoint
|
156
|
+
def execute
|
157
|
+
result = #{@app_name.camelize}Schema.execute(
|
158
|
+
params[:query],
|
159
|
+
variables: ensure_hash(params[:variables]),
|
160
|
+
context: { current_user: authenticated_user },
|
161
|
+
operation_name: params[:operationName]
|
162
|
+
)
|
163
|
+
render json: result
|
164
|
+
end
|
165
|
+
|
166
|
+
private
|
167
|
+
|
168
|
+
def authenticated_user
|
169
|
+
# Here you need to authenticate the user.
|
170
|
+
# You can use devise, then just write:
|
171
|
+
current_user
|
172
|
+
end
|
173
|
+
|
174
|
+
# Handle form data, JSON body, or a blank value
|
175
|
+
def ensure_hash(ambiguous_param)
|
176
|
+
case ambiguous_param
|
177
|
+
when String
|
178
|
+
ambiguous_param.present? ? ensure_hash(JSON.parse(ambiguous_param)) : {}
|
179
|
+
when Hash, ActionController::Parameters
|
180
|
+
ambiguous_param
|
181
|
+
when nil
|
182
|
+
{}
|
183
|
+
else
|
184
|
+
raise ArgumentError, 'Unexpected parameter: ' + ambiguous_param
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
STRING
|
190
|
+
)
|
191
|
+
end
|
192
|
+
|
193
|
+
def write_subscription_type
|
194
|
+
File.write(
|
195
|
+
'app/graphql/subscription_type.rb',
|
196
|
+
<<~STRING
|
197
|
+
SubscriptionType = GraphQL::ObjectType.define do
|
198
|
+
name 'Subscription'
|
199
|
+
end
|
200
|
+
STRING
|
201
|
+
)
|
202
|
+
end
|
203
|
+
|
204
|
+
def write_mutation_type
|
205
|
+
File.write(
|
206
|
+
'app/graphql/mutation_type.rb',
|
207
|
+
<<~'STRING'
|
208
|
+
MutationType = GraphQL::ObjectType.define do
|
209
|
+
name 'Mutation'
|
210
|
+
|
211
|
+
Graphql::Rails::Api::Config.mutation_resources.each do |methd, resources|
|
212
|
+
resources.each do |resource|
|
213
|
+
field(
|
214
|
+
"#{methd}_#{resource.singularize}".to_sym,
|
215
|
+
"#{resource.camelize}::Mutations::#{methd.camelize}".constantize
|
216
|
+
)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
221
|
+
STRING
|
222
|
+
)
|
223
|
+
end
|
224
|
+
|
225
|
+
def write_query_type
|
226
|
+
File.write(
|
227
|
+
'app/graphql/query_type.rb',
|
228
|
+
<<~'STRING'
|
229
|
+
QueryType = GraphQL::ObjectType.define do
|
230
|
+
name 'Query'
|
231
|
+
|
232
|
+
Graphql::Rails::Api::Config.query_resources.each do |resource|
|
233
|
+
field resource.singularize do
|
234
|
+
description "Return a #{resource.classify}"
|
235
|
+
type !"#{resource.camelize}::Type".constantize
|
236
|
+
argument :id, !types.String
|
237
|
+
resolve ApplicationService.call(resource, :show)
|
238
|
+
end
|
239
|
+
|
240
|
+
field resource.pluralize do
|
241
|
+
description "Return a #{resource.classify}"
|
242
|
+
type !types[!"#{resource.camelize}::Type".constantize]
|
243
|
+
argument :page, types.Int
|
244
|
+
argument :per_page, types.Int
|
245
|
+
resolve ApplicationService.call(resource, :index)
|
246
|
+
end
|
247
|
+
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|
251
|
+
STRING
|
252
|
+
)
|
253
|
+
end
|
254
|
+
|
255
|
+
def apollo_compat
|
256
|
+
<<~'STRING'
|
257
|
+
# /!\ do not remove /!\
|
258
|
+
# Apollo Data compat.
|
259
|
+
ClientDirective = GraphQL::Directive.define do
|
260
|
+
name 'client'
|
261
|
+
locations([GraphQL::Directive::FIELD])
|
262
|
+
default_directive true
|
263
|
+
end
|
264
|
+
ConnectionDirective = GraphQL::Directive.define do
|
265
|
+
name 'connection'
|
266
|
+
locations([GraphQL::Directive::FIELD])
|
267
|
+
argument :key, GraphQL::STRING_TYPE
|
268
|
+
argument :filter, GraphQL::STRING_TYPE.to_list_type
|
269
|
+
default_directive true
|
270
|
+
end
|
271
|
+
# end of Apollo Data compat.
|
272
|
+
STRING
|
273
|
+
end
|
274
|
+
|
275
|
+
def write_schema
|
276
|
+
logger = <<~'STRING'
|
277
|
+
type_error_logger = Logger.new("#{Rails.root}/log/graphql_type_errors.log")
|
278
|
+
STRING
|
279
|
+
|
280
|
+
error_handler = <<~'STRING'
|
281
|
+
type_error_logger.error "#{err} for #{query_ctx.query.query_string} \
|
282
|
+
with #{query_ctx.query.provided_variables}"
|
283
|
+
STRING
|
284
|
+
|
285
|
+
File.write(
|
286
|
+
"app/graphql/#{@app_name}_schema.rb",
|
287
|
+
<<~STRING
|
288
|
+
#{logger}
|
289
|
+
#{apollo_compat if options.apollo_compatibility?}
|
290
|
+
# Schema definition
|
291
|
+
#{@app_name.camelize}Schema = GraphQL::Schema.define do
|
292
|
+
mutation(MutationType)
|
293
|
+
query(QueryType)
|
294
|
+
#{'directives [ConnectionDirective, ClientDirective]' if options.apollo_compatibility?}
|
295
|
+
#{'use GraphQL::Subscriptions::ActionCableSubscriptions' if options.action_cable_subs?}
|
296
|
+
subscription(SubscriptionType)
|
297
|
+
type_error lambda { |err, query_ctx|
|
298
|
+
#{error_handler}
|
299
|
+
}
|
300
|
+
end
|
301
|
+
STRING
|
302
|
+
)
|
303
|
+
end
|
304
|
+
|
305
|
+
def write_service
|
306
|
+
File.write(
|
307
|
+
'app/graphql/application_service.rb',
|
308
|
+
<<~'STRING'
|
309
|
+
class ApplicationService
|
310
|
+
|
311
|
+
attr_accessor :params, :object, :fields, :user
|
312
|
+
|
313
|
+
def initialize(params: {}, object: nil, object_id: nil, user: nil, context: nil)
|
314
|
+
@params = params.to_h.symbolize_keys
|
315
|
+
@context = context
|
316
|
+
@object = object || (object_id && model.visible_for(user: user).find_by(id: object_id))
|
317
|
+
@object_id = object_id
|
318
|
+
@user = user
|
319
|
+
end
|
320
|
+
|
321
|
+
def self.call(resource, meth)
|
322
|
+
lambda { |_obj, args, context|
|
323
|
+
params = args && args[resource] ? args[resource] : args
|
324
|
+
"#{resource.to_s.pluralize.camelize.constantize}::Service".constantize.new(
|
325
|
+
params: params, user: context[:current_user],
|
326
|
+
object_id: args[:id], context: context
|
327
|
+
).send(meth)
|
328
|
+
}
|
329
|
+
end
|
330
|
+
|
331
|
+
def index
|
332
|
+
Graphql::HydrateQuery.new(model.visible_for(user: @user), @context).run
|
333
|
+
end
|
334
|
+
|
335
|
+
def show
|
336
|
+
puts 'SHOW'
|
337
|
+
object = Graphql::HydrateQuery.new(model.visible_for(user: @user), @context, id: params[:id]).run
|
338
|
+
return not_allowed if object.blank?
|
339
|
+
object
|
340
|
+
end
|
341
|
+
|
342
|
+
def create
|
343
|
+
puts 'CREATE'
|
344
|
+
object = model.new(params.select { |p| model.new.respond_to?(p) })
|
345
|
+
if object.save
|
346
|
+
object
|
347
|
+
else
|
348
|
+
graphql_error(object.errors.full_messages.join(', '))
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
def destroy
|
353
|
+
puts 'DESTROY'
|
354
|
+
object = model.find_by(id: params[:id])
|
355
|
+
return not_allowed if write_not_allowed
|
356
|
+
if object.destroy
|
357
|
+
object
|
358
|
+
else
|
359
|
+
graphql_error(object.errors.full_messages.join(', '))
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
def update
|
364
|
+
puts "UPDATE, #{params}, #{object}"
|
365
|
+
return not_allowed if write_not_allowed
|
366
|
+
if object.update_attributes(params)
|
367
|
+
object
|
368
|
+
else
|
369
|
+
graphql_error(object.errors.full_messages.join(', '))
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
private
|
374
|
+
|
375
|
+
def write_not_allowed
|
376
|
+
!model.visible_for(user: user).include?(object) if object
|
377
|
+
end
|
378
|
+
|
379
|
+
def access_not_allowed
|
380
|
+
!model.visible_for(user: user).include?(object) if object
|
381
|
+
end
|
382
|
+
|
383
|
+
def not_allowed
|
384
|
+
graphql_error('403 - Not allowed')
|
385
|
+
end
|
386
|
+
|
387
|
+
def graphql_error(message)
|
388
|
+
GraphQL::ExecutionError.new(message)
|
389
|
+
end
|
390
|
+
|
391
|
+
def singular_resource
|
392
|
+
resource_name.singularize
|
393
|
+
end
|
394
|
+
|
395
|
+
def model
|
396
|
+
singular_resource.camelize.constantize
|
397
|
+
end
|
398
|
+
|
399
|
+
def resource_name
|
400
|
+
self.class.to_s.split(':').first.underscore
|
401
|
+
end
|
402
|
+
|
403
|
+
end
|
404
|
+
|
405
|
+
STRING
|
406
|
+
)
|
407
|
+
end
|
408
|
+
|
409
|
+
|
410
|
+
def write_at(file_name, line, data)
|
411
|
+
open(file_name, 'r+') do |f|
|
412
|
+
while (line -= 1).positive?
|
413
|
+
f.readline
|
414
|
+
end
|
415
|
+
pos = f.pos
|
416
|
+
rest = f.read
|
417
|
+
f.seek pos
|
418
|
+
f.write data
|
419
|
+
f.write rest
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
end
|
424
|
+
end
|
@@ -0,0 +1,330 @@
|
|
1
|
+
class GraphqlResourceGenerator < Rails::Generators::NamedBase
|
2
|
+
|
3
|
+
%i[migration model mutations service graphql_input_type graphql_type propagation migrate].each do |opt|
|
4
|
+
class_option(opt, type: :boolean, default: true)
|
5
|
+
end
|
6
|
+
|
7
|
+
TYPES_MAPPING = {
|
8
|
+
'id' => '!types.ID',
|
9
|
+
'uuid' => '!types.String',
|
10
|
+
'text' => 'types.String',
|
11
|
+
'datetime' => 'types.String',
|
12
|
+
'integer' => 'types.Int',
|
13
|
+
'json' => 'types.String',
|
14
|
+
'jsonb' => 'types.String'
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
def create_graphql_files
|
18
|
+
return if args.blank?
|
19
|
+
parse_args
|
20
|
+
|
21
|
+
# Generate migration
|
22
|
+
generate_create_migration(@resource, @fields_to_migration) if options.migration?
|
23
|
+
|
24
|
+
# Graphql Basic mutations
|
25
|
+
generate_basic_mutations(@resource) if options.mutations?
|
26
|
+
|
27
|
+
# Graphql Type
|
28
|
+
generate_graphql_type(@resource) if options.graphql_type?
|
29
|
+
|
30
|
+
# Model
|
31
|
+
generate_model(@resource) if options.model?
|
32
|
+
|
33
|
+
# Service
|
34
|
+
generate_service(@resource) if options.service?
|
35
|
+
handle_many_to_many_fields(@resource) if options.propagation?
|
36
|
+
|
37
|
+
# Propagation
|
38
|
+
add_has_many_to_models(@resource) if options.propagation?
|
39
|
+
add_has_many_fields_to_types(@resource) if options.propagation?
|
40
|
+
|
41
|
+
system('bundle exec rails db:migrate') if options.migrate?
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def types_mapping(type)
|
47
|
+
TYPES_MAPPING[type] || "types.#{type.capitalize}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def parse_args
|
51
|
+
if Graphql::Rails::Api::Config.instance.id_type == :uuid
|
52
|
+
@id_db_type = 'uuid'
|
53
|
+
@id_type = '!types.String'
|
54
|
+
else
|
55
|
+
@id_db_type = 'integer'
|
56
|
+
@id_type = '!types.ID'
|
57
|
+
end
|
58
|
+
|
59
|
+
@resource = file_name.singularize
|
60
|
+
@has_many = []
|
61
|
+
@many_to_many = []
|
62
|
+
@mutations_directory = "#{graphql_resource_directory(@resource)}/mutations"
|
63
|
+
|
64
|
+
@args = args.each_with_object({}) do |f, hash|
|
65
|
+
next if f.split(':').count != 2
|
66
|
+
case f.split(':').first
|
67
|
+
when 'belongs_to' then hash["#{f.split(':').last.singularize}_id"] = @id_db_type
|
68
|
+
when 'has_many' then @has_many << f.split(':').last.pluralize
|
69
|
+
when 'many_to_many' then @many_to_many << f.split(':').last.pluralize
|
70
|
+
else
|
71
|
+
hash[f.split(':').first] = f.split(':').last
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
@id_fields = @args.select { |k, _| k.end_with?('_id') }
|
76
|
+
|
77
|
+
@fields_to_migration = @args.map do |f|
|
78
|
+
"t.#{f.reverse.join(' :')}"
|
79
|
+
end.join("\n ")
|
80
|
+
end
|
81
|
+
|
82
|
+
def graphql_resource_directory(resource)
|
83
|
+
"app/graphql/#{resource.pluralize}"
|
84
|
+
end
|
85
|
+
|
86
|
+
def generate_create_migration(resource, fields)
|
87
|
+
system("bundle exec rails generate migration create_#{resource} --skip")
|
88
|
+
migration_file = Dir.glob("db/migrate/*create_#{resource}*").last
|
89
|
+
File.write(
|
90
|
+
migration_file,
|
91
|
+
<<~STRING
|
92
|
+
class Create#{resource.camelize} < ActiveRecord::Migration[5.1]
|
93
|
+
def change
|
94
|
+
create_table :#{resource.pluralize}, #{'id: :uuid ' if Graphql::Rails::Api::Config.instance.id_type == :uuid}do |t|
|
95
|
+
#{fields}
|
96
|
+
t.timestamps
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
STRING
|
101
|
+
)
|
102
|
+
end
|
103
|
+
|
104
|
+
def generate_basic_mutations(resource)
|
105
|
+
system("mkdir -p #{@mutations_directory}")
|
106
|
+
system("rails generate graphql_mutations #{resource}")
|
107
|
+
|
108
|
+
# Graphql Input Type
|
109
|
+
generate_graphql_input_type(resource) if options.graphql_input_type?
|
110
|
+
end
|
111
|
+
|
112
|
+
def generate_graphql_input_type(resource)
|
113
|
+
system("mkdir -p #{@mutations_directory}")
|
114
|
+
File.write(
|
115
|
+
"#{@mutations_directory}/input_type.rb",
|
116
|
+
<<~STRING
|
117
|
+
#{resource_class(resource)}::Mutations::InputType = GraphQL::InputObjectType.define do
|
118
|
+
name '#{resource_class(resource).singularize}InputType'
|
119
|
+
description 'Properties for updating a #{resource_class(resource).singularize}'
|
120
|
+
|
121
|
+
#{map_types(input_type: true)}
|
122
|
+
|
123
|
+
end
|
124
|
+
STRING
|
125
|
+
)
|
126
|
+
end
|
127
|
+
|
128
|
+
def generate_graphql_type(resource)
|
129
|
+
File.write(
|
130
|
+
"#{graphql_resource_directory(resource)}/type.rb",
|
131
|
+
<<~STRING
|
132
|
+
#{resource_class(resource)}::Type = GraphQL::ObjectType.define do
|
133
|
+
name '#{resource_class(resource).singularize}'
|
134
|
+
field :id, #{@id_type}
|
135
|
+
field :created_at, types.String
|
136
|
+
field :updated_at, types.String
|
137
|
+
#{map_types(input_type: false)}
|
138
|
+
end
|
139
|
+
STRING
|
140
|
+
)
|
141
|
+
end
|
142
|
+
|
143
|
+
def generate_model(resource)
|
144
|
+
generate_empty_model(resource)
|
145
|
+
end
|
146
|
+
|
147
|
+
def add_has_many_fields_to_type(field, resource)
|
148
|
+
file_name = "app/graphql/#{field.pluralize}/type.rb"
|
149
|
+
if File.read(file_name).include?("field :#{resource.singularize}_ids") ||
|
150
|
+
File.read(file_name).include?("field :#{resource.pluralize}")
|
151
|
+
return
|
152
|
+
end
|
153
|
+
write_at(
|
154
|
+
file_name, 4,
|
155
|
+
<<-STRING
|
156
|
+
field :#{resource.singularize}_ids, !types[#{@id_type}]
|
157
|
+
field :#{resource.pluralize}, !types[!#{resource.pluralize.camelize}::Type]
|
158
|
+
STRING
|
159
|
+
)
|
160
|
+
|
161
|
+
input_type_file_name = "app/graphql/#{field.pluralize}/mutations/input_type.rb"
|
162
|
+
if File.read(input_type_file_name).include?("argument :#{resource.singularize}_id") ||
|
163
|
+
File.read(input_type_file_name).include?("argument :#{resource.singularize}")
|
164
|
+
return
|
165
|
+
end
|
166
|
+
write_at(
|
167
|
+
input_type_file_name, 4,
|
168
|
+
<<-STRING
|
169
|
+
argument :#{resource.singularize}_ids, !types[#{@id_type}]
|
170
|
+
STRING
|
171
|
+
)
|
172
|
+
|
173
|
+
end
|
174
|
+
|
175
|
+
def add_belongs_to_field_to_type(field, resource)
|
176
|
+
file_name = "app/graphql/#{resource.pluralize}/type.rb"
|
177
|
+
if File.read(file_name).include?("field :#{field.singularize}_id") ||
|
178
|
+
File.read(file_name).include?("field :#{field.singularize}")
|
179
|
+
return
|
180
|
+
end
|
181
|
+
write_at(
|
182
|
+
file_name, 4,
|
183
|
+
<<-STRING
|
184
|
+
field :#{field.singularize}_id, #{@id_type}
|
185
|
+
field :#{field.singularize}, !#{field.pluralize.camelize}::Type
|
186
|
+
STRING
|
187
|
+
)
|
188
|
+
input_type_file_name = "app/graphql/#{resource.pluralize}/mutations/input_type.rb"
|
189
|
+
if File.read(input_type_file_name).include?("argument :#{field.singularize}_id") ||
|
190
|
+
File.read(input_type_file_name).include?("argument :#{field.singularize}")
|
191
|
+
return
|
192
|
+
end
|
193
|
+
write_at(
|
194
|
+
input_type_file_name, 4,
|
195
|
+
<<-STRING
|
196
|
+
argument :#{field.singularize}_id, #{@id_type}
|
197
|
+
STRING
|
198
|
+
)
|
199
|
+
end
|
200
|
+
|
201
|
+
def add_has_many_fields_to_types(resource)
|
202
|
+
@has_many.each do |f|
|
203
|
+
add_has_many_fields_to_type(resource, f)
|
204
|
+
add_belongs_to_field_to_type(resource, f)
|
205
|
+
end
|
206
|
+
@id_fields.each do |f, _|
|
207
|
+
add_has_many_fields_to_type(f.gsub('_id', ''), resource)
|
208
|
+
add_belongs_to_field_to_type(f.gsub('_id', ''), resource)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def generate_empty_model(resource)
|
213
|
+
File.write(
|
214
|
+
"app/models/#{resource}.rb",
|
215
|
+
<<~STRING
|
216
|
+
class #{resource.singularize.camelize} < ApplicationRecord
|
217
|
+
|
218
|
+
end
|
219
|
+
STRING
|
220
|
+
)
|
221
|
+
end
|
222
|
+
|
223
|
+
def generate_service(resource)
|
224
|
+
File.write(
|
225
|
+
"app/graphql/#{resource.pluralize}/service.rb",
|
226
|
+
<<~STRING
|
227
|
+
module #{resource.pluralize.camelize}
|
228
|
+
class Service < ApplicationService
|
229
|
+
|
230
|
+
end
|
231
|
+
end
|
232
|
+
STRING
|
233
|
+
)
|
234
|
+
end
|
235
|
+
|
236
|
+
def handle_many_to_many_fields(resource)
|
237
|
+
@many_to_many.each do |field|
|
238
|
+
generate_create_migration(
|
239
|
+
"#{resource}_#{field}",
|
240
|
+
<<-STRING
|
241
|
+
t.#{@id_db_type} :#{resource.underscore.singularize}_id
|
242
|
+
t.#{@id_db_type} :#{field.underscore.singularize}_id
|
243
|
+
STRING
|
244
|
+
)
|
245
|
+
generate_empty_model("#{resource}_#{field.singularize}")
|
246
|
+
add_to_model("#{resource}_#{field.singularize}", "belongs_to :#{resource.singularize}")
|
247
|
+
add_to_model("#{resource}_#{field.singularize}", "belongs_to :#{field.singularize}")
|
248
|
+
add_to_model(resource, "has_many :#{field.pluralize}, through: :#{resource}_#{field.pluralize}")
|
249
|
+
add_to_model(resource, "has_many :#{resource}_#{field.pluralize}")
|
250
|
+
add_to_model(field, "has_many :#{resource.pluralize}, through: :#{resource}_#{field.pluralize}")
|
251
|
+
add_to_model(field, "has_many :#{resource}_#{field.pluralize}")
|
252
|
+
add_has_many_fields_to_type(resource, field)
|
253
|
+
add_has_many_fields_to_type(field, resource)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def add_has_many_to_models(resource)
|
258
|
+
@has_many.each do |field|
|
259
|
+
generate_has_many_migration(resource, has_many: field)
|
260
|
+
add_to_model(resource, "has_many :#{field.pluralize}")
|
261
|
+
add_to_model(field, "belongs_to :#{resource.singularize}")
|
262
|
+
end
|
263
|
+
@id_fields.each do |k, _|
|
264
|
+
field = k.gsub('_id', '')
|
265
|
+
add_to_model(field, "has_many :#{resource.pluralize}")
|
266
|
+
add_to_model(resource, "belongs_to :#{field.singularize}")
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def map_types(input_type: false)
|
271
|
+
result = args&.map do |k, v|
|
272
|
+
field_name = k
|
273
|
+
field_type = types_mapping(v)
|
274
|
+
res = "#{input_type ? 'argument' : 'field'} :#{field_name}, #{field_type}"
|
275
|
+
if !input_type && field_name.ends_with?('_id')
|
276
|
+
res += "\n field :#{field_name.gsub('_id', '')}, " \
|
277
|
+
"!#{field_name.gsub('_id', '').pluralize.camelize}::Type"
|
278
|
+
end
|
279
|
+
res
|
280
|
+
end&.join("\n ")
|
281
|
+
input_type ? result.gsub("field :id, #{@id_type}\n", '') : result
|
282
|
+
end
|
283
|
+
|
284
|
+
# Helpers methods
|
285
|
+
|
286
|
+
def resource_class(resource)
|
287
|
+
resource.pluralize.camelize
|
288
|
+
end
|
289
|
+
|
290
|
+
def add_to_model(model, line)
|
291
|
+
file_name = "app/models/#{model.underscore.singularize}.rb"
|
292
|
+
return unless File.exist?(file_name)
|
293
|
+
return if File.read(file_name).include?(line)
|
294
|
+
write_at(file_name, 3, " #{line}\n")
|
295
|
+
end
|
296
|
+
|
297
|
+
def generate_has_many_migration(resource, has_many:)
|
298
|
+
return if has_many.singularize.camelize.constantize.new.respond_to?("#{resource.singularize}_id")
|
299
|
+
system("bundle exec rails generate migration add_#{resource.singularize}_id_to_#{has_many}")
|
300
|
+
migration_file = Dir.glob("db/migrate/*add_#{resource.singularize}_id_to_#{has_many}*").last
|
301
|
+
File.write(
|
302
|
+
migration_file,
|
303
|
+
<<~STRING
|
304
|
+
class Add#{resource.singularize.camelize}IdTo#{has_many.camelize} < ActiveRecord::Migration[5.1]
|
305
|
+
def change
|
306
|
+
add_column :#{has_many.pluralize}, :#{resource.singularize}_id, :#{@id_db_type}
|
307
|
+
end
|
308
|
+
end
|
309
|
+
STRING
|
310
|
+
)
|
311
|
+
end
|
312
|
+
|
313
|
+
def generate_belongs_to_migration(resource, belongs_to:)
|
314
|
+
generate_has_many_migration(belongs_to, has_many: resource)
|
315
|
+
end
|
316
|
+
|
317
|
+
def write_at(file_name, line, data)
|
318
|
+
open(file_name, 'r+') do |f|
|
319
|
+
while (line -= 1).positive?
|
320
|
+
f.readline
|
321
|
+
end
|
322
|
+
pos = f.pos
|
323
|
+
rest = f.read
|
324
|
+
f.seek(pos)
|
325
|
+
f.write(data)
|
326
|
+
f.write(rest)
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
module Graphql
|
2
|
+
class HydrateQuery
|
3
|
+
|
4
|
+
def initialize(model, context, id: nil)
|
5
|
+
@fields = context&.irep_node&.scoped_children&.values&.first
|
6
|
+
@model = model
|
7
|
+
@id = id
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
hash = parse_fields(@fields)
|
12
|
+
selectable_values = transform_to_selectable_values(hash)
|
13
|
+
joins = remove_keys_with_nil_values(Marshal.load(Marshal.dump(hash)))
|
14
|
+
join_model = @model.includes(joins)
|
15
|
+
join_model = join_model.where(id: @id) if @id.present?
|
16
|
+
res2d = pluck_to_hash_with_ids(join_model, pluckable_attributes(selectable_values))
|
17
|
+
joins_with_root = { model_name.to_sym => remove_keys_with_nil_values(Marshal.load(Marshal.dump(hash))) }
|
18
|
+
ir = nest(joins_with_root, res2d).first
|
19
|
+
@id ? ir_to_output(ir).first : ir_to_output(ir)
|
20
|
+
end
|
21
|
+
|
22
|
+
def pluck_to_hash_with_ids(model, keys)
|
23
|
+
keys.each do |k|
|
24
|
+
resource = k.split('.').first
|
25
|
+
keys << "#{resource.pluralize}.id" unless keys.include?("#{resource}.id")
|
26
|
+
end
|
27
|
+
keys = keys.compact.uniq
|
28
|
+
model.pluck(*keys).map do |pa|
|
29
|
+
Hash[keys.zip([pa].flatten)]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def pluckable_attributes(keys)
|
34
|
+
db_attributes = keys.uniq.map { |k| k.gsub(/\..*$/, '') }.uniq.map do |resource|
|
35
|
+
next unless Object.const_defined?(resource.singularize.camelize)
|
36
|
+
resource.singularize.camelize.constantize.new.attributes.keys.map do |attribute|
|
37
|
+
"#{resource}.#{attribute}"
|
38
|
+
end
|
39
|
+
end.flatten.compact
|
40
|
+
keys.select { |e| db_attributes.flatten.include?(e) }.map do |e|
|
41
|
+
split = e.split('.')
|
42
|
+
"#{split.first.pluralize}.#{split.last}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def ir_to_output(inter_result)
|
47
|
+
model_name = inter_result&.first&.first&.first&.first&.to_s
|
48
|
+
return [] if model_name.blank?
|
49
|
+
if singular?(model_name)
|
50
|
+
ir_node_to_output(inter_result.first)
|
51
|
+
else
|
52
|
+
inter_result.map do |ir_node|
|
53
|
+
ir_node_to_output(ir_node) if ir_node
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def ir_node_to_output(ir_node)
|
59
|
+
t = ir_node[:results].first.each_with_object({}) do |(attribute, v), h|
|
60
|
+
h[attribute.gsub(ir_node.keys.reject { |key| key == :results }.first.first.to_s.pluralize + '.', '')] = v
|
61
|
+
end
|
62
|
+
relations = ir_node.values&.first&.map { |e| e&.first&.first&.first&.first }
|
63
|
+
relations.zip(ir_node[ir_node.keys.reject { |key| key == :results }&.first]).to_h.map do |key, value|
|
64
|
+
res = ir_to_output(value)
|
65
|
+
t[key] = res if value
|
66
|
+
t[key].compact! if t[key].is_a?(Array)
|
67
|
+
end
|
68
|
+
Struct.new(*t.keys.map(&:to_sym)).new(*t.values) if !t.keys.blank? && !t.values.compact.blank?
|
69
|
+
end
|
70
|
+
|
71
|
+
def singular?(string)
|
72
|
+
string.singularize == string
|
73
|
+
end
|
74
|
+
|
75
|
+
def nest(joins, res)
|
76
|
+
joins.map do |relation_name, other_joins|
|
77
|
+
res.group_by do |row|
|
78
|
+
[relation_name, row["#{relation_name.to_s.pluralize}.id"]]
|
79
|
+
end.map do |k, ungrouped|
|
80
|
+
Hash[k, nest(other_joins, ungrouped)].merge(results: extract_values_of_level(k[0], ungrouped).uniq)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def extract_values_of_level(level, ungrouped)
|
86
|
+
ungrouped.map do |row|
|
87
|
+
row.select { |k, _| k =~ /#{level.to_s.pluralize}.*/ }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def transform_to_selectable_values(hash, res = nil)
|
92
|
+
@values ||= []
|
93
|
+
hash.each do |k, v|
|
94
|
+
if v.nil?
|
95
|
+
@values << "#{res || model_name}.#{k}" unless activerecord_model?(k)
|
96
|
+
else
|
97
|
+
next @values << "#{res || model_name}.#{k}" unless activerecord_model?(k)
|
98
|
+
transform_to_selectable_values(v, k)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
@values
|
102
|
+
end
|
103
|
+
|
104
|
+
def remove_keys_with_nil_values(hash)
|
105
|
+
hash.symbolize_keys!
|
106
|
+
hash.each_key do |k|
|
107
|
+
if hash[k].nil? || !activerecord_model?(k)
|
108
|
+
hash.delete(k)
|
109
|
+
else
|
110
|
+
remove_keys_with_nil_values(hash[k])
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def parse_fields(fields)
|
116
|
+
fields.each_with_object({}) do |(k, v), h|
|
117
|
+
next if k == '__typename'
|
118
|
+
h[k] = v.scoped_children == {} ? nil : parse_fields(v.scoped_children.values.first)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def model_name
|
123
|
+
@model.class.to_s.split('::').first.underscore.pluralize
|
124
|
+
end
|
125
|
+
|
126
|
+
def activerecord_model?(name)
|
127
|
+
class_name = name.to_s.singularize.camelize
|
128
|
+
begin
|
129
|
+
class_name.constantize.ancestors.include?(ApplicationRecord)
|
130
|
+
rescue NameError
|
131
|
+
false
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Graphql
|
2
|
+
module Rails
|
3
|
+
module Api
|
4
|
+
class Config
|
5
|
+
|
6
|
+
include Singleton
|
7
|
+
attr_accessor :id_type, :basic_mutations
|
8
|
+
|
9
|
+
def self.query_resources
|
10
|
+
Dir.glob("#{File.expand_path('.')}/app/graphql/*/type.rb").map do |dir|
|
11
|
+
dir.split('/').last(2).first
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.mutation_resources
|
16
|
+
mutations = Dir.glob("#{File.expand_path('.')}/app/graphql/*/mutations/*.rb").reject do |e|
|
17
|
+
e.end_with?('type.rb', 'types.rb')
|
18
|
+
end
|
19
|
+
mutations = mutations.map { |e| e.split('/').last.gsub('.rb', '') }.uniq
|
20
|
+
mutations.each_with_object({}) do |meth, h|
|
21
|
+
h[meth] = Dir.glob("#{File.expand_path('.')}/app/graphql/*/mutations/#{meth}.rb").map do |dir|
|
22
|
+
dir.split('/').last(3).first
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: graphql-rails-api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- poilon
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-05-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: graphql
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rails
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5.1'
|
41
|
+
description: This gem purpose is to make graphql easier to use in ruby. Mainly developed
|
42
|
+
for from-scratch app
|
43
|
+
email:
|
44
|
+
- poilon@gmail.com
|
45
|
+
executables: []
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- MIT-LICENSE
|
50
|
+
- README.md
|
51
|
+
- Rakefile
|
52
|
+
- lib/generators/graphql_mutations/graphql_mutations_generator.rb
|
53
|
+
- lib/generators/graphql_rails_api/install_generator.rb
|
54
|
+
- lib/generators/graphql_resource/USAGE
|
55
|
+
- lib/generators/graphql_resource/graphql_resource_generator.rb
|
56
|
+
- lib/graphql/hydrate_query.rb
|
57
|
+
- lib/graphql/rails/api/config.rb
|
58
|
+
- lib/graphql/rails/api/version.rb
|
59
|
+
- lib/tasks/graphql/rails/api_tasks.rake
|
60
|
+
homepage: https://github.com/poilon/graphql-rails-api
|
61
|
+
licenses:
|
62
|
+
- MIT
|
63
|
+
metadata: {}
|
64
|
+
post_install_message:
|
65
|
+
rdoc_options: []
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
requirements: []
|
79
|
+
rubyforge_project:
|
80
|
+
rubygems_version: 2.6.14
|
81
|
+
signing_key:
|
82
|
+
specification_version: 4
|
83
|
+
summary: Graphql rails api framework to create easily graphql api with rails
|
84
|
+
test_files: []
|