usage
Dream provides many useful tools for accessing and manipulating associations. Leveraging these powerful tools will enable you to easily bring forward complex, nested relationships throughout your app without ever manually writing a single line of sql yourself.
loading
With associations already created in the database, there are several ways to access them. The most common way to do this would be to leverage the preload method, which will enable you to load an association onto your record as you are loading the record itself, like so:
const user = await User.preload('posts').execute()
user.posts
// [Post{}, Post{}, ...]
Additionally, you can retroactively load associations onto an instance using the load method:
let user = await User.firstOrFail()
user = await user.load('posts').execute()
user.posts
// [Post{}, Post{}, ...]
When loading and preloading, additional where statements can be passed in, and many associations can be chained together:
let user = await User.firstOrFail()
user = await user.load('posts', { body: null }).load('pets').execute()
user.posts
// [Post{ body: null }, Post{ body: null }, ...]
user.pets
// [Cat{}, Dog{}]
In addition to loading associations onto an existing instance, you can also construct a new query targeting your association using associationQuery:
const posts = await user.associationQuery('posts').all()
// [Post{}, Post{}, ...]
Using associationQuery, you can add additional statements to your query before loading, like so:
const posts = await user
.associationQuery('posts')
.where({ body: null })
.innerJoin('comments', { body: null })
.all()
// [Post{ body: null }, Post{ body: null }, ...]
association and associationOrFail
Dream provides convenient methods for accessing single associations (HasOne and BelongsTo) that handle both loaded and unloaded states efficiently.
association
The association method returns the associated record if already loaded, or queries for it if not loaded. It accepts required and passthrough options for dynamic loading.
// Simple usage - returns loaded association or queries for it
const userSettings = await user.association('userSettings')
// UserSettings | null
// With required conditions (must be provided if association uses DreamConst.required)
const favoritePet = await user.association('favoritePet', {
required: { species: 'cat' },
})
// With passthrough data for serializers
const profile = await user.association('profile', {
passthrough: { locale: 'en-US' },
})
associationOrFail
Similar to association, but throws a RecordNotFound exception if the association is not found.
// Will throw if userSettings doesn't exist
const userSettings = await user.associationOrFail('userSettings')
// UserSettings (guaranteed to exist)
// With required conditions
const favoritePet = await user.associationOrFail('favoritePet', {
required: { species: 'dog' },
})
Use required and passthrough instead of and conditions with these methods. The and parameter would be misleading for already-loaded associations since it wouldn't apply retroactively. The required and passthrough parameters are necessary for the association loading to work correctly.
creating
Dream provides some helpful tools for saving associations. One of the simplest and most common ways of saving BelongsTo associations is to attach them directly to their parent while saving it, like so:
const user = await User.firstOrFail()
const post = await Post.create({ user })
This approach will automatically connect the userId field on the new Post model to the primary key field of the User model. This approach will also work with STI or polymorphic BelongsTo associations, since Dream is smart enough to autoset type fields in both cases.
Note: This approach will not work for
HasOneorHasManyassociations.
Additionally, Dream provides the createAssociation method for creating associations, which can be used like so:
await user.createAssociation('posts', { body: 'hello world' })
updating
Updating associations can happen in a multitude of ways. If the instance has already been loaded and attached to another instance, it can be saved just the same as it's parent, using the standard tools provided to all dream instances, like so:
const user = await User.preload('userSettings').firstOrFail()
await user.userSettings.update({ active: true })
Additionally, you can leverage updateAssociation to update associations which haven't been loaded yet:
await user.updateAssociation('userSettings', { active: true })
NOTE: in the case of HasMany associations, calling
updateAssociationwill apply to all discovered associations.
Additional where clauses can be applied during update, like so:
await user.updateAssociation('userSettings', { active: true }, { where: { userId: [1, 2, 3] } })
destroying
Dream also provides helpful tools for destroying associated records. The most commonly used tool is destroyAssociation, a method which will destroy all associated records, and return the number of records deleted:
await user.destroyAssociation('posts')
// 3
You can pass an additional where clause while destroying associations, like so:
await user.destroyAssociation('posts', { where: { body: null } })
// 3
If you would like to bypass cascading on dependent: "destroy" child associations, you can pass cascade: false like so:
await user.destroyAssociation('posts', { cascade: false })
// 3
Additionally, one is free to set up cascade deletion, either at the db level (which is preferred), or else by leveraging dependent: 'cascade' at the association level (though usually, this would only be done for polymorphic relationships, since cascade deleting at the DB level will not work in those cases.)