CRUD Operations
This document explains how to perform Create, Read, Update, and Delete (CRUD) operations using models in ReActive Record.
Creating Records
Creating a Single Record
You can create records in two ways:
- Using the static
create()
method to create and save in one step:
const user = await User.create({
email: 'john@example.com',
name: 'John Doe'
})
- Creating an instance, assigning values, and then calling
save()
:
const user = new User()
user.email = 'john@example.com'
user.name = 'John Doe'
await user.save()
You can also use the fill()
and merge()
methods to update model properties before saving:
const user = new User()
user.fill({ email: 'john@example.com' }) // fills only undefined properties
user.merge({ name: 'John Doe' }) // merges and overwrites properties
await user.save()
Both creating a new
instance of the constructor and using ReactiveModel.create
achieve the same result. The second approach can be useful when you need to perform additional operations or validations before saving.
Creating Multiple Records
Use createMany()
to create and save multiple records at once:
const users = await User.createMany([
{ email: 'john@example.com', name: 'John Doe' },
{ email: 'jane@example.com', name: 'Jane Doe' }
])
Reading Records
Find by Primary Key
Use find()
to find a record by its primary key. Returns undefined
if not found.
const user = await User.find(1)
Find or Fail
Use findOrFail()
to find a record by primary key or throw an error if not found.
try {
const user = await User.findOrFail(1)
} catch (error) {
// Handle not found error
}
Find by Property
Use findBy()
to find a record by a specific property. Returns undefined
if not found.
const user = await User.findBy('email', 'john@example.com')
Find Many by Property
Use findManyBy()
to find multiple records by a property value.
const admins = await User.findManyBy('role', ['admin', 'moderator'])
First Record
Use first()
to get the first record or undefined
if none exist.
const firstUser = await User.first()
All Records
Use all()
to get all records.
const allUsers = await User.all()
Updating Records
Updating an Instance
Modify properties on a model instance and call save()
to persist changes:
const user = await User.find(1)
if (user) {
user.name = 'New Name'
await user.save()
}
Checking for Pending Changes
Use the dirty
property to check if there are unsaved changes:
if (user.dirty) {
console.log('Pending changes:', user.pending)
}
Updating Multiple Records
You can update multiple records using the query builder's new update()
method, which fetches matching records and updates them:
await User.query()
.where(user => user.role === 'guest')
.update({ role: 'member' })
Deleting Records
Deleting an Instance
Call delete()
on a model instance to delete it:
const user = await User.find(1)
if (user) {
await user.delete()
}
Finding Multiple Records by ID
Use findMany()
to find multiple records by their primary keys:
const users = await User.findMany([1, 2, 3])
Mass Deletion
You can delete multiple records using the query builder's delete()
method:
await User.query()
.where(user => user.status === 'inactive')
.delete()
Finding or Creating Records
First or New
Use firstOrNew()
to get the first matching record or create a new instance (without saving):
const user = await User.firstOrNew(
{ email: 'john@example.com' }, // search criteria
{ name: 'John Doe' } // attributes for new instance
)
First or Create
Use firstOrCreate()
to get the first matching record or create and save a new one:
const user = await User.firstOrCreate(
{ email: 'john@example.com' }, // search criteria
{ name: 'John Doe' } // attributes for new instance
)
Update or Create
Use updateOrCreate()
to update a matching record or create a new one:
const user = await User.updateOrCreate(
{ email: 'john@example.com' }, // search criteria
{ name: 'Updated Name' } // attributes to update/create
)
Model State and Deletion
When a model is deleted using the delete()
method:
- The record is removed from the database
- The model instance becomes read-only
- All properties remain readable but cannot be modified
- Any attempt to modify properties or call methods like
save()
,merge()
,fill()
, orreset()
will throw aReactiveModelDeletedException
- Attempting to subscribe to events will throw a
ReactiveModelUnsubscribableException
Example handling deleted state:
const user = await User.find(1)
if (user) {
await user.delete()
// Properties are still readable
console.log(user.id, user.name) // OK
try {
user.name = 'New Name' // Throws ReactiveModelDeletedException
} catch (error) {
console.log('Cannot modify deleted model')
}
try {
user.onChange(() => {}) // Throws ReactiveModelUnsubscribableException
} catch (error) {
console.log('Cannot subscribe to deleted model events')
}
}
Reactivity and CRUD
Models are reactive by default. You can listen to changes on records using:
onChange()
- Listen to any model changesonPropertyChange()
- Listen to specific property changesonDelta()
- Listen to detailed change information
Example:
const user = await User.find(1)
if (user) {
// Listen to all changes
user.onChange((newState) => {
console.log('User updated:', newState)
})
// Listen to specific property
user.onPropertyChange('email', (newValue, oldValue) => {
console.log('Email changed:', { newValue, oldValue })
})
// Listen to detailed changes
user.onDelta((delta) => {
console.log('Changes:', delta)
})
}
Additional Methods
Instance Methods
fill()
- Fill undefined propertiesmerge()
- Merge propertiesreset()
- Reset pending changes to clear unsaved modificationsclone()
- Create a new instance with the same properties as the current one, excluding the primary key
Static Methods
findByOrFail()
- Find by property or failfirstOrFail()
- Get first record or failtruncate()
- Delete all records
Best Practices
Use appropriate methods for your use case:
createMany()
when creating multiple records at oncefind()
when undefined is an acceptable resultfindOrFail()
when the record must existfirstOrCreate()
to ensure a record existsupdateOrCreate()
to upsert records
Handle errors appropriately:
typescripttry { const user = await User.findOrFail(1) } catch (error) { if (error instanceof MissingReactiveModelRecordError) { // Handle not found case } }
Check model state:
typescript// Check for unsaved changes if (model.dirty) { console.log('Pending changes:', model.pending) } // Check if model is deleted try { await model.save() } catch (error) { if (error instanceof ReactiveModelDeletedException) { console.log('Cannot modify deleted model') } }
Leverage reactivity effectively:
typescript// Use onChange for general updates model.onChange((newState, oldState) => { updateUI(newState) }) // Use onPropertyChange for specific fields model.onPropertyChange('status', (newValue, oldValue) => { updateStatusIndicator(newValue) }) // Use onDelta for detailed change tracking model.onDelta((changes) => { logChanges(changes) })
Clean up event listeners when no longer needed:
typescriptconst onChange = (state) => { /* ... */ } model.onChange(onChange) // Later when done: model.offChange(onChange)
For more information on advanced querying and relationships, see: