rest_framework 0.9.16 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 59084182eeedcec7d3f1a1a913a99e55a6139bb394e4c712744c5d80778db800
4
- data.tar.gz: 552ea8a39833dc6a3ec4c689f120ba549af0db49a3fafa29d63cd4cb17d85a85
3
+ metadata.gz: 34653421f170160fbdd29c3119c728ba5fe7591fd969f9d50d2e20d18cecf8bb
4
+ data.tar.gz: ed35098916c941a897087d9b455f69e86f5f53b1e81442e22aa135c81877c45c
5
5
  SHA512:
6
- metadata.gz: cffd1cde2cbf956d46344018c137b723397118f3aa4ea16c2d9abf5c9d9694c8a78d8f706f96e28c637ec4f58d692553da372a832a0bd44cc58154134b733d33
7
- data.tar.gz: 1eea97e45fc0f4c47edbdb04a8de05fe6e383e259ea1d3ff48196b6601aeb87341aa6750f8eb4411dd0e16934452c5a02b2d26877a9ec8382ae538312f305603
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
- return api_response(
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
- return api_response({message: "Hello, world!"})
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
- return api_response(self.get_records.first!)
108
+ render_api(self.get_records.first!)
109
109
  end
110
110
 
111
111
  def get_recordset
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.16
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 = @route_props[:with_path_args].call(route[:route]) %>
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
- metadata = controller.class.fields_metadata[f]
25
- next if !metadata || metadata[:kind] == "association" || metadata[:read_only]
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 metadata[:kind] == "rich_text" %>
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 metadata[:kind] == "attachment" %>
31
+ <% elsif cfg[:kind] == "attachment" %>
32
32
  <label class="form-label w-100"><%= controller.class.label_for(f) %>
33
- <%= form.file_field f, multiple: metadata.dig(:attachment, :macro) == :has_many_attached %>
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 = @route_props[:with_path_args].call(route[:route]) %>
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], @route_props[:with_path_args].call(route[:route]) %>
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::NilPassedToAPIResponseError < RESTFramework::Errors::BaseError
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 `api_response`; this is unsupported. If you want a blank
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::NilPassedToAPIResponseError = RESTFramework::Errors::NilPassedToAPIResponseError
14
+ RESTFramework::NilPassedToRenderAPIError = RESTFramework::Errors::NilPassedToRenderAPIError
@@ -3,5 +3,5 @@ end
3
3
 
4
4
  require_relative "errors/base_error"
5
5
 
6
- require_relative "errors/nil_passed_to_api_response_error"
6
+ require_relative "errors/nil_passed_to_render_api_error"
7
7
  require_relative "errors/unknown_model_error"
@@ -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 if @controller.ordering_query_param.blank?
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[@controller.ordering_query_param]
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.field_config_for(field)[:sub_fields] || []
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
- (self::RRF_BASE_CONFIG.keys + self::RRF_BASE_INSTANCE_CONFIG.keys).each { |k|
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 (with defaults) unless they already exist.
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 serializer_class
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.serializer_class).new(
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
- return api_response(
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 api_response(payload, **kwargs)
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 `api_response`). The caller should
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::NilPassedToAPIResponseError
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
- @route_props, @route_groups = RESTFramework::Utils.get_routes(
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 :render_api, :api_response
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
- return api_response(self.options_metadata)
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 options
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 options_metadata
10
- return super.merge({bulk_create: true})
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 api_response(serialized_records)
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
- return api_response(serialized_records)
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 api_response(serialized_records)
65
+ return render_api(serialized_records)
66
66
  end
67
67
 
68
- return api_response(
68
+ render_api(
69
69
  {message: "Bulk destroy requires an array of primary keys as input."},
70
70
  status: 400,
71
71
  )