Skip to content

Commit 6128275

Browse files
Sync docs from wheels@489cfbe
1 parent a9de84b commit 6128275

File tree

2 files changed

+135
-0
lines changed

2 files changed

+135
-0
lines changed

docs/3.1.0/guides/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@
133133
* [Responding with Multiple Formats](handling-requests-with-controllers/responding-with-multiple-formats.md)
134134
* [Using the Flash](handling-requests-with-controllers/using-the-flash.md)
135135
* [Middleware](handling-requests-with-controllers/middleware.md)
136+
* [Route Model Binding](handling-requests-with-controllers/route-model-binding.md)
136137
* [Using Filters](handling-requests-with-controllers/using-filters.md)
137138
* [Verification](handling-requests-with-controllers/verification.md)
138139
* [Event Handlers](handling-requests-with-controllers/event-handlers.md)
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# Route Model Binding
2+
3+
Route model binding automatically resolves model instances from route `key` parameters during dispatch, before your controller action runs. Instead of manually looking up records in every action, Wheels does it for you.
4+
5+
## The Problem
6+
7+
Without route model binding, every controller action that works with a specific record starts with the same boilerplate:
8+
9+
```cfm
10+
// app/controllers/Users.cfc
11+
function show() {
12+
user = model("User").findByKey(params.key);
13+
if (IsBoolean(user) && !user) {
14+
redirectTo(route="users");
15+
}
16+
}
17+
18+
function edit() {
19+
user = model("User").findByKey(params.key);
20+
if (IsBoolean(user) && !user) {
21+
redirectTo(route="users");
22+
}
23+
}
24+
```
25+
26+
## The Solution
27+
28+
With route model binding enabled, the resolved model instance is automatically available in `params`:
29+
30+
```cfm
31+
// config/routes.cfm
32+
mapper()
33+
.resources(name="users", binding=true)
34+
.end();
35+
36+
// app/controllers/Users.cfc
37+
function show() {
38+
// params.user is already a User model instance!
39+
// If the record doesn't exist, a 404 is automatically returned.
40+
user = params.user;
41+
}
42+
```
43+
44+
## Enabling Route Model Binding
45+
46+
### Per-Route (Recommended)
47+
48+
Add `binding=true` to individual resource declarations:
49+
50+
```cfm
51+
// config/routes.cfm
52+
mapper()
53+
.resources(name="users", binding=true)
54+
.resources(name="posts", binding=true)
55+
.resources("comments") // no binding on this resource
56+
.end();
57+
```
58+
59+
### Globally
60+
61+
Enable for all resource routes at once:
62+
63+
```cfm
64+
// config/settings.cfm
65+
set(routeModelBinding=true);
66+
```
67+
68+
Per-route `binding=false` can override the global setting:
69+
70+
```cfm
71+
set(routeModelBinding=true);
72+
73+
// In routes.cfm — comments won't have binding even though global is on
74+
mapper()
75+
.resources("users") // binding enabled (global)
76+
.resources(name="comments", binding=false) // binding disabled (per-route)
77+
.end();
78+
```
79+
80+
## How It Works
81+
82+
1. **Convention:** The controller name is singularized and capitalized to derive the model name. `posts` controller → `Post` model.
83+
2. **Lookup:** `model("Post").findByKey(params.key)` is called automatically.
84+
3. **Storage:** The resolved instance is stored in `params` under the singular name: `params.post`.
85+
4. **404 Handling:** If no record is found, Wheels throws `Wheels.RecordNotFound` which renders a 404 page.
86+
5. **No key, no binding:** Actions without a `key` parameter (like `index` and `create`) are unaffected.
87+
88+
## Explicit Model Name
89+
90+
If your controller name doesn't match your model name, specify the model explicitly:
91+
92+
```cfm
93+
// "writers" controller, but the model is "Author"
94+
mapper()
95+
.resources(name="writers", binding="Author")
96+
.end();
97+
98+
// In the controller, the resolved model uses the model name:
99+
// params.author (not params.writer)
100+
```
101+
102+
## Scoped Binding
103+
104+
Binding inherits through route scopes, so you can enable it for an entire API namespace:
105+
106+
```cfm
107+
mapper()
108+
.scope(path="/api", binding=true)
109+
.resources("users") // binding enabled
110+
.resources("posts") // binding enabled
111+
.end()
112+
.end();
113+
```
114+
115+
## Error Handling
116+
117+
When a record is not found, Wheels throws a `Wheels.RecordNotFound` error. In development mode, you'll see a detailed error page. In production, this renders your 404 page.
118+
119+
If binding is enabled on a route whose controller doesn't correspond to a model (e.g., a `settings` controller with no `Setting` model), binding is silently skipped — no error is thrown.
120+
121+
## What's in params?
122+
123+
| Scenario | `params.key` | `params.<model>` |
124+
|----------|-------------|------------------|
125+
| Binding enabled, record found | `"42"` (preserved) | Model instance |
126+
| Binding enabled, no key param | N/A | Not set |
127+
| Binding disabled | `"42"` (preserved) | Not set |
128+
| No matching model class | `"42"` (preserved) | Not set |
129+
130+
## Limitations
131+
132+
- **Single resource only:** Nested parent resources are not automatically resolved. `/users/5/posts/3` resolves `Post` from `params.key` but does not resolve `User` from `params.userKey`.
133+
- **Primary key only:** Binding uses `findByKey()`. Slug-based or custom lookups should use a before filter.
134+
- **Soft deletes:** Uses default `findByKey()` behavior, which excludes soft-deleted records.

0 commit comments

Comments
 (0)