Skip to content

Instantly share code, notes, and snippets.

@ericeslinger
Last active May 13, 2016 21:09
Show Gist options
  • Save ericeslinger/a83e74501e9901c8b795 to your computer and use it in GitHub Desktop.
Save ericeslinger/a83e74501e9901c8b795 to your computer and use it in GitHub Desktop.
Add side-loads to BookshelfJS
Knex = require 'knex'
Promise = require 'bluebird'
FlormModel = require './flormModel'
Document = FlormModel.extend
tableName: 'documents'
# here's an example sideload construct. I want to know document editor ids, which are stored in the
# edges join table - that table stores parent_id and child_id polymorphically (with parent_type and child_type)
# also polymorphically with join type- 'grant: edit' denotes editorship.
# this specific kind of hasMany and belongstoMany kind of query could be done straight up in BookshelfJS using
# advanced synax in the withRelated and hasMany calls, but it's a good minimal example of what I'm trying to
# accomplish.
sideloadIds:
editor_ids: (m)-> # m is passed in as a parameter as I had trouble getting
Knex.floDB 'edges' # things to execute in the right 'this' context
.where
parent_type: 'documents'
parent_id: m.get('id')
child_type: 'profiles'
type: 'grant: edit'
.select 'child_id as id'
.then (result)->
result.map (v)->v.id
module.exports = Document
florm = require('bookshelf').florm
Knex = require 'knex'
Promise = require 'bluebird'
FlormModel = florm.Model.extend
sideload: (names = Object.keys(@sideloadIds))->
names = [names] unless Array.isArray(names)
Promise.all names.map (name)=>
if @sideloadIds[name]?
@sideloadIds[name](@)
.then (result)=>
@set name, result
FlormModel.prototype.fetch = ->
florm.Model.prototype.fetch.apply @, arguments
.then =>
@sideload()
.then =>
@
FlormModel.prototype.fetchAll = ->
florm.Model.prototype.fetchAll.apply @, arguments
.then (models)->
Promise.all models.map (m)->
m.sideload()
.then ->
models
module.exports = FlormModel

FlormModel extends Bookshelf's Model class to allow for arbitrary side-loads on the model definition. The example loads IDs from the profiles table using the edges table as a join table.

Each element on sideloadIds should be a function that returns a promise. The promise should resolve to some value (doesn't matter what), which will be set on the actual Models during fetch or fetchAll calls.

This is ideal for either:

  • creating loosely-coupled relations for an API, as (for example) the JSON API spec prefers relations to only include a set of ids (like reply_ids: [1, 2, 3] instead of replies: [{reply1}, {reply2}, {reply3}]).
  • using more complicated SQL queries in your model definition. My own needs have some relations that don't fit into a clean hasMany style query (there's some complicated joins).

NOTE: there's no error handling in this example, as that would clutter it up. Do it yourself.

If you don't want the sideload code to fire off automatically on fetching (it does generate more queries), you can go ahead and just remove the fetch and fetchAll extensions and call FlormModel.sideload('thing_ids'), .sideload(['thing_ids', 'other_thing_ids']), or .sideload() on your own time.

@thomasdashney
Copy link

Thanks for this! An alternative for people who would rather chain it onto the fetch would be to write something like the following:

function sideload(model) {
  if (!model) // make sure there's a model
    return model;
  if (model.length) { // collection
    return Promise.all(model.map(function(m) {
      return m.sideload();
    })).then(function() {
      return model;
    });
  }
  // else
  return model.sideload().then(function() {
    return model;
  });
};

// then in your fetch..
model
  .where({id: 4})
  .fetch() // fetchAll() works here as well
  .then(sideload)
  .then(function(model) {
    // etc
  });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment