rest_framework 2.0.0.beta4 → 2.0.0.beta5
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 +14 -11
- data/VERSION +1 -1
- data/lib/rest_framework/controller.rb +79 -10
- data/lib/rest_framework/routers.rb +18 -14
- 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: e483f8e7b422c2a45545f8c6e5d6313cc8a3df9dc286e76d4a6f09b9c717d9bf
|
|
4
|
+
data.tar.gz: 16d95dda8928c50b01cbc9cc291778928c9023d77e05c35f51cc9c8411a4c7d0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 05ae9adf0e303bc8aa23acda8d33f8e2026de7e123ec57a1c51dc030b92b25caed62d872badf76b9c8e3c0f549c5899b59c3352b2620c767b61d4e1b1ac34672
|
|
7
|
+
data.tar.gz: ca6913825b5eaf6c6545359c494b8b38eae2deb87e285ed8d1e761ab38700f1f0e2be4469d3ab6c604bc98d30918b4c1c40ce0098d32027e0bed52db51c2b049
|
data/README.md
CHANGED
|
@@ -137,18 +137,20 @@ end
|
|
|
137
137
|
|
|
138
138
|
### Routing
|
|
139
139
|
|
|
140
|
-
Use `
|
|
141
|
-
`resources` for a plural model controller and
|
|
142
|
-
|
|
143
|
-
|
|
140
|
+
Use `rest_resource` to route a controller (`rest_resources` routes several that share options). They
|
|
141
|
+
wrap Rails' `resource` / `resources` routers, picking `resources` for a plural model controller and
|
|
142
|
+
`resource` otherwise — so plurality follows the controller's config, not the helper name. Built-in
|
|
143
|
+
and extra actions are routed automatically: a controller with a `model` gets the full CRUD set; a
|
|
144
|
+
modelless controller is routed at its root (its `index`, which renders `index_content`). A block
|
|
145
|
+
nests resources, and a nested recordset is auto-scoped to its parent (`/movies/:movie_id/genres` →
|
|
146
|
+
`Movie.find(params[:movie_id]).genres`).
|
|
144
147
|
|
|
145
148
|
```ruby
|
|
146
149
|
Rails.application.routes.draw do
|
|
147
|
-
|
|
150
|
+
rest_resource :api # `ApiController` serves the `/api` root.
|
|
148
151
|
|
|
149
152
|
namespace :api do
|
|
150
|
-
|
|
151
|
-
rest_route :users
|
|
153
|
+
rest_resources :movies, :users
|
|
152
154
|
end
|
|
153
155
|
end
|
|
154
156
|
```
|
|
@@ -181,9 +183,9 @@ changes; the migration checklist that follows walks through updating an existing
|
|
|
181
183
|
- **Declarative actions** — `add_action` / `remove_action` declare extra routes with an explicit
|
|
182
184
|
`member` / `collection` scope and per-declaration propagation, and can disable built-ins too,
|
|
183
185
|
replacing the `extra_actions` config hashes.
|
|
184
|
-
- **
|
|
185
|
-
|
|
186
|
-
`
|
|
186
|
+
- **Simpler routing** — `rest_resource` routes one controller and `rest_resources` routes several
|
|
187
|
+
(plurality of the *routes* follows the controller's config, not the helper name); `rest_root` and
|
|
188
|
+
`rest_route` are gone, and nested resources are auto-scoped to their parent.
|
|
187
189
|
- **Action delegation** — mark an action `metadata: { delegate: true }` to dispatch it to a model
|
|
188
190
|
class method (collection) or record method (member), passing query params through as args/kwargs.
|
|
189
191
|
- **Consumer-driven association queries** (opt-in via `enable_association_queries`) — clients can
|
|
@@ -218,7 +220,8 @@ See the guide for details on each item.
|
|
|
218
220
|
- [ ] Convert `extra_actions` / `extra_member_actions` hashes to `add_action` / `remove_action`.
|
|
219
221
|
- [ ] Render custom actions with `render(api: ...)`, replacing the older `api_response(...)` /
|
|
220
222
|
`render_api(...)`.
|
|
221
|
-
- [ ] Replace `
|
|
223
|
+
- [ ] Replace `rest_root` and `rest_route` with `rest_resource` (single controller) or
|
|
224
|
+
`rest_resources` (several) — plurality of the routes now comes from controller config.
|
|
222
225
|
- [ ] Fold any dedicated root controller into the namespace's base controller, which now serves the
|
|
223
226
|
root via `index_content` (the standalone `root` action and `rest_root` are gone).
|
|
224
227
|
- [ ] Rename `singleton_controller` → `singular`.
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.0.0.
|
|
1
|
+
2.0.0.beta5
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
# module rather than defining a separate submodule.
|
|
4
4
|
module RESTFramework::Controller
|
|
5
5
|
RRF_BASE_CONFIG = {
|
|
6
|
+
model: nil,
|
|
6
7
|
singular: nil,
|
|
7
8
|
|
|
8
9
|
# Options related to metadata and display.
|
|
@@ -12,10 +13,6 @@ module RESTFramework::Controller
|
|
|
12
13
|
inflect_acronyms: RESTFramework.config.inflect_acronyms,
|
|
13
14
|
openapi_include_children: false,
|
|
14
15
|
|
|
15
|
-
# Options related to models.
|
|
16
|
-
model: nil,
|
|
17
|
-
recordset: nil,
|
|
18
|
-
|
|
19
16
|
# Bulk configuration.
|
|
20
17
|
#
|
|
21
18
|
# When `bulk` is truthy, it enables the default bulk behavior (`:default`), which is per-record
|
|
@@ -98,6 +95,10 @@ module RESTFramework::Controller
|
|
|
98
95
|
# Option for `recordset.create` vs `Model.create` behavior.
|
|
99
96
|
create_from_recordset: true,
|
|
100
97
|
|
|
98
|
+
# Options for scoped nested routing.
|
|
99
|
+
scope_nested_by_parent: true,
|
|
100
|
+
scope_nested_through_controllers: true,
|
|
101
|
+
|
|
101
102
|
# Options related to serialization.
|
|
102
103
|
rescue_unknown_format_with: :json,
|
|
103
104
|
serializer_class: nil,
|
|
@@ -840,16 +841,84 @@ module RESTFramework::Controller
|
|
|
840
841
|
alias_method :get_update_params, :get_body_params
|
|
841
842
|
alias_method :get_destroy_params, :get_body_params
|
|
842
843
|
|
|
843
|
-
# Get the set of records this controller has access to.
|
|
844
|
+
# Get the set of records this controller has access to. Override this to scope records (e.g. to
|
|
845
|
+
# the current user); the default scopes to a nested parent resource when one is present in the
|
|
846
|
+
# path, otherwise the model's default scope (all records).
|
|
844
847
|
def get_recordset
|
|
845
|
-
return
|
|
848
|
+
return nil unless self.class.model
|
|
849
|
+
|
|
850
|
+
self._rrf_nested_parent_recordset || self.class.model.all
|
|
851
|
+
end
|
|
852
|
+
|
|
853
|
+
# For a nested route, walk the whole parent chain in path order and return the innermost
|
|
854
|
+
# collection of this controller's model — e.g. `/movies/:movie_id/genres/:genre_id/tracks` becomes
|
|
855
|
+
# `Movie.find(movie_id).genres.find(genre_id).tracks`. Every link is enforced (a broken one raises
|
|
856
|
+
# `RecordNotFound` -> 404), and each association is resolved from its parent, so `belongs_to`,
|
|
857
|
+
# `has_many`, and `has_and_belongs_to_many` children all work. Each parent is looked up via its
|
|
858
|
+
# own controller's recordset (see `scope_nested_through_controllers`), so per-level access scoping
|
|
859
|
+
# is enforced. Returns `nil` when there is no nested parent, or a `<name>_id` param can't connect.
|
|
860
|
+
def _rrf_nested_parent_recordset
|
|
861
|
+
# Set on an ad-hoc parent instance below, so evaluating a parent's `get_recordset` doesn't
|
|
862
|
+
# recurse back into nested scoping (we want the parent's own scope, not to re-nest it).
|
|
863
|
+
return nil if @_rrf_scoping_parent
|
|
864
|
+
return nil unless self.class.scope_nested_by_parent && request
|
|
865
|
+
|
|
866
|
+
# `<name>_id` path parameters that name a model, in route order (outermost parent first).
|
|
867
|
+
parents = request.path_parameters.filter_map { |key, value|
|
|
868
|
+
key = key.to_s
|
|
869
|
+
next unless key.end_with?("_id")
|
|
870
|
+
|
|
871
|
+
model = key.delete_suffix("_id").classify.safe_constantize
|
|
872
|
+
next unless model.is_a?(Class) && model < ActiveRecord::Base
|
|
873
|
+
|
|
874
|
+
[ model, value ]
|
|
875
|
+
}
|
|
876
|
+
return nil if parents.empty?
|
|
877
|
+
|
|
878
|
+
# Find the outermost parent within its controller's scope, then descend: each next parent is
|
|
879
|
+
# constrained both to the previous parent's association and to its own controller's scope.
|
|
880
|
+
record = nil
|
|
881
|
+
parents.each do |model, id|
|
|
882
|
+
scope = _rrf_parent_recordset(model)
|
|
883
|
+
|
|
884
|
+
unless record.nil?
|
|
885
|
+
assoc = _rrf_collection_association_name(record.class, model)
|
|
886
|
+
return nil unless assoc
|
|
846
887
|
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
888
|
+
scope = record.public_send(assoc).merge(scope)
|
|
889
|
+
end
|
|
890
|
+
|
|
891
|
+
record = scope.find(id)
|
|
850
892
|
end
|
|
851
893
|
|
|
852
|
-
|
|
894
|
+
# Finally, the innermost parent's collection of this controller's model.
|
|
895
|
+
assoc = _rrf_collection_association_name(record.class, self.class.model)
|
|
896
|
+
return nil unless assoc
|
|
897
|
+
|
|
898
|
+
record.public_send(assoc)
|
|
899
|
+
end
|
|
900
|
+
|
|
901
|
+
# A parent's recordset for the nested-scope walk: its own controller's `get_recordset` (so that
|
|
902
|
+
# controller's access scoping is reused), or the bare model when the feature is off or no sibling
|
|
903
|
+
# controller is found. The ad-hoc instance shares this request and skips its own nested scoping.
|
|
904
|
+
def _rrf_parent_recordset(model)
|
|
905
|
+
return model.all unless self.class.scope_nested_through_controllers
|
|
906
|
+
|
|
907
|
+
controller = RESTFramework::Utils.controller_for_model(self.class, model)
|
|
908
|
+
return model.all unless controller
|
|
909
|
+
|
|
910
|
+
instance = controller.new
|
|
911
|
+
instance.request = request
|
|
912
|
+
instance.response = response
|
|
913
|
+
instance.instance_variable_set(:@_rrf_scoping_parent, true)
|
|
914
|
+
instance.get_recordset
|
|
915
|
+
end
|
|
916
|
+
|
|
917
|
+
# The name of `klass`'s collection association whose records are `target_model`, or `nil`.
|
|
918
|
+
def _rrf_collection_association_name(klass, target_model)
|
|
919
|
+
klass.reflect_on_all_associations.find { |ref|
|
|
920
|
+
ref.collection? && !ref.polymorphic? && ref.klass == target_model
|
|
921
|
+
}&.name
|
|
853
922
|
end
|
|
854
923
|
|
|
855
924
|
# Filter the recordset and return records this request has access to.
|
|
@@ -32,20 +32,12 @@ module ActionDispatch::Routing
|
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
# Route
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
raise ArgumentError, "rest_route: options and a block require a single name"
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
names.each { |name| _rrf_rest_route(name, **kwargs, &block) }
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Route a single controller from its action store.
|
|
48
|
-
def _rrf_rest_route(name, **kwargs)
|
|
35
|
+
# Route a single controller from its action store. Whether it routes as a singular `resource` or
|
|
36
|
+
# a plural `resources` is decided by the controller's own config (`singular`, and whether it has
|
|
37
|
+
# a `model`) — not by the method name: a plural model controller gets collection/member scopes,
|
|
38
|
+
# while singular and non-model controllers route everything at the root. Pass a block to nest
|
|
39
|
+
# resources like Rails' `resources` (the nested controller resolves in the current scope).
|
|
40
|
+
def rest_resource(name, **kwargs)
|
|
49
41
|
controller = kwargs.delete(:controller) || name
|
|
50
42
|
if controller.is_a?(Class)
|
|
51
43
|
controller_class = controller
|
|
@@ -84,5 +76,17 @@ module ActionDispatch::Routing
|
|
|
84
76
|
yield if block_given?
|
|
85
77
|
end
|
|
86
78
|
end
|
|
79
|
+
|
|
80
|
+
# Route several controllers that share the same options in one call — handy for condensing a
|
|
81
|
+
# namespace. Per-name options (`path:`, `as:`, `controller:`) and a block only make sense for a
|
|
82
|
+
# single controller, so they're rejected when several names are given. The `s` is about routing
|
|
83
|
+
# multiple controllers, not plural routes — a single plural controller is still `rest_resource`.
|
|
84
|
+
def rest_resources(*names, **kwargs, &block)
|
|
85
|
+
if names.size > 1 && (block || (kwargs.keys & [ :path, :as, :controller ]).any?)
|
|
86
|
+
raise ArgumentError, "rest_resources: per-name options and a block require a single name"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
names.each { |name| rest_resource(name, **kwargs, &block) }
|
|
90
|
+
end
|
|
87
91
|
end
|
|
88
92
|
end
|
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.beta5
|
|
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-08-
|
|
11
|
+
date: 2026-08-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|