dirty
In an ORM ecosystem, it is often necessary to determine if a record has any changes to be saved or not. Dream provides methods for introspecting the changing state of your model:
const place = await Place.findOrFail(id) // name: 'Cozy Cabin'
place.name = 'Bear Den Lodge'
place.isDirty // true
place.willSaveChangeToAttribute('name') // true
place.willSaveChangeToAttribute('style') // false
place.dirtyAttributes() // { name: 'Bear Den Lodge' } — pending values
place.changedAttributes() // { name: 'Cozy Cabin' } — original values
isDirty
The isDirty getter returns true or false, depending on whether a record has unsaved changes:
const user = await User.first()
console.log(user.isDirty) // false
user.email = 'new@email'
console.log(user.isDirty) // true
willSaveChangeToAttribute
willSaveChangeToAttribute(column) checks a single column for a pending change:
const user = await User.first()
user.email = 'new@email'
user.willSaveChangeToAttribute('email') // true
user.willSaveChangeToAttribute('name') // false
dirtyAttributes
dirtyAttributes() returns the columns with pending changes and the values about to be saved:
const user = await User.first() // email: 'how@yadoin'
user.email = 'new@email'
console.log(user.dirtyAttributes())
// {
// email: 'new@email',
// }
On a record that has not been saved yet, every column given a value other than undefined counts as dirty, so User.new({ name: 'Alice' }) reports name immediately.
changedAttributes
changedAttributes() returns the same columns as dirtyAttributes(), but with the values they held before the change, as of the last load or save:
const user = await User.first() // email: 'how@yadoin'
user.email = 'new@email'
console.log(user.changedAttributes())
// {
// email: 'how@yadoin',
// }
changes
For both sides at once, call changes, which gives the previous and new value for each changed field:
const user = await User.first()
user.email = 'new@email'
console.log(user.changes())
// {
// email: {
// was: 'how@yadoin',
// now: 'new@email',
// },
// }
changes() keeps reporting what the last save changed, beside any newer unsaved change, until the next save.
Dirty tracking in hooks and validations
In a validation or Before* hook, the pending-change methods describe the save in progress. this.willSaveChangeToAttribute('status') tells whether this save changes status; this.status is the value being saved, this.changedAttributes().status is the value before the change, and this.changes() gives both as { was, now }. To run a Before* hook only when a column changes, gate it with ifChanging, which on a create counts only the columns that were assigned.
Save's After* hooks run once the snapshot has been refreshed, so there willSaveChangeToAttribute is false and dirtyAttributes() is empty. Use savedChangeToAttribute(column) and changes(), which report the last save:
@deco.AfterSave()
public async logRename(this: Place) {
if (this.savedChangeToAttribute('name')) {
// ...
}
}
After a create, the last save covers every column the insert returned — the primary key, timestamps, database defaults, and null for each nullable column nobody assigned — so savedChangeToAttribute is true for all of them. To run an after hook only when a column changed, gate it with ifChanged, which is checked at the save itself and on a create counts only the columns that were assigned.
Dirty tracking is against the last load or save, not the current DB row
The comparison these methods report is against the instance's own snapshot from its last load or save — not against whatever the row currently holds in the database. A persisted instance with nothing dirty issues no UPDATE on save() or update(), and leaves updatedAt unstamped: update({}), or an update() assigning values equal to the current ones, is a no-op rather than a touch. Before-save hooks and validations still run first, so a hook that dirties the record turns it back into a real write.
Encrypted properties
Assigning a new plaintext to an @deco.Encrypted() property marks its encrypted<Name> column dirty; assigning the plaintext it already holds leaves the record clean, so that update issues no UPDATE either. dirtyAttributes() and changedAttributes() report the persisted encrypted<Name> key, not the plaintext virtual property — getAttribute('<plaintext>') returns undefined because it isn't the decrypting accessor, while getAttribute('encrypted<Name>') returns ciphertext. Read the decrypted value via the instance property (instance.<plaintext>) instead.