Collection Operation Pre/Post Hooks (Advanced)¶
You should generally only have to implement the
Collection Operation Handlers and apply the appropriate configuration to your
Collection instance to achieve the desired
behavior. Despite this, you may find that you need more flexibility in certain
situations, especially when instantiating a concrete Collection implementation.
There are four hooks that you can override to change the
behavior of a Collection operation. These hooks have the following signatures
and are called for each operation in the order listed, with the handler
being called between pre<OPERATION NAME> and post<OPERATION NAME>.
pre<OPERATION NAME>Operation(config, req, res, context)pre<OPERATION NAME>(<REQUIRED OPERATION PARAMETERS>, options, context)post<OPERATION NAME>(result, <REQUIRED OPERATION PARAMETERS>, options, context)post<OPERATION NAME>Operation(result, config, req, res, context)
Where <OPERATION NAME> is the name of the operation with the first letter
capitalized (e.g., InsertObject for the insertObject operation) and
<REQUIRED OPERATION PARAMETERS> are the leading required parameters in each
operation handler signature (e.g., id and update in the case of
updateObject).
Each of these hooks has a generic implementation for each operation in
Collection and will be described in the
following sections.
pre<OPERATION NAME>Operation¶
The base pre<OPERATION NAME>Operation hooks are responsible for building the
options parameter based on the incoming request and config
for this operation. As such, the return value of these methods should be
the initialized options parameter that will be passed on to the handler. It should be
noted that at this step, options should contain all parameters that will
be passed to the operation handler (e.g., for the updateObject operation,
preUpdateObjectOperation would return a options object that contained the ID
parameter, update parameter, along with any other parameters or context that
may be relevant). In general, options is simply assigned req.parameters.
The preInsertObjectOperation method,
for instance, validates that the ID property is not present in the object to be
inserted into the collection. Additionally, if
idGenerator is present, it will call
its generateId method and set the ID for the incoming object that will
ultimately be passed to the operation handler method.
It should be noted, that required parameters to an operation handler (object
in the case of insertObject(object, options)) should remain in the
options object at this step as they will be extracted from options and
passed as the leading parameters to the handler.
As an example, let’s say that we want objects in a collection belonging to
separate users to appear as if they share the same IDs (e.g. user “foo” would
see a different object than user “bar” when making a request to
/collection/1). You could extend preFindObjectOperation as follows:
preFindObjectOperation(config, req, res, context) {
var options = Collection.prototype.preFindObjectOperation.call(this, config, req, res, context)
var idPrefix = getUserIdPrefix(req.user)
options[this.idPathParameterName] = idPrefix + '-' + options[this.idPathParameterName]
return options
}
pre<OPERATION NAME>¶
The base pre<OPERATION NAME> hooks have the same signature as the operation
handler (e.g., preInsert(objects, options, context)) and are no-ops that
simply pass through their arguments. The requirements for return value when
overriding are loose. You can either augment the parameters by side-effect and
return nothing or override parameters by returning an object whose keys match
the parameter names (note, context is meant to be side-effected and does not
need to be part of the return value) and whose values are the updated
parameters. You can omit any parameters that you do not intend to override.
For example, if you were creating an instance of
MongoDBCollection and wanted to add a created
field to any object being inserted, you might do something like the following:
preInsertObject(object, options, context) {
object.created = new Date()
}
post<OPERATION NAME>¶
The base post<OPERATION NAME> hooks have the same signature as the operation
handler with the result of the operation handler prepended to the parameter list
(e.g., postInsert(result, objects, options, context)) and, similar to their
pre<OPERATION NAME> counterparts, simply return the result. These hooks are
useful if you want to augment the result object in some way. For example, you
may want to sanitize some fields in a result:
postFindObject(result, id, options, context) {
if (!_.isNil(result)) {
result.apiKey = 'REDACTED'
}
return result
}
post<OPERATION NAME>Operation¶
The base post<OPERATION NAME>Operation hooks take a result, as returned
from post<OPERATION NAME>, as well as a config, request object, and response
object, and update the response to be sent to the user (e.g., set the status
code). Finally, they return the result and pass control back to carbond.
These hooks are useful when you want to further augment the response. For
example, you may log the last time a request was made by a particular user and
return that in a header in the response:
postFindObjectOperation(result, config, req, res, context) {
result = Collection.prototype.postFindObjectOperation.call(this, result, config, req, res)
var lastAccessTime = getLastAccessTimeForUser(req.user)
res.set('X-Last-Access-Time', lastAccessTime)
return result
}
The context parameter¶
The context parameter (as mentioned in operation handlers) is the last argument to all hook methods. This
parameter is not used internally by carbond and is present solely for the
convenience of passing data between hook/handler functions to support any
functionality that may be hard to accomplish otherwise. For example, you could
track the amount of time it takes to execute the processing chain (hooks and
handler) and return that in a header to the client as follows:
preFindObjectOperation(config, req, res, context) {
context.start = new Date()
return Collection.prototype.preFindObjectOperation.call(this, config, req, res, context)
}
...
postFindObjectOperation(result, config, req, res, context) {
var result = Collection.prototype.postFindObjectOperation.call(this, result, config, req, res)
res.set('X-OP-Time-MS', new Date() - context.start)
return result
}