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