graphql-rails-api 0.6.0 → 0.7.2

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: 066b640b12080a61dc163b4989543cb22cff0c0bc000d5c5f2881ef74a37656a
4
- data.tar.gz: '08e978790a84b509ad77c8a0fb58c6d496d891c095092676289c1516fcb478d9'
3
+ metadata.gz: 8f3e6a19fed3e87964c4c5c50f51a4b9527023484e477f07c5bbed8f6aecd601
4
+ data.tar.gz: 0c3940236953b52221fc0db3531b11a6e138d9f3f29901abd51dd54016dac92f
5
5
  SHA512:
6
- metadata.gz: 68ab8d0cc279ca2edebdc20bf3b490b73e8badf3a9f7f0284ef9c3f85c4a1c56ff8f29f8d69d1f16eb7b97e2035e3223308d2f3b093ab5692c40ad560a42cc35
7
- data.tar.gz: 44ffdd02ea1cf52065c29db18df694c518ca7b08a9a3264ff25895d702308127265dbce8319ea6e7ce5844df2aeb50f6f3d9c5cdcc4ac0e574675064ab71e58a
6
+ metadata.gz: c3a5906fc17b0fc02172622dd6e13417fafa3e607a6742fef6dc090b64749bd8d7dc2c12fe49a8760931a0ec91e80f0b91382ca9d854017a90848a0a0ff44e6a
7
+ data.tar.gz: a9091317f41816ae66c687e952626e2166473e9d488a25ea837c1f57f3f19bac0ee1a1cb14704d3d978ad3e30e463f247fb3097221be920e2b1f9421e8165769
@@ -0,0 +1,312 @@
1
+ class GraphqlAddFieldsGenerator < Rails::Generators::NamedBase
2
+
3
+ %i[
4
+ migration model mutations service graphql_input_type
5
+ graphql_type propagation connection migrate
6
+ ].each do |opt|
7
+ class_option(opt, type: :boolean, default: true)
8
+ end
9
+
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'
18
+ }.freeze
19
+
20
+ def create_graphql_files
21
+ return if args.blank?
22
+
23
+ parse_args
24
+
25
+ # Generate migration
26
+ generate_migration(@resource, @fields_to_migration) if options.migration?
27
+
28
+ complete_graphql_input_type if options.graphql_input_type?
29
+
30
+ # # Graphql Type
31
+ complete_graphql_type(@resource) if options.graphql_type?
32
+
33
+ # # Propagation
34
+ handle_many_to_many_fields(@resource) if options.propagation?
35
+ add_has_many_to_models(@resource) if options.propagation?
36
+ add_has_many_fields_to_types(@resource) if options.propagation?
37
+
38
+ system('bundle exec rails db:migrate') if options.migrate?
39
+ end
40
+
41
+ private
42
+
43
+ def types_mapping(type)
44
+ TYPES_MAPPING[type] || 'types.String'
45
+ end
46
+
47
+ def complete_graphql_input_type
48
+ return if map_types(input_type: true).blank?
49
+
50
+ write_at("#{@mutations_directory}/input_type.rb", 4, " #{map_types(input_type: true)}\n")
51
+ end
52
+
53
+ def complete_graphql_type(resource)
54
+ return if map_types(input_type: false).blank?
55
+
56
+ write_at("#{graphql_resource_directory(resource)}/type.rb", 4, " #{map_types(input_type: false)}\n")
57
+ end
58
+
59
+ def parse_args
60
+ @id_db_type = 'uuid'
61
+ @id_type = 'types.String'
62
+
63
+ @resource = file_name.singularize
64
+ @has_many = []
65
+ @many_to_many = []
66
+ @mutations_directory = "#{graphql_resource_directory(@resource)}/mutations"
67
+ @belongs_to_fields = {}
68
+
69
+ @args = args.each_with_object({}) do |f, hash|
70
+ next if f.split(':').count != 2
71
+
72
+ case f.split(':').first
73
+ when 'belongs_to' then
74
+ hash["#{f.split(':').last.singularize}_id"] = @id_db_type
75
+ @belongs_to_fields["#{f.split(':').last.singularize}_id"] = @id_db_type
76
+ when 'has_many' then @has_many << f.split(':').last.pluralize
77
+ when 'many_to_many' then @many_to_many << f.split(':').last.pluralize
78
+ else
79
+ hash[f.split(':').first] = f.split(':').last
80
+ end
81
+ end
82
+
83
+ @fields_to_migration = @args.map do |f|
84
+ "add_column :#{@resource.pluralize}, :#{f.join(', :')}"
85
+ end.join("\n ")
86
+ @named_fields = @args.keys.join('_')
87
+ end
88
+
89
+ def generate_migration(resource, fields)
90
+ return if @named_fields.blank?
91
+
92
+ system("bundle exec rails generate migration add_#{@named_fields}_to_#{resource} --skip")
93
+ File.write(
94
+ Dir.glob("db/migrate/*add_#{@named_fields}_to_#{resource}.rb").last,
95
+ <<~STRING
96
+ class Add#{@named_fields.camelize}To#{resource.camelize} < ActiveRecord::Migration[5.2]
97
+ def change
98
+ #{fields}
99
+ end
100
+ end
101
+ STRING
102
+ )
103
+ end
104
+
105
+ def graphql_resource_directory(resource)
106
+ "app/graphql/#{resource.pluralize}"
107
+ end
108
+
109
+ def add_has_many_fields_to_type(field, resource)
110
+ file_name = "app/graphql/#{field.pluralize}/type.rb"
111
+ if File.read(file_name).include?("field :#{resource.singularize}_ids") ||
112
+ File.read(file_name).include?("field :#{resource.pluralize}") ||
113
+ File.read(file_name).include?("connection :#{resource.pluralize}_connection")
114
+ return
115
+ end
116
+
117
+ write_at(
118
+ file_name, 4,
119
+ <<-STRING
120
+ field :#{resource.singularize}_ids, types[#{@id_type}]
121
+ field :#{resource.pluralize}, types[#{resource.pluralize.camelize}::Type]
122
+ connection :#{resource.pluralize}_connection, #{resource.pluralize.camelize}::Connection
123
+ STRING
124
+ )
125
+
126
+ input_type_file_name = "app/graphql/#{field.pluralize}/mutations/input_type.rb"
127
+ if File.read(input_type_file_name).include?("argument :#{resource.singularize}_id") ||
128
+ File.read(input_type_file_name).include?("argument :#{resource.singularize}")
129
+ return
130
+ end
131
+
132
+ write_at(
133
+ input_type_file_name, 4,
134
+ <<-STRING
135
+ argument :#{resource.singularize}_ids, types[#{@id_type}]
136
+ STRING
137
+ )
138
+ end
139
+
140
+ def add_belongs_to_field_to_type(field, resource)
141
+ file_name = "app/graphql/#{resource.pluralize}/type.rb"
142
+ if File.read(file_name).include?("field :#{field.singularize}_id") ||
143
+ File.read(file_name).include?("field :#{field.singularize}")
144
+ return
145
+ end
146
+
147
+ 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
153
+ )
154
+ input_type_file_name = "app/graphql/#{resource.pluralize}/mutations/input_type.rb"
155
+ if File.read(input_type_file_name).include?("argument :#{field.singularize}_id") ||
156
+ File.read(input_type_file_name).include?("argument :#{field.singularize}")
157
+ return
158
+ end
159
+
160
+ write_at(
161
+ input_type_file_name, 4,
162
+ <<-STRING
163
+ argument :#{field.singularize}_id, #{@id_type}
164
+ STRING
165
+ )
166
+ end
167
+
168
+ def add_has_many_fields_to_types(resource)
169
+ @has_many.each do |f|
170
+ add_has_many_fields_to_type(resource, f)
171
+ add_belongs_to_field_to_type(resource, f)
172
+ end
173
+ @belongs_to_fields.each do |f, _|
174
+ add_has_many_fields_to_type(f.gsub('_id', ''), resource)
175
+ add_belongs_to_field_to_type(f.gsub('_id', ''), resource)
176
+ end
177
+ end
178
+
179
+ def generate_empty_model(resource)
180
+ File.write(
181
+ "app/models/#{resource}.rb",
182
+ <<~STRING
183
+ class #{resource.singularize.camelize} < ApplicationRecord
184
+
185
+ end
186
+ STRING
187
+ )
188
+ end
189
+
190
+ def handle_many_to_many_fields(resource)
191
+ @many_to_many.each do |field|
192
+ generate_create_migration(
193
+ "#{resource}_#{field}",
194
+ <<-STRING
195
+ t.#{@id_db_type} :#{resource.underscore.singularize}_id
196
+ t.#{@id_db_type} :#{field.underscore.singularize}_id
197
+ STRING
198
+ )
199
+ generate_empty_model("#{resource}_#{field.singularize}")
200
+ add_to_model("#{resource}_#{field.singularize}", "belongs_to :#{resource.singularize}")
201
+ add_to_model("#{resource}_#{field.singularize}", "belongs_to :#{field.singularize}")
202
+ add_to_model(resource, "has_many :#{field.pluralize}, through: :#{resource}_#{field.pluralize}")
203
+ add_to_model(resource, "has_many :#{resource}_#{field.pluralize}")
204
+ add_to_model(field, "has_many :#{resource.pluralize}, through: :#{resource}_#{field.pluralize}")
205
+ add_to_model(field, "has_many :#{resource}_#{field.pluralize}")
206
+ add_has_many_fields_to_type(resource, field)
207
+ add_has_many_fields_to_type(field, resource)
208
+ end
209
+ end
210
+
211
+ def add_has_many_to_models(resource)
212
+ @has_many.each do |field|
213
+ generate_has_many_migration(resource, has_many: field)
214
+ add_to_model(resource, "has_many :#{field.pluralize}")
215
+ add_to_model(field, "belongs_to :#{resource.singularize}")
216
+ end
217
+ @belongs_to_fields.each do |k, _|
218
+ field = k.gsub('_id', '')
219
+ add_to_model(field, "has_many :#{resource.pluralize}")
220
+ add_to_model(resource, "belongs_to :#{field.singularize}")
221
+ end
222
+ end
223
+
224
+ def map_types(input_type: false)
225
+ result = args&.map do |k, v|
226
+ field_name = k
227
+ field_type = types_mapping(v)
228
+ res = "#{input_type ? 'argument' : 'field'} :#{field_name}, #{field_type}"
229
+ if !input_type && field_name.ends_with?('_id') && @belongs_to_fields.key?(field_name)
230
+ res += "\n field :#{field_name.gsub('_id', '')}, " \
231
+ "#{field_name.gsub('_id', '').pluralize.camelize}::Type"
232
+ end
233
+ res
234
+ end&.join("\n ")
235
+ input_type ? result.gsub("field :id, #{@id_type}\n", '') : result
236
+ end
237
+
238
+ # Helpers methods
239
+
240
+ def resource_class(resource)
241
+ resource.pluralize.camelize
242
+ end
243
+
244
+ def add_to_model(model, line)
245
+ file_name = "app/models/#{model.underscore.singularize}.rb"
246
+ return if !File.exist?(file_name) || File.read(file_name).include?(line)
247
+
248
+ line_count = `wc -l "#{file_name}"`.strip.split(' ')[0].to_i
249
+
250
+ line_nb = 0
251
+ File.open(file_name).each do |l|
252
+ line_nb += 1
253
+ break if l.include?('ApplicationRecord')
254
+ end
255
+ raise 'Your model must inherit from ApplicationRecord to make it work' if line_nb >= line_count
256
+
257
+ write_at(file_name, line_nb + 2, " #{line}\n")
258
+ end
259
+
260
+ def generate_has_many_migration(resource, has_many:)
261
+ return if has_many.singularize.camelize.constantize.new.respond_to?("#{resource.singularize}_id")
262
+
263
+ system("bundle exec rails generate migration add_#{resource.singularize}_id_to_#{has_many}")
264
+ migration_file = Dir.glob("db/migrate/*add_#{resource.singularize}_id_to_#{has_many}.rb").last
265
+ File.write(
266
+ migration_file,
267
+ <<~STRING
268
+ class Add#{resource.singularize.camelize}IdTo#{has_many.camelize} < ActiveRecord::Migration[5.2]
269
+ def change
270
+ add_column :#{has_many.pluralize}, :#{resource.singularize}_id, :#{@id_db_type}
271
+ end
272
+ end
273
+ STRING
274
+ )
275
+ end
276
+
277
+ def generate_create_migration(resource, fields)
278
+ system("bundle exec rails generate migration create_#{resource} --skip")
279
+ migration_file = Dir.glob("db/migrate/*create_#{resource}.rb").last
280
+ File.write(
281
+ migration_file,
282
+ <<~STRING
283
+ class Create#{resource.camelize} < ActiveRecord::Migration[5.2]
284
+ def change
285
+ create_table :#{resource.pluralize}, id: :uuid do |t|
286
+ #{fields}
287
+ t.timestamps
288
+ end
289
+ end
290
+ end
291
+ STRING
292
+ )
293
+ end
294
+
295
+ def generate_belongs_to_migration(resource, belongs_to:)
296
+ generate_has_many_migration(belongs_to, has_many: resource)
297
+ end
298
+
299
+ def write_at(file_name, line, data)
300
+ open(file_name, 'r+') do |f|
301
+ while (line -= 1).positive?
302
+ f.readline
303
+ end
304
+ pos = f.pos
305
+ rest = f.read
306
+ f.seek(pos)
307
+ f.write(data)
308
+ f.write(rest)
309
+ end
310
+ end
311
+
312
+ end
@@ -1,7 +1,6 @@
1
1
  class GraphqlMutationsGenerator < Rails::Generators::NamedBase
2
2
 
3
3
  def generate
4
- @id = Graphql::Rails::Api::Config.instance.id_type == :uuid ? '!types.String' : '!types.ID'
5
4
  resource = file_name.underscore.singularize
6
5
  dir = "app/graphql/#{resource.pluralize}/mutations"
7
6
  system("mkdir -p #{dir}")
@@ -22,7 +21,7 @@ class GraphqlMutationsGenerator < Rails::Generators::NamedBase
22
21
  description 'creates some #{resource_class(resource).pluralize}'
23
22
  type types[#{resource_class(resource)}::Type]
24
23
 
25
- argument :#{resource}, types[#{resource_class(resource)}::Mutations::InputType]
24
+ argument :#{resource}, !types[#{resource_class(resource)}::Mutations::InputType]
26
25
 
27
26
  resolve ApplicationService.call(:#{resource}, :bulk_create)
28
27
  end
@@ -38,7 +37,7 @@ class GraphqlMutationsGenerator < Rails::Generators::NamedBase
38
37
  description 'Updates some #{resource_class(resource).pluralize}'
39
38
  type types[#{resource_class(resource)}::Type]
40
39
 
41
- argument :#{resource}, types[#{resource_class(resource)}::Mutations::InputType]
40
+ argument :#{resource}, !types[#{resource_class(resource)}::Mutations::InputType]
42
41
 
43
42
  resolve ApplicationService.call(:#{resource}, :bulk_update)
44
43
  end
@@ -54,7 +53,7 @@ class GraphqlMutationsGenerator < Rails::Generators::NamedBase
54
53
  description 'Creates a #{resource_class(resource).singularize}'
55
54
  type #{resource_class(resource)}::Type
56
55
 
57
- argument :#{resource}, #{resource_class(resource)}::Mutations::InputType
56
+ argument :#{resource}, !#{resource_class(resource)}::Mutations::InputType
58
57
 
59
58
  resolve ApplicationService.call(:#{resource}, :create)
60
59
  end
@@ -70,8 +69,8 @@ class GraphqlMutationsGenerator < Rails::Generators::NamedBase
70
69
  description 'Updates a #{resource_class(resource).singularize}'
71
70
  type #{resource_class(resource)}::Type
72
71
 
73
- argument :id, #{@id}
74
- argument :#{resource}, #{resource_class(resource)}::Mutations::InputType
72
+ argument :id, types.String
73
+ argument :#{resource}, !#{resource_class(resource)}::Mutations::InputType
75
74
 
76
75
  resolve ApplicationService.call(:#{resource}, :update)
77
76
  end
@@ -87,7 +86,7 @@ class GraphqlMutationsGenerator < Rails::Generators::NamedBase
87
86
  description 'Destroys a #{resource_class(resource).singularize}'
88
87
  type #{resource_class(resource)}::Type
89
88
 
90
- argument :id, #{@id}
89
+ argument :id, !types.String
91
90
 
92
91
  resolve ApplicationService.call(:#{resource}, :destroy)
93
92
  end
@@ -4,41 +4,50 @@ module GraphqlRailsApi
4
4
  class InstallGenerator < Rails::Generators::Base
5
5
 
6
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
7
  class_option('generate_graphql_route', type: :boolean, default: true)
10
8
 
11
9
  def generate_files
12
10
  @app_name = File.basename(Rails.root.to_s).underscore
13
11
  system('mkdir -p app/graphql/')
14
12
 
13
+ write_uuid_extensions_migration
14
+
15
15
  write_service
16
- write_application_record_methods
17
16
  write_schema
18
17
  write_query_type
19
18
  write_mutation_type
20
- write_subscription_type
19
+
21
20
  write_controller
22
- if options.action_cable_subs?
23
- write_websocket_connection
24
- write_online_users_channel
25
- end
21
+
22
+ write_websocket_models
23
+ write_websocket_connection
24
+ write_subscriptions_channel
25
+
26
+ write_application_record_methods
26
27
  write_initializer
27
28
  write_require_application_rb
28
29
  write_route if options.generate_graphql_route?
29
- write_uuid_extensions_migration if options.pg_uuid?
30
30
  end
31
31
 
32
32
  private
33
33
 
34
+ def write_websocket_models
35
+ system 'rails g graphql_resource user first_name:string last_name:string email:string'
36
+ system 'rails g graphql_resource websocket_connection belongs_to:user connection_identifier:string'
37
+ system 'rails g graphql_resource subscribed_query belongs_to:websocket_connection result_hash:string query:string'
38
+ end
39
+
34
40
  def write_route
35
41
  route_file = File.read('config/routes.rb')
36
42
  return if route_file.include?('graphql')
43
+
37
44
  File.write(
38
45
  'config/routes.rb',
39
46
  route_file.gsub(
40
47
  "Rails.application.routes.draw do\n",
41
- "Rails.application.routes.draw do\n post '/graphql', to: 'graphql#execute'\n"
48
+ "Rails.application.routes.draw do\n" \
49
+ " post '/graphql', to: 'graphql#execute'\n" \
50
+ " mount ActionCable.server => '/cable'\n"
42
51
  )
43
52
  )
44
53
  end
@@ -60,43 +69,17 @@ module GraphqlRailsApi
60
69
  all
61
70
  end
62
71
 
63
- STRING
64
- )
65
- return unless options.action_cable_subs?
72
+ def self.broadcast_queries
73
+ WebsocketConnection.all.each do |wsc|
74
+ wsc.subscribed_queries.each do |sq|
75
+ result = #{@app_name.camelize}Schema.execute(sq.query, context: { current_user: wsc.user })
76
+ hex = Digest::SHA1.hexdigest(result.to_s)
77
+ next if sq.result_hash == hex
66
78
 
67
- lines_count = File.read('app/models/application_record.rb').lines.count
68
- write_at(
69
- 'app/models/application_record.rb',
70
- lines_count,
71
- <<-STRING
72
- after_commit :notify_online_users
73
-
74
- def notify_online_users
75
- Redis.current.keys('#{Rails.application.class.parent_name.underscore}_subscribed_query_*').each_with_object({}) do |key, hash|
76
- hash[
77
- key.gsub('#{Rails.application.class.parent_name.underscore}_subscribed_query_', '')
78
- ] = Redis.current.hgetall(key).each_with_object([]) do |(data, vars), array|
79
- data = data.split('/////')
80
- array << { query: data[0], store: data[1], variables: vars.blank? ? nil : JSON.parse(vars), scope: data[2] }
79
+ sq.update_attributes(result_hash: hex)
80
+ SubscriptionsChannel.broadcast_to(wsc, query: sq.query, result: result.to_s)
81
81
  end
82
- end.each do |user_id, user_queries_array|
83
- user_queries_array.map { |user_hash| notify_user(user_id, user_hash) }
84
- end
85
- end
86
-
87
- def notify_user(user_id, user_hash)
88
- model_name = self.class.to_s.underscore
89
- if !user_hash[:query].include?(model_name.singularize + '(id: $id') &&
90
- !user_hash[:query].include?(' ' + model_name.pluralize)
91
- return
92
82
  end
93
- return if user_hash[:query].include?(model_name + '(id: $id') && user_hash[:variables]['id'] != id
94
-
95
- u = User.find_by(id: user_id)
96
- return unless u
97
-
98
- result = #{Rails.application.class.parent_name}ApiSchema.execute(user_hash[:query], context: { current_user: u }, variables: user_hash[:variables])
99
- OnlineUsersChannel.broadcast_to(u, store: user_hash[:store], scope: user_hash[:scope], result: result['data'])
100
83
  end
101
84
 
102
85
  STRING
@@ -104,11 +87,7 @@ module GraphqlRailsApi
104
87
  end
105
88
 
106
89
  def write_require_application_rb
107
- write_at(
108
- 'config/application.rb',
109
- 5,
110
- "require 'graphql/hydrate_query'\n"
111
- )
90
+ write_at('config/application.rb', 5, "require 'graphql/hydrate_query'\nrequire 'rkelly'\n")
112
91
  end
113
92
 
114
93
  def write_uuid_extensions_migration
@@ -117,7 +96,7 @@ module GraphqlRailsApi
117
96
  File.write(
118
97
  migration_file,
119
98
  <<~STRING
120
- class UuidPgExtensions < ActiveRecord::Migration[5.1]
99
+ class UuidPgExtensions < ActiveRecord::Migration[5.2]
121
100
 
122
101
  def change
123
102
  execute 'CREATE EXTENSION "pgcrypto" SCHEMA pg_catalog;'
@@ -136,8 +115,6 @@ module GraphqlRailsApi
136
115
  require 'graphql/rails/api/config'
137
116
 
138
117
  config = Graphql::Rails::Api::Config.instance
139
-
140
- config.id_type = #{options.pg_uuid? ? ':uuid' : ':id'} # :id or :uuid
141
118
  STRING
142
119
  )
143
120
  end
@@ -149,11 +126,13 @@ module GraphqlRailsApi
149
126
  module ApplicationCable
150
127
  class Connection < ActionCable::Connection::Base
151
128
 
152
- identified_by :current_user
129
+ identified_by :websocket_connection
153
130
 
154
131
  def connect
155
132
  # Check authentication, and define current user
156
- self.current_user = nil
133
+ self.websocket_connection = WebsocketConnection.create(
134
+ # user_id: current_user.id
135
+ )
157
136
  end
158
137
 
159
138
  end
@@ -162,40 +141,39 @@ module GraphqlRailsApi
162
141
  )
163
142
  end
164
143
 
165
- def write_online_users_channel
144
+ def write_subscriptions_channel
166
145
  File.write(
167
- 'app/channels/online_users_channel.rb',
146
+ 'app/channels/subscriptions_channel.rb',
168
147
  <<~STRING
169
- class OnlineUsersChannel < ApplicationCable::Channel
148
+ class SubscriptionsChannel < ApplicationCable::Channel
170
149
 
171
150
  def subscribed
172
- stream_for(current_user)
173
- Redis.current.hset('#{Rails.application.class.parent_name.underscore}_online_users', current_user.id, '1')
174
- User.online.each do |user|
175
- OnlineUsersChannel.broadcast_to(user, User.online_user_ids)
151
+ stream_for(websocket_connection)
152
+ websocket_connection.update_attributes(connection_identifier: connection.connection_identifier)
153
+ ci = ActionCable.server.connections.map(&:connection_identifier)
154
+ WebsocketConnection.all.each do |wsc|
155
+ wsc.destroy unless ci.include?(wsc.connection_identifier)
176
156
  end
177
157
  end
178
158
 
179
159
  def subscribe_to_query(data)
180
- Redis.current.hset(
181
- '#{Rails.application.class.parent_name.underscore}_subscribed_query_' + current_user.id,
182
- data['query'] + '/////' + data['store'] + '/////' + data['scope'],
183
- data['variables']
160
+ websocket_connection.subscribed_queries.find_or_create_by(query: data['query'])
161
+ SubscriptionsChannel.broadcast_to(
162
+ websocket_connection,
163
+ query: data['query'],
164
+ result: #{@app_name.camelize}Schema.execute(data['query'], context: { current_user: websocket_connection.user })
184
165
  )
185
166
  end
186
167
 
187
168
  def unsubscribe_to_query(data)
188
- Redis.current.hdel(
189
- '#{Rails.application.class.parent_name.underscore}_subscribed_query_' + current_user.id,
190
- data['query'] + '/////' + data['store']
191
- )
169
+ websocket_connection.subscribed_queries.find_by(query: data['query'])&.destroy
192
170
  end
193
171
 
194
172
  def unsubscribed
195
- Redis.current.hset('#{Rails.application.class.parent_name.underscore}_online_users', current_user.id, '0')
196
- Redis.current.hdel('#{Rails.application.class.parent_name.underscore}_subscribed_query_' + current_user.id, current_user.id)
197
- User.online.each do |user|
198
- OnlineUsersChannel.broadcast_to(user, User.online_user_ids)
173
+ websocket_connection.destroy
174
+ ci = ActionCable.server.connections.map(&:connection_identifier)
175
+ WebsocketConnection.all.each do |wsc|
176
+ wsc.destroy unless ci.include?(wsc.connection_identifier)
199
177
  end
200
178
  end
201
179
 
@@ -219,6 +197,7 @@ module GraphqlRailsApi
219
197
  context: { current_user: authenticated_user },
220
198
  operation_name: params[:operationName]
221
199
  )
200
+ ApplicationRecord.broadcast_queries
222
201
  render json: result
223
202
  end
224
203
 
@@ -226,9 +205,6 @@ module GraphqlRailsApi
226
205
 
227
206
  def authenticated_user
228
207
  # Here you need to authenticate the user.
229
- # You can use devise, then just write:
230
-
231
- # current_user
232
208
  end
233
209
 
234
210
  # Handle form data, JSON body, or a blank value
@@ -250,17 +226,6 @@ module GraphqlRailsApi
250
226
  )
251
227
  end
252
228
 
253
- def write_subscription_type
254
- File.write(
255
- 'app/graphql/subscription_type.rb',
256
- <<~STRING
257
- SubscriptionType = GraphQL::ObjectType.define do
258
- name 'Subscription'
259
- end
260
- STRING
261
- )
262
- end
263
-
264
229
  def write_mutation_type
265
230
  File.write(
266
231
  'app/graphql/mutation_type.rb',
@@ -291,14 +256,14 @@ module GraphqlRailsApi
291
256
 
292
257
  Graphql::Rails::Api::Config.query_resources.each do |resource|
293
258
  field resource.singularize do
294
- description "Return a #{resource.classify}"
259
+ description "Returns a #{resource.classify}"
295
260
  type !"#{resource.camelize}::Type".constantize
296
261
  argument :id, !types.String
297
262
  resolve ApplicationService.call(resource, :show)
298
263
  end
299
264
 
300
265
  field resource.pluralize do
301
- description "Return a #{resource.classify}"
266
+ description "Returns a #{resource.classify}"
302
267
  type !types[!"#{resource.camelize}::Type".constantize]
303
268
  argument :page, types.Int
304
269
  argument :per_page, types.Int
@@ -307,6 +272,11 @@ module GraphqlRailsApi
307
272
 
308
273
  end
309
274
 
275
+ field :me, Users::Type do
276
+ description 'Returns the current user'
277
+ resolve ->(_, _, ctx) { ctx[:current_user] }
278
+ end
279
+
310
280
  end
311
281
  STRING
312
282
  )
@@ -352,8 +322,6 @@ module GraphqlRailsApi
352
322
  mutation(MutationType)
353
323
  query(QueryType)
354
324
  #{'directives [ConnectionDirective, ClientDirective]' if options.apollo_compatibility?}
355
- #{'use GraphQL::Subscriptions::ActionCableSubscriptions' if options.action_cable_subs?}
356
- subscription(SubscriptionType)
357
325
  type_error lambda { |err, query_ctx|
358
326
  #{error_handler}
359
327
  }
@@ -389,76 +357,86 @@ module GraphqlRailsApi
389
357
  end
390
358
 
391
359
  def index
392
- HydrateQuery.new(
360
+ Graphql::HydrateQuery.new(
393
361
  model.all,
394
362
  @context,
395
363
  order_by: params[:order_by],
396
364
  filter: params[:filter],
397
365
  user: user
398
- ).run
366
+ ).run.compact
399
367
  end
400
368
 
401
369
  def show
402
- return not_allowed if access_not_allowed
403
-
404
- showed_resource = HydrateQuery.new(model.all, @context, user: user, id: object.id).run
405
-
406
- return not_allowed if showed_resource.blank?
370
+ object = Graphql::HydrateQuery.new(model.all, @context, user: user, id: params[:id]).run
371
+ return not_allowed if object.blank?
407
372
 
408
- showed_resource
373
+ object
409
374
  end
410
375
 
411
376
  def create
412
- if user&.action?("can_create_#{singular_resource}")
413
- created_resource = model.new(params.select { |p| model.new.respond_to?(p) })
414
- return not_allowed if not_allowed_to_create_resource(created_resource)
415
-
416
- created_resource.save ? created_resource : graphql_error(created_resource.errors.full_messages.join(', '))
417
- elsif user&.action?("can_create_#{singular_resource}_with_verif")
418
- Verification.create(action: 'create', model: model.to_s, params: params, user_id: user.id)
419
- graphql_error("Pending verification for a #{singular_resource} creation")
377
+ object = model.new(params.select { |p| model.new.respond_to?(p) })
378
+ return not_allowed if not_allowed_to_create_resource(object)
379
+
380
+ if object.save
381
+ object
420
382
  else
421
- not_allowed
383
+ graphql_error(object.errors.full_messages.join(', '))
384
+ end
385
+ end
386
+
387
+ def bulk_create
388
+ result = model.import(params.map { |p| p.select { |param| model.new.respond_to?(param) } })
389
+ result.each { |e| e.run_callbacks(:save) }
390
+ hyd = Graphql::HydrateQuery.new(model.where(id: result.ids), @context).run.compact + result.failed_instances.map do |i|
391
+ graphql_error(i.errors.full_messages)
392
+ end
393
+ return hyd.first if hyd.all? { |e| e.is_a?(GraphQL::ExecutionError) }
394
+
395
+ hyd
396
+ end
397
+
398
+ def bulk_update
399
+ visible_ids = model.where(id: params.map { |p| p[:id] }).pluck(:id)
400
+ return not_allowed if (model.visible_for(user: user).pluck(:id) & visible_ids).size < visible_ids.size
401
+
402
+ hash = params.each_with_object({}) { |p, h| h[p.delete(:id)] = p }
403
+ failed_instances = []
404
+ result = model.update(hash.keys, hash.values).map { |e| e.errors.blank? ? e : (failed_instances << e && nil) }
405
+ hyd = Graphql::HydrateQuery.new(model.where(id: result.compact.map(&:id)), @context).run.compact + failed_instances.map do |i|
406
+ graphql_error(i.errors.full_messages)
422
407
  end
408
+ hyd.all? { |e| e.is_a?(GraphQL::ExecutionError) } ? hyd.first : hyd
423
409
  end
424
410
 
425
411
  def update
426
412
  return not_allowed if write_not_allowed
427
413
 
428
- if user.action?("can_update_#{singular_resource}")
429
- object.update_attributes(params) ? object : graphql_error(object.errors.full_messages.join(', '))
430
- elsif user.action?("can_update_#{singular_resource}_with_verif")
431
- create_update_verification
414
+ if object.update_attributes(params)
415
+ object
432
416
  else
433
- not_allowed
417
+ graphql_error(object.errors.full_messages.join(', '))
434
418
  end
435
419
  end
436
420
 
437
421
  def destroy
438
- return not_allowed if write_not_allowed || !user.action?("can_delete_#{singular_resource}")
422
+ object = model.find_by(id: params[:id])
423
+ return not_allowed if write_not_allowed
439
424
 
440
- object.destroy ? object : graphql_error(object.errors.full_messages.join(', '))
425
+ if object.destroy
426
+ object
427
+ else
428
+ graphql_error(object.errors.full_messages.join(', '))
429
+ end
441
430
  end
442
431
 
443
432
  private
444
433
 
445
- def create_update_verification
446
- Verification.create(
447
- action: 'update', model: model.to_s, params: params.merge(id: object.id), user_id: user.id
448
- )
449
- graphql_error("Pending verification for a #{singular_resource} update")
450
- end
451
-
452
434
  def write_not_allowed
453
- return true unless object
454
-
455
- !model.writable_for(user: user).pluck(:id).include?(object.id)
435
+ !model.visible_for(user: user).include?(object) if object
456
436
  end
457
437
 
458
438
  def access_not_allowed
459
- return true unless object
460
-
461
- !model.visible_for(user: user).pluck(:id).include?(object.id)
439
+ !model.visible_for(user: user).include?(object) if object
462
440
  end
463
441
 
464
442
  def not_allowed
@@ -497,11 +475,15 @@ module GraphqlRailsApi
497
475
 
498
476
  end
499
477
 
500
-
501
478
  STRING
502
479
  )
503
480
  end
504
481
 
482
+
483
+ def append(file_name)
484
+
485
+ end
486
+
505
487
  def write_at(file_name, line, data)
506
488
  open(file_name, 'r+') do |f|
507
489
  while (line -= 1).positive?
@@ -8,7 +8,7 @@ class GraphqlResourceGenerator < Rails::Generators::NamedBase
8
8
  end
9
9
 
10
10
  TYPES_MAPPING = {
11
- 'id' => 'types.ID',
11
+ 'id' => 'types.String',
12
12
  'uuid' => 'types.String',
13
13
  'boolean' => 'types.Boolean',
14
14
  'float' => 'types.Float',
@@ -55,13 +55,8 @@ class GraphqlResourceGenerator < Rails::Generators::NamedBase
55
55
  end
56
56
 
57
57
  def parse_args
58
- if Graphql::Rails::Api::Config.instance.id_type == :uuid
59
- @id_db_type = 'uuid'
60
- @id_type = 'types.String'
61
- else
62
- @id_db_type = 'integer'
63
- @id_type = 'types.ID'
64
- end
58
+ @id_db_type = 'uuid'
59
+ @id_type = 'types.String'
65
60
 
66
61
  @resource = file_name.singularize
67
62
  @has_many = []
@@ -100,7 +95,7 @@ class GraphqlResourceGenerator < Rails::Generators::NamedBase
100
95
  <<~STRING
101
96
  class Create#{resource.camelize} < ActiveRecord::Migration[5.2]
102
97
  def change
103
- create_table :#{resource.pluralize}, #{'id: :uuid ' if Graphql::Rails::Api::Config.instance.id_type == :uuid}do |t|
98
+ create_table :#{resource.pluralize}, id: :uuid do |t|
104
99
  #{fields}
105
100
  t.timestamps
106
101
  end
@@ -4,13 +4,12 @@ require 'rkelly'
4
4
  module Graphql
5
5
  class HydrateQuery
6
6
 
7
- def initialize(model, context, order_by: nil, filter: nil, check_visibility: true, id: nil, user: nil)
7
+ def initialize(model, context, order_by: nil, filter: nil, id: nil, user: nil)
8
8
  @context = context
9
9
  @filter = filter
10
10
  @order_by = order_by
11
11
  @model = model
12
12
  @models = [model_name.singularize.camelize]
13
- @check_visibility = check_visibility
14
13
  @id = id
15
14
  @user = user
16
15
  end
@@ -19,14 +18,17 @@ module Graphql
19
18
  @model = @model.where(transform_filter(@filter)) if @filter
20
19
  @model = @model.order(@order_by) if @order_by
21
20
  @model = @model.where(id: @id) if @id
22
- plucked = @model.deep_pluck(*hash_to_array_of_hashes(parse_fields(@context&.irep_node), @model))
21
+ plucked = DeepPluck::Model.new(@model.visible_for(user: @user), user: @user).add(
22
+ hash_to_array_of_hashes(parse_fields(@context&.irep_node), @model)
23
+ ).load_all
23
24
  result = plucked_attr_to_structs(plucked, model_name.singularize.camelize.constantize)&.compact
24
25
  @id ? result.first : result
25
26
  end
26
27
 
27
28
  def transform_filter(filter)
28
- parsed_filter = RKelly::Parser.new.parse(filter).to_ecma
29
- parsed_filter.gsub('||', 'OR').gsub('&&', 'AND').gsub('===', '=').gsub('==', '=').delete(';')
29
+ parsed_filter = RKelly::Parser.new.parse(filter.gsub('like', ' | ')).to_ecma
30
+ parsed_filter.gsub(' | ', ' like ').
31
+ gsub('||', 'OR').gsub('&&', 'AND').gsub('===', '=').gsub('==', '=').delete(';')
30
32
  end
31
33
 
32
34
  def plucked_attr_to_structs(arr, parent_model)
@@ -34,13 +36,9 @@ module Graphql
34
36
  end
35
37
 
36
38
  def hash_to_struct(hash, parent_model)
37
- if @check_visibility &&
38
- (visibility_hash[parent_model].blank? || !visibility_hash[parent_model].include?(hash['id']))
39
- return
40
- end
41
-
42
39
  hash.each_with_object(OpenStruct.new) do |(k, v), struct|
43
40
  m = evaluate_model(parent_model, k)
41
+
44
42
  next struct[k.to_sym] = plucked_attr_to_structs(v, m) if v.is_a?(Array) && m
45
43
 
46
44
  next struct[k.to_sym] = hash_to_struct(v, m) if v.is_a?(Hash) && m
@@ -49,15 +47,6 @@ module Graphql
49
47
  end
50
48
  end
51
49
 
52
- def visibility_hash
53
- @visibility_hash ||= @models.reject(&:blank?).each_with_object({}) do |model, hash|
54
- visible_ids = model.constantize.visible_for(user: @user)&.pluck(:id)
55
- next if visible_ids.blank?
56
-
57
- hash[model.constantize] = visible_ids
58
- end
59
- end
60
-
61
50
  def hash_to_array_of_hashes(hash, parent_class)
62
51
  return if parent_class.nil? || hash.nil?
63
52
 
@@ -66,6 +55,7 @@ module Graphql
66
55
 
67
56
  hash.each_with_object([]) do |(k, v), arr|
68
57
  next arr << k if parent_class.new.attributes.key?(k)
58
+ next arr << v if parent_class.new.attributes.key?(v)
69
59
 
70
60
  klass = evaluate_model(parent_class, k)
71
61
  @models << klass.to_s unless @models.include?(klass.to_s)
@@ -94,8 +84,11 @@ module Graphql
94
84
  end
95
85
 
96
86
  def evaluate_model(parent, child)
97
- child_class_name = child.to_s.singularize.camelize
87
+ return unless parent.reflect_on_association(child)
88
+
89
+ child_class_name = parent.reflect_on_association(child).class_name
98
90
  parent_class_name = parent.to_s.singularize.camelize
91
+
99
92
  return child_class_name.constantize if activerecord_model?(child_class_name)
100
93
 
101
94
  return unless activerecord_model?(parent_class_name)
@@ -111,7 +104,7 @@ module Graphql
111
104
  return if fields.blank?
112
105
 
113
106
  fields.each_with_object({}) do |(k, v), h|
114
- h[k] = v.scoped_children == {} ? nil : parse_fields(v)
107
+ h[k] = v.scoped_children == {} ? v.definition.name : parse_fields(v)
115
108
  end
116
109
  end
117
110
 
@@ -4,7 +4,6 @@ module Graphql
4
4
  class Config
5
5
 
6
6
  include Singleton
7
- attr_accessor :id_type
8
7
 
9
8
  def self.query_resources
10
9
  Dir.glob("#{File.expand_path('.')}/app/graphql/*/type.rb").map do |dir|
@@ -1,7 +1,7 @@
1
1
  module Graphql
2
2
  module Rails
3
3
  module Api
4
- VERSION = '0.6.0'.freeze
4
+ VERSION = '0.7.2'.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.6.0
4
+ version: 0.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - poilon
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-12-04 00:00:00.000000000 Z
11
+ date: 2019-02-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: graphql
@@ -25,25 +25,19 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.7'
27
27
  - !ruby/object:Gem::Dependency
28
- name: deep_pluck
28
+ name: deep_pluck_with_authorization
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '1.1'
34
- - - ">="
35
- - !ruby/object:Gem::Version
36
- version: 1.1.0
33
+ version: 1.1.2
37
34
  type: :runtime
38
35
  prerelease: false
39
36
  version_requirements: !ruby/object:Gem::Requirement
40
37
  requirements:
41
38
  - - "~>"
42
39
  - !ruby/object:Gem::Version
43
- version: '1.1'
44
- - - ">="
45
- - !ruby/object:Gem::Version
46
- version: 1.1.0
40
+ version: 1.1.2
47
41
  - !ruby/object:Gem::Dependency
48
42
  name: rails
49
43
  requirement: !ruby/object:Gem::Requirement
@@ -89,7 +83,8 @@ files:
89
83
  - MIT-LICENSE
90
84
  - README.md
91
85
  - Rakefile
92
- - lib/generators/graphql_connections/graphql_all_connections_generator.rb
86
+ - lib/generators/graphql_add_fields/graphql_add_fields_generator.rb
87
+ - lib/generators/graphql_all_connections/graphql_all_connections_generator.rb
93
88
  - lib/generators/graphql_mutations/graphql_bulk_create_mutations_generator.rb
94
89
  - lib/generators/graphql_mutations/graphql_bulk_update_mutations_generator.rb
95
90
  - lib/generators/graphql_mutations/graphql_mutations_generator.rb