Skip to content

Commit dbe19f5

Browse files
akoclaude
andcommitted
docs: add event handler examples, skill, quick reference, and tests
Following the PR review checklist for entity event handlers: - mdl-examples/doctype-tests/01-domain-model-examples.mdl: working CREATE MICROFLOW + ALTER ENTITY ADD/DROP EVENT HANDLER examples - .claude/skills/mendix/generate-domain-model.md: new section with syntax, moments, events, and usage notes - docs/01-project/MDL_QUICK_REFERENCE.md: ALTER ENTITY table rows - docs-site/src/language/entities.md: dedicated Event Handlers section in the language reference, plus syntax block update - roundtrip_entity_test.go: integration tests for CREATE ENTITY with event handler and ALTER ENTITY ADD/DROP EVENT HANDLER Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e61bd06 commit dbe19f5

6 files changed

Lines changed: 180 additions & 0 deletions

File tree

.claude/skills/mendix/generate-domain-model.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,33 @@ WHERE e.Status != 'CANCELLED' -- Correct: uses enum value
253253
WHERE e.Status != 'Cancelled' -- Wrong: this is the caption
254254
```
255255

256+
### Entity Event Handlers
257+
258+
Microflows can run before/after entity Create, Commit, Delete, or Rollback. Use the optional `RAISE ERROR` clause to make a handler act as a validation microflow — if it returns false, the operation is aborted.
259+
260+
```sql
261+
-- In CREATE ENTITY (handlers go after attributes/indexes)
262+
CREATE PERSISTENT ENTITY Sales.Order (
263+
Total: Decimal,
264+
Status: String(50)
265+
)
266+
ON BEFORE COMMIT CALL Sales.ACT_ValidateOrder RAISE ERROR
267+
ON AFTER CREATE CALL Sales.ACT_InitDefaults;
268+
269+
-- Add via ALTER ENTITY
270+
ALTER ENTITY Sales.Order
271+
ADD EVENT HANDLER ON BEFORE DELETE CALL Sales.ACT_CheckCanDelete RAISE ERROR;
272+
273+
-- Drop via ALTER ENTITY
274+
ALTER ENTITY Sales.Order
275+
DROP EVENT HANDLER ON BEFORE COMMIT;
276+
```
277+
278+
**Moments**: `BEFORE`, `AFTER`
279+
**Events**: `CREATE`, `COMMIT`, `DELETE`, `ROLLBACK`
280+
281+
Each (Moment, Event) combination can only have one handler per entity. The microflow must exist (the executor validates the reference). `RAISE ERROR` is optional — without it, the handler runs but its return value doesn't affect the operation.
282+
256283
### Associations
257284

258285
**CRITICAL: Association Directionality**

cmd/mxcli/lsp_completions_gen.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs-site/src/language/entities.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ CREATE [OR MODIFY] <entity-type> ENTITY <Module>.<Name> (
2020
<attribute-definitions>
2121
)
2222
[INDEX (<column-list>)]
23+
[ON BEFORE|AFTER CREATE|COMMIT|DELETE|ROLLBACK CALL <Module>.<Microflow> [RAISE ERROR]]
2324
[;|/]
2425
```
2526

@@ -142,6 +143,36 @@ INDEX (Email);
142143

143144
Attribute-level documentation appears in Studio Pro when hovering over the attribute in the domain model.
144145

146+
## Entity Event Handlers
147+
148+
Persistent entities can have microflow event handlers that run before or after Create, Commit, Delete, or Rollback operations. The optional `RAISE ERROR` clause makes the handler act as a validation microflow — if it returns false, the operation is aborted.
149+
150+
```sql
151+
CREATE PERSISTENT ENTITY Sales.Order (
152+
Total: Decimal,
153+
Status: String(50)
154+
)
155+
ON BEFORE COMMIT CALL Sales.ACT_ValidateOrder RAISE ERROR
156+
ON AFTER CREATE CALL Sales.ACT_InitDefaults;
157+
```
158+
159+
Event handlers can also be added or removed via `ALTER ENTITY`:
160+
161+
```sql
162+
-- Add a handler to an existing entity
163+
ALTER ENTITY Sales.Order
164+
ADD EVENT HANDLER ON BEFORE DELETE CALL Sales.ACT_CheckCanDelete RAISE ERROR;
165+
166+
-- Remove a handler
167+
ALTER ENTITY Sales.Order
168+
DROP EVENT HANDLER ON BEFORE COMMIT;
169+
```
170+
171+
**Moments**: `BEFORE`, `AFTER`
172+
**Events**: `CREATE`, `COMMIT`, `DELETE`, `ROLLBACK`
173+
174+
Each (Moment, Event) combination supports one handler per entity. The microflow must exist in the project (validated at execution time).
175+
145176
## DROP ENTITY
146177

147178
Removes an entity from the domain model:

docs/01-project/MDL_QUICK_REFERENCE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ Modifies an existing entity without full replacement.
6363
| Rename attribute | `ALTER ENTITY Module.Name RENAME OldName TO NewName;` | |
6464
| Add index | `ALTER ENTITY Module.Name ADD INDEX (Col1 [ASC\|DESC], ...);` | |
6565
| Drop index | `ALTER ENTITY Module.Name DROP INDEX (Col1, ...);` | |
66+
| Add event handler | `ALTER ENTITY Module.Name ADD EVENT HANDLER ON BEFORE COMMIT CALL Mod.MF [RAISE ERROR];` | Moments: BEFORE/AFTER, Events: CREATE/COMMIT/DELETE/ROLLBACK |
67+
| Drop event handler | `ALTER ENTITY Module.Name DROP EVENT HANDLER ON BEFORE COMMIT;` | |
6668
| Set documentation | `ALTER ENTITY Module.Name SET DOCUMENTATION 'text';` | |
6769
| Set position | `ALTER ENTITY Module.Name SET POSITION (100, 200);` | Canvas position |
6870

mdl-examples/doctype-tests/01-domain-model-examples.mdl

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1453,6 +1453,36 @@ ALTER ENTITY DmTest.VATRate
14531453
ALTER ENTITY DmTest.VATRate
14541454
SET POSITION (300, 400);
14551455

1456+
-- ============================================================================
1457+
-- ENTITY EVENT HANDLERS
1458+
-- ============================================================================
1459+
-- Entity event handlers run microflows before/after Create, Commit, Delete,
1460+
-- or Rollback. RAISE ERROR makes the handler act as a validation microflow:
1461+
-- if it returns false, the operation is aborted.
1462+
1463+
CREATE MICROFLOW DmTest.ACT_ValidateBankAccount ()
1464+
BEGIN
1465+
LOG INFO 'Validating bank account';
1466+
END;
1467+
/
1468+
1469+
CREATE MICROFLOW DmTest.ACT_InitBankAccountDefaults ()
1470+
BEGIN
1471+
LOG INFO 'Initializing defaults';
1472+
END;
1473+
/
1474+
1475+
-- Add event handlers via ALTER ENTITY
1476+
ALTER ENTITY DmTest.BankAccount
1477+
ADD EVENT HANDLER ON BEFORE COMMIT CALL DmTest.ACT_ValidateBankAccount RAISE ERROR;
1478+
1479+
ALTER ENTITY DmTest.BankAccount
1480+
ADD EVENT HANDLER ON AFTER CREATE CALL DmTest.ACT_InitBankAccountDefaults;
1481+
1482+
-- Drop an event handler
1483+
ALTER ENTITY DmTest.BankAccount
1484+
DROP EVENT HANDLER ON AFTER CREATE;
1485+
14561486
-- @version: 10.18+
14571487
-- ============================================================================
14581488
-- PART 11: COMPLEX VIEW ENTITIES WITH SUBQUERIES — requires Mendix 10.18+

mdl/executor/roundtrip_entity_test.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"bytes"
99
"os"
1010
"path/filepath"
11+
"strings"
1112
"testing"
1213

1314
"github.com/mendixlabs/mxcli/mdl/ast"
@@ -88,6 +89,94 @@ func TestRoundtripEntity_WithIndex(t *testing.T) {
8889
})
8990
}
9091

92+
func TestRoundtripEntity_WithEventHandler(t *testing.T) {
93+
env := setupTestEnv(t)
94+
defer env.teardown()
95+
96+
entityName := testModule + ".TestEntityEventHandler"
97+
mfName := testModule + ".ACT_ValidateTestEntity"
98+
99+
// Create a microflow first (event handler references it)
100+
if err := env.executeMDL(`CREATE OR MODIFY MICROFLOW ` + mfName + ` ()
101+
BEGIN
102+
LOG INFO 'validating';
103+
END;`); err != nil {
104+
t.Fatalf("failed to create microflow: %v", err)
105+
}
106+
107+
// Create entity with event handler
108+
createMDL := `CREATE OR MODIFY PERSISTENT ENTITY ` + entityName + ` (
109+
Name: String(100)
110+
)
111+
ON BEFORE COMMIT CALL ` + mfName + ` RAISE ERROR;`
112+
113+
// Verify roundtrip preserves the event handler
114+
env.assertContains(createMDL, []string{
115+
"PERSISTENT ENTITY",
116+
"Name:",
117+
"String(100)",
118+
"ON BEFORE COMMIT CALL",
119+
mfName,
120+
"RAISE ERROR",
121+
})
122+
}
123+
124+
func TestRoundtripEntity_AlterAddDropEventHandler(t *testing.T) {
125+
env := setupTestEnv(t)
126+
defer env.teardown()
127+
128+
entityName := testModule + ".TestAlterEventHandler"
129+
mfName := testModule + ".ACT_AlterEventTest"
130+
131+
// Create microflow
132+
if err := env.executeMDL(`CREATE OR MODIFY MICROFLOW ` + mfName + ` ()
133+
BEGIN
134+
LOG INFO 'test';
135+
END;`); err != nil {
136+
t.Fatalf("failed to create microflow: %v", err)
137+
}
138+
139+
// Create entity without handlers
140+
if err := env.executeMDL(`CREATE OR MODIFY PERSISTENT ENTITY ` + entityName + ` (
141+
Code: String(50)
142+
);`); err != nil {
143+
t.Fatalf("failed to create entity: %v", err)
144+
}
145+
146+
// Add event handler via ALTER
147+
if err := env.executeMDL(`ALTER ENTITY ` + entityName + `
148+
ADD EVENT HANDLER ON AFTER CREATE CALL ` + mfName + `;`); err != nil {
149+
t.Fatalf("failed to add event handler: %v", err)
150+
}
151+
152+
// Verify handler appears in DESCRIBE
153+
out, err := env.describeMDL(`DESCRIBE ENTITY ` + entityName + `;`)
154+
if err != nil {
155+
t.Fatalf("describe failed: %v", err)
156+
}
157+
if !strings.Contains(out, "ON AFTER CREATE CALL") {
158+
t.Errorf("expected ON AFTER CREATE CALL in DESCRIBE output, got:\n%s", out)
159+
}
160+
if !strings.Contains(out, mfName) {
161+
t.Errorf("expected microflow name %q in DESCRIBE output, got:\n%s", mfName, out)
162+
}
163+
164+
// Drop the event handler
165+
if err := env.executeMDL(`ALTER ENTITY ` + entityName + `
166+
DROP EVENT HANDLER ON AFTER CREATE;`); err != nil {
167+
t.Fatalf("failed to drop event handler: %v", err)
168+
}
169+
170+
// Verify handler is gone
171+
out, err = env.describeMDL(`DESCRIBE ENTITY ` + entityName + `;`)
172+
if err != nil {
173+
t.Fatalf("describe after drop failed: %v", err)
174+
}
175+
if strings.Contains(out, "ON AFTER CREATE CALL") {
176+
t.Errorf("event handler should be removed but still in DESCRIBE output:\n%s", out)
177+
}
178+
}
179+
91180
func TestRoundtripEnumeration(t *testing.T) {
92181
env := setupTestEnv(t)
93182
defer env.teardown()

0 commit comments

Comments
 (0)