rest_framework 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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.
|