pavarotti - easily create CRUD controllers
We consistently require controllers that have CRUD functionality (create, read, update and delete).
To avoid institutionalising ourselves, we define generic
versions of these methods here which we can then use to e.g. build a
controller that manages users. Such a controller would be created with
usersController = require('pavarotti').methodsFor(User)
, where User
is the
user model.
Methods
The methods we would like to build abstract versions of, are:
-
set
Create an item with the given params, or update the item with given
id
with the given params. The newly saved item is returned. -
get
Get a single item by its id.
-
find
Get all items that match the criteria given, possibly paging and sorting the result.
-
remove
Delete a single item by its id.
Config
We can customise these methods by passing in configuration options to the main
methodsFor
method, which constructs the CRUD controller. The options are:
set
-
beforeSet
: Model |-> Model
bfunction called before the model is saved to the database. Is given the model that will be saved. The result is the final object saved to the database. An error stops the method.
Defaults to
bf.identity
. -
afterSet
: Model |-> Model
bfunction called after the model has been saved to the database. Is given the model that was saved. The result will be the output of the
set
method. An error stops the method.Defaults to
bf.identity
.
get
-
afterGet:
: Model |-> Model
bfunction called after the model has been retrieved from the database. Is given the model that was retrieved. The result will be the output of the
get
method. An error stops the method.Defaults to
bf.identity
.
find
-
beforeFind:
: Params |-> Params
bfunction called at the beginning of the
find
method. Is given the params to thefind
method. The result will be fed into the next stem of thefind
method. An error stops the method.Defaults to
bf.identity
. -
buildFindFilter
: Params |-> FindFilter
bfunction that, given the params to the
find
method, builds the object that is given to mongoose to filter the model collection (thefind()
filter). If null, builds the find filter using properties that are in thefilter
object in the params.Defaults to
null
. -
buildFindSort
: Params |-> FindSort
bfunction that, given the params to the
find
method, builds the object that is given to mongoose to sort the model collection (thesort()
object). If null, builds the sort filter using properties thare are in thesort
object in the params such that e.g.{ sort: { name: 'asc' } }
becomes{ name: 1 }
.Defaults to
null
. -
afterFind: `: { items: Model[], total: number, filteredTotal: number } |-> any
bfunction that, given the final result of the
find
method (results, number of total items and number of filtered items) returns some object that will be the output of thefind
method.Defaults to
bf.identity
.
Dependencies
_ = require 'underscore'bf = require 'barefoot'
Abstraction
The CRUD controller methods are generated once from a single initialisation call
to methodsFor
. This method takes the model that the CRUD methods are for and a
variety of additional configuration methods that lets the consumer of this CRUD
controller customise each of the CRUD methods.
= config = _defaults config beforeSet: bfidentity afterSet: bfidentity afterGet: bfidentity beforeFind: bfidentity buildFindFilter: null buildFindSort: null afterFind: bfidentity crud =
Create and update (set)
We create and update with the single set
method. This performs an insert
operation if no id
is given, and an update operation is an id
is given. In
any case, the newly saved item is returned.
crud.set = bfchain -> bfvalidate _id: String getOrCreate configbeforeSet save configafterSet
First, attempt to retrieve the item by its id. If there is none found then
create a new item. We use mongoose's set
method to apply the given params to
the fetched model.
= w = bferrorWrapper done modelfindById paramsidw item ?= itemset params done nullitem
Now, we have a fully populated offer model. All that's left to do is save it!
= itemsave done
Read (get)
We can retrieve a single item by its id very simply. This method takes one
param, id
, and returns the item (or null if none exists).
crud.get = bfchain -> bfvalidate id: String bfselect pid _bind modelfindByIdmodel configafterGet
Read (find)
We can search for items using a single super method that takes many different
filtering params (findFilterParams
) and sorting params (findSortParams
) and
retrieves a paginated list of items that match. The consumer of the CRUD API
helper is able to modify the mongo find()
object and sort()
object.
crud.find = bfchain -> bfvalidate _filter: Object _sort: Object _skip: Number _limit: Number configbeforeFind bfparallel configbuildFindFilter ? buildFilter configbuildFindSort ? buildSort bfidentity runQuery configafterFind
The default buildFindFilter
just filters on the parameters given in
params.filter
.
= done nullparamsfilter
The default buildFindSort
convers the params.sort
object given from e.g.
{prop: 'asc'}
to {prop: 1}
.
= sort = null if paramssort? sort = for po of paramssort if o == 'asc' sortp= 1 else if o == 'desc' sortp= -1 done nullsort
We require very flexible pagination, so running the query is a little tedious, though simple enough.
= seq = bfsequence done filter ?= sort ?= total = 0 filteredTotal = 0 seqthen modelfindcount seqw total = count next seqthen modelfindfiltercount seqw filteredTotal = count next seqthen query = modelfindfilter if paramsskip? query = queryskipparamsskip if paramslimit? query = querylimitparamslimit queryexec seqw done null items: items total: total filteredTotal: filteredTotal
Delete (remove)
Deleting or removing an item is a simple method that retrieves an object by its
id
and then removes it if it exists. If no item with the given id
exists,
nothing happens.
crud.remove = bfchain -> bfvalidate id: String _bind modelremovemodel
Finishing up
We must conclude the methodsFor
function we began to define.
return crud
And then export this function.
module.exports = {
methodsFor
}