You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Browse filesBrowse the repository at this point in the historyBrowse files
authored
feat(model): add includeCalculated to additively opt in select=false SQL properties (#3254)
* feat(model): add includeCalculated to additively opt in select=false SQL properties
Adds an `includeCalculated` argument to findAll(), findOne(), and
findByKey() that opts already-declared calculated SQL properties
(property(name=..., sql=..., select=false)) back into a single finder.
Unlike `select`, it is additive — the named calculated properties are
merged on top of the default column list inside $createSQLFieldList
rather than replacing it, so the rest of the record is still returned.
This closes the inverse of the existing select=false declaration: a
property kept off the hot path can now be pulled back in per-call
without hand-listing every other column.
Unknown names throw Wheels.CalculatedPropertyNotFound in
development/testing and are ignored in production, mirroring existing
dev-only validation patterns. Pure list manipulation — no closures,
struct member functions, or other cross-engine traps.
Fixes#3252
Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
* docs: document includeCalculated finder argument for select=false SQL properties
Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
* docs: fix premature code fence in calculated-properties snippet
The includeCalculated usage example introduced a closing code fence
mid-section, ejecting the method-based calculated-property example out
of the cfm block and orphaning the trailing fence (12 -> 13 fences).
Remove the stray fence so the whole section renders as one code block;
fence parity restored to 12, matching develop.
Signed-off-by: Peter Amiri <petera@pai.com>
---------
Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Signed-off-by: Peter Amiri <petera@pai.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Peter Amiri <petera@pai.com>
Create: `model("User").new(params.user).save()`, or `model("User").create(params.user)`.
301
304
Include associations: `findAll(include="role,orders")`. Pagination: `findAll(page=params.page, perPage=25)`.
305
+
Opt a `select=false` calculated property into one call (additive): `findAll(includeCalculated="fullName")`. Unknown names throw `Wheels.CalculatedPropertyNotFound` in dev/testing.
- Added an `includeCalculated` argument to `findAll()`, `findOne()`, and `findByKey()` for additively opting a `select=false` calculated SQL property back into a single finder — e.g. `model("User").findAll(includeCalculated="fullName")`. Unlike `select`, it merges the named calculated properties on top of the default column list rather than replacing it, so the rest of the record is still returned. Unknown names throw `Wheels.CalculatedPropertyNotFound` in development/testing and are ignored in production (#3252)
Copy file name to clipboardExpand all lines: vendor/wheels/model/read.cfc
+10-2Lines changed: 10 additions & 2 deletions
Original file line number
Diff line number
Diff line change
@@ -10,6 +10,7 @@ component {
10
10
* @order Maps to the `ORDER` BY clause of the query. You do not need to specify the table name(s); Wheels will do that for you.
11
11
* @group Maps to the `GROUP BY` clause of the query. You do not need to specify the table name(s); Wheels will do that for you.
12
12
* @select Determines how the `SELECT` clause for the query used to return data will look. You can pass in a list of the properties (which map to columns) that you want returned from your table(s). If you don't set this argument at all, Wheels will select all properties from your table(s). If you specify a table name (e.g. `users.email`) or alias a column (e.g. `fn AS firstName`) in the list, then the entire list will be passed through unchanged and used in the `SELECT` clause of the query. By default, all column names in tables joined via the `include` argument will be prepended with the singular version of the included table name.
13
+
* @includeCalculated List of calculated property names (declared via `property(name="...", sql="...", select=false)`) to additively opt into this finder's `SELECT` clause. Unlike `select`, this does not replace the default column list — the named calculated properties are merged on top of all default columns, so the rest of the record is still returned. Useful for pulling a `select=false` computed property back in on a single finder without spelling out every other column. Unknown names throw `Wheels.CalculatedPropertyNotFound` in `development`/`testing` and are ignored in `production`.
13
14
* @distinct Whether to add the `DISTINCT` keyword to your `SELECT` clause. Wheels will, when necessary, add this automatically (when using pagination and a `hasMany` association is used in the `include` argument, to name one example).
14
15
* @include Associations that should be included in the query using `INNER` or `LEFT OUTER` joins (which join type that is used depends on how the association has been set up in your model). If all included associations are set on the current model, you can specify them in a list (e.g. `department,addresses,emails`). You can build more complex include strings by using parentheses when the association is set on an included model, like `album(artist(genre))`, for example. These complex `include` strings only work when `returnAs` is set to `query` though.
15
16
* @maxRows Maximum number of records to retrieve. Passed on to the `maxRows` `cfquery` attribute. The default, `-1`, means that all records will be retrieved.
@@ -32,6 +33,7 @@ component {
32
33
stringorder,
33
34
stringgroup,
34
35
stringselect="",
36
+
stringincludeCalculated="",
35
37
booleandistinct="false",
36
38
stringinclude="",
37
39
numericmaxRows="-1",
@@ -214,7 +216,8 @@ component {
214
216
include =arguments.include,
215
217
includeSoftDeletes =arguments.includeSoftDeletes,
216
218
list =arguments.select,
217
-
returnAs =arguments.returnAs
219
+
returnAs =arguments.returnAs,
220
+
includeCalculated =arguments.includeCalculated
218
221
);
219
222
// Strip dialect quotes: $createSQLFieldList now quotes identifiers; the bare-identifier regex below requires unquoted input.
* @key Primary key value(s) of the record. Separate with comma if passing in multiple primary key values. Accepts a string, list, or a numeric value.
409
413
* @select [see:findAll].
414
+
* @includeCalculated [see:findAll].
410
415
* @include [see:findAll].
411
416
* @handle Handle to use for the query. This is used to set the name of the query in the debug output (which otherwise defaults to `userFindOneQuery` for example).
for (local.c =1; local.c<=local.calcEnd; local.c++) {
540
+
local.calcName=Trim(local.calcArray[local.c]);
541
+
if (!Len(local.calcName)) {
542
+
continue;
543
+
}
544
+
if (!StructKeyExists(variables.wheels.class.calculatedProperties, local.calcName)) {
545
+
// Dev/testing fail loud on a typo; no-op in production (mirrors existing
546
+
// dev-only validation such as Wheels.PaginationNav.InvalidArgument).
547
+
if (ListFindNoCase("development,testing", get("environment"))) {
548
+
Throw(
549
+
type ="Wheels.CalculatedPropertyNotFound",
550
+
message ="The calculated property `#local.calcName#` was not found on the `#variables.wheels.class.modelName#` model.",
551
+
extendedInfo ="The `includeCalculated` argument only accepts the names of calculated properties declared via `property(name=""..."", sql=""..."")` in the model's `config()`. Declared calculated properties: #StructKeyList(variables.wheels.class.calculatedProperties)#."
552
+
);
553
+
}
554
+
continue;
555
+
}
556
+
// dedup: $createSQLFieldList already de-duplicates, but skip obvious repeats
557
+
if (!ListFindNoCase(arguments.list, local.calcName)) {
// go through the properties and map them to the database unless the developer passed in a table name or an alias in which case we assume they know what they're doing and leave the select clause as is
Copy file name to clipboardExpand all lines: web/sites/guides/src/content/docs/v4-0-0/basics/models-and-the-orm.mdx
+20Lines changed: 20 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -65,6 +65,26 @@ component extends="Model" {
65
65
66
66
Composite keys are also supported — pass a comma-separated list to `setPrimaryKey("tenantId,entryId")`. Most apps never need them.
67
67
68
+
## Calculated SQL properties
69
+
70
+
A `property()` declaration can carry a `sql` expression that Wheels evaluates as a computed `SELECT` column. Pass `select=false` to keep the property off the default `SELECT` (reducing hot-path cost), then opt it back into a specific finder with `includeCalculated`:
Unknown names passed to `includeCalculated` throw `Wheels.CalculatedPropertyNotFound` in `development` and `testing`, and are silently ignored in `production`.
87
+
68
88
## Finders
69
89
70
90
The finders read from the database. Every finder below is a method on the class, called via `model("Name")`. The calls return different shapes depending on whether you're loading many, one, or just asking a question.
0 commit comments