graphql-rails-api 0.1.3
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 +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: []
|