rest_framework 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +3 -3
- data/VERSION +1 -1
- data/app/views/rest_framework/routes_and_forms/_html_form.html.erb +6 -6
- data/app/views/rest_framework/routes_and_forms/_raw_form.html.erb +1 -1
- data/app/views/rest_framework/routes_and_forms/routes/_route.html.erb +1 -1
- data/lib/rest_framework/errors/{nil_passed_to_api_response_error.rb → nil_passed_to_render_api_error.rb} +3 -3
- data/lib/rest_framework/errors.rb +1 -1
- data/lib/rest_framework/filters/ordering_filter.rb +4 -7
- data/lib/rest_framework/filters/query_filter.rb +1 -4
- data/lib/rest_framework/filters/ransack_filter.rb +4 -4
- data/lib/rest_framework/filters/search_filter.rb +3 -6
- data/lib/rest_framework/mixins/base_controller_mixin.rb +92 -100
- data/lib/rest_framework/mixins/bulk_model_controller_mixin.rb +7 -7
- data/lib/rest_framework/mixins/model_controller_mixin.rb +215 -190
- data/lib/rest_framework/paginators/page_number_paginator.rb +12 -11
- data/lib/rest_framework/routers.rb +11 -1
- data/lib/rest_framework/serializers/native_serializer.rb +10 -10
- data/lib/rest_framework/utils.rb +24 -24
- metadata +3 -3
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.
|
@@ -18,11 +19,6 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
18
19
|
serialize_to_json: true,
|
19
20
|
serialize_to_xml: true,
|
20
21
|
|
21
|
-
# Custom integrations (reduces serializer performance due to method calls).
|
22
|
-
enable_action_text: false,
|
23
|
-
enable_active_storage: false,
|
24
|
-
}
|
25
|
-
RRF_BASE_INSTANCE_CONFIG = {
|
26
22
|
# Options related to pagination.
|
27
23
|
paginator_class: nil,
|
28
24
|
page_size: 20,
|
@@ -33,6 +29,10 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
33
29
|
# Option to disable serializer adapters by default, mainly introduced because Active Model
|
34
30
|
# Serializers will do things like serialize `[]` into `{"":[]}`.
|
35
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,
|
36
36
|
}
|
37
37
|
|
38
38
|
# Default action for API root.
|
@@ -58,78 +58,11 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
58
58
|
)
|
59
59
|
end
|
60
60
|
|
61
|
-
# Collect actions (including extra actions) metadata for this controller.
|
62
|
-
def actions_metadata
|
63
|
-
actions = {}
|
64
|
-
|
65
|
-
# Start with builtin actions.
|
66
|
-
RESTFramework::BUILTIN_ACTIONS.merge(
|
67
|
-
RESTFramework::RRF_BUILTIN_ACTIONS,
|
68
|
-
).each do |action, methods|
|
69
|
-
next unless self.method_defined?(action)
|
70
|
-
|
71
|
-
actions[action] = {
|
72
|
-
path: "", methods: methods, type: :builtin, metadata: {label: self.label_for(action)}
|
73
|
-
}
|
74
|
-
end
|
75
|
-
|
76
|
-
# Add builtin bulk actions.
|
77
|
-
RESTFramework::RRF_BUILTIN_BULK_ACTIONS.each do |action, methods|
|
78
|
-
next unless self.method_defined?(action)
|
79
|
-
|
80
|
-
actions[action] = {
|
81
|
-
path: "", methods: methods, type: :builtin, metadata: {label: self.label_for(action)}
|
82
|
-
}
|
83
|
-
end
|
84
|
-
|
85
|
-
# Add extra actions.
|
86
|
-
if extra_actions = self.try(:extra_actions)
|
87
|
-
actions.merge!(RESTFramework::Utils.parse_extra_actions(extra_actions, controller: self))
|
88
|
-
end
|
89
|
-
|
90
|
-
return actions
|
91
|
-
end
|
92
|
-
|
93
|
-
# Collect member actions (including extra member actions) metadata for this controller.
|
94
|
-
def member_actions_metadata
|
95
|
-
actions = {}
|
96
|
-
|
97
|
-
# Start with builtin actions.
|
98
|
-
RESTFramework::BUILTIN_MEMBER_ACTIONS.each do |action, methods|
|
99
|
-
next unless self.method_defined?(action)
|
100
|
-
|
101
|
-
actions[action] = {
|
102
|
-
path: "", methods: methods, type: :builtin, metadata: {label: self.label_for(action)}
|
103
|
-
}
|
104
|
-
end
|
105
|
-
|
106
|
-
# Add extra actions.
|
107
|
-
if extra_actions = self.try(:extra_member_actions)
|
108
|
-
actions.merge!(RESTFramework::Utils.parse_extra_actions(extra_actions, controller: self))
|
109
|
-
end
|
110
|
-
|
111
|
-
return actions
|
112
|
-
end
|
113
|
-
|
114
|
-
def options_metadata
|
115
|
-
return {
|
116
|
-
title: self.get_title,
|
117
|
-
description: self.description,
|
118
|
-
renders: [
|
119
|
-
"text/html",
|
120
|
-
self.serialize_to_json ? "application/json" : nil,
|
121
|
-
self.serialize_to_xml ? "application/xml" : nil,
|
122
|
-
].compact,
|
123
|
-
actions: self.actions_metadata,
|
124
|
-
member_actions: self.member_actions_metadata,
|
125
|
-
}.compact
|
126
|
-
end
|
127
|
-
|
128
61
|
# Define any behavior to execute at the end of controller definition.
|
129
62
|
# :nocov:
|
130
63
|
def rrf_finalize
|
131
64
|
if RESTFramework.config.freeze_config
|
132
|
-
|
65
|
+
self::RRF_BASE_CONFIG.keys.each { |k|
|
133
66
|
v = self.send(k)
|
134
67
|
v.freeze if v.is_a?(Hash) || v.is_a?(Array)
|
135
68
|
}
|
@@ -146,17 +79,13 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
146
79
|
# By default, the layout should be set to `rest_framework`.
|
147
80
|
base.layout("rest_framework")
|
148
81
|
|
149
|
-
# Add class attributes
|
82
|
+
# Add class attributes unless they already exist.
|
150
83
|
RRF_BASE_CONFIG.each do |a, default|
|
151
84
|
next if base.respond_to?(a)
|
152
85
|
|
86
|
+
# Don't leak class attributes to the instance to avoid conflicting with action methods.
|
153
87
|
base.class_attribute(a, default: default, instance_accessor: false)
|
154
88
|
end
|
155
|
-
RRF_BASE_INSTANCE_CONFIG.each do |a, default|
|
156
|
-
next if base.respond_to?(a)
|
157
|
-
|
158
|
-
base.class_attribute(a, default: default)
|
159
|
-
end
|
160
89
|
|
161
90
|
# Alias `extra_actions` to `extra_collection_actions`.
|
162
91
|
unless base.respond_to?(:extra_collection_actions)
|
@@ -204,26 +133,17 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
204
133
|
end
|
205
134
|
end
|
206
135
|
|
207
|
-
def
|
208
|
-
# TODO: Compatibility; remove in 1.0.
|
209
|
-
if klass = self.try(:get_serializer_class)
|
210
|
-
return klass
|
211
|
-
end
|
212
|
-
|
136
|
+
def get_serializer_class
|
213
137
|
return self.class.serializer_class
|
214
138
|
end
|
215
139
|
|
216
140
|
# Serialize the given data using the `serializer_class`.
|
217
141
|
def serialize(data, **kwargs)
|
218
|
-
return RESTFramework::Utils.wrap_ams(self.
|
142
|
+
return RESTFramework::Utils.wrap_ams(self.get_serializer_class).new(
|
219
143
|
data, controller: self, **kwargs
|
220
144
|
).serialize
|
221
145
|
end
|
222
146
|
|
223
|
-
def options_metadata
|
224
|
-
return self.class.options_metadata
|
225
|
-
end
|
226
|
-
|
227
147
|
def rrf_error_handler(e)
|
228
148
|
status = case e
|
229
149
|
when ActiveRecord::RecordNotFound
|
@@ -232,7 +152,7 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
232
152
|
400
|
233
153
|
end
|
234
154
|
|
235
|
-
|
155
|
+
render_api(
|
236
156
|
{
|
237
157
|
message: e.message,
|
238
158
|
errors: e.try(:record).try(:errors),
|
@@ -242,19 +162,23 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
242
162
|
)
|
243
163
|
end
|
244
164
|
|
165
|
+
def route_groups
|
166
|
+
return @route_groups ||= RESTFramework::Utils.get_routes(Rails.application.routes, request)
|
167
|
+
end
|
168
|
+
|
245
169
|
# Render a browsable API for `html` format, along with basic `json`/`xml` formats, and with
|
246
170
|
# support or passing custom `kwargs` to the underlying `render` calls.
|
247
|
-
def
|
171
|
+
def render_api(payload, **kwargs)
|
248
172
|
html_kwargs = kwargs.delete(:html_kwargs) || {}
|
249
173
|
json_kwargs = kwargs.delete(:json_kwargs) || {}
|
250
174
|
xml_kwargs = kwargs.delete(:xml_kwargs) || {}
|
251
175
|
|
252
176
|
# Raise helpful error if payload is nil. Usually this happens when a record is not found (e.g.,
|
253
|
-
# 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
|
254
178
|
# actually be calling `find_by!` to raise ActiveRecord::RecordNotFound and allowing the REST
|
255
179
|
# framework to catch this error and return an appropriate error response.
|
256
180
|
if payload.nil?
|
257
|
-
raise RESTFramework::
|
181
|
+
raise RESTFramework::NilPassedToRenderAPIError
|
258
182
|
end
|
259
183
|
|
260
184
|
# If `payload` is an `ActiveRecord::Relation` or `ActiveRecord::Base`, then serialize it.
|
@@ -263,7 +187,7 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
263
187
|
end
|
264
188
|
|
265
189
|
# Do not use any adapters by default, if configured.
|
266
|
-
if self.disable_adapters_by_default && !kwargs.key?(:adapter)
|
190
|
+
if self.class.disable_adapters_by_default && !kwargs.key?(:adapter)
|
267
191
|
kwargs[:adapter] = nil
|
268
192
|
end
|
269
193
|
|
@@ -295,9 +219,7 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
295
219
|
end
|
296
220
|
@title ||= self.class.get_title
|
297
221
|
@description ||= self.class.description
|
298
|
-
|
299
|
-
Rails.application.routes, request
|
300
|
-
)
|
222
|
+
self.route_groups
|
301
223
|
begin
|
302
224
|
render(**kwargs.merge(html_kwargs))
|
303
225
|
rescue ActionView::MissingTemplate
|
@@ -318,10 +240,80 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
318
240
|
end
|
319
241
|
|
320
242
|
# TODO: Might make this the default render method in the future.
|
321
|
-
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
|
322
314
|
|
323
315
|
def options
|
324
|
-
|
316
|
+
render_api(self.openapi_metadata)
|
325
317
|
end
|
326
318
|
end
|
327
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
|
)
|