rest_framework 1.0.0.rc1 → 1.0.1
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 +6 -2
- data/VERSION +1 -1
- data/app/views/rest_framework/_head.html.erb +3 -1
- data/app/views/rest_framework/_heading.html.erb +1 -0
- data/app/views/rest_framework/head/_extra.html.erb +0 -0
- data/app/views/rest_framework/heading/_actions.html.erb +2 -1
- data/app/views/rest_framework/heading/_extra.html.erb +0 -0
- data/app/views/rest_framework/heading/actions/_extra.html.erb +0 -0
- data/app/views/rest_framework/routes_and_forms/_html_form.html.erb +2 -2
- data/lib/rest_framework/errors/nil_passed_to_render_api_error.rb +1 -1
- data/lib/rest_framework/errors/unknown_model_error.rb +1 -1
- data/lib/rest_framework/filters/ordering_filter.rb +3 -3
- data/lib/rest_framework/filters/query_filter.rb +14 -14
- data/lib/rest_framework/filters/ransack_filter.rb +1 -1
- data/lib/rest_framework/filters/search_filter.rb +3 -3
- data/lib/rest_framework/generators/controller_generator.rb +2 -2
- data/lib/rest_framework/mixins/base_controller_mixin.rb +34 -33
- data/lib/rest_framework/mixins/bulk_model_controller_mixin.rb +8 -15
- data/lib/rest_framework/mixins/model_controller_mixin.rb +91 -65
- data/lib/rest_framework/paginators/page_number_paginator.rb +6 -6
- data/lib/rest_framework/routers.rb +10 -16
- data/lib/rest_framework/serializers/active_model_serializer_adapter_factory.rb +4 -2
- data/lib/rest_framework/serializers/base_serializer.rb +7 -5
- data/lib/rest_framework/serializers/native_serializer.rb +82 -129
- data/lib/rest_framework/utils.rb +27 -26
- data/lib/rest_framework/version.rb +1 -1
- data/lib/rest_framework.rb +24 -17
- data/vendor/assets/javascripts/rest_framework/external.min.js +4 -4
- data/vendor/assets/stylesheets/rest_framework/external.min.css +3 -3
- metadata +8 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '087da1898a00546ec0964c7f16a31ccdfdfefe6574e614764a9a3fe50dbbd89b'
|
|
4
|
+
data.tar.gz: a9720b7867fb57f681d786080d570161ff4ebb653015d429b331397a9962d95d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cd6d3507ce2a40c4dffa147b5bd381ff5ae4b9dedc0f42d879ef96c66c9f67b07abb7de1005fe46fdaa8412aedd1d0c2c5332c17a11c407a577997b70dc3455f
|
|
7
|
+
data.tar.gz: d2d76d723db9f61c731aeb58d9cfd0c5c02c73bc7c75406ae835c4a24fc75f0fdfc9df24083313b2dc792b1dc7fe86eb4d8bdc32f6c15dfd60a5603ee7114cea
|
data/README.md
CHANGED
|
@@ -152,5 +152,9 @@ After you clone the repository, cd'ing into the directory should create a new ge
|
|
|
152
152
|
using RVM. Then run `bin/setup` to install the appropriate gems and set things up.
|
|
153
153
|
|
|
154
154
|
The top-level `bin/rails` proxies all Rails commands to the test project, so you can operate it via
|
|
155
|
-
the usual commands (e.g., `rails test`, `rails
|
|
156
|
-
|
|
155
|
+
the usual commands (e.g., `rails test`, `rails console`). For development, use `bin/dev` to run the
|
|
156
|
+
web server and the job queue, which serves the test app and coverage/brakeman reports:
|
|
157
|
+
|
|
158
|
+
- Test App: [http://127.0.0.1:3000](http://127.0.0.1:3000)
|
|
159
|
+
- API: [http://127.0.0.1:3000/api](http://127.0.0.1:3000/api)
|
|
160
|
+
- Reports: [http://127.0.0.1:3000/reports](http://127.0.0.1:3000/reports)
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0.
|
|
1
|
+
1.0.1
|
|
@@ -184,7 +184,7 @@
|
|
|
184
184
|
el.setAttribute("data-bs-theme", "dark")
|
|
185
185
|
})
|
|
186
186
|
} else {
|
|
187
|
-
console.log(`RRF: Unknown mode: ${
|
|
187
|
+
console.log(`RRF: Unknown mode: ${realMode}`)
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
|
|
@@ -372,3 +372,5 @@
|
|
|
372
372
|
.then((body) => { rrfReplaceDocument(body) })
|
|
373
373
|
}
|
|
374
374
|
</script>
|
|
375
|
+
|
|
376
|
+
<%= render partial: "rest_framework/head/extra" %>
|
|
File without changes
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<div style="float: right">
|
|
2
|
+
<%= render partial: "rest_framework/heading/actions/extra" %>
|
|
2
3
|
<% if @route_groups.values[0].any? { |r| r[:matches_path] && r[:verb] == "DELETE" && r[:action] == "destroy" } %>
|
|
3
4
|
<button type="button" class="btn btn-danger" onclick="rrfDelete(this)">DELETE</button>
|
|
4
5
|
<% end %>
|
|
@@ -6,4 +7,4 @@
|
|
|
6
7
|
<button type="button" class="btn btn-primary" onclick="rrfOptions(this)">OPTIONS</button>
|
|
7
8
|
<% end %>
|
|
8
9
|
<button type="button" class="btn btn-primary" onclick="rrfGet(this)">GET</button>
|
|
9
|
-
</div>
|
|
10
|
+
</div>
|
|
File without changes
|
|
File without changes
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
}.compact) do |form| %>
|
|
21
21
|
<% controller.get_fields.map(&:to_s).each do |f| %>
|
|
22
22
|
<%
|
|
23
|
-
# Don't provide form fields for associations or
|
|
23
|
+
# Don't provide form fields for associations or read-only fields.
|
|
24
24
|
cfg = controller.class.field_configuration[f]
|
|
25
|
-
next if !cfg || cfg[:kind] == "association" || cfg[:
|
|
25
|
+
next if !cfg || cfg[:kind] == "association" || cfg[:read_only]
|
|
26
26
|
%>
|
|
27
27
|
<div class="mb-2">
|
|
28
28
|
<% if cfg[:kind] == "rich_text" %>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
class RESTFramework::Errors::NilPassedToRenderAPIError < RESTFramework::Errors::BaseError
|
|
2
2
|
def message
|
|
3
|
-
|
|
3
|
+
<<~MSG.split("\n").join(" ")
|
|
4
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.,
|
|
@@ -5,7 +5,7 @@ class RESTFramework::Errors::UnknownModelError < RESTFramework::Errors::BaseErro
|
|
|
5
5
|
end
|
|
6
6
|
|
|
7
7
|
def message
|
|
8
|
-
|
|
8
|
+
<<~MSG.split("\n").join(" ")
|
|
9
9
|
The model class for `#{@controller_class}` could not be determined. Any controller that
|
|
10
10
|
includes `RESTFramework::BaseModelControllerMixin` (directly or indirectly) must either set
|
|
11
11
|
the `model` attribute on the controller, or the model must be deducible from the controller
|
|
@@ -2,7 +2,7 @@
|
|
|
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
|
-
|
|
5
|
+
@controller.class.ordering_fields&.map(&:to_s) || @controller.get_fields
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
# Convert ordering string to an ordering configuration.
|
|
@@ -34,7 +34,7 @@ class RESTFramework::Filters::OrderingFilter < RESTFramework::Filters::BaseFilte
|
|
|
34
34
|
return ordering
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
nil
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
# Order data according to the request query parameters.
|
|
@@ -46,7 +46,7 @@ class RESTFramework::Filters::OrderingFilter < RESTFramework::Filters::BaseFilte
|
|
|
46
46
|
return data.send(reorder ? :reorder : :order, ordering)
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
data
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
@@ -13,18 +13,19 @@ class RESTFramework::Filters::QueryFilter < RESTFramework::Filters::BaseFilter
|
|
|
13
13
|
true: true,
|
|
14
14
|
false: false,
|
|
15
15
|
null: nil,
|
|
16
|
-
lt: ->(f, v) { {f => ...v} },
|
|
16
|
+
lt: ->(f, v) { { f => ...v } },
|
|
17
17
|
# `gt` must negate `lte` because Rails doesn't support `>` with endless ranges.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
# Ref: https://github.com/rails/rails/pull/47345
|
|
19
|
+
gt: ->(f, v) { Not.new({ f => ..v }) },
|
|
20
|
+
lte: ->(f, v) { { f => ..v } },
|
|
21
|
+
gte: ->(f, v) { { f => v.. } },
|
|
22
|
+
not: ->(f, v) { Not.new({ f => v }) },
|
|
23
|
+
cont: ->(f, v) { [ "#{f} LIKE ?", "%#{ActiveRecord::Base.sanitize_sql_like(v)}%" ] },
|
|
23
24
|
in: ->(f, v) {
|
|
24
25
|
if v.is_a?(Array)
|
|
25
|
-
{f => v.map { |
|
|
26
|
+
{ f => v.map { |el| el == "null" ? nil : el } }
|
|
26
27
|
elsif v.is_a?(String)
|
|
27
|
-
{f => v.split(",").map { |
|
|
28
|
+
{ f => v.split(",").map { |el| el == "null" ? nil : el } }
|
|
28
29
|
end
|
|
29
30
|
},
|
|
30
31
|
}.freeze
|
|
@@ -33,7 +34,7 @@ class RESTFramework::Filters::QueryFilter < RESTFramework::Filters::BaseFilter
|
|
|
33
34
|
# Get a list of filter fields for the current action.
|
|
34
35
|
def _get_fields
|
|
35
36
|
# Always return a list of strings; `@controller.get_fields` already does this.
|
|
36
|
-
|
|
37
|
+
@controller.class.filter_fields&.map(&:to_s) || @controller.get_fields
|
|
37
38
|
end
|
|
38
39
|
|
|
39
40
|
# Helper to find a variation of a field using a predicate. For example, there could be a field
|
|
@@ -62,7 +63,7 @@ class RESTFramework::Filters::QueryFilter < RESTFramework::Filters::BaseFilter
|
|
|
62
63
|
base_query = @controller.request.query_parameters.map { |field, v|
|
|
63
64
|
# First, if field is a simple filterable field, return early.
|
|
64
65
|
if field.in?(fields)
|
|
65
|
-
next [field, v]
|
|
66
|
+
next [ field, v ]
|
|
66
67
|
end
|
|
67
68
|
|
|
68
69
|
# First, try to parse a simple predicate and check if it is filterable.
|
|
@@ -81,7 +82,7 @@ class RESTFramework::Filters::QueryFilter < RESTFramework::Filters::BaseFilter
|
|
|
81
82
|
sub_fields = @controller.class.field_configuration[root_field][:sub_fields] || []
|
|
82
83
|
if sub_field.in?(sub_fields)
|
|
83
84
|
includes << root_field.to_sym
|
|
84
|
-
next [field, v]
|
|
85
|
+
next [ field, v ]
|
|
85
86
|
elsif pred_sub_field && pred_sub_field.in?(sub_fields)
|
|
86
87
|
includes << root_field.to_sym
|
|
87
88
|
field = pred_field
|
|
@@ -99,13 +100,12 @@ class RESTFramework::Filters::QueryFilter < RESTFramework::Filters::BaseFilter
|
|
|
99
100
|
if cfg.is_a?(Proc)
|
|
100
101
|
pred_queries << cfg.call(field, v)
|
|
101
102
|
else
|
|
102
|
-
pred_queries << {field => cfg}
|
|
103
|
+
pred_queries << { field => cfg }
|
|
103
104
|
end
|
|
104
105
|
|
|
105
106
|
next nil
|
|
106
107
|
}.compact.to_h.symbolize_keys
|
|
107
108
|
|
|
108
|
-
puts "GNS: #{base_query.inspect} #{pred_queries.inspect} #{includes.inspect}"
|
|
109
109
|
return base_query, pred_queries, includes
|
|
110
110
|
end
|
|
111
111
|
|
|
@@ -129,7 +129,7 @@ class RESTFramework::Filters::QueryFilter < RESTFramework::Filters::BaseFilter
|
|
|
129
129
|
end
|
|
130
130
|
end
|
|
131
131
|
|
|
132
|
-
|
|
132
|
+
data
|
|
133
133
|
end
|
|
134
134
|
end
|
|
135
135
|
|
|
@@ -6,7 +6,7 @@ class RESTFramework::Filters::SearchFilter < RESTFramework::Filters::BaseFilter
|
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
columns = @controller.class.get_model.column_names
|
|
9
|
-
|
|
9
|
+
@controller.get_fields.select { |f|
|
|
10
10
|
f.in?(RESTFramework.config.search_columns) && f.in?(columns)
|
|
11
11
|
}
|
|
12
12
|
end
|
|
@@ -30,12 +30,12 @@ class RESTFramework::Filters::SearchFilter < RESTFramework::Filters::BaseFilter
|
|
|
30
30
|
fields.map { |f|
|
|
31
31
|
"CAST(#{f} AS #{data_type}) #{@controller.class.search_ilike ? "ILIKE" : "LIKE"} ?"
|
|
32
32
|
}.join(" OR "),
|
|
33
|
-
*(["%#{search}%"] * fields.length),
|
|
33
|
+
*([ "%#{search}%" ] * fields.length),
|
|
34
34
|
)
|
|
35
35
|
end
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
data
|
|
39
39
|
end
|
|
40
40
|
end
|
|
41
41
|
|
|
@@ -5,7 +5,7 @@ require "rails/generators"
|
|
|
5
5
|
# :nocov:
|
|
6
6
|
class RESTFrameworkCustomGeneratorControllerNamespace < String
|
|
7
7
|
def camelize
|
|
8
|
-
|
|
8
|
+
"RESTFramework"
|
|
9
9
|
end
|
|
10
10
|
end
|
|
11
11
|
# :nocov:
|
|
@@ -41,7 +41,7 @@ class RESTFramework::Generators::ControllerGenerator < Rails::Generators::Base
|
|
|
41
41
|
# Some projects may not have the inflection "REST" as an acronym, which changes this generator to
|
|
42
42
|
# be namespaced in `r_e_s_t_framework`, which is weird.
|
|
43
43
|
def self.namespace
|
|
44
|
-
|
|
44
|
+
RESTFrameworkCustomGeneratorControllerNamespace.new("rest_framework:controller")
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def create_rest_controller_file
|
|
@@ -11,7 +11,7 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
|
11
11
|
title: nil,
|
|
12
12
|
description: nil,
|
|
13
13
|
version: nil,
|
|
14
|
-
inflect_acronyms: ["ID", "IDs", "REST", "API", "APIs"].freeze,
|
|
14
|
+
inflect_acronyms: [ "ID", "IDs", "REST", "API", "APIs" ].freeze,
|
|
15
15
|
openapi_include_children: false,
|
|
16
16
|
|
|
17
17
|
# Options related to serialization.
|
|
@@ -38,14 +38,14 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
|
38
38
|
|
|
39
39
|
# Default action for API root.
|
|
40
40
|
def root
|
|
41
|
-
render(api: {message: "This is the API root."})
|
|
41
|
+
render(api: { message: "This is the API root." })
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
module ClassMethods
|
|
45
45
|
# By default, this is the name of the controller class, titleized and with any custom inflection
|
|
46
46
|
# acronyms applied.
|
|
47
47
|
def get_title
|
|
48
|
-
|
|
48
|
+
self.title || RESTFramework::Utils.inflect(
|
|
49
49
|
self.name.demodulize.chomp("Controller").titleize(keep_id_suffix: true),
|
|
50
50
|
self.inflect_acronyms,
|
|
51
51
|
)
|
|
@@ -53,10 +53,7 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
|
53
53
|
|
|
54
54
|
# Get a label from a field/column name, titleized and inflected.
|
|
55
55
|
def label_for(s)
|
|
56
|
-
|
|
57
|
-
s.to_s.titleize(keep_id_suffix: true),
|
|
58
|
-
self.inflect_acronyms,
|
|
59
|
-
)
|
|
56
|
+
RESTFramework::Utils.inflect(s.to_s.titleize(keep_id_suffix: true), self.inflect_acronyms)
|
|
60
57
|
end
|
|
61
58
|
|
|
62
59
|
# Define any behavior to execute at the end of controller definition.
|
|
@@ -72,7 +69,7 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
|
72
69
|
# :nocov:
|
|
73
70
|
|
|
74
71
|
def openapi_response_content_types
|
|
75
|
-
|
|
72
|
+
@openapi_response_content_types ||= [
|
|
76
73
|
"text/html",
|
|
77
74
|
self.serialize_to_json ? "application/json" : nil,
|
|
78
75
|
self.serialize_to_xml ? "application/xml" : nil,
|
|
@@ -80,7 +77,7 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
|
80
77
|
end
|
|
81
78
|
|
|
82
79
|
def openapi_request_content_types
|
|
83
|
-
|
|
80
|
+
@openapi_request_content_types ||= [
|
|
84
81
|
"application/json",
|
|
85
82
|
"application/x-www-form-urlencoded",
|
|
86
83
|
"multipart/form-data",
|
|
@@ -91,7 +88,7 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
|
91
88
|
resp_cts = self.openapi_response_content_types
|
|
92
89
|
req_cts = self.openapi_request_content_types
|
|
93
90
|
|
|
94
|
-
|
|
91
|
+
routes.group_by { |r| r[:concat_path] }.map { |concat_path, routes|
|
|
95
92
|
[
|
|
96
93
|
concat_path.gsub(/:([0-9A-Za-z_-]+)/, "{\\1}"),
|
|
97
94
|
routes.map { |route|
|
|
@@ -99,28 +96,24 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
|
99
96
|
summary = metadata.delete(:label).presence || self.label_for(route[:action])
|
|
100
97
|
description = metadata.delete(:description).presence
|
|
101
98
|
extra_action = RESTFramework::EXTRA_ACTION_ROUTES.include?(route[:path])
|
|
102
|
-
error_response = {"$ref" => "#/components/responses/BadRequest"}
|
|
103
|
-
not_found_response = {"$ref" => "#/components/responses/NotFound"}
|
|
104
|
-
spec = {tags: [tag], summary: summary, description: description}.compact
|
|
99
|
+
error_response = { "$ref" => "#/components/responses/BadRequest" }
|
|
100
|
+
not_found_response = { "$ref" => "#/components/responses/NotFound" }
|
|
101
|
+
spec = { tags: [ tag ], summary: summary, description: description }.compact
|
|
105
102
|
|
|
106
103
|
# All routes should have a successful response.
|
|
107
104
|
spec[:responses] = {
|
|
108
|
-
200 => {content: resp_cts.map { |ct| [ct, {}] }.to_h, description: "Success"},
|
|
105
|
+
200 => { content: resp_cts.map { |ct| [ ct, {} ] }.to_h, description: "Success" },
|
|
109
106
|
}
|
|
110
107
|
|
|
111
108
|
# Builtin POST, PUT, PATCH, and DELETE should have a 400 and 404 response.
|
|
112
|
-
if route[:verb].in?(["POST", "PUT", "PATCH", "DELETE"]) && !extra_action
|
|
109
|
+
if route[:verb].in?([ "POST", "PUT", "PATCH", "DELETE" ]) && !extra_action
|
|
113
110
|
spec[:responses][400] = error_response
|
|
114
111
|
spec[:responses][404] = not_found_response
|
|
115
112
|
end
|
|
116
113
|
|
|
117
114
|
# All POST, PUT, PATCH should have a request body.
|
|
118
|
-
if route[:verb].in?(["POST", "PUT", "PATCH"])
|
|
119
|
-
spec[:requestBody] ||= {
|
|
120
|
-
content: req_cts.map { |ct|
|
|
121
|
-
[ct, {}]
|
|
122
|
-
}.to_h,
|
|
123
|
-
}
|
|
115
|
+
if route[:verb].in?([ "POST", "PUT", "PATCH" ])
|
|
116
|
+
spec[:requestBody] ||= { content: req_cts.map { |ct| [ ct, {} ] }.to_h }
|
|
124
117
|
end
|
|
125
118
|
|
|
126
119
|
# Add remaining metadata as an extension.
|
|
@@ -134,7 +127,7 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
|
134
127
|
name: p,
|
|
135
128
|
in: "path",
|
|
136
129
|
required: true,
|
|
137
|
-
schema: {type: "integer"},
|
|
130
|
+
schema: { type: "integer" },
|
|
138
131
|
}
|
|
139
132
|
},
|
|
140
133
|
},
|
|
@@ -146,23 +139,25 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
|
146
139
|
def openapi_document(request, route_group_name, routes)
|
|
147
140
|
server = request.base_url + request.original_fullpath.gsub(/\?.*/, "")
|
|
148
141
|
|
|
149
|
-
|
|
142
|
+
{
|
|
150
143
|
openapi: "3.1.1",
|
|
151
144
|
info: {
|
|
152
145
|
title: self.get_title,
|
|
153
146
|
description: self.description,
|
|
154
147
|
version: self.version.to_s,
|
|
155
148
|
}.compact,
|
|
156
|
-
servers: [{url: server}],
|
|
149
|
+
servers: [ { url: server } ],
|
|
157
150
|
paths: self.openapi_paths(routes, route_group_name),
|
|
158
|
-
tags: [{name: route_group_name, description: self.description}.compact],
|
|
151
|
+
tags: [ { name: route_group_name, description: self.description }.compact ],
|
|
159
152
|
components: {
|
|
160
153
|
schemas: {
|
|
161
154
|
"Error" => {
|
|
162
155
|
type: "object",
|
|
163
|
-
required: ["message"],
|
|
156
|
+
required: [ "message" ],
|
|
164
157
|
properties: {
|
|
165
|
-
message: {type: "string"
|
|
158
|
+
message: { type: "string" },
|
|
159
|
+
errors: { type: "object" },
|
|
160
|
+
exception: { type: "string" },
|
|
166
161
|
},
|
|
167
162
|
},
|
|
168
163
|
},
|
|
@@ -170,13 +165,19 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
|
170
165
|
"BadRequest": {
|
|
171
166
|
description: "Bad Request",
|
|
172
167
|
content: self.openapi_response_content_types.map { |ct|
|
|
173
|
-
[
|
|
168
|
+
[
|
|
169
|
+
ct,
|
|
170
|
+
ct == "text/html" ? {} : { schema: { "$ref" => "#/components/schemas/Error" } },
|
|
171
|
+
]
|
|
174
172
|
}.to_h,
|
|
175
173
|
},
|
|
176
174
|
"NotFound": {
|
|
177
175
|
description: "Not Found",
|
|
178
176
|
content: self.openapi_response_content_types.map { |ct|
|
|
179
|
-
[
|
|
177
|
+
[
|
|
178
|
+
ct,
|
|
179
|
+
ct == "text/html" ? {} : { schema: { "$ref" => "#/components/schemas/Error" } },
|
|
180
|
+
]
|
|
180
181
|
}.to_h,
|
|
181
182
|
},
|
|
182
183
|
},
|
|
@@ -249,12 +250,12 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
|
249
250
|
end
|
|
250
251
|
|
|
251
252
|
def get_serializer_class
|
|
252
|
-
|
|
253
|
+
self.class.serializer_class
|
|
253
254
|
end
|
|
254
255
|
|
|
255
256
|
# Serialize the given data using the `serializer_class`.
|
|
256
257
|
def serialize(data, **kwargs)
|
|
257
|
-
|
|
258
|
+
RESTFramework::Utils.wrap_ams(self.get_serializer_class).new(
|
|
258
259
|
data, controller: self, **kwargs
|
|
259
260
|
).serialize
|
|
260
261
|
end
|
|
@@ -278,7 +279,7 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
|
278
279
|
end
|
|
279
280
|
|
|
280
281
|
def route_groups
|
|
281
|
-
|
|
282
|
+
@route_groups ||= RESTFramework::Utils.get_routes(Rails.application.routes, request)
|
|
282
283
|
end
|
|
283
284
|
|
|
284
285
|
# Render a browsable API for `html` format, along with basic `json`/`xml` formats, and with
|
|
@@ -379,7 +380,7 @@ module RESTFramework::Mixins::BaseControllerMixin
|
|
|
379
380
|
end
|
|
380
381
|
end
|
|
381
382
|
|
|
382
|
-
|
|
383
|
+
document
|
|
383
384
|
end
|
|
384
385
|
|
|
385
386
|
def options
|
|
@@ -7,7 +7,7 @@ module RESTFramework::Mixins::BulkCreateModelMixin
|
|
|
7
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
9
|
def openapi_document
|
|
10
|
-
|
|
10
|
+
super.merge({ "x-rrf-bulk-create": true })
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def create
|
|
@@ -17,7 +17,7 @@ module RESTFramework::Mixins::BulkCreateModelMixin
|
|
|
17
17
|
return render(api: serialized_records)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
super
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
# Perform the `create` call, and return the collection of (possibly) created records.
|
|
@@ -25,9 +25,7 @@ module RESTFramework::Mixins::BulkCreateModelMixin
|
|
|
25
25
|
create_data = self.get_create_params(bulk_mode: true)[:_json]
|
|
26
26
|
|
|
27
27
|
# Perform bulk create in a transaction.
|
|
28
|
-
|
|
29
|
-
next self.get_create_from.create(create_data)
|
|
30
|
-
end
|
|
28
|
+
ActiveRecord::Base.transaction { self.create_from.create(create_data) }
|
|
31
29
|
end
|
|
32
30
|
end
|
|
33
31
|
|
|
@@ -42,17 +40,15 @@ module RESTFramework::Mixins::BulkUpdateModelMixin
|
|
|
42
40
|
# Perform the `update` call and return the collection of (possibly) updated records.
|
|
43
41
|
def update_all!
|
|
44
42
|
pk = self.class.get_model.primary_key
|
|
45
|
-
|
|
43
|
+
data = if params[:_json].is_a?(Array)
|
|
46
44
|
self.get_create_params(bulk_mode: :update)[:_json].index_by { |r| r[pk] }
|
|
47
45
|
else
|
|
48
46
|
create_params = self.get_create_params
|
|
49
|
-
{create_params[pk] => create_params}
|
|
47
|
+
{ create_params[pk] => create_params }
|
|
50
48
|
end
|
|
51
49
|
|
|
52
50
|
# Perform bulk update in a transaction.
|
|
53
|
-
|
|
54
|
-
next self.get_recordset.update(update_data.keys, update_data.values)
|
|
55
|
-
end
|
|
51
|
+
ActiveRecord::Base.transaction { self.get_recordset.update(data.keys, data.values) }
|
|
56
52
|
end
|
|
57
53
|
end
|
|
58
54
|
|
|
@@ -66,8 +62,7 @@ module RESTFramework::Mixins::BulkDestroyModelMixin
|
|
|
66
62
|
end
|
|
67
63
|
|
|
68
64
|
render(
|
|
69
|
-
api: {message: "Bulk destroy requires an array of primary keys as input."},
|
|
70
|
-
status: 400,
|
|
65
|
+
api: { message: "Bulk destroy requires an array of primary keys as input." }, status: 400,
|
|
71
66
|
)
|
|
72
67
|
end
|
|
73
68
|
|
|
@@ -77,9 +72,7 @@ module RESTFramework::Mixins::BulkDestroyModelMixin
|
|
|
77
72
|
destroy_data = self.request.request_parameters[:_json]
|
|
78
73
|
|
|
79
74
|
# Perform bulk destroy in a transaction.
|
|
80
|
-
|
|
81
|
-
next self.get_recordset.where(pk => destroy_data).destroy_all
|
|
82
|
-
end
|
|
75
|
+
ActiveRecord::Base.transaction { self.get_recordset.where(pk => destroy_data).destroy_all }
|
|
83
76
|
end
|
|
84
77
|
end
|
|
85
78
|
|