rest_framework 0.10.0 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +3 -3
- data/VERSION +1 -1
- data/app/views/rest_framework/routes_and_forms/_html_form.html.erb +6 -6
- data/app/views/rest_framework/routes_and_forms/_raw_form.html.erb +1 -1
- data/app/views/rest_framework/routes_and_forms/routes/_route.html.erb +1 -1
- data/lib/rest_framework/errors/{nil_passed_to_api_response_error.rb → nil_passed_to_render_api_error.rb} +3 -3
- data/lib/rest_framework/errors.rb +1 -1
- data/lib/rest_framework/filters/ordering_filter.rb +4 -7
- data/lib/rest_framework/filters/query_filter.rb +1 -4
- data/lib/rest_framework/filters/ransack_filter.rb +4 -4
- data/lib/rest_framework/filters/search_filter.rb +3 -6
- data/lib/rest_framework/mixins/base_controller_mixin.rb +92 -100
- data/lib/rest_framework/mixins/bulk_model_controller_mixin.rb +7 -7
- data/lib/rest_framework/mixins/model_controller_mixin.rb +215 -190
- data/lib/rest_framework/paginators/page_number_paginator.rb +12 -11
- data/lib/rest_framework/routers.rb +11 -1
- data/lib/rest_framework/serializers/native_serializer.rb +10 -10
- data/lib/rest_framework/utils.rb +24 -24
- metadata +3 -3
@@ -24,14 +24,13 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
24
24
|
fields: nil,
|
25
25
|
field_config: nil,
|
26
26
|
|
27
|
-
# Options for what should be included/excluded from default fields.
|
28
|
-
exclude_associations: false,
|
29
|
-
}
|
30
|
-
RRF_BASE_MODEL_INSTANCE_CONFIG = {
|
31
27
|
# Attributes for finding records.
|
32
28
|
find_by_fields: nil,
|
33
29
|
find_by_query_param: "find_by",
|
34
30
|
|
31
|
+
# Options for what should be included/excluded from default fields.
|
32
|
+
exclude_associations: false,
|
33
|
+
|
35
34
|
# Options for handling request body parameters.
|
36
35
|
allowed_parameters: nil,
|
37
36
|
filter_pk_from_request_body: true,
|
@@ -125,113 +124,72 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
125
124
|
return input_fields
|
126
125
|
end
|
127
126
|
|
128
|
-
# Get a field
|
129
|
-
def
|
130
|
-
|
131
|
-
@_field_config_for ||= {}
|
132
|
-
return @_field_config_for[f] if @_field_config_for[f]
|
133
|
-
|
134
|
-
config = self.field_config&.dig(f) || {}
|
135
|
-
|
136
|
-
# Default sub-fields if field is an association.
|
137
|
-
if ref = self.get_model.reflections[f.to_s]
|
138
|
-
if ref.polymorphic?
|
139
|
-
columns = {}
|
140
|
-
else
|
141
|
-
model = ref.klass
|
142
|
-
columns = model.columns_hash
|
143
|
-
end
|
144
|
-
config[:sub_fields] ||= RESTFramework::Utils.sub_fields_for(ref)
|
145
|
-
config[:sub_fields] = config[:sub_fields].map(&:to_s)
|
146
|
-
|
147
|
-
# Serialize very basic metadata about sub-fields.
|
148
|
-
config[:sub_fields_metadata] = config[:sub_fields].map { |sf|
|
149
|
-
v = {}
|
127
|
+
# Get a full field configuration, including defaults and inferred values.
|
128
|
+
def field_configuration
|
129
|
+
return @field_configuration if @field_configuration
|
150
130
|
|
151
|
-
|
152
|
-
v[:kind] = "column"
|
153
|
-
end
|
154
|
-
|
155
|
-
next [sf, v]
|
156
|
-
}.to_h.compact.presence
|
157
|
-
end
|
158
|
-
|
159
|
-
return @_field_config_for[f] = config.compact
|
160
|
-
end
|
161
|
-
|
162
|
-
# Get metadata about the resource's fields.
|
163
|
-
def fields_metadata
|
164
|
-
return @_fields_metadata if @_fields_metadata
|
165
|
-
|
166
|
-
# Get metadata sources.
|
131
|
+
field_config = self.field_config&.with_indifferent_access || {}
|
167
132
|
model = self.get_model
|
168
|
-
fields = self.get_fields.map(&:to_s)
|
169
133
|
columns = model.columns_hash
|
170
134
|
column_defaults = model.column_defaults
|
171
135
|
reflections = model.reflections
|
172
136
|
attributes = model._default_attributes
|
173
137
|
readonly_attributes = model.readonly_attributes
|
174
|
-
exclude_body_fields = self.exclude_body_fields
|
138
|
+
exclude_body_fields = self.exclude_body_fields&.map(&:to_s)
|
175
139
|
rich_text_association_names = model.reflect_on_all_associations(:has_one)
|
176
140
|
.collect(&:name)
|
177
141
|
.select { |n| n.to_s.start_with?("rich_text_") }
|
178
142
|
attachment_reflections = model.attachment_reflections
|
179
143
|
|
180
|
-
return @
|
181
|
-
|
182
|
-
|
183
|
-
type: nil,
|
184
|
-
kind: nil,
|
185
|
-
label: self.label_for(f),
|
186
|
-
primary_key: nil,
|
187
|
-
required: nil,
|
188
|
-
read_only: nil,
|
189
|
-
}
|
144
|
+
return @field_configuration = self.get_fields.map { |f|
|
145
|
+
cfg = field_config[f]&.dup || {}
|
146
|
+
cfg[:label] ||= self.label_for(f)
|
190
147
|
|
191
|
-
#
|
148
|
+
# Annotate primary key.
|
192
149
|
if model.primary_key == f
|
193
|
-
|
150
|
+
cfg[:primary_key] = true
|
151
|
+
|
152
|
+
unless cfg.key?(:readonly)
|
153
|
+
cfg[:readonly] = true
|
154
|
+
end
|
194
155
|
end
|
195
156
|
|
196
|
-
#
|
197
|
-
if
|
198
|
-
|
157
|
+
# Annotate readonly attributes.
|
158
|
+
if f.in?(readonly_attributes) || f.in?(exclude_body_fields)
|
159
|
+
cfg[:readonly] = true
|
199
160
|
end
|
200
161
|
|
201
|
-
#
|
162
|
+
# Annotate column data.
|
202
163
|
if column = columns[f]
|
203
|
-
|
204
|
-
|
205
|
-
|
164
|
+
cfg[:kind] = "column"
|
165
|
+
cfg[:type] ||= column.type
|
166
|
+
cfg[:required] = true unless column.null
|
206
167
|
end
|
207
168
|
|
208
|
-
#
|
209
|
-
|
210
|
-
|
211
|
-
unless column_default.nil?
|
212
|
-
metadata[:default] = column_default
|
169
|
+
# Add default values from the model's schema.
|
170
|
+
if column_default = column_defaults[f] && !cfg[:default].nil?
|
171
|
+
cfg[:default] ||= column_default
|
213
172
|
end
|
214
173
|
|
215
|
-
#
|
216
|
-
if
|
217
|
-
|
218
|
-
default =
|
219
|
-
metadata[:default] = default unless default.nil?
|
174
|
+
# Add metadata from the model's attributes hash.
|
175
|
+
if attribute = attributes[f]
|
176
|
+
if cfg[:default].nil? && default = attribute.value_before_type_cast
|
177
|
+
cfg[:default] = default
|
220
178
|
end
|
221
|
-
|
179
|
+
cfg[:kind] ||= "attribute"
|
222
180
|
|
223
181
|
# Get any type information from the attribute.
|
224
182
|
if type = attribute.type
|
225
|
-
|
183
|
+
cfg[:type] ||= type.type if type.type
|
226
184
|
|
227
185
|
# Get enum variants.
|
228
186
|
if type.is_a?(ActiveRecord::Enum::EnumType)
|
229
|
-
|
187
|
+
cfg[:enum_variants] = type.send(:mapping)
|
230
188
|
|
231
|
-
#
|
189
|
+
# TranslateEnum Integration:
|
232
190
|
translate_method = "translated_#{f.pluralize}"
|
233
191
|
if model.respond_to?(translate_method)
|
234
|
-
|
192
|
+
cfg[:enum_translations] = model.send(translate_method)
|
235
193
|
end
|
236
194
|
end
|
237
195
|
end
|
@@ -239,53 +197,65 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
239
197
|
|
240
198
|
# Get association metadata.
|
241
199
|
if ref = reflections[f]
|
242
|
-
|
200
|
+
cfg[:kind] = "association"
|
201
|
+
|
202
|
+
# Determine sub-fields for associations.
|
203
|
+
if ref.polymorphic?
|
204
|
+
ref_columns = {}
|
205
|
+
else
|
206
|
+
ref_columns = ref.klass.columns_hash
|
207
|
+
end
|
208
|
+
cfg[:sub_fields] ||= RESTFramework::Utils.sub_fields_for(ref)
|
209
|
+
cfg[:sub_fields] = cfg[:sub_fields].map(&:to_s)
|
210
|
+
|
211
|
+
# Very basic metadata about sub-fields.
|
212
|
+
cfg[:sub_fields_metadata] = cfg[:sub_fields].map { |sf|
|
213
|
+
v = {}
|
214
|
+
|
215
|
+
if ref_columns[sf]
|
216
|
+
v[:kind] = "column"
|
217
|
+
else
|
218
|
+
v[:kind] = "method"
|
219
|
+
end
|
220
|
+
|
221
|
+
next [sf, v]
|
222
|
+
}.to_h.compact.presence
|
243
223
|
|
244
224
|
# Determine if we render id/ids fields. Unfortunately, `has_one` does not provide this
|
245
225
|
# interface.
|
246
|
-
if self.permit_id_assignment && id_field = RESTFramework::Utils.
|
247
|
-
|
226
|
+
if self.permit_id_assignment && id_field = RESTFramework::Utils.id_field_for(f, ref)
|
227
|
+
cfg[:id_field] = id_field
|
248
228
|
end
|
249
229
|
|
250
230
|
# Determine if we render nested attributes options.
|
251
231
|
if self.permit_nested_attributes_assignment && (
|
252
232
|
nested_opts = model.nested_attributes_options[f.to_sym].presence
|
253
233
|
)
|
254
|
-
|
234
|
+
cfg[:nested_attributes_options] = {field: "#{f}_attributes", **nested_opts}
|
255
235
|
end
|
256
236
|
|
257
237
|
begin
|
258
|
-
|
238
|
+
cfg[:association_pk] = ref.active_record_primary_key
|
259
239
|
rescue ActiveRecord::UnknownPrimaryKey
|
260
240
|
end
|
261
|
-
|
262
|
-
|
263
|
-
collection: ref.collection?,
|
264
|
-
class_name: ref.class_name,
|
265
|
-
foreign_key: ref.foreign_key,
|
266
|
-
primary_key: pk,
|
267
|
-
polymorphic: ref.polymorphic?,
|
268
|
-
table_name: ref.polymorphic? ? nil : ref.table_name,
|
269
|
-
options: ref.options.as_json.presence,
|
270
|
-
}.compact
|
241
|
+
|
242
|
+
cfg[:reflection] = ref
|
271
243
|
end
|
272
244
|
|
273
245
|
# Determine if this is an ActionText "rich text".
|
274
246
|
if :"rich_text_#{f}".in?(rich_text_association_names)
|
275
|
-
|
247
|
+
cfg[:kind] = "rich_text"
|
276
248
|
end
|
277
249
|
|
278
250
|
# Determine if this is an ActiveStorage attachment.
|
279
251
|
if ref = attachment_reflections[f]
|
280
|
-
|
281
|
-
|
282
|
-
macro: ref.macro,
|
283
|
-
}
|
252
|
+
cfg[:kind] = "attachment"
|
253
|
+
cfg[:attachment_type] = ref.macro
|
284
254
|
end
|
285
255
|
|
286
256
|
# Determine if this is just a method.
|
287
|
-
if !
|
288
|
-
|
257
|
+
if !cfg[:kind] && model.method_defined?(f)
|
258
|
+
cfg[:kind] = "method"
|
289
259
|
end
|
290
260
|
|
291
261
|
# Collect validator options into a hash on their type, while also updating `required` based
|
@@ -299,7 +269,7 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
299
269
|
next if IGNORE_VALIDATORS_WITH_KEYS.any? { |k| options.key?(k) }
|
300
270
|
|
301
271
|
# Update `required` if we find a presence validator.
|
302
|
-
|
272
|
+
cfg[:required] = true if kind == :presence
|
303
273
|
|
304
274
|
# Resolve procs (and lambdas), and symbols for certain arguments.
|
305
275
|
if options[:in].is_a?(Proc)
|
@@ -308,27 +278,65 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
308
278
|
options = options.merge(in: model.send(options[:in]))
|
309
279
|
end
|
310
280
|
|
311
|
-
|
312
|
-
|
313
|
-
|
281
|
+
cfg[:validators] ||= {}
|
282
|
+
cfg[:validators][kind] ||= []
|
283
|
+
cfg[:validators][kind] << options
|
314
284
|
end
|
315
285
|
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
next [f, metadata.compact]
|
320
|
-
}.to_h
|
286
|
+
next [f, cfg]
|
287
|
+
}.to_h.with_indifferent_access
|
321
288
|
end
|
322
289
|
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
290
|
+
def openapi_schema
|
291
|
+
return @openapi_schema if @openapi_schema
|
292
|
+
|
293
|
+
field_configuration = self.field_configuration
|
294
|
+
@openapi_schema = {
|
295
|
+
required: field_configuration.select { |_, cfg| cfg[:required] }.keys,
|
296
|
+
type: "object",
|
297
|
+
properties: field_configuration.map { |f, cfg|
|
298
|
+
v = {title: cfg[:label]}
|
299
|
+
|
300
|
+
if cfg[:kind] == "association"
|
301
|
+
v[:type] = cfg[:reflection].collection? ? "array" : "object"
|
302
|
+
elsif cfg[:kind] == "rich_text"
|
303
|
+
v[:type] = "string"
|
304
|
+
v[:"x-rrf-rich_text"] = true
|
305
|
+
elsif cfg[:kind] == "attachment"
|
306
|
+
v[:type] = "string"
|
307
|
+
v[:"x-rrf-attachment"] = cfg[:attachment_type]
|
308
|
+
else
|
309
|
+
v[:type] = cfg[:type]
|
310
|
+
end
|
311
|
+
|
312
|
+
v[:readOnly] = true if cfg[:readonly]
|
313
|
+
v[:default] = cfg[:default] if cfg.key?(:default)
|
314
|
+
|
315
|
+
if enum_variants = cfg[:enum_variants]
|
316
|
+
v[:enum] = enum_variants.keys
|
317
|
+
v[:"x-rrf-enum_variants"] = enum_variants
|
318
|
+
end
|
319
|
+
|
320
|
+
if validators = cfg[:validators]
|
321
|
+
v[:"x-rrf-validators"] = validators
|
322
|
+
end
|
323
|
+
|
324
|
+
v[:"x-rrf-kind"] = cfg[:kind] if cfg[:kind]
|
325
|
+
|
326
|
+
if cfg[:reflection]
|
327
|
+
v[:"x-rrf-reflection"] = cfg[:reflection]
|
328
|
+
v[:"x-rrf-association_pk"] = cfg[:association_pk]
|
329
|
+
v[:"x-rrf-sub_fields"] = cfg[:sub_fields]
|
330
|
+
v[:"x-rrf-sub_fields_metadata"] = cfg[:sub_fields_metadata]
|
331
|
+
v[:"x-rrf-id_field"] = cfg[:id_field]
|
332
|
+
v[:"x-rrf-nested_attributes_options"] = cfg[:nested_attributes_options]
|
333
|
+
end
|
334
|
+
|
335
|
+
next [f, v]
|
336
|
+
}.to_h,
|
337
|
+
}
|
338
|
+
|
339
|
+
return @openapi_schema
|
332
340
|
end
|
333
341
|
|
334
342
|
def setup_delegation
|
@@ -341,9 +349,9 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
341
349
|
model = self.class.get_model
|
342
350
|
|
343
351
|
if model.method(action).parameters.last&.first == :keyrest
|
344
|
-
return
|
352
|
+
return render_api(model.send(action, **params))
|
345
353
|
else
|
346
|
-
return
|
354
|
+
return render_api(model.send(action))
|
347
355
|
end
|
348
356
|
end
|
349
357
|
end
|
@@ -357,9 +365,9 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
357
365
|
record = self.get_record
|
358
366
|
|
359
367
|
if record.method(action).parameters.last&.first == :keyrest
|
360
|
-
return
|
368
|
+
return render_api(record.send(action, **params))
|
361
369
|
else
|
362
|
-
return
|
370
|
+
return render_api(record.send(action))
|
363
371
|
end
|
364
372
|
end
|
365
373
|
end
|
@@ -373,7 +381,7 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
373
381
|
# self.setup_channel
|
374
382
|
|
375
383
|
if RESTFramework.config.freeze_config
|
376
|
-
|
384
|
+
self::RRF_BASE_MODEL_CONFIG.keys.each { |k|
|
377
385
|
v = self.send(k)
|
378
386
|
v.freeze if v.is_a?(Hash) || v.is_a?(Array)
|
379
387
|
}
|
@@ -395,27 +403,51 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
395
403
|
|
396
404
|
base.class_attribute(a, default: default, instance_accessor: false)
|
397
405
|
end
|
398
|
-
RRF_BASE_MODEL_INSTANCE_CONFIG.each do |a, default|
|
399
|
-
next if base.respond_to?(a)
|
400
|
-
|
401
|
-
base.class_attribute(a, default: default)
|
402
|
-
end
|
403
406
|
end
|
404
407
|
|
405
|
-
# Get a list of fields for this controller.
|
406
408
|
def get_fields
|
407
409
|
return self.class.get_fields(input_fields: self.class.fields)
|
408
410
|
end
|
409
411
|
|
410
|
-
def
|
411
|
-
|
412
|
+
def openapi_metadata
|
413
|
+
data = super
|
414
|
+
routes = self.route_groups.values[0]
|
415
|
+
schema_name = routes[0][:controller].camelize.gsub("::", ".")
|
416
|
+
|
417
|
+
# Insert schema into metadata.
|
418
|
+
data[:components] ||= {}
|
419
|
+
data[:components][:schemas] ||= {}
|
420
|
+
data[:components][:schemas][schema_name] = self.class.openapi_schema
|
421
|
+
|
422
|
+
# Reference schema for specific actions with a `requestBody`.
|
423
|
+
data[:paths].each do |_path, actions|
|
424
|
+
actions.each do |_method, action|
|
425
|
+
next unless action.is_a?(Hash)
|
426
|
+
|
427
|
+
injectables = [action.dig(:requestBody, :content), *action[:responses].values.map { |r|
|
428
|
+
r[:content]
|
429
|
+
}].compact
|
430
|
+
injectables.each do |i|
|
431
|
+
i.each do |_, v|
|
432
|
+
v[:schema] = {"$ref" => "#/components/schemas/#{schema_name}"}
|
433
|
+
end
|
434
|
+
end
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
return data.merge(
|
439
|
+
{
|
440
|
+
"x-rrf-primary_key" => self.class.get_model.primary_key,
|
441
|
+
"x-rrf-callbacks" => self._process_action_callbacks.as_json,
|
442
|
+
},
|
443
|
+
)
|
412
444
|
end
|
413
445
|
|
414
|
-
# Get a
|
446
|
+
# Get a hash of strong parameters for the current action.
|
415
447
|
def get_allowed_parameters
|
416
448
|
return @_get_allowed_parameters if defined?(@_get_allowed_parameters)
|
417
449
|
|
418
|
-
@_get_allowed_parameters = self.allowed_parameters
|
450
|
+
@_get_allowed_parameters = self.class.allowed_parameters
|
419
451
|
return @_get_allowed_parameters if @_get_allowed_parameters
|
420
452
|
|
421
453
|
# Assemble strong parameters.
|
@@ -424,6 +456,7 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
424
456
|
reflections = self.class.get_model.reflections
|
425
457
|
@_get_allowed_parameters = self.get_fields.map { |f|
|
426
458
|
f = f.to_s
|
459
|
+
config = self.class.field_configuration[f]
|
427
460
|
|
428
461
|
# ActionText Integration:
|
429
462
|
if self.class.enable_action_text && reflections.key?("rich_test_#{f}")
|
@@ -442,28 +475,30 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
442
475
|
next nil
|
443
476
|
end
|
444
477
|
|
445
|
-
|
446
|
-
|
478
|
+
if config[:reflection]
|
479
|
+
# Add `_id`/`_ids` variations for associations.
|
480
|
+
if id_field = config[:id_field]
|
481
|
+
if id_field.ends_with?("_ids")
|
482
|
+
hash_variations[id_field] = []
|
483
|
+
else
|
484
|
+
variations << id_field
|
485
|
+
end
|
486
|
+
end
|
447
487
|
|
448
|
-
|
449
|
-
|
450
|
-
if
|
451
|
-
hash_variations[
|
452
|
-
|
453
|
-
|
488
|
+
# Add `_attributes` variations for associations.
|
489
|
+
# TODO: Consider adjusting this based on `nested_attributes_options`.
|
490
|
+
if self.class.permit_nested_attributes_assignment
|
491
|
+
hash_variations["#{f}_attributes"] = (
|
492
|
+
config[:sub_fields] + ["_destroy"]
|
493
|
+
)
|
454
494
|
end
|
455
|
-
end
|
456
495
|
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
self.class.field_config_for(f)[:sub_fields] + ["_destroy"]
|
461
|
-
)
|
496
|
+
# Associations are not allowed to be submitted in their bare form (if they are submitted
|
497
|
+
# that way, they will be translated to either id/ids or nested attributes assignment).
|
498
|
+
next nil
|
462
499
|
end
|
463
500
|
|
464
|
-
|
465
|
-
# way, they will be translated to either ID assignment or nested attributes assignment).
|
466
|
-
next nil
|
501
|
+
next f
|
467
502
|
}.compact
|
468
503
|
@_get_allowed_parameters += variations
|
469
504
|
@_get_allowed_parameters << hash_variations
|
@@ -471,22 +506,11 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
471
506
|
return @_get_allowed_parameters
|
472
507
|
end
|
473
508
|
|
474
|
-
def
|
509
|
+
def get_serializer_class
|
475
510
|
return super || RESTFramework::NativeSerializer
|
476
511
|
end
|
477
512
|
|
478
|
-
|
479
|
-
# TODO: Compatibility; remove in 1.0.
|
480
|
-
if filtered_data = self.try(:get_filtered_data, data)
|
481
|
-
return filtered_data
|
482
|
-
end
|
483
|
-
|
484
|
-
return self.filter_backends&.reduce(data) { |d, filter|
|
485
|
-
filter.new(controller: self).filter_data(d)
|
486
|
-
} || data
|
487
|
-
end
|
488
|
-
|
489
|
-
# Use strong parameters to filter the request body using the configured allowed parameters.
|
513
|
+
# Use strong parameters to filter the request body.
|
490
514
|
def get_body_params(bulk_mode: nil)
|
491
515
|
data = self.request.request_parameters
|
492
516
|
pk = self.class.get_model&.primary_key
|
@@ -502,7 +526,7 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
502
526
|
# Assume nested attributes assignment.
|
503
527
|
attributes_key = "#{name}_attributes"
|
504
528
|
data[attributes_key] = data.delete(name) unless data[attributes_key]
|
505
|
-
elsif id_field = RESTFramework::Utils.
|
529
|
+
elsif id_field = RESTFramework::Utils.id_field_for(name, ref)
|
506
530
|
# Assume id/ids assignment.
|
507
531
|
data[id_field] = data.delete(name) unless data[id_field]
|
508
532
|
end
|
@@ -571,12 +595,12 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
571
595
|
end
|
572
596
|
|
573
597
|
# Filter primary key, if configured.
|
574
|
-
if self.filter_pk_from_request_body && bulk_mode != :update
|
598
|
+
if self.class.filter_pk_from_request_body && bulk_mode != :update
|
575
599
|
body_params.delete(pk)
|
576
600
|
end
|
577
601
|
|
578
602
|
# Filter fields in `exclude_body_fields`.
|
579
|
-
(self.exclude_body_fields || []).each { |f| body_params.delete(f) }
|
603
|
+
(self.class.exclude_body_fields || []).each { |f| body_params.delete(f) }
|
580
604
|
|
581
605
|
return body_params
|
582
606
|
end
|
@@ -595,13 +619,16 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
595
619
|
return nil
|
596
620
|
end
|
597
621
|
|
598
|
-
#
|
622
|
+
# Filter the recordset and return records this request has access to.
|
599
623
|
def get_records
|
600
|
-
|
624
|
+
data = self.get_recordset
|
625
|
+
|
626
|
+
return @records ||= self.class.filter_backends&.reduce(data) { |d, filter|
|
627
|
+
filter.new(controller: self).filter_data(d)
|
628
|
+
} || data
|
601
629
|
end
|
602
630
|
|
603
|
-
# Get a single record by primary key or another column, if allowed.
|
604
|
-
# and exposed to the view as the `@record` instance variable.
|
631
|
+
# Get a single record by primary key or another column, if allowed.
|
605
632
|
def get_record
|
606
633
|
return @record if @record
|
607
634
|
|
@@ -609,9 +636,9 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
609
636
|
is_pk = true
|
610
637
|
|
611
638
|
# Find by another column if it's permitted.
|
612
|
-
if find_by_param = self.find_by_query_param.presence
|
639
|
+
if find_by_param = self.class.find_by_query_param.presence
|
613
640
|
if find_by = params[find_by_param].presence
|
614
|
-
find_by_fields = self.find_by_fields&.map(&:to_s)
|
641
|
+
find_by_fields = self.class.find_by_fields&.map(&:to_s)
|
615
642
|
|
616
643
|
if !find_by_fields || find_by.in?(find_by_fields)
|
617
644
|
is_pk = false unless find_by_key == find_by
|
@@ -621,7 +648,7 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
621
648
|
end
|
622
649
|
|
623
650
|
# Get the recordset, filtering if configured.
|
624
|
-
collection = if self.filter_recordset_before_find
|
651
|
+
collection = if self.class.filter_recordset_before_find
|
625
652
|
self.get_records
|
626
653
|
else
|
627
654
|
self.get_recordset
|
@@ -637,7 +664,7 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
637
664
|
|
638
665
|
# Determine what collection to call `create` on.
|
639
666
|
def get_create_from
|
640
|
-
if self.create_from_recordset
|
667
|
+
if self.class.create_from_recordset
|
641
668
|
# Create with any properties inherited from the recordset. We exclude any `select` clauses
|
642
669
|
# in case model callbacks need to call `count` on this collection, which typically raises a
|
643
670
|
# SQL `SyntaxError`.
|
@@ -654,19 +681,17 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
654
681
|
# This is kinda slow, so perhaps we should eventually integrate `errors` serialization into
|
655
682
|
# the serializer directly. This would fail for active model serializers, but maybe we don't
|
656
683
|
# care?
|
657
|
-
s = RESTFramework::Utils.wrap_ams(self.
|
658
|
-
|
684
|
+
s = RESTFramework::Utils.wrap_ams(self.get_serializer_class)
|
685
|
+
return records.map do |record|
|
659
686
|
s.new(record, controller: self).serialize.merge!({errors: record.errors.presence}.compact)
|
660
687
|
end
|
661
|
-
|
662
|
-
return serialized_records
|
663
688
|
end
|
664
689
|
end
|
665
690
|
|
666
691
|
# Mixin for listing records.
|
667
692
|
module RESTFramework::Mixins::ListModelMixin
|
668
693
|
def index
|
669
|
-
return
|
694
|
+
return render_api(self.get_index_records)
|
670
695
|
end
|
671
696
|
|
672
697
|
# Get records with both filtering and pagination applied.
|
@@ -674,13 +699,13 @@ module RESTFramework::Mixins::ListModelMixin
|
|
674
699
|
records = self.get_records
|
675
700
|
|
676
701
|
# Handle pagination, if enabled.
|
677
|
-
if self.paginator_class
|
678
|
-
#
|
679
|
-
# set to "0"
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
paginator =
|
702
|
+
if paginator_class = self.class.paginator_class
|
703
|
+
# Paginate if there is a `max_page_size`, or if there is no `page_size_query_param`, or if the
|
704
|
+
# page size is not set to "0".
|
705
|
+
max_page_size = self.class.max_page_size
|
706
|
+
page_size_query_param = self.class.page_size_query_param
|
707
|
+
if max_page_size || !page_size_query_param || params[page_size_query_param] != "0"
|
708
|
+
paginator = paginator_class.new(data: records, controller: self)
|
684
709
|
page = paginator.get_page
|
685
710
|
serialized_page = self.serialize(page)
|
686
711
|
return paginator.get_paginated_response(serialized_page)
|
@@ -694,14 +719,14 @@ end
|
|
694
719
|
# Mixin for showing records.
|
695
720
|
module RESTFramework::Mixins::ShowModelMixin
|
696
721
|
def show
|
697
|
-
return
|
722
|
+
return render_api(self.get_record)
|
698
723
|
end
|
699
724
|
end
|
700
725
|
|
701
726
|
# Mixin for creating records.
|
702
727
|
module RESTFramework::Mixins::CreateModelMixin
|
703
728
|
def create
|
704
|
-
return
|
729
|
+
return render_api(self.create!, status: :created)
|
705
730
|
end
|
706
731
|
|
707
732
|
# Perform the `create!` call and return the created record.
|
@@ -713,7 +738,7 @@ end
|
|
713
738
|
# Mixin for updating records.
|
714
739
|
module RESTFramework::Mixins::UpdateModelMixin
|
715
740
|
def update
|
716
|
-
return
|
741
|
+
return render_api(self.update!)
|
717
742
|
end
|
718
743
|
|
719
744
|
# Perform the `update!` call and return the updated record.
|
@@ -728,7 +753,7 @@ end
|
|
728
753
|
module RESTFramework::Mixins::DestroyModelMixin
|
729
754
|
def destroy
|
730
755
|
self.destroy!
|
731
|
-
return
|
756
|
+
return render_api("")
|
732
757
|
end
|
733
758
|
|
734
759
|
# Perform the `destroy!` call and return the destroyed (and frozen) record.
|