Class: IntrospectiveGrape::API
Overview
rubocop:disable Metrics/ClassLength
Constant Summary
collapse
- PLURAL_REFLECTIONS =
[ActiveRecord::Reflection::HasManyReflection, ActiveRecord::Reflection::HasManyReflection].freeze
- PG2RUBY =
mapping of activerecord/postgres ‘type’s to ruby data classes, where they differ
{ datetime: DateTime }.freeze
Constants included
from Helpers
Helpers::API_ACTIONS
Class Method Summary
collapse
-
.add_destroy_param(dsl, model, reflection, action) ⇒ Object
-
.build_nested_attributes(routes, hash) ⇒ Object
-
.build_routes(routes, model, reflection_name = nil) ⇒ Object
-
.check_model_for_type(model, field) ⇒ Object
-
.convert_nested_params_hash(dsl, routes) ⇒ Object
rubocop:enable Metrics/AbcSize.
-
.db_type_constant(db_type) ⇒ Object
-
.define_create(dsl, routes, model, api_params) ⇒ Object
-
.define_destroy(dsl, routes, model, _api_params) ⇒ Object
-
.define_endpoints(routes, api_params) ⇒ Object
-
.define_index(dsl, routes, model, api_params) ⇒ Object
-
.define_restful_api(dsl, routes, model, api_params) ⇒ Object
-
.define_routes(routes, api_params) ⇒ Object
-
.define_show(dsl, routes, model, _api_params) ⇒ Object
-
.define_update(dsl, routes, model, api_params) ⇒ Object
-
.file_attachment?(model, field) ⇒ Boolean
-
.find_relation(model, reflection) ⇒ Object
-
.generate_nested_params(dsl, action, model, fields) ⇒ Object
-
.generate_params(dsl, action, model, fields, is_root_endpoint = false) ⇒ Object
-
.inherited(child) ⇒ Object
-
.merge_nested_params(routes, params) ⇒ Object
-
.param_required?(model, field) ⇒ Boolean
-
.param_type(model, field) ⇒ Object
-
.plural?(model, reflection) ⇒ Boolean
-
.plural_reflection?(model, reflection) ⇒ Boolean
-
.restful(model, strong_params = [], routes = []) ⇒ Object
rubocop:disable Metrics/AbcSize.
-
.uploaded_file?(model, field) ⇒ Boolean
-
.validations(model, field) ⇒ Object
Methods included from Helpers
all_or_none, authentication_method, authentication_method=, default_includes, exclude_actions, include_actions, paginate, pagination, skip_presence_validations, whitelist
add_new_records_to_root_record, create_new_record
Methods included from Filters
apply_filter_params, apply_filters, apply_simple_filter, custom_filter, custom_filters, declare_filter_params, declare_simple_filter, default_sort, filter_doc, filter_on, filters, humanize_date_range, identifier_filter?, simple_filters, special_filter_enabled?, timestamp_filter
Methods included from Traversal
find_leaf, find_leaves, verify_record_found, verify_records_found
Methods included from Doc
create_documentation, destroy_documentation, index_documentation, show_documentation, update_documentation
snake_params_before_validation
Class Method Details
.add_destroy_param(dsl, model, reflection, action) ⇒ Object
457
458
459
460
461
462
463
464
465
466
|
# File 'lib/introspective_grape/api.rb', line 457
def add_destroy_param(dsl, model, reflection, action)
return if action == :create
raise "#{model} does not accept nested attributes for #{reflection}" unless model.nested_attributes_options[reflection.to_sym]
return unless model.nested_attributes_options[reflection.to_sym][:allow_destroy]
dsl.optional '_destroy', type: Integer
end
|
.build_nested_attributes(routes, hash) ⇒ Object
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
|
# File 'lib/introspective_grape/api.rb', line 314
def build_nested_attributes(routes, hash)
return {} if routes.blank? || hash[routes.first.param]
route = routes.shift
id = hash.delete route.key
attributes = id ? { id: id } : {}
attributes.merge!( hash ) if routes.blank?
if route.many?
{ route.param => [attributes.merge( build_nested_attributes(routes, hash) )] }
else
{ route.param => attributes.merge( build_nested_attributes(routes, hash) ) }
end
end
|
.build_routes(routes, model, reflection_name = nil) ⇒ Object
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
|
# File 'lib/introspective_grape/api.rb', line 282
def build_routes(routes, model, reflection_name=nil)
routes = routes.clone
parent_model = routes.last&.model
return routes if model == parent_model
name = reflection_name || model.name.underscore
reflection = parent_model&.reflections&.fetch(reflection_name)
swagger_key = IntrospectiveGrape.config.camelize_parameters ? "#{name.singularize.camelize(:lower)}Id" : "#{name.singularize}_id"
routes.push Route.new(
klass: self,
name: name,
param: "#{name}_attributes",
model: model,
many: plural?(parent_model, reflection),
key: :"#{name.singularize}_id",
swagger_key: swagger_key, reflection: reflection
)
end
|
.check_model_for_type(model, field) ⇒ Object
436
437
438
|
# File 'lib/introspective_grape/api.rb', line 436
def check_model_for_type(model, field)
(model.try(:grape_param_types) || {}).with_indifferent_access[field]
end
|
.convert_nested_params_hash(dsl, routes) ⇒ Object
rubocop:enable Metrics/AbcSize
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
|
# File 'lib/introspective_grape/api.rb', line 234
def convert_nested_params_hash(dsl, routes)
root = routes.first
klass = self
dsl.after_validation do
next unless params[root.key]
@model = root.model.includes( root.klass.default_includes(root.model) ).find(params[root.key])
@params.merge!( klass.merge_nested_params(routes, params) )
end
end
|
.db_type_constant(db_type) ⇒ Object
440
441
442
443
444
445
446
|
# File 'lib/introspective_grape/api.rb', line 440
def db_type_constant(db_type)
begin
db_type.to_s.camelize.constantize
rescue StandardError
nil
end
end
|
.define_create(dsl, routes, model, api_params) ⇒ Object
180
181
182
183
184
185
186
187
188
189
190
191
192
193
|
# File 'lib/introspective_grape/api.rb', line 180
def define_create(dsl, routes, model, api_params)
name = routes.last.name.singularize
klass = routes.first.klass
dsl.desc "create a #{name}" do
detail klass.create_documentation(name)
end
dsl.params do
klass.generate_params(self, :create, model, api_params, true)
end
dsl.post do
representation = @model ? klass.add_new_records_to_root_record(self, routes, params, @model) : klass.create_new_record(self, routes, params)
present representation, with: "#{klass}::#{model}Entity".constantize
end
end
|
.define_destroy(dsl, routes, model, _api_params) ⇒ Object
218
219
220
221
222
223
224
225
226
227
228
229
230
231
|
# File 'lib/introspective_grape/api.rb', line 218
def define_destroy(dsl, routes, model, _api_params)
klass = routes.first.klass
name = routes.last.name.singularize
dsl.desc "destroy a #{name}" do
detail klass.destroy_documentation(name)
end
dsl.params do
requires routes.last.swagger_key, type: klass.param_type(model, model.primary_key)
end
dsl.delete ":#{routes.last.swagger_key}" do
authorize @model, :destroy?
present status: (klass.find_leaf(routes, @model, params).destroy ? true : false)
end
end
|
.define_endpoints(routes, api_params) ⇒ Object
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
|
# File 'lib/introspective_grape/api.rb', line 263
def define_endpoints(routes, api_params)
routes = routes.clone
api_params = api_params.clone
model = routes.last.model || return
namespace = routes[0..-2].map {|p| "#{p.name.pluralize}/:#{p.swagger_key}/" }.join + routes.last.name.pluralize
klass = self
resource namespace do
klass.convert_nested_params_hash(self, routes)
klass.define_restful_api(self, routes, model, api_params)
end
end
|
.define_index(dsl, routes, model, api_params) ⇒ Object
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
|
# File 'lib/introspective_grape/api.rb', line 140
def define_index(dsl, routes, model, api_params)
root = routes.first
klass = routes.first.klass
name = routes.last.name.pluralize
simple_filters(klass, model, api_params)
dsl.desc "list #{name}" do
detail klass.index_documentation(name)
end
dsl.params do
klass.declare_filter_params(self, klass, model, api_params)
use :pagination, per_page: klass.[:per_page]||25, max_per_page: klass.[:max_per_page], offset: klass.[:offset]||0 if klass.
end
dsl.get '/' do
authorize root.model.new, :index?
records = policy_scope( root.model.includes(klass.default_includes(root.model)) )
records = klass.apply_filter_params(klass, model, api_params, params, records)
records = records.map {|r| klass.find_leaves( routes, r, params ) }.flatten.compact.uniq
records = paginate(Kaminari.paginate_array(records)) if klass.
present records, with: "#{klass}::#{model}Entity".constantize
end
end
|
.define_restful_api(dsl, routes, model, api_params) ⇒ Object
256
257
258
259
260
261
|
# File 'lib/introspective_grape/api.rb', line 256
def define_restful_api(dsl, routes, model, api_params)
API_ACTIONS.each do |action|
send("define_#{action}", dsl, routes, model, api_params) unless exclude_actions(model).include?(action)
end
end
|
.define_routes(routes, api_params) ⇒ Object
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
|
# File 'lib/introspective_grape/api.rb', line 118
def define_routes(routes, api_params)
define_endpoints(routes, api_params)
model = routes.last.model || return
api_params.select {|a| a.is_a?(Hash) }.each do |nested|
nested.each do |r, fields|
reflection_name = r.to_s.sub(/_attributes$/, '')
begin
relation = model.reflections[reflection_name].class_name.constantize
rescue StandardError
Rails.logger.fatal "Can't find associated model for #{r} on #{model}"
end
next_routes = build_routes(routes, relation, reflection_name)
define_routes(next_routes, fields)
end
end
end
|
.define_show(dsl, routes, model, _api_params) ⇒ Object
168
169
170
171
172
173
174
175
176
177
178
|
# File 'lib/introspective_grape/api.rb', line 168
def define_show(dsl, routes, model, _api_params)
name = routes.last.name.singularize
klass = routes.first.klass
dsl.desc "retrieve a #{name}" do
detail klass.show_documentation(name)
end
dsl.get ":#{routes.last.swagger_key}" do
authorize @model, :show?
present klass.find_leaf(routes, @model, params), with: "#{klass}::#{model}Entity".constantize
end
end
|
.define_update(dsl, routes, model, api_params) ⇒ Object
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
|
# File 'lib/introspective_grape/api.rb', line 195
def define_update(dsl, routes, model, api_params)
klass = routes.first.klass
name = routes.last.name.singularize
dsl.desc "update a #{name}" do
detail klass.update_documentation(name)
end
dsl.params do
klass.generate_params(self, :update, model, api_params, true)
end
dsl.put ":#{routes.last.swagger_key}" do
authorize @model, :update?
@model.update!( safe_params(params).permit(klass.whitelist) )
if IntrospectiveGrape.config.skip_object_reload
present klass.find_leaf(routes, @model, params), with: "#{klass}::#{model}Entity".constantize
else
default_includes = routes.first.klass.default_includes(routes.first.model)
present klass.find_leaf(routes, @model.class.includes(default_includes).find(@model.id), params), with: "#{klass}::#{model}Entity".constantize
end
end
end
|
.file_attachment?(model, field) ⇒ Boolean
412
413
414
415
416
|
# File 'lib/introspective_grape/api.rb', line 412
def file_attachment?(model, field)
(model.respond_to?(:uploaders) && model.uploaders[field.to_sym]) ||
(model.respond_to?(:attachment_definitions) && model.attachment_definitions[field.to_sym]) ||
(defined?(Paperclip::Attachment) && model.send(:new).try(field).is_a?(Paperclip::Attachment))
end
|
.find_relation(model, reflection) ⇒ Object
404
405
406
407
408
409
410
|
# File 'lib/introspective_grape/api.rb', line 404
def find_relation(model, reflection)
begin
model.reflections[reflection].class_name.constantize
rescue StandardError
model
end
end
|
.generate_nested_params(dsl, action, model, fields) ⇒ Object
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
|
# File 'lib/introspective_grape/api.rb', line 369
def generate_nested_params(dsl, action, model, fields)
klass = self
fields.each do |r, v|
reflection = r.to_s.sub(/_attributes$/, '')
relation = find_relation(model, reflection)
if file_attachment?(model, r)
s = i(filename type name tempfile head) - v
Rails.logger.warn "Missing required file upload parameters #{s} for uploader field #{r}" if s.present?
elsif plural_reflection?(model, reflection)
dsl.optional r, type: Array do |dl|
klass.generate_params(dl, action, relation, v)
klass.add_destroy_param(dl, model, reflection, action)
end
else
dsl.optional r, type: Hash do |dl|
klass.generate_params(dl, action, relation, v)
klass.add_destroy_param(dl, model, reflection, action)
end
end
end
end
|
.generate_params(dsl, action, model, fields, is_root_endpoint = false) ⇒ Object
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
|
# File 'lib/introspective_grape/api.rb', line 335
def generate_params(dsl, action, model, fields, is_root_endpoint=false)
raise "Invalid action: #{action}" unless i(update create).include?(action)
fields -= [:id] if is_root_endpoint
fields.each do |field|
if field.is_a?(Hash)
generate_nested_params(dsl, action, model, field)
elsif action == :create && param_required?(model, field)
dsl.requires field, { type: param_type(model, field) }.merge( validations(model, field) )
else
dsl.optional field, { type: param_type(model, field) }.merge( validations(model, field) )
end
end
end
|
.inherited(child) ⇒ Object
59
60
61
62
63
64
65
66
67
|
# File 'lib/introspective_grape/api.rb', line 59
def inherited(child)
super
child.before do
send(IntrospectiveGrape::API.authentication_method(self))
end
child.snake_params_before_validation if IntrospectiveGrape.config.camelize_parameters
end
|
.merge_nested_params(routes, params) ⇒ Object
251
252
253
254
|
# File 'lib/introspective_grape/api.rb', line 251
def merge_nested_params(routes, params)
attr = params.reject {|k| [routes.first.key, :api_key].include?(k) }
build_nested_attributes(routes[1..], attr)
end
|
.param_required?(model, field) ⇒ Boolean
448
449
450
451
452
453
454
455
|
# File 'lib/introspective_grape/api.rb', line 448
def param_required?(model, field)
return false if skip_presence_validations.include?(field)
validated_field = field.match?(/_id/) ? field.to_s.sub(/_id\z/, '').to_sym : field.to_sym
model.validators_on(validated_field).any? {|v| v.is_a? ActiveRecord::Validations::PresenceValidator }
end
|
.param_type(model, field) ⇒ Object
418
419
420
421
422
423
424
425
426
427
428
429
430
|
# File 'lib/introspective_grape/api.rb', line 418
def param_type(model, field)
field = field.to_s
db_type = (model&.try(:columns_hash) || {})[field]&.type
uploaded_file?(model, field) ||
check_model_for_type(model, field) ||
PG2RUBY[db_type] ||
db_type_constant(db_type) ||
String
end
|
.plural?(model, reflection) ⇒ Boolean
310
311
312
|
# File 'lib/introspective_grape/api.rb', line 310
def plural?(model, reflection)
model && PLURAL_REFLECTIONS.include?(reflection.class)
end
|
.plural_reflection?(model, reflection) ⇒ Boolean
400
401
402
|
# File 'lib/introspective_grape/api.rb', line 400
def plural_reflection?(model, reflection)
PLURAL_REFLECTIONS.include?( model.reflections[reflection].class )
end
|
.restful(model, strong_params = [], routes = []) ⇒ Object
rubocop:disable Metrics/AbcSize
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
# File 'lib/introspective_grape/api.rb', line 76
def restful(model, strong_params=[], routes=[])
raise IntrospectiveGrapeError.new("#{model.name}'s attribute_param_types class method needs to be changed to grape_param_types") if model.respond_to?(:attribute_param_types)
begin
ActiveRecord::Migration.check_all_pending!
rescue StandardError => e
warn "Migrations are pending: #{e.message}"
return
end
strong_params.map! {|f| f.is_a?(String) ? f.to_sym : f }
strong_params = model.attribute_names.map(&:to_sym) - i(id updated_at created_at) if strong_params.blank?
whitelist = whitelist( strong_params )
routes = build_routes(routes, model)
resource routes.first.name.pluralize do
yield if block_given?
end
define_routes(routes, whitelist)
end
|
.uploaded_file?(model, field) ⇒ Boolean
432
433
434
|
# File 'lib/introspective_grape/api.rb', line 432
def uploaded_file?(model, field)
file_attachment?(model, field) && Rack::Multipart::UploadedFile
end
|
.validations(model, field) ⇒ Object
365
366
367
|
# File 'lib/introspective_grape/api.rb', line 365
def validations(model, field)
(model.try(:grape_validations) || {}).with_indifferent_access[field] || {}
end
|