rest_framework 0.7.7 → 0.7.8
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/VERSION +1 -1
- data/app/views/layouts/rest_framework.html.erb +16 -4
- data/app/views/rest_framework/_form_routes.html.erb +10 -0
- data/app/views/rest_framework/_head.html.erb +15 -5
- data/app/views/rest_framework/_html_form.html.erb +7 -0
- data/app/views/rest_framework/_raw_form.html.erb +1 -10
- data/lib/rest_framework/controller_mixins/base.rb +0 -1
- data/lib/rest_framework/controller_mixins/models.rb +69 -45
- data/lib/rest_framework/routers.rb +1 -1
- data/lib/rest_framework/serializers.rb +84 -36
- data/lib/rest_framework/utils.rb +10 -6
- data/lib/rest_framework.rb +16 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e5c7efedb6af2b7a589a2b3f7cedd600f9851bb4ba6be334465285f052e961ed
|
4
|
+
data.tar.gz: e14be403f25acf8e17ba54e324ec7b13e838a28b171e4866fcbb287b4f15a4dd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 730bb926137c31d86215cb433c97ea2d5252c24d8b38f8ce78596296a30cfc3786a5b69664b927323dbf9598385ed9b5a79cb931821ee9dad9aa9e7c7d08e111
|
7
|
+
data.tar.gz: 12af341b9c75a9c63b9a48a5ca71aab3dcb521f03862a74acd595d732df1e55ee9d8f16eb34485ad8b640439c4ab28dde08db20f8e598c583c66df3a3a58d121
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.7.
|
1
|
+
0.7.8
|
@@ -126,15 +126,22 @@
|
|
126
126
|
Routes
|
127
127
|
</a>
|
128
128
|
</li>
|
129
|
-
<%
|
129
|
+
<% @_rrf_form_routes = @route_groups.values[0].select { |r|
|
130
130
|
r[:matches_params] && r[:verb].in?(["POST", "PUT", "PATCH"])
|
131
131
|
} %>
|
132
|
-
<% unless
|
132
|
+
<% unless @_rrf_form_routes.empty? %>
|
133
133
|
<li class="nav-item">
|
134
134
|
<a class="nav-link" href="#tab-raw-form" data-bs-toggle="tab" role="tab">
|
135
135
|
Raw Form
|
136
136
|
</a>
|
137
137
|
</li>
|
138
|
+
<% if RESTFramework.features[:html_forms] %>
|
139
|
+
<li class="nav-item">
|
140
|
+
<a class="nav-link" href="#tab-raw-form" data-bs-toggle="tab" role="tab">
|
141
|
+
HTML Form
|
142
|
+
</a>
|
143
|
+
</li>
|
144
|
+
<% end %>
|
138
145
|
<% end %>
|
139
146
|
</ul>
|
140
147
|
</div>
|
@@ -142,10 +149,15 @@
|
|
142
149
|
<div class="tab-pane fade show active" id="tab-routes" role="tab">
|
143
150
|
<%= render partial: 'rest_framework/routes' %>
|
144
151
|
</div>
|
145
|
-
<% unless
|
152
|
+
<% unless @_rrf_form_routes.empty? %>
|
146
153
|
<div class="tab-pane fade" id="tab-raw-form" role="tab">
|
147
|
-
<%= render partial: 'rest_framework/raw_form'
|
154
|
+
<%= render partial: 'rest_framework/raw_form' %>
|
148
155
|
</div>
|
156
|
+
<% if RESTFramework.features[:html_forms] %>
|
157
|
+
<div class="tab-pane fade" id="tab-raw-form" role="tab">
|
158
|
+
<%= render partial: 'rest_framework/html_form' %>
|
159
|
+
</div>
|
160
|
+
<% end %>
|
149
161
|
<% end %>
|
150
162
|
</div>
|
151
163
|
</div>
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<div class="mb-2">
|
2
|
+
<label class="form-label w-100">Route
|
3
|
+
<select class="form-control" id="rawFormRoute">
|
4
|
+
<% @_rrf_form_routes.each do |route| %>
|
5
|
+
<% path = @route_props[:with_path_args].call(route[:route]) %>
|
6
|
+
<option value="<%= route[:verb] %>:<%= path %>"><%= route[:verb] %> <%= route[:relative_path] %></option>
|
7
|
+
<% end %>
|
8
|
+
</select>
|
9
|
+
</label>
|
10
|
+
</div>
|
@@ -73,8 +73,8 @@ code {
|
|
73
73
|
// What to do when document loads.
|
74
74
|
document.addEventListener("DOMContentLoaded", (event) => {
|
75
75
|
// Pretty-print JSON.
|
76
|
-
[...document.getElementsByClassName("language-json")].forEach((
|
77
|
-
|
76
|
+
[...document.getElementsByClassName("language-json")].forEach((el, index) => {
|
77
|
+
el.innerHTML = neatJSON(JSON.parse(el.innerText), {
|
78
78
|
wrap: 80,
|
79
79
|
afterComma: 1,
|
80
80
|
afterColon: 1,
|
@@ -84,15 +84,25 @@ document.addEventListener("DOMContentLoaded", (event) => {
|
|
84
84
|
// Then highlight it.
|
85
85
|
hljs.highlightAll();
|
86
86
|
|
87
|
+
// Replace all text nodes with anchor tag links.
|
88
|
+
[...document.querySelectorAll(".rrf-copy code")].forEach((el, index) => {
|
89
|
+
el.innerHTML = rrfLinkify(el.innerHTML)
|
90
|
+
});
|
91
|
+
|
87
92
|
// Insert copy link and callback to copy contents of `<code>` element.
|
88
|
-
[...document.
|
89
|
-
|
93
|
+
[...document.querySelectorAll("rrf-copy")].forEach((el, index) => {
|
94
|
+
el.insertAdjacentHTML(
|
90
95
|
"afterbegin",
|
91
96
|
"<a class=\"rrf-copy-link\" onclick=\"return rrfCopyToClipboard(this)\" href=\"#\">Copy to Clipboard</a>",
|
92
97
|
)
|
93
|
-
})
|
98
|
+
});
|
94
99
|
})
|
95
100
|
|
101
|
+
// Convert plain-text links to anchor tag links.
|
102
|
+
function rrfLinkify(text) {
|
103
|
+
return text.replace(/(https?:\/\/[^\s<>"]+)/g, "<a href=\"$1\" target=\"_blank\">$1</a>")
|
104
|
+
}
|
105
|
+
|
96
106
|
// Replace the document when doing form submission (mainly to support PUT/PATCH/DELETE).
|
97
107
|
function rrfReplaceDocument(content) {
|
98
108
|
// Replace the document with provided content.
|
@@ -1,14 +1,5 @@
|
|
1
1
|
<div style="max-width: 60em; margin: auto">
|
2
|
-
|
3
|
-
<label class="form-label w-100">Route
|
4
|
-
<select class="form-control" id="rawFormRoute">
|
5
|
-
<% raw_form_routes.each do |route| %>
|
6
|
-
<% path = @route_props[:with_path_args].call(route[:route]) %>
|
7
|
-
<option value="<%= route[:verb] %>:<%= path %>"><%= route[:verb] %> <%= route[:relative_path] %></option>
|
8
|
-
<% end %>
|
9
|
-
</select>
|
10
|
-
</label>
|
11
|
-
</div>
|
2
|
+
<%= render partial: "rest_framework/form_routes" %>
|
12
3
|
|
13
4
|
<div class="mb-2">
|
14
5
|
<label class="form-label w-100">Media Type
|
@@ -11,7 +11,6 @@ module RESTFramework::BaseControllerMixin
|
|
11
11
|
exclude_body_fields: [
|
12
12
|
:created_at, :created_by, :created_by_id, :updated_at, :updated_by, :updated_by_id
|
13
13
|
].freeze,
|
14
|
-
accept_generic_params_as_body_params: false,
|
15
14
|
extra_actions: nil,
|
16
15
|
extra_member_actions: nil,
|
17
16
|
filter_backends: nil,
|
@@ -15,6 +15,11 @@ module RESTFramework::BaseModelControllerMixin
|
|
15
15
|
field_config: nil,
|
16
16
|
action_fields: nil,
|
17
17
|
|
18
|
+
# Options for what should be included/excluded from default fields.
|
19
|
+
exclude_associations: false,
|
20
|
+
include_active_storage: false,
|
21
|
+
include_action_text: false,
|
22
|
+
|
18
23
|
# Attributes for finding records.
|
19
24
|
find_by_fields: nil,
|
20
25
|
find_by_query_param: "find_by",
|
@@ -29,6 +34,9 @@ module RESTFramework::BaseModelControllerMixin
|
|
29
34
|
native_serializer_plural_config: nil,
|
30
35
|
native_serializer_only_query_param: "only",
|
31
36
|
native_serializer_except_query_param: "except",
|
37
|
+
native_serializer_associations_limit: nil,
|
38
|
+
native_serializer_associations_limit_query_param: "associations_limit",
|
39
|
+
native_serializer_include_associations_count: false,
|
32
40
|
|
33
41
|
# Attributes for default model filtering, ordering, and searching.
|
34
42
|
filterset_fields: nil,
|
@@ -39,15 +47,16 @@ module RESTFramework::BaseModelControllerMixin
|
|
39
47
|
search_query_param: "search",
|
40
48
|
search_ilike: false,
|
41
49
|
|
50
|
+
# Options for association assignment.
|
51
|
+
permit_id_assignment: true,
|
52
|
+
permit_nested_attributes_assignment: true,
|
53
|
+
|
42
54
|
# Option for `recordset.create` vs `Model.create` behavior.
|
43
55
|
create_from_recordset: true,
|
44
56
|
|
45
57
|
# Control if filtering is done before find.
|
46
58
|
filter_recordset_before_find: true,
|
47
59
|
|
48
|
-
# Option to exclude associations from default fields.
|
49
|
-
exclude_associations: false,
|
50
|
-
|
51
60
|
# Control if bulk operations are done in a transaction and rolled back on error, or if all bulk
|
52
61
|
# operations are attempted and errors simply returned in the response.
|
53
62
|
bulk_transactional: false,
|
@@ -91,19 +100,26 @@ module RESTFramework::BaseModelControllerMixin
|
|
91
100
|
return self.get_model.human_attribute_name(s, default: super)
|
92
101
|
end
|
93
102
|
|
94
|
-
# Get
|
95
|
-
|
96
|
-
|
103
|
+
# Get the available fields. Returning `nil` indicates that anything should be accepted. If
|
104
|
+
# `fallback` is true, then we should fallback to this controller's model columns, or an empty
|
105
|
+
# array.
|
106
|
+
def get_fields(input_fields: nil, fallback: true)
|
107
|
+
input_fields ||= self.fields if fallback
|
108
|
+
|
109
|
+
# If fields is a hash, then parse it.
|
110
|
+
if input_fields.is_a?(Hash)
|
97
111
|
return RESTFramework::Utils.parse_fields_hash(
|
98
|
-
|
112
|
+
input_fields, self.get_model, exclude_associations: self.exclude_associations
|
99
113
|
)
|
114
|
+
elsif !input_fields && fallback
|
115
|
+
# Otherwise, if fields is nil and fallback is true, then fallback to columns.
|
116
|
+
model = self.get_model
|
117
|
+
return model ? RESTFramework::Utils.fields_for(
|
118
|
+
model, exclude_associations: self.exclude_associations
|
119
|
+
) : []
|
100
120
|
end
|
101
121
|
|
102
|
-
return
|
103
|
-
self.get_model ? RESTFramework::Utils.fields_for(
|
104
|
-
self.get_model, exclude_associations: self.exclude_associations
|
105
|
-
) : []
|
106
|
-
)
|
122
|
+
return input_fields
|
107
123
|
end
|
108
124
|
|
109
125
|
# Get a field's config, including defaults.
|
@@ -112,8 +128,12 @@ module RESTFramework::BaseModelControllerMixin
|
|
112
128
|
|
113
129
|
# Default sub-fields if field is an association.
|
114
130
|
if ref = self.get_model.reflections[f]
|
115
|
-
|
116
|
-
|
131
|
+
if ref.polymorphic?
|
132
|
+
columns = {}
|
133
|
+
else
|
134
|
+
model = ref.klass
|
135
|
+
columns = model.columns_hash
|
136
|
+
end
|
117
137
|
config[:sub_fields] ||= RESTFramework::Utils.sub_fields_for(ref)
|
118
138
|
|
119
139
|
# Serialize very basic metadata about sub-fields.
|
@@ -193,18 +213,37 @@ module RESTFramework::BaseModelControllerMixin
|
|
193
213
|
# Get association metadata.
|
194
214
|
if ref = reflections[f]
|
195
215
|
metadata[:kind] = "association"
|
216
|
+
|
217
|
+
# Determine if we render id/ids fields.
|
218
|
+
if self.permit_id_assignment
|
219
|
+
if ref.collection?
|
220
|
+
metadata[:id_field] = "#{f.singularize}_ids"
|
221
|
+
else
|
222
|
+
metadata[:id_field] = "#{f}_id"
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
# Determine if we render nested attributes options.
|
227
|
+
if self.permit_nested_attributes_assignment
|
228
|
+
if nested_opts = model.nested_attributes_options[f.to_sym].presence
|
229
|
+
nested_opts[:field] = "#{f}_attributes"
|
230
|
+
metadata[:nested_attributes_options] = nested_opts
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
196
234
|
begin
|
197
235
|
pk = ref.active_record_primary_key
|
198
236
|
rescue ActiveRecord::UnknownPrimaryKey
|
199
237
|
end
|
200
238
|
metadata[:association] = {
|
201
239
|
macro: ref.macro,
|
240
|
+
collection: ref.collection?,
|
202
241
|
class_name: ref.class_name,
|
203
242
|
foreign_key: ref.foreign_key,
|
204
243
|
primary_key: pk,
|
205
244
|
polymorphic: ref.polymorphic?,
|
206
245
|
table_name: ref.table_name,
|
207
|
-
options: ref.options.presence,
|
246
|
+
options: ref.options.as_json.presence,
|
208
247
|
}.compact
|
209
248
|
end
|
210
249
|
|
@@ -327,26 +366,10 @@ module RESTFramework::BaseModelControllerMixin
|
|
327
366
|
return (action_config[action] if action) || self.class.send(generic_config_key)
|
328
367
|
end
|
329
368
|
|
330
|
-
# Get a list of fields
|
331
|
-
# accepted unless `fallback` is true, in which case we should fallback to this controller's model
|
332
|
-
# columns, or en empty array.
|
369
|
+
# Get a list of fields, taking into account the current action.
|
333
370
|
def get_fields(fallback: false)
|
334
|
-
fields = _get_specific_action_config(:action_fields, :fields)
|
335
|
-
|
336
|
-
# If fields is a hash, then parse it.
|
337
|
-
if fields.is_a?(Hash)
|
338
|
-
return RESTFramework::Utils.parse_fields_hash(
|
339
|
-
fields, self.class.get_model, exclude_associations: self.class.exclude_associations
|
340
|
-
)
|
341
|
-
elsif !fields && fallback
|
342
|
-
# Otherwise, if fields is nil and fallback is true, then fallback to columns.
|
343
|
-
model = self.class.get_model
|
344
|
-
return model ? RESTFramework::Utils.fields_for(
|
345
|
-
model, exclude_associations: self.class.exclude_associations
|
346
|
-
) : []
|
347
|
-
end
|
348
|
-
|
349
|
-
return fields
|
371
|
+
fields = self._get_specific_action_config(:action_fields, :fields)
|
372
|
+
return self.class.get_fields(input_fields: fields, fallback: fallback)
|
350
373
|
end
|
351
374
|
|
352
375
|
# Pass fields to get dynamic metadata based on which fields are available.
|
@@ -389,26 +412,27 @@ module RESTFramework::BaseModelControllerMixin
|
|
389
412
|
# allowed parameters or fields.
|
390
413
|
allowed_params = self.get_allowed_parameters&.map(&:to_s)
|
391
414
|
body_params = if allowed_params
|
392
|
-
data.select { |p|
|
415
|
+
data.select { |p|
|
416
|
+
p.in?(allowed_params) || (
|
417
|
+
self.class.permit_id_assignment && (
|
418
|
+
p.chomp("_id").in?(allowed_params) || p.chomp("_ids").pluralize.in?(allowed_params)
|
419
|
+
)
|
420
|
+
) || (
|
421
|
+
self.class.permit_nested_attributes_assignment &&
|
422
|
+
p.chomp("_attributes").in?(allowed_params)
|
423
|
+
|
424
|
+
)
|
425
|
+
}
|
393
426
|
else
|
394
427
|
data
|
395
428
|
end
|
396
429
|
|
397
|
-
# Add query params in place of missing body params, if configured.
|
398
|
-
if self.class.accept_generic_params_as_body_params && allowed_params
|
399
|
-
(allowed_params - body_params.keys).each do |k|
|
400
|
-
if value = params[k].presence
|
401
|
-
body_params[k] = value
|
402
|
-
end
|
403
|
-
end
|
404
|
-
end
|
405
|
-
|
406
430
|
# Filter primary key if configured.
|
407
431
|
if self.class.filter_pk_from_request_body
|
408
432
|
body_params.delete(self.class.get_model&.primary_key)
|
409
433
|
end
|
410
434
|
|
411
|
-
# Filter fields in exclude_body_fields
|
435
|
+
# Filter fields in `exclude_body_fields`.
|
412
436
|
(self.class.exclude_body_fields || []).each { |f| body_params.delete(f) }
|
413
437
|
|
414
438
|
return body_params
|
@@ -157,7 +157,7 @@ module ActionDispatch::Routing
|
|
157
157
|
public_send(:resource, name, only: [], **kwargs) do
|
158
158
|
# Route a root for this resource.
|
159
159
|
if route_root_to
|
160
|
-
get("", action: route_root_to)
|
160
|
+
get("", action: route_root_to, as: "")
|
161
161
|
end
|
162
162
|
|
163
163
|
collection do
|
@@ -192,6 +192,83 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
|
|
192
192
|
return cfg
|
193
193
|
end
|
194
194
|
|
195
|
+
# Get the associations limit from the controller.
|
196
|
+
def _get_associations_limit
|
197
|
+
return @_get_associations_limit if defined?(@_get_associations_limit)
|
198
|
+
|
199
|
+
limit = @controller.class.native_serializer_associations_limit
|
200
|
+
|
201
|
+
# Extract the limit from the query parameters if it's set.
|
202
|
+
if query_param = @controller.class.native_serializer_associations_limit_query_param
|
203
|
+
if @controller.request.query_parameters.key?(query_param)
|
204
|
+
query_limit = @controller.request.query_parameters[query_param].to_i
|
205
|
+
if query_limit > 0
|
206
|
+
limit = query_limit
|
207
|
+
else
|
208
|
+
limit = nil
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
return @_get_associations_limit = limit
|
214
|
+
end
|
215
|
+
|
216
|
+
# Get a serializer configuration from the controller. `@controller` and `@model` must be set.
|
217
|
+
def _get_controller_serializer_config(fields)
|
218
|
+
columns = []
|
219
|
+
includes = {}
|
220
|
+
methods = []
|
221
|
+
serializer_methods = {}
|
222
|
+
fields.each do |f|
|
223
|
+
if f.in?(@model.column_names)
|
224
|
+
columns << f
|
225
|
+
elsif ref = @model.reflections[f]
|
226
|
+
sub_columns = []
|
227
|
+
sub_methods = []
|
228
|
+
@controller.class.get_field_config(f)[:sub_fields].each do |sf|
|
229
|
+
if !ref.polymorphic? && sf.in?(ref.klass.column_names)
|
230
|
+
sub_columns << sf
|
231
|
+
else
|
232
|
+
sub_methods << sf
|
233
|
+
end
|
234
|
+
end
|
235
|
+
sub_config = {only: sub_columns, methods: sub_methods}
|
236
|
+
|
237
|
+
# Apply certain rules regarding collection associations.
|
238
|
+
if ref.collection?
|
239
|
+
# If we need to limit the number of serialized association records, then dynamically add a
|
240
|
+
# serializer method to do so.
|
241
|
+
if limit = self._get_associations_limit
|
242
|
+
method_name = "__rrf_limit_method_#{f}"
|
243
|
+
serializer_methods[method_name] = f
|
244
|
+
self.define_singleton_method(method_name) do |record|
|
245
|
+
next record.send(f).limit(limit).as_json(**sub_config)
|
246
|
+
end
|
247
|
+
else
|
248
|
+
includes[f] = sub_config
|
249
|
+
end
|
250
|
+
|
251
|
+
# If we need to include the association count, then add it here.
|
252
|
+
if @controller.class.native_serializer_include_associations_count
|
253
|
+
method_name = "__rrf_count_method_#{f}"
|
254
|
+
serializer_methods[method_name] = "#{f}.count"
|
255
|
+
self.define_singleton_method(method_name) do |record|
|
256
|
+
next record.send(f).count
|
257
|
+
end
|
258
|
+
end
|
259
|
+
else
|
260
|
+
includes[f] = sub_config
|
261
|
+
end
|
262
|
+
elsif @model.method_defined?(f)
|
263
|
+
methods << f
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
return {
|
268
|
+
only: columns, include: includes, methods: methods, serializer_methods: serializer_methods
|
269
|
+
}
|
270
|
+
end
|
271
|
+
|
195
272
|
# Get the raw serializer config. Use `deep_dup` on any class mutables (array, hash, etc) to avoid
|
196
273
|
# mutating class state.
|
197
274
|
def _get_raw_serializer_config
|
@@ -206,40 +283,11 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
|
|
206
283
|
end
|
207
284
|
|
208
285
|
# If the config wasn't determined, build a serializer config from controller fields.
|
209
|
-
if fields = @controller&.get_fields(fallback: true)
|
210
|
-
|
211
|
-
|
212
|
-
columns = []
|
213
|
-
includes = {}
|
214
|
-
methods = []
|
215
|
-
if @model
|
216
|
-
fields.each do |f|
|
217
|
-
if f.in?(@model.column_names)
|
218
|
-
columns << f
|
219
|
-
elsif @model.reflections.key?(f)
|
220
|
-
sub_columns = []
|
221
|
-
sub_methods = []
|
222
|
-
@controller.class.get_field_config(f)[:sub_fields].each do |sf|
|
223
|
-
sub_model = @model.reflections[f].klass
|
224
|
-
if sf.in?(sub_model.column_names)
|
225
|
-
sub_columns << sf
|
226
|
-
elsif sub_model.method_defined?(sf)
|
227
|
-
sub_methods << sf
|
228
|
-
end
|
229
|
-
end
|
230
|
-
includes[f] = {only: sub_columns, methods: sub_methods}
|
231
|
-
elsif @model.method_defined?(f)
|
232
|
-
methods << f
|
233
|
-
end
|
234
|
-
end
|
235
|
-
else
|
236
|
-
columns = fields
|
237
|
-
end
|
238
|
-
|
239
|
-
return {only: columns, include: includes, methods: methods}
|
286
|
+
if @model && fields = @controller&.get_fields(fallback: true)
|
287
|
+
return self._get_controller_serializer_config(fields.deep_dup)
|
240
288
|
end
|
241
289
|
|
242
|
-
# By default, pass an empty configuration,
|
290
|
+
# By default, pass an empty configuration, using the default Rails serializer.
|
243
291
|
return {}
|
244
292
|
end
|
245
293
|
|
@@ -250,14 +298,14 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
|
|
250
298
|
|
251
299
|
# Serialize a single record and merge results of `serializer_methods`.
|
252
300
|
def _serialize(record, config, serializer_methods)
|
253
|
-
# Ensure serializer_methods is either falsy, or
|
254
|
-
if serializer_methods && !serializer_methods.
|
255
|
-
serializer_methods = [serializer_methods]
|
301
|
+
# Ensure serializer_methods is either falsy, or a hash.
|
302
|
+
if serializer_methods && !serializer_methods.is_a?(Hash)
|
303
|
+
serializer_methods = [serializer_methods].flatten.map { |m| [m, m] }.to_h
|
256
304
|
end
|
257
305
|
|
258
306
|
# Merge serialized record with any serializer method results.
|
259
307
|
return record.serializable_hash(config).merge(
|
260
|
-
serializer_methods&.map { |m| [
|
308
|
+
serializer_methods&.map { |m, k| [k.to_sym, self.send(m, record)] }.to_h,
|
261
309
|
)
|
262
310
|
end
|
263
311
|
|
data/lib/rest_framework/utils.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module RESTFramework::Utils
|
2
2
|
HTTP_METHOD_ORDERING = %w(GET POST PUT PATCH DELETE OPTIONS HEAD)
|
3
|
-
LABEL_FIELDS = %w(name label login title email username)
|
3
|
+
LABEL_FIELDS = %w(name label login title email username url)
|
4
4
|
|
5
5
|
# Convert `extra_actions` hash to a consistent format: `{path:, methods:, kwargs:}`, and
|
6
6
|
# additional metadata fields.
|
@@ -168,8 +168,14 @@ module RESTFramework::Utils
|
|
168
168
|
return model.column_names.reject { |c|
|
169
169
|
c.in?(foreign_keys)
|
170
170
|
} + model.reflections.map { |association, ref|
|
171
|
-
|
172
|
-
|
171
|
+
# Exclude certain associations (by default, active storage and action text associations).
|
172
|
+
if ref.class_name.in?(RESTFramework.config.exclude_association_classes)
|
173
|
+
next nil
|
174
|
+
end
|
175
|
+
|
176
|
+
if ref.collection? && RESTFramework.config.large_reverse_association_tables&.include?(
|
177
|
+
ref.table_name,
|
178
|
+
)
|
173
179
|
next nil
|
174
180
|
end
|
175
181
|
|
@@ -179,9 +185,7 @@ module RESTFramework::Utils
|
|
179
185
|
|
180
186
|
# Get the sub-fields that may be serialized and filtered/ordered for a reflection.
|
181
187
|
def self.sub_fields_for(ref)
|
182
|
-
model = ref.klass
|
183
|
-
|
184
|
-
if model
|
188
|
+
if !ref.polymorphic? && model = ref.klass
|
185
189
|
sub_fields = [model.primary_key].flatten.compact
|
186
190
|
|
187
191
|
# Preferrably find a database column to use as label.
|
data/lib/rest_framework.rb
CHANGED
@@ -21,6 +21,12 @@ module RESTFramework
|
|
21
21
|
# Global configuration should be kept minimal, as controller-level configurations allows multiple
|
22
22
|
# APIs to be defined to behave differently.
|
23
23
|
class Config
|
24
|
+
DEFAULT_EXCLUDE_ASSOCIATION_CLASSES = %w(
|
25
|
+
ActionText::RichText
|
26
|
+
ActiveStorage::Attachment
|
27
|
+
ActiveStorage::Blob
|
28
|
+
).freeze
|
29
|
+
|
24
30
|
# Do not run `rrf_finalize` on controllers automatically using a `TracePoint` hook. This is a
|
25
31
|
# performance option and must be global because we have to determine this before any
|
26
32
|
# controller-specific configuration is set. If this is set to `true`, then you must manually
|
@@ -44,8 +50,12 @@ module RESTFramework
|
|
44
50
|
# Option to disable `rescue_from` on the controller mixins.
|
45
51
|
attr_accessor :disable_rescue_from
|
46
52
|
|
53
|
+
# Options to exclude certain classes from being added by default as association fields.
|
54
|
+
attr_accessor :exclude_association_classes
|
55
|
+
|
47
56
|
def initialize
|
48
57
|
self.show_backtrace = Rails.env.development?
|
58
|
+
self.exclude_association_classes = DEFAULT_EXCLUDE_ASSOCIATION_CLASSES
|
49
59
|
end
|
50
60
|
end
|
51
61
|
|
@@ -56,6 +66,12 @@ module RESTFramework
|
|
56
66
|
def self.configure
|
57
67
|
yield(self.config)
|
58
68
|
end
|
69
|
+
|
70
|
+
def self.features
|
71
|
+
return @features ||= {
|
72
|
+
html_forms: false,
|
73
|
+
}
|
74
|
+
end
|
59
75
|
end
|
60
76
|
|
61
77
|
require_relative "rest_framework/controller_mixins"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rest_framework
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gregory N. Schmit
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-01-
|
11
|
+
date: 2023-01-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -35,7 +35,9 @@ files:
|
|
35
35
|
- README.md
|
36
36
|
- VERSION
|
37
37
|
- app/views/layouts/rest_framework.html.erb
|
38
|
+
- app/views/rest_framework/_form_routes.html.erb
|
38
39
|
- app/views/rest_framework/_head.html.erb
|
40
|
+
- app/views/rest_framework/_html_form.html.erb
|
39
41
|
- app/views/rest_framework/_raw_form.html.erb
|
40
42
|
- app/views/rest_framework/_route.html.erb
|
41
43
|
- app/views/rest_framework/_routes.html.erb
|