|
| 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