rest_framework 0.9.2 → 0.9.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/app/views/rest_framework/_external.html.erb +13 -0
- data/app/views/rest_framework/_head.html.erb +2 -192
- data/{docs/assets/js/rest_framework.js → app/views/rest_framework/_shared.html} +69 -42
- data/docs/Gemfile +1 -0
- data/docs/Gemfile.lock +14 -14
- data/docs/_config.yml +4 -2
- data/docs/_guide/2_controllers.md +342 -0
- data/docs/_guide/3_serializers.md +1 -1
- data/docs/_guide/4_filtering_and_ordering.md +8 -8
- data/docs/_includes/external.html +9 -0
- data/docs/_includes/head.html +135 -15
- data/docs/_includes/shared.html +164 -0
- data/lib/rest_framework/controller_mixins/base.rb +23 -36
- data/lib/rest_framework/controller_mixins/models.rb +86 -75
- data/lib/rest_framework/controller_mixins.rb +1 -0
- data/lib/rest_framework/engine.rb +9 -0
- data/lib/rest_framework/filters/base.rb +9 -0
- data/lib/rest_framework/filters/model_ordering.rb +48 -0
- data/lib/rest_framework/filters/model_query.rb +51 -0
- data/lib/rest_framework/filters/model_search.rb +41 -0
- data/lib/rest_framework/filters/ransack.rb +25 -0
- data/lib/rest_framework/filters.rb +6 -150
- data/lib/rest_framework/paginators.rb +7 -11
- data/lib/rest_framework/serializers.rb +10 -10
- data/lib/rest_framework/utils.rb +15 -7
- data/lib/rest_framework.rb +93 -4
- data/vendor/assets/javascripts/rest_framework/bootstrap.js +7 -0
- data/vendor/assets/javascripts/rest_framework/highlight-json.js +7 -0
- data/vendor/assets/javascripts/rest_framework/highlight-xml.js +29 -0
- data/vendor/assets/javascripts/rest_framework/highlight.js +1202 -0
- data/vendor/assets/javascripts/rest_framework/neatjson.js +8 -0
- data/vendor/assets/javascripts/rest_framework/trix.js +6 -0
- data/vendor/assets/stylesheets/rest_framework/bootstrap-icons.css +13 -0
- data/vendor/assets/stylesheets/rest_framework/bootstrap.css +6 -0
- data/vendor/assets/stylesheets/rest_framework/highlight-a11y-dark.css +7 -0
- data/vendor/assets/stylesheets/rest_framework/highlight-a11y-light.css +7 -0
- data/vendor/assets/stylesheets/rest_framework/trix.css +410 -0
- metadata +23 -5
- data/docs/_guide/2_controller_mixins.md +0 -293
- data/docs/assets/css/rest_framework.css +0 -159
@@ -1,293 +0,0 @@
|
|
1
|
-
---
|
2
|
-
layout: default
|
3
|
-
title: Controller Mixins
|
4
|
-
position: 2
|
5
|
-
slug: controller-mixins
|
6
|
-
---
|
7
|
-
# Controller Mixins
|
8
|
-
|
9
|
-
This is the core of the REST Framework. Generally speaking, projects already have an existing
|
10
|
-
controller inheritance hierarchy, so we want developers to be able to maintain that project
|
11
|
-
structure while leveraging the power of the REST Framework. Also, different controllers which
|
12
|
-
inherit from the same parent often need different REST Framework mixins. For these reasons, REST
|
13
|
-
Framework provides the controller functionality as modules that you mix into your controllers.
|
14
|
-
|
15
|
-
## Response Rendering
|
16
|
-
|
17
|
-
Before we go into the various controller mixins, one of the core capabilities of the REST Framework
|
18
|
-
is to provide system-consumable responses along side a browsable API for developers. While you can
|
19
|
-
use Rails' builtin rendering tools, such as `render`, the REST Framework provides a rendering helper
|
20
|
-
called `api_response`. This helper allows you to return a browsable API response for the `html`
|
21
|
-
format which shows you what the JSON/XML response would look like, along with faster and lighter
|
22
|
-
responses for `json` and `xml` formats.
|
23
|
-
|
24
|
-
Here is an example:
|
25
|
-
|
26
|
-
```ruby
|
27
|
-
class ApiController < ApplicationController
|
28
|
-
include RESTFramework::BaseControllerMixin
|
29
|
-
self.extra_actions = {test: :get}
|
30
|
-
|
31
|
-
def test
|
32
|
-
api_response({message: "Test successful!"})
|
33
|
-
end
|
34
|
-
end
|
35
|
-
```
|
36
|
-
|
37
|
-
## `BaseControllerMixin`
|
38
|
-
|
39
|
-
To transform a controller into the simplest possible RESTful controller, you can include
|
40
|
-
`BaseControllerMixin`, which provides a simple `root` action so it can be used at the API root.
|
41
|
-
|
42
|
-
```ruby
|
43
|
-
class ApiController < ApplicationController
|
44
|
-
include RESTFramework::BaseControllerMixin
|
45
|
-
end
|
46
|
-
```
|
47
|
-
|
48
|
-
### Controller Attributes
|
49
|
-
|
50
|
-
You can customize the behavior of `BaseControllerMixin` by setting or mutating various class
|
51
|
-
attributes.
|
52
|
-
|
53
|
-
#### `singleton_controller`
|
54
|
-
|
55
|
-
This property primarily controls the routes that are generated for a RESTful controller. If you use
|
56
|
-
`api_resource`/`api_resources` to define whether the generates routes are for a collection or for
|
57
|
-
a single member, then you do not need to use this property. However, if you are autogenerating those
|
58
|
-
routers, then `singleton_controller` will tell REST Framework whether to provide collection routes
|
59
|
-
(when `singleton_controller` is falsy) or member routes (when `singleton_controller` is truthy). To
|
60
|
-
read more about singular vs plural routing, see Rails' documentation here:
|
61
|
-
https://guides.rubyonrails.org/routing.html#singular-resources.
|
62
|
-
|
63
|
-
#### `extra_actions`
|
64
|
-
|
65
|
-
This property defines extra actions on the controller to be routed. It is a hash of
|
66
|
-
`endpoint -> method(s)` (where `method(s)` can be a method symbol or an array of method symbols).
|
67
|
-
|
68
|
-
```ruby
|
69
|
-
class ApiController < ApplicationController
|
70
|
-
include RESTFramework::BaseControllerMixin
|
71
|
-
self.extra_actions = {test: :get}
|
72
|
-
|
73
|
-
def test
|
74
|
-
api_response({message: "Test successful!"})
|
75
|
-
end
|
76
|
-
end
|
77
|
-
```
|
78
|
-
|
79
|
-
Or with multiple methods:
|
80
|
-
|
81
|
-
```ruby
|
82
|
-
class ApiController < ApplicationController
|
83
|
-
include RESTFramework::BaseControllerMixin
|
84
|
-
self.extra_actions = {test: [:get, :post]}
|
85
|
-
|
86
|
-
def test
|
87
|
-
api_response({message: "Test successful!"})
|
88
|
-
end
|
89
|
-
end
|
90
|
-
```
|
91
|
-
|
92
|
-
If your action conflicts with a builtin method, then you can also override the path:
|
93
|
-
|
94
|
-
```ruby
|
95
|
-
class ApiController < ApplicationController
|
96
|
-
include RESTFramework::BaseControllerMixin
|
97
|
-
|
98
|
-
# This will route `test_action` to `/test`, in case there is already a `test` method that cannot
|
99
|
-
# be overridden.
|
100
|
-
self.extra_actions = {test_action: {path: :test, methods: :get}}
|
101
|
-
|
102
|
-
def test_action
|
103
|
-
api_response({message: "Test successful!"})
|
104
|
-
end
|
105
|
-
end
|
106
|
-
```
|
107
|
-
|
108
|
-
## `ModelControllerMixin`
|
109
|
-
|
110
|
-
`ModelControllerMixin` assists with providing the standard model CRUD (create, read, update,
|
111
|
-
destroy) for your controller. This is the most commonly used mixin since it provides default
|
112
|
-
behavior for models which matches Rails' default routing.
|
113
|
-
|
114
|
-
```ruby
|
115
|
-
class Api::MoviesController < ApiController
|
116
|
-
include RESTFramework::ModelControllerMixin
|
117
|
-
end
|
118
|
-
```
|
119
|
-
|
120
|
-
By default, all columns and associations are included in `self.fields`, which can be helpful when
|
121
|
-
developing an administrative API. For user-facing APIs, however, `self.fields` should always be
|
122
|
-
explicitly defined.
|
123
|
-
|
124
|
-
### Controller Attributes
|
125
|
-
|
126
|
-
You can customize the behavior of `ModelControllerMixin` by setting or mutating various class
|
127
|
-
attributes.
|
128
|
-
|
129
|
-
#### `model`
|
130
|
-
|
131
|
-
The `model` property allows you to define the model if it is not obvious from the controller name.
|
132
|
-
|
133
|
-
```ruby
|
134
|
-
class Api::CoolMoviesController < ApiController
|
135
|
-
include RESTFramework::ModelControllerMixin
|
136
|
-
|
137
|
-
self.model = Movie
|
138
|
-
end
|
139
|
-
```
|
140
|
-
|
141
|
-
#### `recordset`
|
142
|
-
|
143
|
-
The `recordset` property allows you to define the set of records this API should be limited to. If
|
144
|
-
you need to change the recordset based on properties of the request, then you can override the
|
145
|
-
`get_recordset()` method.
|
146
|
-
|
147
|
-
```ruby
|
148
|
-
class Api::CoolMoviesController < ApiController
|
149
|
-
include RESTFramework::ModelControllerMixin
|
150
|
-
|
151
|
-
self.recordset = Movie.where(cool: true).order({id: :asc})
|
152
|
-
end
|
153
|
-
```
|
154
|
-
|
155
|
-
#### `extra_member_actions`
|
156
|
-
|
157
|
-
The `extra_member_actions` property allows you to define additional actions on individual records.
|
158
|
-
|
159
|
-
```ruby
|
160
|
-
class Api::MoviesController < ApiController
|
161
|
-
include RESTFramework::ModelControllerMixin
|
162
|
-
|
163
|
-
self.extra_member_actions = {disable: :post}
|
164
|
-
|
165
|
-
def disable
|
166
|
-
@record = self.get_record # REST Framework will rescue ActiveRecord::RecordNotFound
|
167
|
-
|
168
|
-
# REST Framework will rescue ActiveRecord::RecordInvalid or ActiveRecord::RecordNotSaved
|
169
|
-
@record.update!(enabled: false)
|
170
|
-
|
171
|
-
return api_response(@record)
|
172
|
-
end
|
173
|
-
end
|
174
|
-
```
|
175
|
-
|
176
|
-
#### `fields`
|
177
|
-
|
178
|
-
The `fields` property defines the default fields for serialization and for parameters allowed from
|
179
|
-
the body or query string.
|
180
|
-
|
181
|
-
```ruby
|
182
|
-
class Api::MoviesController < ApiController
|
183
|
-
include RESTFramework::ModelControllerMixin
|
184
|
-
|
185
|
-
self.fields = [:id, :name]
|
186
|
-
end
|
187
|
-
```
|
188
|
-
|
189
|
-
#### `action_fields`
|
190
|
-
|
191
|
-
The `action_fields` property is similar to `fields`, but allows you to define different fields for
|
192
|
-
different actions. A good example is to serialize expensive computed properties only in the `show`
|
193
|
-
action, but not in the `list` action (where many records are serialized).
|
194
|
-
|
195
|
-
```ruby
|
196
|
-
class Api::MoviesController < ApiController
|
197
|
-
include RESTFramework::ModelControllerMixin
|
198
|
-
|
199
|
-
self.fields = [:id, :name]
|
200
|
-
self.action_fields = {
|
201
|
-
show: [:id, :name, :some_expensive_computed_property],
|
202
|
-
}
|
203
|
-
end
|
204
|
-
```
|
205
|
-
|
206
|
-
#### `native_serializer_config`
|
207
|
-
|
208
|
-
These properties define the serializer configuration if you are using the native `ActiveModel`
|
209
|
-
serializer. You can also specify serializers for singular/plural
|
210
|
-
|
211
|
-
```ruby
|
212
|
-
class Api::MoviesController < ApiController
|
213
|
-
include RESTFramework::ModelControllerMixin
|
214
|
-
|
215
|
-
self.native_serializer_config = {
|
216
|
-
only: [:id, :name],
|
217
|
-
methods: [:active, :some_expensive_computed_property],
|
218
|
-
include: {cast_members: { only: [:id, :name], methods: [:net_worth] }},
|
219
|
-
}
|
220
|
-
|
221
|
-
# Or you could configure a default and a plural serializer:
|
222
|
-
self.native_serializer_plural_config = {
|
223
|
-
only: [:id, :name],
|
224
|
-
methods: [:active],
|
225
|
-
include: {cast_members: { only: [:id, :name], methods: [:net_worth] }},
|
226
|
-
}
|
227
|
-
self.native_serializer_config = {
|
228
|
-
only: [:id, :name],
|
229
|
-
methods: [:active, :some_expensive_computed_property],
|
230
|
-
include: {cast_members: { only: [:id, :name], methods: [:net_worth] }},
|
231
|
-
}
|
232
|
-
|
233
|
-
# Or you could configure a default and a singular serializer:
|
234
|
-
self.native_serializer_config = {
|
235
|
-
only: [:id, :name],
|
236
|
-
methods: [:active],
|
237
|
-
include: {cast_members: { only: [:id, :name], methods: [:net_worth] }},
|
238
|
-
}
|
239
|
-
self.native_serializer_singular_config = {
|
240
|
-
only: [:id, :name],
|
241
|
-
methods: [:active, :some_expensive_computed_property],
|
242
|
-
include: {cast_members: { only: [:id, :name], methods: [:net_worth] }},
|
243
|
-
}
|
244
|
-
end
|
245
|
-
```
|
246
|
-
|
247
|
-
#### `allowed_parameters` / `allowed_action_parameters`
|
248
|
-
|
249
|
-
These properties define the permitted parameters to be used in the request body for create/update
|
250
|
-
actions. If you need different allowed parameters, then you can also override the
|
251
|
-
`get_create_params` or `get_update_params` methods.
|
252
|
-
|
253
|
-
```ruby
|
254
|
-
class Api::MoviesController < ApiController
|
255
|
-
include RESTFramework::ModelControllerMixin
|
256
|
-
|
257
|
-
self.allowed_parameters = [:name]
|
258
|
-
end
|
259
|
-
```
|
260
|
-
|
261
|
-
#### `create_from_recordset` (default: `true`)
|
262
|
-
|
263
|
-
The `create_from_recordset` attribute (`true` by default) is a boolean to control the behavior in
|
264
|
-
the `create` action. If it is disabled, records will not be created from the filtered recordset, but
|
265
|
-
rather will be created directly from the model interface.
|
266
|
-
|
267
|
-
For example, if this is your controller:
|
268
|
-
|
269
|
-
```ruby
|
270
|
-
class Api::CoolMoviesController < ApiController
|
271
|
-
include RESTFramework::ModelControllerMixin
|
272
|
-
|
273
|
-
def get_recordset
|
274
|
-
return Movie.where(cool: true)
|
275
|
-
end
|
276
|
-
end
|
277
|
-
```
|
278
|
-
|
279
|
-
Then if you hit the `create` action with the payload `{name: "Superman"}`, it will also set `cool`
|
280
|
-
to `true` on the new record, because that property is inherited from the recordset.
|
281
|
-
|
282
|
-
## `ReadOnlyModelControllerMixin`
|
283
|
-
|
284
|
-
`ReadOnlyModelControllerMixin` only enables list/show actions. In this example, since we're naming
|
285
|
-
this controller in a way that doesn't make the model obvious, we can set that explicitly:
|
286
|
-
|
287
|
-
```ruby
|
288
|
-
class Api::ReadOnlyMoviesController < ApiController
|
289
|
-
include RESTFramework::ReadOnlyModelControllerMixin
|
290
|
-
|
291
|
-
self.model = Movie
|
292
|
-
end
|
293
|
-
```
|
@@ -1,159 +0,0 @@
|
|
1
|
-
/********************************
|
2
|
-
* START OF LIB/DOCS COMMON CSS *
|
3
|
-
********************************/
|
4
|
-
|
5
|
-
:root {
|
6
|
-
--rrf-red: #900;
|
7
|
-
--rrf-red-hover: #5f0c0c;
|
8
|
-
--rrf-light-red: #db2525;
|
9
|
-
--rrf-light-red-hover: #b80404;
|
10
|
-
}
|
11
|
-
#rrfAccentBar {
|
12
|
-
background-color: var(--rrf-red);
|
13
|
-
height: .3em;
|
14
|
-
}
|
15
|
-
header nav { background-color: black; }
|
16
|
-
|
17
|
-
/* Header adjustments. */
|
18
|
-
h1 { font-size: 2rem; }
|
19
|
-
h2 { font-size: 1.7rem; }
|
20
|
-
h3 { font-size: 1.5rem; }
|
21
|
-
h4 { font-size: 1.3rem; }
|
22
|
-
h5 { font-size: 1.1rem; }
|
23
|
-
h6 { font-size: 1rem; }
|
24
|
-
h1, h2, h3, h4, h5, h6 {
|
25
|
-
color: var(--rrf-red);
|
26
|
-
}
|
27
|
-
html[data-bs-theme="dark"] h1,
|
28
|
-
html[data-bs-theme="dark"] h2,
|
29
|
-
html[data-bs-theme="dark"] h3,
|
30
|
-
html[data-bs-theme="dark"] h4,
|
31
|
-
html[data-bs-theme="dark"] h5,
|
32
|
-
html[data-bs-theme="dark"] h6 {
|
33
|
-
color: var(--rrf-light-red);
|
34
|
-
}
|
35
|
-
|
36
|
-
/* Improve code and code blocks. */
|
37
|
-
pre code {
|
38
|
-
display: block;
|
39
|
-
overflow-x: auto;
|
40
|
-
}
|
41
|
-
code, .trix-content pre {
|
42
|
-
padding: .5em !important;
|
43
|
-
background-color: #eee !important;
|
44
|
-
border: 1px solid #aaa;
|
45
|
-
border-radius: 3px;
|
46
|
-
}
|
47
|
-
p code {
|
48
|
-
padding: .1em .3em !important;
|
49
|
-
}
|
50
|
-
html[data-bs-theme="dark"] code, html[data-bs-theme="dark"] .trix-content pre {
|
51
|
-
background-color: #2b2b2b !important;
|
52
|
-
}
|
53
|
-
|
54
|
-
/* Anchors */
|
55
|
-
a:not(.nav-link) {
|
56
|
-
text-decoration: none;
|
57
|
-
color: var(--rrf-red);
|
58
|
-
}
|
59
|
-
a:hover:not(.nav-link) {
|
60
|
-
text-decoration: underline;
|
61
|
-
color: var(--rrf-red-hover);
|
62
|
-
}
|
63
|
-
html[data-bs-theme="dark"] a:not(.nav-link) { color: var(--rrf-light-red); }
|
64
|
-
html[data-bs-theme="dark"] a:hover:not(.nav-link) { color: var(--rrf-light-red-hover); }
|
65
|
-
|
66
|
-
/******************************
|
67
|
-
* END OF LIB/DOCS COMMON CSS *
|
68
|
-
******************************/
|
69
|
-
|
70
|
-
/* Header adjustments. */
|
71
|
-
h1, h2, h3, h4, h5, h6 {
|
72
|
-
width: 100%;
|
73
|
-
font-weight: normal;
|
74
|
-
margin-top: 1.8rem;
|
75
|
-
margin-bottom: 1.2rem;
|
76
|
-
}
|
77
|
-
h1 a:not(:hover),
|
78
|
-
h2 a:not(:hover),
|
79
|
-
h3 a:not(:hover),
|
80
|
-
h4 a:not(:hover),
|
81
|
-
h5 a:not(:hover),
|
82
|
-
h6 a:not(:hover) {
|
83
|
-
color: #ddd;
|
84
|
-
}
|
85
|
-
html[data-bs-theme="dark"] h1 a:not(:hover),
|
86
|
-
html[data-bs-theme="dark"] h2 a:not(:hover),
|
87
|
-
html[data-bs-theme="dark"] h3 a:not(:hover),
|
88
|
-
html[data-bs-theme="dark"] h4 a:not(:hover),
|
89
|
-
html[data-bs-theme="dark"] h5 a:not(:hover),
|
90
|
-
html[data-bs-theme="dark"] h6 a:not(:hover) {
|
91
|
-
color: #444;
|
92
|
-
}
|
93
|
-
h1 a:hover, h2 a:hover, h3 a:hover, h4 a:hover, h5 a:hover, h6 a:hover {
|
94
|
-
text-decoration: none !important;
|
95
|
-
}
|
96
|
-
|
97
|
-
/* Navbar */
|
98
|
-
.navbar .navbar-toggler {
|
99
|
-
margin: .2em 0;
|
100
|
-
padding: .2em .3em;
|
101
|
-
border: none;
|
102
|
-
}
|
103
|
-
.navbar .navbar-toggler .navbar-toggler-icon {
|
104
|
-
height: 1.1em;
|
105
|
-
width: 1.1em;
|
106
|
-
}
|
107
|
-
.navbar .navbar-nav .nav-item .nav-link {
|
108
|
-
padding: .45em .6em;
|
109
|
-
}
|
110
|
-
.navbar .navbar-nav .nav-item .nav-link:hover {
|
111
|
-
background-color: #262a2f;
|
112
|
-
}
|
113
|
-
.navbar .dropdown-menu a.dropdown-item {
|
114
|
-
font-size: .9em;
|
115
|
-
padding: .2em .8em;
|
116
|
-
}
|
117
|
-
|
118
|
-
/* Headers table. */
|
119
|
-
.headers-table {
|
120
|
-
padding: .5em 1em;
|
121
|
-
background-color: #eee;
|
122
|
-
border: 1px solid #aaa;
|
123
|
-
border-radius: .3em;
|
124
|
-
font-size: .9em;
|
125
|
-
}
|
126
|
-
html[data-bs-theme="dark"] .headers-table {
|
127
|
-
background-color: #2b2b2b;
|
128
|
-
}
|
129
|
-
.headers-table:empty { display: none; }
|
130
|
-
.headers-table ul {
|
131
|
-
list-style-type: none;
|
132
|
-
margin: 0;
|
133
|
-
padding-left: 0;
|
134
|
-
padding-right: .6em;
|
135
|
-
}
|
136
|
-
.headers-table ul li { margin: .3em 0; }
|
137
|
-
.headers-table ul ul { padding-left: .8em; padding-right: 0; }
|
138
|
-
.headers-table > ul > li {
|
139
|
-
font-weight: bold;
|
140
|
-
}
|
141
|
-
|
142
|
-
/* Style the github and mode component. */
|
143
|
-
#rrfGithubAndModeWrapper {
|
144
|
-
height: 2.4em;
|
145
|
-
}
|
146
|
-
#rrfGithubComponent {
|
147
|
-
display: inline-block;
|
148
|
-
position: relative;
|
149
|
-
top: .6em;
|
150
|
-
}
|
151
|
-
#rrfModeComponent .dropdown-toggle {
|
152
|
-
float: right;
|
153
|
-
}
|
154
|
-
#rrfModeComponent .dropdown-menu {
|
155
|
-
position: absolute;
|
156
|
-
right: 0;
|
157
|
-
left: auto;
|
158
|
-
top: 100%;
|
159
|
-
}
|