rest_framework 0.9.9 → 0.9.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +15 -21
- data/VERSION +1 -1
- data/app/views/rest_framework/_head.html.erb +1 -4
- data/app/views/rest_framework/routes_and_forms/_routes.html.erb +1 -1
- 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/model_controller_mixin.rb +10 -19
- data/lib/rest_framework/version.rb +1 -1
- data/lib/rest_framework.rb +15 -1
- data/vendor/assets/stylesheets/rest_framework/external.min.css +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 04ef01197efeca7c6276edbf2ed4c9ef3f50e4fc4d1df927c3d530953d5aab2a
|
4
|
+
data.tar.gz: 71bd781956d163f33cdc7fd6aa3950ce93d4f86a0c03325e2bebcd2ba1dfc74b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 96cbf98cec8f47c54309fa5b2b46c90c60249e7da1fa7142ef85c041bfee1154b9fd60c5d420d145a2dfb852df4d8af906e3577cb2f5f70d0bc6fd2da9beeb97
|
7
|
+
data.tar.gz: 4b0ab5e4d529f54e4890c8e0da2763ee8915a393ce1260da5c7ce70d6ef940c64e36f15d5a5b70cca255ec881c53b4c48bbcd5f95f2e7d83cc88e233b623b327
|
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
|
|
@@ -40,8 +38,7 @@ bundle install
|
|
40
38
|
|
41
39
|
This section provides some simple examples to quickly get you started using the framework.
|
42
40
|
|
43
|
-
For the purpose of this example, you'll want to add an `api_controller.rb` to your controllers, as
|
44
|
-
well as a directory for the resources:
|
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:
|
45
42
|
|
46
43
|
```text
|
47
44
|
controllers/
|
@@ -54,8 +51,7 @@ controllers/
|
|
54
51
|
|
55
52
|
### Controller Mixins
|
56
53
|
|
57
|
-
The root `ApiController` can include any common behavior you want to share across all your API
|
58
|
-
controllers:
|
54
|
+
The root `ApiController` can include any common behavior you want to share across all your API controllers:
|
59
55
|
|
60
56
|
```ruby
|
61
57
|
class ApiController < ApplicationController
|
@@ -71,9 +67,8 @@ class ApiController < ApplicationController
|
|
71
67
|
end
|
72
68
|
```
|
73
69
|
|
74
|
-
A root controller can provide actions that exist on the root of your API.
|
75
|
-
dedicated root controller, rather than using the `ApiController` for this purpose, so that actions
|
76
|
-
don't propagate to child controllers:
|
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:
|
77
72
|
|
78
73
|
```ruby
|
79
74
|
class Api::RootController < ApiController
|
@@ -119,7 +114,8 @@ class Api::MoviesController < ApiController
|
|
119
114
|
end
|
120
115
|
```
|
121
116
|
|
122
|
-
|
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:
|
123
119
|
|
124
120
|
```ruby
|
125
121
|
class Api::UsersController < ApiController
|
@@ -131,10 +127,9 @@ end
|
|
131
127
|
|
132
128
|
### Routing
|
133
129
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
the root, use `rest_root`.
|
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`.
|
138
133
|
|
139
134
|
```ruby
|
140
135
|
Rails.application.routes.draw do
|
@@ -151,8 +146,7 @@ end
|
|
151
146
|
|
152
147
|
## Development/Testing
|
153
148
|
|
154
|
-
After you clone the repository, cd'ing into the directory should create a new gemset if you are
|
155
|
-
|
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.
|
156
151
|
|
157
|
-
The top-level `bin/rails` proxies all Rails commands to the test project, so you can operate it via
|
158
|
-
the usual commands (e.g., `rails test`, `rails server` and `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.11
|
@@ -90,9 +90,6 @@
|
|
90
90
|
html[data-bs-theme="dark"] .rrf-routes .rrf-route-group-header:hover {
|
91
91
|
background-color: #333;
|
92
92
|
}
|
93
|
-
.rrf-routes .rrf-route-group-header td {
|
94
|
-
cursor: pointer;
|
95
|
-
}
|
96
93
|
|
97
94
|
/* Disable bootstrap's collapsing animation because in tables it causes delayed jerkiness. */
|
98
95
|
.rrf-routes .collapsing {
|
@@ -194,7 +191,7 @@
|
|
194
191
|
// Initialize dark/light mode before page fully loads to prevent flash.
|
195
192
|
rrfSetSelectedMode(localStorage.getItem("rrfMode"))
|
196
193
|
|
197
|
-
// Initialize dark/light mode after page load (
|
194
|
+
// Initialize dark/light mode after page load (duplicate so mode component is updated).
|
198
195
|
document.addEventListener("DOMContentLoaded", (event) => {
|
199
196
|
rrfSetSelectedMode(localStorage.getItem("rrfMode"))
|
200
197
|
|
@@ -16,7 +16,7 @@
|
|
16
16
|
<%# Render any other groups under dropdowns. %>
|
17
17
|
<% @route_groups.drop(1).each_with_index do |(name, route_group), index| %>
|
18
18
|
<tr data-bs-toggle="collapse" data-bs-target="#route-group-<%= index %>" class="rrf-route-group-header">
|
19
|
-
<td colspan="3" class="text-center user-select-none"><%= name %></td>
|
19
|
+
<td colspan="3" class="text-center user-select-none" style="cursor: pointer"><%= name %></td>
|
20
20
|
</tr>
|
21
21
|
<tbody id="route-group-<%= index %>" class="collapse">
|
22
22
|
<% route_group.each do |route| %>
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|