Skip to main content

creating

In any application, you will often times find yourself in need of having some things in your database. Otherwise...I mean... what are we doing here? Snarcasm aside, Dream provides many different ways to insert records into the database, depending on what you are doing and why.

create

When looking for a clean, direct way to get something into a table in your database, look no further than the static create method. The create method will provide you with a simple, type-protected way to safely insert attributes into the database:

import User from 'app/models/user'

const user = await User.create({
email: 'hello@world.biz',
password: 'fishandfriends',
})

When using the static create method, the BeforeCreate, BeforeSave, AfterCreate, AfterSave, AfterCreateCommit, and AfterSaveCommit hooks will all fire, enabling lifecycle events defined in your model layer to kick off additional changes to your application's ecosystem.

new and save

Sometimes, you may find yourself wanting to slowly build up attributes, or just making change to one field, conditionally. This is the perfect time to leverage the new and save methods:

const user = User.new()

if (thing1) {
user.email = 'hello@world.biz'
} else {
user.email = 'goodbye@cruelworld'
}

await user.save()

You can skip lifecycle hooks on either form — useful for bulk imports and historical data backfills where the new-record side effects shouldn't fire:

await User.create({ email: 'imported@example.com' }, { skipHooks: true })

Dream also provides two methods to use for a locate-or-create pattern:

findOrCreateBy

Utilizing findOrCreateBy, we can either collect a record if it exists, or else create it. Any fields passed into the createWith option will be applied only if the record is being created:

const user = await User.findOrCreateBy({ email: 'how@yadoin' }, { createWith: { password: 'mypassword' } })

There is a race condition between the find and create steps — if concurrent requests could match the same lookup attributes, prefer createOrFindBy below.

createOrFindBy

The createOrFindBy method is very similar to findOrCreateBy, except it will attempt to create the record first. This pattern relies on unique constraints at the DB level to reject the create statement if another record already exists with those fields, which avoids the race condition in findOrCreateBy. Since the create action will fail at the database level when a matching record already exists, createOrFindBy may not be used in a transaction:

// this expects that the `email` column will have a unique constraint in the DB
const user = await User.createOrFindBy({ email: 'how@yadoin' }, { createWith: { password: 'mypassword' } })
caution

On the unique-violation fallback, createOrFindBy re-finds using that same first argument, so it must hold exactly the unique index's attributes and nothing more. An extra attribute narrows the lookup, and if a submitted value differs from the stored row the re-find comes back empty and CreateOrFindByFailedToCreateAndFind turns the duplicate case into a 500. Everything else — including a field that carries its own separate unique constraint — belongs in createWith, since any unique violation lands in the same fallback and the first argument can't identify the row that field collided with.

// unique index on (place_id, guest_id, check_in_month)
const booking = await Booking.createOrFindBy(
{ place, guest, checkInMonth },
{ createWith: { nights: 3 } }
)

Without a real unique index on the lookup attribute(s), createOrFindBy can silently insert duplicate rows — it only detects an existing record via a uniqueness violation on the insert, and has no lookup attributes indexed to violate.

Dream also provides updateOrCreateBy and createOrUpdateBy for an upsert-style find-or-update pattern — see the updateOrCreateBy and createOrUpdateBy guides.