rest_framework 0.9.8 → 0.9.10
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 +76 -54
- data/VERSION +1 -1
- data/app/views/rest_framework/_head.html.erb +1 -2
- data/app/views/rest_framework/_heading.html.erb +1 -1
- data/app/views/rest_framework/_payloads.html.erb +1 -1
- data/app/views/rest_framework/_request_metadata.html.erb +2 -4
- data/app/views/rest_framework/_routes_and_forms.html.erb +2 -2
- data/lib/rest_framework/errors/base_error.rb +5 -0
- data/lib/rest_framework/errors/nil_passed_to_api_response_error.rb +14 -0
- data/lib/rest_framework/errors/unknown_model_error.rb +18 -0
- data/lib/rest_framework/errors.rb +4 -28
- data/lib/rest_framework/filters/model_ordering_filter.rb +3 -1
- data/lib/rest_framework/filters/model_query_filter.rb +21 -7
- data/lib/rest_framework/mixins/bulk_model_controller_mixin.rb +9 -15
- data/lib/rest_framework/mixins/model_controller_mixin.rb +27 -50
- data/lib/rest_framework/paginators/page_number_paginator.rb +12 -1
- data/lib/rest_framework/version.rb +1 -1
- data/lib/rest_framework.rb +15 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2db255f618d449ef621f1337d182993c21b5d8d87e5e8c960c8b42093248c91d
|
4
|
+
data.tar.gz: 4612f2e11733ae69e418cd2877b8150e2a1dbd4fb0cd34ed29727dcec007f305
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 04457aa989c261900e8e1e55829dbb3cc3aed3afda800433944497996a330a92fdff403351be8e4b83b7b44fea658ecd57a3ab20b4b9e41587398a52b8203680
|
7
|
+
data.tar.gz: e67d99fd29629b7bf31e37f3f27edc8e7d5d222172e6b91e5d2340eec0d891b340029097dc798411410d9c5ecbdffe316521c0dd47e8810c6fe897b0ff59624b
|
data/README.md
CHANGED
@@ -7,12 +7,10 @@
|
|
7
7
|
|
8
8
|
A framework for DRY RESTful APIs in Ruby on Rails.
|
9
9
|
|
10
|
-
**The Problem**: Building controllers for APIs usually involves writing a lot of redundant CRUD
|
11
|
-
|
12
|
-
filtering, and pagination can be tedious.
|
10
|
+
**The Problem**: Building controllers for APIs usually involves writing a lot of redundant CRUD logic, and routing them can be obnoxious.
|
11
|
+
Building and maintaining features like ordering, filtering, and pagination can be tedious.
|
13
12
|
|
14
|
-
**The Solution**: This framework implements browsable API responses, CRUD actions for your models,
|
15
|
-
and features like ordering/filtering/pagination, so you can focus on building awesome APIs.
|
13
|
+
**The Solution**: This framework implements browsable API responses, CRUD actions for your models, and features like ordering/filtering/pagination, so you can focus on building awesome APIs.
|
16
14
|
|
17
15
|
Website/Guide: [rails-rest-framework.com](https://rails-rest-framework.com)
|
18
16
|
|
@@ -27,87 +25,119 @@ YARD Docs: [rubydoc.info/gems/rest_framework](https://rubydoc.info/gems/rest_fra
|
|
27
25
|
Add this line to your application's Gemfile:
|
28
26
|
|
29
27
|
```ruby
|
30
|
-
gem
|
28
|
+
gem "rest_framework"
|
31
29
|
```
|
32
30
|
|
33
|
-
And then
|
31
|
+
And then run:
|
34
32
|
|
35
33
|
```shell
|
36
|
-
|
34
|
+
bundle install
|
37
35
|
```
|
38
36
|
|
39
|
-
|
37
|
+
## Quick Usage Tutorial
|
40
38
|
|
41
|
-
|
42
|
-
$ gem install rest_framework
|
43
|
-
```
|
39
|
+
This section provides some simple examples to quickly get you started using the framework.
|
44
40
|
|
45
|
-
|
41
|
+
For the purpose of this example, you'll want to add an `api_controller.rb` to your controllers, as well as a directory for the resources:
|
42
|
+
|
43
|
+
```text
|
44
|
+
controllers/
|
45
|
+
├─ api_controller.rb
|
46
|
+
└─ api/
|
47
|
+
├─ root_controller.rb
|
48
|
+
├─ movies_controller.rb
|
49
|
+
└─ users_controller.rb
|
50
|
+
```
|
46
51
|
|
47
52
|
### Controller Mixins
|
48
53
|
|
49
|
-
|
50
|
-
`ReadOnlyModelControllerMixin`, or `ModelControllerMixin`. `BaseControllerMixin` provides a `root`
|
51
|
-
action and a simple interface for routing arbitrary additional actions:
|
54
|
+
The root `ApiController` can include any common behavior you want to share across all your API controllers:
|
52
55
|
|
53
56
|
```ruby
|
54
57
|
class ApiController < ApplicationController
|
55
58
|
include RESTFramework::BaseControllerMixin
|
59
|
+
|
60
|
+
# Setting up a paginator class here makes more sense than defining it on every child controller.
|
61
|
+
self.paginator_class = RESTFramework::PageNumberPaginator
|
62
|
+
|
63
|
+
# The page_size attribute doesn't exist on the `BaseControllerMixin`, but for child controllers
|
64
|
+
# that include the `ModelControllerMixin`, they will inherit this attribute and will not overwrite
|
65
|
+
# it.
|
66
|
+
class_attribute(:page_size, default: 30)
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
A root controller can provide actions that exist on the root of your API.
|
71
|
+
It's best to define a dedicated root controller, rather than using the `ApiController` for this purpose, so that actions don't propagate to child controllers:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
class Api::RootController < ApiController
|
56
75
|
self.extra_actions = {test: :get}
|
57
76
|
|
77
|
+
def root
|
78
|
+
return api_response(
|
79
|
+
{
|
80
|
+
message: "Welcome to the API.",
|
81
|
+
how_to_authenticate: <<~END.lines.map(&:strip).join(" "),
|
82
|
+
You can use this API with your normal login session. Otherwise, you can insert your API
|
83
|
+
key into a Bearer Authorization header, or into the URL parameters with the name
|
84
|
+
`api_key`.
|
85
|
+
END
|
86
|
+
},
|
87
|
+
)
|
88
|
+
end
|
89
|
+
|
58
90
|
def test
|
59
|
-
|
91
|
+
return api_response({message: "Hello, world!"})
|
60
92
|
end
|
61
93
|
end
|
62
94
|
```
|
63
95
|
|
64
|
-
|
96
|
+
And here is an example of a resource controller:
|
65
97
|
|
66
98
|
```ruby
|
67
99
|
class Api::MoviesController < ApiController
|
68
100
|
include RESTFramework::ModelControllerMixin
|
69
101
|
|
70
|
-
self.
|
102
|
+
self.fields = [:id, :name, :release_date, :enabled]
|
103
|
+
self.extra_member_actions = {first: :get}
|
104
|
+
|
105
|
+
def first
|
106
|
+
# Always use the bang method, since the framework will rescue `RecordNotFound` and return a
|
107
|
+
# sensible error response.
|
108
|
+
return api_response(self.get_records.first!)
|
109
|
+
end
|
110
|
+
|
111
|
+
def get_recordset
|
112
|
+
return Movie.where(enabled: true)
|
113
|
+
end
|
71
114
|
end
|
72
115
|
```
|
73
116
|
|
74
|
-
`
|
75
|
-
|
117
|
+
When `fields` is nil, then it will default to all columns.
|
118
|
+
The `fields` attribute can also be a hash to include or exclude fields rather than defining them manually:
|
76
119
|
|
77
120
|
```ruby
|
78
|
-
class Api::
|
79
|
-
include RESTFramework::
|
121
|
+
class Api::UsersController < ApiController
|
122
|
+
include RESTFramework::ModelControllerMixin
|
80
123
|
|
81
|
-
self.
|
124
|
+
self.fields = {include: [:calculated_popularity], exclude: [:impersonation_token]}
|
82
125
|
end
|
83
126
|
```
|
84
127
|
|
85
|
-
Note that you can also override the `get_recordset` instance method to override the API behavior
|
86
|
-
dynamically per-request.
|
87
|
-
|
88
128
|
### Routing
|
89
129
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
also use `rest_root` to route the root of your API:
|
130
|
+
Use `rest_route` for non-resourceful controllers, or `rest_resource` / `rest_resources` resourceful routers.
|
131
|
+
These routers add some features to the Rails builtin `resource`/`resources` routers, such as automatically routing extra actions defined on the controller.
|
132
|
+
To route the root, use `rest_root`.
|
94
133
|
|
95
134
|
```ruby
|
96
135
|
Rails.application.routes.draw do
|
97
|
-
|
98
|
-
|
99
|
-
rest_resources :movies
|
100
|
-
rest_resources :users
|
101
|
-
end
|
102
|
-
end
|
103
|
-
```
|
104
|
-
|
105
|
-
Or if you want the API root to be routed to `Api::RootController#root`:
|
136
|
+
# If you wanted to route actions from the `ApiController`, then you would use this:
|
137
|
+
# rest_root :api # Will find `api_controller` and route the `root` action to '/api'.
|
106
138
|
|
107
|
-
```ruby
|
108
|
-
Rails.application.routes.draw do
|
109
139
|
namespace :api do
|
110
|
-
rest_root #
|
140
|
+
rest_root # Will route `Api::RootController#root` to '/' in this namespace ('/api').
|
111
141
|
rest_resources :movies
|
112
142
|
rest_resources :users
|
113
143
|
end
|
@@ -116,15 +146,7 @@ end
|
|
116
146
|
|
117
147
|
## Development/Testing
|
118
148
|
|
119
|
-
After you clone the repository, cd'ing into the directory should create a new gemset if you are
|
120
|
-
|
121
|
-
|
122
|
-
To run the test suite:
|
123
|
-
|
124
|
-
```shell
|
125
|
-
$ rails test
|
126
|
-
```
|
149
|
+
After you clone the repository, cd'ing into the directory should create a new gemset if you are using RVM.
|
150
|
+
Then run `bin/setup` to install the appropriate gems and set things up.
|
127
151
|
|
128
|
-
The top-level `bin/rails` proxies all Rails commands to the test project, so you can operate it via
|
129
|
-
the usual commands. Ensure you run `rails db:setup` before running `rails server` or
|
130
|
-
`rails console`.
|
152
|
+
The top-level `bin/rails` proxies all Rails commands to the test project, so you can operate it via the usual commands (e.g., `rails test`, `rails server` and `rails console`).
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.9.
|
1
|
+
0.9.10
|
@@ -29,7 +29,6 @@
|
|
29
29
|
h1, h2, h3, h4, h5, h6 {
|
30
30
|
color: var(--rrf-red);
|
31
31
|
font-weight: normal;
|
32
|
-
margin-bottom: 0;
|
33
32
|
}
|
34
33
|
html[data-bs-theme="dark"] h1,
|
35
34
|
html[data-bs-theme="dark"] h2,
|
@@ -290,7 +289,7 @@
|
|
290
289
|
|
291
290
|
// Convert plain-text links to anchor tag links.
|
292
291
|
function rrfLinkify(text) {
|
293
|
-
return text.replace(/(https?:\/\/[^\s<>"]+)/g, "<a href=\"$1\"
|
292
|
+
return text.replace(/(https?:\/\/[^\s<>"]+)/g, "<a href=\"$1\">$1</a>")
|
294
293
|
}
|
295
294
|
|
296
295
|
// Replace the document when doing form submission (mainly to support PUT/PATCH/DELETE).
|
@@ -1,7 +1,7 @@
|
|
1
1
|
<div class="row">
|
2
2
|
<div>
|
3
3
|
<%= render partial: "rest_framework/heading/actions" if @route_groups.present? %>
|
4
|
-
<h1
|
4
|
+
<h1 style="margin: 0"><%= @heading_title || @title %></h1>
|
5
5
|
<% if @description.present? %>
|
6
6
|
<br><br><p style="display: inline-block; margin-bottom: 0"><%= @description %></p>
|
7
7
|
<% end %>
|
@@ -17,7 +17,7 @@
|
|
17
17
|
<% end %>
|
18
18
|
</ul>
|
19
19
|
</div>
|
20
|
-
<div class="tab-content
|
20
|
+
<div class="tab-content">
|
21
21
|
<div class="tab-pane fade show active" id="tab-json" role="tabpanel">
|
22
22
|
<% if @json_payload.present? %>
|
23
23
|
<div><pre class="rrf-copy"><code class="language-json"><%=
|
@@ -1,16 +1,14 @@
|
|
1
1
|
<div class="row">
|
2
2
|
<div>
|
3
|
-
<pre><code><%
|
3
|
+
<pre class="mb-2"><code class="language-plaintext"><%
|
4
4
|
concat request.request_method
|
5
5
|
if request.method != request.request_method
|
6
6
|
concat " (via #{request.method})"
|
7
7
|
end
|
8
8
|
concat " #{request.path}"
|
9
9
|
%></code></pre>
|
10
|
-
<pre><code><%
|
10
|
+
<pre><code class="language-plaintext"><%
|
11
11
|
concat "HTTP #{response.status} #{response.message}"
|
12
|
-
concat "\n"
|
13
|
-
concat "Content-Type: #{response.content_type}"
|
14
12
|
%></code></pre>
|
15
13
|
</div>
|
16
14
|
</div>
|
@@ -34,7 +34,7 @@
|
|
34
34
|
<% end %>
|
35
35
|
</ul>
|
36
36
|
</div>
|
37
|
-
<div class="tab-content
|
37
|
+
<div class="tab-content">
|
38
38
|
<div class="tab-pane fade show active" id="tab-routes" role="tabpanel">
|
39
39
|
<%= render partial: "rest_framework/routes_and_forms/routes" %>
|
40
40
|
</div>
|
@@ -49,4 +49,4 @@
|
|
49
49
|
</div>
|
50
50
|
<% end %>
|
51
51
|
</div>
|
52
|
-
</div>
|
52
|
+
</div>
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class RESTFramework::Errors::NilPassedToAPIResponseError < RESTFramework::Errors::BaseError
|
2
|
+
def message
|
3
|
+
return <<~MSG.split("\n").join(" ")
|
4
|
+
Payload of `nil` was passed to `api_response`; this is unsupported. If you want a blank
|
5
|
+
response, pass `''` (an empty string) as the payload. If this was the result of a `find_by`
|
6
|
+
(or similar Active Record method) not finding a record, you should use the bang version (e.g.,
|
7
|
+
`find_by!`) to raise `ActiveRecord::RecordNotFound`, which the REST controller will catch and
|
8
|
+
return an appropriate error response.
|
9
|
+
MSG
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Alias for convenience.
|
14
|
+
RESTFramework::NilPassedToAPIResponseError = RESTFramework::Errors::NilPassedToAPIResponseError
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class RESTFramework::Errors::UnknownModelError < RESTFramework::Errors::BaseError
|
2
|
+
def initialize(controller_class)
|
3
|
+
super()
|
4
|
+
@controller_class = controller_class
|
5
|
+
end
|
6
|
+
|
7
|
+
def message
|
8
|
+
return <<~MSG.split("\n").join(" ")
|
9
|
+
The model class for `#{@controller_class}` could not be determined. Any controller that
|
10
|
+
includes `RESTFramework::BaseModelControllerMixin` (directly or indirectly) must either set
|
11
|
+
the `model` attribute on the controller, or the model must be deducible from the controller
|
12
|
+
name (e.g., `UsersController` could resolve to the `User` model).
|
13
|
+
MSG
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Alias for convenience.
|
18
|
+
RESTFramework::UnknownModelError = RESTFramework::Errors::UnknownModelError
|
@@ -1,31 +1,7 @@
|
|
1
|
-
|
2
|
-
class RESTFramework::Error < StandardError
|
1
|
+
module RESTFramework::Errors
|
3
2
|
end
|
4
3
|
|
5
|
-
|
6
|
-
def message
|
7
|
-
return <<~MSG.split("\n").join(" ")
|
8
|
-
Payload of `nil` was passed to `api_response`; this is unsupported. If you want a blank
|
9
|
-
response, pass `''` (an empty string) as the payload. If this was the result of a `find_by`
|
10
|
-
(or similar Active Record method) not finding a record, you should use the bang version (e.g.,
|
11
|
-
`find_by!`) to raise `ActiveRecord::RecordNotFound`, which the REST controller will catch and
|
12
|
-
return an appropriate error response.
|
13
|
-
MSG
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
class RESTFramework::UnknownModelError < RESTFramework::Error
|
18
|
-
def initialize(controller_class)
|
19
|
-
super()
|
20
|
-
@controller_class = controller_class
|
21
|
-
end
|
4
|
+
require_relative "errors/base_error"
|
22
5
|
|
23
|
-
|
24
|
-
|
25
|
-
The model class for `#{@controller_class}` could not be determined. Any controller that
|
26
|
-
includes `RESTFramework::BaseModelControllerMixin` (directly or indirectly) must either set
|
27
|
-
the `model` attribute on the controller, or the model must be deducible from the controller
|
28
|
-
name (e.g., `UsersController` could resolve to the `User` model).
|
29
|
-
MSG
|
30
|
-
end
|
31
|
-
end
|
6
|
+
require_relative "errors/nil_passed_to_api_response_error"
|
7
|
+
require_relative "errors/unknown_model_error"
|
@@ -15,6 +15,7 @@ class RESTFramework::Filters::ModelOrderingFilter < RESTFramework::Filters::Base
|
|
15
15
|
|
16
16
|
if order_string.present?
|
17
17
|
ordering = {}.with_indifferent_access
|
18
|
+
|
18
19
|
order_string.split(",").each do |field|
|
19
20
|
if field[0] == "-"
|
20
21
|
column = field[1..-1]
|
@@ -24,10 +25,11 @@ class RESTFramework::Filters::ModelOrderingFilter < RESTFramework::Filters::Base
|
|
24
25
|
direction = :asc
|
25
26
|
end
|
26
27
|
|
27
|
-
next if !column.in?(fields) && column.split(".").first.in?(fields)
|
28
|
+
next if !column.in?(fields) && !column.split(".").first.in?(fields)
|
28
29
|
|
29
30
|
ordering[column] = direction
|
30
31
|
end
|
32
|
+
|
31
33
|
return ordering
|
32
34
|
end
|
33
35
|
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# A simple filtering backend that supports filtering a recordset based on query parameters.
|
2
2
|
class RESTFramework::Filters::ModelQueryFilter < RESTFramework::Filters::BaseFilter
|
3
|
+
NIL_VALUES = ["nil", "null"].freeze
|
4
|
+
|
3
5
|
# Get a list of filterset fields for the current action.
|
4
6
|
def _get_fields
|
5
7
|
# Always return a list of strings; `@controller.get_fields` already does this.
|
@@ -9,8 +11,9 @@ class RESTFramework::Filters::ModelQueryFilter < RESTFramework::Filters::BaseFil
|
|
9
11
|
# Filter params for keys allowed by the current action's filterset_fields/fields config.
|
10
12
|
def _get_filter_params
|
11
13
|
fields = self._get_fields
|
14
|
+
includes = []
|
12
15
|
|
13
|
-
|
16
|
+
filter_params = @controller.request.query_parameters.select { |p, _|
|
14
17
|
# Remove any trailing `__in` from the field name.
|
15
18
|
field = p.chomp("__in")
|
16
19
|
|
@@ -20,7 +23,12 @@ class RESTFramework::Filters::ModelQueryFilter < RESTFramework::Filters::BaseFil
|
|
20
23
|
next false unless field.in?(fields)
|
21
24
|
|
22
25
|
sub_fields = @controller.class.get_field_config(field)[:sub_fields] || []
|
23
|
-
|
26
|
+
if sub_field.in?(sub_fields)
|
27
|
+
includes << field.to_sym
|
28
|
+
next true
|
29
|
+
end
|
30
|
+
|
31
|
+
next false
|
24
32
|
end
|
25
33
|
|
26
34
|
next field.in?(fields)
|
@@ -28,21 +36,27 @@ class RESTFramework::Filters::ModelQueryFilter < RESTFramework::Filters::BaseFil
|
|
28
36
|
# Convert fields ending in `__in` to array values.
|
29
37
|
if p.end_with?("__in")
|
30
38
|
p = p.chomp("__in")
|
31
|
-
v = v.split(",")
|
39
|
+
v = v.split(",").map { |v| v.in?(NIL_VALUES) ? nil : v }
|
32
40
|
end
|
33
41
|
|
34
42
|
# Convert "nil" and "null" to nil.
|
35
|
-
|
36
|
-
v = nil
|
37
|
-
end
|
43
|
+
v = nil if v.in?(NIL_VALUES)
|
38
44
|
|
39
45
|
[p, v]
|
40
46
|
}.to_h.symbolize_keys
|
47
|
+
|
48
|
+
return filter_params, includes
|
41
49
|
end
|
42
50
|
|
43
51
|
# Filter data according to the request query parameters.
|
44
52
|
def get_filtered_data(data)
|
45
|
-
|
53
|
+
filter_params, includes = self._get_filter_params
|
54
|
+
|
55
|
+
if filter_params.any?
|
56
|
+
if includes.any?
|
57
|
+
data = data.includes(*includes)
|
58
|
+
end
|
59
|
+
|
46
60
|
return data.where(**filter_params)
|
47
61
|
end
|
48
62
|
|
@@ -31,9 +31,6 @@ module RESTFramework::Mixins::BulkCreateModelMixin
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
-
# Alias for convenience.
|
35
|
-
RESTFramework::BulkCreateModelMixin = RESTFramework::Mixins::BulkCreateModelMixin
|
36
|
-
|
37
34
|
# Mixin for updating records in bulk.
|
38
35
|
module RESTFramework::Mixins::BulkUpdateModelMixin
|
39
36
|
def update_all
|
@@ -59,9 +56,6 @@ module RESTFramework::Mixins::BulkUpdateModelMixin
|
|
59
56
|
end
|
60
57
|
end
|
61
58
|
|
62
|
-
# Alias for convenience.
|
63
|
-
RESTFramework::BulkUpdateModelMixin = RESTFramework::Mixins::BulkUpdateModelMixin
|
64
|
-
|
65
59
|
# Mixin for destroying records in bulk.
|
66
60
|
module RESTFramework::Mixins::BulkDestroyModelMixin
|
67
61
|
def destroy_all
|
@@ -89,21 +83,21 @@ module RESTFramework::Mixins::BulkDestroyModelMixin
|
|
89
83
|
end
|
90
84
|
end
|
91
85
|
|
92
|
-
# Alias for convenience.
|
93
|
-
RESTFramework::BulkDestroyModelMixin = RESTFramework::Mixins::BulkDestroyModelMixin
|
94
|
-
|
95
86
|
# Mixin that includes all the CRUD bulk mixins.
|
96
87
|
module RESTFramework::Mixins::BulkModelControllerMixin
|
97
|
-
include RESTFramework::ModelControllerMixin
|
88
|
+
include RESTFramework::Mixins::ModelControllerMixin
|
98
89
|
|
99
|
-
include RESTFramework::BulkCreateModelMixin
|
100
|
-
include RESTFramework::BulkUpdateModelMixin
|
101
|
-
include RESTFramework::BulkDestroyModelMixin
|
90
|
+
include RESTFramework::Mixins::BulkCreateModelMixin
|
91
|
+
include RESTFramework::Mixins::BulkUpdateModelMixin
|
92
|
+
include RESTFramework::Mixins::BulkDestroyModelMixin
|
102
93
|
|
103
94
|
def self.included(base)
|
104
|
-
RESTFramework::ModelControllerMixin.included(base)
|
95
|
+
RESTFramework::Mixins::ModelControllerMixin.included(base)
|
105
96
|
end
|
106
97
|
end
|
107
98
|
|
108
|
-
#
|
99
|
+
# Aliases for convenience.
|
100
|
+
RESTFramework::BulkCreateModelMixin = RESTFramework::Mixins::BulkCreateModelMixin
|
101
|
+
RESTFramework::BulkUpdateModelMixin = RESTFramework::Mixins::BulkUpdateModelMixin
|
102
|
+
RESTFramework::BulkDestroyModelMixin = RESTFramework::Mixins::BulkDestroyModelMixin
|
109
103
|
RESTFramework::BulkModelControllerMixin = RESTFramework::Mixins::BulkModelControllerMixin
|
@@ -33,17 +33,7 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
33
33
|
# Options for handling request body parameters.
|
34
34
|
allowed_parameters: nil,
|
35
35
|
filter_pk_from_request_body: true,
|
36
|
-
exclude_body_fields:
|
37
|
-
created_at
|
38
|
-
created_by
|
39
|
-
created_by_id
|
40
|
-
updated_at
|
41
|
-
updated_by
|
42
|
-
updated_by_id
|
43
|
-
_method
|
44
|
-
utf8
|
45
|
-
authenticity_token
|
46
|
-
].freeze,
|
36
|
+
exclude_body_fields: RESTFramework.config.exclude_body_fields,
|
47
37
|
|
48
38
|
# Attributes for the default native serializer.
|
49
39
|
native_serializer_config: nil,
|
@@ -515,7 +505,7 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
515
505
|
ActionController::Parameters.new(data).permit(*allowed_params)
|
516
506
|
end
|
517
507
|
|
518
|
-
# Filter primary key if configured.
|
508
|
+
# Filter primary key, if configured.
|
519
509
|
if self.filter_pk_from_request_body && bulk_mode != :update
|
520
510
|
body_params.delete(pk)
|
521
511
|
end
|
@@ -527,7 +517,7 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
527
517
|
#
|
528
518
|
# rubocop:disable Layout/LineLength
|
529
519
|
#
|
530
|
-
#
|
520
|
+
# Example base64 image:
|
531
521
|
# data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAApgAAAKYB3X3/OAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAANCSURBVEiJtZZPbBtFFMZ/M7ubXdtdb1xSFyeilBapySVU8h8OoFaooFSqiihIVIpQBKci6KEg9Q6H9kovIHoCIVQJJCKE1ENFjnAgcaSGC6rEnxBwA04Tx43t2FnvDAfjkNibxgHxnWb2e/u992bee7tCa00YFsffekFY+nUzFtjW0LrvjRXrCDIAaPLlW0nHL0SsZtVoaF98mLrx3pdhOqLtYPHChahZcYYO7KvPFxvRl5XPp1sN3adWiD1ZAqD6XYK1b/dvE5IWryTt2udLFedwc1+9kLp+vbbpoDh+6TklxBeAi9TL0taeWpdmZzQDry0AcO+jQ12RyohqqoYoo8RDwJrU+qXkjWtfi8Xxt58BdQuwQs9qC/afLwCw8tnQbqYAPsgxE1S6F3EAIXux2oQFKm0ihMsOF71dHYx+f3NND68ghCu1YIoePPQN1pGRABkJ6Bus96CutRZMydTl+TvuiRW1m3n0eDl0vRPcEysqdXn+jsQPsrHMquGeXEaY4Yk4wxWcY5V/9scqOMOVUFthatyTy8QyqwZ+kDURKoMWxNKr2EeqVKcTNOajqKoBgOE28U4tdQl5p5bwCw7BWquaZSzAPlwjlithJtp3pTImSqQRrb2Z8PHGigD4RZuNX6JYj6wj7O4TFLbCO/Mn/m8R+h6rYSUb3ekokRY6f/YukArN979jcW+V/S8g0eT/N3VN3kTqWbQ428m9/8k0P/1aIhF36PccEl6EhOcAUCrXKZXXWS3XKd2vc/TRBG9O5ELC17MmWubD2nKhUKZa26Ba2+D3P+4/MNCFwg59oWVeYhkzgN/JDR8deKBoD7Y+ljEjGZ0sosXVTvbc6RHirr2reNy1OXd6pJsQ+gqjk8VWFYmHrwBzW/n+uMPFiRwHB2I7ih8ciHFxIkd/3Omk5tCDV1t+2nNu5sxxpDFNx+huNhVT3/zMDz8usXC3ddaHBj1GHj/As08fwTS7Kt1HBTmyN29vdwAw+/wbwLVOJ3uAD1wi/dUH7Qei66PfyuRj4Ik9is+hglfbkbfR3cnZm7chlUWLdwmprtCohX4HUtlOcQjLYCu+fzGJH2QRKvP3UNz8bWk1qMxjGTOMThZ3kvgLI5AzFfo379UAAAAASUVORK5CYII=
|
532
522
|
#
|
533
523
|
# rubocop:enable Layout/LineLength
|
@@ -572,7 +562,6 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
572
562
|
# Cache the result.
|
573
563
|
return @record if @record
|
574
564
|
|
575
|
-
recordset = self.get_recordset
|
576
565
|
find_by_key = self.class.get_model.primary_key
|
577
566
|
is_pk = true
|
578
567
|
|
@@ -588,16 +577,18 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
588
577
|
end
|
589
578
|
end
|
590
579
|
|
591
|
-
#
|
592
|
-
if self.filter_recordset_before_find
|
593
|
-
|
580
|
+
# Get the recordset, filtering if configured.
|
581
|
+
collection = if self.filter_recordset_before_find
|
582
|
+
self.get_records
|
583
|
+
else
|
584
|
+
self.get_recordset
|
594
585
|
end
|
595
586
|
|
596
587
|
# Return the record. Route key is always `:id` by Rails convention.
|
597
588
|
if is_pk
|
598
|
-
return @record =
|
589
|
+
return @record = collection.find(request.path_parameters[:id])
|
599
590
|
else
|
600
|
-
return @record =
|
591
|
+
return @record = collection.find_by!(find_by_key => request.path_parameters[:id])
|
601
592
|
end
|
602
593
|
end
|
603
594
|
|
@@ -631,9 +622,6 @@ module RESTFramework::Mixins::BaseModelControllerMixin
|
|
631
622
|
end
|
632
623
|
end
|
633
624
|
|
634
|
-
# Alias for convenience.
|
635
|
-
RESTFramework::BaseModelControllerMixin = RESTFramework::Mixins::BaseModelControllerMixin
|
636
|
-
|
637
625
|
# Mixin for listing records.
|
638
626
|
module RESTFramework::Mixins::ListModelMixin
|
639
627
|
def index
|
@@ -662,9 +650,6 @@ module RESTFramework::Mixins::ListModelMixin
|
|
662
650
|
end
|
663
651
|
end
|
664
652
|
|
665
|
-
# Alias for convenience.
|
666
|
-
RESTFramework::ListModelMixin = RESTFramework::Mixins::ListModelMixin
|
667
|
-
|
668
653
|
# Mixin for showing records.
|
669
654
|
module RESTFramework::Mixins::ShowModelMixin
|
670
655
|
def show
|
@@ -672,9 +657,6 @@ module RESTFramework::Mixins::ShowModelMixin
|
|
672
657
|
end
|
673
658
|
end
|
674
659
|
|
675
|
-
# Alias for convenience.
|
676
|
-
RESTFramework::ShowModelMixin = RESTFramework::Mixins::ShowModelMixin
|
677
|
-
|
678
660
|
# Mixin for creating records.
|
679
661
|
module RESTFramework::Mixins::CreateModelMixin
|
680
662
|
def create
|
@@ -687,9 +669,6 @@ module RESTFramework::Mixins::CreateModelMixin
|
|
687
669
|
end
|
688
670
|
end
|
689
671
|
|
690
|
-
# Alias for convenience.
|
691
|
-
RESTFramework::CreateModelMixin = RESTFramework::Mixins::CreateModelMixin
|
692
|
-
|
693
672
|
# Mixin for updating records.
|
694
673
|
module RESTFramework::Mixins::UpdateModelMixin
|
695
674
|
def update
|
@@ -704,9 +683,6 @@ module RESTFramework::Mixins::UpdateModelMixin
|
|
704
683
|
end
|
705
684
|
end
|
706
685
|
|
707
|
-
# Alias for convenience.
|
708
|
-
RESTFramework::UpdateModelMixin = RESTFramework::Mixins::UpdateModelMixin
|
709
|
-
|
710
686
|
# Mixin for destroying records.
|
711
687
|
module RESTFramework::Mixins::DestroyModelMixin
|
712
688
|
def destroy
|
@@ -720,38 +696,39 @@ module RESTFramework::Mixins::DestroyModelMixin
|
|
720
696
|
end
|
721
697
|
end
|
722
698
|
|
723
|
-
# Alias for convenience.
|
724
|
-
RESTFramework::DestroyModelMixin = RESTFramework::Mixins::DestroyModelMixin
|
725
|
-
|
726
699
|
# Mixin that includes show/list mixins.
|
727
700
|
module RESTFramework::Mixins::ReadOnlyModelControllerMixin
|
728
|
-
include RESTFramework::BaseModelControllerMixin
|
701
|
+
include RESTFramework::Mixins::BaseModelControllerMixin
|
729
702
|
|
730
|
-
include RESTFramework::ListModelMixin
|
731
|
-
include RESTFramework::ShowModelMixin
|
703
|
+
include RESTFramework::Mixins::ListModelMixin
|
704
|
+
include RESTFramework::Mixins::ShowModelMixin
|
732
705
|
|
733
706
|
def self.included(base)
|
734
707
|
RESTFramework::BaseModelControllerMixin.included(base)
|
735
708
|
end
|
736
709
|
end
|
737
710
|
|
738
|
-
# Alias for convenience.
|
739
|
-
RESTFramework::ReadOnlyModelControllerMixin = RESTFramework::Mixins::ReadOnlyModelControllerMixin
|
740
|
-
|
741
711
|
# Mixin that includes all the CRUD mixins.
|
742
712
|
module RESTFramework::Mixins::ModelControllerMixin
|
743
|
-
include RESTFramework::BaseModelControllerMixin
|
713
|
+
include RESTFramework::Mixins::BaseModelControllerMixin
|
744
714
|
|
745
|
-
include RESTFramework::ListModelMixin
|
746
|
-
include RESTFramework::ShowModelMixin
|
747
|
-
include RESTFramework::CreateModelMixin
|
748
|
-
include RESTFramework::UpdateModelMixin
|
749
|
-
include RESTFramework::DestroyModelMixin
|
715
|
+
include RESTFramework::Mixins::ListModelMixin
|
716
|
+
include RESTFramework::Mixins::ShowModelMixin
|
717
|
+
include RESTFramework::Mixins::CreateModelMixin
|
718
|
+
include RESTFramework::Mixins::UpdateModelMixin
|
719
|
+
include RESTFramework::Mixins::DestroyModelMixin
|
750
720
|
|
751
721
|
def self.included(base)
|
752
722
|
RESTFramework::BaseModelControllerMixin.included(base)
|
753
723
|
end
|
754
724
|
end
|
755
725
|
|
756
|
-
#
|
726
|
+
# Aliases for convenience.
|
727
|
+
RESTFramework::BaseModelControllerMixin = RESTFramework::Mixins::BaseModelControllerMixin
|
728
|
+
RESTFramework::ListModelMixin = RESTFramework::Mixins::ListModelMixin
|
729
|
+
RESTFramework::ShowModelMixin = RESTFramework::Mixins::ShowModelMixin
|
730
|
+
RESTFramework::CreateModelMixin = RESTFramework::Mixins::CreateModelMixin
|
731
|
+
RESTFramework::UpdateModelMixin = RESTFramework::Mixins::UpdateModelMixin
|
732
|
+
RESTFramework::DestroyModelMixin = RESTFramework::Mixins::DestroyModelMixin
|
733
|
+
RESTFramework::ReadOnlyModelControllerMixin = RESTFramework::Mixins::ReadOnlyModelControllerMixin
|
757
734
|
RESTFramework::ModelControllerMixin = RESTFramework::Mixins::ModelControllerMixin
|
@@ -59,13 +59,24 @@ class RESTFramework::Paginators::PageNumberPaginator < RESTFramework::Paginators
|
|
59
59
|
|
60
60
|
# Wrap the serialized page with appropriate metadata. TODO: include links.
|
61
61
|
def get_paginated_response(serialized_page)
|
62
|
+
page_query_param = @controller.page_query_param
|
63
|
+
base_params = @controller.params.to_unsafe_h
|
64
|
+
next_url = if @page_number < @total_pages
|
65
|
+
@controller.url_for({**base_params, page_query_param => @page_number + 1})
|
66
|
+
end
|
67
|
+
previous_url = if @page_number > 1
|
68
|
+
@controller.url_for({**base_params, page_query_param => @page_number - 1})
|
69
|
+
end
|
70
|
+
|
62
71
|
return {
|
63
72
|
count: @count,
|
64
73
|
page: @page_number,
|
65
74
|
page_size: @page_size,
|
66
75
|
total_pages: @total_pages,
|
76
|
+
next: next_url,
|
77
|
+
previous: previous_url,
|
67
78
|
results: serialized_page,
|
68
|
-
}
|
79
|
+
}.compact
|
69
80
|
end
|
70
81
|
end
|
71
82
|
|
@@ -1,5 +1,5 @@
|
|
1
|
-
# Do not use Rails-specific helper methods here (e.g., `blank?`) so the module can run standalone.
|
2
1
|
module RESTFramework
|
2
|
+
# Do not use Rails-specific helper methods here (e.g., `blank?`) so the module can run standalone.
|
3
3
|
module Version
|
4
4
|
VERSION_FILEPATH = File.expand_path("../../VERSION", __dir__)
|
5
5
|
UNKNOWN = "0-unknown"
|
data/lib/rest_framework.rb
CHANGED
@@ -122,9 +122,19 @@ module RESTFramework
|
|
122
122
|
# Global configuration should be kept minimal, as controller-level configurations allows multiple
|
123
123
|
# APIs to be defined to behave differently.
|
124
124
|
class Config
|
125
|
-
DEFAULT_EXCLUDE_ASSOCIATION_CLASSES = [].freeze
|
126
125
|
DEFAULT_LABEL_FIELDS = %w(name label login title email username url).freeze
|
127
126
|
DEFAULT_SEARCH_COLUMNS = DEFAULT_LABEL_FIELDS + %w(description note).freeze
|
127
|
+
DEFAULT_EXCLUDE_BODY_FIELDS = %w[
|
128
|
+
created_at
|
129
|
+
created_by
|
130
|
+
created_by_id
|
131
|
+
updated_at
|
132
|
+
updated_by
|
133
|
+
updated_by_id
|
134
|
+
_method
|
135
|
+
utf8
|
136
|
+
authenticity_token
|
137
|
+
].freeze
|
128
138
|
|
129
139
|
# Do not run `rrf_finalize` on controllers automatically using a `TracePoint` hook. This is a
|
130
140
|
# performance option and must be global because we have to determine this before any
|
@@ -155,6 +165,9 @@ module RESTFramework
|
|
155
165
|
# The default search columns to use when generating search filters.
|
156
166
|
attr_accessor :search_columns
|
157
167
|
|
168
|
+
# The default list of fields to exclude from the body of the request.
|
169
|
+
attr_accessor :exclude_body_fields
|
170
|
+
|
158
171
|
# Option to use vendored assets (requires sprockets or propshaft) rather than linking to
|
159
172
|
# external assets (the default).
|
160
173
|
attr_accessor :use_vendored_assets
|
@@ -163,6 +176,7 @@ module RESTFramework
|
|
163
176
|
self.show_backtrace = Rails.env.development?
|
164
177
|
self.label_fields = DEFAULT_LABEL_FIELDS
|
165
178
|
self.search_columns = DEFAULT_SEARCH_COLUMNS
|
179
|
+
self.exclude_body_fields = DEFAULT_EXCLUDE_BODY_FIELDS
|
166
180
|
end
|
167
181
|
end
|
168
182
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rest_framework
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gregory N. Schmit
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-07-
|
11
|
+
date: 2023-07-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -53,6 +53,9 @@ files:
|
|
53
53
|
- lib/rest_framework.rb
|
54
54
|
- lib/rest_framework/engine.rb
|
55
55
|
- lib/rest_framework/errors.rb
|
56
|
+
- lib/rest_framework/errors/base_error.rb
|
57
|
+
- lib/rest_framework/errors/nil_passed_to_api_response_error.rb
|
58
|
+
- lib/rest_framework/errors/unknown_model_error.rb
|
56
59
|
- lib/rest_framework/filters.rb
|
57
60
|
- lib/rest_framework/filters/base_filter.rb
|
58
61
|
- lib/rest_framework/filters/model_ordering_filter.rb
|