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
|
@@ -13,22 +13,21 @@ class RESTFramework::Paginators::PageNumberPaginator < RESTFramework::Paginators
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def _page_size
|
|
16
|
-
page_size =
|
|
16
|
+
page_size = nil
|
|
17
17
|
|
|
18
|
-
# Get from
|
|
18
|
+
# Get from query param, if allowed.
|
|
19
19
|
if param = @controller.class.page_size_query_param
|
|
20
|
-
if
|
|
21
|
-
|
|
20
|
+
if raw = @controller.params[param].presence
|
|
21
|
+
parsed = raw.to_i
|
|
22
|
+
page_size = parsed if parsed > 0
|
|
22
23
|
end
|
|
23
24
|
end
|
|
24
25
|
|
|
25
|
-
#
|
|
26
|
-
|
|
27
|
-
page_size = @controller.class.page_size.to_i
|
|
28
|
-
end
|
|
26
|
+
# Fall back to the configured page size.
|
|
27
|
+
page_size ||= @controller.class.page_size&.to_i || 1
|
|
29
28
|
|
|
30
29
|
# Ensure we don't exceed the max page size.
|
|
31
|
-
max_page_size = @controller.class.max_page_size
|
|
30
|
+
max_page_size = @controller.class.max_page_size
|
|
32
31
|
if max_page_size && page_size > max_page_size
|
|
33
32
|
page_size = max_page_size
|
|
34
33
|
end
|
|
@@ -46,7 +45,7 @@ class RESTFramework::Paginators::PageNumberPaginator < RESTFramework::Paginators
|
|
|
46
45
|
page_number = 1
|
|
47
46
|
else
|
|
48
47
|
page_number = page_number.to_i
|
|
49
|
-
if page_number
|
|
48
|
+
if page_number < 1
|
|
50
49
|
page_number = 1
|
|
51
50
|
end
|
|
52
51
|
end
|
|
@@ -61,7 +60,7 @@ class RESTFramework::Paginators::PageNumberPaginator < RESTFramework::Paginators
|
|
|
61
60
|
# Wrap the serialized page with appropriate metadata.
|
|
62
61
|
def get_paginated_response(serialized_page)
|
|
63
62
|
page_query_param = @controller.class.page_query_param
|
|
64
|
-
base_params = @controller.
|
|
63
|
+
base_params = @controller.request.query_parameters.symbolize_keys
|
|
65
64
|
next_url = if @page_number < @total_pages
|
|
66
65
|
@controller.url_for({ **base_params, page_query_param => @page_number + 1 })
|
|
67
66
|
end
|
|
@@ -31,7 +31,7 @@ module ActionDispatch::Routing
|
|
|
31
31
|
|
|
32
32
|
begin
|
|
33
33
|
controller = mod.const_get(name_reverse)
|
|
34
|
-
rescue
|
|
34
|
+
rescue NameError
|
|
35
35
|
reraise = true
|
|
36
36
|
end
|
|
37
37
|
|
|
@@ -109,16 +109,25 @@ module ActionDispatch::Routing
|
|
|
109
109
|
next unless controller_class.method_defined?(action)
|
|
110
110
|
|
|
111
111
|
[ methods ].flatten.each do |m|
|
|
112
|
-
|
|
112
|
+
# Anchor the route since Rails 8.1 OPTIONS routes are non-anchored by default, which
|
|
113
|
+
# causes parent OPTIONS routes to greedily intercept sub-path requests.
|
|
114
|
+
public_send(m, "", action: action, anchor: true) if self.respond_to?(m)
|
|
113
115
|
end
|
|
114
116
|
end
|
|
115
117
|
|
|
116
|
-
# Route bulk actions, if configured.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
118
|
+
# Route bulk actions, if configured. These require a model and are gated by the `bulk`
|
|
119
|
+
# attribute, and may be individually excluded via `excluded_actions`.
|
|
120
|
+
if controller_class.model && controller_class.bulk
|
|
121
|
+
bulk_exclude = controller_class.excluded_actions&.to_set || Set.new
|
|
122
|
+
RESTFramework::RRF_BUILTIN_BULK_ACTIONS.each do |action, methods|
|
|
123
|
+
next unless controller_class.method_defined?(action)
|
|
124
|
+
next if bulk_exclude.include?(action)
|
|
125
|
+
|
|
126
|
+
[ methods ].flatten.each do |m|
|
|
127
|
+
# Anchor the route since Rails 8.1 OPTIONS routes are non-anchored by default, which
|
|
128
|
+
# causes parent OPTIONS routes to greedily intercept sub-path requests.
|
|
129
|
+
public_send(m, "", action: action, anchor: true) if self.respond_to?(m)
|
|
130
|
+
end
|
|
122
131
|
end
|
|
123
132
|
end
|
|
124
133
|
end
|
|
@@ -179,7 +188,9 @@ module ActionDispatch::Routing
|
|
|
179
188
|
next unless controller_class.method_defined?(action)
|
|
180
189
|
|
|
181
190
|
[ methods ].flatten.each do |m|
|
|
182
|
-
|
|
191
|
+
# Anchor the route since Rails 8.1 OPTIONS routes are non-anchored by default, which
|
|
192
|
+
# causes parent OPTIONS routes to greedily intercept sub-path requests.
|
|
193
|
+
public_send(m, "", action: action, anchor: true) if self.respond_to?(m)
|
|
183
194
|
end
|
|
184
195
|
end
|
|
185
196
|
end
|
|
@@ -28,10 +28,12 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
|
|
|
28
28
|
# Determine model either explicitly, or by inspecting @object or @controller.
|
|
29
29
|
@model = model
|
|
30
30
|
@model ||= @object.class if @object.is_a?(ActiveRecord::Base)
|
|
31
|
-
@model ||= @object
|
|
32
|
-
|
|
31
|
+
@model ||= @object.klass if @many && @object.is_a?(ActiveRecord::Relation)
|
|
32
|
+
@model ||= @object.first.class if @many &&
|
|
33
|
+
@object.is_a?(Enumerable) &&
|
|
34
|
+
@object.first.is_a?(ActiveRecord::Base)
|
|
33
35
|
|
|
34
|
-
@model ||= @controller.class.
|
|
36
|
+
@model ||= @controller.class.model if @controller
|
|
35
37
|
end
|
|
36
38
|
|
|
37
39
|
def action
|
data/lib/rest_framework/utils.rb
CHANGED
|
@@ -42,17 +42,27 @@ module RESTFramework::Utils
|
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def self.get_skipped_builtin_actions(controller_class, singular)
|
|
45
|
-
(
|
|
46
|
-
(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
candidates = (
|
|
46
|
+
RESTFramework::BUILTIN_ACTIONS.keys - (singular ? [ :index ] : [])
|
|
47
|
+
) + RESTFramework::BUILTIN_MEMBER_ACTIONS.keys
|
|
48
|
+
|
|
49
|
+
return candidates unless controller_class.model
|
|
50
|
+
|
|
51
|
+
exclude = controller_class.excluded_actions&.to_set || Set.new
|
|
52
|
+
candidates.reject do |action|
|
|
53
|
+
controller_class.method_defined?(action) && !exclude.include?(action)
|
|
51
54
|
end
|
|
52
55
|
end
|
|
53
56
|
|
|
54
57
|
# Get the first route pattern which matches the given request.
|
|
55
58
|
def self.get_request_route(application_routes, request)
|
|
59
|
+
# Prefer the route already resolved by the router to avoid an expensive `recognize` call. This
|
|
60
|
+
# is also required for Rails 8.1+ where OPTIONS routes are non-anchored, causing `path_info` to
|
|
61
|
+
# be modified during dispatch, which makes `recognize` fail from inside the controller action.
|
|
62
|
+
if route = request.env["action_dispatch.route"]
|
|
63
|
+
return route
|
|
64
|
+
end
|
|
65
|
+
|
|
56
66
|
application_routes.router.recognize(request) { |route, _| return route }
|
|
57
67
|
end
|
|
58
68
|
|
|
@@ -241,4 +251,12 @@ module RESTFramework::Utils
|
|
|
241
251
|
|
|
242
252
|
s
|
|
243
253
|
end
|
|
254
|
+
|
|
255
|
+
# Used for deprecated mixins that rely on model being determined from the controller name.
|
|
256
|
+
def self.get_model(controller_class)
|
|
257
|
+
begin
|
|
258
|
+
controller_class.name.demodulize.chomp("Controller").singularize.constantize
|
|
259
|
+
rescue NameError
|
|
260
|
+
end
|
|
261
|
+
end
|
|
244
262
|
end
|
data/lib/rest_framework.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module RESTFramework
|
|
4
|
-
BUILTIN_FORM_ACTIONS =
|
|
4
|
+
BUILTIN_FORM_ACTIONS = [ :new, :edit ].freeze
|
|
5
5
|
BUILTIN_ACTIONS = {
|
|
6
6
|
index: :get,
|
|
7
7
|
new: :get,
|
|
@@ -144,6 +144,7 @@ module RESTFramework
|
|
|
144
144
|
password
|
|
145
145
|
password_confirmation
|
|
146
146
|
].freeze
|
|
147
|
+
DEFAULT_INFLECT_ACRONYMS = [ "ID", "IDs", "REST", "API", "APIs" ].freeze
|
|
147
148
|
|
|
148
149
|
# Permits use of `render(api: obj)` syntax over `render_api(obj)`; `true` by default.
|
|
149
150
|
attr_accessor :register_api_renderer
|
|
@@ -168,9 +169,6 @@ module RESTFramework
|
|
|
168
169
|
# Whether the backtrace should be shown in rescued errors.
|
|
169
170
|
attr_accessor :show_backtrace
|
|
170
171
|
|
|
171
|
-
# Disable `rescue_from` on the controller mixins.
|
|
172
|
-
attr_accessor :disable_rescue_from
|
|
173
|
-
|
|
174
172
|
# The default label fields to use when generating labels for `has_many` associations.
|
|
175
173
|
attr_accessor :label_fields
|
|
176
174
|
|
|
@@ -181,6 +179,9 @@ module RESTFramework
|
|
|
181
179
|
attr_accessor :read_only_fields
|
|
182
180
|
attr_accessor :write_only_fields
|
|
183
181
|
|
|
182
|
+
# List of acronyms to be inflected in controller titles and field labels.
|
|
183
|
+
attr_accessor :inflect_acronyms
|
|
184
|
+
|
|
184
185
|
# Option to use vendored assets (requires sprockets or propshaft) rather than linking to
|
|
185
186
|
# external assets (the default).
|
|
186
187
|
attr_accessor :use_vendored_assets
|
|
@@ -188,6 +189,7 @@ module RESTFramework
|
|
|
188
189
|
def initialize
|
|
189
190
|
self.register_api_renderer = true
|
|
190
191
|
self.auto_finalize = true
|
|
192
|
+
self.freeze_config = true
|
|
191
193
|
|
|
192
194
|
self.show_backtrace = Rails.env.development?
|
|
193
195
|
|
|
@@ -195,6 +197,7 @@ module RESTFramework
|
|
|
195
197
|
self.search_columns = DEFAULT_SEARCH_COLUMNS
|
|
196
198
|
self.read_only_fields = DEFAULT_READ_ONLY_FIELDS
|
|
197
199
|
self.write_only_fields = DEFAULT_WRITE_ONLY_FIELDS
|
|
200
|
+
self.inflect_acronyms = DEFAULT_INFLECT_ACRONYMS
|
|
198
201
|
end
|
|
199
202
|
end
|
|
200
203
|
|
|
@@ -209,15 +212,20 @@ module RESTFramework
|
|
|
209
212
|
def self.features
|
|
210
213
|
@features ||= {}
|
|
211
214
|
end
|
|
215
|
+
|
|
216
|
+
def self.deprecator
|
|
217
|
+
@deprecator ||= ActiveSupport::Deprecation.new("2.0", "REST Framework")
|
|
218
|
+
end
|
|
212
219
|
end
|
|
213
220
|
|
|
214
221
|
require_relative "rest_framework/engine"
|
|
215
222
|
require_relative "rest_framework/errors"
|
|
216
223
|
require_relative "rest_framework/filters"
|
|
217
|
-
require_relative "rest_framework/generators"
|
|
218
224
|
require_relative "rest_framework/mixins"
|
|
219
225
|
require_relative "rest_framework/paginators"
|
|
220
226
|
require_relative "rest_framework/routers"
|
|
221
227
|
require_relative "rest_framework/serializers"
|
|
222
228
|
require_relative "rest_framework/utils"
|
|
223
229
|
require_relative "rest_framework/version"
|
|
230
|
+
|
|
231
|
+
require_relative "rest_framework/controller"
|
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: 1.0
|
|
4
|
+
version: 1.2.0
|
|
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: 2026-04-
|
|
11
|
+
date: 2026-04-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -54,19 +54,18 @@ files:
|
|
|
54
54
|
- app/views/rest_framework/routes_and_forms/_routes.html.erb
|
|
55
55
|
- app/views/rest_framework/routes_and_forms/routes/_route.html.erb
|
|
56
56
|
- lib/rest_framework.rb
|
|
57
|
+
- lib/rest_framework/controller.rb
|
|
58
|
+
- lib/rest_framework/controller/bulk.rb
|
|
59
|
+
- lib/rest_framework/controller/crud.rb
|
|
60
|
+
- lib/rest_framework/controller/openapi.rb
|
|
57
61
|
- lib/rest_framework/engine.rb
|
|
58
62
|
- lib/rest_framework/errors.rb
|
|
59
|
-
- lib/rest_framework/errors/base_error.rb
|
|
60
|
-
- lib/rest_framework/errors/nil_passed_to_render_api_error.rb
|
|
61
|
-
- lib/rest_framework/errors/unknown_model_error.rb
|
|
62
63
|
- lib/rest_framework/filters.rb
|
|
63
64
|
- lib/rest_framework/filters/base_filter.rb
|
|
64
65
|
- lib/rest_framework/filters/ordering_filter.rb
|
|
65
66
|
- lib/rest_framework/filters/query_filter.rb
|
|
66
67
|
- lib/rest_framework/filters/ransack_filter.rb
|
|
67
68
|
- lib/rest_framework/filters/search_filter.rb
|
|
68
|
-
- lib/rest_framework/generators.rb
|
|
69
|
-
- lib/rest_framework/generators/controller_generator.rb
|
|
70
69
|
- lib/rest_framework/mixins.rb
|
|
71
70
|
- lib/rest_framework/mixins/base_controller_mixin.rb
|
|
72
71
|
- lib/rest_framework/mixins/bulk_model_controller_mixin.rb
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
class RESTFramework::Errors::NilPassedToRenderAPIError < RESTFramework::Errors::BaseError
|
|
2
|
-
def message
|
|
3
|
-
<<~MSG.split("\n").join(" ")
|
|
4
|
-
Payload of `nil` was passed to `render_api`; 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::NilPassedToRenderAPIError = RESTFramework::Errors::NilPassedToRenderAPIError
|
|
@@ -1,18 +0,0 @@
|
|
|
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
|
-
<<~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,64 +0,0 @@
|
|
|
1
|
-
require "rails/generators"
|
|
2
|
-
|
|
3
|
-
# Most projects don't have the inflection "REST" as an acronym, so this is a helper class to prevent
|
|
4
|
-
# this generator from being namespaced as `"r_e_s_t_framework"`.
|
|
5
|
-
# :nocov:
|
|
6
|
-
class RESTFrameworkCustomGeneratorControllerNamespace < String
|
|
7
|
-
def camelize
|
|
8
|
-
"RESTFramework"
|
|
9
|
-
end
|
|
10
|
-
end
|
|
11
|
-
# :nocov:
|
|
12
|
-
|
|
13
|
-
class RESTFramework::Generators::ControllerGenerator < Rails::Generators::Base
|
|
14
|
-
PATH_REGEX = %r{^[a-z0-9][a-z0-9_/]+$}
|
|
15
|
-
|
|
16
|
-
desc <<~END
|
|
17
|
-
Description:
|
|
18
|
-
Generates a new REST Framework controller.
|
|
19
|
-
|
|
20
|
-
Specify the controller as a path, including the module, if needed, like:
|
|
21
|
-
'parent_module/controller_name'.
|
|
22
|
-
|
|
23
|
-
Example:
|
|
24
|
-
`rails generate rest_framework:controller user_api/groups`
|
|
25
|
-
|
|
26
|
-
Generates a controller at `app/controllers/user_api/groups_controller.rb` named
|
|
27
|
-
`UserApi::GroupsController`.
|
|
28
|
-
END
|
|
29
|
-
|
|
30
|
-
argument :path, type: :string
|
|
31
|
-
class_option(
|
|
32
|
-
:parent_class, type: :string, default: "ApplicationController", desc: "Inheritance parent"
|
|
33
|
-
)
|
|
34
|
-
class_option(
|
|
35
|
-
:include_base,
|
|
36
|
-
type: :boolean,
|
|
37
|
-
default: false,
|
|
38
|
-
desc: "Include `BaseControllerMixin`, not `ModelControllerMixin`",
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
# Some projects may not have the inflection "REST" as an acronym, which changes this generator to
|
|
42
|
-
# be namespaced in `r_e_s_t_framework`, which is weird.
|
|
43
|
-
def self.namespace
|
|
44
|
-
RESTFrameworkCustomGeneratorControllerNamespace.new("rest_framework:controller")
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def create_rest_controller_file
|
|
48
|
-
unless PATH_REGEX.match?(self.path)
|
|
49
|
-
raise StandardError, "Path isn't valid."
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# Remove '_controller' from end of path, if it exists.
|
|
53
|
-
cleaned_path = self.path.delete_suffix("_controller")
|
|
54
|
-
|
|
55
|
-
content = <<~END
|
|
56
|
-
class #{cleaned_path.camelize}Controller < #{options[:parent_class]}
|
|
57
|
-
include RESTFramework::#{
|
|
58
|
-
options[:include_base] ? "BaseControllerMixin" : "ModelControllerMixin"
|
|
59
|
-
}
|
|
60
|
-
end
|
|
61
|
-
END
|
|
62
|
-
create_file("app/controllers/#{cleaned_path}_controller.rb", content)
|
|
63
|
-
end
|
|
64
|
-
end
|