|
| 1 | +# Attributes |
| 2 | + |
| 3 | +Complete reference of all Trysil attributes. |
| 4 | + |
| 5 | +## Entity Mapping |
| 6 | + |
| 7 | +Unit: `Trysil.Attributes` |
| 8 | + |
| 9 | +| Attribute | Target | Description | |
| 10 | +|---|---|---| |
| 11 | +| `TTable(name)` | Class | Maps class to database table | |
| 12 | +| `TSequence(name)` | Class | Sequence for ID generation | |
| 13 | +| `TPrimaryKey` | Field | Marks primary key field | |
| 14 | +| `TColumn(name)` | Field | Maps field to database column | |
| 15 | +| `TDetailColumn(fk, name)` | Field | Maps a detail/lookup column | |
| 16 | +| `TVersionColumn` | Field | Enables optimistic locking | |
| 17 | +| `TRelation(table, fk, cascade)` | Class | Declares child relationship | |
| 18 | +| `TWhereClause(sql)` | Class | Adds fixed WHERE clause to all queries | |
| 19 | +| `TWhereClauseParameter(name, value)` | Class | Parameter for `TWhereClause` | |
| 20 | + |
| 21 | +### TTable |
| 22 | + |
| 23 | +```pascal |
| 24 | +[TTable('Persons')] |
| 25 | +TPerson = class |
| 26 | +``` |
| 27 | + |
| 28 | +Maps the entity class to a database table. Required on every entity. |
| 29 | + |
| 30 | +### TSequence |
| 31 | + |
| 32 | +```pascal |
| 33 | +[TSequence('PersonsID')] |
| 34 | +TPerson = class |
| 35 | +``` |
| 36 | + |
| 37 | +Names the database sequence used for ID generation. Behavior varies by database: |
| 38 | + |
| 39 | +- **SQL Server**: `NEXT VALUE FOR [dbo].[PersonsID]` |
| 40 | +- **PostgreSQL**: `nextval('PersonsID')` |
| 41 | +- **Firebird**: `NEXT VALUE FOR PersonsID` |
| 42 | +- **SQLite**: `AUTOINCREMENT` (sequence name used as reference) |
| 43 | + |
| 44 | +### TPrimaryKey |
| 45 | + |
| 46 | +```pascal |
| 47 | +[TPrimaryKey] |
| 48 | +[TColumn('ID')] |
| 49 | +FID: TTPrimaryKey; |
| 50 | +``` |
| 51 | + |
| 52 | +Marks the primary key field. Must be `TTPrimaryKey` (`Int32`). One per entity. |
| 53 | + |
| 54 | +### TColumn |
| 55 | + |
| 56 | +```pascal |
| 57 | +[TColumn('Firstname')] |
| 58 | +FFirstname: String; |
| 59 | +``` |
| 60 | + |
| 61 | +Maps a field to a database column by name. The field must be `strict private`. |
| 62 | + |
| 63 | +### TDetailColumn |
| 64 | + |
| 65 | +```pascal |
| 66 | +[TDetailColumn('CompanyID', 'CompanyName')] |
| 67 | +FCompanyName: String; |
| 68 | +``` |
| 69 | + |
| 70 | +Maps a read-only column from a related table. First parameter is the foreign key column, second is the detail column name. |
| 71 | + |
| 72 | +### TVersionColumn |
| 73 | + |
| 74 | +```pascal |
| 75 | +[TVersionColumn] |
| 76 | +[TColumn('VersionID')] |
| 77 | +FVersionID: TTVersion; |
| 78 | +``` |
| 79 | + |
| 80 | +Enables optimistic locking. The version is incremented on each update. If another transaction has modified the record (version mismatch), `ETConcurrentUpdateException` is raised. |
| 81 | + |
| 82 | +### TRelation |
| 83 | + |
| 84 | +```pascal |
| 85 | +[TRelation('Employees', 'CompanyID', False)] |
| 86 | +TCompany = class |
| 87 | +``` |
| 88 | + |
| 89 | +Parameters: |
| 90 | + |
| 91 | +1. **Child table name** — the table that references this entity |
| 92 | +2. **Foreign key column** — the column in the child table |
| 93 | +3. **Cascade delete** — `True`: auto-delete children; `False`: block delete if children exist (raises `ETDataIntegrityException`) |
| 94 | + |
| 95 | +Multiple `TRelation` attributes can be applied to the same class. |
| 96 | + |
| 97 | +### TWhereClause / TWhereClauseParameter |
| 98 | + |
| 99 | +```pascal |
| 100 | +[TTable('Users')] |
| 101 | +[TWhereClause('Active = :Active AND Role = :Role')] |
| 102 | +[TWhereClauseParameter('Active', True)] |
| 103 | +[TWhereClauseParameter('Role', 'admin')] |
| 104 | +TActiveAdmin = class |
| 105 | +``` |
| 106 | + |
| 107 | +Adds a fixed WHERE clause to every query on this entity. Parameters are **compile-time constants only**. For dynamic filtering, use [`TTFilterBuilder<T>`](../guide/filtering.md). |
| 108 | + |
| 109 | +`TWhereClauseParameter` constructors accept: `String`, `Integer`, `Int64`, `Double`, `Boolean`, `TDateTime`. |
| 110 | + |
| 111 | +--- |
| 112 | + |
| 113 | +## Validation |
| 114 | + |
| 115 | +Unit: `Trysil.Validation.Attributes` |
| 116 | + |
| 117 | +| Attribute | Description | Signature | |
| 118 | +|---|---|---| |
| 119 | +| `TRequired` | Not empty, null, or zero | `Create` or `Create(errorMsg)` | |
| 120 | +| `TMaxLength(n)` | Maximum string length | `Create(length)` or `Create(length, errorMsg)` | |
| 121 | +| `TMinLength(n)` | Minimum string length | `Create(length)` or `Create(length, errorMsg)` | |
| 122 | +| `TMaxValue(n)` | Maximum numeric value | `Create(Integer\|Double)` or with `errorMsg` | |
| 123 | +| `TMinValue(n)` | Minimum numeric value | `Create(Integer\|Double)` or with `errorMsg` | |
| 124 | +| `TGreater(n)` | Greater than n | `Create(Integer\|Double)` or with `errorMsg` | |
| 125 | +| `TLess(n)` | Less than n | `Create(Integer\|Double)` or with `errorMsg` | |
| 126 | +| `TRange(min, max)` | Value in range | `Create(min, max)` or with `errorMsg` | |
| 127 | +| `TRegex(pattern)` | Matches regex pattern | `Create(regex)` or `Create(regex, errorMsg)` | |
| 128 | +| `TEmail` | Valid email format | `Create` or `Create(errorMsg)` | |
| 129 | +| `TDisplayName(name)` | Human-readable field name for errors | `Create(displayName)` | |
| 130 | +| `TValidator` | Marks custom validator method | Marker attribute | |
| 131 | + |
| 132 | +### Examples |
| 133 | + |
| 134 | +```pascal |
| 135 | +[TRequired] |
| 136 | +[TMaxLength(50)] |
| 137 | +[TColumn('Firstname')] |
| 138 | +FFirstname: String; |
| 139 | +
|
| 140 | +[TMinValue(0)] |
| 141 | +[TMaxValue(100)] |
| 142 | +[TColumn('Score')] |
| 143 | +FScore: Integer; |
| 144 | +
|
| 145 | +[TEmail('Please enter a valid email')] |
| 146 | +[TColumn('Email')] |
| 147 | +FEmail: String; |
| 148 | +
|
| 149 | +[TRange(1, 999)] |
| 150 | +[TDisplayName('Order Number')] |
| 151 | +[TColumn('OrderNo')] |
| 152 | +FOrderNo: Integer; |
| 153 | +
|
| 154 | +[TRegex('^\+?[0-9\s\-]+$', 'Invalid phone number')] |
| 155 | +[TColumn('Phone')] |
| 156 | +FPhone: String; |
| 157 | +``` |
| 158 | + |
| 159 | +All validation attributes optionally accept a custom error message as the last parameter. If omitted, a default message is generated using the `TDisplayName` (if present) or the column name. |
| 160 | + |
| 161 | +--- |
| 162 | + |
| 163 | +## Events |
| 164 | + |
| 165 | +Unit: `Trysil.Events.Attributes` |
| 166 | + |
| 167 | +### Class-Level Event Attributes |
| 168 | + |
| 169 | +Register an event class for an entity: |
| 170 | + |
| 171 | +| Attribute | Description | |
| 172 | +|---|---| |
| 173 | +| `TInsertEvent(eventClass)` | Event class for insert operations | |
| 174 | +| `TUpdateEvent(eventClass)` | Event class for update operations | |
| 175 | +| `TDeleteEvent(eventClass)` | Event class for delete operations | |
| 176 | + |
| 177 | +```pascal |
| 178 | +[TInsertEvent(TPersonInsertEvent)] |
| 179 | +[TUpdateEvent(TPersonUpdateEvent)] |
| 180 | +[TDeleteEvent(TPersonDeleteEvent)] |
| 181 | +TPerson = class |
| 182 | +``` |
| 183 | + |
| 184 | +### Method-Level Event Attributes |
| 185 | + |
| 186 | +Declare event methods directly on the entity: |
| 187 | + |
| 188 | +| Attribute | Description | |
| 189 | +|---|---| |
| 190 | +| `TBeforeInsert` | Method called before insert | |
| 191 | +| `TAfterInsert` | Method called after insert | |
| 192 | +| `TBeforeUpdate` | Method called before update | |
| 193 | +| `TAfterUpdate` | Method called after update | |
| 194 | +| `TBeforeDelete` | Method called before delete | |
| 195 | +| `TAfterDelete` | Method called after delete | |
| 196 | + |
| 197 | +```pascal |
| 198 | +TPerson = class |
| 199 | +strict private |
| 200 | + [TBeforeInsert] |
| 201 | + procedure OnBeforeInsert; |
| 202 | + [TAfterUpdate] |
| 203 | + procedure OnAfterUpdate; |
| 204 | +end; |
| 205 | +``` |
| 206 | + |
| 207 | +See [Events](../guide/events.md) for detailed usage. |
| 208 | + |
| 209 | +--- |
| 210 | + |
| 211 | +## JSON |
| 212 | + |
| 213 | +Unit: `Trysil.JSon.Attributes` |
| 214 | + |
| 215 | +| Attribute | Description | |
| 216 | +|---|---| |
| 217 | +| `TJSonIgnore` | Exclude field from JSON serialization/deserialization | |
| 218 | + |
| 219 | +```pascal |
| 220 | +[TJSonIgnore] |
| 221 | +[TColumn('InternalHash')] |
| 222 | +FInternalHash: String; |
| 223 | +``` |
| 224 | + |
| 225 | +See [JSON Module](../json/index.md) for serialization documentation. |
| 226 | + |
| 227 | +--- |
| 228 | + |
| 229 | +## HTTP |
| 230 | + |
| 231 | +Unit: `Trysil.Http.Attributes` |
| 232 | + |
| 233 | +### Routing |
| 234 | + |
| 235 | +| Attribute | Description | |
| 236 | +|---|---| |
| 237 | +| `TUri(path)` | Controller base URI | |
| 238 | +| `TGet` / `TGet(path)` | GET endpoint | |
| 239 | +| `TPost` / `TPost(path)` | POST endpoint | |
| 240 | +| `TPut` / `TPut(path)` | PUT endpoint | |
| 241 | +| `TDelete` / `TDelete(path)` | DELETE endpoint | |
| 242 | + |
| 243 | +URL parameters use `?` as placeholder: |
| 244 | + |
| 245 | +```pascal |
| 246 | +[TUri('/api/persons')] |
| 247 | +TPersonController = class(TTHttpController<TAPIContext>) |
| 248 | +public |
| 249 | + [TGet] // GET /api/persons |
| 250 | + procedure GetAll; |
| 251 | +
|
| 252 | + [TGet('/?')] // GET /api/persons/123 |
| 253 | + procedure GetById(const AID: TTPrimaryKey); |
| 254 | +
|
| 255 | + [TPost] // POST /api/persons |
| 256 | + procedure Insert; |
| 257 | +
|
| 258 | + [TPut] // PUT /api/persons |
| 259 | + procedure Update; |
| 260 | +
|
| 261 | + [TDelete('/?/?')] // DELETE /api/persons/123/1 |
| 262 | + procedure Delete(const AID: TTPrimaryKey; const AVersionID: TTVersion); |
| 263 | +end; |
| 264 | +``` |
| 265 | + |
| 266 | +### Authentication & Authorization |
| 267 | + |
| 268 | +| Attribute | Description | |
| 269 | +|---|---| |
| 270 | +| `TAuthorizationType(type)` | Authentication requirement for controller | |
| 271 | +| `TArea(name)` | Required authorization area for method | |
| 272 | + |
| 273 | +```pascal |
| 274 | +// No authentication required |
| 275 | +[TAuthorizationType(TTHttpAuthorizationType.None)] |
| 276 | +TLogonController = class(TTHttpController<TAPIContext>) |
| 277 | +
|
| 278 | +// Require 'admin' area |
| 279 | +[TGet] |
| 280 | +[TArea('admin')] |
| 281 | +procedure GetSettings; |
| 282 | +``` |
| 283 | + |
| 284 | +`TTHttpAuthorizationType` values: |
| 285 | + |
| 286 | +- `None` — no authentication required |
| 287 | +- `Authentication` — authentication required (default) |
| 288 | + |
| 289 | +See [HTTP Module](../http/index.md) for full documentation. |
0 commit comments