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
442
443
444
445
446
447
448
449
450
451
|
# File 'lib/introspective_grape/api.rb', line 442
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
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
|
# File 'lib/introspective_grape/api.rb', line 299
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
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
|
# File 'lib/introspective_grape/api.rb', line 272
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
421
422
423
|
# File 'lib/introspective_grape/api.rb', line 421
def check_model_for_type(model, field)
(model.try(:grape_param_types) || {}).with_indifferent_access[field]
end
|
.convert_nested_params_hash(dsl, routes) ⇒ Object
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
|
# File 'lib/introspective_grape/api.rb', line 224
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
425
426
427
428
429
430
431
|
# File 'lib/introspective_grape/api.rb', line 425
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
|
# File 'lib/introspective_grape/api.rb', line 173
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
212
213
214
215
216
217
218
219
220
221
222
|
# File 'lib/introspective_grape/api.rb', line 212
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.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
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
|
# File 'lib/introspective_grape/api.rb', line 253
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
133
134
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
|
# File 'lib/introspective_grape/api.rb', line 133
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
246
247
248
249
250
251
|
# File 'lib/introspective_grape/api.rb', line 246
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
# File 'lib/introspective_grape/api.rb', line 111
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
161
162
163
164
165
166
167
168
169
170
171
|
# File 'lib/introspective_grape/api.rb', line 161
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
|
# File 'lib/introspective_grape/api.rb', line 188
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
397
398
399
400
401
|
# File 'lib/introspective_grape/api.rb', line 397
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
389
390
391
392
393
394
395
|
# File 'lib/introspective_grape/api.rb', line 389
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
354
355
356
357
358
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
|
# File 'lib/introspective_grape/api.rb', line 354
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
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
|
# File 'lib/introspective_grape/api.rb', line 320
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)
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
241
242
243
244
|
# File 'lib/introspective_grape/api.rb', line 241
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
433
434
435
436
437
438
439
440
|
# File 'lib/introspective_grape/api.rb', line 433
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
403
404
405
406
407
408
409
410
411
412
413
414
415
|
# File 'lib/introspective_grape/api.rb', line 403
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
295
296
297
|
# File 'lib/introspective_grape/api.rb', line 295
def plural?(model, reflection)
(model && PLURAL_REFLECTIONS.include?(reflection.class))
end
|
.plural_reflection?(model, reflection) ⇒ Boolean
385
386
387
|
# File 'lib/introspective_grape/api.rb', line 385
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
|
# 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)
define_routes(routes, whitelist)
resource routes.first.name.pluralize do
yield if block_given?
end
end
|
.uploaded_file?(model, field) ⇒ Boolean
417
418
419
|
# File 'lib/introspective_grape/api.rb', line 417
def uploaded_file?(model, field)
file_attachment?(model, field) && Rack::Multipart::UploadedFile
end
|
.validations(model, field) ⇒ Object
350
351
352
|
# File 'lib/introspective_grape/api.rb', line 350
def validations(model, field)
(model.try(:grape_validations) || {}).with_indifferent_access[field] || {}
end
|