Database Hooks
Overview
Database hooks are executed when a database record is modified through Skygear. It allows you to run custom codes before or after a record is created, updated or deleted. This is useful when you have to perform operations like data validation and sending emails based on the change in database records,
// Reject empty 'name' before saveing a cat to the database
skygearCloud.beforeSave('cat', function(record, original, pool, options) {
if (!record["name"]) {
return new Promise((resolve, reject) => {
reject(new Error('Missing cat name'));
});
}
return;
}, {
async: false
});
In this example, the cloud code function validate_cat_name
is registered as
a database hook by using the function skygear.before_save
and specifying the record type cat
(i.e. database
table name) that it should hook to.
You can have multiple functions registered with the same database hook. They will all be executed but the execution order is not guaranteed. If this is not desired, you can call each of the functions from a single database hook.
Available Database Hooks
There are 4 types of database hooks, registered with the function decorators listed below.
These function decorators are available from the skygear
module, i.e.
you can use skygear.before_save
after import skygear
.
before_save(record_type, async=True)
: to run before a record is created or updatedafter_save(record_type, async=True)
: to run after a record is created or updatedbefore_delete(record_type, async=True)
: to run before a record is deletedafter_delete(record_type, async=True)
: to run after a record is deleted
Decorator parameters:
record_type
(string): the record type (table name) the function should hook toasync
(boolean): whether the function should be executed asynchronously (Default isTrue
). If it is set toFalse
, the client SDK will only receive the response of a database operation only after the database hook has finished its execution.
before_save hook
beforeSave(recordType: string, func: function(record: lib/record.js~Record, originalRecord: lib/record.js~Record, pool: pool, options: *): *, async: Boolean)
Functions decorated with before_save
are executed just before a record
(of the specified record type) is created or updated in the database.
Typical usages of the before_save
hook includes data validation, setting
default values and checking permissions.
Caution: async
must be set to False
in order for the before_save
hook to work properly. It includes modifying record attributes or
raising an exception to abort the save operation.
Hook function parameters
When a function is decorated with before_save
, it takes three parameters
when invoked:
skygearCloud.beforeSave('comment', function(record, original, pool, options) {
// write your code
}, {
async: false
});
-
record
(SkygearRecord
class)It is the record that is going to be saved to the database. The
record
is an instance of the SkygearRecord
class. Reading and Altering its values are done similar to how you manipulating a JavaScript object:// read a value num = record.number // alter a value record.number = 100
Note: This
record
is a complete object including all attributes. It means that even when you are updating an existing record by providing only one attribute, you still have the full record object containing the existing values of other attributes.
For the metadata attributes, they can be accessed but not altered.
Among those attributes, only the record ID, record type,
owner ID and ACL reflect the latest values as they will be saved to
the database.
Values of the other attributes (updated_at
, updated_by
,
created_at
and created_by
), if existed, are the
existing ones, e.g. the updated_at
is the time
the record was last updated.
record_type = record['_recordType'];
record_id = record['_id'];
owner_id = record['ownerID'];
acl = record['_access'];
updated_at = record['updatedAt'];
updated_by = record['updatedBy'];
created_at = record['createdAt'];
created_by = record['createdBy'];
-
original_record
(SkygearRecord
class)It is the existing record object in the database, as identified by the
_id
. It is useful when you need to compare to the existing value when you update a record.If you are creating a new record,
original_record
will beundefined
.
-
pool
(Database connection pool of the Skygear PostgreSQL)It is an instance of the node-postgres.
Tips:
- The database schema name is
app_<your_app_name>
, i.e. if your Skygear endpoint istodo.skygeario.com
, your schema name isapp_todo
. Alternatively You can find your schema name by connecting to your database.
Caution: Any queries made using
pool
connect to the database directly. These queries do not pass through Skygear. Therefore they are not subject to ACL restrictions; and the metadata attributes of a record, e.g._updated_at
, do not get updated upon anUPDATE
SQL query. - The database schema name is
Return Value
A record (object
) should be returned. The returned record will be saved to the
database, instead of the record
provided as the argument.
The record will be saved as is if you return either of the following:
- not returning at all
null
- the
record
in the argument
If you raise an exception, the record will not be saved.
An UnexpectedError
will be returned, with the Exception message in the
message
attribute.
Example
The following before_save
hook example on the Selfie
record type
demonstrates:
- data validation (
image_url
cannot be empty) - setting a default value (
likes_count
is zero) - executing a database SQL query (update the user's last seen time)
skygearCloud.beforeSave('selfie', function(record, original, pool, options) {
// Check for non-empty image URL
if (!record['image_url']) {
return new Promise((resolve, reject) => {
reject(new Error('Empty Selfie URL'));
});
}
// Set initial "like" count if it's a new record
if (!original) {
console.log('Original record is null');
record['likes_count'] = 0;
}
pool.query(`UPDATE app_hello_world.user SET last_seen = CURRENT_TIMESTAMP WHERE _id ='${record.ownerID}'`, function(err, result) {
if (err) {
console.log(err);
return;
}
console.log(result);
});
return record;
}, {
async: false
});
after_save hook
afterSave(recordType: string, func: function(record: lib/record.js~Record, originalRecord: lib/record.js~Record, pool: pool, options: *): *, async: Boolean)
Functions decorated with after_save
are executed after a record
(of the specified record type) is created or updated in the database.
It is different from the before_save
hook that in an after_save
hook
the record
has been saved to the database. This means that we cannot
alter the record
in place or stop the record
from being saved by
raising an exception.
Typical usages of the after_save
hook includes operations that take
significant time to complete, e.g. sending emails, or
updating records related to the newly saved record.
Hook function parameters
When a function is decorated with after_save
, it takes three parameters
when invoked:
skygearCloud.afterSave('comment', function(record, original, pool, options) {
// write your code
}, {
async: false
});
These parameters are the same as
those in a function decorated with the before_save
hook.
The only difference is that the record
in the after_save
hook contains all
up-to-date metadata attributes, whereas the before_save
hook only has
a few up-to-date.
Tips: If you want to alter the record
, you can either use
the provided db
connection to execute raw SQL, or
call the Skygear API using the container.
Return Value
No return value is necessary.
Example
The following after_save
hook example on the selfie
record type
demonstrates:
- updating the user's selfie count after a selfie is successfully saved
skygearCloud.afterSave('selfie', function(record, original, pool, options) {
if (!original) {
pool.query(`UPDATE app_hello_world.user SET selfie_count = selfie_count + 1, _updated_at = CURRENT_TIMESTAMP WHERE '_id' = '${record.createdBy}'`, function(err, result) {
if (err) {
console.log(err);
return;
}
console.log(result);
})
}
return;
}, {
async: false
});
before_delete hook
beforeDelete(recordType: string, func: function(record: lib/record.js~Record, originalRecord: lib/record.js~Record, pool: pool, options: *): *, async: Boolean)
Functions decorated with before_delete
are executed just before a record
(of the specified record type) is deleted from the database.
Typical usages of the before_delete
hook includes permission checks for
business logic.
Hook function parameters
When a function is decorated with before_delete
, it takes two parameters
when invoked, without the original_record
parameter from the before_save
or after_save
hooks:
skygearCloud.beforeDelete('comment', function(record, original, pool, options) {
// write your code
}, {
async: false
});
These parameters are the same as
those in a function with the before_save
hook.
This time the record
contains all up-to-date metadata attributes.
Return Value
No return value is necessary.
If you raise an exception, the record will not be deleted.
An UnexpectedError
will be returned, with the Exception message in the
message
attribute.
Caution: async
must be set to False
if you need to cancel the
delete operation by raising an exception.
Example
The following before_delete
hook example on the group_chat_user
record type
demonstrates:
- a feature that the last admin in a group chat cannot be deleted
// Todo
after_delete hook
afterDelete(recordType: string, func: function(record: lib/record.js~Record, originalRecord: lib/record.js~Record, pool: pool, options: *): *, async: Boolean)
Functions decorated with after_delete
are executed after a record
(of the specified record type) has been deleted from the database.
Typical usages of the after_delete
hook includes
sending notifications or database cleanups.
Hook function parameters
When a function is decorated with after_delete
,
it takes two parameters like the before_delete
hook, record
and db
.
skygearCloud.afterDelete('comment', function(record, original, pool, options) {
// write your code
}, {
async: false
});
These parameters are the same as
those in a function decorated with the before_save
hook.
This time the record
contains all up-to-date metadata attributes.
Return Value
No return value is necessary.
Example
The following after_delete
hook example on the group_chat_user
record type
demonstrates:
- sending email to the user when he is removed from the group chat
// Todo