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
Copy file name to clipboardExpand all lines: docs/README_developer.md
+68Lines changed: 68 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -85,6 +85,74 @@ After each merge step, if the resulting entry lacks a `name` field, the slot/att
85
85
86
86
---
87
87
88
+
## CRUD Operations (`AppContext.js`)
89
+
90
+
DataHarmonizer's 1-to-many mode links multiple Handsontable tabs in a parent–child hierarchy driven by foreign-key annotations in the schema. `AppContext.js` implements all CRUD coordination across those tabs.
91
+
92
+
### Relation map (`crudGetRelations`)
93
+
94
+
At load time, `crudGetRelations(schema)` walks every class's `attributes` and looks for slots annotated with `foreign_key: OtherClass.slot_name`. For each such slot it builds a symmetric entry in `this.relations`:
`relations[cls].dependents` is then populated by `crudGetDependents`, which walks the child graph breadth-first to collect every transitive descendant of a class.
104
+
105
+
### Dependent-row state (`dependent_rows`)
106
+
107
+
`this.dependent_rows` is a `Map` keyed by class name. Each entry records the current cascade context for that class:
108
+
109
+
| Field | Meaning |
110
+
|-------|---------|
111
+
|`fkey_vals`| Foreign-key slot values used to filter this class's rows |
|`key_vals`| The class's own key slot values (used by children as their FK values) |
114
+
|`key_changed_vals`| Which key slots are changing and to what new value |
115
+
116
+
### Refreshing tab display (`refreshTabDisplay`)
117
+
118
+
Called whenever the user selects a row in any tab. It calls `crudGetDependentRows` to recompute `dependent_rows` for the selected class and all its descendants, then:
119
+
120
+
- Calls `setDHTabStatus` on each descendant — enabling or disabling the tab based on `fkey_status`.
121
+
- Calls `tabFilter` on each descendant — hiding rows whose FK values do not match the cascade context.
122
+
123
+
`tabFilter` uses Handsontable's HiddenRows plugin so that hidden rows remain in the source data and are not deleted.
When a user edits a key slot in a tab's row, `DataHarmonizer.js`'s `beforeChange` hook fires. It calls `getChangeReport` which in turn calls `crudGetDependentRows` with the pending change. If any descendant rows would be affected, a confirmation dialog lists them. On confirmation, each affected row in each descendant tab has its FK slot updated via `setSourceDataAtCell` (physical row index, reaches hidden rows).
128
+
129
+
#### How cascade FK values are built for dependents
130
+
131
+
`crudGetDependentRows` iterates the `dependents` map of the changed class in dependency order. For each dependent class it derives `fkey_vals`**purely from the cascade chain already recorded in `dependent_rows`** — not from the user's current tab selection. This ensures the cascade scope is determined only by the record being changed:
132
+
133
+
- If `start_name = 'Schema'` and Schema.name is being renamed, every descendant (Class, Slot, UniqueKey, Enum, etc.) is searched using only `{schema_id: oldName}`. A currently selected Class row does **not** narrow the search to that class's rows.
134
+
- If `start_name = 'Class'` and Class.name is being renamed, descendants are searched using `{class_id: oldName, schema_id: <value from Schema's cascade entry>}`.
135
+
136
+
Null FK values (parents not in the cascade chain) are excluded from the row search, so only the keys that actually flow from the changed record are applied as filters.
137
+
138
+
`key_vals` stored for intermediate classes in `dependent_rows` contains only FK-chain values (no user-selection values), preventing user selections from leaking into downstream FK lookups.
The same `getChangeReport` machinery is used. On confirmation, dependent rows are removed via `crudDeleteRowsByPhysical`, which filters the source data array and calls `hot.loadData()` — removing all matching rows (visible or hidden) without needing visual-row index conversion.
Searches are done on physical (source) row indices using `getSourceDataAtCell`, so hidden rows are always included. Null values in the search filter are treated as "match any" by being excluded from the filter before the search.
147
+
148
+
### Current scope and future directions
149
+
150
+
The cascade system currently operates in **single-hierarchy** mode: a rename or delete on a given class propagates to all descendants of that class using only the changed record's key values as the filter. The user's selections in other tabs do not influence the cascade scope.
151
+
152
+
A future option could support **multi-focus cascading**, where the user first narrows one or more intermediate tabs (e.g., selects a specific Class row) and the cascade is then scoped to that focused subset — for example, renaming only the UniqueKey rows that belong to the selected class, rather than all UniqueKey rows for the schema. This would require extending `crudGetDependentRows` to optionally incorporate user-selection values from non-start-class tabs into the `fkey_vals` chain.
`script/tabular_to_schema.py` was the previous way of assembling a complete DataHarmonizer `schema.yaml` from spreadsheet-based source files. It combines three inputs:
0 commit comments