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
-
.db_type_constant(db_type) ⇒ Object
-
.define_create(dsl, routes, model, api_params) ⇒ Object
-
.define_destroy(dsl, routes, model, _api_params) ⇒ Object
rubocop:enable Metrics/AbcSize.
-
.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
447
448
449
450
451
452
453
454
455
456
|
# File 'lib/introspective_grape/api.rb', line 447
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
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
|
# File 'lib/introspective_grape/api.rb', line 304
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
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
|
# File 'lib/introspective_grape/api.rb', line 277
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 OpenStruct.new(klass: self, name: name, param: "#{name}_attributes", model: model,
many?: plural?(parent_model, reflection),
key: "#{name.singularize}_id".to_sym,
swagger_key: swagger_key, reflection: reflection)
end
|
.check_model_for_type(model, field) ⇒ Object
426
427
428
|
# File 'lib/introspective_grape/api.rb', line 426
def check_model_for_type(model, field)
(model.try(:grape_param_types) || {}).with_indifferent_access[field]
end
|
.convert_nested_params_hash(dsl, routes) ⇒ Object
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
|
# File 'lib/introspective_grape/api.rb', line 229
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
430
431
432
433
434
435
436
|
# File 'lib/introspective_grape/api.rb', line 430
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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
|
# File 'lib/introspective_grape/api.rb', line 175
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
rubocop:enable Metrics/AbcSize
214
215
216
217
218
219
220
221
222
223
224
225
226
227
|
# File 'lib/introspective_grape/api.rb', line 214
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
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
|
# File 'lib/introspective_grape/api.rb', line 258
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
|
# File 'lib/introspective_grape/api.rb', line 135
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
251
252
253
254
255
256
|
# File 'lib/introspective_grape/api.rb', line 251
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
|
# File 'lib/introspective_grape/api.rb', line 113
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
163
164
165
166
167
168
169
170
171
172
173
|
# File 'lib/introspective_grape/api.rb', line 163
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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
|
# File 'lib/introspective_grape/api.rb', line 190
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_attributes!( 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
402
403
404
405
406
|
# File 'lib/introspective_grape/api.rb', line 402
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
394
395
396
397
398
399
400
|
# File 'lib/introspective_grape/api.rb', line 394
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
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
|
# File 'lib/introspective_grape/api.rb', line 359
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
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
|
# File 'lib/introspective_grape/api.rb', line 325
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
.merge_nested_params(routes, params) ⇒ Object
246
247
248
249
|
# File 'lib/introspective_grape/api.rb', line 246
def merge_nested_params(routes, params)
attr = params.reject {|k| [routes.first.key, :api_key].include?(k) }
build_nested_attributes(routes[1..-1], attr)
end
|
.param_required?(model, field) ⇒ Boolean
438
439
440
441
442
443
444
445
|
# File 'lib/introspective_grape/api.rb', line 438
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
408
409
410
411
412
413
414
415
416
417
418
419
420
|
# File 'lib/introspective_grape/api.rb', line 408
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
300
301
302
|
# File 'lib/introspective_grape/api.rb', line 300
def plural?(model, reflection)
(model && PLURAL_REFLECTIONS.include?(reflection.class))
end
|
.plural_reflection?(model, reflection) ⇒ Boolean
390
391
392
|
# File 'lib/introspective_grape/api.rb', line 390
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
|
# 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_pending! rescue return end
strong_params.map! {|f| f.is_a?(String) ? f.to_sym : f }
strong_params = strong_params.blank? ? model.attribute_names.map(&:to_sym) - %i(id updated_at created_at) : strong_params
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
422
423
424
|
# File 'lib/introspective_grape/api.rb', line 422
def uploaded_file?(model, field)
file_attachment?(model, field) && Rack::Multipart::UploadedFile
end
|
.validations(model, field) ⇒ Object
355
356
357
|
# File 'lib/introspective_grape/api.rb', line 355
def validations(model, field)
(model.try(:grape_validations) || {}).with_indifferent_access[field] || {}
end
|