ground
Version:
A framework work with sails and backbone.
186 lines (141 loc) • 5.69 kB
text/coffeescript
# Author: Andy Zhao(andy@nodeswork.com)
#
# A sample for how to use it as sails model.
#
# class YourModel extends GroundModel
#
# schema:
# name: String
#
# YourModel.sails()
_ = require 'underscore'
async = require 'async'
mongoose = require 'mongoose'
changeCase = require 'change-case'
groundDb = require './ground_db'
__constants =
# The available lifecycle callback function for sails model.
LifecycleCallbackFunctionNames: [
'beforeValidation', 'beforeCreate', 'afterCreate', 'beforeUpdate', 'afterUpdate'
'beforeDestroy', 'afterDestroy'
]
ExtendFuncs:
removeMongooseDoc: (cb) ->
sailsDoc = @__sailsDoc
for key in _.keys(sailsDoc)
delete sailsDoc[key]
delete @__sailsDoc
_.extend sailsDoc, @toObject()
cb null
beforeValidation: (cb) ->
@validate cb
__constants.ExtendFuncs.beforeCreate = __constants.ExtendFuncs.removeMongooseDoc
__constants.ExtendFuncs.beforeUpdate = __constants.ExtendFuncs.removeMongooseDoc
__helper =
sailsType: (v) ->
switch
when _.isArray v then 'array'
when not _.isFunction(v) and not v.type? then 'json'
when v is mongoose.Schema.Types.Mixed then 'json'
when _.isFunction(v) and v.name is 'Mixed' then 'json'
else {}
sailsSchema: (schema) ->
_.reduce(schema, ((memo, v, k) -> memo[k] = __helper.sailsType(v); memo), {})
class GroundModel
# The database name.
@adapter: 'test'
# To specify the collection name. By default, it will translate the model class name to a
# related collection name.
@collection: null
# The schema of the model, using mongoose format. See the mongoose document for help.
@schema: {}
# Sails lifecycle callbacks. As commented below.
@callbacks: {}
# beforeValidation: (cb) ->
# beforeCreate: (cb) ->
# afterCreate: (cb) ->
# beforeUpdate: (cb) ->
# afterUpdate: (cb) ->
# beforeDestroy: (cb) ->
# afterDestroy: (cb) ->
# The statics function of the model. 'this' will be bound to sails model class.
@statics:
validate: (doc, cb) ->
model = new (@ground.mongoose()) doc
model.validate cb
findByIds: (ids, cb) ->
async.series (_.map ids, (id) => (cb) => @findOneById id, cb), cb
@methods: {}
# The indexes of the model.
@indexes: {}
# Returns the mongoose model interface.
@mongoose: () ->
return @__mongooseModel if @__mongooseModel?
modelPath = @collection ? @__getModelPathFromModelName()
connection = groundDb.mongooseConnection @adapter
@__mongooseModel ?= connection.model modelPath, @__mongooseSchema()
# Returns the sails model interface.
@sails: ->
@__sailsModel ?= _.extend {
# The adapter which appears inside config/adapters.coffee
adapter: @adapter
# The attributes of the current model. Since we are using mongoose to do the validation, so
# most time, the schema of sails model would be empty, and in schemaless mode.
attributes: @__sailsAttributes()
schema: no
# Same with mongoose table name.
tableName: @mongoose().collection.name
}, @__sailsLifecycleCallbacks()
# Returns the sails attributes. By using mongoose to do the validation, no need to returns the
# real attributes. But will involve some instance methods inside the attributes.
@__sailsAttributes: ->
_.extend {},
@__super__?.constructor?.__sailsAttributes() ? {},
@__methods(),
__helper.sailsSchema(@schema)
ground: @
# Returns the sails lifecycle callback function for specific callback name.
@__sailsLifecycleCallback: (callbackName) ->
return [] unless callbackFunc = @callbacks?[callbackName]
callbacks = @__super__?.constructor?.__sailsLifecycleCallback(callbackName).slice(0) ? []
if _.indexOf(callbacks, callbackFunc) < 0 then callbacks.push callbackFunc
callbacks
# Assemble the sails lifecycle callbacks. All the defined function for super classes would also
# be called. The order will be from base class to subclasses.
@__sailsLifecycleCallbacks: ->
callbacks = {}
for functionName in __constants.LifecycleCallbackFunctionNames
callbackFuncs = @__sailsLifecycleCallback(functionName)
if __constants.ExtendFuncs[functionName]
callbackFuncs.push __constants.ExtendFuncs[functionName]
if callbackFuncs.length is 0
continue
do (functionName, callbackFuncs) => callbacks[functionName] = (values, cb) =>
values.__mongooseDoc ?= new (@mongoose())(values)
values.__mongooseDoc.__sailsDoc ?= values
async.series (_.bind(func, values.__mongooseDoc) for func in callbackFuncs), (err) ->
cb err
callbacks
# Returns the nested schema.
@__schema: -> _.extend {}, @__super__?.constructor?.__schema() ? {}, @schema
# Returns the nested methods.
@__methods: -> _.extend {}, @__super__?.constructor?.__methods() ? {}, @methods
# Returns the nested statics.
@__statics: -> _.extend {}, @__super__?.constructor?.__statics() ? {}, @statics
# Assemble the NOT cached mongoose schema.
@__mongooseSchema: ->
mongooseSchema = new mongoose.Schema @__schema(), _id: no
_.extend mongooseSchema.methods, @__methods()
_.extend mongooseSchema.statics, @__statics()
mongooseSchema
# Returns the model path from model name.
#
# e.g. class GroupModel -> groups
# class UserGroup -> user_groups
@__getModelPathFromModelName: ->
modelName = if @name.length > 5 and @name.substring(@name.length - 5) is 'Model'
@name.substring(0, @name.length - 5)
else
@name
changeCase.snakeCase modelName
module.exports = GroundModel