rest_framework 2.0.0.beta1 → 2.0.0.beta3
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 +6 -4
- data/VERSION +1 -1
- data/lib/rest_framework/controller.rb +40 -22
- data/lib/rest_framework/filters/ordering_filter.rb +2 -1
- data/lib/rest_framework/filters/query_filter.rb +2 -2
- data/lib/rest_framework/filters/search_filter.rb +4 -8
- data/lib/rest_framework/serializers/native_serializer.rb +39 -9
- 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: 7cedfa02dac90388b712f4e15e29d07a40e45df23847bebf5da02eea03862e75
|
|
4
|
+
data.tar.gz: 2de9c962810e9473dd5eb68c2ee42da764ed793e341049a49d9f4ca8d34fa48e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f52b1b1075fef181d9e771df56f1c088c73601d600606411974febb1bb544facacc7d16d83c2b34929acc0d0f44a0e518098a3a121fcdf27f7e5f12be3a5d8ca
|
|
7
|
+
data.tar.gz: c08ffc3af87bb7d7337dd1a908c7c6199fa3e70328b06dbebd1a691a0c3f235224b0119026eda4274f555385254383d2a7a29719b339f5c487f0ee82a7b2bcd2
|
data/README.md
CHANGED
|
@@ -202,8 +202,10 @@ changes; the migration checklist that follows walks through updating an existing
|
|
|
202
202
|
- **Delegated actions wrap their result under a `return` key**, and raise on a missing or non-public
|
|
203
203
|
target instead of silently 404-ing.
|
|
204
204
|
- **Removed** `rrf_finalize` and the `auto_finalize` / `freeze_config` hooks.
|
|
205
|
-
- **Security hardening** —
|
|
206
|
-
|
|
205
|
+
- **Security hardening** — `find_by`, filtering, ordering, and search are scoped to serialized,
|
|
206
|
+
non-`write_only` fields (so hidden/secret columns can't be used as lookup/enumeration keys), plus
|
|
207
|
+
per-element read-only stripping on bulk writes, an ordering-oracle fix, sanitized
|
|
208
|
+
`StatementInvalid` messages, and safer query-filter parsing.
|
|
207
209
|
|
|
208
210
|
### Migrating from Older Versions
|
|
209
211
|
|
|
@@ -217,8 +219,8 @@ See the guide for details on each item.
|
|
|
217
219
|
- [ ] Render custom actions with `render(api: ...)`, replacing the older `api_response(...)` /
|
|
218
220
|
`render_api(...)`.
|
|
219
221
|
- [ ] Replace `rest_resource` / `rest_resources` / `rest_root` with `rest_route`.
|
|
220
|
-
|
|
221
|
-
|
|
222
|
+
- [ ] Fold any dedicated root controller into the namespace's base controller, which now serves the
|
|
223
|
+
root via `index_content` (the standalone `root` action and `rest_root` are gone).
|
|
222
224
|
- [ ] Rename `singleton_controller` → `singular`.
|
|
223
225
|
- [ ] Remove `rrf_finalize` calls and the `auto_finalize` / `freeze_config` config (all gone).
|
|
224
226
|
- [ ] Expect paginated `index` responses by default (`self.paginator_class = nil` restores a bare
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.0.0.
|
|
1
|
+
2.0.0.beta3
|
|
@@ -253,33 +253,29 @@ module RESTFramework::Controller
|
|
|
253
253
|
self.model&.human_attribute_name(s, default: default_title) || default_title
|
|
254
254
|
end
|
|
255
255
|
|
|
256
|
-
#
|
|
257
|
-
#
|
|
258
|
-
def get_fields
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if input_fields.is_a?(Hash)
|
|
263
|
-
return RESTFramework::Utils.parse_fields_hash(
|
|
264
|
-
input_fields,
|
|
256
|
+
# Resolve the `fields` config to an array of strings. Memoized, since `fields` and the flags it
|
|
257
|
+
# depends on are class-level config fixed at load time.
|
|
258
|
+
def get_fields
|
|
259
|
+
@get_fields ||= if self.fields.is_a?(Hash)
|
|
260
|
+
RESTFramework::Utils.parse_fields_hash(
|
|
261
|
+
self.fields,
|
|
265
262
|
self.model,
|
|
266
263
|
exclude_associations: self.exclude_associations,
|
|
267
264
|
action_text: self.enable_action_text,
|
|
268
265
|
active_storage: self.enable_active_storage,
|
|
269
266
|
)
|
|
270
|
-
elsif
|
|
271
|
-
|
|
272
|
-
|
|
267
|
+
elsif self.fields
|
|
268
|
+
self.fields.map(&:to_s)
|
|
269
|
+
elsif self.model
|
|
270
|
+
RESTFramework::Utils.fields_for(
|
|
273
271
|
self.model,
|
|
274
272
|
exclude_associations: self.exclude_associations,
|
|
275
273
|
action_text: self.enable_action_text,
|
|
276
274
|
active_storage: self.enable_active_storage,
|
|
277
|
-
)
|
|
278
|
-
|
|
279
|
-
|
|
275
|
+
)
|
|
276
|
+
else
|
|
277
|
+
[]
|
|
280
278
|
end
|
|
281
|
-
|
|
282
|
-
input_fields
|
|
283
279
|
end
|
|
284
280
|
|
|
285
281
|
# Get a full field configuration, including defaults and inferred values.
|
|
@@ -647,7 +643,27 @@ module RESTFramework::Controller
|
|
|
647
643
|
end
|
|
648
644
|
|
|
649
645
|
def get_fields
|
|
650
|
-
self.class.get_fields
|
|
646
|
+
self.class.get_fields
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
def readable_fields
|
|
650
|
+
cfg = self.class.field_configuration
|
|
651
|
+
self.get_fields.reject { |f| cfg[f]&.[](:write_only) }
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
# `readable_fields` restricted to real columns, for query surfaces that build SQL directly
|
|
655
|
+
# (find_by, search) and would raise on a virtual/method field.
|
|
656
|
+
def readable_columns
|
|
657
|
+
self.readable_fields & self.class.model.column_names
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
# `readable_fields` restricted to columns and associations, for surfaces that also resolve dotted
|
|
661
|
+
# `association.sub_field` paths (filtering, ordering). Excludes virtual/method fields, which have
|
|
662
|
+
# no column to order or filter by.
|
|
663
|
+
def readable_columns_or_associations
|
|
664
|
+
cfg = self.class.field_configuration
|
|
665
|
+
columns = self.class.model.column_names
|
|
666
|
+
self.readable_fields.select { |f| f.in?(columns) || cfg[f]&.[](:kind) == "association" }
|
|
651
667
|
end
|
|
652
668
|
|
|
653
669
|
# Get a hash of strong parameters for the current action.
|
|
@@ -657,7 +673,8 @@ module RESTFramework::Controller
|
|
|
657
673
|
@_get_allowed_parameters = self.class.allowed_parameters
|
|
658
674
|
return @_get_allowed_parameters if @_get_allowed_parameters
|
|
659
675
|
|
|
660
|
-
# Assemble strong parameters.
|
|
676
|
+
# Assemble strong parameters. Read-only fields are permitted here and stripped later per-action
|
|
677
|
+
# by `_rrf_strip_read_only_fields` (which keeps the primary key on bulk update to find records).
|
|
661
678
|
variations = []
|
|
662
679
|
hash_variations = {}
|
|
663
680
|
reflections = self.class.model.reflections
|
|
@@ -868,9 +885,10 @@ module RESTFramework::Controller
|
|
|
868
885
|
# Find by another column if it's permitted.
|
|
869
886
|
if find_by_param = self.class.find_by_query_param.presence
|
|
870
887
|
if find_by = request.query_parameters[find_by_param].presence
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
)
|
|
888
|
+
# Default to readable columns: excluding write_only keeps hidden values from being used as
|
|
889
|
+
# lookup keys, and restricting to real columns keeps virtual/method fields from reaching a
|
|
890
|
+
# doomed `find_by(<not a column>)` (which would raise on the DB).
|
|
891
|
+
find_by_fields = self.class.find_by_fields&.map(&:to_s) || self.readable_columns
|
|
874
892
|
|
|
875
893
|
# A `find_by` was explicitly requested, so it must be a permitted field.
|
|
876
894
|
raise ActiveRecord::RecordNotFound unless find_by.in?(find_by_fields)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# A filter backend which handles ordering of the recordset.
|
|
2
2
|
class RESTFramework::Filters::OrderingFilter < RESTFramework::Filters::BaseFilter
|
|
3
3
|
def _get_fields
|
|
4
|
-
|
|
4
|
+
# Always return a list of strings; `@controller.readable_columns_or_associations` already does.
|
|
5
|
+
@controller.class.ordering_fields&.map(&:to_s) || @controller.readable_columns_or_associations
|
|
5
6
|
end
|
|
6
7
|
|
|
7
8
|
# Convert the ordering param into an `[ordering, references]` pair: the ordering config for
|
|
@@ -43,8 +43,8 @@ class RESTFramework::Filters::QueryFilter < RESTFramework::Filters::BaseFilter
|
|
|
43
43
|
ARRAY_PREDICATES = %i[in not].freeze
|
|
44
44
|
|
|
45
45
|
def _get_fields
|
|
46
|
-
# Always return a list of strings; `@controller.
|
|
47
|
-
@controller.class.filter_fields&.map(&:to_s) || @controller.
|
|
46
|
+
# Always return a list of strings; `@controller.readable_columns_or_associations` already does.
|
|
47
|
+
@controller.class.filter_fields&.map(&:to_s) || @controller.readable_columns_or_associations
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
# Helper to find a variation of a field using a predicate. For example, there could be a field
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
class RESTFramework::Filters::SearchFilter < RESTFramework::Filters::BaseFilter
|
|
2
2
|
def _get_fields
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
columns = @controller.class.model.column_names
|
|
8
|
-
@controller.get_fields.select { |f|
|
|
9
|
-
f.in?(RESTFramework.config.search_columns) && f.in?(columns)
|
|
10
|
-
}
|
|
3
|
+
# Always return a list of strings; `@controller.readable_columns` already does.
|
|
4
|
+
@controller.class.search_fields&.map(&:to_s) || (
|
|
5
|
+
@controller.readable_columns & RESTFramework.config.search_columns
|
|
6
|
+
)
|
|
11
7
|
end
|
|
12
8
|
|
|
13
9
|
# Filter data according to the request query parameters.
|
|
@@ -184,6 +184,39 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
|
|
|
184
184
|
(ref.klass.method_defined?(field) && !ref.klass.reflect_on_association(field.to_sym))
|
|
185
185
|
end
|
|
186
186
|
|
|
187
|
+
# Recursively translate an association's fields into a `serializable_hash` config
|
|
188
|
+
# (`only`/`methods`/`include`). Columns go to `only` and plain methods to `methods`; a field that
|
|
189
|
+
# is itself an association is recursed into (as a nested `include`) only when it has its own entry
|
|
190
|
+
# in `field_config[:field_config]`. Otherwise it falls through to a method and serializes as
|
|
191
|
+
# before (its full `as_json`), so deeper nesting is opt-in and never narrows the default output.
|
|
192
|
+
def _build_association_config(model, fields, field_config)
|
|
193
|
+
nested = field_config[:field_config] || {}
|
|
194
|
+
only = []
|
|
195
|
+
methods = []
|
|
196
|
+
includes = {}
|
|
197
|
+
|
|
198
|
+
fields.each do |sf|
|
|
199
|
+
sf = sf.to_s
|
|
200
|
+
sub_field_config = nested[sf.to_sym] || nested[sf]
|
|
201
|
+
|
|
202
|
+
if sf.in?(model.column_names)
|
|
203
|
+
only << sf
|
|
204
|
+
elsif sub_field_config && (ref = model.reflect_on_association(sf.to_sym)) && !ref.polymorphic?
|
|
205
|
+
sub_fields = sub_field_config[:fields]&.map(&:to_s) ||
|
|
206
|
+
RESTFramework::Utils.association_fields_for(ref)
|
|
207
|
+
includes[sf] = self._build_association_config(ref.klass, sub_fields, sub_field_config)
|
|
208
|
+
elsif model.method_defined?(sf)
|
|
209
|
+
methods << sf
|
|
210
|
+
else
|
|
211
|
+
only << sf
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
config = { only: only, methods: methods }
|
|
216
|
+
config[:include] = includes if includes.any?
|
|
217
|
+
config
|
|
218
|
+
end
|
|
219
|
+
|
|
187
220
|
# Get a serializer configuration from the controller. `@controller` and `@model` must be set.
|
|
188
221
|
def _get_controller_serializer_config
|
|
189
222
|
columns = []
|
|
@@ -208,16 +241,13 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
|
|
|
208
241
|
if f.in?(column_names)
|
|
209
242
|
columns << f
|
|
210
243
|
elsif ref = reflections[f]
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
sub_methods << sf
|
|
218
|
-
end
|
|
244
|
+
effective_fields = self._effective_association_fields(f, ref, field_config)
|
|
245
|
+
sub_config = if ref.polymorphic?
|
|
246
|
+
# No single target class to introspect, so serialize every field as a method.
|
|
247
|
+
{ only: [], methods: effective_fields }
|
|
248
|
+
else
|
|
249
|
+
self._build_association_config(ref.klass, effective_fields, field_config)
|
|
219
250
|
end
|
|
220
|
-
sub_config = { only: sub_columns, methods: sub_methods }
|
|
221
251
|
|
|
222
252
|
# Apply certain rules regarding collection associations.
|
|
223
253
|
if ref.collection?
|
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: 2.0.0.
|
|
4
|
+
version: 2.0.0.beta3
|
|
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-08-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|