rest_framework 0.9.16 → 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 +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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 34653421f170160fbdd29c3119c728ba5fe7591fd969f9d50d2e20d18cecf8bb
|
4
|
+
data.tar.gz: ed35098916c941a897087d9b455f69e86f5f53b1e81442e22aa135c81877c45c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b02af4cc8f15d804a8ece266a3678a5a0ae328cf505aa64478f4239392a2ccc1ddbcbdde333fc8d80c9ba8fbb59d2a057c8fed6150fe0758f068c7c3dca3670c
|
7
|
+
data.tar.gz: 33d87fa722fa5d0a23ccfd81ea6d5705cc37b49bb9b88ab62f504c76255429c77d0c75b2d83a8f86c3752c6fc7c9a60f3fa9078daebfca5461929d052f43d45f
|
data/README.md
CHANGED
@@ -75,7 +75,7 @@ class Api::RootController < ApiController
|
|
75
75
|
self.extra_actions = {test: :get}
|
76
76
|
|
77
77
|
def root
|
78
|
-
|
78
|
+
render_api(
|
79
79
|
{
|
80
80
|
message: "Welcome to the API.",
|
81
81
|
how_to_authenticate: <<~END.lines.map(&:strip).join(" "),
|
@@ -88,7 +88,7 @@ class Api::RootController < ApiController
|
|
88
88
|
end
|
89
89
|
|
90
90
|
def test
|
91
|
-
|
91
|
+
render_api({message: "Hello, world!"})
|
92
92
|
end
|
93
93
|
end
|
94
94
|
```
|
@@ -105,7 +105,7 @@ class Api::MoviesController < ApiController
|
|
105
105
|
def first
|
106
106
|
# Always use the bang method, since the framework will rescue `RecordNotFound` and return a
|
107
107
|
# sensible error response.
|
108
|
-
|
108
|
+
render_api(self.get_records.first!)
|
109
109
|
end
|
110
110
|
|
111
111
|
def get_recordset
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.11.0
|
@@ -3,7 +3,7 @@
|
|
3
3
|
<label class="form-label w-100">Route
|
4
4
|
<select class="form-control form-control-sm" id="htmlFormRoute">
|
5
5
|
<% @_rrf_form_routes_html.each do |route| %>
|
6
|
-
<% path =
|
6
|
+
<% path = route[:path_with_params] %>
|
7
7
|
<option value="<%= route[:verb] %>:<%= path %>"><%= route[:verb] %> <%= route[:relative_path] %></option>
|
8
8
|
<% end %>
|
9
9
|
</select>
|
@@ -21,16 +21,16 @@
|
|
21
21
|
<% controller.get_fields.map(&:to_s).each do |f| %>
|
22
22
|
<%
|
23
23
|
# Don't provide form fields for associations or primary keys.
|
24
|
-
|
25
|
-
next if !
|
24
|
+
cfg = controller.class.field_configuration[f]
|
25
|
+
next if !cfg || cfg[:kind] == "association" || cfg[:readonly]
|
26
26
|
%>
|
27
27
|
<div class="mb-2">
|
28
|
-
<% if
|
28
|
+
<% if cfg[:kind] == "rich_text" %>
|
29
29
|
<label class="form-label w-100"><%= controller.class.label_for(f) %></label>
|
30
30
|
<%= form.rich_text_area f %>
|
31
|
-
<% elsif
|
31
|
+
<% elsif cfg[:kind] == "attachment" %>
|
32
32
|
<label class="form-label w-100"><%= controller.class.label_for(f) %>
|
33
|
-
<%= form.file_field f, multiple:
|
33
|
+
<%= form.file_field f, multiple: cfg[:attachment_type] == :has_many_attached %>
|
34
34
|
</label>
|
35
35
|
<% else %>
|
36
36
|
<label class="form-label w-100"><%= controller.class.label_for(f) %>
|
@@ -3,7 +3,7 @@
|
|
3
3
|
<label class="form-label w-100">Route
|
4
4
|
<select class="form-control form-control-sm" id="rawFormRoute">
|
5
5
|
<% @_rrf_form_routes_raw.each do |route| %>
|
6
|
-
<% path =
|
6
|
+
<% path = route[:path_with_params] %>
|
7
7
|
<option
|
8
8
|
value="<%= route[:verb] %>:<%= path %>"
|
9
9
|
data-supports-files="<%= !route[:action].in?(["update_all", "destroy", "destroy_all"]) ? "true" : "" %>"
|
@@ -1,7 +1,7 @@
|
|
1
1
|
<tr>
|
2
2
|
<td>
|
3
3
|
<% if route[:verb] == "GET" && route[:matches_params] %>
|
4
|
-
<%= link_to route[:relative_path],
|
4
|
+
<%= link_to route[:relative_path], route[:path_with_params] %>
|
5
5
|
<% else %>
|
6
6
|
<%= route[:relative_path] %>
|
7
7
|
<% end %>
|
@@ -1,7 +1,7 @@
|
|
1
|
-
class RESTFramework::Errors::
|
1
|
+
class RESTFramework::Errors::NilPassedToRenderAPIError < RESTFramework::Errors::BaseError
|
2
2
|
def message
|
3
3
|
return <<~MSG.split("\n").join(" ")
|
4
|
-
Payload of `nil` was passed to `
|
4
|
+
Payload of `nil` was passed to `render_api`; this is unsupported. If you want a blank
|
5
5
|
response, pass `''` (an empty string) as the payload. If this was the result of a `find_by`
|
6
6
|
(or similar Active Record method) not finding a record, you should use the bang version (e.g.,
|
7
7
|
`find_by!`) to raise `ActiveRecord::RecordNotFound`, which the REST controller will catch and
|
@@ -11,4 +11,4 @@ class RESTFramework::Errors::NilPassedToAPIResponseError < RESTFramework::Errors
|
|
11
11
|
end
|
12
12
|
|
13
13
|
# Alias for convenience.
|
14
|
-
RESTFramework::
|
14
|
+
RESTFramework::NilPassedToRenderAPIError = RESTFramework::Errors::NilPassedToRenderAPIError
|
@@ -2,16 +2,16 @@
|
|
2
2
|
class RESTFramework::Filters::OrderingFilter < RESTFramework::Filters::BaseFilter
|
3
3
|
# Get a list of ordering fields for the current action.
|
4
4
|
def _get_fields
|
5
|
-
return @controller.ordering_fields&.map(&:to_s) || @controller.get_fields
|
5
|
+
return @controller.class.ordering_fields&.map(&:to_s) || @controller.get_fields
|
6
6
|
end
|
7
7
|
|
8
8
|
# Convert ordering string to an ordering configuration.
|
9
9
|
def _get_ordering
|
10
|
-
return nil
|
10
|
+
return nil unless param = @controller.class.ordering_query_param.presence
|
11
11
|
|
12
12
|
# Ensure ordering_fields are strings since the split param will be strings.
|
13
13
|
fields = self._get_fields
|
14
|
-
order_string = @controller.params[
|
14
|
+
order_string = @controller.params[param]
|
15
15
|
|
16
16
|
if order_string.present?
|
17
17
|
ordering = {}.with_indifferent_access
|
@@ -40,7 +40,7 @@ class RESTFramework::Filters::OrderingFilter < RESTFramework::Filters::BaseFilte
|
|
40
40
|
# Order data according to the request query parameters.
|
41
41
|
def filter_data(data)
|
42
42
|
ordering = self._get_ordering
|
43
|
-
reorder = !@controller.ordering_no_reorder
|
43
|
+
reorder = !@controller.class.ordering_no_reorder
|
44
44
|
|
45
45
|
if ordering && !ordering.empty?
|
46
46
|
return data.send(reorder ? :reorder : :order, ordering)
|
@@ -52,6 +52,3 @@ end
|
|
52
52
|
|
53
53
|
# Alias for convenience.
|
54
54
|
RESTFramework::OrderingFilter = RESTFramework::Filters::OrderingFilter
|
55
|
-
|
56
|
-
# TODO: Compatibility; remove in 1.0.
|
57
|
-
RESTFramework::ModelOrderingFilter = RESTFramework::Filters::OrderingFilter
|
@@ -22,7 +22,7 @@ class RESTFramework::Filters::QueryFilter < RESTFramework::Filters::BaseFilter
|
|
22
22
|
field, sub_field = match[1..2]
|
23
23
|
next false unless field.in?(fields)
|
24
24
|
|
25
|
-
sub_fields = @controller.class.
|
25
|
+
sub_fields = @controller.class.field_configuration[field][:sub_fields] || []
|
26
26
|
if sub_field.in?(sub_fields)
|
27
27
|
includes << field.to_sym
|
28
28
|
next true
|
@@ -66,6 +66,3 @@ end
|
|
66
66
|
|
67
67
|
# Alias for convenience.
|
68
68
|
RESTFramework::QueryFilter = RESTFramework::Filters::QueryFilter
|
69
|
-
|
70
|
-
# TODO: Compatibility; remove in 1.0.
|
71
|
-
RESTFramework::ModelQueryFilter = RESTFramework::Filters::QueryFilter
|
@@ -2,13 +2,13 @@
|
|
2
2
|
class RESTFramework::Filters::RansackFilter < RESTFramework::Filters::BaseFilter
|
3
3
|
# Filter data according to the request query parameters.
|
4
4
|
def filter_data(data)
|
5
|
-
q = @controller.request.query_parameters[@controller.ransack_query_param]
|
5
|
+
q = @controller.request.query_parameters[@controller.class.ransack_query_param]
|
6
6
|
|
7
7
|
if q.present?
|
8
|
-
distinct = @controller.ransack_distinct
|
8
|
+
distinct = @controller.class.ransack_distinct
|
9
9
|
|
10
10
|
# Determine if `distinct` is determined by query param.
|
11
|
-
if distinct_query_param = @controller.ransack_distinct_query_param
|
11
|
+
if distinct_query_param = @controller.class.ransack_distinct_query_param
|
12
12
|
if distinct_query = @controller.request.query_parameters[distinct_query_param].presence
|
13
13
|
distinct_from_query = ActiveRecord::Type::Boolean.new.cast(distinct_query)
|
14
14
|
unless distinct_from_query.nil?
|
@@ -17,7 +17,7 @@ class RESTFramework::Filters::RansackFilter < RESTFramework::Filters::BaseFilter
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
-
return data.ransack(q, @controller.ransack_options || {}).result(distinct: distinct)
|
20
|
+
return data.ransack(q, @controller.class.ransack_options || {}).result(distinct: distinct)
|
21
21
|
end
|
22
22
|
|
23
23
|
return data
|
@@ -1,7 +1,7 @@
|
|
1
1
|
class RESTFramework::Filters::SearchFilter < RESTFramework::Filters::BaseFilter
|
2
2
|
# Get a list of search fields for the current action.
|
3
3
|
def _get_fields
|
4
|
-
if search_fields = @controller.search_fields
|
4
|
+
if search_fields = @controller.class.search_fields
|
5
5
|
return search_fields&.map(&:to_s)
|
6
6
|
end
|
7
7
|
|
@@ -13,7 +13,7 @@ class RESTFramework::Filters::SearchFilter < RESTFramework::Filters::BaseFilter
|
|
13
13
|
|
14
14
|
# Filter data according to the request query parameters.
|
15
15
|
def filter_data(data)
|
16
|
-
search = @controller.request.query_parameters[@controller.search_query_param]
|
16
|
+
search = @controller.request.query_parameters[@controller.class.search_query_param]
|
17
17
|
|
18
18
|
if search.present?
|
19
19
|
if fields = self._get_fields.presence
|
@@ -28,7 +28,7 @@ class RESTFramework::Filters::SearchFilter < RESTFramework::Filters::BaseFilter
|
|
28
28
|
# Ensure we pass user input as arguments to prevent SQL injection.
|
29
29
|
return data.where(
|
30
30
|
fields.map { |f|
|
31
|
-
"CAST(#{f} AS #{data_type}) #{@controller.search_ilike ? "ILIKE" : "LIKE"} ?"
|
31
|
+
"CAST(#{f} AS #{data_type}) #{@controller.class.search_ilike ? "ILIKE" : "LIKE"} ?"
|
32
32
|
}.join(" OR "),
|
33
33
|
*(["%#{search}%"] * fields.length),
|
34
34
|
)
|
@@ -41,6 +41,3 @@ end
|
|
41
41
|
|
42
42
|
# Alias for convenience.
|
43
43
|
RESTFramework::SearchFilter = RESTFramework::Filters::SearchFilter
|
44
|
-
|
45
|
-
# TODO: Compatibility; remove in 1.0.
|
46
|
-
RESTFramework::ModelSearchFilter = RESTFramework::Filters::SearchFilter
|
@@ -10,6 +10,7 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
10
10
|
# Options related to metadata and display.
|
11
11
|
title: nil,
|
12
12
|
description: nil,
|
13
|
+
version: nil,
|
13
14
|
inflect_acronyms: ["ID", "IDs", "REST", "API", "APIs"].freeze,
|
14
15
|
|
15
16
|
# Options related to serialization.
|
@@ -17,8 +18,7 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
17
18
|
serializer_class: nil,
|
18
19
|
serialize_to_json: true,
|
19
20
|
serialize_to_xml: true,
|
20
|
-
|
21
|
-
RRF_BASE_INSTANCE_CONFIG = {
|
21
|
+
|
22
22
|
# Options related to pagination.
|
23
23
|
paginator_class: nil,
|
24
24
|
page_size: 20,
|
@@ -29,6 +29,10 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
29
29
|
# Option to disable serializer adapters by default, mainly introduced because Active Model
|
30
30
|
# Serializers will do things like serialize `[]` into `{"":[]}`.
|
31
31
|
disable_adapters_by_default: true,
|
32
|
+
|
33
|
+
# Custom integrations (reduces serializer performance due to method calls).
|
34
|
+
enable_action_text: false,
|
35
|
+
enable_active_storage: false,
|
32
36
|
}
|
33
37
|
|
34
38
|
# Default action for API root.
|
@@ -54,78 +58,11 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
54
58
|
)
|
55
59
|
end
|
56
60
|
|
57
|
-
# Collect actions (including extra actions) metadata for this controller.
|
58
|
-
def actions_metadata
|
59
|
-
actions = {}
|
60
|
-
|
61
|
-
# Start with builtin actions.
|
62
|
-
RESTFramework::BUILTIN_ACTIONS.merge(
|
63
|
-
RESTFramework::RRF_BUILTIN_ACTIONS,
|
64
|
-
).each do |action, methods|
|
65
|
-
next unless self.method_defined?(action)
|
66
|
-
|
67
|
-
actions[action] = {
|
68
|
-
path: "", methods: methods, type: :builtin, metadata: {label: self.label_for(action)}
|
69
|
-
}
|
70
|
-
end
|
71
|
-
|
72
|
-
# Add builtin bulk actions.
|
73
|
-
RESTFramework::RRF_BUILTIN_BULK_ACTIONS.each do |action, methods|
|
74
|
-
next unless self.method_defined?(action)
|
75
|
-
|
76
|
-
actions[action] = {
|
77
|
-
path: "", methods: methods, type: :builtin, metadata: {label: self.label_for(action)}
|
78
|
-
}
|
79
|
-
end
|
80
|
-
|
81
|
-
# Add extra actions.
|
82
|
-
if extra_actions = self.try(:extra_actions)
|
83
|
-
actions.merge!(RESTFramework::Utils.parse_extra_actions(extra_actions, controller: self))
|
84
|
-
end
|
85
|
-
|
86
|
-
return actions
|
87
|
-
end
|
88
|
-
|
89
|
-
# Collect member actions (including extra member actions) metadata for this controller.
|
90
|
-
def member_actions_metadata
|
91
|
-
actions = {}
|
92
|
-
|
93
|
-
# Start with builtin actions.
|
94
|
-
RESTFramework::BUILTIN_MEMBER_ACTIONS.each do |action, methods|
|
95
|
-
next unless self.method_defined?(action)
|
96
|
-
|
97
|
-
actions[action] = {
|
98
|
-
path: "", methods: methods, type: :builtin, metadata: {label: self.label_for(action)}
|
99
|
-
}
|
100
|
-
end
|
101
|
-
|
102
|
-
# Add extra actions.
|
103
|
-
if extra_actions = self.try(:extra_member_actions)
|
104
|
-
actions.merge!(RESTFramework::Utils.parse_extra_actions(extra_actions, controller: self))
|
105
|
-
end
|
106
|
-
|
107
|
-
return actions
|
108
|
-
end
|
109
|
-
|
110
|
-
def options_metadata
|
111
|
-
return {
|
112
|
-
title: self.get_title,
|
113
|
-
description: self.description,
|
114
|
-
renders: [
|
115
|
-
"text/html",
|
116
|
-
self.serialize_to_json ? "application/json" : nil,
|
117
|
-
self.serialize_to_xml ? "application/xml" : nil,
|
118
|
-
].compact,
|
119
|
-
actions: self.actions_metadata,
|
120
|
-
member_actions: self.member_actions_metadata,
|
121
|
-
}.compact
|
122
|
-
end
|
123
|
-
|
124
61
|
# Define any behavior to execute at the end of controller definition.
|
125
62
|
# :nocov:
|
126
63
|
def rrf_finalize
|
127
64
|
if RESTFramework.config.freeze_config
|
128
|
-
|
65
|
+
self::RRF_BASE_CONFIG.keys.each { |k|
|
129
66
|
v = self.send(k)
|
130
67
|
v.freeze if v.is_a?(Hash) || v.is_a?(Array)
|
131
68
|
}
|
@@ -142,17 +79,13 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
142
79
|
# By default, the layout should be set to `rest_framework`.
|
143
80
|
base.layout("rest_framework")
|
144
81
|
|
145
|
-
# Add class attributes
|
82
|
+
# Add class attributes unless they already exist.
|
146
83
|
RRF_BASE_CONFIG.each do |a, default|
|
147
84
|
next if base.respond_to?(a)
|
148
85
|
|
86
|
+
# Don't leak class attributes to the instance to avoid conflicting with action methods.
|
149
87
|
base.class_attribute(a, default: default, instance_accessor: false)
|
150
88
|
end
|
151
|
-
RRF_BASE_INSTANCE_CONFIG.each do |a, default|
|
152
|
-
next if base.respond_to?(a)
|
153
|
-
|
154
|
-
base.class_attribute(a, default: default)
|
155
|
-
end
|
156
89
|
|
157
90
|
# Alias `extra_actions` to `extra_collection_actions`.
|
158
91
|
unless base.respond_to?(:extra_collection_actions)
|
@@ -200,26 +133,17 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
200
133
|
end
|
201
134
|
end
|
202
135
|
|
203
|
-
def
|
204
|
-
# TODO: Compatibility; remove in 1.0.
|
205
|
-
if klass = self.try(:get_serializer_class)
|
206
|
-
return klass
|
207
|
-
end
|
208
|
-
|
136
|
+
def get_serializer_class
|
209
137
|
return self.class.serializer_class
|
210
138
|
end
|
211
139
|
|
212
140
|
# Serialize the given data using the `serializer_class`.
|
213
141
|
def serialize(data, **kwargs)
|
214
|
-
return RESTFramework::Utils.wrap_ams(self.
|
142
|
+
return RESTFramework::Utils.wrap_ams(self.get_serializer_class).new(
|
215
143
|
data, controller: self, **kwargs
|
216
144
|
).serialize
|
217
145
|
end
|
218
146
|
|
219
|
-
def options_metadata
|
220
|
-
return self.class.options_metadata
|
221
|
-
end
|
222
|
-
|
223
147
|
def rrf_error_handler(e)
|
224
148
|
status = case e
|
225
149
|
when ActiveRecord::RecordNotFound
|
@@ -228,7 +152,7 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
228
152
|
400
|
229
153
|
end
|
230
154
|
|
231
|
-
|
155
|
+
render_api(
|
232
156
|
{
|
233
157
|
message: e.message,
|
234
158
|
errors: e.try(:record).try(:errors),
|
@@ -238,19 +162,23 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
238
162
|
)
|
239
163
|
end
|
240
164
|
|
165
|
+
def route_groups
|
166
|
+
return @route_groups ||= RESTFramework::Utils.get_routes(Rails.application.routes, request)
|
167
|
+
end
|
168
|
+
|
241
169
|
# Render a browsable API for `html` format, along with basic `json`/`xml` formats, and with
|
242
170
|
# support or passing custom `kwargs` to the underlying `render` calls.
|
243
|
-
def
|
171
|
+
def render_api(payload, **kwargs)
|
244
172
|
html_kwargs = kwargs.delete(:html_kwargs) || {}
|
245
173
|
json_kwargs = kwargs.delete(:json_kwargs) || {}
|
246
174
|
xml_kwargs = kwargs.delete(:xml_kwargs) || {}
|
247
175
|
|
248
176
|
# Raise helpful error if payload is nil. Usually this happens when a record is not found (e.g.,
|
249
|
-
# when passing something like `User.find_by(id: some_id)` to `
|
177
|
+
# when passing something like `User.find_by(id: some_id)` to `render_api`). The caller should
|
250
178
|
# actually be calling `find_by!` to raise ActiveRecord::RecordNotFound and allowing the REST
|
251
179
|
# framework to catch this error and return an appropriate error response.
|
252
180
|
if payload.nil?
|
253
|
-
raise RESTFramework::
|
181
|
+
raise RESTFramework::NilPassedToRenderAPIError
|
254
182
|
end
|
255
183
|
|
256
184
|
# If `payload` is an `ActiveRecord::Relation` or `ActiveRecord::Base`, then serialize it.
|
@@ -259,7 +187,7 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
259
187
|
end
|
260
188
|
|
261
189
|
# Do not use any adapters by default, if configured.
|
262
|
-
if self.disable_adapters_by_default && !kwargs.key?(:adapter)
|
190
|
+
if self.class.disable_adapters_by_default && !kwargs.key?(:adapter)
|
263
191
|
kwargs[:adapter] = nil
|
264
192
|
end
|
265
193
|
|
@@ -291,9 +219,7 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
291
219
|
end
|
292
220
|
@title ||= self.class.get_title
|
293
221
|
@description ||= self.class.description
|
294
|
-
|
295
|
-
Rails.application.routes, request
|
296
|
-
)
|
222
|
+
self.route_groups
|
297
223
|
begin
|
298
224
|
render(**kwargs.merge(html_kwargs))
|
299
225
|
rescue ActionView::MissingTemplate
|
@@ -314,10 +240,80 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
314
240
|
end
|
315
241
|
|
316
242
|
# TODO: Might make this the default render method in the future.
|
317
|
-
alias_method :
|
243
|
+
alias_method :api_response, :render_api
|
244
|
+
|
245
|
+
def openapi_metadata
|
246
|
+
response_content_types = [
|
247
|
+
"text/html",
|
248
|
+
self.class.serialize_to_json ? "application/json" : nil,
|
249
|
+
self.class.serialize_to_xml ? "application/xml" : nil,
|
250
|
+
].compact
|
251
|
+
request_content_types = [
|
252
|
+
"application/json",
|
253
|
+
"application/xml",
|
254
|
+
"application/x-www-form-urlencoded",
|
255
|
+
"multipart/form-data",
|
256
|
+
].compact
|
257
|
+
routes = self.route_groups.values[0]
|
258
|
+
server = request.base_url + request.original_fullpath.gsub(/\?.*/, "")
|
259
|
+
|
260
|
+
return {
|
261
|
+
openapi: "3.1.1",
|
262
|
+
info: {
|
263
|
+
title: self.class.get_title,
|
264
|
+
description: self.class.description,
|
265
|
+
version: self.class.version.to_s,
|
266
|
+
}.compact,
|
267
|
+
servers: [{url: server}],
|
268
|
+
paths: routes.group_by { |r| r[:concat_path] }.map { |concat_path, routes|
|
269
|
+
[
|
270
|
+
concat_path.gsub(/:([0-9A-Za-z_-]+)/, "{\\1}"),
|
271
|
+
routes.map { |route|
|
272
|
+
metadata = route[:route].defaults[:metadata] || {}
|
273
|
+
summary = metadata[:label].presence || self.class.label_for(route[:action])
|
274
|
+
description = metadata[:description].presence
|
275
|
+
remaining_metadata = metadata.except(:label, :description).presence
|
276
|
+
|
277
|
+
[
|
278
|
+
route[:verb].downcase,
|
279
|
+
{
|
280
|
+
summary: summary,
|
281
|
+
description: description,
|
282
|
+
responses: {
|
283
|
+
200 => {
|
284
|
+
content: response_content_types.map { |ct|
|
285
|
+
[ct, {}]
|
286
|
+
}.to_h,
|
287
|
+
description: "",
|
288
|
+
},
|
289
|
+
},
|
290
|
+
requestBody: route[:verb].in?(["GET", "DELETE", "OPTIONS", "TRACE"]) ? nil : {
|
291
|
+
content: request_content_types.map { |ct|
|
292
|
+
[ct, {}]
|
293
|
+
}.to_h,
|
294
|
+
},
|
295
|
+
"x-rrf-metadata": remaining_metadata,
|
296
|
+
}.compact,
|
297
|
+
]
|
298
|
+
}.to_h.merge(
|
299
|
+
{
|
300
|
+
parameters: routes.first[:route].required_parts.map { |p|
|
301
|
+
{
|
302
|
+
name: p,
|
303
|
+
in: "path",
|
304
|
+
required: true,
|
305
|
+
schema: {type: "integer"},
|
306
|
+
}
|
307
|
+
},
|
308
|
+
},
|
309
|
+
),
|
310
|
+
]
|
311
|
+
}.to_h,
|
312
|
+
}.compact
|
313
|
+
end
|
318
314
|
|
319
315
|
def options
|
320
|
-
|
316
|
+
render_api(self.openapi_metadata)
|
321
317
|
end
|
322
318
|
end
|
323
319
|
|
@@ -4,17 +4,17 @@ require_relative "model_controller_mixin"
|
|
4
4
|
# the existing `create` action to support bulk creation.
|
5
5
|
module RESTFramework::Mixins::BulkCreateModelMixin
|
6
6
|
# While bulk update/destroy are obvious because they create new router endpoints, bulk create
|
7
|
-
# overloads the existing collection `POST` endpoint, so we add a special key to the
|
7
|
+
# overloads the existing collection `POST` endpoint, so we add a special key to the OpenAPI
|
8
8
|
# metadata to indicate bulk create is supported.
|
9
|
-
def
|
10
|
-
return super.merge({
|
9
|
+
def openapi_metadata
|
10
|
+
return super.merge({"x-rrf-bulk-create": true})
|
11
11
|
end
|
12
12
|
|
13
13
|
def create
|
14
14
|
if params[:_json].is_a?(Array)
|
15
15
|
records = self.create_all!
|
16
16
|
serialized_records = self.bulk_serialize(records)
|
17
|
-
return
|
17
|
+
return render_api(serialized_records)
|
18
18
|
end
|
19
19
|
|
20
20
|
return super
|
@@ -36,7 +36,7 @@ module RESTFramework::Mixins::BulkUpdateModelMixin
|
|
36
36
|
def update_all
|
37
37
|
records = self.update_all!
|
38
38
|
serialized_records = self.bulk_serialize(records)
|
39
|
-
|
39
|
+
render_api(serialized_records)
|
40
40
|
end
|
41
41
|
|
42
42
|
# Perform the `update` call and return the collection of (possibly) updated records.
|
@@ -62,10 +62,10 @@ module RESTFramework::Mixins::BulkDestroyModelMixin
|
|
62
62
|
if params[:_json].is_a?(Array)
|
63
63
|
records = self.destroy_all!
|
64
64
|
serialized_records = self.bulk_serialize(records)
|
65
|
-
return
|
65
|
+
return render_api(serialized_records)
|
66
66
|
end
|
67
67
|
|
68
|
-
|
68
|
+
render_api(
|
69
69
|
{message: "Bulk destroy requires an array of primary keys as input."},
|
70
70
|
status: 400,
|
71
71
|
)
|