rest_framework 0.9.2 → 0.9.3
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/rest_framework/_external.html.erb +13 -0
- data/app/views/rest_framework/_head.html.erb +2 -192
- data/{docs/assets/js/rest_framework.js → app/views/rest_framework/_shared.html} +69 -42
- data/docs/Gemfile +1 -0
- data/docs/Gemfile.lock +14 -14
- data/docs/_config.yml +4 -2
- data/docs/_guide/2_controllers.md +342 -0
- data/docs/_guide/3_serializers.md +1 -1
- data/docs/_guide/4_filtering_and_ordering.md +8 -8
- data/docs/_includes/external.html +9 -0
- data/docs/_includes/head.html +135 -15
- data/docs/_includes/shared.html +164 -0
- data/lib/rest_framework/controller_mixins/base.rb +23 -36
- data/lib/rest_framework/controller_mixins/models.rb +86 -75
- data/lib/rest_framework/controller_mixins.rb +1 -0
- data/lib/rest_framework/engine.rb +9 -0
- data/lib/rest_framework/filters/base.rb +9 -0
- data/lib/rest_framework/filters/model_ordering.rb +48 -0
- data/lib/rest_framework/filters/model_query.rb +51 -0
- data/lib/rest_framework/filters/model_search.rb +41 -0
- data/lib/rest_framework/filters/ransack.rb +25 -0
- data/lib/rest_framework/filters.rb +6 -150
- data/lib/rest_framework/paginators.rb +7 -11
- data/lib/rest_framework/serializers.rb +10 -10
- data/lib/rest_framework/utils.rb +15 -7
- data/lib/rest_framework.rb +93 -4
- data/vendor/assets/javascripts/rest_framework/bootstrap.js +7 -0
- data/vendor/assets/javascripts/rest_framework/highlight-json.js +7 -0
- data/vendor/assets/javascripts/rest_framework/highlight-xml.js +29 -0
- data/vendor/assets/javascripts/rest_framework/highlight.js +1202 -0
- data/vendor/assets/javascripts/rest_framework/neatjson.js +8 -0
- data/vendor/assets/javascripts/rest_framework/trix.js +6 -0
- data/vendor/assets/stylesheets/rest_framework/bootstrap-icons.css +13 -0
- data/vendor/assets/stylesheets/rest_framework/bootstrap.css +6 -0
- data/vendor/assets/stylesheets/rest_framework/highlight-a11y-dark.css +7 -0
- data/vendor/assets/stylesheets/rest_framework/highlight-a11y-light.css +7 -0
- data/vendor/assets/stylesheets/rest_framework/trix.css +410 -0
- metadata +23 -5
- data/docs/_guide/2_controller_mixins.md +0 -293
- data/docs/assets/css/rest_framework.css +0 -159
@@ -0,0 +1,164 @@
|
|
1
|
+
<!--
|
2
|
+
AUTOGENERATED
|
3
|
+
Updates must be written to `shared.{css,js}` and synced with `rake maintain_assets`.
|
4
|
+
-->
|
5
|
+
<style>
|
6
|
+
:root {
|
7
|
+
--rrf-red: #900;
|
8
|
+
--rrf-red-hover: #5f0c0c;
|
9
|
+
--rrf-light-red: #db2525;
|
10
|
+
--rrf-light-red-hover: #b80404;
|
11
|
+
}
|
12
|
+
#rrfAccentBar {
|
13
|
+
background-color: var(--rrf-red);
|
14
|
+
height: .3em;
|
15
|
+
}
|
16
|
+
header nav { background-color: black; }
|
17
|
+
|
18
|
+
/* Header adjustments. */
|
19
|
+
h1 { font-size: 2rem; }
|
20
|
+
h2 { font-size: 1.7rem; }
|
21
|
+
h3 { font-size: 1.5rem; }
|
22
|
+
h4 { font-size: 1.3rem; }
|
23
|
+
h5 { font-size: 1.1rem; }
|
24
|
+
h6 { font-size: 1rem; }
|
25
|
+
h1, h2, h3, h4, h5, h6 {
|
26
|
+
color: var(--rrf-red);
|
27
|
+
}
|
28
|
+
html[data-bs-theme="dark"] h1,
|
29
|
+
html[data-bs-theme="dark"] h2,
|
30
|
+
html[data-bs-theme="dark"] h3,
|
31
|
+
html[data-bs-theme="dark"] h4,
|
32
|
+
html[data-bs-theme="dark"] h5,
|
33
|
+
html[data-bs-theme="dark"] h6 {
|
34
|
+
color: var(--rrf-light-red);
|
35
|
+
}
|
36
|
+
|
37
|
+
/* Improve code and code blocks. */
|
38
|
+
pre code, .trix-content pre {
|
39
|
+
display: block;
|
40
|
+
overflow-x: auto;
|
41
|
+
padding: .5em !important;
|
42
|
+
}
|
43
|
+
code, .trix-content pre {
|
44
|
+
--bs-code-color: black;
|
45
|
+
background-color: #eee !important;
|
46
|
+
border: 1px solid #aaa;
|
47
|
+
border-radius: 3px;
|
48
|
+
padding: .1em .3em;
|
49
|
+
}
|
50
|
+
html[data-bs-theme="dark"] code, html[data-bs-theme="dark"] .trix-content pre {
|
51
|
+
--bs-code-color: white;
|
52
|
+
background-color: #2b2b2b !important;
|
53
|
+
}
|
54
|
+
|
55
|
+
/* Anchors */
|
56
|
+
a:not(.nav-link) {
|
57
|
+
text-decoration: none;
|
58
|
+
color: var(--rrf-red);
|
59
|
+
}
|
60
|
+
a:hover:not(.nav-link) {
|
61
|
+
text-decoration: underline;
|
62
|
+
color: var(--rrf-red-hover);
|
63
|
+
}
|
64
|
+
html[data-bs-theme="dark"] a:not(.nav-link) { color: var(--rrf-light-red); }
|
65
|
+
html[data-bs-theme="dark"] a:hover:not(.nav-link) { color: var(--rrf-light-red-hover); }
|
66
|
+
|
67
|
+
</style>
|
68
|
+
<script>
|
69
|
+
;(() => {
|
70
|
+
// Get the real mode from a selected mode. Anything other than "light" or "dark" is treated as
|
71
|
+
// "system" mode.
|
72
|
+
const rrfGetRealMode = (selectedMode) => {
|
73
|
+
if (selectedMode === "light" || selectedMode === "dark") {
|
74
|
+
return selectedMode
|
75
|
+
}
|
76
|
+
|
77
|
+
if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
78
|
+
return "dark"
|
79
|
+
}
|
80
|
+
|
81
|
+
return "light"
|
82
|
+
}
|
83
|
+
|
84
|
+
// Set the mode, given a "selected" mode.
|
85
|
+
const rrfSetSelectedMode = (selectedMode) => {
|
86
|
+
// Anything except "light" or "dark" is casted to "system".
|
87
|
+
if (selectedMode !== "light" && selectedMode !== "dark") {
|
88
|
+
selectedMode = "system"
|
89
|
+
}
|
90
|
+
|
91
|
+
// Store selected mode in `localStorage`.
|
92
|
+
localStorage.setItem("rrfMode", selectedMode)
|
93
|
+
|
94
|
+
// Set the mode selector to the selected mode.
|
95
|
+
const modeComponent = document.getElementById("rrfModeComponent")
|
96
|
+
if (modeComponent) {
|
97
|
+
let labelHTML
|
98
|
+
modeComponent.querySelectorAll("button[data-rrf-mode-value]").forEach((el) => {
|
99
|
+
if (el.getAttribute("data-rrf-mode-value") === selectedMode) {
|
100
|
+
el.classList.add("active")
|
101
|
+
labelHTML = el.querySelector("i").outerHTML.replace("ms-2", "me-1")
|
102
|
+
} else {
|
103
|
+
el.classList.remove("active")
|
104
|
+
}
|
105
|
+
})
|
106
|
+
modeComponent.querySelector("button[data-bs-toggle]").innerHTML = labelHTML
|
107
|
+
}
|
108
|
+
|
109
|
+
// Get the real mode to use.
|
110
|
+
realMode = rrfGetRealMode(selectedMode)
|
111
|
+
|
112
|
+
// Set the `realMode` effects.
|
113
|
+
if (realMode === "light") {
|
114
|
+
document.querySelectorAll(".rrf-light-mode").forEach((el) => {
|
115
|
+
el.disabled = false
|
116
|
+
})
|
117
|
+
document.querySelectorAll(".rrf-dark-mode").forEach((el) => {
|
118
|
+
el.disabled = true
|
119
|
+
})
|
120
|
+
document.querySelectorAll(".rrf-mode").forEach((el) => {
|
121
|
+
el.setAttribute("data-bs-theme", "light")
|
122
|
+
})
|
123
|
+
} else if (realMode === "dark") {
|
124
|
+
document.querySelectorAll(".rrf-light-mode").forEach((el) => {
|
125
|
+
el.disabled = true
|
126
|
+
})
|
127
|
+
document.querySelectorAll(".rrf-dark-mode").forEach((el) => {
|
128
|
+
el.disabled = false
|
129
|
+
})
|
130
|
+
document.querySelectorAll(".rrf-mode").forEach((el) => {
|
131
|
+
el.setAttribute("data-bs-theme", "dark")
|
132
|
+
})
|
133
|
+
} else {
|
134
|
+
console.log(`RRF: Unknown mode: ${mode}`)
|
135
|
+
}
|
136
|
+
}
|
137
|
+
|
138
|
+
// Initialize dark/light mode before page fully loads to prevent flash.
|
139
|
+
rrfSetSelectedMode(localStorage.getItem("rrfMode"))
|
140
|
+
|
141
|
+
// Initialize dark/light mode after page load (mostly so mode component is updated).
|
142
|
+
document.addEventListener("DOMContentLoaded", (event) => {
|
143
|
+
rrfSetSelectedMode(localStorage.getItem("rrfMode"))
|
144
|
+
|
145
|
+
// Also set up mode selector.
|
146
|
+
document.querySelectorAll("#rrfModeComponent button[data-rrf-mode-value]").forEach((el) => {
|
147
|
+
el.addEventListener("click", (event) => {
|
148
|
+
rrfSetSelectedMode(event.target.getAttribute("data-rrf-mode-value"))
|
149
|
+
})
|
150
|
+
})
|
151
|
+
})
|
152
|
+
|
153
|
+
// Handle case where user changes system theme.
|
154
|
+
if (window.matchMedia) {
|
155
|
+
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => {
|
156
|
+
const selectedMode = localStorage.getItem("rrfMode")
|
157
|
+
if (selectedMode !== "light" && selectedMode !== "dark") {
|
158
|
+
rrfSetSelectedMode("system")
|
159
|
+
}
|
160
|
+
})
|
161
|
+
}
|
162
|
+
})()
|
163
|
+
|
164
|
+
</script>
|
@@ -6,28 +6,18 @@ require_relative "../utils"
|
|
6
6
|
# the ability to route arbitrary actions with `extra_actions`. This is also where `api_response`
|
7
7
|
# is defined.
|
8
8
|
module RESTFramework::BaseControllerMixin
|
9
|
-
|
10
|
-
filter_pk_from_request_body: true,
|
11
|
-
exclude_body_fields: %w[
|
12
|
-
created_at
|
13
|
-
created_by
|
14
|
-
created_by_id
|
15
|
-
updated_at
|
16
|
-
updated_by
|
17
|
-
updated_by_id
|
18
|
-
_method
|
19
|
-
utf8
|
20
|
-
authenticity_token
|
21
|
-
].freeze,
|
9
|
+
RRF_BASE_CONFIG = {
|
22
10
|
extra_actions: nil,
|
23
11
|
extra_member_actions: nil,
|
24
|
-
filter_backends: nil,
|
25
12
|
singleton_controller: nil,
|
26
13
|
|
27
14
|
# Options related to metadata and display.
|
28
15
|
title: nil,
|
29
16
|
description: nil,
|
30
17
|
inflect_acronyms: ["ID", "IDs", "REST", "API", "APIs"].freeze,
|
18
|
+
}
|
19
|
+
RRF_BASE_INSTANCE_CONFIG = {
|
20
|
+
filter_backends: nil,
|
31
21
|
|
32
22
|
# Options related to serialization.
|
33
23
|
rescue_unknown_format_with: :json,
|
@@ -42,10 +32,6 @@ module RESTFramework::BaseControllerMixin
|
|
42
32
|
page_size_query_param: "page_size",
|
43
33
|
max_page_size: nil,
|
44
34
|
|
45
|
-
# Options related to bulk actions and batch processing.
|
46
|
-
bulk_guard_query_param: nil,
|
47
|
-
enable_batch_processing: nil,
|
48
|
-
|
49
35
|
# Option to disable serializer adapters by default, mainly introduced because Active Model
|
50
36
|
# Serializers will do things like serialize `[]` into `{"":[]}`.
|
51
37
|
disable_adapters_by_default: true,
|
@@ -146,7 +132,7 @@ module RESTFramework::BaseControllerMixin
|
|
146
132
|
# :nocov:
|
147
133
|
def rrf_finalize
|
148
134
|
if RESTFramework.config.freeze_config
|
149
|
-
self::
|
135
|
+
(self::RRF_BASE_CONFIG.keys + self::RRF_BASE_INSTANCE_CONFIG.keys).each { |k|
|
150
136
|
v = self.send(k)
|
151
137
|
v.freeze if v.is_a?(Hash) || v.is_a?(Array)
|
152
138
|
}
|
@@ -161,14 +147,15 @@ module RESTFramework::BaseControllerMixin
|
|
161
147
|
base.extend(ClassMethods)
|
162
148
|
|
163
149
|
# Add class attributes (with defaults) unless they already exist.
|
164
|
-
|
150
|
+
RRF_BASE_CONFIG.each do |a, default|
|
165
151
|
next if base.respond_to?(a)
|
166
152
|
|
167
|
-
base.class_attribute(a)
|
153
|
+
base.class_attribute(a, default: default, instance_accessor: false)
|
154
|
+
end
|
155
|
+
RRF_BASE_INSTANCE_CONFIG.each do |a, default|
|
156
|
+
next if base.respond_to?(a)
|
168
157
|
|
169
|
-
|
170
|
-
# parameter on `class_attribute`.
|
171
|
-
base.send(:"#{a}=", default)
|
158
|
+
base.class_attribute(a, default: default)
|
172
159
|
end
|
173
160
|
|
174
161
|
# Alias `extra_actions` to `extra_collection_actions`.
|
@@ -219,7 +206,7 @@ module RESTFramework::BaseControllerMixin
|
|
219
206
|
|
220
207
|
# Get the configured serializer class.
|
221
208
|
def get_serializer_class
|
222
|
-
return nil unless serializer_class = self.
|
209
|
+
return nil unless serializer_class = self.serializer_class
|
223
210
|
|
224
211
|
# Support dynamically resolving serializer given a symbol or string.
|
225
212
|
serializer_class = serializer_class.to_s if serializer_class.is_a?(Symbol)
|
@@ -242,7 +229,7 @@ module RESTFramework::BaseControllerMixin
|
|
242
229
|
|
243
230
|
# Get filtering backends, defaulting to no backends.
|
244
231
|
def get_filter_backends
|
245
|
-
return self.
|
232
|
+
return self.filter_backends || []
|
246
233
|
end
|
247
234
|
|
248
235
|
# Filter an arbitrary data set over all configured filter backends.
|
@@ -299,7 +286,7 @@ module RESTFramework::BaseControllerMixin
|
|
299
286
|
end
|
300
287
|
|
301
288
|
# Do not use any adapters by default, if configured.
|
302
|
-
if self.
|
289
|
+
if self.disable_adapters_by_default && !kwargs.key?(:adapter)
|
303
290
|
kwargs[:adapter] = nil
|
304
291
|
end
|
305
292
|
|
@@ -309,27 +296,27 @@ module RESTFramework::BaseControllerMixin
|
|
309
296
|
begin
|
310
297
|
respond_to do |format|
|
311
298
|
if payload == ""
|
312
|
-
format.json { head(kwargs[:status] || :no_content) } if self.
|
313
|
-
format.xml { head(kwargs[:status] || :no_content) } if self.
|
299
|
+
format.json { head(kwargs[:status] || :no_content) } if self.serialize_to_json
|
300
|
+
format.xml { head(kwargs[:status] || :no_content) } if self.serialize_to_xml
|
314
301
|
else
|
315
302
|
format.json {
|
316
303
|
jkwargs = kwargs.merge(json_kwargs)
|
317
304
|
render(json: payload, layout: false, **jkwargs)
|
318
|
-
} if self.
|
305
|
+
} if self.serialize_to_json
|
319
306
|
format.xml {
|
320
307
|
xkwargs = kwargs.merge(xml_kwargs)
|
321
308
|
render(xml: payload, layout: false, **xkwargs)
|
322
|
-
} if self.
|
309
|
+
} if self.serialize_to_xml
|
323
310
|
# TODO: possibly support more formats here if supported?
|
324
311
|
end
|
325
312
|
format.html {
|
326
313
|
@payload = payload
|
327
314
|
if payload == ""
|
328
|
-
@json_payload = "" if self.
|
329
|
-
@xml_payload = "" if self.
|
315
|
+
@json_payload = "" if self.serialize_to_json
|
316
|
+
@xml_payload = "" if self.serialize_to_xml
|
330
317
|
else
|
331
|
-
@json_payload = payload.to_json if self.
|
332
|
-
@xml_payload = payload.to_xml if self.
|
318
|
+
@json_payload = payload.to_json if self.serialize_to_json
|
319
|
+
@xml_payload = payload.to_xml if self.serialize_to_xml
|
333
320
|
end
|
334
321
|
@title ||= self.class.get_title
|
335
322
|
@description ||= self.class.description
|
@@ -347,7 +334,7 @@ module RESTFramework::BaseControllerMixin
|
|
347
334
|
}
|
348
335
|
end
|
349
336
|
rescue ActionController::UnknownFormat
|
350
|
-
if !already_rescued_unknown_format && rescue_format = self.
|
337
|
+
if !already_rescued_unknown_format && rescue_format = self.rescue_unknown_format_with
|
351
338
|
request.format = rescue_format
|
352
339
|
already_rescued_unknown_format = true
|
353
340
|
retry
|
@@ -14,7 +14,7 @@ module RESTFramework::BaseModelControllerMixin
|
|
14
14
|
}
|
15
15
|
include RESTFramework::BaseControllerMixin
|
16
16
|
|
17
|
-
|
17
|
+
RRF_BASE_MODEL_CONFIG = {
|
18
18
|
# Core attributes related to models.
|
19
19
|
model: nil,
|
20
20
|
recordset: nil,
|
@@ -22,20 +22,31 @@ module RESTFramework::BaseModelControllerMixin
|
|
22
22
|
# Attributes for configuring record fields.
|
23
23
|
fields: nil,
|
24
24
|
field_config: nil,
|
25
|
-
action_fields: nil,
|
26
25
|
|
27
26
|
# Options for what should be included/excluded from default fields.
|
28
27
|
exclude_associations: false,
|
29
28
|
include_active_storage: false,
|
30
29
|
include_action_text: false,
|
31
|
-
|
30
|
+
}
|
31
|
+
RRF_BASE_MODEL_INSTANCE_CONFIG = {
|
32
32
|
# Attributes for finding records.
|
33
33
|
find_by_fields: nil,
|
34
34
|
find_by_query_param: "find_by",
|
35
35
|
|
36
|
-
#
|
36
|
+
# Options for handling request body parameters.
|
37
37
|
allowed_parameters: nil,
|
38
|
-
|
38
|
+
filter_pk_from_request_body: true,
|
39
|
+
exclude_body_fields: %w[
|
40
|
+
created_at
|
41
|
+
created_by
|
42
|
+
created_by_id
|
43
|
+
updated_at
|
44
|
+
updated_by
|
45
|
+
updated_by_id
|
46
|
+
_method
|
47
|
+
utf8
|
48
|
+
authenticity_token
|
49
|
+
].freeze,
|
39
50
|
|
40
51
|
# Attributes for the default native serializer.
|
41
52
|
native_serializer_config: nil,
|
@@ -59,13 +70,18 @@ module RESTFramework::BaseModelControllerMixin
|
|
59
70
|
# Options for association assignment.
|
60
71
|
permit_id_assignment: true,
|
61
72
|
permit_nested_attributes_assignment: true,
|
62
|
-
allow_all_nested_attributes: false,
|
63
73
|
|
64
74
|
# Option for `recordset.create` vs `Model.create` behavior.
|
65
75
|
create_from_recordset: true,
|
66
76
|
|
67
77
|
# Control if filtering is done before find.
|
68
78
|
filter_recordset_before_find: true,
|
79
|
+
|
80
|
+
# Options for `ransack` filtering.
|
81
|
+
ransack_options: nil,
|
82
|
+
ransack_query_param: "q",
|
83
|
+
ransack_distinct: true,
|
84
|
+
ransack_distinct_query_param: "distinct",
|
69
85
|
}
|
70
86
|
|
71
87
|
module ClassMethods
|
@@ -130,6 +146,7 @@ module RESTFramework::BaseModelControllerMixin
|
|
130
146
|
columns = model.columns_hash
|
131
147
|
end
|
132
148
|
config[:sub_fields] ||= RESTFramework::Utils.sub_fields_for(ref)
|
149
|
+
config[:sub_fields] = config[:sub_fields].map(&:to_s)
|
133
150
|
|
134
151
|
# Serialize very basic metadata about sub-fields.
|
135
152
|
config[:sub_fields_metadata] = config[:sub_fields].map { |sf|
|
@@ -228,21 +245,17 @@ module RESTFramework::BaseModelControllerMixin
|
|
228
245
|
if ref = reflections[f]
|
229
246
|
metadata[:kind] = "association"
|
230
247
|
|
231
|
-
# Determine if we render id/ids fields.
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
elsif ref.belongs_to?
|
236
|
-
metadata[:id_field] = "#{f}_id"
|
237
|
-
end
|
248
|
+
# Determine if we render id/ids fields. Unfortunately, `has_one` does not provide this
|
249
|
+
# interface.
|
250
|
+
if self.permit_id_assignment && id_field = RESTFramework::Utils.get_id_field(f, ref)
|
251
|
+
metadata[:id_field] = id_field
|
238
252
|
end
|
239
253
|
|
240
254
|
# Determine if we render nested attributes options.
|
241
|
-
if self.permit_nested_attributes_assignment
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
end
|
255
|
+
if self.permit_nested_attributes_assignment && (
|
256
|
+
nested_opts = model.nested_attributes_options[f.to_sym].presence
|
257
|
+
)
|
258
|
+
metadata[:nested_attributes_options] = {field: "#{f}_attributes", **nested_opts}
|
246
259
|
end
|
247
260
|
|
248
261
|
begin
|
@@ -364,7 +377,7 @@ module RESTFramework::BaseModelControllerMixin
|
|
364
377
|
# self.setup_channel
|
365
378
|
|
366
379
|
if RESTFramework.config.freeze_config
|
367
|
-
self::
|
380
|
+
(self::RRF_BASE_MODEL_CONFIG.keys + self::RRF_BASE_MODEL_INSTANCE_CONFIG.keys).each { |k|
|
368
381
|
v = self.send(k)
|
369
382
|
v.freeze if v.is_a?(Hash) || v.is_a?(Array)
|
370
383
|
}
|
@@ -381,31 +394,21 @@ module RESTFramework::BaseModelControllerMixin
|
|
381
394
|
base.extend(ClassMethods)
|
382
395
|
|
383
396
|
# Add class attributes (with defaults) unless they already exist.
|
384
|
-
|
397
|
+
RRF_BASE_MODEL_CONFIG.each do |a, default|
|
385
398
|
next if base.respond_to?(a)
|
386
399
|
|
387
|
-
base.class_attribute(a)
|
388
|
-
|
389
|
-
# Set default manually so we can still support Rails 4. Maybe later we can use the default
|
390
|
-
# parameter on `class_attribute`.
|
391
|
-
base.send(:"#{a}=", default)
|
400
|
+
base.class_attribute(a, default: default, instance_accessor: false)
|
392
401
|
end
|
393
|
-
|
394
|
-
|
395
|
-
def _get_specific_action_config(action_config_key, generic_config_key)
|
396
|
-
action_config = self.class.send(action_config_key)&.with_indifferent_access || {}
|
397
|
-
action = self.action_name&.to_sym
|
398
|
-
|
399
|
-
# Index action should use :list serializer if :index is not provided.
|
400
|
-
action = :list if action == :index && !action_config.key?(:index)
|
402
|
+
RRF_BASE_MODEL_INSTANCE_CONFIG.each do |a, default|
|
403
|
+
next if base.respond_to?(a)
|
401
404
|
|
402
|
-
|
405
|
+
base.class_attribute(a, default: default)
|
406
|
+
end
|
403
407
|
end
|
404
408
|
|
405
|
-
# Get a list of fields
|
409
|
+
# Get a list of fields for this controller.
|
406
410
|
def get_fields
|
407
|
-
|
408
|
-
return self.class.get_fields(input_fields: fields)
|
411
|
+
return self.class.get_fields(input_fields: self.class.fields)
|
409
412
|
end
|
410
413
|
|
411
414
|
# Pass fields to get dynamic metadata based on which fields are available.
|
@@ -413,19 +416,11 @@ module RESTFramework::BaseModelControllerMixin
|
|
413
416
|
return self.class.get_options_metadata
|
414
417
|
end
|
415
418
|
|
416
|
-
# Get a list of find_by fields for the current action.
|
417
|
-
def get_find_by_fields
|
418
|
-
return self.class.find_by_fields
|
419
|
-
end
|
420
|
-
|
421
419
|
# Get a list of parameters allowed for the current action.
|
422
420
|
def get_allowed_parameters
|
423
421
|
return @_get_allowed_parameters if defined?(@_get_allowed_parameters)
|
424
422
|
|
425
|
-
@_get_allowed_parameters = self.
|
426
|
-
:allowed_action_parameters,
|
427
|
-
:allowed_parameters,
|
428
|
-
)
|
423
|
+
@_get_allowed_parameters = self.allowed_parameters
|
429
424
|
return @_get_allowed_parameters if @_get_allowed_parameters
|
430
425
|
|
431
426
|
# For fields, automatically add `_id`/`_ids` and `_attributes` variations for associations.
|
@@ -454,20 +449,16 @@ module RESTFramework::BaseModelControllerMixin
|
|
454
449
|
# Return field if it's not an association.
|
455
450
|
next f unless ref = reflections[f]
|
456
451
|
|
457
|
-
if self.
|
458
|
-
if
|
459
|
-
hash_variations[
|
460
|
-
|
461
|
-
variations <<
|
452
|
+
if self.permit_id_assignment && id_field = RESTFramework::Utils.get_id_field(f, ref)
|
453
|
+
if id_field.ends_with?("_ids")
|
454
|
+
hash_variations[id_field] = []
|
455
|
+
else
|
456
|
+
variations << id_field
|
462
457
|
end
|
463
458
|
end
|
464
459
|
|
465
|
-
if self.
|
466
|
-
|
467
|
-
hash_variations["#{f}_attributes"] = {}
|
468
|
-
else
|
469
|
-
hash_variations["#{f}_attributes"] = self.class.get_field_config(f)[:sub_fields]
|
470
|
-
end
|
460
|
+
if self.permit_nested_attributes_assignment
|
461
|
+
hash_variations["#{f}_attributes"] = self.class.get_field_config(f)[:sub_fields]
|
471
462
|
end
|
472
463
|
|
473
464
|
# Associations are not allowed to be submitted in their bare form.
|
@@ -483,37 +474,57 @@ module RESTFramework::BaseModelControllerMixin
|
|
483
474
|
return super || RESTFramework::NativeSerializer
|
484
475
|
end
|
485
476
|
|
486
|
-
# Get filtering backends, defaulting to using `
|
477
|
+
# Get filtering backends, defaulting to using `ModelQueryFilter`, `ModelOrderingFilter`, and
|
487
478
|
# `ModelSearchFilter`.
|
488
479
|
def get_filter_backends
|
489
|
-
return self.
|
490
|
-
RESTFramework::
|
480
|
+
return self.filter_backends || [
|
481
|
+
RESTFramework::ModelQueryFilter,
|
491
482
|
RESTFramework::ModelOrderingFilter,
|
492
483
|
RESTFramework::ModelSearchFilter,
|
493
484
|
]
|
494
485
|
end
|
495
486
|
|
496
487
|
# Use strong parameters to filter the request body using the configured allowed parameters.
|
497
|
-
def get_body_params(
|
498
|
-
data
|
488
|
+
def get_body_params(bulk_mode: nil)
|
489
|
+
data = self.request.request_parameters
|
499
490
|
pk = self.class.get_model&.primary_key
|
491
|
+
allowed_params = self.get_allowed_parameters
|
492
|
+
|
493
|
+
# Before we filter the data, dynamically dispatch association assignment to either the id/ids
|
494
|
+
# assignment ActiveRecord API or the nested assignment ActiveRecord API. Note that there is no
|
495
|
+
# need to check for `permit_id_assignment` or `permit_nested_attributes_assignment` here, since
|
496
|
+
# that is enforced by strong parameters generated by `get_allowed_parameters`.
|
497
|
+
self.class.get_model.reflections.each do |name, ref|
|
498
|
+
if payload = data[name]
|
499
|
+
if payload.is_a?(Hash) || (payload.is_a?(Array) && payload.all? { |x| x.is_a?(Hash) })
|
500
|
+
# Assume nested attributes assignment.
|
501
|
+
attributes_key = "#{name}_attributes"
|
502
|
+
data[attributes_key] = data.delete(name) unless data[attributes_key]
|
503
|
+
elsif id_field = RESTFramework::Utils.get_id_field(name, ref)
|
504
|
+
# Assume id/ids assignment.
|
505
|
+
data[id_field] = data.delete(name) unless data[id_field]
|
506
|
+
end
|
507
|
+
end
|
508
|
+
end
|
500
509
|
|
501
510
|
# Filter the request body with strong params. If `bulk` is true, then we apply allowed
|
502
511
|
# parameters to the `_json` key of the request body.
|
503
|
-
body_params = if
|
512
|
+
body_params = if allowed_params == true
|
513
|
+
ActionController::Parameters.new(data).permit!
|
514
|
+
elsif bulk_mode
|
504
515
|
pk = bulk_mode == :update ? [pk] : []
|
505
|
-
ActionController::Parameters.new(data).permit({_json:
|
516
|
+
ActionController::Parameters.new(data).permit({_json: allowed_params + pk})
|
506
517
|
else
|
507
|
-
ActionController::Parameters.new(data).permit(*
|
518
|
+
ActionController::Parameters.new(data).permit(*allowed_params)
|
508
519
|
end
|
509
520
|
|
510
521
|
# Filter primary key if configured.
|
511
|
-
if self.
|
522
|
+
if self.filter_pk_from_request_body && bulk_mode != :update
|
512
523
|
body_params.delete(pk)
|
513
524
|
end
|
514
525
|
|
515
526
|
# Filter fields in `exclude_body_fields`.
|
516
|
-
(self.
|
527
|
+
(self.exclude_body_fields || []).each { |f| body_params.delete(f) }
|
517
528
|
|
518
529
|
# ActiveStorage Integration: Translate base64 encoded attachments to upload objects.
|
519
530
|
#
|
@@ -595,9 +606,9 @@ module RESTFramework::BaseModelControllerMixin
|
|
595
606
|
is_pk = true
|
596
607
|
|
597
608
|
# Find by another column if it's permitted.
|
598
|
-
if find_by_param = self.
|
609
|
+
if find_by_param = self.find_by_query_param.presence
|
599
610
|
if find_by = params[find_by_param].presence
|
600
|
-
find_by_fields = self.
|
611
|
+
find_by_fields = self.find_by_fields&.map(&:to_s)
|
601
612
|
|
602
613
|
if !find_by_fields || find_by.in?(find_by_fields)
|
603
614
|
is_pk = false unless find_by_key == find_by
|
@@ -621,7 +632,7 @@ module RESTFramework::BaseModelControllerMixin
|
|
621
632
|
|
622
633
|
# Determine what collection to call `create` on.
|
623
634
|
def get_create_from
|
624
|
-
if self.
|
635
|
+
if self.create_from_recordset
|
625
636
|
# Create with any properties inherited from the recordset. We exclude any `select` clauses
|
626
637
|
# in case model callbacks need to call `count` on this collection, which typically raises a
|
627
638
|
# SQL `SyntaxError`.
|
@@ -660,13 +671,13 @@ module RESTFramework::ListModelMixin
|
|
660
671
|
records = self.get_records
|
661
672
|
|
662
673
|
# Handle pagination, if enabled.
|
663
|
-
if self.
|
674
|
+
if self.paginator_class
|
664
675
|
# If there is no `max_page_size`, `page_size_query_param` is not `nil`, and the page size is
|
665
676
|
# set to "0", then skip pagination.
|
666
|
-
unless !self.
|
667
|
-
self.
|
668
|
-
params[self.
|
669
|
-
paginator = self.
|
677
|
+
unless !self.max_page_size &&
|
678
|
+
self.page_size_query_param &&
|
679
|
+
params[self.page_size_query_param] == "0"
|
680
|
+
paginator = self.paginator_class.new(data: records, controller: self)
|
670
681
|
page = paginator.get_page
|
671
682
|
serialized_page = self.serialize(page)
|
672
683
|
return paginator.get_paginated_response(serialized_page)
|
@@ -1,2 +1,11 @@
|
|
1
1
|
class RESTFramework::Engine < Rails::Engine
|
2
|
+
initializer "rest_framework.assets" do
|
3
|
+
config.after_initialize do |app|
|
4
|
+
if RESTFramework.config.use_vendored_assets
|
5
|
+
app.config.assets.precompile += RESTFramework::EXTERNAL_ASSETS.keys.map do |name|
|
6
|
+
"rest_framework/#{name}"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
2
11
|
end
|