rest_framework 0.9.16 → 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 +93 -97
- data/lib/rest_framework/mixins/bulk_model_controller_mixin.rb +7 -7
- data/lib/rest_framework/mixins/model_controller_mixin.rb +256 -222
- 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 +24 -14
- data/lib/rest_framework/utils.rb +51 -40
- 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,
|
@@ -103,13 +102,20 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
103
102
|
# If fields is a hash, then parse it.
|
104
103
|
if input_fields.is_a?(Hash)
|
105
104
|
return RESTFramework::Utils.parse_fields_hash(
|
106
|
-
input_fields,
|
105
|
+
input_fields,
|
106
|
+
self.get_model,
|
107
|
+
exclude_associations: self.exclude_associations,
|
108
|
+
action_text: self.enable_action_text,
|
109
|
+
active_storage: self.enable_active_storage,
|
107
110
|
)
|
108
111
|
elsif !input_fields
|
109
112
|
# Otherwise, if fields is nil, then fallback to columns.
|
110
113
|
model = self.get_model
|
111
114
|
return model ? RESTFramework::Utils.fields_for(
|
112
|
-
model,
|
115
|
+
model,
|
116
|
+
exclude_associations: self.exclude_associations,
|
117
|
+
action_text: self.enable_action_text,
|
118
|
+
active_storage: self.enable_active_storage,
|
113
119
|
) : []
|
114
120
|
elsif input_fields
|
115
121
|
input_fields = input_fields.map(&:to_s)
|
@@ -118,113 +124,72 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
118
124
|
return input_fields
|
119
125
|
end
|
120
126
|
|
121
|
-
# Get a field
|
122
|
-
def
|
123
|
-
|
124
|
-
@_field_config_for ||= {}
|
125
|
-
return @_field_config_for[f] if @_field_config_for[f]
|
126
|
-
|
127
|
-
config = self.field_config&.dig(f) || {}
|
128
|
-
|
129
|
-
# Default sub-fields if field is an association.
|
130
|
-
if ref = self.get_model.reflections[f.to_s]
|
131
|
-
if ref.polymorphic?
|
132
|
-
columns = {}
|
133
|
-
else
|
134
|
-
model = ref.klass
|
135
|
-
columns = model.columns_hash
|
136
|
-
end
|
137
|
-
config[:sub_fields] ||= RESTFramework::Utils.sub_fields_for(ref)
|
138
|
-
config[:sub_fields] = config[:sub_fields].map(&:to_s)
|
139
|
-
|
140
|
-
# Serialize very basic metadata about sub-fields.
|
141
|
-
config[:sub_fields_metadata] = config[:sub_fields].map { |sf|
|
142
|
-
v = {}
|
143
|
-
|
144
|
-
if columns[sf]
|
145
|
-
v[:kind] = "column"
|
146
|
-
end
|
147
|
-
|
148
|
-
next [sf, v]
|
149
|
-
}.to_h.compact.presence
|
150
|
-
end
|
151
|
-
|
152
|
-
return @_field_config_for[f] = config.compact
|
153
|
-
end
|
154
|
-
|
155
|
-
# Get metadata about the resource's fields.
|
156
|
-
def fields_metadata
|
157
|
-
return @_fields_metadata if @_fields_metadata
|
127
|
+
# Get a full field configuration, including defaults and inferred values.
|
128
|
+
def field_configuration
|
129
|
+
return @field_configuration if @field_configuration
|
158
130
|
|
159
|
-
|
131
|
+
field_config = self.field_config&.with_indifferent_access || {}
|
160
132
|
model = self.get_model
|
161
|
-
fields = self.get_fields.map(&:to_s)
|
162
133
|
columns = model.columns_hash
|
163
134
|
column_defaults = model.column_defaults
|
164
135
|
reflections = model.reflections
|
165
136
|
attributes = model._default_attributes
|
166
137
|
readonly_attributes = model.readonly_attributes
|
167
|
-
exclude_body_fields = self.exclude_body_fields
|
138
|
+
exclude_body_fields = self.exclude_body_fields&.map(&:to_s)
|
168
139
|
rich_text_association_names = model.reflect_on_all_associations(:has_one)
|
169
140
|
.collect(&:name)
|
170
141
|
.select { |n| n.to_s.start_with?("rich_text_") }
|
171
142
|
attachment_reflections = model.attachment_reflections
|
172
143
|
|
173
|
-
return @
|
174
|
-
|
175
|
-
|
176
|
-
type: nil,
|
177
|
-
kind: nil,
|
178
|
-
label: self.label_for(f),
|
179
|
-
primary_key: nil,
|
180
|
-
required: nil,
|
181
|
-
read_only: nil,
|
182
|
-
}
|
144
|
+
return @field_configuration = self.get_fields.map { |f|
|
145
|
+
cfg = field_config[f]&.dup || {}
|
146
|
+
cfg[:label] ||= self.label_for(f)
|
183
147
|
|
184
|
-
#
|
148
|
+
# Annotate primary key.
|
185
149
|
if model.primary_key == f
|
186
|
-
|
150
|
+
cfg[:primary_key] = true
|
151
|
+
|
152
|
+
unless cfg.key?(:readonly)
|
153
|
+
cfg[:readonly] = true
|
154
|
+
end
|
187
155
|
end
|
188
156
|
|
189
|
-
#
|
190
|
-
if
|
191
|
-
|
157
|
+
# Annotate readonly attributes.
|
158
|
+
if f.in?(readonly_attributes) || f.in?(exclude_body_fields)
|
159
|
+
cfg[:readonly] = true
|
192
160
|
end
|
193
161
|
|
194
|
-
#
|
162
|
+
# Annotate column data.
|
195
163
|
if column = columns[f]
|
196
|
-
|
197
|
-
|
198
|
-
|
164
|
+
cfg[:kind] = "column"
|
165
|
+
cfg[:type] ||= column.type
|
166
|
+
cfg[:required] = true unless column.null
|
199
167
|
end
|
200
168
|
|
201
|
-
#
|
202
|
-
|
203
|
-
|
204
|
-
unless column_default.nil?
|
205
|
-
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
|
206
172
|
end
|
207
173
|
|
208
|
-
#
|
209
|
-
if
|
210
|
-
|
211
|
-
default =
|
212
|
-
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
|
213
178
|
end
|
214
|
-
|
179
|
+
cfg[:kind] ||= "attribute"
|
215
180
|
|
216
181
|
# Get any type information from the attribute.
|
217
182
|
if type = attribute.type
|
218
|
-
|
183
|
+
cfg[:type] ||= type.type if type.type
|
219
184
|
|
220
185
|
# Get enum variants.
|
221
186
|
if type.is_a?(ActiveRecord::Enum::EnumType)
|
222
|
-
|
187
|
+
cfg[:enum_variants] = type.send(:mapping)
|
223
188
|
|
224
|
-
#
|
189
|
+
# TranslateEnum Integration:
|
225
190
|
translate_method = "translated_#{f.pluralize}"
|
226
191
|
if model.respond_to?(translate_method)
|
227
|
-
|
192
|
+
cfg[:enum_translations] = model.send(translate_method)
|
228
193
|
end
|
229
194
|
end
|
230
195
|
end
|
@@ -232,53 +197,65 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
232
197
|
|
233
198
|
# Get association metadata.
|
234
199
|
if ref = reflections[f]
|
235
|
-
|
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
|
236
223
|
|
237
224
|
# Determine if we render id/ids fields. Unfortunately, `has_one` does not provide this
|
238
225
|
# interface.
|
239
|
-
if self.permit_id_assignment && id_field = RESTFramework::Utils.
|
240
|
-
|
226
|
+
if self.permit_id_assignment && id_field = RESTFramework::Utils.id_field_for(f, ref)
|
227
|
+
cfg[:id_field] = id_field
|
241
228
|
end
|
242
229
|
|
243
230
|
# Determine if we render nested attributes options.
|
244
231
|
if self.permit_nested_attributes_assignment && (
|
245
232
|
nested_opts = model.nested_attributes_options[f.to_sym].presence
|
246
233
|
)
|
247
|
-
|
234
|
+
cfg[:nested_attributes_options] = {field: "#{f}_attributes", **nested_opts}
|
248
235
|
end
|
249
236
|
|
250
237
|
begin
|
251
|
-
|
238
|
+
cfg[:association_pk] = ref.active_record_primary_key
|
252
239
|
rescue ActiveRecord::UnknownPrimaryKey
|
253
240
|
end
|
254
|
-
|
255
|
-
|
256
|
-
collection: ref.collection?,
|
257
|
-
class_name: ref.class_name,
|
258
|
-
foreign_key: ref.foreign_key,
|
259
|
-
primary_key: pk,
|
260
|
-
polymorphic: ref.polymorphic?,
|
261
|
-
table_name: ref.polymorphic? ? nil : ref.table_name,
|
262
|
-
options: ref.options.as_json.presence,
|
263
|
-
}.compact
|
241
|
+
|
242
|
+
cfg[:reflection] = ref
|
264
243
|
end
|
265
244
|
|
266
245
|
# Determine if this is an ActionText "rich text".
|
267
246
|
if :"rich_text_#{f}".in?(rich_text_association_names)
|
268
|
-
|
247
|
+
cfg[:kind] = "rich_text"
|
269
248
|
end
|
270
249
|
|
271
250
|
# Determine if this is an ActiveStorage attachment.
|
272
251
|
if ref = attachment_reflections[f]
|
273
|
-
|
274
|
-
|
275
|
-
macro: ref.macro,
|
276
|
-
}
|
252
|
+
cfg[:kind] = "attachment"
|
253
|
+
cfg[:attachment_type] = ref.macro
|
277
254
|
end
|
278
255
|
|
279
256
|
# Determine if this is just a method.
|
280
|
-
if !
|
281
|
-
|
257
|
+
if !cfg[:kind] && model.method_defined?(f)
|
258
|
+
cfg[:kind] = "method"
|
282
259
|
end
|
283
260
|
|
284
261
|
# Collect validator options into a hash on their type, while also updating `required` based
|
@@ -292,7 +269,7 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
292
269
|
next if IGNORE_VALIDATORS_WITH_KEYS.any? { |k| options.key?(k) }
|
293
270
|
|
294
271
|
# Update `required` if we find a presence validator.
|
295
|
-
|
272
|
+
cfg[:required] = true if kind == :presence
|
296
273
|
|
297
274
|
# Resolve procs (and lambdas), and symbols for certain arguments.
|
298
275
|
if options[:in].is_a?(Proc)
|
@@ -301,27 +278,65 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
301
278
|
options = options.merge(in: model.send(options[:in]))
|
302
279
|
end
|
303
280
|
|
304
|
-
|
305
|
-
|
306
|
-
|
281
|
+
cfg[:validators] ||= {}
|
282
|
+
cfg[:validators][kind] ||= []
|
283
|
+
cfg[:validators][kind] << options
|
307
284
|
end
|
308
285
|
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
next [f, metadata.compact]
|
313
|
-
}.to_h
|
286
|
+
next [f, cfg]
|
287
|
+
}.to_h.with_indifferent_access
|
314
288
|
end
|
315
289
|
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
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
|
325
340
|
end
|
326
341
|
|
327
342
|
def setup_delegation
|
@@ -334,9 +349,9 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
334
349
|
model = self.class.get_model
|
335
350
|
|
336
351
|
if model.method(action).parameters.last&.first == :keyrest
|
337
|
-
return
|
352
|
+
return render_api(model.send(action, **params))
|
338
353
|
else
|
339
|
-
return
|
354
|
+
return render_api(model.send(action))
|
340
355
|
end
|
341
356
|
end
|
342
357
|
end
|
@@ -350,9 +365,9 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
350
365
|
record = self.get_record
|
351
366
|
|
352
367
|
if record.method(action).parameters.last&.first == :keyrest
|
353
|
-
return
|
368
|
+
return render_api(record.send(action, **params))
|
354
369
|
else
|
355
|
-
return
|
370
|
+
return render_api(record.send(action))
|
356
371
|
end
|
357
372
|
end
|
358
373
|
end
|
@@ -366,7 +381,7 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
366
381
|
# self.setup_channel
|
367
382
|
|
368
383
|
if RESTFramework.config.freeze_config
|
369
|
-
|
384
|
+
self::RRF_BASE_MODEL_CONFIG.keys.each { |k|
|
370
385
|
v = self.send(k)
|
371
386
|
v.freeze if v.is_a?(Hash) || v.is_a?(Array)
|
372
387
|
}
|
@@ -388,27 +403,51 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
388
403
|
|
389
404
|
base.class_attribute(a, default: default, instance_accessor: false)
|
390
405
|
end
|
391
|
-
RRF_BASE_MODEL_INSTANCE_CONFIG.each do |a, default|
|
392
|
-
next if base.respond_to?(a)
|
393
|
-
|
394
|
-
base.class_attribute(a, default: default)
|
395
|
-
end
|
396
406
|
end
|
397
407
|
|
398
|
-
# Get a list of fields for this controller.
|
399
408
|
def get_fields
|
400
409
|
return self.class.get_fields(input_fields: self.class.fields)
|
401
410
|
end
|
402
411
|
|
403
|
-
def
|
404
|
-
|
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
|
+
)
|
405
444
|
end
|
406
445
|
|
407
|
-
# Get a
|
446
|
+
# Get a hash of strong parameters for the current action.
|
408
447
|
def get_allowed_parameters
|
409
448
|
return @_get_allowed_parameters if defined?(@_get_allowed_parameters)
|
410
449
|
|
411
|
-
@_get_allowed_parameters = self.allowed_parameters
|
450
|
+
@_get_allowed_parameters = self.class.allowed_parameters
|
412
451
|
return @_get_allowed_parameters if @_get_allowed_parameters
|
413
452
|
|
414
453
|
# Assemble strong parameters.
|
@@ -417,46 +456,49 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
417
456
|
reflections = self.class.get_model.reflections
|
418
457
|
@_get_allowed_parameters = self.get_fields.map { |f|
|
419
458
|
f = f.to_s
|
459
|
+
config = self.class.field_configuration[f]
|
420
460
|
|
421
|
-
#
|
422
|
-
if reflections.key?("#{f}
|
423
|
-
hash_variations[f] = ACTIVESTORAGE_KEYS
|
461
|
+
# ActionText Integration:
|
462
|
+
if self.class.enable_action_text && reflections.key?("rich_test_#{f}")
|
424
463
|
next f
|
425
464
|
end
|
426
465
|
|
427
|
-
# ActiveStorage Integration: `
|
428
|
-
if reflections.key?("#{f}
|
466
|
+
# ActiveStorage Integration: `has_one_attached`
|
467
|
+
if self.class.enable_active_storage && reflections.key?("#{f}_attachment")
|
429
468
|
hash_variations[f] = ACTIVESTORAGE_KEYS
|
430
|
-
next
|
469
|
+
next f
|
431
470
|
end
|
432
471
|
|
433
|
-
#
|
434
|
-
if reflections.key?("
|
435
|
-
|
472
|
+
# ActiveStorage Integration: `has_many_attached`
|
473
|
+
if self.class.enable_active_storage && reflections.key?("#{f}_attachments")
|
474
|
+
hash_variations[f] = ACTIVESTORAGE_KEYS
|
475
|
+
next nil
|
436
476
|
end
|
437
477
|
|
438
|
-
|
439
|
-
|
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
|
440
487
|
|
441
|
-
|
442
|
-
|
443
|
-
if
|
444
|
-
hash_variations[
|
445
|
-
|
446
|
-
|
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
|
+
)
|
447
494
|
end
|
448
|
-
end
|
449
495
|
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
self.class.field_config_for(f)[:sub_fields] + ["_destroy"]
|
454
|
-
)
|
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
|
455
499
|
end
|
456
500
|
|
457
|
-
|
458
|
-
# way, they will be translated to either ID assignment or nested attributes assignment).
|
459
|
-
next nil
|
501
|
+
next f
|
460
502
|
}.compact
|
461
503
|
@_get_allowed_parameters += variations
|
462
504
|
@_get_allowed_parameters << hash_variations
|
@@ -464,22 +506,11 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
464
506
|
return @_get_allowed_parameters
|
465
507
|
end
|
466
508
|
|
467
|
-
def
|
509
|
+
def get_serializer_class
|
468
510
|
return super || RESTFramework::NativeSerializer
|
469
511
|
end
|
470
512
|
|
471
|
-
|
472
|
-
# TODO: Compatibility; remove in 1.0.
|
473
|
-
if filtered_data = self.try(:get_filtered_data, data)
|
474
|
-
return filtered_data
|
475
|
-
end
|
476
|
-
|
477
|
-
return self.filter_backends&.reduce(data) { |d, filter|
|
478
|
-
filter.new(controller: self).filter_data(d)
|
479
|
-
} || data
|
480
|
-
end
|
481
|
-
|
482
|
-
# Use strong parameters to filter the request body using the configured allowed parameters.
|
513
|
+
# Use strong parameters to filter the request body.
|
483
514
|
def get_body_params(bulk_mode: nil)
|
484
515
|
data = self.request.request_parameters
|
485
516
|
pk = self.class.get_model&.primary_key
|
@@ -495,7 +526,7 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
495
526
|
# Assume nested attributes assignment.
|
496
527
|
attributes_key = "#{name}_attributes"
|
497
528
|
data[attributes_key] = data.delete(name) unless data[attributes_key]
|
498
|
-
elsif id_field = RESTFramework::Utils.
|
529
|
+
elsif id_field = RESTFramework::Utils.id_field_for(name, ref)
|
499
530
|
# Assume id/ids assignment.
|
500
531
|
data[id_field] = data.delete(name) unless data[id_field]
|
501
532
|
end
|
@@ -513,31 +544,33 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
513
544
|
#
|
514
545
|
# rubocop:enable Layout/LineLength
|
515
546
|
has_many_attached_scalar_data = {}
|
516
|
-
self.class.
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
v = BASE64_TRANSLATE.call(k, v)
|
521
|
-
|
522
|
-
# Remember scalars because Rails strong params will remove it.
|
547
|
+
if self.class.enable_active_storage
|
548
|
+
self.class.get_model.attachment_reflections.keys.each do |k|
|
549
|
+
if data[k].is_a?(Array)
|
550
|
+
data[k] = data[k].map { |v|
|
523
551
|
if v.is_a?(String)
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
552
|
+
v = BASE64_TRANSLATE.call(k, v)
|
553
|
+
|
554
|
+
# Remember scalars because Rails strong params will remove it.
|
555
|
+
if v.is_a?(String)
|
556
|
+
has_many_attached_scalar_data[k] ||= []
|
557
|
+
has_many_attached_scalar_data[k] << v
|
558
|
+
end
|
559
|
+
elsif v.is_a?(Hash)
|
560
|
+
if v[:io].is_a?(String)
|
561
|
+
v[:io] = StringIO.new(Base64.decode64(v[:io]))
|
562
|
+
end
|
530
563
|
end
|
531
|
-
end
|
532
564
|
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
565
|
+
next v
|
566
|
+
}
|
567
|
+
elsif data[k].is_a?(Hash)
|
568
|
+
if data[k][:io].is_a?(String)
|
569
|
+
data[k][:io] = StringIO.new(Base64.decode64(data[k][:io]))
|
570
|
+
end
|
571
|
+
elsif data[k].is_a?(String)
|
572
|
+
data[k] = BASE64_TRANSLATE.call(k, data[k])
|
538
573
|
end
|
539
|
-
elsif data[k].is_a?(String)
|
540
|
-
data[k] = BASE64_TRANSLATE.call(k, data[k])
|
541
574
|
end
|
542
575
|
end
|
543
576
|
|
@@ -562,12 +595,12 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
562
595
|
end
|
563
596
|
|
564
597
|
# Filter primary key, if configured.
|
565
|
-
if self.filter_pk_from_request_body && bulk_mode != :update
|
598
|
+
if self.class.filter_pk_from_request_body && bulk_mode != :update
|
566
599
|
body_params.delete(pk)
|
567
600
|
end
|
568
601
|
|
569
602
|
# Filter fields in `exclude_body_fields`.
|
570
|
-
(self.exclude_body_fields || []).each { |f| body_params.delete(f) }
|
603
|
+
(self.class.exclude_body_fields || []).each { |f| body_params.delete(f) }
|
571
604
|
|
572
605
|
return body_params
|
573
606
|
end
|
@@ -586,13 +619,16 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
586
619
|
return nil
|
587
620
|
end
|
588
621
|
|
589
|
-
#
|
622
|
+
# Filter the recordset and return records this request has access to.
|
590
623
|
def get_records
|
591
|
-
|
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
|
592
629
|
end
|
593
630
|
|
594
|
-
# Get a single record by primary key or another column, if allowed.
|
595
|
-
# and exposed to the view as the `@record` instance variable.
|
631
|
+
# Get a single record by primary key or another column, if allowed.
|
596
632
|
def get_record
|
597
633
|
return @record if @record
|
598
634
|
|
@@ -600,9 +636,9 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
600
636
|
is_pk = true
|
601
637
|
|
602
638
|
# Find by another column if it's permitted.
|
603
|
-
if find_by_param = self.find_by_query_param.presence
|
639
|
+
if find_by_param = self.class.find_by_query_param.presence
|
604
640
|
if find_by = params[find_by_param].presence
|
605
|
-
find_by_fields = self.find_by_fields&.map(&:to_s)
|
641
|
+
find_by_fields = self.class.find_by_fields&.map(&:to_s)
|
606
642
|
|
607
643
|
if !find_by_fields || find_by.in?(find_by_fields)
|
608
644
|
is_pk = false unless find_by_key == find_by
|
@@ -612,7 +648,7 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
612
648
|
end
|
613
649
|
|
614
650
|
# Get the recordset, filtering if configured.
|
615
|
-
collection = if self.filter_recordset_before_find
|
651
|
+
collection = if self.class.filter_recordset_before_find
|
616
652
|
self.get_records
|
617
653
|
else
|
618
654
|
self.get_recordset
|
@@ -628,7 +664,7 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
628
664
|
|
629
665
|
# Determine what collection to call `create` on.
|
630
666
|
def get_create_from
|
631
|
-
if self.create_from_recordset
|
667
|
+
if self.class.create_from_recordset
|
632
668
|
# Create with any properties inherited from the recordset. We exclude any `select` clauses
|
633
669
|
# in case model callbacks need to call `count` on this collection, which typically raises a
|
634
670
|
# SQL `SyntaxError`.
|
@@ -645,19 +681,17 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
645
681
|
# This is kinda slow, so perhaps we should eventually integrate `errors` serialization into
|
646
682
|
# the serializer directly. This would fail for active model serializers, but maybe we don't
|
647
683
|
# care?
|
648
|
-
s = RESTFramework::Utils.wrap_ams(self.
|
649
|
-
|
684
|
+
s = RESTFramework::Utils.wrap_ams(self.get_serializer_class)
|
685
|
+
return records.map do |record|
|
650
686
|
s.new(record, controller: self).serialize.merge!({errors: record.errors.presence}.compact)
|
651
687
|
end
|
652
|
-
|
653
|
-
return serialized_records
|
654
688
|
end
|
655
689
|
end
|
656
690
|
|
657
691
|
# Mixin for listing records.
|
658
692
|
module RESTFramework::Mixins::ListModelMixin
|
659
693
|
def index
|
660
|
-
return
|
694
|
+
return render_api(self.get_index_records)
|
661
695
|
end
|
662
696
|
|
663
697
|
# Get records with both filtering and pagination applied.
|
@@ -665,13 +699,13 @@ module RESTFramework::Mixins::ListModelMixin
|
|
665
699
|
records = self.get_records
|
666
700
|
|
667
701
|
# Handle pagination, if enabled.
|
668
|
-
if self.paginator_class
|
669
|
-
#
|
670
|
-
# set to "0"
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
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)
|
675
709
|
page = paginator.get_page
|
676
710
|
serialized_page = self.serialize(page)
|
677
711
|
return paginator.get_paginated_response(serialized_page)
|
@@ -685,14 +719,14 @@ end
|
|
685
719
|
# Mixin for showing records.
|
686
720
|
module RESTFramework::Mixins::ShowModelMixin
|
687
721
|
def show
|
688
|
-
return
|
722
|
+
return render_api(self.get_record)
|
689
723
|
end
|
690
724
|
end
|
691
725
|
|
692
726
|
# Mixin for creating records.
|
693
727
|
module RESTFramework::Mixins::CreateModelMixin
|
694
728
|
def create
|
695
|
-
return
|
729
|
+
return render_api(self.create!, status: :created)
|
696
730
|
end
|
697
731
|
|
698
732
|
# Perform the `create!` call and return the created record.
|
@@ -704,7 +738,7 @@ end
|
|
704
738
|
# Mixin for updating records.
|
705
739
|
module RESTFramework::Mixins::UpdateModelMixin
|
706
740
|
def update
|
707
|
-
return
|
741
|
+
return render_api(self.update!)
|
708
742
|
end
|
709
743
|
|
710
744
|
# Perform the `update!` call and return the updated record.
|
@@ -719,7 +753,7 @@ end
|
|
719
753
|
module RESTFramework::Mixins::DestroyModelMixin
|
720
754
|
def destroy
|
721
755
|
self.destroy!
|
722
|
-
return
|
756
|
+
return render_api("")
|
723
757
|
end
|
724
758
|
|
725
759
|
# Perform the `destroy!` call and return the destroyed (and frozen) record.
|