Initial commit
parent
0dc51bb0b9
commit
1faf24a0ca
@ -0,0 +1 @@
|
|||||||
|
module.exports = require "./lib"
|
@ -0,0 +1,97 @@
|
|||||||
|
Model = require "./model"
|
||||||
|
knex = require "knex"
|
||||||
|
|
||||||
|
class YAORM
|
||||||
|
constructor: (options) ->
|
||||||
|
@_models = {}
|
||||||
|
|
||||||
|
if options.knex?
|
||||||
|
@knex = options.knex
|
||||||
|
else if options.hostname?
|
||||||
|
@knex = knex({
|
||||||
|
client: switch options.driver
|
||||||
|
# Some aliases...
|
||||||
|
when "postgres", "postgresql" then "pg"
|
||||||
|
when "mysql" then "mysql2"
|
||||||
|
else options.driver
|
||||||
|
connection:
|
||||||
|
host: options.hostname
|
||||||
|
user: options.username
|
||||||
|
password: options.password
|
||||||
|
database: options.database
|
||||||
|
charset: options.charset ? "utf8"
|
||||||
|
debug: options.debug ? false
|
||||||
|
})
|
||||||
|
else if options.knexfile?
|
||||||
|
knexfile = require "knexfile"
|
||||||
|
|
||||||
|
if knexfile.connection?
|
||||||
|
# Basic configuration
|
||||||
|
@knex = knex(knexfile)
|
||||||
|
else
|
||||||
|
# Environment configuration
|
||||||
|
if not options.environment?
|
||||||
|
# FIXME: Error
|
||||||
|
null
|
||||||
|
@knex = knex(knexfile[options.environment])
|
||||||
|
else
|
||||||
|
# FIXME: Error
|
||||||
|
|
||||||
|
_registerModel: (model) ->
|
||||||
|
model._YAORM = this
|
||||||
|
@_models[model.name] = model
|
||||||
|
return this
|
||||||
|
|
||||||
|
_createRelation: (type, options) ->
|
||||||
|
options.type = type
|
||||||
|
return options
|
||||||
|
|
||||||
|
loadModel: (modelPath) ->
|
||||||
|
# We use the existing model as a prototype, so that we don't run into conflicts if two different YAORM instances were to use the same loaded model.
|
||||||
|
baseModel = require(modelPath)
|
||||||
|
model = Object.create(baseModel)
|
||||||
|
@_registerModel(model)
|
||||||
|
|
||||||
|
defineModel: (modelName, options) ->
|
||||||
|
model = new Model(modelName, options)
|
||||||
|
@_registerModel(model)
|
||||||
|
|
||||||
|
model: (modelName) ->
|
||||||
|
return @_models[modelName]
|
||||||
|
|
||||||
|
express: ->
|
||||||
|
return (req, res, next) ->
|
||||||
|
null
|
||||||
|
next()
|
||||||
|
|
||||||
|
hasOne: (modelName, options = {}) ->
|
||||||
|
if options.foreignKey?
|
||||||
|
options.remoteKey = options.foreignKey
|
||||||
|
delete options.foreignKey
|
||||||
|
|
||||||
|
options.modelName = modelName
|
||||||
|
@_createRelation "hasOne", options
|
||||||
|
|
||||||
|
hasMany: (modelName, options = {}) ->
|
||||||
|
if options.foreignKey?
|
||||||
|
options.remoteKey = options.foreignKey
|
||||||
|
delete options.foreignKey
|
||||||
|
|
||||||
|
options.modelName = modelName
|
||||||
|
@_createRelation "hasMany", options
|
||||||
|
|
||||||
|
belongsTo: (modelName, options = {}) ->
|
||||||
|
if options.foreignKey?
|
||||||
|
options.localKey = options.foreignKey
|
||||||
|
delete options.foreignKey
|
||||||
|
|
||||||
|
options.modelName = modelName
|
||||||
|
@_createRelation "belongsTo", options
|
||||||
|
|
||||||
|
exportMethod = (options) ->
|
||||||
|
return new YAORM(options)
|
||||||
|
|
||||||
|
exportMethod.defineModel = (modelName, options) ->
|
||||||
|
return new Model(modelName, options)
|
||||||
|
|
||||||
|
module.exports = exportMethod
|
@ -0,0 +1,188 @@
|
|||||||
|
Promise = require "bluebird"
|
||||||
|
util = require "util"
|
||||||
|
|
||||||
|
Record = require "./record"
|
||||||
|
|
||||||
|
module.exports = class Model
|
||||||
|
constructor: (@name, @options) ->
|
||||||
|
# FIXME: Validation! tableName
|
||||||
|
@_isInstance = false
|
||||||
|
@options.idAttribute ?= "id"
|
||||||
|
|
||||||
|
_getInstance: ->
|
||||||
|
if @_isInstance
|
||||||
|
return this
|
||||||
|
else
|
||||||
|
instance = Object.create(this)
|
||||||
|
instance._isInstance = true
|
||||||
|
instance._queryBuilder = @_YAORM.knex(@options.tableName)
|
||||||
|
return instance
|
||||||
|
|
||||||
|
_fromQuery: (qbFunc) ->
|
||||||
|
instance = @_getInstance()
|
||||||
|
qbFunc(instance._queryBuilder)
|
||||||
|
return instance
|
||||||
|
|
||||||
|
_where: (whereStatements) ->
|
||||||
|
@_fromQuery (queryBuilder) ->
|
||||||
|
queryBuilder.where whereStatements
|
||||||
|
|
||||||
|
_all: ->
|
||||||
|
# We don't need to add anything to the query here, since we want all the records.
|
||||||
|
@_getInstance()
|
||||||
|
|
||||||
|
_createResultHandler: (options = {}) ->
|
||||||
|
return (rows) =>
|
||||||
|
self = this
|
||||||
|
|
||||||
|
Promise.map rows, (row) =>
|
||||||
|
# FIXME: Can this be done with a JOIN, perhaps?
|
||||||
|
record = self._createRecord()
|
||||||
|
record.isNew = false
|
||||||
|
record._setData(row)
|
||||||
|
|
||||||
|
if not options.relations?
|
||||||
|
options.relations = []
|
||||||
|
else if not util.isArray(options.relations)
|
||||||
|
options.relations = [options.relations]
|
||||||
|
|
||||||
|
record._loadRelations(options.relations ? [], row)
|
||||||
|
.then (rows) ->
|
||||||
|
if (options.single ? false)
|
||||||
|
if rows.length > 0
|
||||||
|
return rows[0]
|
||||||
|
else
|
||||||
|
# FIXME: Error
|
||||||
|
else
|
||||||
|
if rows.length > 0 or (options.required ? true) == false
|
||||||
|
return rows
|
||||||
|
else
|
||||||
|
# FIXME: Error
|
||||||
|
|
||||||
|
_createResultHandlerSingle: (options = {}) ->
|
||||||
|
options.single = true
|
||||||
|
@_createResultHandler(options)
|
||||||
|
|
||||||
|
_createResultHandlerCount: (options = {}) ->
|
||||||
|
return (rows) =>
|
||||||
|
return rows[0].CNT
|
||||||
|
|
||||||
|
_createRecord: ->
|
||||||
|
record = new Record()
|
||||||
|
record._setModel(this)
|
||||||
|
return record
|
||||||
|
|
||||||
|
_populateRecord: (record, data) ->
|
||||||
|
record._setData(data)
|
||||||
|
|
||||||
|
_getRelations: (relations, data) ->
|
||||||
|
Promise.try =>
|
||||||
|
relationKeys = Object.keys(relations)
|
||||||
|
|
||||||
|
Promise.map relationKeys, (attribute) =>
|
||||||
|
# FIXME: Shallow-clone these options? Immutability etc.
|
||||||
|
options = relations[attribute]
|
||||||
|
|
||||||
|
switch options.type
|
||||||
|
when "hasOne" then @_getHasOne(options.modelName, options, data)
|
||||||
|
when "hasMany" then @_getHasMany(options.modelName, options, data)
|
||||||
|
when "belongsTo" then @_getBelongsTo(options.modelName, options, data)
|
||||||
|
.reduce ((obj, remoteRecord, i) ->
|
||||||
|
obj[relationKeys[i]] = remoteRecord
|
||||||
|
return obj
|
||||||
|
), {}
|
||||||
|
|
||||||
|
_getSimpleRelation: (modelName, options, data) ->
|
||||||
|
Promise.try =>
|
||||||
|
remoteModel = @_YAORM.model(modelName)
|
||||||
|
|
||||||
|
switch options.type
|
||||||
|
when "hasOne", "hasMany" then options.localKey ?= remoteModel.options.idAttribute
|
||||||
|
when "belongsTo" then options.remoteKey ?= remoteModel.options.idAttribute
|
||||||
|
|
||||||
|
whereStatements = {}
|
||||||
|
whereStatements[options.remoteKey] = data[options.localKey]
|
||||||
|
options.query ?= (->)
|
||||||
|
|
||||||
|
queryBuilder = remoteModel
|
||||||
|
._where whereStatements
|
||||||
|
._fromQuery(options.query)
|
||||||
|
.query()
|
||||||
|
|
||||||
|
if (options.single ? false)
|
||||||
|
queryBuilder
|
||||||
|
.limit 1
|
||||||
|
.then remoteModel._createResultHandlerSingle(options)
|
||||||
|
else
|
||||||
|
queryBuilder
|
||||||
|
.then remoteModel._createResultHandler(options)
|
||||||
|
|
||||||
|
# The logic regarding which is the localKey and which is the remoteKey, is handled in the YAORM instance.
|
||||||
|
_getHasOne: (modelName, options, data) ->
|
||||||
|
options.single = true
|
||||||
|
@_getSimpleRelation(modelName, options, data)
|
||||||
|
|
||||||
|
_getHasMany: (modelName, options, data) ->
|
||||||
|
@_getSimpleRelation(modelName, options, data)
|
||||||
|
|
||||||
|
_getBelongsTo: (modelName, options, data) ->
|
||||||
|
options.single = true
|
||||||
|
@_getSimpleRelation(modelName, options, data)
|
||||||
|
|
||||||
|
create: (data) ->
|
||||||
|
record = @_createRecord()
|
||||||
|
record.isNew = true
|
||||||
|
return record
|
||||||
|
|
||||||
|
query: ->
|
||||||
|
return @_queryBuilder
|
||||||
|
|
||||||
|
find: (id, options = {}) ->
|
||||||
|
whereStatements = {}
|
||||||
|
whereStatements[@options.idAttribute] = id
|
||||||
|
@getOneWhere(whereStatements, options)
|
||||||
|
|
||||||
|
getOneWhere: (whereStatements, options = {}) ->
|
||||||
|
@_where(whereStatements)
|
||||||
|
.query()
|
||||||
|
.limit(1)
|
||||||
|
.then(@_createResultHandlerSingle(options))
|
||||||
|
|
||||||
|
getAllWhere: (whereStatements, options = {}) ->
|
||||||
|
@_where(whereStatements)
|
||||||
|
.query()
|
||||||
|
.then(@_createResultHandler(options))
|
||||||
|
|
||||||
|
countWhere: (whereStatements, options = {}) ->
|
||||||
|
@_where(whereStatements)
|
||||||
|
.query()
|
||||||
|
.count("#{@options.idAttribute} as CNT")
|
||||||
|
.then(@_createResultHandlerCount(options))
|
||||||
|
|
||||||
|
getOneFromQuery: (qbFunc, options = {}) ->
|
||||||
|
@_fromQuery(qbFunc)
|
||||||
|
.query()
|
||||||
|
.limit(1)
|
||||||
|
.then(@_createResultHandlerSingle(options))
|
||||||
|
|
||||||
|
getAllFromQuery: (qbFunc, options = {}) ->
|
||||||
|
@_fromQuery(qbFunc)
|
||||||
|
.query()
|
||||||
|
.then(@_createResultHandler(options))
|
||||||
|
|
||||||
|
countFromQuery: (qbFunc, options = {}) ->
|
||||||
|
@_fromQuery(qbFunc)
|
||||||
|
.query()
|
||||||
|
.count("#{@options.idAttribute} as CNT")
|
||||||
|
.then(@_createResultHandlerCount(options))
|
||||||
|
|
||||||
|
getAll: (options = {}) ->
|
||||||
|
@_all()
|
||||||
|
.query()
|
||||||
|
.then(@_createResultHandler(options))
|
||||||
|
|
||||||
|
countAll: (options = {}) ->
|
||||||
|
@_all()
|
||||||
|
.query()
|
||||||
|
.count("#{@options.idAttribute} as CNT")
|
||||||
|
.then(@_createResultHandlerCount(options))
|
@ -0,0 +1,66 @@
|
|||||||
|
Promise = require "bluebird"
|
||||||
|
|
||||||
|
_shallowClone = (obj) ->
|
||||||
|
newObject = {}
|
||||||
|
for key, value of obj
|
||||||
|
newObject[key] = value
|
||||||
|
return newObject
|
||||||
|
|
||||||
|
module.exports = class Record
|
||||||
|
constructor: ->
|
||||||
|
@_data = {}
|
||||||
|
@_savedData = {}
|
||||||
|
@_changedData = {}
|
||||||
|
|
||||||
|
_setModel: (model) ->
|
||||||
|
self = this
|
||||||
|
|
||||||
|
@_model = model
|
||||||
|
|
||||||
|
if model.options.columns?
|
||||||
|
model.options.columns.forEach (column) =>
|
||||||
|
Object.defineProperty this, column,
|
||||||
|
get: -> self.get column
|
||||||
|
set: (value) -> self.set column, value
|
||||||
|
|
||||||
|
_setData: (data) ->
|
||||||
|
# We might need a deep clone here?
|
||||||
|
@_data = _shallowClone(data)
|
||||||
|
@_savedData = _shallowClone(data)
|
||||||
|
|
||||||
|
_loadRelations: (relations, data) ->
|
||||||
|
Promise.map relations, (relation) =>
|
||||||
|
{key: relation, value: @_model.options.relations[relation]}
|
||||||
|
.reduce ((obj, relationData) =>
|
||||||
|
obj[relationData.key] = relationData.value
|
||||||
|
return obj
|
||||||
|
), {}
|
||||||
|
.then (relations) =>
|
||||||
|
@_model._getRelations(relations, data)
|
||||||
|
.then (relations) =>
|
||||||
|
for attribute, record of relations
|
||||||
|
this[attribute] = record
|
||||||
|
|
||||||
|
return this
|
||||||
|
|
||||||
|
_saveAttributes: (attributes) ->
|
||||||
|
null # do stuff
|
||||||
|
|
||||||
|
# Upon success...
|
||||||
|
@_savedData = @_data
|
||||||
|
@_changedData = {}
|
||||||
|
|
||||||
|
get: (attribute) ->
|
||||||
|
return @_data[attribute]
|
||||||
|
|
||||||
|
set: (attribute, value) ->
|
||||||
|
@_data[attribute] = value
|
||||||
|
@_changedData[attribute] = value
|
||||||
|
|
||||||
|
save: ->
|
||||||
|
# This only saves the changed attributes - it is almost always what you want.
|
||||||
|
@_saveAttributes(@_changedData)
|
||||||
|
|
||||||
|
saveAll: ->
|
||||||
|
# This saves *all* the attributes as they are currently set in the object - even if something else has changed them in the database in the meantime. You probably don't need this.
|
||||||
|
@_saveAttributes(@_data)
|
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "yaorm",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Yet Another ORM based on Knex.",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git@git.cryto.net:projects/joepie91/node-yaorm"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"orm",
|
||||||
|
"knex",
|
||||||
|
"database",
|
||||||
|
"mysql",
|
||||||
|
"postgresql",
|
||||||
|
"sqlite"
|
||||||
|
],
|
||||||
|
"author": "Sven Slootweg",
|
||||||
|
"license": "WTFPL",
|
||||||
|
"dependencies": {
|
||||||
|
"bluebird": "^2.9.22",
|
||||||
|
"knex": "^0.7.6"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"pretty-error": "^1.1.1"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
require("pretty-error").start().skipPackage("bluebird", "coffee-script").skipNodeFiles()
|
||||||
|
|
||||||
|
Promise = require "bluebird"
|
||||||
|
#Promise.longStackTraces()
|
||||||
|
|
||||||
|
yaorm = require("./")({
|
||||||
|
driver: "pg"
|
||||||
|
hostname: "localhost"
|
||||||
|
username: "postgres"
|
||||||
|
database: "team"
|
||||||
|
})
|
||||||
|
|
||||||
|
yaorm.defineModel "User",
|
||||||
|
tableName: "users"
|
||||||
|
columns: ["id", "username", "display_name", "email_address", "hash", "external_identifier", "external_authentication", "created_at", "updated_at", "activated", "activation_key"]
|
||||||
|
relations:
|
||||||
|
"projects": yaorm.hasMany("Project", foreignKey: "owner_id")
|
||||||
|
|
||||||
|
yaorm.defineModel "Project",
|
||||||
|
tableName: "projects"
|
||||||
|
columns: ["id", "owner_id", "public", "name", "slug", "description", "created_at", "updated_at", "default_permissions"]
|
||||||
|
relations:
|
||||||
|
"owner": yaorm.belongsTo("User", foreignKey: "owner_id")
|
||||||
|
|
||||||
|
Promise.try ->
|
||||||
|
yaorm.model("Project").getAll(relations: "owner")
|
||||||
|
.map (project) -> [project.name, project.owner.display_name]
|
||||||
|
.then (projects) ->
|
||||||
|
console.log projects
|
||||||
|
.catch (err) ->
|
||||||
|
console.log err.stack
|
||||||
|
|
||||||
|
### TODO:
|
||||||
|
* Nested relations
|
||||||
|
* More complex relations (many-to-many?)
|
||||||
|
* Save (UPDATE + INSERT)
|
||||||
|
* Deep save (incl. nested relations)
|
||||||
|
###
|
Loading…
Reference in New Issue