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
}