rest_framework 1.1.0 → 2.0.0.beta1
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 +107 -41
- data/VERSION +1 -1
- data/app/views/rest_framework/routes_and_forms/_html_form.html.erb +1 -1
- data/lib/rest_framework/controller/actions.rb +256 -0
- data/lib/rest_framework/controller/bulk.rb +247 -32
- data/lib/rest_framework/controller/crud.rb +13 -9
- data/lib/rest_framework/controller/openapi.rb +12 -8
- data/lib/rest_framework/controller.rb +275 -164
- data/lib/rest_framework/errors.rb +70 -3
- data/lib/rest_framework/filters/base_filter.rb +10 -0
- data/lib/rest_framework/filters/ordering_filter.rb +41 -23
- data/lib/rest_framework/filters/query_filter.rb +24 -5
- data/lib/rest_framework/filters/search_filter.rb +8 -4
- data/lib/rest_framework/paginators/page_number_paginator.rb +34 -19
- data/lib/rest_framework/routers.rb +52 -182
- data/lib/rest_framework/serializers/active_model_serializer_adapter_factory.rb +2 -2
- data/lib/rest_framework/serializers/base_serializer.rb +2 -2
- data/lib/rest_framework/serializers/native_serializer.rb +78 -24
- data/lib/rest_framework/utils.rb +39 -70
- data/lib/rest_framework/version.rb +8 -5
- data/lib/rest_framework.rb +7 -41
- metadata +5 -12
- 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/generators/controller_generator.rb +0 -64
- data/lib/rest_framework/generators.rb +0 -4
- data/lib/rest_framework/mixins/base_controller_mixin.rb +0 -12
- data/lib/rest_framework/mixins/bulk_model_controller_mixin.rb +0 -55
- data/lib/rest_framework/mixins/model_controller_mixin.rb +0 -110
- data/lib/rest_framework/mixins.rb +0 -7
|
@@ -16,7 +16,7 @@ class RESTFramework::Serializers::BaseSerializer
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
# Synonym for `serialize` for compatibility with `active_model_serializers`.
|
|
19
|
-
# :
|
|
19
|
+
# simplecov:disable
|
|
20
20
|
def serializable_hash(*args)
|
|
21
21
|
self.serialize(*args)
|
|
22
22
|
end
|
|
@@ -35,7 +35,7 @@ class RESTFramework::Serializers::BaseSerializer
|
|
|
35
35
|
def associations(*args, **kwargs)
|
|
36
36
|
[]
|
|
37
37
|
end
|
|
38
|
-
# :
|
|
38
|
+
# simplecov:enable
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
# Alias for convenience.
|
|
@@ -28,8 +28,10 @@ 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
36
|
@model ||= @controller.class.model if @controller
|
|
35
37
|
end
|
|
@@ -109,25 +111,77 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
|
|
|
109
111
|
controller_serializer || @controller.class.native_serializer_config
|
|
110
112
|
end
|
|
111
113
|
|
|
112
|
-
#
|
|
113
|
-
|
|
114
|
-
|
|
114
|
+
# The record cap for a collection association (`nil` = unlimited). The default is applied even
|
|
115
|
+
# when the feature is off, so responses are always bounded. `key?` (not `||`) reads the
|
|
116
|
+
# `field_config` override so an explicit `nil` there means unlimited/uncapped rather than falling
|
|
117
|
+
# back to the controller default.
|
|
118
|
+
def _effective_association_limit(association_name, field_config)
|
|
119
|
+
controller = @controller&.class
|
|
115
120
|
|
|
116
|
-
|
|
121
|
+
default = field_config.key?(:limit) ?
|
|
122
|
+
field_config[:limit] : controller&.association_limit
|
|
123
|
+
return default unless controller&.enable_association_queries
|
|
117
124
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
125
|
+
requested = self._requested_association_limit(association_name)
|
|
126
|
+
return default if requested.nil?
|
|
127
|
+
|
|
128
|
+
max = field_config.key?(:limit_max) ?
|
|
129
|
+
field_config[:limit_max] : controller.association_limit_max
|
|
130
|
+
|
|
131
|
+
# `all` means "as many as allowed" — the cap, or unlimited when the cap is `nil`.
|
|
132
|
+
return max if requested == :all
|
|
133
|
+
|
|
134
|
+
max ? [ requested, max ].min : requested
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# `all`, `none`, and `0` all mean "no limit" — `0` is free to reuse as a sentinel since an
|
|
138
|
+
# association is dropped via `except`, not `limit=0`. Anything else must be a plain positive
|
|
139
|
+
# integer, so a nested/array param or junk can't drive the query.
|
|
140
|
+
def _requested_association_limit(association_name)
|
|
141
|
+
return nil unless prefix = @controller.class.association_query_prefix.presence
|
|
142
|
+
|
|
143
|
+
raw = @controller.request&.query_parameters&.[]("#{prefix}.#{association_name}.limit")
|
|
144
|
+
return nil unless raw.is_a?(String)
|
|
145
|
+
|
|
146
|
+
raw = raw.strip
|
|
147
|
+
return :all if raw.in?(%w[all none])
|
|
148
|
+
return nil unless raw.match?(/\A\d+\z/)
|
|
149
|
+
|
|
150
|
+
# The regex guarantees a non-negative integer, so anything but zero is a positive limit.
|
|
151
|
+
limit = raw.to_i
|
|
152
|
+
limit.zero? ? :all : limit
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# The fields to serialize for association `f`, honoring a consumer's `?<prefix>.<f>.fields=`
|
|
156
|
+
# request bounded by the allowlist. Only active when the feature is enabled.
|
|
157
|
+
def _effective_association_fields(association_name, ref, field_config)
|
|
158
|
+
default_fields = field_config[:fields]
|
|
159
|
+
return default_fields unless @controller&.class&.enable_association_queries
|
|
160
|
+
return default_fields if ref.polymorphic? # no single target class to bound the request
|
|
161
|
+
|
|
162
|
+
requested = self._requested_association_fields(association_name)
|
|
163
|
+
return default_fields if requested.blank?
|
|
164
|
+
|
|
165
|
+
# The primary key is always kept so records stay identifiable. `_valid_association_field?` is
|
|
166
|
+
# the last line of defense against leaking a non-serializable or nested-association field.
|
|
167
|
+
allowed = (default_fields + (field_config[:requestable_fields] || [])).uniq
|
|
168
|
+
valid = (requested & allowed).select { |sf| self._valid_association_field?(ref, sf) }
|
|
169
|
+
(Array(ref.klass.primary_key).map(&:to_s) + valid).uniq
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Only a plain scalar string is honored, so a nested-hash/array param can't reach `where`/`split`.
|
|
173
|
+
def _requested_association_fields(association_name)
|
|
174
|
+
return nil unless prefix = @controller.class.association_query_prefix.presence
|
|
175
|
+
|
|
176
|
+
raw = @controller.request&.query_parameters&.[]("#{prefix}.#{association_name}.fields")
|
|
177
|
+
return nil unless raw.is_a?(String)
|
|
178
|
+
|
|
179
|
+
raw.split(",").map { |x| x.strip.presence }.compact
|
|
180
|
+
end
|
|
129
181
|
|
|
130
|
-
|
|
182
|
+
def _valid_association_field?(ref, field)
|
|
183
|
+
field.in?(ref.klass.column_names) ||
|
|
184
|
+
(ref.klass.method_defined?(field) && !ref.klass.reflect_on_association(field.to_sym))
|
|
131
185
|
end
|
|
132
186
|
|
|
133
187
|
# Get a serializer configuration from the controller. `@controller` and `@model` must be set.
|
|
@@ -156,7 +210,7 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
|
|
|
156
210
|
elsif ref = reflections[f]
|
|
157
211
|
sub_columns = []
|
|
158
212
|
sub_methods = []
|
|
159
|
-
field_config
|
|
213
|
+
self._effective_association_fields(f, ref, field_config).each do |sf|
|
|
160
214
|
if !ref.polymorphic? && sf.in?(ref.klass.column_names)
|
|
161
215
|
sub_columns << sf
|
|
162
216
|
else
|
|
@@ -167,9 +221,9 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
|
|
|
167
221
|
|
|
168
222
|
# Apply certain rules regarding collection associations.
|
|
169
223
|
if ref.collection?
|
|
170
|
-
#
|
|
171
|
-
#
|
|
172
|
-
if limit = self.
|
|
224
|
+
# A finite limit needs a per-record `.limit` query, since eager `includes` can't cap rows
|
|
225
|
+
# per parent; an unlimited (`nil`) association falls through to `includes` preloading.
|
|
226
|
+
if limit = self._effective_association_limit(f, field_config)
|
|
173
227
|
serializer_methods[f] = f
|
|
174
228
|
self.define_singleton_method(f) do |record|
|
|
175
229
|
next record.send(f).limit(limit).as_json(**sub_config)
|
|
@@ -179,7 +233,7 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
|
|
|
179
233
|
#
|
|
180
234
|
# # Even though we use a serializer method, if the count will later be added, then put
|
|
181
235
|
# # this field into the includes_map.
|
|
182
|
-
# if @controller.class.
|
|
236
|
+
# if @controller.class.include_association_count
|
|
183
237
|
# includes_map[f] = f.to_sym
|
|
184
238
|
# end
|
|
185
239
|
else
|
|
@@ -188,7 +242,7 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
|
|
|
188
242
|
end
|
|
189
243
|
|
|
190
244
|
# If we need to include the association count, then add it here.
|
|
191
|
-
if @controller.class.
|
|
245
|
+
if @controller.class.include_association_count
|
|
192
246
|
method_name = "#{f}.count"
|
|
193
247
|
serializer_methods[method_name] = method_name
|
|
194
248
|
self.define_singleton_method(method_name) do |record|
|
data/lib/rest_framework/utils.rb
CHANGED
|
@@ -1,61 +1,15 @@
|
|
|
1
1
|
module RESTFramework::Utils
|
|
2
2
|
HTTP_VERB_ORDERING = %w[GET POST PUT PATCH DELETE OPTIONS HEAD]
|
|
3
3
|
|
|
4
|
-
# Convert `extra_actions` hash to a consistent format: `{path:, methods:, metadata:, kwargs:}`.
|
|
5
|
-
def self.parse_extra_actions(extra_actions)
|
|
6
|
-
(extra_actions || {}).map { |k, v|
|
|
7
|
-
path = k
|
|
8
|
-
kwargs = {}
|
|
9
|
-
|
|
10
|
-
# Convert structure to path/methods/kwargs.
|
|
11
|
-
if v.is_a?(Hash)
|
|
12
|
-
# Symbolize keys (which also makes a copy so we don't mutate the original).
|
|
13
|
-
v = v.symbolize_keys
|
|
14
|
-
|
|
15
|
-
# Cast method/methods to an array.
|
|
16
|
-
methods = [ v.delete(:methods), v.delete(:method) ].flatten.compact
|
|
17
|
-
|
|
18
|
-
# Override path if it's provided.
|
|
19
|
-
if v.key?(:path)
|
|
20
|
-
path = v.delete(:path)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
# Extract metadata, if provided.
|
|
24
|
-
metadata = v.delete(:metadata).presence
|
|
25
|
-
|
|
26
|
-
# Pass any further kwargs to the underlying Rails interface.
|
|
27
|
-
kwargs = v
|
|
28
|
-
else
|
|
29
|
-
methods = [ v ].flatten
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
next [
|
|
33
|
-
k,
|
|
34
|
-
{
|
|
35
|
-
path: path,
|
|
36
|
-
methods: methods,
|
|
37
|
-
metadata: metadata,
|
|
38
|
-
kwargs: kwargs,
|
|
39
|
-
}.compact,
|
|
40
|
-
]
|
|
41
|
-
}.to_h
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def self.get_skipped_builtin_actions(controller_class, singular)
|
|
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)
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
4
|
# Get the first route pattern which matches the given request.
|
|
58
5
|
def self.get_request_route(application_routes, request)
|
|
6
|
+
# Prefer the route already resolved by the router to avoid an expensive `recognize` call. This
|
|
7
|
+
# is also required for Rails 8.1+ where OPTIONS routes are non-anchored, causing `path_info` to
|
|
8
|
+
# be modified during dispatch, which makes `recognize` fail from inside the controller action.
|
|
9
|
+
if route = request.env["action_dispatch.route"]
|
|
10
|
+
return route
|
|
11
|
+
end
|
|
12
|
+
|
|
59
13
|
application_routes.router.recognize(request) { |route, _| return route }
|
|
60
14
|
end
|
|
61
15
|
|
|
@@ -165,9 +119,8 @@ module RESTFramework::Utils
|
|
|
165
119
|
parsed_fields.map(&:to_s)
|
|
166
120
|
end
|
|
167
121
|
|
|
168
|
-
# Get the fields for a given model, including not just columns (which includes
|
|
169
|
-
#
|
|
170
|
-
# strings, not symbols.
|
|
122
|
+
# Get the fields for a given model, including not just columns (which includes foreign keys), but
|
|
123
|
+
# also associations. Note that we always return an array of strings, not symbols.
|
|
171
124
|
def self.fields_for(model, exclude_associations:, action_text:, active_storage:)
|
|
172
125
|
foreign_keys = model.reflect_on_all_associations(:belongs_to).map(&:foreign_key)
|
|
173
126
|
base_fields = model.column_names.reject { |c| c.in?(foreign_keys) }
|
|
@@ -201,23 +154,23 @@ module RESTFramework::Utils
|
|
|
201
154
|
base_fields + associations + atf + asf
|
|
202
155
|
end
|
|
203
156
|
|
|
204
|
-
# Get the
|
|
205
|
-
def self.
|
|
157
|
+
# Get the association's fields that may be serialized and filtered/ordered for a reflection.
|
|
158
|
+
def self.association_fields_for(ref)
|
|
206
159
|
if !ref.polymorphic? && model = ref.klass
|
|
207
|
-
|
|
160
|
+
fields = [ model.primary_key ].flatten.compact
|
|
208
161
|
label_fields = RESTFramework.config.label_fields
|
|
209
162
|
|
|
210
163
|
# Preferably find a database column to use as label.
|
|
211
164
|
if match = label_fields.find { |f| f.in?(model.column_names) }
|
|
212
|
-
return
|
|
165
|
+
return fields + [ match ]
|
|
213
166
|
end
|
|
214
167
|
|
|
215
168
|
# Otherwise, find a method.
|
|
216
169
|
if match = label_fields.find { |f| model.method_defined?(f) }
|
|
217
|
-
return
|
|
170
|
+
return fields + [ match ]
|
|
218
171
|
end
|
|
219
172
|
|
|
220
|
-
return
|
|
173
|
+
return fields
|
|
221
174
|
end
|
|
222
175
|
|
|
223
176
|
[ "id", "name" ]
|
|
@@ -236,6 +189,30 @@ module RESTFramework::Utils
|
|
|
236
189
|
nil
|
|
237
190
|
end
|
|
238
191
|
|
|
192
|
+
# Find the REST controller for `model` at the same namespace level as `current_controller`, e.g.
|
|
193
|
+
# `Api::Demo::MoviesController` + `Genre` => `Api::Demo::GenresController`, or `nil` if none. The
|
|
194
|
+
# `model` match guards against trusting a same-named controller for a different model.
|
|
195
|
+
def self.controller_for_model(current_controller, model)
|
|
196
|
+
return nil unless model && (base_name = current_controller.name)
|
|
197
|
+
|
|
198
|
+
namespace = base_name.deconstantize
|
|
199
|
+
model_name = model.model_name
|
|
200
|
+
|
|
201
|
+
# Plural for a collection controller, singular for a singular-resource one.
|
|
202
|
+
[ model_name.plural, model_name.singular ].each do |name|
|
|
203
|
+
candidate_name = "#{name.camelize}Controller"
|
|
204
|
+
candidate_name = "#{namespace}::#{candidate_name}" if namespace.present?
|
|
205
|
+
|
|
206
|
+
candidate = candidate_name.safe_constantize
|
|
207
|
+
next unless candidate.is_a?(Class) && candidate.include?(RESTFramework::Controller)
|
|
208
|
+
next unless candidate.model == model
|
|
209
|
+
|
|
210
|
+
return candidate
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
nil
|
|
214
|
+
end
|
|
215
|
+
|
|
239
216
|
# Wrap a serializer with an adapter if it is an ActiveModel::Serializer.
|
|
240
217
|
def self.wrap_ams(s)
|
|
241
218
|
if defined?(ActiveModel::Serializer) && (s < ActiveModel::Serializer)
|
|
@@ -244,12 +221,4 @@ module RESTFramework::Utils
|
|
|
244
221
|
|
|
245
222
|
s
|
|
246
223
|
end
|
|
247
|
-
|
|
248
|
-
# Used for deprecated mixins that rely on model being determined from the controller name.
|
|
249
|
-
def self.get_model(controller_class)
|
|
250
|
-
begin
|
|
251
|
-
controller_class.name.demodulize.chomp("Controller").singularize.constantize
|
|
252
|
-
rescue NameError
|
|
253
|
-
end
|
|
254
|
-
end
|
|
255
224
|
end
|
|
@@ -22,11 +22,14 @@ module RESTFramework
|
|
|
22
22
|
UNKNOWN
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
def self.stamp_version
|
|
26
|
-
#
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
def self.stamp_version(version = nil)
|
|
26
|
+
# Stamp the given version into the VERSION file, deriving it from git when none is provided.
|
|
27
|
+
# Returns the stamped version so callers don't rely on the (require-time) `VERSION` constant.
|
|
28
|
+
version ||= self.get_version
|
|
29
|
+
if version != UNKNOWN
|
|
30
|
+
File.write(VERSION_FILEPATH, version)
|
|
29
31
|
end
|
|
32
|
+
version
|
|
30
33
|
end
|
|
31
34
|
|
|
32
35
|
def self.unstamp_version
|
|
@@ -34,5 +37,5 @@ module RESTFramework
|
|
|
34
37
|
end
|
|
35
38
|
end
|
|
36
39
|
|
|
37
|
-
VERSION = Version.get_version
|
|
40
|
+
VERSION = Version.get_version(skip_git: true)
|
|
38
41
|
end
|
data/lib/rest_framework.rb
CHANGED
|
@@ -1,26 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module RESTFramework
|
|
4
|
-
BUILTIN_FORM_ACTIONS = [ :new, :edit ].freeze
|
|
5
|
-
BUILTIN_ACTIONS = {
|
|
6
|
-
index: :get,
|
|
7
|
-
new: :get,
|
|
8
|
-
create: :post,
|
|
9
|
-
}.freeze
|
|
10
|
-
BUILTIN_MEMBER_ACTIONS = {
|
|
11
|
-
show: :get,
|
|
12
|
-
edit: :get,
|
|
13
|
-
update: [ :put, :patch ].freeze,
|
|
14
|
-
destroy: :delete,
|
|
15
|
-
}.freeze
|
|
16
|
-
RRF_BUILTIN_ACTIONS = {
|
|
17
|
-
options: :options,
|
|
18
|
-
}.freeze
|
|
19
|
-
RRF_BUILTIN_BULK_ACTIONS = {
|
|
20
|
-
update_all: [ :put, :patch ].freeze,
|
|
21
|
-
destroy_all: :delete,
|
|
22
|
-
}.freeze
|
|
23
|
-
|
|
24
4
|
# Storage for extra routes and associated metadata.
|
|
25
5
|
EXTRA_ACTION_ROUTES = Set.new
|
|
26
6
|
ROUTE_METADATA = {}
|
|
@@ -47,6 +27,7 @@ module RESTFramework
|
|
|
47
27
|
# Bootstrap Icons
|
|
48
28
|
"bootstrap-icons.min.css" => {
|
|
49
29
|
url: "https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css",
|
|
30
|
+
sri: "sha384-XGjxtQfXaH2tnPFa9x+ruJTuLE3Aa6LhHSWRr1XeTyhezb4abCG4ccI5AkVDxqC+",
|
|
50
31
|
inline_fonts: true,
|
|
51
32
|
},
|
|
52
33
|
|
|
@@ -74,19 +55,23 @@ module RESTFramework
|
|
|
74
55
|
extra_tag_attrs: { class: "rrf-light-mode" },
|
|
75
56
|
},
|
|
76
57
|
|
|
77
|
-
# NeatJSON
|
|
58
|
+
# NeatJSON. The package publishes no minified build; jsdelivr's `neatjson.min.js` is synthesized
|
|
59
|
+
# on the fly (so its bytes/SRI aren't stable), so we pin the published `neatjson.js` instead.
|
|
78
60
|
"neatjson.min.js" => {
|
|
79
|
-
url: "https://cdn.jsdelivr.net/npm/neatjson@0.10.6/javascript/neatjson.
|
|
61
|
+
url: "https://cdn.jsdelivr.net/npm/neatjson@0.10.6/javascript/neatjson.js",
|
|
62
|
+
sri: "sha384-7yRMvyGBuBnnlsgFwUDx7wY7Y/kXt7Irg5xppXVAI0yp3R0870lWaXNP/JE2rbFA",
|
|
80
63
|
exclude_from_docs: true,
|
|
81
64
|
},
|
|
82
65
|
|
|
83
66
|
# Trix
|
|
84
67
|
"trix.min.css" => {
|
|
85
68
|
url: "https://unpkg.com/trix@2.0.8/dist/trix.css",
|
|
69
|
+
sri: "sha384-Cgg84c/W0Q5VrTzc4ITmV88Ocx4Pn1YlTXPvSTJjX6+lxJRLYrZ4chaighxlqOY1",
|
|
86
70
|
exclude_from_docs: true,
|
|
87
71
|
},
|
|
88
72
|
"trix.min.js" => {
|
|
89
73
|
url: "https://unpkg.com/trix@2.0.8/dist/trix.umd.min.js",
|
|
74
|
+
sri: "sha384-Ki3zDe3whjAHK/GOksrYLGCU8m0SkmiJQ4Kqm3jQlM3YCl2hFBf17s9bzjVYmXWX",
|
|
90
75
|
exclude_from_docs: true,
|
|
91
76
|
},
|
|
92
77
|
}.map { |name, cfg|
|
|
@@ -149,19 +134,6 @@ module RESTFramework
|
|
|
149
134
|
# Permits use of `render(api: obj)` syntax over `render_api(obj)`; `true` by default.
|
|
150
135
|
attr_accessor :register_api_renderer
|
|
151
136
|
|
|
152
|
-
# Run `rrf_finalize` on controllers automatically using a `TracePoint` hook. This is `true` by
|
|
153
|
-
# default, and can be disabled for performance, and must be global because we have to determine
|
|
154
|
-
# this before any controller-specific configuration is set. If this is set to `false`, then you
|
|
155
|
-
# must manually call `rrf_finalize` after any configuration on each controller that needs to
|
|
156
|
-
# participate in:
|
|
157
|
-
# - Model delegation, for the helper methods to be defined dynamically.
|
|
158
|
-
# - Websockets, for `::Channel` class to be defined dynamically.
|
|
159
|
-
# - Controller configuration freezing.
|
|
160
|
-
attr_accessor :auto_finalize
|
|
161
|
-
|
|
162
|
-
# Freeze configuration attributes during finalization to prevent accidental mutation.
|
|
163
|
-
attr_accessor :freeze_config
|
|
164
|
-
|
|
165
137
|
# Specify reverse association tables that are typically very large, and therefore should not be
|
|
166
138
|
# added to fields by default.
|
|
167
139
|
attr_accessor :large_reverse_association_tables
|
|
@@ -169,9 +141,6 @@ module RESTFramework
|
|
|
169
141
|
# Whether the backtrace should be shown in rescued errors.
|
|
170
142
|
attr_accessor :show_backtrace
|
|
171
143
|
|
|
172
|
-
# Disable `rescue_from` on the controller mixins.
|
|
173
|
-
attr_accessor :disable_rescue_from
|
|
174
|
-
|
|
175
144
|
# The default label fields to use when generating labels for `has_many` associations.
|
|
176
145
|
attr_accessor :label_fields
|
|
177
146
|
|
|
@@ -191,7 +160,6 @@ module RESTFramework
|
|
|
191
160
|
|
|
192
161
|
def initialize
|
|
193
162
|
self.register_api_renderer = true
|
|
194
|
-
self.auto_finalize = true
|
|
195
163
|
|
|
196
164
|
self.show_backtrace = Rails.env.development?
|
|
197
165
|
|
|
@@ -223,8 +191,6 @@ end
|
|
|
223
191
|
require_relative "rest_framework/engine"
|
|
224
192
|
require_relative "rest_framework/errors"
|
|
225
193
|
require_relative "rest_framework/filters"
|
|
226
|
-
require_relative "rest_framework/generators"
|
|
227
|
-
require_relative "rest_framework/mixins"
|
|
228
194
|
require_relative "rest_framework/paginators"
|
|
229
195
|
require_relative "rest_framework/routers"
|
|
230
196
|
require_relative "rest_framework/serializers"
|
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:
|
|
4
|
+
version: 2.0.0.beta1
|
|
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-
|
|
11
|
+
date: 2026-07-31 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -55,25 +55,18 @@ files:
|
|
|
55
55
|
- app/views/rest_framework/routes_and_forms/routes/_route.html.erb
|
|
56
56
|
- lib/rest_framework.rb
|
|
57
57
|
- lib/rest_framework/controller.rb
|
|
58
|
+
- lib/rest_framework/controller/actions.rb
|
|
58
59
|
- lib/rest_framework/controller/bulk.rb
|
|
59
60
|
- lib/rest_framework/controller/crud.rb
|
|
60
61
|
- lib/rest_framework/controller/openapi.rb
|
|
61
62
|
- lib/rest_framework/engine.rb
|
|
62
63
|
- lib/rest_framework/errors.rb
|
|
63
|
-
- lib/rest_framework/errors/base_error.rb
|
|
64
|
-
- lib/rest_framework/errors/nil_passed_to_render_api_error.rb
|
|
65
64
|
- lib/rest_framework/filters.rb
|
|
66
65
|
- lib/rest_framework/filters/base_filter.rb
|
|
67
66
|
- lib/rest_framework/filters/ordering_filter.rb
|
|
68
67
|
- lib/rest_framework/filters/query_filter.rb
|
|
69
68
|
- lib/rest_framework/filters/ransack_filter.rb
|
|
70
69
|
- lib/rest_framework/filters/search_filter.rb
|
|
71
|
-
- lib/rest_framework/generators.rb
|
|
72
|
-
- lib/rest_framework/generators/controller_generator.rb
|
|
73
|
-
- lib/rest_framework/mixins.rb
|
|
74
|
-
- lib/rest_framework/mixins/base_controller_mixin.rb
|
|
75
|
-
- lib/rest_framework/mixins/bulk_model_controller_mixin.rb
|
|
76
|
-
- lib/rest_framework/mixins/model_controller_mixin.rb
|
|
77
70
|
- lib/rest_framework/paginators.rb
|
|
78
71
|
- lib/rest_framework/paginators/base_paginator.rb
|
|
79
72
|
- lib/rest_framework/paginators/page_number_paginator.rb
|
|
@@ -105,9 +98,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
105
98
|
version: 2.7.5
|
|
106
99
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
107
100
|
requirements:
|
|
108
|
-
- - "
|
|
101
|
+
- - ">"
|
|
109
102
|
- !ruby/object:Gem::Version
|
|
110
|
-
version:
|
|
103
|
+
version: 1.3.1
|
|
111
104
|
requirements: []
|
|
112
105
|
rubygems_version: 3.4.10
|
|
113
106
|
signing_key:
|
|
@@ -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,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
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
module RESTFramework::Mixins::BaseControllerMixin
|
|
2
|
-
def self.included(base)
|
|
3
|
-
RESTFramework.deprecator.warn(
|
|
4
|
-
"BaseControllerMixin is deprecated; use RESTFramework::Controller instead.",
|
|
5
|
-
)
|
|
6
|
-
|
|
7
|
-
base.include(RESTFramework::Controller)
|
|
8
|
-
end
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
# Alias for convenience.
|
|
12
|
-
RESTFramework::BaseControllerMixin = RESTFramework::Mixins::BaseControllerMixin
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
module RESTFramework::Mixins::BulkCreateModelMixin
|
|
2
|
-
def self.included(base)
|
|
3
|
-
RESTFramework.deprecator.warn(<<~TXT).squish
|
|
4
|
-
BulkCreateModelMixin is deprecated; set the `bulk = true` class attribute instead.
|
|
5
|
-
TXT
|
|
6
|
-
|
|
7
|
-
base.bulk = true
|
|
8
|
-
end
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
# Mixin for updating records in bulk.
|
|
12
|
-
module RESTFramework::Mixins::BulkUpdateModelMixin
|
|
13
|
-
def self.included(base)
|
|
14
|
-
RESTFramework.deprecator.warn(<<~TXT).squish
|
|
15
|
-
BulkUpdateModelMixin is deprecated; set the `bulk = true`, and `excluded_actions` class
|
|
16
|
-
attributes instead.
|
|
17
|
-
TXT
|
|
18
|
-
|
|
19
|
-
base.bulk = true
|
|
20
|
-
base.excluded_actions = (base.excluded_actions - [ :update_all ]).freeze
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
# Mixin for destroying records in bulk.
|
|
25
|
-
module RESTFramework::Mixins::BulkDestroyModelMixin
|
|
26
|
-
def self.included(base)
|
|
27
|
-
RESTFramework.deprecator.warn(<<~TXT).squish
|
|
28
|
-
BulkDestroyModelMixin is deprecated; set the `bulk = true`, and `excluded_actions` class
|
|
29
|
-
attributes instead.
|
|
30
|
-
TXT
|
|
31
|
-
|
|
32
|
-
base.bulk = true
|
|
33
|
-
base.excluded_actions = (base.excluded_actions - [ :destroy_all ]).freeze
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# Mixin that includes all the CRUD bulk mixins.
|
|
38
|
-
module RESTFramework::Mixins::BulkModelControllerMixin
|
|
39
|
-
def self.included(base)
|
|
40
|
-
RESTFramework.deprecator.warn(<<~TXT).squish
|
|
41
|
-
BulkModelControllerMixin is deprecated; use RESTFramework::Controller and set the `model` and
|
|
42
|
-
`bulk = true` class attributes instead.
|
|
43
|
-
TXT
|
|
44
|
-
|
|
45
|
-
base.include(RESTFramework::Controller)
|
|
46
|
-
base.model = RESTFramework::Utils.get_model(base)
|
|
47
|
-
base.bulk = true
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# Aliases for convenience.
|
|
52
|
-
RESTFramework::BulkCreateModelMixin = RESTFramework::Mixins::BulkCreateModelMixin
|
|
53
|
-
RESTFramework::BulkUpdateModelMixin = RESTFramework::Mixins::BulkUpdateModelMixin
|
|
54
|
-
RESTFramework::BulkDestroyModelMixin = RESTFramework::Mixins::BulkDestroyModelMixin
|
|
55
|
-
RESTFramework::BulkModelControllerMixin = RESTFramework::Mixins::BulkModelControllerMixin
|