Class: IntrospectiveGrape::API
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) ⇒ Object
-
.build_nested_attributes(routes, hash) ⇒ Object
-
.build_routes(routes, model, reflection_name = nil) ⇒ Object
-
.convert_nested_params_hash(dsl, routes) ⇒ 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
-
.generate_nested_params(dsl, action, model, fields) ⇒ Object
-
.generate_params(dsl, action, model, fields, is_root_endpoint = false) ⇒ Object
-
.inherited(child) ⇒ Object
-
.is_file_attachment?(model, field) ⇒ Boolean
-
.param_required?(model, f) ⇒ Boolean
-
.param_type(model, f) ⇒ Object
-
.restful(model, strong_params = [], routes = []) ⇒ Object
We will probably need before and after hooks eventually, but haven’t yet…
-
.validations(model, field) ⇒ Object
Methods included from Helpers
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_simple_filter, custom_filter, custom_filters, declare_filter_params, default_sort, filter_on, filters, identifier_filter, simple_filters, 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) ⇒ Object
410
411
412
413
414
415
416
|
# File 'lib/introspective_grape/api.rb', line 410
def add_destroy_param(dsl,model,reflection)
raise "#{model} does not accept nested attributes for #{reflection}" if !model.nested_attributes_options[reflection.to_sym]
if model.nested_attributes_options[reflection.to_sym][:allow_destroy]
dsl.optional '_destroy', type: Integer
end
end
|
.build_nested_attributes(routes, hash) ⇒ Object
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
|
# File 'lib/introspective_grape/api.rb', line 292
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
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
|
# File 'lib/introspective_grape/api.rb', line 270
def build_routes(routes, model, reflection_name=nil)
routes = routes.clone
parent_model = routes.last.try(:model)
return routes if model == parent_model
name = reflection_name || model.name.underscore
reflection = parent_model.try(:reflections).try(:fetch,reflection_name)
many = parent_model && PLURAL_REFLECTIONS.include?( reflection.class ) ? true : false
swaggerKey = 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?: many, key: "#{name.singularize}_id".to_sym, swaggerKey: swaggerKey, reflection: reflection)
end
|
.convert_nested_params_hash(dsl, routes) ⇒ Object
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
# File 'lib/introspective_grape/api.rb', line 129
def convert_nested_params_hash(dsl, routes)
root = routes.first
klass = root.klass
dsl.after_validation do
if params[root.key]
@model = root.model.includes( klass.default_includes(root.model) ).find(params[root.key])
end
if routes.size > 1
nested_attributes = klass.build_nested_attributes(routes[1..-1], params.except(root.key,:api_key) )
@params.merge!( nested_attributes ) if nested_attributes.kind_of?(Hash)
end
end
end
|
.define_create(dsl, routes, model, api_params) ⇒ Object
222
223
224
225
226
227
228
229
230
231
232
233
234
235
|
# File 'lib/introspective_grape/api.rb', line 222
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 || "creates a new #{name} record"
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
257
258
259
260
261
262
263
264
265
266
267
|
# File 'lib/introspective_grape/api.rb', line 257
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 || "destroys the details of a #{name}"
end
dsl.delete ":#{routes.last.swaggerKey}" do
authorize @model, :destroy?
present status: (klass.find_leaf(routes, @model, params).destroy ? true : false)
end
end
|
.define_endpoints(routes, api_params) ⇒ Object
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
|
# File 'lib/introspective_grape/api.rb', line 158
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.swaggerKey}/" }.join + routes.last.name.pluralize
resource namespace do
convert_nested_params_hash(self, routes)
define_restful_api(self, routes, model, api_params)
end
end
|
.define_index(dsl, routes, model, api_params) ⇒ Object
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
|
# File 'lib/introspective_grape/api.rb', line 177
def define_index(dsl, routes, model, api_params)
include Grape::Kaminari
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 || "returns list of all #{name}"
end
dsl.params do
klass.declare_filter_params(self, klass, model, api_params)
end
if klass.
paginate per_page: klass.[:per_page]||25, max_per_page: klass.[:max_per_page], offset: klass.[:offset]||0
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
151
152
153
154
155
156
|
# File 'lib/introspective_grape/api.rb', line 151
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
# File 'lib/introspective_grape/api.rb', line 107
def define_routes(routes, api_params)
define_endpoints(routes, api_params)
model = routes.last.model || return
api_params.select{|a| a.kind_of?(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
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
210
211
212
213
214
215
216
217
218
219
220
|
# File 'lib/introspective_grape/api.rb', line 210
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 || "returns details on a #{name}"
end
dsl.get ":#{routes.last.swaggerKey}" 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
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
|
# File 'lib/introspective_grape/api.rb', line 237
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 || "updates the details of a #{name}"
end
dsl.params do
klass.generate_params(self, :update, model, api_params, true)
end
dsl.put ":#{routes.last.swaggerKey}" do
authorize @model, :update?
@model.update_attributes!( safe_params(params).permit(klass.whitelist) )
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
|
.generate_nested_params(dsl, action, model, fields) ⇒ Object
348
349
350
351
352
353
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
|
# File 'lib/introspective_grape/api.rb', line 348
def generate_nested_params(dsl,action,model,fields)
klass = self
fields.each do |r,v|
reflection = r.to_s.sub(/_attributes$/,'') relation = begin model.reflections[reflection].class_name.constantize rescue model end
if is_file_attachment?(model,r)
s = [:filename, :type, :name, :tempfile, :head]-v
if s.present?
Rails.logger.warn "Missing required file upload parameters #{s} for uploader field #{r}"
end
elsif PLURAL_REFLECTIONS.include?( model.reflections[reflection].class )
dsl.optional r, type: Array do |dl|
klass.generate_params(dl,action,relation,v)
klass.add_destroy_param(dl,model,reflection) unless action==:create
end
else
dsl.optional r, type: Hash do |dl|
klass.generate_params(dl,action,relation,v)
klass.add_destroy_param(dl,model,reflection) unless action==:create
end
end
end
end
|
.generate_params(dsl, action, model, fields, is_root_endpoint = false) ⇒ Object
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
|
# File 'lib/introspective_grape/api.rb', line 315
def generate_params(dsl, action, model, fields, is_root_endpoint=false)
raise "Invalid action: #{action}" unless [:update, :create].include?(action)
fields -= [:id] if is_root_endpoint
fields.each do |field|
if field.kind_of?(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
.is_file_attachment?(model, field) ⇒ Boolean
381
382
383
384
385
|
# File 'lib/introspective_grape/api.rb', line 381
def is_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).kind_of?(Paperclip::Attachment)
end
|
.param_required?(model, f) ⇒ Boolean
401
402
403
404
405
406
407
408
|
# File 'lib/introspective_grape/api.rb', line 401
def param_required?(model,f)
return false if skip_presence_validations.include?(f)
validated_field = f =~ /_id/ ? f.to_s.sub(/_id\z/,'').to_sym : f.to_sym
model.validators_on(validated_field).any? {|v| v.kind_of? ActiveRecord::Validations::PresenceValidator }
end
|
.param_type(model, f) ⇒ Object
387
388
389
390
391
392
393
394
395
396
397
398
399
|
# File 'lib/introspective_grape/api.rb', line 387
def param_type(model,f)
f = f.to_s
db_type = (model.try(:columns_hash)||{})[f].try(:type)
( is_file_attachment?(model,f) && Rack::Multipart::UploadedFile ) ||
(model.try(:grape_param_types)||{}).with_indifferent_access[f] ||
Pg2Ruby[db_type] ||
begin db_type.to_s.camelize.constantize rescue nil end ||
String
end
|
.restful(model, strong_params = [], routes = []) ⇒ Object
We will probably need before and after hooks eventually, but haven’t yet… api_actions.each do |a|
define_method "before_#{a}_hook" do |model,params| ; end
define_method "after_#{a}_hook" do |model,params| ; end
end
71
72
73
74
75
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
|
# File 'lib/introspective_grape/api.rb', line 71
def restful(model, strong_params=[], routes=[])
if model.respond_to?(:attribute_param_types)
raise IntrospectiveGrapeError.new("#{model.name}'s attribute_param_types class method needs to be changed to grape_param_types")
end
begin ActiveRecord::Migration.check_pending! rescue return end
strong_params.map!{|f| f.kind_of?(String) ? f.to_sym : f }
strong_params = strong_params.blank? ? model.attribute_names.map(&:to_sym)-[: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
|
.validations(model, field) ⇒ Object
344
345
346
|
# File 'lib/introspective_grape/api.rb', line 344
def validations(model, field)
(model.try(:grape_validations) || {}).with_indifferent_access[field] || {}
end
|