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 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
  )