rest_framework 1.0.2 → 1.2.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 +31 -26
- data/VERSION +1 -1
- data/app/views/rest_framework/_routes_and_forms.html.erb +2 -5
- data/app/views/rest_framework/routes_and_forms/_html_form.html.erb +1 -1
- data/app/views/rest_framework/routes_and_forms/_raw_form.html.erb +1 -1
- data/lib/rest_framework/controller/bulk.rb +272 -0
- data/lib/rest_framework/controller/crud.rb +65 -0
- data/lib/rest_framework/controller/openapi.rb +252 -0
- data/lib/rest_framework/controller.rb +839 -0
- data/lib/rest_framework/engine.rb +12 -2
- data/lib/rest_framework/errors.rb +53 -4
- data/lib/rest_framework/filters/ordering_filter.rb +0 -1
- data/lib/rest_framework/filters/query_filter.rb +7 -2
- data/lib/rest_framework/filters/search_filter.rb +5 -5
- data/lib/rest_framework/mixins/base_controller_mixin.rb +3 -383
- data/lib/rest_framework/mixins/bulk_model_controller_mixin.rb +27 -68
- data/lib/rest_framework/mixins/model_controller_mixin.rb +60 -807
- data/lib/rest_framework/paginators/page_number_paginator.rb +10 -11
- data/lib/rest_framework/routers.rb +20 -9
- data/lib/rest_framework/serializers/native_serializer.rb +5 -3
- data/lib/rest_framework/utils.rb +24 -6
- data/lib/rest_framework.rb +13 -5
- metadata +6 -7
- data/lib/rest_framework/errors/base_error.rb +0 -5
- data/lib/rest_framework/errors/nil_passed_to_render_api_error.rb +0 -14
- data/lib/rest_framework/errors/unknown_model_error.rb +0 -18
- data/lib/rest_framework/generators/controller_generator.rb +0 -64
- data/lib/rest_framework/generators.rb +0 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8119cab13c69b27ebfa36906e6bcd577688f2ba63b136e0833f4cfdeae3ca5c3
|
|
4
|
+
data.tar.gz: 149343b13dfdf94b5804beb502d2296d95f487774d1cb3a1dd30dd63f4ab2afc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 17d7143b9ac7a9a3f01fab29a369a083750d26f5923bea7d6b9b9dad407efe25bc30321de0024e1d1f3883aa86838bb250b2ee8c4c749e159386b42d5038f5c8
|
|
7
|
+
data.tar.gz: b1794ca103409bc69f235d72603bd8c9ecd91f674cf4e41c8072ec4c35b4e0d5ca26058f6903554df404183ee7c9624dac9b8f33b67fe212874e98bc4a600355
|
data/README.md
CHANGED
|
@@ -38,10 +38,22 @@ bundle install
|
|
|
38
38
|
|
|
39
39
|
## Quick Usage Tutorial
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
To add REST framework features to a controller, include the `Controller` module:
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
```ruby
|
|
44
|
+
class ApiController < ApplicationController
|
|
45
|
+
include RESTFramework::Controller
|
|
46
|
+
|
|
47
|
+
# Here is where you can set configuration class attributes that will propagate to child
|
|
48
|
+
# controllers.
|
|
49
|
+
|
|
50
|
+
# Setting up a paginator class here makes more sense than defining it on every child controller.
|
|
51
|
+
self.paginator_class = RESTFramework::PageNumberPaginator
|
|
52
|
+
self.page_size = 30
|
|
53
|
+
end
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Here is what the directory structure might look like for resource controllers:
|
|
45
57
|
|
|
46
58
|
```text
|
|
47
59
|
controllers/
|
|
@@ -52,29 +64,18 @@ controllers/
|
|
|
52
64
|
└─ users_controller.rb
|
|
53
65
|
```
|
|
54
66
|
|
|
55
|
-
### Controller
|
|
56
|
-
|
|
57
|
-
The root `ApiController` can include any common behavior you want to share across all your API
|
|
58
|
-
controllers:
|
|
59
|
-
|
|
60
|
-
```ruby
|
|
61
|
-
class ApiController < ApplicationController
|
|
62
|
-
include RESTFramework::BaseControllerMixin
|
|
63
|
-
|
|
64
|
-
# Setting up a paginator class here makes more sense than defining it on every child controller.
|
|
65
|
-
self.paginator_class = RESTFramework::PageNumberPaginator
|
|
66
|
-
self.page_size = 30
|
|
67
|
-
end
|
|
68
|
-
```
|
|
67
|
+
### Root Controller
|
|
69
68
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
It is typically a good pattern for the root of your API to have a dedicated `Api::RootController`
|
|
70
|
+
outside the inheritance chain of your other API controllers, so that you can define actions on the
|
|
71
|
+
root without them propagating to child controllers, and so you can set global configuration on the
|
|
72
|
+
`ApiController`.
|
|
73
73
|
|
|
74
74
|
```ruby
|
|
75
75
|
class Api::RootController < ApiController
|
|
76
76
|
self.extra_actions = {test: :get}
|
|
77
77
|
|
|
78
|
+
# The root action is routed by `rest_root`.
|
|
78
79
|
def root
|
|
79
80
|
render(
|
|
80
81
|
api: {
|
|
@@ -94,17 +95,19 @@ class Api::RootController < ApiController
|
|
|
94
95
|
end
|
|
95
96
|
```
|
|
96
97
|
|
|
97
|
-
|
|
98
|
+
### Resource Controllers
|
|
99
|
+
|
|
100
|
+
Other API controllers can be associated to a resource/model by setting the `model` class attribute.
|
|
98
101
|
|
|
99
102
|
```ruby
|
|
100
103
|
class Api::MoviesController < ApiController
|
|
101
|
-
|
|
102
|
-
|
|
104
|
+
self.model = Movie # Automatically routes the standard CRUD actions for this controller.
|
|
105
|
+
self.bulk = true # Enables bulk create/update/destroy actions for this controller.
|
|
103
106
|
self.fields = [:id, :name, :release_date, :enabled]
|
|
104
107
|
self.extra_member_actions = {first: :get}
|
|
105
108
|
|
|
106
109
|
def first
|
|
107
|
-
# Always use
|
|
110
|
+
# Always use bang methods, since the framework will rescue `RecordNotFound` and return a
|
|
108
111
|
# sensible error response.
|
|
109
112
|
render(api: self.get_records.first!)
|
|
110
113
|
end
|
|
@@ -120,9 +123,11 @@ to include or exclude fields rather than defining them manually:
|
|
|
120
123
|
|
|
121
124
|
```ruby
|
|
122
125
|
class Api::UsersController < ApiController
|
|
123
|
-
include RESTFramework::ModelControllerMixin
|
|
124
|
-
|
|
125
126
|
self.fields = {include: [:calculated_popularity], exclude: [:impersonation_token]}
|
|
127
|
+
|
|
128
|
+
# You can even disable some of the builtin actions. For example, this effectively makes the
|
|
129
|
+
# resource read-only:
|
|
130
|
+
self.excluded_actions = [:create, :update, :destroy, :update_all, :destroy_all]
|
|
126
131
|
end
|
|
127
132
|
```
|
|
128
133
|
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0
|
|
1
|
+
1.2.0
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
<%
|
|
2
|
-
@is_model_controller = controller.class.included_modules.include?(RESTFramework::ModelControllerMixin)
|
|
3
|
-
%>
|
|
4
1
|
<div class="row">
|
|
5
2
|
<div>
|
|
6
3
|
<ul class="nav nav-tabs">
|
|
@@ -25,7 +22,7 @@
|
|
|
25
22
|
</a>
|
|
26
23
|
</li>
|
|
27
24
|
<% end %>
|
|
28
|
-
<% if @_rrf_form_routes_html.present? &&
|
|
25
|
+
<% if @_rrf_form_routes_html.present? && controller.class.model %>
|
|
29
26
|
<li class="nav-item">
|
|
30
27
|
<a class="nav-link" href="#tabHtmlForm" data-bs-toggle="tab" role="tab">
|
|
31
28
|
HTML Form
|
|
@@ -43,7 +40,7 @@
|
|
|
43
40
|
<%= render partial: "rest_framework/routes_and_forms/raw_form" %>
|
|
44
41
|
</div>
|
|
45
42
|
<% end %>
|
|
46
|
-
<% if @_rrf_form_routes_html.present? &&
|
|
43
|
+
<% if @_rrf_form_routes_html.present? && controller.class.model %>
|
|
47
44
|
<div class="tab-pane fade" id="tabHtmlForm" role="tabpanel">
|
|
48
45
|
<%= render partial: "rest_framework/routes_and_forms/html_form" %>
|
|
49
46
|
</div>
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
scope: "",
|
|
19
19
|
local: true,
|
|
20
20
|
}.compact) do |form| %>
|
|
21
|
-
<% controller.get_fields.
|
|
21
|
+
<% controller.get_fields.each do |f| %>
|
|
22
22
|
<%
|
|
23
23
|
# Don't provide form fields for associations or read-only fields.
|
|
24
24
|
cfg = controller.class.field_configuration[f]
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
</label>
|
|
30
30
|
</div>
|
|
31
31
|
|
|
32
|
-
<% if
|
|
32
|
+
<% if model = controller.class.model %>
|
|
33
33
|
<% if attachment_reflections = model.attachment_reflections.presence %>
|
|
34
34
|
<div class="mb-2" style="display: none" id="rawFilesFormWrapper">
|
|
35
35
|
<%= form_with(**{
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
module RESTFramework::Controller
|
|
2
|
+
RRF_DEFAULT_BULK_MAX_SIZE = 1000
|
|
3
|
+
RRF_DEFAULT_BULK_MAX_RAW_SIZE = 10000
|
|
4
|
+
|
|
5
|
+
def _bulk_max_size
|
|
6
|
+
@_bulk_max_size ||= self.class.bulk_max_size || RRF_DEFAULT_BULK_MAX_SIZE
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def _bulk_max_raw_size
|
|
10
|
+
@_bulk_max_raw_size ||= self.class.bulk_max_raw_size || RRF_DEFAULT_BULK_MAX_RAW_SIZE
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def _bulk_serialize(records)
|
|
14
|
+
# This is kinda slow, so perhaps we should eventually integrate `errors` serialization into
|
|
15
|
+
# the serializer directly. This would fail for active model serializers, but maybe we don't
|
|
16
|
+
# care?
|
|
17
|
+
s = RESTFramework::Utils.wrap_ams(self.get_serializer_class)
|
|
18
|
+
records.map do |record|
|
|
19
|
+
s.new(record, controller: self).serialize.merge!({ errors: record.errors.presence }.compact)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def _bulk_mode
|
|
24
|
+
return @_bulk_mode if defined?(@_bulk_mode)
|
|
25
|
+
|
|
26
|
+
# If mode override is allowed, check the query param.
|
|
27
|
+
if self.class.bulk_allow_mode_override && (qp = self.class.bulk_mode_query_param)
|
|
28
|
+
if (requested = request.query_parameters[qp].presence)
|
|
29
|
+
requested = requested.to_sym
|
|
30
|
+
unless requested.in?([ :default, :raw ])
|
|
31
|
+
raise RESTFramework::InvalidBulkParametersError.new(
|
|
32
|
+
"Invalid bulk mode: #{requested}. Must be `default` or `raw`.",
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
return @_bulk_mode = requested
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Normalize: `true` and `:default` both mean per-record processing.
|
|
40
|
+
@_bulk_mode = self.class.bulk == :raw ? :raw : :default
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Resolve whether partial fulfillment is enabled for this request.
|
|
44
|
+
def _bulk_partial
|
|
45
|
+
return @_bulk_partial if defined?(@_bulk_partial)
|
|
46
|
+
|
|
47
|
+
# Check the query param first if configured.
|
|
48
|
+
if (qp = self.class.bulk_partial_query_param)
|
|
49
|
+
if (requested = request.query_parameters[qp].presence)
|
|
50
|
+
return @_bulk_partial = ActiveModel::Type::Boolean.new.cast(requested)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
@_bulk_partial = self.class.bulk_partial
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Validate and extract bulk object data from request parameters.
|
|
58
|
+
def _bulk_object_data(bulk_action, bulk_mode)
|
|
59
|
+
data = self.get_body_params(bulk_action: bulk_action)[:_json]
|
|
60
|
+
|
|
61
|
+
unless data&.is_a?(Array) && data.all? { |r| r.is_a?(ActionController::Parameters) }
|
|
62
|
+
raise RESTFramework::InvalidBulkParametersError.new("Expected an array of objects.")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Enforce size limits.
|
|
66
|
+
max = bulk_mode == :raw ? self._bulk_max_raw_size : self._bulk_max_size
|
|
67
|
+
if max && data.length > max
|
|
68
|
+
raise RESTFramework::InvalidBulkParametersError.new(
|
|
69
|
+
"Too many records (#{data.length}) for #{bulk_mode} mode; maximum is #{max}.",
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
data
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Validate and extract bulk primary key data from request parameters.
|
|
77
|
+
def _bulk_pk_data
|
|
78
|
+
data = self.get_destroy_params(bulk_action: :destroy)[:_json]
|
|
79
|
+
|
|
80
|
+
unless data&.is_a?(Array) && data.all? { |r| r.is_a?(String) || r.is_a?(Numeric) }
|
|
81
|
+
raise RESTFramework::InvalidBulkParametersError.new("Expected an array of primary keys.")
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Enforce size limits.
|
|
85
|
+
max = self._bulk_mode == :raw ? self._bulk_max_raw_size : self._bulk_max_size
|
|
86
|
+
if max && data.length > max
|
|
87
|
+
raise RESTFramework::InvalidBulkParametersError.new(
|
|
88
|
+
"Too many records (#{data.length}) for #{self._bulk_mode} mode; maximum is #{max}.",
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
data
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def create_all
|
|
96
|
+
if self._bulk_mode == :raw
|
|
97
|
+
result = self.create_all_raw!
|
|
98
|
+
return render(api: { message: "Bulk create successful.", result: result })
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
records = self.create_all_default!
|
|
102
|
+
render(
|
|
103
|
+
api: { message: "Bulk create successful.", records: self._bulk_serialize(records) },
|
|
104
|
+
status: :created,
|
|
105
|
+
)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def create_all_raw!
|
|
109
|
+
pk = self.class.model.primary_key
|
|
110
|
+
data = self._bulk_object_data(:create, :raw)
|
|
111
|
+
|
|
112
|
+
unless first_keys = data.first&.keys&.sort
|
|
113
|
+
raise RESTFramework::InvalidBulkParametersError.new("Expected objects with attrs.")
|
|
114
|
+
end
|
|
115
|
+
unless data.all? { |r| r.keys.sort == first_keys }
|
|
116
|
+
raise RESTFramework::InvalidBulkParametersError.new("All objects must have the same attrs.")
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
self.create_from.insert_all(data, unique_by: pk)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def create_all_default!
|
|
123
|
+
data = self._bulk_object_data(:create, :default)
|
|
124
|
+
collection = self.create_from
|
|
125
|
+
|
|
126
|
+
if self._bulk_partial
|
|
127
|
+
# Partial: save each record individually, return all (some may have errors).
|
|
128
|
+
data.map { |attrs| collection.create(attrs) }
|
|
129
|
+
else
|
|
130
|
+
# Transactional: validate all first, then save in a transaction or raise.
|
|
131
|
+
records = data.map { |attrs| collection.new(attrs) }
|
|
132
|
+
failed = records.reject(&:valid?)
|
|
133
|
+
|
|
134
|
+
if failed.any?
|
|
135
|
+
raise RESTFramework::BulkRecordErrorsError.new(records)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
self.class.model.transaction do
|
|
139
|
+
records.each(&:save!)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
records
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def update_all
|
|
147
|
+
if self._bulk_mode == :raw
|
|
148
|
+
result = self.update_all_raw!
|
|
149
|
+
return render(api: { message: "Bulk update successful.", result: result })
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
records = self.update_all_default!
|
|
153
|
+
render(api: { message: "Bulk update successful.", records: self._bulk_serialize(records) })
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def update_all_raw!
|
|
157
|
+
pk = self.class.model.primary_key
|
|
158
|
+
data = self._bulk_object_data(:update, :raw)
|
|
159
|
+
|
|
160
|
+
data_ids = data.map { |r| r[pk] }.uniq
|
|
161
|
+
if data_ids.include?(nil)
|
|
162
|
+
raise RESTFramework::InvalidBulkParametersError.new(
|
|
163
|
+
"Bulk update requires the primary key (#{pk}) for all records.",
|
|
164
|
+
)
|
|
165
|
+
end
|
|
166
|
+
found_ids = self.get_recordset.where(pk => data_ids).pluck(pk)
|
|
167
|
+
if found_ids.length != data_ids.length
|
|
168
|
+
missing = data_ids - found_ids
|
|
169
|
+
raise RESTFramework::InvalidBulkParametersError.new(
|
|
170
|
+
"Records not found with #{pk}: #{missing.join(', ')}.",
|
|
171
|
+
)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
unless first_keys = data.first&.keys&.sort
|
|
175
|
+
raise RESTFramework::InvalidBulkParametersError.new("Expected objects with attrs.")
|
|
176
|
+
end
|
|
177
|
+
unless data.all? { |r| r.keys.sort == first_keys }
|
|
178
|
+
raise RESTFramework::InvalidBulkParametersError.new("All objects must have the same attrs.")
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
self.get_recordset.upsert_all(data, unique_by: pk)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def update_all_default!
|
|
185
|
+
pk = self.class.model.primary_key
|
|
186
|
+
data = self._bulk_object_data(:update, :default)
|
|
187
|
+
|
|
188
|
+
data_ids = data.map { |r| r[pk] }.uniq
|
|
189
|
+
if data_ids.include?(nil)
|
|
190
|
+
raise RESTFramework::InvalidBulkParametersError.new(
|
|
191
|
+
"Bulk update requires the primary key (#{pk}) for all records.",
|
|
192
|
+
)
|
|
193
|
+
end
|
|
194
|
+
existing = self.get_recordset.where(pk => data_ids).index_by { |r| r.send(pk) }
|
|
195
|
+
if existing.length != data_ids.length
|
|
196
|
+
missing = data_ids - existing.keys
|
|
197
|
+
raise RESTFramework::InvalidBulkParametersError.new(
|
|
198
|
+
"Records not found with #{pk}: #{missing.join(', ')}.",
|
|
199
|
+
)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Assign attributes to each record.
|
|
203
|
+
records = data.map { |attrs|
|
|
204
|
+
record = existing[attrs[pk]]
|
|
205
|
+
record.assign_attributes(attrs.except(pk))
|
|
206
|
+
record
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if self._bulk_partial
|
|
210
|
+
# Partial: save each record individually.
|
|
211
|
+
records.each(&:save)
|
|
212
|
+
records
|
|
213
|
+
else
|
|
214
|
+
# Transactional: validate all first, then save in a transaction or raise.
|
|
215
|
+
failed = records.reject(&:valid?)
|
|
216
|
+
|
|
217
|
+
if failed.any?
|
|
218
|
+
raise RESTFramework::BulkRecordErrorsError.new(records)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
self.class.model.transaction do
|
|
222
|
+
records.each(&:save!)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
records
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def destroy_all
|
|
230
|
+
if self._bulk_mode == :raw
|
|
231
|
+
deleted = self.destroy_all_raw!
|
|
232
|
+
return render(api: { message: "Bulk destroy successful.", result: deleted })
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
records = self.destroy_all_default!
|
|
236
|
+
render(api: { message: "Bulk destroy successful.", records: self._bulk_serialize(records) })
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def destroy_all_raw!
|
|
240
|
+
data = self._bulk_pk_data
|
|
241
|
+
pk = self.class.model.primary_key
|
|
242
|
+
self.get_recordset.where(pk => data).delete_all
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def destroy_all_default!
|
|
246
|
+
data = self._bulk_pk_data
|
|
247
|
+
pk = self.class.model.primary_key
|
|
248
|
+
records = self.get_recordset.where(pk => data).to_a
|
|
249
|
+
|
|
250
|
+
# In transactional mode, verify all requested records exist.
|
|
251
|
+
if !self._bulk_partial && records.length != data.uniq.length
|
|
252
|
+
found_ids = records.map { |r| r.send(pk) }
|
|
253
|
+
missing = data.uniq - found_ids
|
|
254
|
+
raise RESTFramework::InvalidBulkParametersError.new(
|
|
255
|
+
"Bulk destroy requires all records to exist. Missing #{pk}: #{missing.join(', ')}.",
|
|
256
|
+
)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
if self._bulk_partial
|
|
260
|
+
# Partial: destroy each record individually.
|
|
261
|
+
records.each(&:destroy)
|
|
262
|
+
records
|
|
263
|
+
else
|
|
264
|
+
# Transactional: destroy all in a transaction, roll back on failure.
|
|
265
|
+
self.class.model.transaction do
|
|
266
|
+
records.each(&:destroy!)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
records
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
module RESTFramework::Controller
|
|
2
|
+
def create
|
|
3
|
+
# Bulk create: if `bulk` is enabled and the request body is an array, delegate to `create_all`.
|
|
4
|
+
if self.class.bulk && params[:_json].is_a?(Array)
|
|
5
|
+
return self.create_all
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
render(api: self.create!, status: :created)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Perform the `create!` call and return the created record.
|
|
12
|
+
def create!
|
|
13
|
+
self.create_from.create!(self.get_create_params)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def index
|
|
17
|
+
render(api: self.index!)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Get records with both filtering and pagination applied.
|
|
21
|
+
def index!
|
|
22
|
+
records = self.get_records
|
|
23
|
+
|
|
24
|
+
# Handle pagination, if enabled.
|
|
25
|
+
if paginator_class = self.class.paginator_class
|
|
26
|
+
# Paginate if there is a `max_page_size`, or if there is no `page_size_query_param`, or if the
|
|
27
|
+
# page size is not set to "0".
|
|
28
|
+
max_page_size = self.class.max_page_size
|
|
29
|
+
page_size_query_param = self.class.page_size_query_param
|
|
30
|
+
if max_page_size || !page_size_query_param || params[page_size_query_param] != "0"
|
|
31
|
+
paginator = paginator_class.new(data: records, controller: self)
|
|
32
|
+
page = paginator.get_page
|
|
33
|
+
serialized_page = self.serialize(page)
|
|
34
|
+
return paginator.get_paginated_response(serialized_page)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
records
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def show
|
|
42
|
+
render(api: self.get_record)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def update
|
|
46
|
+
render(api: self.update!)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Perform the `update!` call and return the updated record.
|
|
50
|
+
def update!
|
|
51
|
+
record = self.get_record
|
|
52
|
+
record.update!(self.get_update_params)
|
|
53
|
+
record
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def destroy
|
|
57
|
+
self.destroy!
|
|
58
|
+
render(api: "")
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Perform the `destroy!` call and return the destroyed (and frozen) record.
|
|
62
|
+
def destroy!
|
|
63
|
+
self.get_record.destroy!
|
|
64
|
+
end
|
|
65
|
+
end
|