├── .gitattributes ├── test ├── lib │ ├── helper.js │ ├── Category.js │ ├── Article.js │ ├── database.js │ ├── User.js │ └── data.js ├── schema │ ├── migration.js │ ├── scope.js │ ├── types.js │ ├── hooks.js │ ├── relation.js │ ├── validation.js │ └── queries.js ├── before.js ├── unit │ ├── user.js │ ├── category.js │ └── article.js └── model │ ├── article-promised.js │ ├── user.js │ ├── category.js │ └── article.js ├── .jshintignore ├── media ├── memory.png ├── mysql.png ├── neo4j.png ├── redis.png ├── riak.png ├── sqlite.png ├── arangodb.png ├── couchdb.png ├── firebird.png ├── mariadb.png ├── mongodb.png ├── tingodb.png ├── cassandra.png ├── postgresql.png └── rethinkdb.png ├── .npmignore ├── .gitignore ├── tea.yaml ├── .jshintrc ├── .editorconfig ├── index.js ├── LICENSE ├── lib ├── hookable.js ├── solr.js ├── adapters │ ├── http.js │ ├── memory.js │ ├── riak.js │ ├── tingodb.js │ ├── couchbase.js │ ├── nano.js │ └── mongodb.js ├── list.js ├── utils.js ├── query.js └── sql.js ├── Makefile ├── .travis.yml ├── .gitlab-ci.yml ├── package.json └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /test/lib/helper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Alex on 12/18/2015. 3 | */ 4 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | media 3 | support 4 | tmp 5 | db 6 | coverage -------------------------------------------------------------------------------- /media/memory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biggora/caminte/HEAD/media/memory.png -------------------------------------------------------------------------------- /media/mysql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biggora/caminte/HEAD/media/mysql.png -------------------------------------------------------------------------------- /media/neo4j.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biggora/caminte/HEAD/media/neo4j.png -------------------------------------------------------------------------------- /media/redis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biggora/caminte/HEAD/media/redis.png -------------------------------------------------------------------------------- /media/riak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biggora/caminte/HEAD/media/riak.png -------------------------------------------------------------------------------- /media/sqlite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biggora/caminte/HEAD/media/sqlite.png -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.bak 2 | *.log 3 | .idea 4 | node_modules 5 | db 6 | tmp 7 | coverage 8 | -------------------------------------------------------------------------------- /media/arangodb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biggora/caminte/HEAD/media/arangodb.png -------------------------------------------------------------------------------- /media/couchdb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biggora/caminte/HEAD/media/couchdb.png -------------------------------------------------------------------------------- /media/firebird.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biggora/caminte/HEAD/media/firebird.png -------------------------------------------------------------------------------- /media/mariadb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biggora/caminte/HEAD/media/mariadb.png -------------------------------------------------------------------------------- /media/mongodb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biggora/caminte/HEAD/media/mongodb.png -------------------------------------------------------------------------------- /media/tingodb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biggora/caminte/HEAD/media/tingodb.png -------------------------------------------------------------------------------- /media/cassandra.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biggora/caminte/HEAD/media/cassandra.png -------------------------------------------------------------------------------- /media/postgresql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biggora/caminte/HEAD/media/postgresql.png -------------------------------------------------------------------------------- /media/rethinkdb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biggora/caminte/HEAD/media/rethinkdb.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.bak 2 | *.log 3 | /.idea/ 4 | /node_modules/ 5 | /db/ 6 | /tmp/ 7 | /coverage/ 8 | -------------------------------------------------------------------------------- /tea.yaml: -------------------------------------------------------------------------------- 1 | # https://tea.xyz/what-is-this-file 2 | --- 3 | version: 1.0.0 4 | codeOwners: 5 | - '0xdC30512ad161D6D26959cC706916Ae515d97fB26' 6 | quorum: 1 7 | -------------------------------------------------------------------------------- /test/schema/migration.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Alex on 12/18/2015. 3 | */ 4 | 5 | /* 6 | forEachProperty: [Function], 7 | registerProperty: [Function], 8 | defineProperty: [Function], 9 | */ -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": false, 6 | "curly": false, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 2, 10 | "latedef": true, 11 | "newcap": true, 12 | "noarg": true, 13 | "quotmark": "single", 14 | "regexp": true, 15 | "undef": true, 16 | "unused": true, 17 | "strict": false, 18 | "trailing": true, 19 | "smarttabs": true, 20 | "jquery": true, 21 | "white": true, 22 | "multistr": true 23 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = space 11 | indent_size = 4 12 | 13 | # We recommend you to keep these unchanged 14 | end_of_line = lf 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | 22 | # Tab indentation (no size specified) 23 | [Makefile] 24 | indent_style = tab 25 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | */ 4 | var schema = require('./lib/schema'); 5 | var pkg = require('./package'); 6 | var abc = require('./lib/abstract-class'); 7 | var vld = require('./lib/validatable'); 8 | var Schema = schema.Schema; 9 | 10 | exports.schema = {models:{}}; 11 | exports.Schema = Schema; 12 | exports.AbstractClass = abc.AbstractClass; 13 | exports.Validatable = vld.Validatable; 14 | exports.__defineGetter__('BaseSQL', function () { 15 | return require('./lib/sql'); 16 | }); 17 | 18 | exports.init = function (trinte) { 19 | if (global.trinte) { 20 | global.trinte.orm = exports; 21 | } else { 22 | trinte.orm = {Schema: exports.Schema, AbstractClass: exports.AbstractClass}; 23 | } 24 | }; 25 | 26 | exports.model = function (name){ 27 | return this.schema.models[name.toLowerCase()]; 28 | }; 29 | 30 | exports.version = pkg.version; 31 | 32 | exports.__defineGetter__('test', function () { 33 | return require('./tmp/tests/common_test'); 34 | }); 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011 Anatoliy Chakkaev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /test/before.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Order tests 3 | * 4 | * Created by create caminte-cli script 5 | * App based on CaminteJS 6 | * CaminteJS homepage http://www.camintejs.com 7 | **/ 8 | 9 | var fs = require('fs'); 10 | var dbDir = './db'; 11 | var onlyJs = function(file) { 12 | return /\.js$/.test(file); 13 | }; 14 | 15 | /* create dir */ 16 | var dstat, tstat; 17 | try { 18 | dstat = fs.statSync(dbDir); 19 | } catch(err) {} 20 | if(!dstat) { fs.mkdirSync(dbDir,'0755'); } 21 | try { 22 | tstat = fs.statSync(dbDir + '/test'); 23 | } catch(err) {} 24 | if(!tstat) { fs.mkdirSync(dbDir + '/test','0755'); } 25 | 26 | /* units tests */ 27 | var units = fs.readdirSync(__dirname+'/unit').filter(onlyJs); 28 | 29 | units.forEach(function(unit){ 30 | require('./unit/' + unit); 31 | }); 32 | 33 | /* models tests */ 34 | var models = fs.readdirSync(__dirname+'/model').filter(onlyJs); 35 | 36 | models.forEach(function(model){ 37 | require('./model/' + model); 38 | }); 39 | 40 | /* schema tests */ 41 | var routes = fs.readdirSync(__dirname+'/schema').filter(onlyJs); 42 | 43 | routes.forEach(function(route){ 44 | require('./schema/' + route); 45 | }); -------------------------------------------------------------------------------- /lib/hookable.js: -------------------------------------------------------------------------------- 1 | exports.Hookable = Hookable; 2 | 3 | function Hookable() { 4 | // hookable class 5 | }; 6 | 7 | Hookable.afterInitialize = null; 8 | Hookable.beforeValidation = null; 9 | Hookable.afterValidation = null; 10 | Hookable.beforeSave = null; 11 | Hookable.afterSave = null; 12 | Hookable.beforeCreate = null; 13 | Hookable.afterCreate = null; 14 | Hookable.beforeUpdate = null; 15 | Hookable.afterUpdate = null; 16 | Hookable.beforeDestroy = null; 17 | Hookable.afterDestroy = null; 18 | 19 | Hookable.prototype.trigger = function trigger(actionName, work, data) { 20 | var capitalizedName = capitalize(actionName); 21 | var afterHook = this.constructor['after' + capitalizedName]; 22 | var beforeHook = this.constructor['before' + capitalizedName]; 23 | var inst = this; 24 | 25 | // we only call 'before' hook when we have actual action (work) to perform 26 | if (work) { 27 | if (beforeHook) { 28 | // before hook should be called on instance with one param: callback 29 | beforeHook.call(inst, function () { 30 | // actual action also have one param: callback 31 | work.call(inst, next); 32 | }, data); 33 | } else { 34 | work.call(inst, next); 35 | } 36 | } else { 37 | next(); 38 | } 39 | 40 | function next(done) { 41 | if (afterHook) { 42 | afterHook.call(inst, done); 43 | } else if (done) { 44 | done.call(this); 45 | } 46 | } 47 | }; 48 | 49 | function capitalize(string) { 50 | return string.charAt(0).toUpperCase() + string.slice(1); 51 | } 52 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ISTANBUL = `which istanbul` 2 | MOCHA = `which _mocha` 3 | MOCHA_REPORTER = spec 4 | MOCHA_RUN = $(MOCHA) -r should -R $(MOCHA_REPORTER) 5 | MOCHA_WATCH = $(MOCHA) -r should -R $(MOCHA_REPORTER) -w 6 | MOCHA_COV = $(ISTANBUL) cover $(MOCHA) -- -r should -u exports -R spec 7 | JSHINT = $(which jshint) 8 | 9 | check: 10 | @jshint ./lib 11 | 12 | test-units: 13 | @NODE_ENV=test $(MOCHA) test/units -r should -R $(MOCHA_REPORTER) --exit 14 | 15 | test-models: 16 | @NODE_ENV=test $(MOCHA) test/models -r should -R $(MOCHA_REPORTER) --exit 17 | 18 | test-watch: 19 | @NODE_ENV=test $(MOCHA_WATCH) 20 | 21 | test-cov: clear 22 | @NODE_ENV=test $(MOCHA_COV) 23 | 24 | test-mysql: 25 | @CAMINTE_DRIVER=mysql $(MOCHA) --timeout 3000 -r should -R $(MOCHA_REPORTER) --exit 26 | 27 | test-sqlite: 28 | @CAMINTE_DRIVER=sqlite $(MOCHA) --timeout 3000 -r should -R $(MOCHA_REPORTER) --exit 29 | 30 | test-postgres: 31 | @CAMINTE_DRIVER=postgres $(MOCHA) --timeout 3000 -r should -R $(MOCHA_REPORTER) --exit 32 | 33 | test-redis: 34 | @CAMINTE_DRIVER=redis $(MOCHA) --timeout 3000 -r should -R $(MOCHA_REPORTER) --exit 35 | 36 | test-mongo: 37 | @CAMINTE_DRIVER=mongo $(MOCHA) --timeout 3000 -r should -R $(MOCHA_REPORTER) --exit 38 | 39 | test-tingo: 40 | @CAMINTE_DRIVER=tingo $(MOCHA) --timeout 5000 -r should -R $(MOCHA_REPORTER) --exit 41 | 42 | test-rethinkdb: 43 | @CAMINTE_DRIVER=rethinkdb $(MOCHA) --timeout 5000 -r should -R $(MOCHA_REPORTER) --exit 44 | 45 | test-arango: 46 | @CAMINTE_DRIVER=arango $(MOCHA) --timeout 5000 -r should -R $(MOCHA_REPORTER) --exit 47 | 48 | test-neo4j: 49 | @CAMINTE_DRIVER=neo4j $(MOCHA) --timeout 5000 -r should -R $(MOCHA_REPORTER) --exit 50 | 51 | test: test-sqlite test-mysql test-mongo test-redis 52 | 53 | clear: 54 | @rm -rf coverage 55 | 56 | .PHONY: test 57 | .PHONY: doc 58 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: required 3 | node_js: 4 | - 4.5 5 | - 6.5 6 | - 7.0 7 | 8 | env: 9 | - NEO4J_AUTH="none" 10 | 11 | services: 12 | - mysql 13 | - postgresql 14 | - redis-server 15 | - mongodb 16 | 17 | before_script: 18 | - sudo apt-get update -o Acquire::http::No-Cache=True 19 | - sudo apt-get install -y apt-transport-https libgoogle-perftools4 20 | # configure mysql 21 | - mysql -e "CREATE USER 'test'@'%' IDENTIFIED BY 'test';" -uroot 22 | - mysql -e "CREATE DATABASE IF NOT EXISTS test;" -uroot 23 | - mysql -e "GRANT ALL PRIVILEGES ON test . * TO 'test'@'%';" -uroot 24 | - mysql -e "FLUSH PRIVILEGES;" -uroot 25 | - sleep 2 26 | # configure postgres 27 | - psql -c "CREATE ROLE test LOGIN ENCRYPTED PASSWORD 'test' NOINHERIT VALID UNTIL 'infinity';" -U postgres 28 | - psql -c "CREATE DATABASE test WITH ENCODING='UTF8' OWNER=test;" -U postgres 29 | - sleep 2 30 | # configure mongodb 31 | - mongo test --eval 'db.createUser({user:"test", pwd:"test", roles:["readWrite"]});' 32 | # add and configure rethinkdb 33 | - curl http://download.rethinkdb.com/apt/pubkey.gpg | sudo apt-key add - 34 | - echo "deb http://download.rethinkdb.com/apt $(lsb_release -cs) main" | sudo tee -a /etc/apt/sources.list.d/rethinkdb.list 35 | - sudo apt-get update -q 36 | - sudo apt-get install rethinkdb -y 37 | - sudo cp /etc/rethinkdb/default.conf.sample /etc/rethinkdb/instances.d/default.conf 38 | - sudo service rethinkdb restart 39 | # add arangodb 40 | - curl -O https://download.arangodb.com/arangodb2/xUbuntu_$(lsb_release -rs)/amd64/arangodb_2.8.11_amd64.deb 41 | - sudo dpkg -i arangodb_2.8.11_amd64.deb 42 | # start neo4j 43 | - sudo $(which neo4j) start 44 | - netstat -antp | grep LISTEN 45 | -------------------------------------------------------------------------------- /test/lib/Category.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Category schema 3 | * 4 | * Created by create caminte-cli script 5 | * App based on CaminteJS 6 | * CaminteJS homepage http://www.camintejs.com 7 | **/ 8 | 9 | /** 10 | * Define Category Model 11 | * @param {Object} schema 12 | * @return {Object} 13 | **/ 14 | module.exports = function (schema) { 15 | var Category = schema.define('category', { 16 | active: {type: schema.Number, 'default': 0, limit: 1, index: true}, 17 | section: {type: schema.String, limit: 20, 'default': "product", index: true}, 18 | language: {type: schema.String, limit: 5, 'default': "en", index: true}, 19 | title: {type: schema.String, limit: 155}, 20 | description: {type: schema.String, limit: 255}, 21 | translation: {type: schema.Text}, 22 | category_id: {type: schema.Number, 'default': 0, limit: 11, index: true}, 23 | sort_order: {type: schema.Number, limit: 11, 'default': 1}, 24 | image_source: {type: schema.String, limit: 255}, 25 | image_thumbs: {type: schema.Text}, 26 | meta_keys: {type: schema.String, limit: 155}, 27 | meta_desc: {type: schema.String, limit: 155}, 28 | childs: {type: schema.Number, limit: 11}, 29 | create_id: {type: schema.Number, limit: 21}, 30 | modify_id: {type: schema.Number, limit: 21}, 31 | create_ts: {type: schema.Date, 'default': Date.now}, 32 | modify_ts: {type: schema.Date} 33 | }, {}); 34 | /* Validators */ 35 | Category.validatesPresenceOf('title', 'section'); 36 | Category.validatesLengthOf('title', {min: 5, message: {min: 'title is too short'}}); 37 | Category.validatesLengthOf('section', {min: 5, message: {min: 'section is too short'}}); 38 | Category.validatesInclusionOf('language', {in: ['en', 'ru', 'lv', 'es']}); 39 | Category.validatesNumericalityOf('category_id', {int: true}); 40 | /* Scopes */ 41 | Category.scope('published', {active: 1}); 42 | Category.scope('hidden', {active: 0}); 43 | Category.scope('products', {section: 'product'}); 44 | 45 | return Category; 46 | }; -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: biggora/gitlab-zone 2 | # services: 3 | # - arangodb 4 | # - neo4j 5 | 6 | variables: 7 | MYSQL_USER: "test" 8 | MYSQL_PASSWORD: "test" 9 | MYSQL_DATABASE: "test" 10 | MYSQL_ALLOW_EMPTY_PASSWORD: "yes" 11 | POSTGRES_USER: "test" 12 | POSTGRES_PASSWORD: "test" 13 | POSTGRES_DB: "test" 14 | ARANGO_ROOT_PASSWORD: "test" 15 | ARANGO_NO_AUTH: "1" 16 | ARANGO_RANDOM_ROOT_PASSWORD: "0" 17 | 18 | before_script: 19 | - cat /etc/hosts | grep 172 20 | 21 | stages: 22 | - test 23 | 24 | redis: 25 | services: 26 | - redis 27 | stage: test 28 | script: 29 | - npm install bluebird mocha should redis 30 | - make test-redis 31 | only: 32 | - master 33 | tags: 34 | - node 35 | 36 | sqlite: 37 | stage: test 38 | script: 39 | - npm install bluebird mocha should sqlite3 40 | - make test-sqlite 41 | only: 42 | - master 43 | tags: 44 | - node 45 | 46 | mysql: 47 | services: 48 | - mysql 49 | stage: test 50 | script: 51 | - npm install bluebird mocha should mysql 52 | - make test-mysql 53 | only: 54 | - master 55 | tags: 56 | - node 57 | 58 | postgres: 59 | services: 60 | - postgres 61 | stage: test 62 | script: 63 | - npm install bluebird mocha should pg 64 | - make test-postgres 65 | only: 66 | - master 67 | tags: 68 | - node 69 | 70 | mongo: 71 | services: 72 | - mongo 73 | stage: test 74 | script: 75 | - npm install bluebird mocha should mongodb 76 | - make test-mongo 77 | only: 78 | - master 79 | tags: 80 | - node 81 | 82 | arango: 83 | services: 84 | - arangodb 85 | stage: test 86 | script: 87 | - npm install bluebird mocha should arangojs 88 | - make test-arango 89 | only: 90 | - master 91 | tags: 92 | - node 93 | 94 | rethinkdb: 95 | services: 96 | - rethinkdb 97 | stage: test 98 | script: 99 | - npm install bluebird mocha should async generic-pool rethinkdb moment 100 | - make test-rethinkdb 101 | only: 102 | - master 103 | tags: 104 | - node 105 | -------------------------------------------------------------------------------- /test/lib/Article.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Article schema 3 | * 4 | * Created by create caminte-cli script 5 | * App based on CaminteJS 6 | * CaminteJS homepage http://www.camintejs.com 7 | **/ 8 | 9 | /** 10 | * Define Article Model 11 | * @param {Object} schema 12 | * @return {Object} 13 | **/ 14 | module.exports = function (schema) { 15 | var Article = schema.define('article', { 16 | active: {type: schema.Number, limit: 1, default: 0, index: true}, 17 | mainpage: {type: schema.Number, limit: 1, index: true}, 18 | featured: {type: schema.Boolean, default: true, index: true}, 19 | language: {type: schema.String, limit: 5, default: "en", index: true}, 20 | category_id: {type: schema.Number, limit: 11, default: 0, index: true}, 21 | title: {type: schema.String, limit: 155, index: true}, 22 | alias: {type: schema.String, limit: 155, index: true}, 23 | content_short: {type: schema.Text}, 24 | content_full: {type: schema.Text}, 25 | image_source: {type: schema.String, limit: 255}, 26 | image_thumbs: {type: schema.Text}, 27 | video_source: {type: schema.String, limit: 255}, 28 | video_thumbs: {type: schema.Text}, 29 | template: {type: schema.String, limit: 255, default: "default"}, 30 | params: {type: schema.Json}, 31 | longitude: {type: schema.Double}, 32 | latitude: {type: schema.Real}, 33 | price: {type: schema.Float}, 34 | create_ts: {type: schema.Date, default: Date.now}, 35 | modify_ts: {type: schema.Date}, 36 | create_id: {type: schema.Number, limit: 21, index: true}, 37 | modify_id: {type: schema.Number, limit: 21, index: true}, 38 | meta_keys: {type: schema.Json}, 39 | meta_desc: {type: schema.String, limit: 155} 40 | }, {}); 41 | 42 | /* Validators */ 43 | Article.validatesPresenceOf('title', 'alias'); 44 | Article.validatesLengthOf('title', {min: 5, message: {min: 'title is too short'}}); 45 | Article.validatesInclusionOf('language', {in: ['en', 'ru']}); 46 | Article.validatesNumericalityOf('category_id', {int: true}); 47 | Article.validatesUniquenessOf('alias', {message: 'alias is not unique'}); 48 | 49 | return Article; 50 | }; -------------------------------------------------------------------------------- /test/schema/scope.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Scope and Custom methods Test 3 | * Created by caminte-cli script 4 | **/ 5 | /*global 6 | describe, before, after, it 7 | */ 8 | if (!process.env.NODE_ENV) { 9 | process.env.NODE_ENV = 'test'; 10 | } 11 | 12 | var driver = process.env.CAMINTE_DRIVER || 'sqlite'; 13 | var should = require('should'); 14 | var caminte = require('../../'); 15 | var config = require('./../lib/database'); 16 | var samples = require('./../lib/data'); 17 | var dbConf = config[driver]; 18 | var categoryModel = require('./../lib/Category'); 19 | var Schema = caminte.Schema; 20 | dbConf.host = process.env.DB_HOST || dbConf.host || ''; 21 | var schema = new Schema(dbConf.driver, dbConf); 22 | var Category = categoryModel(schema); 23 | // mocha test/schema/scope.js 24 | /** 25 | * Simple tests for the Category model 26 | */ 27 | describe(driver + ' - schema scope:', function () { 28 | 'use strict'; 29 | var category, newCategory = samples.categories[0]; 30 | 31 | before(function (done) { 32 | setTimeout(function(){ 33 | category = new Category(newCategory); 34 | schema.autoupdate(function () { 35 | category.save(function () { 36 | return done && done(); 37 | }); 38 | }); 39 | }, 500); 40 | }); 41 | 42 | after(function (done) { 43 | Category.destroyAll(function(){ 44 | return done && done(); 45 | }); 46 | }); 47 | 48 | describe('#scope', function () { 49 | 50 | it('#published', function (done) { 51 | Category.should.be.have.property('published'); 52 | Category.scope.should.be.type('function'); 53 | Category.published({}, function (err, founds) { 54 | should.not.exist(err); 55 | founds.should.length(0); 56 | done(); 57 | }); 58 | }); 59 | 60 | it('#hidden', function (done) { 61 | Category.should.be.have.property('published'); 62 | Category.scope.should.be.type('function'); 63 | 64 | Category.hidden({}, function (err, founds) { 65 | should.not.exist(err); 66 | founds.should.length(1); 67 | done(); 68 | }); 69 | }); 70 | 71 | it('#products', function (done) { 72 | Category.should.be.have.property('products'); 73 | Category.scope.should.be.type('function'); 74 | 75 | Category.products({}, function (err, founds) { 76 | should.not.exist(err); 77 | founds.should.length(0); 78 | done(); 79 | }); 80 | }); 81 | }); 82 | 83 | }); 84 | -------------------------------------------------------------------------------- /test/schema/types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Alex on 12/18/2015. 3 | */ 4 | /*global 5 | describe, before, after, it 6 | */ 7 | if (!process.env.NODE_ENV) { 8 | process.env.NODE_ENV = 'test'; 9 | } 10 | var driver = process.env.CAMINTE_DRIVER || 'sqlite'; 11 | var should = require('should'); 12 | var caminte = require('../../'); 13 | var config = require('./../lib/database'); 14 | var samples = require('./../lib/data'); 15 | var dbConf = config[driver]; 16 | var articleModel = require('./../lib/Article'); 17 | var Schema = caminte.Schema; 18 | dbConf.host = process.env.DB_HOST || dbConf.host || ''; 19 | var schema = new Schema(dbConf.driver, dbConf); 20 | var Article = articleModel(schema); 21 | 22 | describe(driver + ' - schema types:', function () { 23 | 'use strict'; 24 | var id, article, newArticle = samples.articles[1]; 25 | 26 | before(function (done) { 27 | setTimeout(function(){ 28 | schema.autoupdate(function () { 29 | Article.create(newArticle, function (err, created) { 30 | id = created.id; 31 | Article.findById(id, function (err, found) { 32 | article = found; 33 | return done && done(); 34 | }); 35 | }); 36 | }); 37 | }, 500); 38 | }); 39 | 40 | after(function (done) { 41 | Article.destroyAll(done); 42 | }); 43 | 44 | it("must be a String", function () { 45 | article.language.should.be.String; 46 | }); 47 | 48 | it("must be a Text", function () { 49 | article.content_short.should.be.String; 50 | // article.content_short.should.be.length(); 51 | }); 52 | 53 | it("must be a Boolean", function () { 54 | article.featured.should.be.Boolean; 55 | }); 56 | 57 | it("must be a Number", function () { 58 | article.active.should.be.Number; 59 | }); 60 | 61 | it("must be a Double", function () { 62 | article.longitude.should.be.Double; 63 | }); 64 | 65 | it("must be a Float", function () { 66 | article.longitude.should.be.Float; 67 | }); 68 | 69 | it("must be a Real", function () { 70 | article.latitude.should.be.Double; 71 | }); 72 | 73 | it("must be a Date", function () { 74 | article.create_ts.should.be.Object; 75 | article.create_ts.toISOString.should.be.Function; 76 | }); 77 | 78 | describe('json type', function () { 79 | it("must be Object", function () { 80 | article.params.should.be.Object; 81 | article.params.should.be.have.property('title'); 82 | }); 83 | it("must be Array", function () { 84 | article.meta_keys.should.be.Array; 85 | }); 86 | }); 87 | 88 | }); 89 | -------------------------------------------------------------------------------- /test/lib/database.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Default database configuration file 3 | * 4 | * Created by create caminte-cli script 5 | * App based on CaminteJS 6 | * CaminteJS homepage http://www.camintejs.com 7 | * 8 | * docs: https://github.com/biggora/caminte/wiki/Connecting-to-DB#connecting 9 | **/ 10 | // var travis = process.env.TRAVIS; 11 | var gitlab = process.env.GITLAB_CI; 12 | 13 | module.exports.memory = { 14 | driver : ':memory:' 15 | }; 16 | 17 | module.exports.sqlite = { 18 | driver : 'sqlite3', 19 | database : './db/test.db' 20 | }; 21 | 22 | module.exports.mysql = { 23 | driver : 'mysql', 24 | host : gitlab ? 'mysql' : '127.0.0.1', 25 | port : '3306', 26 | username : 'test', 27 | password : 'test', 28 | database : 'test', 29 | autoReconnect : true 30 | }; 31 | 32 | module.exports.mariadb = { 33 | driver : 'mysql', 34 | host : gitlab ? 'mariadb' : '127.0.0.1', 35 | port : '3306', 36 | username : 'test', 37 | password : 'test', 38 | database : 'test', 39 | autoReconnect : true 40 | }; 41 | 42 | module.exports.postgres = { 43 | driver : 'postgres', 44 | host : gitlab ? 'postgres' : '127.0.0.1', 45 | port : '5432', 46 | username : 'test', 47 | password : 'test', 48 | database : 'test' 49 | }; 50 | 51 | module.exports.firebird = { 52 | driver : 'firebird', 53 | host : gitlab ? 'firebird' : '127.0.0.1', 54 | port : '3050', 55 | username : 'test', 56 | password : 'test', 57 | database : 'test.fdb' 58 | }; 59 | 60 | module.exports.redis = { 61 | driver : 'redis', 62 | host : gitlab ? 'redis' : '127.0.0.1', 63 | port : '6379', 64 | // username : 'test', 65 | // password : 'test', 66 | database : 'test' 67 | }; 68 | 69 | module.exports.mongo = { 70 | driver : 'mongo', 71 | host : gitlab ? 'mongo' : '127.0.0.1', 72 | port : '27017', 73 | database : 'test' 74 | }; 75 | 76 | module.exports.tingo = { 77 | driver : 'tingodb', 78 | database : './db/test' 79 | }; 80 | 81 | module.exports.rethinkdb = { 82 | driver : 'rethinkdb', 83 | host : gitlab ? 'rethinkdb' : '127.0.0.1', 84 | port : '28015', 85 | database : 'test' 86 | }; 87 | 88 | module.exports.neo4j = { 89 | driver : 'neo4j', 90 | host : gitlab ? 'neo4j' : '127.0.0.1', 91 | port : '7474', 92 | database : 'test' 93 | }; 94 | 95 | module.exports.arango = { 96 | driver : 'arango', 97 | host : gitlab ? 'arangodb' : '127.0.0.1', 98 | port : '8529', 99 | database : 'test' 100 | }; 101 | -------------------------------------------------------------------------------- /lib/solr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Alex on 5/24/2014. 3 | */ 4 | 5 | exports.toSolr = function toSolr(params) { 6 | params = params ? params : {}; 7 | 8 | Object.keys(params).forEach(function(key){ 9 | console.log(key); 10 | }); 11 | 12 | try { 13 | 14 | } catch (e) { 15 | // console.log(e) 16 | 17 | } 18 | }; 19 | 20 | exports.fromSolr = function fromSolr(str) { 21 | 22 | }; 23 | 24 | exports.__applyFilter = function(filter) { 25 | var self = this; 26 | if (typeof filter.where === 'function') { 27 | return filter.where; 28 | } 29 | var keys = Object.keys(filter.where); 30 | return function(obj) { 31 | var pass = []; 32 | keys.forEach(function(key) { 33 | if (typeof filter.where[key] === 'object' && !filter.where[key].getTime) { 34 | pass.push(self.parseCond(obj[key], filter.where[key])); 35 | } else { 36 | pass.push(key + ':' + filter.where[key]); 37 | } 38 | }); 39 | return pass; 40 | }; 41 | }; 42 | 43 | exports.__parseCond = function(val, conds) { 44 | var outs = false; 45 | Object.keys(conds).forEach(function(condType) { 46 | switch (condType) { 47 | case 'gt': 48 | outs = val > conds[condType] ? true : false; 49 | break; 50 | case 'gte': 51 | outs = val >= conds[condType] ? true : false; 52 | break; 53 | case 'lt': 54 | outs = val < conds[condType] ? true : false; 55 | break; 56 | case 'lte': 57 | outs = val <= conds[condType] ? true : false; 58 | break; 59 | case 'between': 60 | // need 61 | outs = val !== conds[condType] ? true : false; 62 | break; 63 | case 'inq': 64 | case 'in': 65 | conds[condType].forEach(function(cval) { 66 | if (val === cval) { 67 | outs = true; 68 | } 69 | }); 70 | break; 71 | case 'nin': 72 | conds[condType].forEach(function(cval) { 73 | if (val === cval) { 74 | outs = false; 75 | } 76 | }); 77 | break; 78 | case 'neq': 79 | case 'ne': 80 | outs = val !== conds[condType] ? true : false; 81 | break; 82 | case 'regex': 83 | case 'like': 84 | outs = new RegExp(conds[condType]).test(val); 85 | break; 86 | case 'nlike': 87 | outs = !new RegExp(conds[condType]).test(val); 88 | break; 89 | default: 90 | outs = val === conds[condType] ? true : false; 91 | break; 92 | } 93 | }); 94 | return outs; 95 | }; -------------------------------------------------------------------------------- /test/lib/User.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User schema 3 | * 4 | * Created by create caminte-cli script 5 | * App based on CaminteJS 6 | * CaminteJS homepage http://www.camintejs.com 7 | **/ 8 | 9 | /** 10 | * Define User Model 11 | * @param {Object} schema 12 | * @return {Object} 13 | **/ 14 | module.exports = function (schema) { 15 | var User = schema.define('user', { 16 | active: {type: schema.Number, limit: 1, default: 0, index: true}, 17 | language: {type: schema.String, limit: 5, default: "en"}, 18 | provider: {type: schema.String, limit: 50, default: "password"}, 19 | middle_initial: {type: schema.String, limit: 10}, 20 | first_name: {type: schema.String, limit: 150, index: true}, 21 | last_name: {type: schema.String, limit: 150, index: true}, 22 | screen_name: {type: schema.String, limit: 150, index: true}, 23 | email: {type: schema.String, limit: 150, index: true}, 24 | account_type: {type: schema.Number, limit: 1}, 25 | gender: {type: schema.String, limit: 10, default: "male"}, 26 | birthday: {type: schema.Date}, 27 | age: {type: schema.Number, limit: 11}, 28 | salt: {type: schema.String, limit: 150}, 29 | password: {type: schema.String, limit: 250}, 30 | notes: {type: schema.Text}, 31 | image_source: {type: schema.String, limit: 255}, 32 | image_thumbs: {type: schema.Text}, 33 | terms: {type: schema.Number, limit: 1}, 34 | create_id: {type: schema.Number, limit: 1}, 35 | modify_id: {type: schema.Number, limit: 1}, 36 | expire_ts: {type: schema.Date}, 37 | create_ts: {type: schema.Date}, 38 | disable_ts: {type: schema.Date}, 39 | modify_ts: {type: schema.Date} 40 | }, {}); 41 | /* Validators */ 42 | User.validatesPresenceOf('first_name', 'email'); 43 | User.validatesLengthOf('password', {min: 5, message: {min: 'Password is too short'}}); 44 | User.validatesInclusionOf('language', {in: ['en', 'ru']}); 45 | User.validatesInclusionOf('gender', {in: ['male', 'female']}); 46 | User.validatesExclusionOf('screen_name', {in: ['admin', 'master']}); 47 | User.validatesFormatOf('screen_name', {with: /^\S+$/, message:"is not valid"}); 48 | User.validatesNumericalityOf('age', {int: true}); 49 | User.validatesUniquenessOf('email', {message: 'email is not unique'}); 50 | var userNameValidator = function (err) { 51 | if (this.first_name === 'bad') { err(); } 52 | }; 53 | var emailValidator = function(err){ 54 | if(!/^[-a-z0-9!#$%&'*+/=?^_`{|}~]+(?:\.[-a-z0-9!#$%&'*+/=?^_`{|}~]+)*@(?:[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?\.)*(?:aero|arpa|asia|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|[a-z][a-z])$/.test(this.email)) { err(); } 55 | }; 56 | User.validate('first_name', userNameValidator, {message: 'Bad first_name'}); 57 | User.validate('email', emailValidator, {message: 'Bad email'}); 58 | 59 | return User; 60 | }; -------------------------------------------------------------------------------- /test/unit/user.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User Unit Test 3 | * Created by caminte-cli script 4 | **/ 5 | /*global 6 | describe, before, after, it 7 | */ 8 | if (!process.env.NODE_ENV) { 9 | process.env.NODE_ENV = 'test'; 10 | } 11 | var driver = process.env.CAMINTE_DRIVER || 'sqlite'; 12 | var should = require('should'); 13 | var caminte = require('../../'); 14 | var config = require('./../lib/database'); 15 | var samples = require('./../lib/data'); 16 | var dbConf = config[driver]; 17 | var userModel = require('./../lib/User'); 18 | var Schema = caminte.Schema; 19 | dbConf.host = process.env.DB_HOST || dbConf.host || ''; 20 | var schema = new Schema(dbConf.driver, dbConf); 21 | var User = userModel(schema); 22 | 23 | /** 24 | * Simple tests for the Article model 25 | */ 26 | describe(driver + ' - User unit:', function () { 27 | 'use strict'; 28 | var user, id, newUser = samples.users[0]; 29 | 30 | before(function (done) { 31 | schema.autoupdate(function(){ 32 | return done && done(); 33 | }); 34 | }); 35 | 36 | after(function (done) { 37 | done(); 38 | }); 39 | 40 | describe('create', function () { 41 | 42 | user = new User(newUser); 43 | it('user should be object', function () { 44 | user.should.be.type('object'); 45 | }); 46 | 47 | it('must be valid', function (done) { 48 | user.isValid(function (valid) { 49 | valid.should.be.true; 50 | if (!valid) console.log(user.errors); 51 | done(); 52 | }); 53 | }); 54 | 55 | }); 56 | 57 | describe('save', function () { 58 | 59 | it('should be have #save', function () { 60 | user.should.be.have.property('save'); 61 | user.save.should.be.type('function'); 62 | }); 63 | 64 | it('must be saved', function (done) { 65 | user.save(function (err) { 66 | should.not.exist(err); 67 | user.should.be.have.property('id'); 68 | user.id.should.not.eql(null); 69 | id = user.id; 70 | done(); 71 | }); 72 | }); 73 | 74 | }); 75 | 76 | describe('updateAttributes', function () { 77 | 78 | it('should be have #updateAttributes', function () { 79 | user.should.be.have.property('updateAttributes'); 80 | user.updateAttributes.should.be.type('function'); 81 | }); 82 | 83 | it('must be updated', function (done) { 84 | user.updateAttributes({ 85 | screen_name: 'bigboss' 86 | }, function (err) { 87 | should.not.exist(err); 88 | 89 | done(); 90 | }); 91 | }); 92 | 93 | }); 94 | 95 | describe('destroy', function () { 96 | 97 | it('should be have #destroy', function () { 98 | user.should.be.have.property('destroy'); 99 | user.destroy.should.be.type('function'); 100 | }); 101 | 102 | it('must be destroyed', function (done) { 103 | user.destroy(function (err) { 104 | should.not.exist(err); 105 | done(); 106 | }); 107 | }); 108 | 109 | }); 110 | 111 | }); 112 | -------------------------------------------------------------------------------- /test/unit/category.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Category Unit Test 3 | * Created by caminte-cli script 4 | **/ 5 | /*global 6 | describe, before, after, it 7 | */ 8 | if (!process.env.NODE_ENV) { 9 | process.env.NODE_ENV = 'test'; 10 | } 11 | var driver = process.env.CAMINTE_DRIVER || 'sqlite'; 12 | var should = require('should'); 13 | var caminte = require('../../'); 14 | var config = require('./../lib/database'); 15 | var samples = require('./../lib/data'); 16 | var dbConf = config[driver]; 17 | var categoryModel = require('./../lib/Category'); 18 | var Schema = caminte.Schema; 19 | dbConf.host = process.env.DB_HOST || dbConf.host || ''; 20 | var schema = new Schema(dbConf.driver, dbConf); 21 | var Category = categoryModel(schema); 22 | 23 | /** 24 | * Simple tests for the Article model 25 | */ 26 | describe(driver + ' - Category unit:', function () { 27 | 'use strict'; 28 | var category, id, newCategory = samples.categories[0]; 29 | 30 | before(function (done) { 31 | schema.autoupdate(function(){ 32 | return done && done(); 33 | }); 34 | }); 35 | 36 | after(function (done) { 37 | done(); 38 | }); 39 | 40 | describe('create', function () { 41 | 42 | category = new Category(newCategory); 43 | it('category should be object', function () { 44 | category.should.be.type('object'); 45 | }); 46 | 47 | it('must be valid', function (done) { 48 | category.isValid(function (valid) { 49 | valid.should.be.true; 50 | if (!valid) console.log(category.errors); 51 | done(); 52 | }); 53 | }); 54 | 55 | }); 56 | 57 | describe('save', function () { 58 | 59 | it('should be have #save', function () { 60 | category.should.be.have.property('save'); 61 | category.save.should.be.type('function'); 62 | }); 63 | 64 | it('must be saved', function (done) { 65 | category.save(function (err) { 66 | should.not.exist(err); 67 | category.should.be.have.property('id'); 68 | category.id.should.not.eql(null); 69 | id = category.id; 70 | done(); 71 | }); 72 | }); 73 | 74 | }); 75 | 76 | describe('updateAttributes', function () { 77 | 78 | it('should be have #updateAttributes', function () { 79 | category.should.be.have.property('updateAttributes'); 80 | category.updateAttributes.should.be.type('function'); 81 | }); 82 | 83 | it('must be updated', function (done) { 84 | category.updateAttributes({ 85 | title: 'test 2' 86 | }, function (err) { 87 | should.not.exist(err); 88 | done(); 89 | }); 90 | }); 91 | 92 | }); 93 | 94 | describe('destroy', function () { 95 | 96 | it('should be have #destroy', function () { 97 | category.should.be.have.property('destroy'); 98 | category.destroy.should.be.type('function'); 99 | }); 100 | 101 | it('must be destroyed', function (done) { 102 | category.destroy(function (err) { 103 | should.not.exist(err); 104 | done(); 105 | }); 106 | }); 107 | 108 | }); 109 | 110 | }); 111 | -------------------------------------------------------------------------------- /test/unit/article.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Article Unit Test 3 | * Created by caminte-cli script 4 | **/ 5 | /*global 6 | describe, before, after, it 7 | */ 8 | if (!process.env.NODE_ENV) { 9 | process.env.NODE_ENV = 'test'; 10 | } 11 | var driver = process.env.CAMINTE_DRIVER || 'sqlite'; 12 | var should = require('should'); 13 | var caminte = require('../../'); 14 | var config = require('./../lib/database'); 15 | var samples = require('./../lib/data'); 16 | var dbConf = config[driver]; 17 | var articleModel = require('./../lib/Article'); 18 | var Schema = caminte.Schema; 19 | dbConf.host = process.env.DB_HOST || dbConf.host || ''; 20 | var schema = new Schema(dbConf.driver, dbConf); 21 | var Article = articleModel(schema); 22 | 23 | /** 24 | * Simple tests for the Article model 25 | */ 26 | describe(driver + ' - Article unit:', function () { 27 | 'use strict'; 28 | var article, id, newArticle = samples.articles[0]; 29 | 30 | before(function (done) { 31 | schema.autoupdate(function(){ 32 | return done && done(); 33 | }); 34 | }); 35 | 36 | after(function (done) { 37 | done(); 38 | }); 39 | 40 | describe('create unit with initial data', function () { 41 | 42 | it('unit should be created', function () { 43 | article = new Article(newArticle); 44 | article.should.be.type('object'); 45 | article.active.should.eql(newArticle.active); 46 | article.language.should.eql(newArticle.language); 47 | article.category_id.should.eql(newArticle.category_id); 48 | article.title.should.eql(newArticle.title); 49 | article.alias.should.eql(newArticle.alias); 50 | article.mainpage.should.eql(newArticle.mainpage); 51 | }); 52 | 53 | }); 54 | 55 | describe('validate created unit', function () { 56 | 57 | it('unit must be valid', function (done) { 58 | article.isValid(function (valid) { 59 | valid.should.be.true; 60 | if (!valid) console.log(article.errors); 61 | done(); 62 | }); 63 | }); 64 | 65 | }); 66 | 67 | describe('save unit', function () { 68 | 69 | it('unit should be have #save method', function () { 70 | article.should.be.have.property('save'); 71 | article.save.should.be.type('function'); 72 | }); 73 | 74 | it('unit must be saved', function (done) { 75 | article.save(function (err) { 76 | should.not.exist(err); 77 | article.should.be.have.property('id'); 78 | article.id.should.not.eql(null); 79 | id = article.id; 80 | done(); 81 | }); 82 | }); 83 | 84 | }); 85 | 86 | describe('update unit attributes', function () { 87 | 88 | it('unit should be have #updateAttributes method', function () { 89 | article.should.be.have.property('updateAttributes'); 90 | article.updateAttributes.should.be.type('function'); 91 | }); 92 | 93 | it('unit must be updated', function (done) { 94 | article.updateAttributes({ 95 | title: 'test 2' 96 | }, function (err) { 97 | should.not.exist(err); 98 | 99 | done(); 100 | }); 101 | }); 102 | 103 | }); 104 | 105 | describe('destroy unit', function () { 106 | 107 | it('unit should be have #destroy method', function () { 108 | article.should.be.have.property('destroy'); 109 | article.destroy.should.be.type('function'); 110 | }); 111 | 112 | it('unit must be destroyed', function (done) { 113 | article.destroy(function (err) { 114 | should.not.exist(err); 115 | done(); 116 | }); 117 | }); 118 | 119 | }); 120 | 121 | }); 122 | -------------------------------------------------------------------------------- /test/schema/hooks.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Alex on 12/18/2015. 3 | */ 4 | /*global 5 | describe, before, after, it 6 | */ 7 | if (!process.env.NODE_ENV) { 8 | process.env.NODE_ENV = 'test'; 9 | } 10 | var driver = process.env.CAMINTE_DRIVER || 'sqlite'; 11 | var should = require('should'); 12 | var caminte = require('../../'); 13 | var config = require('./../lib/database'); 14 | var samples = require('./../lib/data'); 15 | var dbConf = config[driver]; 16 | var userModel = require('./../lib/User'); 17 | var Schema = caminte.Schema; 18 | dbConf.host = process.env.DB_HOST || dbConf.host || ''; 19 | var schema = new Schema(dbConf.driver, dbConf); 20 | var User = userModel(schema); 21 | 22 | describe(driver + ' - schema hooks:', function () { 23 | 'use strict'; 24 | var user, nuser, newUser = samples.users[0]; 25 | 26 | before(function (done) { 27 | setTimeout(function(){ 28 | schema.autoupdate(function () { 29 | return done && done(); 30 | }); 31 | }, 500); 32 | }); 33 | 34 | after(function (done) { 35 | User.destroyAll(function(){ 36 | return done && done(); 37 | }); 38 | }); 39 | 40 | it("#afterInitialize", function (done) { 41 | User.afterInitialize = function () { 42 | User.afterInitialize = null; 43 | return done(); 44 | }; 45 | user = new User; 46 | }); 47 | 48 | it("#beforeCreate", function (done) { 49 | User.beforeCreate = function () { 50 | User.beforeCreate = null; 51 | return done(); 52 | }; 53 | User.create(newUser, function (err) { 54 | should.not.exist(err); 55 | }); 56 | }); 57 | 58 | it("#afterCreate", function (done) { 59 | User.afterCreate = function () { 60 | User.afterCreate = null; 61 | return done(); 62 | }; 63 | newUser.email = 'bubles@example.org'; 64 | User.create(newUser, function (err) { 65 | should.not.exist(err); 66 | }); 67 | }); 68 | 69 | it('#beforeSave', function (done) { 70 | User.beforeSave = function () { 71 | User.beforeSave = null; 72 | return done(); 73 | }; 74 | user = new User(newUser); 75 | user.email = 'bubles@example.mobi'; 76 | user.save(function (err) { 77 | should.not.exist(err); 78 | }); 79 | }); 80 | 81 | it('#afterSave', function (done) { 82 | User.afterSave = function () { 83 | User.afterSave = null; 84 | return done(); 85 | }; 86 | nuser = new User(newUser); 87 | nuser.email = 'bubles@example.lv'; 88 | nuser.save(function (err) { 89 | should.not.exist(err); 90 | }); 91 | }); 92 | 93 | it("#beforeUpdate", function (done) { 94 | User.beforeUpdate = function () { 95 | User.beforeUpdate = null; 96 | return done(); 97 | }; 98 | user.updateAttributes({ 99 | email: "1@1.com" 100 | }, function (err) { 101 | should.not.exist(err); 102 | }); 103 | }); 104 | 105 | it("#afterUpdate", function (done) { 106 | User.afterUpdate = function () { 107 | User.afterUpdate = null; 108 | return done(); 109 | }; 110 | nuser.updateAttributes({ 111 | email: "2@2.com" 112 | }, function (err) { 113 | should.not.exist(err); 114 | }); 115 | }); 116 | 117 | it("#beforeDestroy", function (done) { 118 | User.beforeDestroy = function () { 119 | User.beforeDestroy = null; 120 | return done(); 121 | }; 122 | user.destroy(); 123 | }); 124 | 125 | it("#afterDestroy", function (done) { 126 | User.afterDestroy = function () { 127 | User.afterDestroy = null; 128 | return done(); 129 | }; 130 | nuser.destroy(); 131 | }); 132 | 133 | }); 134 | -------------------------------------------------------------------------------- /lib/adapters/http.js: -------------------------------------------------------------------------------- 1 | exports.initialize = function initializeSchema(schema, callback) { 2 | schema.adapter = new WebService(); 3 | process.nextTick(callback); 4 | }; 5 | 6 | function WebService() { 7 | this.name = 'http'; 8 | this._models = {}; 9 | this.cache = {}; 10 | this.ids = {}; 11 | } 12 | 13 | WebService.prototype.define = function defineModel(descr) { 14 | var m = descr.model.modelName; 15 | this._models[m] = descr; 16 | }; 17 | 18 | WebService.prototype.getResourceUrl = function getResourceUrl(model) { 19 | var url = this._models[model].settings.restPath; 20 | if (!url) throw new Error('Resource url (restPath) for ' + model + ' is not defined'); 21 | return url; 22 | }; 23 | 24 | WebService.prototype.getBlankReq = function () { 25 | if (!this.csrfToken) { 26 | this.csrfToken = $('meta[name=csrf-token]').attr('content'); 27 | this.csrfParam = $('meta[name=csrf-param]').attr('content'); 28 | } 29 | var req = {}; 30 | req[this.csrfParam] = this.csrfToken; 31 | return req; 32 | }; 33 | 34 | WebService.prototype.create = function create(model, data, callback) { 35 | var req = this.getBlankReq(); 36 | req[model] = data; 37 | $.post(this.getResourceUrl(model) + '.json', req, function (res) { 38 | if (res.code === 200) { 39 | callback(null, res.data.id); 40 | } else { 41 | callback(res.error); 42 | } 43 | }, 'json'); 44 | // this.cache[model][id] = data; 45 | }; 46 | 47 | WebService.prototype.updateOrCreate = function (model, data, callback) { 48 | var mem = this; 49 | this.exists(model, data.id, function (err, exists) { 50 | if (exists) { 51 | mem.save(model, data, callback); 52 | } else { 53 | mem.create(model, data, function (err, id) { 54 | data.id = id; 55 | callback(err, data); 56 | }); 57 | } 58 | }); 59 | }; 60 | 61 | WebService.prototype.save = function save(model, data, callback) { 62 | var req = this.getBlankReq(); 63 | req._method = 'PUT'; 64 | req[model] = data; 65 | $.post(this.getResourceUrl(model) + '/' + data.id + '.json', req, function (res) { 66 | if (res.code === 200) { 67 | callback(null, res.data); 68 | } else { 69 | callback(res.error); 70 | } 71 | }, 'json'); 72 | }; 73 | 74 | WebService.prototype.exists = function exists(model, id, callback) { 75 | $.getJSON(this.getResourceUrl(model) + '/' + id + '.json', function (res) { 76 | if (res.code === 200) { 77 | callback(null, true); 78 | } else if (res.code === 404) { 79 | callback(null, false); 80 | } else { 81 | callback(res.error); 82 | } 83 | }); 84 | }; 85 | 86 | WebService.prototype.findById = function findById(model, id, callback) { 87 | $.getJSON(this.getResourceUrl(model) + '/' + id + '.json', function (res) { 88 | if (res.code === 200) { 89 | callback(null, res.data); 90 | } else { 91 | callback(res.error); 92 | } 93 | }); 94 | }; 95 | 96 | WebService.prototype.destroy = function destroy(model, id, callback) { 97 | delete this.cache[model][id]; 98 | callback(); 99 | }; 100 | 101 | WebService.prototype.all = function all(model, filter, callback) { 102 | $.getJSON(this.getResourceUrl(model) + '.json?query=' + JSON.stringify(filter), function (res) { 103 | if (res.code === 200) { 104 | callback(null, res.data); 105 | } else { 106 | callback(res.error); 107 | } 108 | }); 109 | }; 110 | 111 | WebService.prototype.destroyAll = function destroyAll(model, callback) { 112 | throw new Error('Not supported'); 113 | }; 114 | 115 | WebService.prototype.count = function count(model, callback, where) { 116 | throw new Error('Not supported'); 117 | }; 118 | 119 | WebService.prototype.updateAttributes = function (model, id, data, callback) { 120 | data.id = id; 121 | this.save(model, data, callback); 122 | }; 123 | 124 | -------------------------------------------------------------------------------- /test/schema/relation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Alex on 12/18/2015. 3 | */ 4 | /*global 5 | describe, before, after, it 6 | */ 7 | if (!process.env.NODE_ENV) { 8 | process.env.NODE_ENV = 'test'; 9 | } 10 | 11 | var driver = process.env.CAMINTE_DRIVER || 'sqlite'; 12 | var should = require('should'); 13 | var caminte = require('../../'); 14 | var config = require('./../lib/database'); 15 | var samples = require('./../lib/data'); 16 | var dbConf = config[driver]; 17 | var userModel = require('./../lib/User'); 18 | var articleModel = require('./../lib/Article'); 19 | var Schema = caminte.Schema; 20 | dbConf.host = process.env.DB_HOST || dbConf.host || ''; 21 | var schema = new Schema(dbConf.driver, dbConf); 22 | var User = userModel(schema); 23 | var Article = articleModel(schema); 24 | 25 | /** 26 | * Simple tests for the User and Article model 27 | */ 28 | describe(driver + ' - relation:', function () { 29 | 'use strict'; 30 | var article, user, newUser = samples.users[0], newArticle = samples.articles[0]; 31 | 32 | User.hasMany(Article, {as: 'articles', foreignKey: 'create_id'}); 33 | Article.belongsTo(User, {as: 'author', foreignKey: 'create_id'}); 34 | 35 | before(function (done) { 36 | setTimeout(function(){ 37 | schema.autoupdate(function () { 38 | user = new User(newUser); 39 | user.save(function () { 40 | return done && done(); 41 | }); 42 | }); 43 | }, 500); 44 | }); 45 | 46 | after(function (done) { 47 | User.destroyAll(function () { 48 | Article.destroyAll(function(){ 49 | return done && done(); 50 | }); 51 | }); 52 | }); 53 | 54 | describe('#hasMany', function () { 55 | 56 | it('#build', function (done) { 57 | article = user.articles.build(newArticle); 58 | should.exist(article); 59 | article.alias.should.be.equal(newArticle.alias); 60 | article.title.should.be.exactly(newArticle.title); 61 | should.deepEqual(article.create_id.toString(), user.id.toString()); 62 | done(); 63 | }); 64 | 65 | it('#create', function (done) { 66 | user.articles.create(newArticle, function (err, created) { 67 | should.not.exist(err); 68 | should.exist(created); 69 | created.alias.should.be.equal(newArticle.alias); 70 | created.title.should.be.exactly(newArticle.title); 71 | should.deepEqual(created.create_id.toString(), user.id.toString()); 72 | done(); 73 | }); 74 | }); 75 | 76 | it('#get (articles)', function (done) { 77 | user.articles(function (err, founds) { 78 | should.not.exist(err); 79 | founds.should.length(1); 80 | should.deepEqual(founds[0].create_id.toString(), user.id.toString()); 81 | done(); 82 | }); 83 | }); 84 | 85 | }); 86 | 87 | describe('#belongsTo', function () { 88 | it('#get (author)', function (done) { 89 | article.author(function(err, found){ 90 | should.not.exist(err); 91 | should.exist(found); 92 | found.first_name.should.be.equal(newUser.first_name); 93 | found.last_name.should.be.exactly(newUser.last_name); 94 | should.deepEqual(article.create_id.toString(), found.id.toString()); 95 | done(); 96 | }); 97 | }); 98 | }); 99 | 100 | /* 101 | User.hasMany(Post, {as: 'posts', foreignKey: 'userId'}); 102 | // creates instance methods: 103 | // user.posts(conds) 104 | // user.posts.build(data) // like new Post({userId: user.id}); 105 | // user.posts.create(data) // build and save 106 | 107 | Post.belongsTo(User, {as: 'author', foreignKey: 'userId'}); 108 | // creates instance methods: 109 | // post.author(callback) -- getter when called with function 110 | // post.author() -- sync getter when called without params 111 | // post.author(user) -- setter when called with object 112 | */ 113 | }); 114 | -------------------------------------------------------------------------------- /test/lib/data.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Alex on 12/27/2015. 3 | */ 4 | module.exports.categories = [ 5 | { 6 | active: 0, 7 | category_id: 0, 8 | language: 'ru', 9 | title: 'My Category 1', 10 | section: 'my-category' 11 | }, 12 | { 13 | active: 0, 14 | category_id: 1, 15 | language: 'en', 16 | title: 'My Category 2', 17 | section: 'my-category-2' 18 | }, 19 | { 20 | active: 0, 21 | category_id: 1, 22 | language: 'lv', 23 | title: 'My Category 3', 24 | section: 'my-category-3' 25 | }, 26 | { 27 | active: 0, 28 | category_id: 2, 29 | language: 'ru', 30 | title: 'My Category 4', 31 | section: 'my-category-4' 32 | }, 33 | { 34 | active: 0, 35 | category_id: 2, 36 | language: 'ru', 37 | title: 'My Category 5', 38 | section: 'my-category-5' 39 | }, 40 | { 41 | active: 0, 42 | category_id: 2, 43 | language: 'lv', 44 | title: 'My Category 6', 45 | section: 'my-category-6' 46 | }, 47 | { 48 | active: 0, 49 | category_id: 3, 50 | language: 'es', 51 | title: 'My Category 7', 52 | section: 'my-category-7' 53 | } 54 | ]; 55 | 56 | module.exports.articles = [ 57 | { 58 | active: 0, 59 | language: 'en', 60 | category_id: 0, 61 | title: 'My Article 1', 62 | alias: 'my-article-1', 63 | mainpage: 0, 64 | params: { 65 | title: 1, 66 | categories: 1 67 | } 68 | }, 69 | { 70 | active: 0, 71 | language: 'en', 72 | category_id: 1, 73 | title: 'My Article 2', 74 | alias: 'my-article-2', 75 | mainpage: 0, 76 | params: { 77 | title: 1, 78 | categories: 1 79 | }, 80 | content_short: 'Application developer focusing on web, mobile and server platforms. Always aiming to use software engineering’s best practices, like testability and design patterns, in a system’s implementation to achieve flexibility and scalability.', 81 | content_full: 'Application developer focusing on web, mobile and server platforms. Always aiming to use software engineering’s best practices, like testability and design patterns, in a system’s implementation to achieve flexibility and scalability.', 82 | meta_keys: ['app', 'developer', 'web'], 83 | longitude: 56.9496490, 84 | latitude: 24.1051860, 85 | price: 23.56 86 | }, 87 | { 88 | active: 0, 89 | language: 'en', 90 | category_id: 1, 91 | title: 'My Article 3', 92 | alias: 'my-article-3', 93 | mainpage: 0, 94 | params: { 95 | title: 1, 96 | categories: 1 97 | } 98 | }, 99 | { 100 | active: 0, 101 | language: 'en', 102 | category_id: 1, 103 | title: 'My Article-4', 104 | alias: 'my-article-4', 105 | mainpage: 0, 106 | params: { 107 | title: 1, 108 | categories: 1 109 | } 110 | }, 111 | { 112 | active: 0, 113 | language: 'en', 114 | category_id: 1, 115 | title: 'My Article-5', 116 | alias: 'my-article-5', 117 | mainpage: 0, 118 | params: { 119 | title: 1, 120 | categories: 1 121 | } 122 | } 123 | ]; 124 | 125 | module.exports.users = [ 126 | { 127 | language: 'en', 128 | first_name: 'Alex', 129 | last_name: 'Gordan', 130 | screen_name: 'alex', 131 | email: 'bubles@example.com', 132 | password: 'aaaaaaaaaa', 133 | age: 45 134 | }, 135 | { 136 | language: 'en', 137 | first_name: 'Marco', 138 | last_name: 'Polo', 139 | screen_name: 'polo', 140 | email: 'polo@example.com', 141 | password: 'xxxxxxxxxx', 142 | age: 95 143 | }, 144 | { 145 | language: 'en', 146 | first_name: 'Nataly', 147 | last_name: 'Prier', 148 | screen_name: 'nataly', 149 | email: 'nataly@example.com', 150 | password: 'nnnnnnnnnn', 151 | age: 21 152 | } 153 | ]; 154 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "caminte", 3 | "description": "ORM for every database: redis, mysql, neo4j, mongodb, rethinkdb, postgres, sqlite, tingodb", 4 | "version": "0.4.1", 5 | "author": { 6 | "name": "Aleksej Gordejev", 7 | "email": "aleksej@gordejev.lv", 8 | "url": "http://www.gordejev.lv" 9 | }, 10 | "homepage": "http://camintejs.com/", 11 | "license": "MIT", 12 | "contributors": [ 13 | { 14 | "name": "Aleksej Gordejev", 15 | "email": "aleksej@gordejev.lv" 16 | }, 17 | { 18 | "name": "Anatoliy Chakkaev", 19 | "email": "rpm1602@gmail.com" 20 | }, 21 | { 22 | "name": "Julien Guimont", 23 | "email": "julien.guimont@gmail.com" 24 | }, 25 | { 26 | "name": "Joseph Junker", 27 | "email": "joseph.jnk@gmail.com" 28 | }, 29 | { 30 | "name": "Henri Bergius", 31 | "email": "henri.bergius@iki.fi" 32 | }, 33 | { 34 | "name": "redvulps", 35 | "email": "fabopereira@gmail.com" 36 | }, 37 | { 38 | "name": "Felipe Sateler", 39 | "email": "fsateler@gmail.com" 40 | }, 41 | { 42 | "name": "Amir M. Mahmoudi", 43 | "email": "a@geeknux.com" 44 | }, 45 | { 46 | "name": "Justinas Stankevičius", 47 | "email": "justinas@justinas.me" 48 | }, 49 | { 50 | "name": "Rick O'Toole", 51 | "email": "patrick.n.otoole@gmail.com" 52 | }, 53 | { 54 | "name": "Nicholas Westlake", 55 | "email": "nicholasredlin@gmail.com" 56 | }, 57 | { 58 | "name": "Michael Pauley", 59 | "email": "" 60 | }, 61 | { 62 | "name": "Tyrone Dougherty", 63 | "email": "email@tyronedougherty.com" 64 | } 65 | ], 66 | "keywords": [ 67 | "orm", 68 | "cross-db", 69 | "caminte", 70 | "database", 71 | "adapter", 72 | "redis", 73 | "mysql", 74 | "mariadb", 75 | "mongodb", 76 | "neo4j", 77 | "nano", 78 | "couchdb", 79 | "firebird", 80 | "postgres", 81 | "sqlite3", 82 | "tingodb", 83 | "rethinkdb", 84 | "arangodb", 85 | "promise" 86 | ], 87 | "directories": { 88 | "lib": "lib", 89 | "media": "media", 90 | "support": "support", 91 | "test": "test" 92 | }, 93 | "repository": { 94 | "type": "git", 95 | "url": "git://github.com/biggora/caminte.git" 96 | }, 97 | "main": "index.js", 98 | "scripts": { 99 | "test": "make test", 100 | "test:mysql": "cross-env CAMINTE_DRIVER=mysql _mocha --timeout 3000 -r should -R spec --exit", 101 | "test:sqlite": "cross-env CAMINTE_DRIVER=sqlite _mocha --timeout 3000 -r should -R spec --exit", 102 | "test:postgres": "cross-env CAMINTE_DRIVER=postgres _mocha --timeout 3000 -r should -R spec --exit", 103 | "test:redis": "cross-env CAMINTE_DRIVER=redis _mocha --timeout 3000 -r should -R spec --exit", 104 | "test:mongo": "cross-env CAMINTE_DRIVER=mongo _mocha --timeout 3000 -r should -R spec --exit" 105 | }, 106 | "engines": { 107 | "node": ">=4", 108 | "npm": ">=2" 109 | }, 110 | "dependencies": { 111 | "bluebird": "^3.5.1", 112 | "uuid": "^3.2.1" 113 | }, 114 | "devDependencies": { 115 | "arangojs": "4.2.0 - 4.4.0", 116 | "async": "^2.6.0", 117 | "cassandra-driver": "^3.5.0", 118 | "coffee-script": "^1.12.7", 119 | "cradle": "^0.7.1", 120 | "cross-env": "5.1.4", 121 | "felix-couchdb": "^1.0.8", 122 | "generic-pool": "^3.4.2", 123 | "istanbul": "^0.4.5", 124 | "jshint": "2.x", 125 | "mocha": "^5.1.1", 126 | "moment": "^2.22.1", 127 | "mongodb": "^3.0.7", 128 | "mongoose": "^5.0.17", 129 | "mysql": "^2.15.0", 130 | "node-neo4j": "^2.0.3", 131 | "pg": "^7.4.1", 132 | "redis": "^2.8.0", 133 | "rethinkdb": "^2.3.3", 134 | "riak-js": "^1.1.0", 135 | "semicov": "^0.2.0", 136 | "should": "^13.2.1", 137 | "sqlite3": "^4.0.0", 138 | "underscore": "^1.9.0" 139 | }, 140 | "optionalDependencies": {} 141 | } 142 | -------------------------------------------------------------------------------- /lib/list.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = List; 3 | 4 | function List(data, type, parent) { 5 | var list = this; 6 | 7 | Object.defineProperty(list, 'parent', { 8 | writable: false, 9 | enumerable: false, 10 | configurable: false, 11 | value: parent 12 | }); 13 | 14 | Object.defineProperty(list, 'nextid', { 15 | writable: true, 16 | enumerable: false, 17 | value: 1 18 | }); 19 | 20 | data = list.items = data || []; 21 | var Item = list.ItemType = ListItem; 22 | 23 | if (typeof type === 'object' && type.constructor.name === 'Array') { 24 | list.ItemType = type[0] || ListItem; 25 | } 26 | 27 | data.forEach(function (item, i) { 28 | data[i] = new Item(item, list); 29 | Object.defineProperty(list, data[i].id, { 30 | writable: true, 31 | enumerable: false, 32 | configurable: true, 33 | value: data[i] 34 | }); 35 | if (list.nextid <= data[i].id) { 36 | list.nextid = data[i].id + 1; 37 | } 38 | }); 39 | 40 | Object.defineProperty(list, 'length', { 41 | enumerable: false, 42 | configurable: true, 43 | get: function () { 44 | return list.items.length; 45 | } 46 | }); 47 | 48 | return list; 49 | } 50 | 51 | var _; 52 | try { 53 | _ = require('underscore'); 54 | } catch (e) { 55 | _ = false; 56 | } 57 | 58 | if (_) { 59 | var _import = [ 60 | // collection methods 61 | 'each', 62 | 'map', 63 | 'reduce', 64 | 'reduceRight', 65 | 'find', 66 | 'filter', 67 | 'reject', 68 | 'all', 69 | 'any', 70 | 'include', 71 | 'invoke', 72 | 'pluck', 73 | 'max', 74 | 'min', 75 | 'sortBy', 76 | 'groupBy', 77 | 'sortedIndex', 78 | 'shuffle', 79 | 'toArray', 80 | 'size', 81 | // array methods 82 | 'first', 83 | 'initial', 84 | 'last', 85 | 'rest', 86 | 'compact', 87 | 'flatten', 88 | 'without', 89 | 'union', 90 | 'intersection', 91 | 'difference', 92 | 'uniq', 93 | 'zip', 94 | 'indexOf', 95 | 'lastIndexOf', 96 | 'range' 97 | ]; 98 | 99 | _import.forEach(function (name) { 100 | List.prototype[name] = function () { 101 | var args = [].slice.call(arguments); 102 | args.unshift(this.items); 103 | return _[name].apply(_, args); 104 | }; 105 | }); 106 | } 107 | 108 | List.prototype.toObject = function () { 109 | return this.items; 110 | }; 111 | 112 | List.prototype.toJSON = function () { 113 | return this.items; 114 | }; 115 | 116 | List.prototype.toString = function () { 117 | return JSON.stringify(this.items); 118 | }; 119 | 120 | List.prototype.autoincrement = function () { 121 | return this.nextid++; 122 | }; 123 | 124 | List.prototype.push = function (obj) { 125 | var item = new ListItem(obj, this); 126 | this.items.push(item); 127 | return item; 128 | }; 129 | 130 | List.prototype.remove = function (obj) { 131 | var id = obj.id ? obj.id : obj; 132 | var found = false; 133 | this.items.forEach(function (o, i) { 134 | if (id && o.id === id) { 135 | found = i; 136 | if (o.id !== id) { 137 | console.log('WARNING! Type of id not matched'); 138 | } 139 | } 140 | }); 141 | if (found !== false) { 142 | delete this[id]; 143 | this.items.splice(found, 1); 144 | } 145 | }; 146 | 147 | List.prototype.forEach = function (cb) { 148 | this.items.forEach(cb); 149 | }; 150 | 151 | List.prototype.sort = function (cb) { 152 | return this.items.sort(cb); 153 | }; 154 | 155 | List.prototype.map = function (cb) { 156 | if (typeof cb === 'function') return this.items.map(cb); 157 | if (typeof cb === 'string') return this.items.map(function (el) { 158 | if (typeof el[cb] === 'function') return el[cb](); 159 | if (el.hasOwnProperty(cb)) return el[cb]; 160 | }); 161 | }; 162 | 163 | function ListItem(data, parent) { 164 | for (var i in data) this[i] = data[i]; 165 | Object.defineProperty(this, 'parent', { 166 | writable: false, 167 | enumerable: false, 168 | configurable: true, 169 | value: parent 170 | }); 171 | if (!this.id) { 172 | this.id = parent.autoincrement(); 173 | } 174 | if (parent.ItemType) { 175 | this.__proto__ = parent.ItemType.prototype; 176 | if (parent.ItemType !== ListItem) { 177 | parent.ItemType.apply(this); 178 | } 179 | } 180 | 181 | this.save = function (c) { 182 | parent.parent.save(c); 183 | }; 184 | } -------------------------------------------------------------------------------- /lib/adapters/memory.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | */ 4 | var utils = require('../utils'); 5 | var helpers = utils.helpers; 6 | 7 | exports.initialize = function initializeSchema(schema, callback) { 8 | schema.adapter = new Memory(); 9 | process.nextTick(callback); 10 | }; 11 | 12 | function Memory() { 13 | this.name = 'memory'; 14 | this._models = {}; 15 | this.cache = {}; 16 | this.ids = {}; 17 | } 18 | 19 | Memory.prototype.define = function defineModel(descr) { 20 | var m = descr.model.modelName; 21 | this._models[m] = descr; 22 | this.cache[m] = {}; 23 | this.ids[m] = 1; 24 | }; 25 | 26 | Memory.prototype.toDatabase = function (model, data) { 27 | var cleaned = {}; 28 | Object.keys(data).forEach(function (key) { 29 | cleaned[key] = data[key]; 30 | }); 31 | return cleaned; 32 | }; 33 | 34 | Memory.prototype.create = function create(model, data, callback) { 35 | var id = data.id || this.ids[model]++; 36 | data.id = id; 37 | this.cache[model][id] = this.toDatabase(model, data); 38 | process.nextTick(function () { 39 | callback(null, id); 40 | }); 41 | }; 42 | 43 | Memory.prototype.updateOrCreate = function (model, data, callback) { 44 | var mem = this; 45 | this.exists(model, data.id, function (err, exists) { 46 | if (exists) { 47 | mem.save(model, data, callback); 48 | } else { 49 | mem.create(model, data, function (err, id) { 50 | data.id = id; 51 | callback(err, data); 52 | }); 53 | } 54 | }); 55 | }; 56 | 57 | Memory.prototype.save = function save(model, data, callback) { 58 | this.cache[model][data.id] = data; 59 | process.nextTick(function () { 60 | callback(null, data); 61 | }); 62 | }; 63 | 64 | Memory.prototype.exists = function exists(model, id, callback) { 65 | process.nextTick(function () { 66 | callback(null, this.cache[model].hasOwnProperty(id)); 67 | }.bind(this)); 68 | }; 69 | 70 | Memory.prototype.findById = function findById(model, id, callback) { 71 | process.nextTick(function () { 72 | callback(null, this.cache[model][id]); 73 | }.bind(this)); 74 | }; 75 | 76 | Memory.prototype.destroy = function destroy(model, id, callback) { 77 | delete this.cache[model][id]; 78 | process.nextTick(callback); 79 | }; 80 | 81 | Memory.prototype.remove = function remove(model, filter, callback) { 82 | var self = this; 83 | self.all(model, filter, function (err, nodes) { 84 | var count = nodes.length; 85 | if (count > 0) { 86 | nodes.forEach(function (node) { 87 | delete self.cache[model][node.id]; 88 | if (--count === 0) { 89 | callback(); 90 | } 91 | }); 92 | } else { 93 | callback(); 94 | } 95 | }); 96 | }; 97 | 98 | Memory.prototype.all = function all(model, filter, callback) { 99 | if ('function' === typeof filter) { 100 | callback = filter; 101 | filter = {}; 102 | } 103 | if (!filter) { 104 | filter = {}; 105 | } 106 | var nodes = Object.keys(this.cache[model]).map(function (key) { 107 | return this.cache[model][key]; 108 | }.bind(this)); 109 | 110 | if (filter) { 111 | 112 | // do we need some filtration? 113 | if (filter.where) { 114 | nodes = nodes ? nodes.filter(helpers.applyFilter(filter)) : nodes; 115 | } 116 | 117 | // do we need some sorting? 118 | if (filter.order) { 119 | var props = this._models[model].properties; 120 | var orders = filter.order; 121 | if (typeof filter.order === "string") { 122 | orders = [filter.order]; 123 | } 124 | orders.forEach(function (key, i) { 125 | var reverse = 1; 126 | var m = key.match(/\s+(A|DE)SC$/i); 127 | if (m) { 128 | key = key.replace(/\s+(A|DE)SC/i, ''); 129 | if (m[1] === 'DE') 130 | reverse = -1; 131 | } 132 | orders[i] = {"key": key, "reverse": reverse}; 133 | }); 134 | nodes = nodes.sort(sorting.bind(orders)); 135 | } 136 | } 137 | 138 | process.nextTick(function () { 139 | callback(null, nodes); 140 | }); 141 | 142 | function sorting(a, b) { 143 | for (var i = 0, l = this.length; i < l; i++) { 144 | if (a[this[i].key] > b[this[i].key]) { 145 | return 1 * this[i].reverse; 146 | } else if (a[this[i].key] < b[this[i].key]) { 147 | return -1 * this[i].reverse; 148 | } 149 | } 150 | return 0; 151 | } 152 | }; 153 | 154 | Memory.prototype.destroyAll = function destroyAll(model, callback) { 155 | Object.keys(this.cache[model]).forEach(function (id) { 156 | delete this.cache[model][id]; 157 | }.bind(this)); 158 | this.cache[model] = {}; 159 | process.nextTick(callback); 160 | }; 161 | 162 | Memory.prototype.count = function count(model, callback, where) { 163 | var cache = this.cache[model]; 164 | var data = Object.keys(cache); 165 | if (where) { 166 | data = data.filter(function (id) { 167 | var ok = true; 168 | Object.keys(where).forEach(function (key) { 169 | if (cache[id][key] !== where[key]) { 170 | ok = false; 171 | } 172 | }); 173 | return ok; 174 | }); 175 | } 176 | process.nextTick(function () { 177 | callback(null, data.length); 178 | }); 179 | }; 180 | 181 | Memory.prototype.updateAttributes = function updateAttributes(model, id, data, cb) { 182 | data.id = id; 183 | var base = this.cache[model][id]; 184 | this.save(model, helpers.merge(base, data), cb); 185 | }; -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | exports.inherits = function(newClass, baseClass) { 2 | Object.keys(baseClass).forEach(function(classMethod) { 3 | newClass[classMethod] = baseClass[classMethod]; 4 | }); 5 | Object.keys(baseClass.prototype).forEach(function(instanceMethod) { 6 | newClass.prototype[instanceMethod] = baseClass.prototype[instanceMethod]; 7 | }); 8 | }; 9 | 10 | exports.safeRequire = function safeRequire(module) { 11 | try { 12 | return require(module); 13 | } catch (e) { 14 | var str = module; 15 | if(module === 'rethinkdb') { str = module + ' generic-pool moment async'; } 16 | console.log('Run "npm install ' + str + '" command to using ' + module + ' database engine'); 17 | process.exit(1); 18 | } 19 | }; 20 | 21 | exports.getState = function getState(orm) { 22 | switch (orm.name) { 23 | case 'mysql': 24 | case 'mariadb': 25 | if (orm.client) { 26 | if (orm.client._protocol) { 27 | if (orm.client._protocol._fatalError) { 28 | if (orm.client._protocol._fatalError.fatal) { 29 | return orm.client._protocol._fatalError; 30 | } 31 | } 32 | } 33 | } 34 | break; 35 | } 36 | return true; 37 | }; 38 | 39 | exports.helpers = { 40 | __slice: [].slice, 41 | __bind: function(fn, me) { 42 | return function() { 43 | return fn.apply(me, arguments); 44 | }; 45 | }, 46 | merge: function(base, update) { 47 | var k, v; 48 | if (!base) { 49 | return update; 50 | } 51 | for (k in update) { 52 | v = update[k]; 53 | base[k] = update[k]; 54 | } 55 | return base; 56 | }, 57 | reverse: function(key) { 58 | var hasOrder = key.match(/\s+(A|DE)SC$/i); 59 | if (hasOrder) { 60 | if (hasOrder[1] === 'DE') { 61 | return -1; 62 | } 63 | } 64 | return 1; 65 | }, 66 | inArray: function(p_val, arr) { 67 | for (var i = 0, l = arr.length; i < l; i++) { 68 | if (arr[i] === p_val) { 69 | return true; 70 | } 71 | } 72 | return false; 73 | }, 74 | stripOrder: function(key) { 75 | return key.replace(/\s+(A|DE)SC/i, ''); 76 | }, 77 | savePrep: function(data) { 78 | var id = data.id; 79 | if (id) { 80 | data._id = id.toString(); 81 | } 82 | delete data.id; 83 | if (data._rev === null) { 84 | return delete data._rev; 85 | } 86 | }, 87 | applyFilter: function(filter) { 88 | var self = this; 89 | if (typeof filter.where === 'function') { 90 | return filter.where; 91 | } 92 | var keys = Object.keys(filter.where); 93 | return function(obj) { 94 | var pass = true; 95 | keys.forEach(function(key) { 96 | if (typeof filter.where[key] === 'object' && !filter.where[key].getTime) { 97 | pass = self.parseCond(obj[key], filter.where[key]); 98 | } else { 99 | if (!self.testString(filter.where[key], obj[key])) { 100 | pass = false; 101 | } 102 | } 103 | }); 104 | return pass; 105 | }; 106 | }, 107 | testString: function(example, value) { 108 | if (typeof value === 'string' && example && example.constructor.name === 'RegExp') { 109 | return value.match(example); 110 | } 111 | // not strict equality 112 | return (example !== null ? example.toString() : example) === (value !== null ? value.toString() : value); 113 | }, 114 | parseCond: function(val, conds) { 115 | var outs = false; 116 | Object.keys(conds).forEach(function(condType) { 117 | switch (condType) { 118 | case 'gt': 119 | outs = val > conds[condType] ? true : false; 120 | break; 121 | case 'gte': 122 | outs = val >= conds[condType] ? true : false; 123 | break; 124 | case 'lt': 125 | outs = val < conds[condType] ? true : false; 126 | break; 127 | case 'lte': 128 | outs = val <= conds[condType] ? true : false; 129 | break; 130 | case 'between': 131 | var bt = conds[condType]; 132 | outs = (val >= bt[0] && val <= bt[1]) ? true : false; 133 | break; 134 | case 'inq': 135 | case 'in': 136 | conds[condType].forEach(function(cval) { 137 | if (val === cval) { 138 | outs = true; 139 | } 140 | }); 141 | break; 142 | case 'nin': 143 | outs = true; 144 | conds[condType].forEach(function(cval) { 145 | if (val === cval) { 146 | outs = false; 147 | } 148 | }); 149 | break; 150 | case 'neq': 151 | case 'ne': 152 | outs = val !== conds[condType] ? true : false; 153 | break; 154 | case 'regex': 155 | case 'like': 156 | outs = new RegExp(conds[condType]).test(val); 157 | break; 158 | case 'nlike': 159 | outs = !new RegExp(conds[condType]).test(val); 160 | break; 161 | default: 162 | outs = val === conds[condType] ? true : false; 163 | break; 164 | } 165 | }); 166 | return outs; 167 | } 168 | }; 169 | -------------------------------------------------------------------------------- /test/schema/validation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Validation Test 3 | * Created by caminte-cli script 4 | **/ 5 | /*global 6 | describe, before, after, it 7 | */ 8 | if (!process.env.NODE_ENV) { 9 | process.env.NODE_ENV = 'test'; 10 | } 11 | 12 | var driver = process.env.CAMINTE_DRIVER || 'sqlite'; 13 | var should = require('should'); 14 | var caminte = require('../../'); 15 | var config = require('./../lib/database'); 16 | var samples = require('./../lib/data'); 17 | var dbConf = config[driver]; 18 | var userModel = require('./../lib/User'); 19 | var Schema = caminte.Schema; 20 | dbConf.host = process.env.DB_HOST || dbConf.host || ''; 21 | var schema = new Schema(dbConf.driver, dbConf); 22 | var User = userModel(schema); 23 | 24 | /** 25 | * Simple tests for the User model 26 | */ 27 | describe(driver + ' - schema validation:', function () { 28 | 'use strict'; 29 | var user1, user2, 30 | newUser1 = samples.users[0], 31 | newUser2 = samples.users[0]; 32 | 33 | before(function (done) { 34 | setTimeout(function(){ 35 | user1 = new User(newUser1); 36 | user2 = new User(newUser2); 37 | schema.autoupdate(function () { 38 | user1.save(done); 39 | }); 40 | }, 500); 41 | }); 42 | 43 | after(function (done) { 44 | User.destroyAll(done); 45 | }); 46 | 47 | describe('#validatesPresenceOf', function () { 48 | 49 | it('must be invalid', function (done) { 50 | user1.first_name = null; 51 | user1.isValid(function (valid) { 52 | valid.should.be.false; 53 | done(); 54 | }); 55 | }); 56 | 57 | it('must be valid', function (done) { 58 | user1.first_name = 'Alex'; 59 | user1.isValid(function (valid) { 60 | valid.should.be.true; 61 | done(); 62 | }); 63 | }); 64 | 65 | }); 66 | 67 | describe('#validatesInclusionOf', function () { 68 | 69 | it('must be invalid', function (done) { 70 | user1.language = 'by'; 71 | user1.isValid(function (valid) { 72 | valid.should.be.false; 73 | done(); 74 | }); 75 | }); 76 | 77 | it('must be valid', function (done) { 78 | user1.language = 'ru'; 79 | user1.isValid(function (valid) { 80 | valid.should.be.true; 81 | done(); 82 | }); 83 | }); 84 | }); 85 | 86 | describe('#validatesLengthOf', function () { 87 | 88 | it('must be invalid', function (done) { 89 | user1.password = 'xx'; 90 | user1.isValid(function (valid) { 91 | valid.should.be.false; 92 | done(); 93 | }); 94 | }); 95 | 96 | it('must be valid', function (done) { 97 | user1.password = 'AAAAAAAAA'; 98 | user1.isValid(function (valid) { 99 | valid.should.be.true; 100 | done(); 101 | }); 102 | }); 103 | 104 | }); 105 | 106 | describe('#validatesNumericalityOf', function () { 107 | 108 | it('must be invalid', function (done) { 109 | user1.age = 'xx'; 110 | user1.isValid(function (valid) { 111 | valid.should.be.false; 112 | done(); 113 | }); 114 | }); 115 | 116 | it('must be valid', function (done) { 117 | user1.age = 45; 118 | user1.isValid(function (valid) { 119 | valid.should.be.true; 120 | done(); 121 | }); 122 | }); 123 | 124 | }); 125 | 126 | describe('#validatesExclusionOf', function () { 127 | 128 | it('must be invalid', function (done) { 129 | user1.screen_name = 'admin'; 130 | user1.isValid(function (valid) { 131 | valid.should.be.false; 132 | done(); 133 | }); 134 | }); 135 | 136 | it('must be valid', function (done) { 137 | user1.screen_name = 'boss'; 138 | user1.isValid(function (valid) { 139 | valid.should.be.true; 140 | done(); 141 | }); 142 | }); 143 | 144 | }); 145 | 146 | describe('#validatesFormatOf', function () { 147 | 148 | it('must be invalid', function (done) { 149 | user1.screen_name = 'red in'; 150 | user1.isValid(function (valid) { 151 | valid.should.be.false; 152 | done(); 153 | }); 154 | }); 155 | 156 | it('must be valid', function (done) { 157 | user1.screen_name = 'hugoboss'; 158 | user1.isValid(function (valid) { 159 | valid.should.be.true; 160 | done(); 161 | }); 162 | }); 163 | 164 | }); 165 | 166 | describe('#validatesUniquenessOf', function () { 167 | 168 | it('must be invalid', function (done) { 169 | user1.email = newUser2.email; 170 | user1.isValid(function (valid) { 171 | valid.should.be.false; 172 | done(); 173 | }); 174 | }); 175 | 176 | it('must be valid', function (done) { 177 | user1.email = newUser1.email; 178 | user1.isValid(function (valid) { 179 | valid.should.be.true; 180 | done(); 181 | }); 182 | }); 183 | 184 | }); 185 | 186 | describe('#validate', function () { 187 | 188 | it('must be invalid', function (done) { 189 | user1.email = 'hg hj h'; 190 | user1.isValid(function (valid) { 191 | valid.should.be.false; 192 | done(); 193 | }); 194 | }); 195 | 196 | it('must be valid', function (done) { 197 | user1.email = newUser1.email; 198 | user1.isValid(function (valid) { 199 | valid.should.be.true; 200 | done(); 201 | }); 202 | }); 203 | 204 | }); 205 | 206 | }); 207 | -------------------------------------------------------------------------------- /test/model/article-promised.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Article Integration Test 3 | * Created by caminte-cli script 4 | **/ 5 | /*global 6 | describe, before, after, it 7 | */ 8 | if (!process.env.NODE_ENV) { 9 | process.env.NODE_ENV = 'test'; 10 | } 11 | var driver = process.env.CAMINTE_DRIVER || 'sqlite'; 12 | var should = require('should'); 13 | var caminte = require('../../'); 14 | var config = require('./../lib/database'); 15 | var samples = require('./../lib/data'); 16 | var dbConf = config[driver]; 17 | var articleModel = require('./../lib/Article'); 18 | var Schema = caminte.Schema; 19 | dbConf.host = process.env.DB_HOST || dbConf.host || ''; 20 | var schema = new Schema(dbConf.driver, dbConf); 21 | var Article = articleModel(schema); 22 | 23 | describe(driver + ' - Promised Article model:', function () { 24 | 'use strict'; 25 | var id, newArticle = samples.articles[1]; 26 | 27 | before(function (done) { 28 | schema.autoupdate(function(){ 29 | return done && done(); 30 | }); 31 | }); 32 | 33 | after(function (done) { 34 | Article.destroyAll(done); 35 | }); 36 | 37 | it('#create', function (done) { 38 | Article.create(newArticle).then(function (created) { 39 | // should.not.exist(err); 40 | created.should.be.have.property('id'); 41 | created.id.should.not.eql(null); 42 | created.category_id.should.eql(1); 43 | created.alias.should.eql(newArticle.alias); 44 | created.title.should.eql(newArticle.title); 45 | created.language.should.eql(newArticle.language); 46 | id = created.id; 47 | done(); 48 | }); 49 | }); 50 | 51 | it('#exists', function (done) { 52 | Article.exists(id).then(function (exists) { 53 | // should.not.exist(err); 54 | exists.should.be.true; 55 | done(); 56 | }); 57 | }); 58 | 59 | it('#findById', function (done) { 60 | Article.findById(id).then(function (found) { 61 | // should.not.exist(err); 62 | found.id.should.deepEqual(id); 63 | done(); 64 | }); 65 | }); 66 | 67 | it('#findOne', function (done) { 68 | Article.findOne({ 69 | where: { 70 | alias: newArticle.alias 71 | } 72 | }).then(function (found) { 73 | // should.not.exist(err); 74 | should.deepEqual(found.id, id); 75 | found.alias.should.eql(newArticle.alias); 76 | done(); 77 | }); 78 | }); 79 | 80 | it('#find', function (done) { 81 | Article.find({where:{}}).then(function (founds) { 82 | // should.not.exist(err); 83 | founds.should.length(1); 84 | done(); 85 | }); 86 | }); 87 | 88 | it('#all', function (done) { 89 | Article.all({where:{}}).then(function (founds) { 90 | // should.not.exist(err); 91 | founds.should.length(1); 92 | done(); 93 | }); 94 | }); 95 | 96 | it('#update', function (done) { 97 | var title = 'Article_2'; 98 | Article.update({ 99 | alias: newArticle.alias 100 | }, { 101 | title: title, 102 | mainpage: 1 103 | }).then(function(affected){ 104 | should.exist(affected); 105 | Article.findById(id).then(function (found) { 106 | // should.not.exist(err); 107 | should.exist(found); 108 | found.alias.should.be.equal(newArticle.alias); 109 | found.title.should.be.exactly(title); 110 | found.mainpage.should.eql(1); 111 | done(); 112 | }); 113 | }); 114 | }); 115 | 116 | it('#findOrCreate', function (done) { 117 | Article.findOrCreate({ 118 | title: 'Article_3' 119 | }, { 120 | language: 'ru', 121 | category_id: 2, 122 | alias: 'my-article-3', 123 | mainpage: 0 124 | }).then(function (created) { 125 | // should.not.exist(err); 126 | should.exist(created); 127 | Article.all({ 128 | where: { 129 | title: 'Article_3' 130 | } 131 | }).then(function (founds) { 132 | // should.not.exist(err); 133 | founds.should.length(1); 134 | done(); 135 | }); 136 | }); 137 | }); 138 | 139 | it('#updateOrCreate', function (done) { 140 | Article.updateOrCreate({ 141 | title: 'Article_3' 142 | }, { 143 | alias: 'my-article-4', 144 | mainpage: 1 145 | }).then(function (updated) { 146 | // should.not.exist(err); 147 | should.exist(updated); 148 | Article.all({ 149 | where: { 150 | alias: 'my-article-4' 151 | } 152 | }).then(function (founds) { 153 | // should.not.exist(err); 154 | founds.should.length(1); 155 | done(); 156 | }); 157 | }); 158 | }); 159 | 160 | it('#count', function (done) { 161 | Article.count({}).then(function (count) { 162 | // should.not.exist(err); 163 | count.should.equal(2); 164 | done(); 165 | }); 166 | }); 167 | 168 | it('#destroyById', function (done) { 169 | Article.destroyById(id).then(function (err) { 170 | should.not.exist(err); 171 | Article.findById(id, function (err, found) { 172 | should.not.exist(err); 173 | should.not.exist(found); 174 | done(); 175 | }); 176 | }); 177 | }); 178 | 179 | it('#destroyAll', function (done) { 180 | Article.destroyAll().then(function (err) { 181 | should.not.exist(err); 182 | Article.find({}, function (err, founds) { 183 | should.not.exist(err); 184 | founds.should.length(0); 185 | done(); 186 | }); 187 | }); 188 | }); 189 | 190 | }); 191 | -------------------------------------------------------------------------------- /lib/query.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Aelxey Gordeyev . 3 | */ 4 | 5 | /** 6 | * Module dependencies. 7 | */ 8 | var utils = require('./utils'); 9 | var helpers = utils.helpers; 10 | 11 | /** 12 | * Query class 13 | * 14 | * @api private 15 | * 16 | * @param {Object} model 17 | * @param {String} action 18 | * @param {mixed} conditions 19 | */ 20 | function Query(model, action, conditions) { 21 | var self = this; 22 | self.model = model; 23 | self.action = action || 'all'; 24 | self.q = { 25 | conditions: {}, 26 | params: {}, 27 | pkey: false, 28 | fields: false 29 | }; 30 | if (typeof conditions === 'object') { 31 | self.q.conditions = helpers.merge(self.q.conditions, conditions); 32 | } 33 | 34 | ['all', 'run', 'exec'].forEach(function(method) { 35 | self[method] = function(params, callback) { 36 | if (arguments.length === 1) { 37 | callback = params; 38 | params = {}; 39 | } 40 | params = buildQuery(params, this); 41 | var action = self.action ? self.action : 'all'; 42 | self.model[action](params, callback); 43 | }; 44 | }); 45 | 46 | ['find', 'findOne'].forEach(function(method) { 47 | self[method] = function(params, callback) { 48 | if (arguments.length === 1) { 49 | callback = params; 50 | params = {}; 51 | } 52 | params = buildQuery(params, this); 53 | self.model[method](params, callback); 54 | }; 55 | }); 56 | 57 | ['skip', 'limit', 'order', 'sort', 'group'].forEach(function(method) { 58 | self[method] = function(key, value) { 59 | this.q.pkey = false; 60 | if (method === 'sort') { 61 | method = 'order'; 62 | } 63 | if (typeof value === 'undefined') { 64 | if (/^-/.test(key)) { 65 | this.q.params[method] = key.replace(/^-/, '') + ' DESC'; 66 | } else { 67 | this.q.params[method] = key; 68 | } 69 | } else { 70 | this.q.params[method] = key + ' ' + value; 71 | } 72 | return this; 73 | }; 74 | }); 75 | 76 | self.asc = function(value) { 77 | this.q.pkey = false; 78 | this.q.params['order'] = value + ' ASC'; 79 | return this; 80 | }; 81 | 82 | self.desc = function(value) { 83 | this.q.pkey = false; 84 | this.q.params['order'] = value + ' DESC'; 85 | return this; 86 | }; 87 | 88 | self.where = function(key, value) { 89 | if (typeof value === 'undefined') { 90 | this.q.pkey = key; 91 | } else { 92 | this.q.pkey = false; 93 | this.q.conditions[key] = value; 94 | } 95 | return this; 96 | }; 97 | 98 | self.or = function(values) { 99 | if (Array.isArray(values)) { 100 | this.q.conditions['or'] = values; 101 | } 102 | return this; 103 | }; 104 | 105 | self.range = function(key, from, to) { 106 | if (typeof to === 'undefined') { 107 | if (this.q.pkey) { 108 | to = from; 109 | from = key; 110 | if (typeof this.q.conditions[this.q.pkey] === 'undefined') { 111 | this.q.conditions[this.q.pkey] = {}; 112 | } 113 | this.q.conditions[this.q.pkey].gt = from; 114 | this.q.conditions[this.q.pkey].lt = to; 115 | } 116 | } else { 117 | this.q.pkey = false; 118 | if (typeof this.q.conditions[key] === 'undefined') { 119 | this.q.conditions[key] = {}; 120 | } 121 | this.q.conditions[key].gt = from; 122 | this.q.conditions[key].lt = to; 123 | } 124 | return this; 125 | }; 126 | 127 | ['gt', 'gte', 'lt', 'lte', 'in', 'inq', 'ne', 'neq', 'nin', 'regex', 'like', 'nlike', 'between'].forEach(function(method) { 128 | self[method] = function(key, value) { 129 | if (typeof value === 'undefined') { 130 | if (this.q.pkey) { 131 | if (typeof this.q.conditions[this.q.pkey] === 'undefined') { 132 | this.q.conditions[this.q.pkey] = {}; 133 | } 134 | this.q.conditions[this.q.pkey][method] = key; 135 | } 136 | } else { 137 | this.q.pkey = false; 138 | if (typeof this.q.conditions[key] === 'undefined') { 139 | this.q.conditions[key] = {}; 140 | } 141 | this.q.conditions[key][method] = value; 142 | } 143 | return this; 144 | }; 145 | }); 146 | 147 | self.slice = function(values) { 148 | if (Array.isArray(values)) { 149 | if (typeof values[1] === 'undefined') { 150 | this.q.params['limit'] = values[0]; 151 | } else { 152 | this.q.params['skip'] = values[0]; 153 | this.q.params['limit'] = values[1]; 154 | } 155 | } 156 | return this; 157 | }; 158 | 159 | /** 160 | * Destroy records 161 | * @param {Object} params 162 | * @param {Function} callback 163 | */ 164 | self.remove = function(params, callback) { 165 | if (arguments.length === 1) { 166 | callback = params; 167 | params = {}; 168 | } 169 | params = buildQuery(params, this); 170 | self.model.remove(params, callback); 171 | }; 172 | 173 | function buildQuery(opts, query) { 174 | if (typeof opts.where === 'undefined') { 175 | opts.where = {}; 176 | } 177 | opts.where = helpers.merge(opts.where, query.q.conditions); 178 | query.q.conditions = {}; 179 | 180 | for (var pkey in query.q.params) { 181 | if (typeof opts[pkey] === 'undefined') { 182 | opts[pkey] = {}; 183 | } 184 | opts[pkey] = query.q.params[pkey]; 185 | } 186 | 187 | query.q.params = {}; 188 | query.q.pkey = false; 189 | return opts; 190 | } 191 | } 192 | 193 | /** 194 | * Exports. 195 | */ 196 | 197 | module.exports = exports = Query; 198 | -------------------------------------------------------------------------------- /lib/sql.js: -------------------------------------------------------------------------------- 1 | module.exports = BaseSQL; 2 | 3 | /** 4 | * Base SQL class 5 | */ 6 | function BaseSQL() { 7 | } 8 | 9 | BaseSQL.prototype.query = function () { 10 | throw new Error('query method should be declared in adapter'); 11 | }; 12 | 13 | BaseSQL.prototype.command = function (sql, callback) { 14 | return this.query(sql, callback); 15 | }; 16 | 17 | BaseSQL.prototype.queryOne = function (sql, callback) { 18 | return this.query(sql, function (err, data) { 19 | if (err) { 20 | return callback && callback(err); 21 | } 22 | return callback && callback(err, data[0]); 23 | }); 24 | }; 25 | 26 | BaseSQL.prototype.table = function (model) { 27 | return this._models[model].model.schema.tableName(model); 28 | }; 29 | 30 | BaseSQL.prototype.escapeName = function (name) { 31 | throw new Error('escapeName method should be declared in adapter'); 32 | }; 33 | 34 | BaseSQL.prototype.tableEscaped = function (model) { 35 | return this.escapeName(this.table(model)); 36 | }; 37 | 38 | BaseSQL.prototype.define = function (descr) { 39 | if (!descr.settings) { 40 | descr.settings = {}; 41 | } 42 | this._models[descr.model.modelName] = descr; 43 | }; 44 | 45 | BaseSQL.prototype.defineProperty = function (model, prop, params) { 46 | this._models[model].properties[prop] = params; 47 | }; 48 | 49 | BaseSQL.prototype.save = function (model, data, callback) { 50 | var sql = 'UPDATE ' + this.tableEscaped(model) + ' SET ' + this.toFields(model, data) + ' WHERE ' + this.escapeName('id') + ' = ' + data.id; 51 | this.query(sql, function (err) { 52 | return callback && callback(err); 53 | }); 54 | }; 55 | 56 | BaseSQL.prototype.exists = function (model, id, callback) { 57 | id = getInstanceId(id); 58 | var sql = 'SELECT 1 FROM ' + 59 | this.tableEscaped(model) + ' WHERE ' + this.escapeName('id') + ' = ' + id + ' LIMIT 1'; 60 | 61 | this.query(sql, function (err, data) { 62 | if (err) { 63 | return callback(err); 64 | } 65 | return callback && callback(null, data.length === 1); 66 | }); 67 | }; 68 | 69 | BaseSQL.prototype.findById = function findById(model, id, callback) { 70 | id = getInstanceId(id); 71 | var self = this; 72 | var sql = 'SELECT * FROM ' + 73 | self.tableEscaped(model) + ' WHERE ' + 74 | self.escapeName('id') + ' = ' + id + ' LIMIT 1'; 75 | 76 | self.query(sql, function (err, data) { 77 | if (data && data.length === 1) { 78 | data[0].id = id; 79 | } else { 80 | data = [null]; 81 | } 82 | return callback && callback(err, self.fromDatabase(model, data[0])); 83 | }.bind(self)); 84 | }; 85 | 86 | BaseSQL.prototype.remove = function remove(model, cond, callback) { 87 | var self = this, sql = 'DELETE FROM ' + this.tableEscaped(model) + ' '; 88 | if (!cond) { 89 | cond = {}; 90 | } 91 | if (cond.where) { 92 | sql += self.buildWhere(cond.where, self, model); 93 | self.command(sql, function (err) { 94 | return callback && callback(err); 95 | }); 96 | } else { 97 | return callback && callback('Undefined cond.where'); 98 | } 99 | }; 100 | 101 | BaseSQL.prototype.destroy = function destroy(model, id, callback) { 102 | var sql = 'DELETE FROM ' + 103 | this.tableEscaped(model) + ' WHERE ' + this.escapeName('id') + ' = ' + getInstanceId(id); 104 | this.command(sql, function (err) { 105 | return callback && callback(err); 106 | }); 107 | }; 108 | 109 | BaseSQL.prototype.destroyAll = function destroyAll(model, callback) { 110 | this.command('DELETE FROM ' + this.tableEscaped(model), function (err) { 111 | if (err) { 112 | return callback && callback(err, []); 113 | } 114 | return callback && callback(err, []); 115 | }.bind(this)); 116 | }; 117 | 118 | BaseSQL.prototype.count = function count(model, callback, cond) { 119 | var self = this, sql = 'SELECT count(*) as cnt FROM ' + self.tableEscaped(model) + ' '; 120 | if (cond && cond.where) { 121 | sql += self.buildWhere(cond.where, self, model); 122 | } 123 | self.queryOne(sql, function (err, res) { 124 | if (err) { 125 | return callback && callback(err); 126 | } 127 | var cnt = parseInt(res && res.cnt || 0); 128 | return callback && callback(err, cnt); 129 | }); 130 | }; 131 | 132 | BaseSQL.prototype.updateAttributes = function updateAttrs(model, id, data, cb) { 133 | data.id = getInstanceId(id); 134 | this.save(model, data, cb); 135 | }; 136 | 137 | BaseSQL.prototype.disconnect = function disconnect() { 138 | this.client.end(); 139 | }; 140 | /** 141 | * Re create existing database tables. 142 | * @param {Function} cb 143 | */ 144 | BaseSQL.prototype.automigrate = function (cb) { 145 | var self = this; 146 | var wait = 0; 147 | 148 | Object.keys(this._models).forEach(function (model) { 149 | wait += 1; 150 | self.dropTable(model, function (err) { 151 | if (err) { 152 | console.log(err); 153 | } 154 | self.createTable(model, function (err) { 155 | if (err) { 156 | console.log(err); 157 | } 158 | return done && done(); 159 | }); 160 | }); 161 | }); 162 | if (wait === 0) { 163 | cb(); 164 | } 165 | function done() { 166 | if (--wait === 0 && cb) { 167 | cb(); 168 | } 169 | } 170 | }; 171 | 172 | BaseSQL.prototype.dropTable = function (model, cb) { 173 | this.command('DROP TABLE IF EXISTS ' + this.tableEscaped(model), cb); 174 | }; 175 | 176 | BaseSQL.prototype.createTable = function (model, indexes, cb) { 177 | var self = this, m = self._models[model]; 178 | if ('function' === typeof indexes) { 179 | cb = indexes; 180 | } 181 | var sql = 'CREATE TABLE ' + self.tableEscaped(model) + 182 | ' (\n ' + self.propertiesSQL(model) + '\n)'; 183 | if (self.name === 'mysql') { 184 | sql += ' CHARSET=utf8;'; 185 | } else if (self.name === 'pg') { 186 | // TODO 187 | // sql = 'PRAGMA encoding = 'UTF-8'; ' + sql; 188 | } else if (self.name === 'cassandra') { 189 | // add sorting indexes 190 | if (m.settings.orderBy && m.settings.orderBy.columns) { 191 | var oda = m.settings.orderBy; 192 | var odd = oda.direction ? oda.direction.toUpperCase() : 'ASC'; 193 | sql += ' WITH CLUSTERING ORDER BY (' + oda.columns + ' ' + odd + ')'; 194 | } 195 | } 196 | 197 | try { 198 | self.command(sql, function (err) { 199 | if (err) { 200 | // console.log('ERROR CREATE TABLE 1: ', model, sql, err); 201 | } 202 | // || self.name === 'cassandra' 203 | if (self.name === 'sqlite3' || self.name === 'mysql') { 204 | self.createIndexes(model, self._models[model], cb); 205 | } else { 206 | return cb && cb(); 207 | } 208 | }); 209 | } catch (err) { 210 | // console.log('ERROR CREATE TABLE 2: ', model, sql, err); 211 | return cb && cb(); 212 | } 213 | }; 214 | 215 | /** 216 | * Normalize id 217 | * 218 | * @param {Mixed} id 219 | */ 220 | function getInstanceId(id) { 221 | if (typeof id === 'object' && id.constructor === Array) { 222 | id = id[0]; 223 | } 224 | return id; 225 | } 226 | -------------------------------------------------------------------------------- /test/schema/queries.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Alex on 12/27/2015. 3 | */ 4 | /*global 5 | describe, before, after, it 6 | */ 7 | if (!process.env.NODE_ENV) { 8 | process.env.NODE_ENV = 'test'; 9 | } 10 | var driver = process.env.CAMINTE_DRIVER || 'sqlite'; 11 | var should = require('should'); 12 | var caminte = require('../../index'); 13 | var config = require('./../lib/database'); 14 | var samples = require('./../lib/data'); 15 | var dbConf = config[driver]; 16 | var categoryModel = require('./../lib/Category'); 17 | var Schema = caminte.Schema; 18 | dbConf.host = process.env.DB_HOST || dbConf.host || ''; 19 | var schema = new Schema(dbConf.driver, dbConf); 20 | var Category = categoryModel(schema); 21 | 22 | describe(driver + ' - queries:', function () { 23 | 'use strict'; 24 | var newCategories = samples.categories, total = newCategories.length; 25 | 26 | before(function (done) { 27 | setTimeout(function(){ 28 | schema.autoupdate(function () { 29 | var cnt = newCategories.length; 30 | if (!cnt) { 31 | return done && done(); 32 | } 33 | newCategories.forEach(function (category) { 34 | Category.create(category, function (err) { 35 | if (err) { 36 | console.log(err); 37 | } 38 | if (--cnt === 0) { 39 | return done && done(); 40 | } 41 | }); 42 | }); 43 | }); 44 | }, 500); 45 | }); 46 | 47 | after(function (done) { 48 | Category.destroyAll(function(){ 49 | return done && done(); 50 | }); 51 | }); 52 | 53 | describe('#order', function () { 54 | 55 | it('by category_id asc', function (done) { 56 | Category.all({ 57 | order: 'category_id ASC' 58 | }, function (err, founds) { 59 | should.not.exist(err); 60 | founds.should.length(total); 61 | var first = founds[0]; 62 | var last = founds[total - 1]; 63 | (first.category_id).should.be.below(last.category_id); 64 | done(); 65 | }); 66 | }); 67 | 68 | it('by category_id desc', function (done) { 69 | Category.all({ 70 | order: 'category_id DESC' 71 | }, function (err, founds) { 72 | should.not.exist(err); 73 | founds.should.length(total); 74 | var first = founds[0]; 75 | var last = founds[total - 1]; 76 | (last.category_id).should.be.below(first.category_id); 77 | done(); 78 | }); 79 | }); 80 | 81 | }); 82 | 83 | describe('#skip', function () { 84 | 85 | it('must be 4 from ' + total, function (done) { 86 | Category.all({ 87 | skip: 3, 88 | limit: 20 89 | }, function (err, founds) { 90 | should.not.exist(err); 91 | founds.should.length(4); 92 | done(); 93 | }); 94 | }); 95 | 96 | }); 97 | 98 | describe('#limit', function () { 99 | 100 | it('must be 3 from ' + total, function (done) { 101 | Category.all({ 102 | limit: 3 103 | }, function (err, founds) { 104 | should.not.exist(err); 105 | founds.should.length(3); 106 | done(); 107 | }); 108 | }); 109 | 110 | }); 111 | 112 | describe('#where', function () { 113 | 114 | it('# = - must be equal 2', function (done) { 115 | Category.all({ 116 | where: { 117 | category_id : 2 118 | } 119 | }, function (err, founds) { 120 | should.not.exist(err); 121 | founds.should.length(3); 122 | done(); 123 | }); 124 | }); 125 | 126 | it('#ne - must be not equal 2', function (done) { 127 | Category.all({ 128 | where: { 129 | category_id : { ne : 2 } 130 | } 131 | }, function (err, founds) { 132 | should.not.exist(err); 133 | founds.should.length(4); 134 | done(); 135 | }); 136 | }); 137 | 138 | it('#lt - must be less then 2', function (done) { 139 | Category.all({ 140 | where: { 141 | category_id : { lt : 2 } 142 | } 143 | }, function (err, founds) { 144 | should.not.exist(err); 145 | founds.should.length(3); 146 | done(); 147 | }); 148 | }); 149 | 150 | it('#lte - must be less then or equal 2', function (done) { 151 | Category.all({ 152 | where: { 153 | category_id : { lte : 2 } 154 | } 155 | }, function (err, founds) { 156 | should.not.exist(err); 157 | founds.should.length(6); 158 | done(); 159 | }); 160 | }); 161 | 162 | it('#gt - must be greater than 2', function (done) { 163 | Category.all({ 164 | where: { 165 | category_id : { gt : 2 } 166 | } 167 | }, function (err, founds) { 168 | should.not.exist(err); 169 | founds.should.length(1); 170 | done(); 171 | }); 172 | }); 173 | 174 | it('#gte - must be greater then or equal 2', function (done) { 175 | Category.all({ 176 | where: { 177 | category_id : { gte : 2 } 178 | } 179 | }, function (err, founds) { 180 | should.not.exist(err); 181 | founds.should.length(4); 182 | done(); 183 | }); 184 | }); 185 | 186 | it('#between - must be between [1,3]', function (done) { 187 | Category.all({ 188 | where: { 189 | category_id : { between : [1,3] } 190 | } 191 | }, function (err, founds) { 192 | should.not.exist(err); 193 | founds.should.length(6); 194 | done(); 195 | }); 196 | }); 197 | 198 | it('#inq - must be in [1,3]', function (done) { 199 | Category.all({ 200 | where: { 201 | category_id : { inq : [1,3] } 202 | } 203 | }, function (err, founds) { 204 | should.not.exist(err); 205 | founds.should.length(3); 206 | done(); 207 | }); 208 | }); 209 | 210 | it('#inq - must be in [ru,lv]', function (done) { 211 | Category.all({ 212 | where: { 213 | language : { inq : ['ru','lv'] } 214 | } 215 | }, function (err, founds) { 216 | should.not.exist(err); 217 | founds.should.length(5); 218 | done(); 219 | }); 220 | }); 221 | 222 | it('#nin - must be not in [1,3]', function (done) { 223 | Category.all({ 224 | where: { 225 | category_id : { nin : [1,3] } 226 | } 227 | }, function (err, founds) { 228 | should.not.exist(err); 229 | founds.should.length(4); 230 | done(); 231 | }); 232 | }); 233 | 234 | it('#nin - must be not in [en,lv]', function (done) { 235 | Category.all({ 236 | where: { 237 | language : { nin : ['en','lv'] } 238 | } 239 | }, function (err, founds) { 240 | should.not.exist(err); 241 | founds.should.length(4); 242 | done(); 243 | }); 244 | }); 245 | }); 246 | 247 | }); 248 | -------------------------------------------------------------------------------- /lib/adapters/riak.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | */ 4 | var utils = require('../utils'); 5 | var safeRequire = utils.safeRequire; 6 | var riak = safeRequire('riak-js'); 7 | 8 | exports.initialize = function initializeSchema(schema, callback) { 9 | schema.client = riak.getClient({ 10 | host: schema.settings.host || '127.0.0.1', 11 | port: schema.settings.port || 8098 12 | }); 13 | 14 | var instrument = { 15 | 'riak.request.start': function (event) { 16 | console.log('[riak-js] ' + event.method.toUpperCase() + ' ' + event.path); 17 | } 18 | } 19 | 20 | schema.client.registerListener(instrument); 21 | schema.adapter = new Riak(schema.settings, schema, callback); 22 | }; 23 | 24 | function Riak(s, schema, callback) { 25 | this.name = 'riak'; 26 | this._models = {}; 27 | this._secondaryIndexes = {}; 28 | this.collections = {}; 29 | this.client = schema.client; 30 | this.schema = schema; 31 | this.s = s; 32 | this.database = s.database || ''; 33 | process.nextTick(callback); 34 | } 35 | 36 | Riak.prototype.define = function (descr) { 37 | var self = this; 38 | var prop = descr.properties || {}; 39 | for (var key in prop) { 40 | if (typeof this._secondaryIndexes[descr.model.modelName] === 'undefined') { 41 | this._secondaryIndexes[descr.model.modelName] = {}; 42 | } 43 | if (prop[key].index || prop[key].unique) { 44 | this._secondaryIndexes[descr.model.modelName][key] = prop[key].type.name; 45 | } 46 | } 47 | self.client.getBucket(descr.model.modelName, function (err, properties) { 48 | self.client.saveBucket(descr.model.modelName, { 49 | allow_mult: false, 50 | search: true 51 | }); 52 | }); 53 | self._models[descr.model.modelName] = descr; 54 | }; 55 | 56 | Riak.prototype.save = function (model, data, callback) { 57 | var self = this; 58 | var opts = self.buildIndexes(model, data); 59 | if (data.id) { 60 | self.client.save(model, data.id, data, opts, callback); 61 | } else { 62 | self.client.save(model, null, data, function (err, obj, meta) { 63 | data.id = meta.key; 64 | self.client.save(model, data.id, data, opts, callback); 65 | }); 66 | } 67 | }; 68 | 69 | Riak.prototype.create = function (model, data, callback) { 70 | this.save(model, data, function (err) { 71 | if (callback) { 72 | callback(err, data.id); 73 | } 74 | }); 75 | }; 76 | 77 | Riak.prototype.exists = function (model, id, callback) { 78 | this.client.exists(model, id, function (err, exists, meta) { 79 | if (callback) { 80 | callback(err, exists); 81 | } 82 | }); 83 | }; 84 | 85 | Riak.prototype.findById = function findById(model, id, callback) { 86 | this.client.get(model, id, callback); 87 | }; 88 | 89 | Riak.prototype.destroy = function destroy(model, id, callback) { 90 | this.client.remove(model, id, callback); 91 | }; 92 | 93 | Riak.prototype.remove = function (model, filter, callback) { 94 | var self = this; 95 | self.all(model, filter, function (err, docs) { 96 | if (docs) { 97 | removeOne(); 98 | function removeOne(error) { 99 | err = err || error; 100 | var rec = docs.pop(); 101 | if (!rec) { 102 | return callback(err && err.statusCode !== '404' ? err : null); 103 | } 104 | self.client.remove(model, rec.id, removeOne); 105 | } 106 | } else { 107 | callback(err); 108 | } 109 | }); 110 | }; 111 | 112 | Riak.prototype.all = function all(model, filter, callback) { 113 | var self = this, where; 114 | if ('function' === typeof filter) { 115 | callback = filter; 116 | filter = {}; 117 | } 118 | if (!filter) { 119 | filter = {}; 120 | } 121 | var opts = { 122 | start: 0 123 | }; 124 | if (filter && filter.where) { 125 | where = self.buildWhere(model, filter.where); 126 | } 127 | if (filter && filter.limit) { 128 | opts.rows = filter.limit; 129 | } 130 | if (filter && filter.skip) { 131 | opts.start = filter.skip; 132 | } 133 | if (filter && filter.order && filter.order !== "") { 134 | var orderOpts = (filter.order || "").split(' '); 135 | var orderFields = (orderOpts[0] || "").split(','); 136 | opts.sort = orderFields[0]; 137 | } 138 | self.client.getAll(model, {}, opts, function (err, result, meta) { 139 | if (err) { 140 | return callback(err, []); 141 | } 142 | 143 | /*, result 144 | result = (result || []).map(function(row) { 145 | // console.log(row); 146 | return row; 147 | });result 148 | */ 149 | return callback(err, result); 150 | }.bind(this)); 151 | }; 152 | 153 | Riak.prototype.destroyAll = function destroyAll(model, callback) { 154 | var self = this; 155 | self.all(model, {}, function (err, recs) { 156 | if (err) { 157 | callback(err); 158 | } 159 | removeOne(); 160 | 161 | function removeOne(error) { 162 | err = err || error; 163 | var rec = recs.pop(); 164 | if (!rec) { 165 | return callback(err && err.statusCode !== '404' ? err : null); 166 | } 167 | console.log(rec.id); 168 | self.client.remove(model, rec.id, removeOne); 169 | } 170 | }); 171 | }; 172 | 173 | Riak.prototype.count = function count(model, callback) { 174 | this.client.count(model, callback); 175 | }; 176 | 177 | Riak.prototype.updateAttributes = function updateAttrs(model, id, data, callback) { 178 | data.id = id; 179 | this.save(model, data, callback); 180 | }; 181 | 182 | Riak.prototype.buildIndexes = function buildIndexes(model, data) { 183 | var idx = this._secondaryIndexes[model], opts = {}; 184 | for (var key in data) { 185 | if (typeof idx[key] !== 'undefined') { 186 | var val = data[key]; 187 | if (idx[key] === 'Number' || idx[key] === 'Date') { 188 | val = parseInt(val); 189 | if (!isNaN(val)) { 190 | opts[key] = val; 191 | } 192 | } else { 193 | if (val !== null) { 194 | opts[key] = val; 195 | } 196 | } 197 | } 198 | } 199 | return Object.keys(opts).length ? {index: opts} : {}; 200 | }; 201 | 202 | Riak.prototype.buildWhere = function buildWhere(model, data) { 203 | var idx = this._secondaryIndexes[model], opts = {}; 204 | for (var key in data) { 205 | if (typeof idx[key] !== 'undefined') { 206 | var val = data[key]; 207 | if (idx[key] === 'Number' || idx[key] === 'Date') { 208 | if (typeof val === 'object') { 209 | var cond = this.buildCond(key, val); 210 | if (cond[key]) { 211 | opts[key] = cond[key]; 212 | } 213 | } else { 214 | val = parseInt(val); 215 | if (!isNaN(val)) { 216 | opts[key] = val; 217 | } 218 | } 219 | } else { 220 | if (val !== null) { 221 | opts[key] = val; 222 | } 223 | } 224 | } 225 | } 226 | return Object.keys(opts).length ? opts : {}; 227 | }; 228 | 229 | Riak.prototype.buildCond = function buildCond(key, conds) { 230 | var outs = {}; 231 | console.log(conds) 232 | Object.keys(conds).forEach(function (condType) { 233 | var val = conds[condType]; 234 | val = (val.getTime) ? val.getTime() : val; 235 | switch (condType) { 236 | case 'gt': 237 | outs[key] = [parseInt(val) + 1, -1]; 238 | break; 239 | case 'gte': 240 | outs[key] = [parseInt(val), -1]; 241 | break; 242 | case 'lt': 243 | outs[key] = [-1, parseInt(val)]; 244 | break; 245 | case 'lte': 246 | outs[key] = [-1, parseInt(val) - 1]; 247 | break; 248 | case 'between': 249 | outs[key] = conds[condType]; 250 | break; 251 | case 'inq': 252 | case 'in': 253 | 254 | break; 255 | case 'nin': 256 | 257 | break; 258 | case 'neq': 259 | case 'ne': 260 | 261 | break; 262 | case 'regex': 263 | case 'like': 264 | 265 | break; 266 | case 'nlike': 267 | 268 | break; 269 | default: 270 | 271 | break; 272 | } 273 | }); 274 | return outs; 275 | }; 276 | 277 | Riak.prototype.fullModelName = function fullModelName(name) { 278 | return this.database + '_' + name; 279 | }; 280 | -------------------------------------------------------------------------------- /lib/adapters/tingodb.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | * mongodb adapter with a few tweaks to run tingodb 4 | */ 5 | var utils = require('../utils'); 6 | var safeRequire = utils.safeRequire; 7 | var tingodb = safeRequire('tingodb'); 8 | var fs = require('fs'); 9 | 10 | exports.initialize = function initializeSchema(schema, callback) { 11 | if (!tingodb) { 12 | return; 13 | } 14 | var s = schema.settings; 15 | s.database = s.database || './db/data'; 16 | s.nativeObjectID = s.nativeObjectID || false; 17 | s.cacheSize = s.cacheSize || 1000; 18 | s.cacheMaxObjSize = s.cacheMaxObjSize || 1024; 19 | s.searchInArray = s.searchInArray || false; 20 | 21 | if (!fs.existsSync(s.database)) { 22 | console.log('Database directory not exists ' + s.database + ', please create!'); 23 | } 24 | 25 | schema.adapter = new TingoDB(s, schema, callback); 26 | }; 27 | 28 | function TingoDB(s, schema, callback) { 29 | this.name = 'tingodb'; 30 | this._models = {}; 31 | this.collections = {}; 32 | this.settings = s; 33 | var Db = tingodb().Db; 34 | this.db = new Db(s.database, s); 35 | this.db.open(function (err, client) { 36 | if (err) { 37 | throw err; 38 | } 39 | if (client) { 40 | this.client = client; 41 | schema.client = client; 42 | callback(); 43 | } else { 44 | throw new Error('client not loaded'); 45 | } 46 | }.bind(this)); 47 | } 48 | 49 | TingoDB.prototype.define = function (descr) { 50 | if (!descr.settings) { 51 | descr.settings = {}; 52 | } 53 | var self = this; 54 | self._models[descr.model.modelName] = descr; 55 | self.collections[descr.model.modelName] = self.db.collection(descr.model.modelName); 56 | setTimeout(function () { 57 | Object.keys(descr.properties).forEach(function (k) { 58 | if (typeof descr.properties[k].index !== 'undefined' || typeof descr.properties[k].unique !== 'undefined') { 59 | var fields = {}, params = {}; 60 | fields[k] = 1; 61 | params['name'] = '_' + k + '_'; 62 | if (typeof descr.properties[k].unique !== 'undefined') { 63 | params['unique'] = true; 64 | } 65 | self.collection(descr.model.modelName).ensureIndex(fields, params); 66 | } 67 | }); 68 | }, 1000); 69 | }; 70 | 71 | TingoDB.prototype.autoupdate = function (callback) { 72 | var self = this; 73 | var settings = self.settings; 74 | if (!fs.existsSync(settings.database)) { 75 | console.log('Database directory not exists ' + settings.database + ', please create!'); 76 | return callback && callback(); 77 | } else { 78 | setTimeout(function () { 79 | return callback && callback(); 80 | }, 1000); 81 | } 82 | }; 83 | 84 | TingoDB.prototype.defineProperty = function (model, prop, params) { 85 | this._models[model].properties[prop] = params; 86 | }; 87 | 88 | TingoDB.prototype.collection = function (name) { 89 | var self = this; 90 | if (!self.collections[name]) { 91 | self.collections[name] = self.client.collection(self.client, name); 92 | } 93 | return self.collections[name]; 94 | }; 95 | 96 | TingoDB.prototype.ensureIndex = function (model, fields, params, callback) { 97 | this.collection(model).ensureIndex(fields, params); 98 | return callback(null); 99 | }; 100 | 101 | TingoDB.prototype.create = function (model, data, callback) { 102 | if (data.id === null) { 103 | delete data.id; 104 | } 105 | this.collection(model).insert(data, {}, function (err, m) { 106 | return callback && callback(err, err ? null : m[0]._id); 107 | }); 108 | }; 109 | 110 | TingoDB.prototype.save = function (model, data, callback) { 111 | var id = data.id; 112 | this.collection(model).update({_id: id}, data, function (err) { 113 | callback(err); 114 | }); 115 | }; 116 | 117 | TingoDB.prototype.update = function (model, filter, data, callback) { 118 | if ('function' === typeof filter) { 119 | return filter(new Error("Get parametrs undefined"), null); 120 | } 121 | if ('function' === typeof data) { 122 | return data(new Error("Set parametrs undefined"), null); 123 | } 124 | filter = filter.where ? filter.where : filter; 125 | this.collection(model).update(filter, data, function (err) { 126 | callback(err); 127 | }); 128 | }; 129 | 130 | TingoDB.prototype.exists = function (model, id, callback) { 131 | this.collection(model).findOne({_id: id}, function (err, data) { 132 | callback(err, !err && data); 133 | }); 134 | }; 135 | 136 | TingoDB.prototype.findById = function findById(model, id, callback) { 137 | this.collection(model).findOne({_id: id}, function (err, data) { 138 | if (data) { 139 | data.id = id; 140 | } 141 | callback(err, data); 142 | }); 143 | }; 144 | 145 | TingoDB.prototype.updateOrCreate = function updateOrCreate(model, data, callback) { 146 | var adapter = this; 147 | if (!data.id) { 148 | return this.create(data, callback); 149 | } 150 | this.findById(model, data.id, function (err, inst) { 151 | if (err) 152 | return callback(err); 153 | if (inst) { 154 | adapter.updateAttributes(model, data.id, data, callback); 155 | } else { 156 | delete data.id; 157 | adapter.create(model, data, function (err, id) { 158 | if (err) 159 | return callback(err); 160 | if (id) { 161 | data.id = id; 162 | delete data._id; 163 | callback(null, data); 164 | } else { 165 | callback(null, null); // wtf? 166 | } 167 | }); 168 | } 169 | }); 170 | }; 171 | 172 | TingoDB.prototype.destroy = function destroy(model, id, callback) { 173 | this.collection(model).remove({_id: id}, callback); 174 | }; 175 | 176 | TingoDB.prototype.remove = function remove(model, filter, callback) { 177 | var cond = buildWhere(filter.where); 178 | this.collection(model).remove(cond, callback); 179 | }; 180 | 181 | TingoDB.prototype.all = function all(model, filter, callback) { 182 | if (!filter) { 183 | filter = {}; 184 | } 185 | var query = {}; 186 | if (filter.where) { 187 | query = buildWhere(filter.where); 188 | } 189 | var cursor = this.collection(model).find(query); 190 | 191 | if (filter.order) { 192 | var keys = filter.order; 193 | if (typeof keys === 'string') { 194 | keys = keys.split(','); 195 | } 196 | var args = {}; 197 | for (var index in keys) { 198 | var m = keys[index].match(/\s+(A|DE)SC$/); 199 | var key = keys[index]; 200 | key = key.replace(/\s+(A|DE)SC$/, '').trim(); 201 | if (m && m[1] === 'DE') { 202 | args[key] = -1; 203 | } else { 204 | args[key] = 1; 205 | } 206 | } 207 | cursor.sort(args); 208 | } 209 | if (filter.limit) { 210 | cursor.limit(filter.limit); 211 | } 212 | if (filter.skip) { 213 | cursor.skip(filter.skip); 214 | } else if (filter.offset) { 215 | cursor.skip(filter.offset); 216 | } 217 | cursor.toArray(function (err, data) { 218 | if (err) 219 | return callback(err); 220 | callback(null, data.map(function (o) { 221 | o.id = o._id; 222 | return o; 223 | })); 224 | }); 225 | }; 226 | 227 | TingoDB.prototype.destroyAll = function destroyAll(model, callback) { 228 | this.collection(model).remove({}, callback); 229 | }; 230 | 231 | TingoDB.prototype.count = function count(model, callback, filter) { 232 | var cond = buildWhere(filter); 233 | this.collection(model).count(cond, callback); 234 | }; 235 | 236 | TingoDB.prototype.updateAttributes = function updateAttrs(model, id, data, callback) { 237 | this.collection(model).findAndModify({_id: id}, [['_id', 'asc']], {$set: data}, {}, callback); 238 | }; 239 | 240 | TingoDB.prototype.disconnect = function () { 241 | this.client.close(); 242 | }; 243 | 244 | function buildWhere(filter) { 245 | var query = {}; 246 | Object.keys(filter).forEach(function (k) { 247 | var cond = filter[k]; 248 | var spec = false; 249 | if (k === 'id') { 250 | k = '_id'; 251 | } 252 | if (cond && cond.constructor.name === 'Object') { 253 | spec = Object.keys(cond)[0]; 254 | cond = cond[spec]; 255 | } 256 | if (spec) { 257 | if (spec === 'between') { 258 | query[k] = {$gte: cond[0], $lte: cond[1]}; 259 | } else { 260 | query[k] = {}; 261 | spec = spec === 'inq' ? 'in' : spec; 262 | spec = spec === 'like' ? 'regex' : spec; 263 | if (spec === 'nlike') { 264 | query[k]['$not'] = new RegExp(cond, 'i'); 265 | } else { 266 | query[k]['$' + spec] = cond; 267 | } 268 | } 269 | } else { 270 | if (cond === null) { 271 | query[k] = {$type: 10}; 272 | } else { 273 | query[k] = cond; 274 | } 275 | } 276 | }); 277 | return query; 278 | } 279 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/biggora/caminte.svg?branch=master)](https://travis-ci.org/biggora/caminte) 2 | [![Dependency Status](https://gemnasium.com/biggora/caminte.svg)](https://gemnasium.com/biggora/caminte) 3 | [![NPM version](https://badge.fury.io/js/caminte.svg)](http://badge.fury.io/js/caminte) 4 | ## About CaminteJS 5 | 6 | CaminteJS is cross-db ORM for nodejs, providing common interface to access 7 | most popular database formats. 8 | 9 | #### CaminteJS adapters: 10 | mysql, sqlite3, riak, postgres, couchdb, mongodb, redis, neo4j, firebird, rethinkdb, tingodb 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 | 33 | ## Installation 34 | 35 | First install [node.js](http://nodejs.org/). Then: 36 | 37 | $ npm install caminte --save 38 | 39 | 40 | ## Overview 41 | 42 | * [Command line interface](https://github.com/biggora/caminte#cli) 43 | * [Usage](https://github.com/biggora/caminte#usage) 44 | * [Connecting to DB](https://github.com/biggora/caminte/wiki/Connecting-to-DB#connecting) 45 | * [Defining a Model](https://github.com/biggora/caminte/wiki/Defining-a-Model#define-model) 46 | * [Define Indices](https://github.com/biggora/caminte/wiki/Defining-a-Model#define-indices) 47 | * [Define Primary Keys](https://github.com/biggora/caminte/wiki/Defining-a-Model#define-primary-keys) 48 | * [Schema data types](https://github.com/biggora/caminte/wiki/Schema-data-types#types) 49 | * [Accessing a Model](https://github.com/biggora/caminte/wiki/Defining-a-Model#accessing-a-model) 50 | * [Setup Relationships](https://github.com/biggora/caminte/wiki/Setup-Relationships-&-Validations#setup-relationships) 51 | * [Setup Validations](https://github.com/biggora/caminte/wiki/Setup-Relationships-&-Validations#setup-validations) 52 | * [Common API methods](https://github.com/biggora/caminte/wiki/Common-API-methods#api) 53 | * [Define any Custom Method](https://github.com/biggora/caminte/wiki/Common-API-methods#custom) 54 | * [Query Interface](https://github.com/biggora/caminte/wiki/Query-Interface#queries) 55 | * [Middleware (Hooks)](https://github.com/biggora/caminte/wiki/Middleware#middleware) 56 | * [Object lifecycle](https://github.com/biggora/caminte/wiki/Object-lifecycle#lifecycle) 57 | * [Your own database adapter](https://github.com/biggora/caminte/wiki/Your-own-database-adapter#adapter) 58 | * [Running tests](https://github.com/biggora/caminte/wiki/Running-tests) 59 | 60 | 61 | ## Online model creator 62 | 63 | Create CaminteJS Models in few minutes with [online model creator](http://www.camintejs.com/en/creator). 64 | 65 | ## CLI 66 | 67 | Use the command line interface tool, `caminte`, to quickly create an models. 68 | 69 | $ npm install caminte-cli -g 70 | 71 | Create structure: 72 | 73 | $ caminte -i -a mysql 74 | 75 | Create model: 76 | 77 | $ caminte -m User active:int name email password note:text created:date 78 | # with tests 79 | $ caminte -t -m User active:int name email password note:text created:date 80 | 81 | Create Tables: 82 | 83 | After created models, you can enable env `AUTOUPDATE` to true, when app initialize, this try create tables structures on database. 84 | 85 | Create model and routes: 86 | 87 | $ caminte -c Post published:bool title content:text created:date 88 | # with tests 89 | $ caminte -t -c User active:int name email password note:text created:date 90 | 91 | 92 | Create model and routes from SQL dump: 93 | 94 | $ caminte -d dumpfile.sql 95 | 96 | [caminte-cli more details.](https://github.com/biggora/caminte-cli) 97 | 98 | 99 | ## Usage 100 | 101 | ```javascript 102 | var caminte = require('caminte'); 103 | var Schema = caminte.Schema; 104 | var schema = new Schema('redis', {port: 6379}); 105 | 106 | // define models 107 | var Post = schema.define('Post', { 108 | title: { type: schema.String, limit: 255 }, 109 | userId: { type: schema.Number }, 110 | content: { type: schema.Text }, 111 | created: { type: schema.Date, default: Date.now }, 112 | updated: { type: schema.Date }, 113 | published: { type: schema.Boolean, default: false, index: true } 114 | }); 115 | 116 | var User = schema.define('User', { 117 | name: { type: schema.String, limit: 255 }, 118 | bio: { type: schema.Text }, 119 | email: { type: schema.String, limit: 155, unique: true }, 120 | approved: { type: schema.Boolean, default: false, index: true } 121 | joinedAt: { type: schema.Date, default: Date.now }, 122 | age: { type: schema.Number }, 123 | gender: { type: schema.String, limit: 10 } 124 | }); 125 | 126 | // setup hooks 127 | Post.afterUpdate = function (next) { 128 | this.updated = new Date(); 129 | this.save(); 130 | next(); 131 | }; 132 | 133 | // define any custom method for instance 134 | User.prototype.getNameAndAge = function () { 135 | return this.name + ', ' + this.age; 136 | }; 137 | 138 | // define scope 139 | Post.scope('active', { published : true }); 140 | 141 | // setup validations 142 | User.validatesPresenceOf('name', 'email'); 143 | User.validatesUniquenessOf('email', {message: 'email is not unique'}); 144 | User.validatesInclusionOf('gender', {in: ['male', 'female']}); 145 | User.validatesNumericalityOf('age', {int: true}); 146 | 147 | // setup relationships 148 | User.hasMany(Post, {as: 'posts', foreignKey: 'userId'}); 149 | 150 | // Common API methods 151 | 152 | var user = new User({ 153 | name: 'Alex', 154 | email: 'example@domain.aga', 155 | age: 40, 156 | gender: 'male' 157 | }); 158 | 159 | user.isValid(function (valid) { 160 | if (!valid) { 161 | return console.log(user.errors); 162 | } 163 | user.save(function(err){ 164 | if (!err) { 165 | return console.log(err); 166 | } 167 | console.log('User created'); 168 | }); 169 | }) 170 | 171 | // just instantiate model 172 | new Post 173 | // save model (of course async) 174 | Post.create(cb); 175 | // all posts 176 | Post.all(cb) 177 | // all posts by user 178 | Post.all({where: {userId: user.id}, order: 'id', limit: 10, skip: 20}); 179 | // the same as prev 180 | user.posts(cb) 181 | // get one latest post 182 | Post.findOne({where: {published: true}, order: 'date DESC'}, cb); 183 | // same as new Post({userId: user.id}); 184 | user.posts.build 185 | // save as Post.create({userId: user.id}, cb); 186 | user.posts.create(cb) 187 | // find instance by id 188 | User.findById(1, cb) 189 | // count instances 190 | User.count([conditions, ]cb) 191 | // destroy instance 192 | user.destroy(cb); 193 | // destroy all instances 194 | User.destroyAll(cb); 195 | 196 | // models also accessible in schema: 197 | schema.models.User; 198 | schema.models.Post; 199 | ``` 200 | 201 | ## Package structure 202 | 203 | Now all common logic described in `./lib/*.js`, and database-specific stuff in `./lib/adapters/*.js`. It's super-tiny, right? 204 | 205 | ## Contributing 206 | 207 | If you have found a bug please write unit test, and make sure all other tests still pass before pushing code to repo. 208 | 209 | ## Recommend extensions 210 | 211 | - [TrinteJS - Javascrpt MVC Framework for Node.JS](http://www.trintejs.com/) 212 | - [Cross-db Session Storage for ExpressJS](https://github.com/biggora/connect-caminte) 213 | - [MongoDB Session Storage for ExpressJS](https://github.com/biggora/express-mongodb) 214 | - [Middleware exposing user-agent for NodeJS](https://github.com/biggora/express-useragent) 215 | - [Uploading files middleware for NodeJS](https://github.com/biggora/express-uploader) 216 | - [2CO NodeJS adapter for 2checkout API payment gateway](https://github.com/biggora/2co) 217 | 218 | ## License 219 | 220 | (The MIT License) 221 | 222 | Copyright (c) 2011 by Anatoliy Chakkaev 223 | 224 | Permission is hereby granted, free of charge, to any person obtaining 225 | a copy of this software and associated documentation files (the 226 | 'Software'), to deal in the Software without restriction, including 227 | without limitation the rights to use, copy, modify, merge, publish, 228 | distribute, sublicense, and/or sell copies of the Software, and to 229 | permit persons to whom the Software is furnished to do so, subject to 230 | the following conditions: 231 | 232 | The above copyright notice and this permission notice shall be 233 | included in all copies or substantial portions of the Software. 234 | 235 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 236 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 237 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 238 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 239 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 240 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 241 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 242 | 243 | 244 | ## Resources 245 | 246 | - Visit the [author website](http://www.gordejev.lv). 247 | - Visit the [CaminteJS](http://www.camintejs.com) home page. 248 | - Follow [@biggora](https://twitter.com/#!/biggora) on Twitter for updates. 249 | - Report issues on the [github issues](https://github.com/biggora/caminte/issues) page. 250 | 251 | [![Analytics](https://ga-beacon.appspot.com/UA-22788134-5/caminte/readme)](https://github.com/igrigorik/ga-beacon) 252 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/biggora/caminte/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 253 | 254 | -------------------------------------------------------------------------------- /test/model/user.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User Integration Test 3 | * Created by caminte-cli script 4 | **/ 5 | /*global 6 | describe, before, after, it 7 | */ 8 | if (!process.env.NODE_ENV) { 9 | process.env.NODE_ENV = 'test'; 10 | } 11 | var driver = process.env.CAMINTE_DRIVER || 'sqlite'; 12 | var should = require('should'); 13 | var caminte = require('../../'); 14 | var config = require('./../lib/database'); 15 | var samples = require('./../lib/data'); 16 | var dbConf = config[driver]; 17 | var userModel = require('./../lib/User'); 18 | var Schema = caminte.Schema; 19 | dbConf.host = process.env.DB_HOST || dbConf.host || ''; 20 | var schema = new Schema(dbConf.driver, dbConf); 21 | var User = userModel(schema); 22 | 23 | describe(driver + ' - User model:', function () { 24 | 'use strict'; 25 | var id, newUser = samples.users[0]; 26 | 27 | before(function (done) { 28 | setTimeout(function() { 29 | schema.autoupdate(function () { 30 | return done && done(); 31 | }); 32 | }, 500); 33 | }); 34 | 35 | after(function (done) { 36 | User.destroyAll(done); 37 | }); 38 | 39 | it('#create', function (done) { 40 | User.create(newUser, function (err, created) { 41 | should.not.exist(err); 42 | created.should.be.have.property('id'); 43 | created.id.should.not.eql(null); 44 | id = created.id; 45 | done(); 46 | }); 47 | }); 48 | 49 | it('#exists', function (done) { 50 | User.exists(id, function (err, exists) { 51 | should.not.exist(err); 52 | exists.should.be.true; 53 | done(); 54 | }); 55 | }); 56 | 57 | it('#findById', function (done) { 58 | User.findById(id, function (err, found) { 59 | should.not.exist(err); 60 | found.id.should.deepEqual(id); 61 | done(); 62 | }); 63 | }); 64 | 65 | it('#findOne', function (done) { 66 | User.findOne({ 67 | where: { 68 | email: newUser.email 69 | } 70 | }, function (err, found) { 71 | should.not.exist(err); 72 | should.deepEqual(found.id, id); 73 | found.language.should.eql(newUser.language); 74 | found.email.should.eql(newUser.email); 75 | done(); 76 | }); 77 | }); 78 | 79 | it('#find', function (done) { 80 | User.find({}, function (err, founds) { 81 | should.not.exist(err); 82 | founds.should.length(1); 83 | done(); 84 | }); 85 | }); 86 | 87 | it('#all', function (done) { 88 | User.all({}, function (err, founds) { 89 | should.not.exist(err); 90 | founds.should.length(1); 91 | done(); 92 | }); 93 | }); 94 | 95 | it('#count', function (done) { 96 | User.count({}, function (err, count) { 97 | should.not.exist(err); 98 | count.should.equal(1); 99 | done(); 100 | }); 101 | }); 102 | /* 103 | it('#upsert', function () { 104 | User.should.be.have.property('upsert'); 105 | User.upsert.should.be.type('function'); 106 | }); 107 | */ 108 | it('#destroyById', function (done) { 109 | User.destroyById(id, function (err) { 110 | should.not.exist(err); 111 | User.findById(id, function (err, found) { 112 | should.not.exist(err); 113 | should.not.exist(found); 114 | done(); 115 | }); 116 | }); 117 | }); 118 | 119 | it('#destroyAll', function (done) { 120 | User.destroyAll(function (err) { 121 | should.not.exist(err); 122 | User.find({}, function (err, founds) { 123 | should.not.exist(err); 124 | founds.should.length(0); 125 | done(); 126 | }); 127 | }); 128 | }); 129 | /* 130 | describe('properties methods:', function () { 131 | 132 | it('#toString', function () { 133 | User.should.be.have.property('toString'); 134 | User.toString.should.be.type('function'); 135 | }); 136 | 137 | it('#forEachProperty', function () { 138 | User.should.be.have.property('forEachProperty'); 139 | User.forEachProperty.should.be.type('function'); 140 | }); 141 | 142 | it('#registerProperty', function () { 143 | User.should.be.have.property('registerProperty'); 144 | User.registerProperty.should.be.type('function'); 145 | }); 146 | 147 | }); 148 | 149 | describe('scope methods:', function () { 150 | 151 | it('#scope', function () { 152 | User.should.be.have.property('scope'); 153 | User.scope.should.be.type('function'); 154 | }); 155 | 156 | }); 157 | 158 | describe('query methods:', function () { 159 | 160 | it('#create', function () { 161 | User.should.be.have.property('create'); 162 | User.create.should.be.type('function'); 163 | }); 164 | 165 | it('#exists', function () { 166 | User.should.be.have.property('exists'); 167 | User.exists.should.be.type('function'); 168 | }); 169 | 170 | it('#count', function () { 171 | User.should.be.have.property('count'); 172 | User.count.should.be.type('function'); 173 | }); 174 | 175 | it('#findOrCreate', function () { 176 | User.should.be.have.property('findOrCreate'); 177 | User.findOrCreate.should.be.type('function'); 178 | }); 179 | 180 | it('#findById', function () { 181 | User.should.be.have.property('findById'); 182 | User.findById.should.be.type('function'); 183 | }); 184 | 185 | it('#findOne', function () { 186 | User.should.be.have.property('findOne'); 187 | User.findOne.should.be.type('function'); 188 | }); 189 | 190 | it('#find', function () { 191 | User.should.be.have.property('find'); 192 | User.find.should.be.type('function'); 193 | }); 194 | 195 | it('#all', function () { 196 | User.should.be.have.property('all'); 197 | User.all.should.be.type('function'); 198 | }); 199 | 200 | it('#run', function () { 201 | User.should.be.have.property('run'); 202 | User.run.should.be.type('function'); 203 | }); 204 | 205 | it('#exec', function () { 206 | User.should.be.have.property('exec'); 207 | User.exec.should.be.type('function'); 208 | }); 209 | 210 | it('#update', function () { 211 | User.should.be.have.property('update'); 212 | User.update.should.be.type('function'); 213 | }); 214 | 215 | it('#updateOrCreate', function () { 216 | User.should.be.have.property('updateOrCreate'); 217 | User.updateOrCreate.should.be.type('function'); 218 | }); 219 | 220 | it('#upsert', function () { 221 | User.should.be.have.property('upsert'); 222 | User.upsert.should.be.type('function'); 223 | }); 224 | 225 | it('#destroyAll', function () { 226 | User.should.be.have.property('destroyAll'); 227 | User.destroyAll.should.be.type('function'); 228 | }); 229 | 230 | it('#destroyById', function () { 231 | User.should.be.have.property('destroyById'); 232 | User.destroyById.should.be.type('function'); 233 | }); 234 | 235 | it('#remove', function () { 236 | User.should.be.have.property('remove'); 237 | User.remove.should.be.type('function'); 238 | }); 239 | 240 | }); 241 | 242 | describe('relations methods:', function () { 243 | it('#hasMany', function () { 244 | User.should.be.have.property('hasMany'); 245 | User.hasMany.should.be.type('function'); 246 | }); 247 | it('#belongsTo', function () { 248 | User.should.be.have.property('belongsTo'); 249 | User.hasMany.should.be.type('function'); 250 | }); 251 | }); 252 | 253 | describe('validations methods:', function () { 254 | 255 | it('#validate', function () { 256 | User.should.be.have.property('validate'); 257 | User.validate.should.be.type('function'); 258 | }); 259 | 260 | it('#validatesPresenceOf', function () { 261 | User.should.be.have.property('validatesPresenceOf'); 262 | User.validatesPresenceOf.should.be.type('function'); 263 | }); 264 | 265 | it('#validatesLengthOf', function () { 266 | User.should.be.have.property('validatesLengthOf'); 267 | User.validatesLengthOf.should.be.type('function'); 268 | }); 269 | 270 | it('#validatesNumericalityOf', function () { 271 | User.should.be.have.property('validatesNumericalityOf'); 272 | User.validatesNumericalityOf.should.be.type('function'); 273 | }); 274 | 275 | it('#validatesInclusionOf', function () { 276 | User.should.be.have.property('validatesInclusionOf'); 277 | User.validatesInclusionOf.should.be.type('function'); 278 | }); 279 | 280 | it('#validatesInclusionOf', function () { 281 | User.should.be.have.property('validatesInclusionOf'); 282 | User.validatesInclusionOf.should.be.type('function'); 283 | }); 284 | 285 | it('#validatesFormatOf', function () { 286 | User.should.be.have.property('validatesFormatOf'); 287 | User.validatesFormatOf.should.be.type('function'); 288 | }); 289 | 290 | it('#validatesUniquenessOf', function () { 291 | User.should.be.have.property('validatesUniquenessOf'); 292 | User.validatesUniquenessOf.should.be.type('function'); 293 | }); 294 | 295 | it('#validateAsync', function () { 296 | User.should.be.have.property('validateAsync'); 297 | User.validateAsync.should.be.type('function'); 298 | }); 299 | 300 | }); 301 | 302 | describe('hook methods:', function () { 303 | 304 | it('#afterInitialize', function () { 305 | User.should.be.have.property('afterInitialize'); 306 | // User.afterInitialize.should.be.type('function'); 307 | }); 308 | 309 | it('#beforeValidation', function () { 310 | User.should.be.have.property('beforeValidation'); 311 | // User.afterInitialize.should.be.type('function'); 312 | }); 313 | 314 | it('#afterValidation', function () { 315 | User.should.be.have.property('afterValidation'); 316 | }); 317 | 318 | it('#beforeSave', function () { 319 | User.should.be.have.property('beforeSave'); 320 | }); 321 | 322 | it('#afterSave', function () { 323 | User.should.be.have.property('afterSave'); 324 | }); 325 | 326 | it('#beforeCreate', function () { 327 | User.should.be.have.property('beforeCreate'); 328 | }); 329 | 330 | it('#afterCreate', function () { 331 | User.should.be.have.property('afterCreate'); 332 | }); 333 | 334 | it('#beforeUpdate', function () { 335 | User.should.be.have.property('beforeUpdate'); 336 | }); 337 | 338 | it('#afterUpdate', function () { 339 | User.should.be.have.property('afterUpdate'); 340 | }); 341 | 342 | it('#beforeDestroy', function () { 343 | User.should.be.have.property('beforeDestroy'); 344 | }); 345 | 346 | it('#afterDestroy', function () { 347 | User.should.be.have.property('afterDestroy'); 348 | }); 349 | }); 350 | */ 351 | }); 352 | -------------------------------------------------------------------------------- /lib/adapters/couchbase.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | */ 4 | var uuid = require('uuid'); 5 | var utils = require('../utils'); 6 | var safeRequire = utils.safeRequire; 7 | var helpers = utils.helpers; 8 | var couchbase = safeRequire('couchbase'); 9 | var CouchBase; 10 | 11 | exports.initialize = function (schema, callback) { 12 | var db, opts; 13 | opts = schema.settings || {}; 14 | 15 | if (!opts.url) { 16 | var host = opts.host || 'localhost'; 17 | var port = opts.port || '8091'; 18 | var database = opts.database || 'test'; 19 | var proto = opts.ssl ? 'couchbases' : 'couchbase'; 20 | opts.url = proto + '://' + host + ':' + port; 21 | } 22 | schema.client = new couchbase.Cluster(opts.url); 23 | db = schema.client.openBucket(database); 24 | schema.adapter = new CouchBase(schema.client, db); 25 | 26 | process.nextTick(function () { 27 | schema.adapter.db = schema.client.openBucket(database); 28 | return callback && callback(); 29 | }.bind(this)); 30 | }; 31 | 32 | function CouchBase(client, db, callback) { 33 | this.name = 'couchbase'; 34 | this.client = client; 35 | this.db = db; 36 | this._models = {}; 37 | } 38 | 39 | CouchBase.prototype.define = function (descr) { 40 | var m, self = this; 41 | m = descr.model.modelName; 42 | descr.properties._rev = { 43 | type: String 44 | }; 45 | var design = { 46 | views: { 47 | all: { 48 | map: 'function (doc, meta) { if (doc._type === "' + m.toLowerCase() + '") { return emit(doc._type, doc); } }', 49 | reduce: '_count' 50 | } 51 | } 52 | }; 53 | return self.db.manager().insertDesignDocument('caminte_' + m.toLowerCase(), design, function (err, doc) { 54 | return self.db.get('caminte_' + m.toLowerCase() + '_counter', function (err, doc) { 55 | if (!doc) { 56 | self.db.insert('caminte_' + m.toLowerCase() + '_counter', 0, function () { 57 | return self._models[m] = descr; 58 | }); 59 | } else { 60 | return self._models[m] = descr; 61 | } 62 | }); 63 | }); 64 | }; 65 | 66 | CouchBase.prototype.create = function (model, data, callback) { 67 | var self = this; 68 | data._type = model.toLowerCase(); 69 | helpers.savePrep(data); 70 | return self.db.counter('caminte_' + data._type + '_counter', +1, function (err, res) { 71 | if (err) { 72 | console.log('create counter for ' + data._type + ' failed', err); 73 | } 74 | var uid = res && res.value ? (data._type + '_' + res.value) : uuid.v1(); 75 | var key = data.id || uid; 76 | data.id = key; 77 | return self.db.upsert(key, self.forDB(model, data), function (err, doc) { 78 | return callback(err, key); 79 | }); 80 | }); 81 | }; 82 | 83 | CouchBase.prototype.save = function (model, data, callback) { 84 | var self = this; 85 | data._type = model.toLowerCase(); 86 | helpers.savePrep(data); 87 | var uid = uuid.v1(); 88 | var key = data.id || data._id || uid; 89 | if (data.id) { 90 | delete data.id; 91 | } 92 | if (data._id) { 93 | delete data._id; 94 | } 95 | return self.db.replace(key, self.forDB(model, data), function (err, doc) { 96 | return callback(err, key); 97 | }); 98 | }; 99 | 100 | CouchBase.prototype.updateOrCreate = function (model, data, callback) { 101 | var self = this; 102 | return self.exists(model, data.id, function (err, exists) { 103 | if (exists) { 104 | return self.save(model, data, callback); 105 | } else { 106 | return self.create(model, data, function (err, id) { 107 | data.id = id; 108 | return callback(err, data); 109 | }); 110 | } 111 | }); 112 | }; 113 | 114 | CouchBase.prototype.exists = function (model, id, callback) { 115 | return this.db.get(id, function (err, doc) { 116 | if (err) { 117 | return callback(null, false); 118 | } 119 | return callback(null, doc); 120 | }); 121 | }; 122 | 123 | CouchBase.prototype.findById = function (model, id, callback) { 124 | var self = this; 125 | return self.db.get(id, function (err, data) { 126 | var doc = data && (data.doc || data.value) ? (data.doc || data.value) : null; 127 | if (doc) { 128 | if (doc._type) { 129 | delete doc._type; 130 | } 131 | doc = self.fromDB(model, doc); 132 | if (doc._id) { 133 | doc.id = doc._id; 134 | delete doc._id; 135 | } 136 | } 137 | return callback(err, doc); 138 | }); 139 | }; 140 | 141 | CouchBase.prototype.destroy = function (model, id, callback) { 142 | var self = this; 143 | return self.db.remove(id, function (err, doc) { 144 | if (err) { 145 | return callback(err); 146 | } 147 | callback.removed = true; 148 | return callback(); 149 | }); 150 | }; 151 | 152 | CouchBase.prototype.updateAttributes = function (model, id, data, callback) { 153 | var self = this; 154 | return self.findById(model, id, function (err, base) { 155 | if (err) { 156 | return callback(err); 157 | } 158 | if (base) { 159 | data = helpers.merge(base, data); 160 | data.id = id; 161 | } 162 | return self.save(model, data, callback); 163 | }); 164 | }; 165 | 166 | CouchBase.prototype.count = function (model, callback, where) { 167 | var self = this; 168 | var query = new couchbase.ViewQuery() 169 | .from('caminte_' + model, 'all') 170 | .reduce(true) 171 | .stale(1) 172 | .include_docs(true); 173 | return self.db.query(query, function (err, body) { 174 | return callback(err, docs.length); 175 | }); 176 | }; 177 | 178 | CouchBase.prototype.destroyAll = function (model, callback) { 179 | var self = this; 180 | return self.all(model, {}, function (err, docs) { 181 | return callback(err, docs); 182 | }); 183 | }; 184 | 185 | CouchBase.prototype.forDB = function (model, data) { 186 | var k, props, v; 187 | if (data === null) { 188 | data = {}; 189 | } 190 | props = this._models[model].properties; 191 | for (k in props) { 192 | v = props[k]; 193 | if (data[k] && props[k].type.name === 'Date' 194 | && (data[k].getTime !== null) 195 | && (typeof data[k].getTime === 'function')) { 196 | data[k] = data[k].getTime(); 197 | } 198 | } 199 | return data; 200 | }; 201 | 202 | CouchBase.prototype.fromDB = function (model, data) { 203 | var date, k, props, v; 204 | if (!data) { 205 | return data; 206 | } 207 | props = this._models[model].properties; 208 | for (k in props) { 209 | v = props[k]; 210 | if ((data[k] !== null) && props[k].type.name === 'Date') { 211 | date = new Date(data[k]); 212 | date.setTime(data[k]); 213 | data[k] = date; 214 | } 215 | } 216 | return data; 217 | }; 218 | 219 | CouchBase.prototype.remove = function (model, filter, callback) { 220 | var self = this; 221 | return self.all(model, filter, function (err, docs) { 222 | var doc; 223 | console.log(docs) 224 | // return _this.db.bulk({ 225 | // docs: docs 226 | // }, function (err, body) { 227 | return callback(err, docs); 228 | // }); 229 | }); 230 | }; 231 | /* 232 | CouchBase.prototype.destroyById = function destroyById(model, id, callback) { 233 | var self = this; 234 | return self.db.remove(id, function (err, doc) { 235 | console.log(err, doc) 236 | return callback(err, doc); 237 | }); 238 | }; 239 | */ 240 | CouchBase.prototype.all = function (model, filter, callback) { 241 | if ('function' === typeof filter) { 242 | callback = filter; 243 | filter = {}; 244 | } 245 | if (!filter) { 246 | filter = {}; 247 | } 248 | var self = this; 249 | var query = new couchbase.ViewQuery() 250 | .from('caminte_' + model, 'all') 251 | .reduce(false) 252 | .include_docs(true); 253 | 254 | if (filter.order) { 255 | if (/desc/gi.test()) { 256 | query.order(couchbase.ViewQuery.Order.DESCENDING); 257 | } 258 | // query.order(filter.order); 259 | } 260 | if (filter.skip) { 261 | query.skip(filter.skip); 262 | } 263 | if (filter.limit) { 264 | query.limit(filter.limit); 265 | } 266 | if (filter.where) { 267 | query.custom(filter.where); 268 | } 269 | 270 | return self.db.query(query, function (err, body) { 271 | var doc, docs, i, k, key, orders, row, sorting, v, where, _i, _len; 272 | if (err) { 273 | if (err.statusCode == 404) { 274 | return err; 275 | } else { 276 | return err; 277 | } 278 | } 279 | docs = body.map(function (row) { 280 | var item = row.value; 281 | item.id = row.id; 282 | return item; 283 | }); 284 | // console.log('docs:', docs) 285 | where = filter !== null ? filter.where : void 0; 286 | if (where) { 287 | docs = docs ? docs.filter(helpers.applyFilter(filter)) : docs; 288 | } 289 | 290 | orders = filter !== null ? filter.order : void 0; 291 | if (orders) { 292 | if (typeof orders === 'string') { 293 | orders = [orders]; 294 | } 295 | sorting = function (a, b) { 296 | var ak, bk, i, item, rev, _i, _len; 297 | for (i = _i = 0, _len = this.length; _i < _len; i = ++_i) { 298 | item = this[i]; 299 | ak = a[this[i].key]; 300 | bk = b[this[i].key]; 301 | rev = this[i].reverse; 302 | if (ak > bk) { 303 | return 1 * rev; 304 | } 305 | if (ak < bk) { 306 | return -1 * rev; 307 | } 308 | } 309 | return 0; 310 | }; 311 | for (i = _i = 0, _len = orders.length; _i < _len; i = ++_i) { 312 | key = orders[i]; 313 | orders[i] = { 314 | reverse: helpers.reverse(key), 315 | key: helpers.stripOrder(key) 316 | }; 317 | } 318 | docs.sort(sorting.bind(orders)); 319 | } 320 | 321 | return callback(err, (function () { 322 | var _j, _len1, _results; 323 | _results = []; 324 | for (_j = 0, _len1 = docs.length; _j < _len1; _j++) { 325 | doc = docs[_j]; 326 | _results.push(this.fromDB(model, doc)); 327 | } 328 | return _results; 329 | }).call(self)); 330 | }); 331 | }; 332 | 333 | CouchBase.prototype.autoupdate = function (callback) { 334 | this.client.manager().createBucket(database, {}, function (err) { 335 | if (err) console.log('createBucket', err) 336 | return callback && callback(); 337 | }); 338 | }; 339 | -------------------------------------------------------------------------------- /lib/adapters/nano.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | */ 4 | var url = require('url'); 5 | var utils = require('../utils'); 6 | var safeRequire = utils.safeRequire; 7 | var helpers = utils.helpers; 8 | var nano = safeRequire('nano'); 9 | var NanoAdapter; 10 | 11 | exports.initialize = function (schema, callback) { 12 | var db, server, opts, srvuri, database; 13 | opts = schema.settings || {}; 14 | 15 | if (!opts.url) { 16 | var host = opts.host || 'localhost'; 17 | var port = opts.port || '5984'; 18 | var proto = opts.ssl ? 'https' : 'http'; 19 | database = opts.database || 'test'; 20 | opts.url = proto + '://' + host + ':' + port + '/' + database; 21 | srvuri = proto + '://' + host + ':' + port; 22 | } else { 23 | var parsed_url = url.parse(opts.url); 24 | database = (parsed_url.path || parsed_url.pathname || '').replace(/\//g, ''); 25 | srvuri = (opts.url || '').replace(parsed_url.path, ''); 26 | } 27 | db = nano(opts); 28 | server = nano(srvuri); 29 | server.db.create(database, function (err, body) { 30 | if (!err) { 31 | console.log('database ' + database + ' created!'); 32 | } 33 | }); 34 | 35 | schema.adapter = new NanoAdapter(db, callback); 36 | 37 | }; 38 | 39 | function NanoAdapter(db, callback) { 40 | this.name = 'nano'; 41 | this.db = db; 42 | this.all = helpers.__bind(this.all, this); 43 | this.fromDB = helpers.__bind(this.fromDB, this); 44 | this.forDB = helpers.__bind(this.forDB, this); 45 | this.destroyAll = helpers.__bind(this.destroyAll, this); 46 | this.count = helpers.__bind(this.count, this); 47 | this.updateAttributes = helpers.__bind(this.updateAttributes, this); 48 | this.destroy = helpers.__bind(this.destroy, this); 49 | this.findById = helpers.__bind(this.findById, this); 50 | this.findOne = helpers.__bind(this.findOne, this); 51 | this.exists = helpers.__bind(this.exists, this); 52 | this.updateOrCreate = helpers.__bind(this.updateOrCreate, this); 53 | this.save = helpers.__bind(this.save, this); 54 | this.create = helpers.__bind(this.create, this); 55 | this.remove = helpers.__bind(this.remove, this); 56 | this.define = helpers.__bind(this.define, this); 57 | this._models = {}; 58 | process.nextTick(function () { 59 | callback(); 60 | }); 61 | } 62 | 63 | NanoAdapter.prototype.define = function (descr) { 64 | var m, self = this; 65 | m = descr.model.modelName; 66 | descr.properties._rev = { 67 | type: String 68 | }; 69 | 70 | var design = { 71 | views: { 72 | all: { 73 | map: 'function (doc) { if (doc.model === "' + m + '") { return emit(doc.model, doc); } }' 74 | } 75 | }, 76 | updates: { 77 | modify: "function (doc, req) { var fields = JSON.parse(req.body); for (var i in fields) { doc[i] = fields[i]; } return [doc, toJSON(doc)];}" 78 | } 79 | };// var resp = eval(uneval(doc)); 80 | return self.db.insert(design, '_design/caminte_' + m, function (err, doc) { 81 | return self._models[m] = descr; 82 | }); 83 | }; 84 | 85 | NanoAdapter.prototype.create = function (model, data, callback) { 86 | var _this = this; 87 | data.model = model; 88 | helpers.savePrep(data); 89 | return this.db.insert(this.forDB(model, data), function (err, doc) { 90 | if (err) { 91 | doc = data; 92 | console.log('### error create:', err.message, doc.id || doc._id, doc._rev) 93 | } 94 | return callback(err, doc.id, doc.rev); 95 | }); 96 | }; 97 | 98 | NanoAdapter.prototype.save = function (model, data, callback) { 99 | var _this = this, id; 100 | data.model = model; 101 | helpers.savePrep(data); 102 | var item = this.forDB(model, data); 103 | id = item._id; 104 | item.up = Date.now(); 105 | return this.db.insert(item, id, function (err, doc) { 106 | // return this.db.atomic("caminte_" + model, "modify", id, item, function (err, doc) { 107 | if (err && err.statusCode != 409) { 108 | console.log('### error save:', err) 109 | } else if (err && err.statusCode == 409) { 110 | doc = item; 111 | err = null; 112 | } 113 | return callback(err, doc.id, doc.rev); 114 | }); 115 | }; 116 | 117 | NanoAdapter.prototype.updateOrCreate = function (model, data, callback) { 118 | var _this = this; 119 | return this.exists(model, data.id, function (err, exists) { 120 | if (exists) { 121 | return _this.save(model, data, callback); 122 | } else { 123 | return _this.create(model, data, function (err, id) { 124 | data.id = id; 125 | return callback(err, data); 126 | }); 127 | } 128 | }); 129 | }; 130 | 131 | NanoAdapter.prototype.exists = function (model, id, callback) { 132 | return this.db.head(id, function (err, _, headers) { 133 | if (err) { 134 | return callback(null, false); 135 | } 136 | return callback(null, headers !== null); 137 | }); 138 | }; 139 | 140 | NanoAdapter.prototype.findById = function (model, id, callback) { 141 | var _this = this; 142 | return this.db.get(id, function (err, doc) { 143 | return callback(err, _this.fromDB(model, doc)); 144 | }); 145 | }; 146 | 147 | NanoAdapter.prototype.destroy = function (model, id, callback) { 148 | var _this = this; 149 | return this.db.get(id, function (err, doc) { 150 | if (err) { 151 | return callback(err); 152 | } 153 | return _this.db.destroy(id, doc._rev, function (err, doc) { 154 | if (err) { 155 | return callback(err); 156 | } 157 | callback.removed = true; 158 | return callback(); 159 | }); 160 | }); 161 | }; 162 | 163 | NanoAdapter.prototype.updateAttributes = function (model, id, data, callback) { 164 | var _this = this; 165 | return this.db.get(id, function (err, base) { 166 | if (err) { 167 | return callback(err); 168 | } 169 | return _this.save(model, helpers.merge(base, data), callback); 170 | }); 171 | }; 172 | 173 | NanoAdapter.prototype.count = function (model, callback, where) { 174 | var _this = this; 175 | return _this.all(model, { 176 | where: where 177 | }, function (err, docs) { 178 | return callback(err, docs.length); 179 | }); 180 | }; 181 | 182 | NanoAdapter.prototype.destroyAll = function (model, callback) { 183 | var _this = this; 184 | return _this.all(model, {}, function (err, docs) { 185 | var doc; 186 | docs = (function () { 187 | var _i, _len, _results; 188 | _results = []; 189 | for (_i = 0, _len = docs.length; _i < _len; _i++) { 190 | doc = docs[_i]; 191 | _results.push({ 192 | _id: doc.id, 193 | _rev: doc._rev, 194 | _deleted: true 195 | }); 196 | } 197 | return _results; 198 | })(); 199 | return _this.db.bulk({ 200 | docs: docs 201 | }, function (err, body) { 202 | return callback(err, body); 203 | }); 204 | }); 205 | }; 206 | 207 | NanoAdapter.prototype.forDB = function (model, data) { 208 | var k, props, v; 209 | if (data === null) { 210 | data = {}; 211 | } 212 | props = this._models[model].properties; 213 | for (k in props) { 214 | v = props[k]; 215 | if (data[k] && v.type.name === 'Date' 216 | && (data[k].getTime !== null) 217 | && (typeof data[k].getTime === 'function')) { 218 | data[k] = data[k].getTime(); 219 | } 220 | } 221 | for (f in data) { 222 | if (typeof data[f] === 'function') { 223 | delete data[f]; 224 | } 225 | } 226 | return data; 227 | }; 228 | 229 | NanoAdapter.prototype.fromDB = function (model, data) { 230 | var date, k, props, v; 231 | if (!data) { 232 | return data; 233 | } 234 | props = this._models[model].properties; 235 | for (k in props) { 236 | v = props[k]; 237 | if ((data[k] !== null) && props[k].type.name === 'Date') { 238 | date = new Date(data[k]); 239 | date.setTime(data[k]); 240 | data[k] = date; 241 | } 242 | } 243 | return data; 244 | }; 245 | 246 | NanoAdapter.prototype.remove = function (model, filter, callback) { 247 | var _this = this; 248 | return _this.all(model, filter, function (err, docs) { 249 | var doc; 250 | docs = (function () { 251 | var _i, _len, _results; 252 | _results = []; 253 | for (_i = 0, _len = docs.length; _i < _len; _i++) { 254 | doc = docs[_i]; 255 | _results.push({ 256 | _id: doc.id, 257 | _rev: doc._rev, 258 | _deleted: true 259 | }); 260 | } 261 | return _results; 262 | })(); 263 | return _this.db.bulk({ 264 | docs: docs 265 | }, function (err, body) { 266 | return callback(err, body); 267 | }); 268 | }); 269 | }; 270 | 271 | 272 | NanoAdapter.prototype.all = function (model, filter, callback) { 273 | if ('function' === typeof filter) { 274 | callback = filter; 275 | filter = {}; 276 | } 277 | if (!filter) { 278 | filter = {}; 279 | } 280 | var _this = this; 281 | var vopts = { 282 | include_docs: true 283 | }; 284 | 285 | return this.db.view('caminte_' + model, 'all', vopts, function (err, body) { 286 | var doc, docs, i, k, key, orders, row, sorting, v, where, _i, _len; 287 | if (err) console.log(err) 288 | docs = (function () { 289 | var _i, _len, _ref, _results; 290 | _ref = body.rows; 291 | _results = []; 292 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 293 | row = _ref[_i]; 294 | row.doc = row.value; 295 | row.doc.id = row.doc._id; 296 | delete row.doc._id; 297 | _results.push(row.doc); 298 | } 299 | return _results; 300 | })(); 301 | 302 | where = filter !== null ? filter.where : void 0; 303 | if (where) { 304 | docs = docs ? docs.filter(helpers.applyFilter(filter)) : docs; 305 | } 306 | 307 | orders = filter !== null ? filter.order : void 0; 308 | if (orders) { 309 | if (typeof orders === 'string') { 310 | orders = [orders]; 311 | } 312 | sorting = function (a, b) { 313 | var ak, bk, i, item, rev, _i, _len; 314 | for (i = _i = 0, _len = this.length; _i < _len; i = ++_i) { 315 | item = this[i]; 316 | ak = a[this[i].key]; 317 | bk = b[this[i].key]; 318 | rev = this[i].reverse; 319 | if (ak > bk) { 320 | return 1 * rev; 321 | } 322 | if (ak < bk) { 323 | return -1 * rev; 324 | } 325 | } 326 | return 0; 327 | }; 328 | for (i = _i = 0, _len = orders.length; _i < _len; i = ++_i) { 329 | key = orders[i]; 330 | orders[i] = { 331 | reverse: helpers.reverse(key), 332 | key: helpers.stripOrder(key) 333 | }; 334 | } 335 | docs.sort(sorting.bind(orders)); 336 | } 337 | 338 | return callback(err, (function () { 339 | var _j, _len1, _results; 340 | _results = []; 341 | for (_j = 0, _len1 = docs.length; _j < _len1; _j++) { 342 | doc = docs[_j]; 343 | _results.push(this.fromDB(model, doc)); 344 | } 345 | return _results; 346 | }).call(_this)); 347 | }); 348 | }; -------------------------------------------------------------------------------- /test/model/category.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Category Integration Test 3 | * Created by caminte-cli script 4 | **/ 5 | /*global 6 | describe, before, after, it 7 | */ 8 | if (!process.env.NODE_ENV) { 9 | process.env.NODE_ENV = 'test'; 10 | } 11 | var driver = process.env.CAMINTE_DRIVER || 'sqlite'; 12 | var should = require('should'); 13 | var caminte = require('../../'); 14 | var config = require('./../lib/database'); 15 | var samples = require('./../lib/data'); 16 | var dbConf = config[driver]; 17 | var categoryModel = require('./../lib/Category'); 18 | var Schema = caminte.Schema; 19 | dbConf.host = process.env.DB_HOST || dbConf.host || ''; 20 | var schema = new Schema(dbConf.driver, dbConf); 21 | var Category = categoryModel(schema); 22 | 23 | describe(driver + ' - Category model:', function () { 24 | 'use strict'; 25 | var id, newCategory = samples.categories[0]; 26 | 27 | before(function (done) { 28 | schema.autoupdate(function(){ 29 | return done && done(); 30 | }); 31 | }); 32 | 33 | after(function (done) { 34 | Category.destroyAll(done); 35 | }); 36 | 37 | it('#create', function (done) { 38 | Category.create(newCategory, function (err, created) { 39 | should.not.exist(err); 40 | created.should.be.have.property('id'); 41 | created.id.should.not.eql(null); 42 | created.category_id.should.eql(newCategory.category_id); 43 | created.section.should.eql(newCategory.section); 44 | created.title.should.eql(newCategory.title); 45 | id = created.id; 46 | done(); 47 | }); 48 | }); 49 | 50 | it('#exists', function (done) { 51 | Category.exists(id, function (err, exists) { 52 | should.not.exist(err); 53 | exists.should.be.true; 54 | done(); 55 | }); 56 | }); 57 | 58 | it('#findById', function (done) { 59 | Category.findById(id, function (err, found) { 60 | should.not.exist(err); 61 | found.id.should.deepEqual(id); 62 | done(); 63 | }); 64 | }); 65 | 66 | it('#findOne', function (done) { 67 | Category.findOne({ 68 | where: { 69 | section: newCategory.section 70 | } 71 | }, function (err, found) { 72 | should.not.exist(err); 73 | should.deepEqual(found.id, id); 74 | found.section.should.eql(newCategory.section); 75 | found.title.should.eql(newCategory.title); 76 | done(); 77 | }); 78 | }); 79 | 80 | it('#find', function (done) { 81 | Category.find({}, function (err, founds) { 82 | should.not.exist(err); 83 | founds.should.length(1); 84 | done(); 85 | }); 86 | }); 87 | 88 | it('#all', function (done) { 89 | Category.all({}, function (err, founds) { 90 | should.not.exist(err); 91 | founds.should.length(1); 92 | done(); 93 | }); 94 | }); 95 | 96 | it('#count', function (done) { 97 | Category.count({}, function (err, count) { 98 | should.not.exist(err); 99 | count.should.equal(1); 100 | done(); 101 | }); 102 | }); 103 | 104 | it('#destroyById', function (done) { 105 | Category.destroyById(id, function (err) { 106 | should.not.exist(err); 107 | Category.findById(id, function (err, found) { 108 | should.not.exist(err); 109 | should.not.exist(found); 110 | done(); 111 | }); 112 | }); 113 | }); 114 | 115 | it('#destroyAll', function (done) { 116 | Category.destroyAll(function (err) { 117 | should.not.exist(err); 118 | Category.find({}, function (err, founds) { 119 | should.not.exist(err); 120 | founds.should.length(0); 121 | done(); 122 | }); 123 | }); 124 | }); 125 | /* 126 | describe('properties methods:', function () { 127 | 128 | it('#toString', function () { 129 | Category.should.be.have.property('toString'); 130 | Category.toString.should.be.type('function'); 131 | }); 132 | 133 | it('#forEachProperty', function () { 134 | Category.should.be.have.property('forEachProperty'); 135 | Category.forEachProperty.should.be.type('function'); 136 | }); 137 | 138 | it('#registerProperty', function () { 139 | Category.should.be.have.property('registerProperty'); 140 | Category.registerProperty.should.be.type('function'); 141 | }); 142 | 143 | }); 144 | 145 | describe('scope methods:', function () { 146 | 147 | it('#scope', function () { 148 | Category.should.be.have.property('scope'); 149 | Category.scope.should.be.type('function'); 150 | }); 151 | 152 | }); 153 | 154 | describe('query methods:', function () { 155 | 156 | it('#create', function () { 157 | Category.should.be.have.property('create'); 158 | Category.create.should.be.type('function'); 159 | }); 160 | 161 | it('#exists', function () { 162 | Category.should.be.have.property('exists'); 163 | Category.exists.should.be.type('function'); 164 | }); 165 | 166 | it('#count', function () { 167 | Category.should.be.have.property('count'); 168 | Category.count.should.be.type('function'); 169 | }); 170 | 171 | it('#findOrCreate', function () { 172 | Category.should.be.have.property('findOrCreate'); 173 | Category.findOrCreate.should.be.type('function'); 174 | }); 175 | 176 | it('#findById', function () { 177 | Category.should.be.have.property('findById'); 178 | Category.findById.should.be.type('function'); 179 | }); 180 | 181 | it('#findOne', function () { 182 | Category.should.be.have.property('findOne'); 183 | Category.findOne.should.be.type('function'); 184 | }); 185 | 186 | it('#find', function () { 187 | Category.should.be.have.property('find'); 188 | Category.find.should.be.type('function'); 189 | }); 190 | 191 | it('#all', function () { 192 | Category.should.be.have.property('all'); 193 | Category.all.should.be.type('function'); 194 | }); 195 | 196 | it('#run', function () { 197 | Category.should.be.have.property('run'); 198 | Category.run.should.be.type('function'); 199 | }); 200 | 201 | it('#exec', function () { 202 | Category.should.be.have.property('exec'); 203 | Category.exec.should.be.type('function'); 204 | }); 205 | 206 | it('#update', function () { 207 | Category.should.be.have.property('update'); 208 | Category.update.should.be.type('function'); 209 | }); 210 | 211 | it('#updateOrCreate', function () { 212 | Category.should.be.have.property('updateOrCreate'); 213 | Category.updateOrCreate.should.be.type('function'); 214 | }); 215 | 216 | it('#upsert', function () { 217 | Category.should.be.have.property('upsert'); 218 | Category.upsert.should.be.type('function'); 219 | }); 220 | 221 | it('#destroyAll', function () { 222 | Category.should.be.have.property('destroyAll'); 223 | Category.destroyAll.should.be.type('function'); 224 | }); 225 | 226 | it('#destroyById', function () { 227 | Category.should.be.have.property('destroyById'); 228 | Category.destroyById.should.be.type('function'); 229 | }); 230 | 231 | it('#remove', function () { 232 | Category.should.be.have.property('remove'); 233 | Category.remove.should.be.type('function'); 234 | }); 235 | 236 | }); 237 | 238 | describe('relations methods:', function () { 239 | it('#hasMany', function () { 240 | Category.should.be.have.property('hasMany'); 241 | Category.hasMany.should.be.type('function'); 242 | }); 243 | it('#belongsTo', function () { 244 | Category.should.be.have.property('belongsTo'); 245 | Category.hasMany.should.be.type('function'); 246 | }); 247 | }); 248 | 249 | describe('validations methods:', function () { 250 | 251 | it('#validate', function () { 252 | Category.should.be.have.property('validate'); 253 | Category.validate.should.be.type('function'); 254 | }); 255 | 256 | it('#validatesPresenceOf', function () { 257 | Category.should.be.have.property('validatesPresenceOf'); 258 | Category.validatesPresenceOf.should.be.type('function'); 259 | }); 260 | 261 | it('#validatesLengthOf', function () { 262 | Category.should.be.have.property('validatesLengthOf'); 263 | Category.validatesLengthOf.should.be.type('function'); 264 | }); 265 | 266 | it('#validatesNumericalityOf', function () { 267 | Category.should.be.have.property('validatesNumericalityOf'); 268 | Category.validatesNumericalityOf.should.be.type('function'); 269 | }); 270 | 271 | it('#validatesInclusionOf', function () { 272 | Category.should.be.have.property('validatesInclusionOf'); 273 | Category.validatesInclusionOf.should.be.type('function'); 274 | }); 275 | 276 | it('#validatesInclusionOf', function () { 277 | Category.should.be.have.property('validatesInclusionOf'); 278 | Category.validatesInclusionOf.should.be.type('function'); 279 | }); 280 | 281 | it('#validatesFormatOf', function () { 282 | Category.should.be.have.property('validatesFormatOf'); 283 | Category.validatesFormatOf.should.be.type('function'); 284 | }); 285 | 286 | it('#validatesUniquenessOf', function () { 287 | Category.should.be.have.property('validatesUniquenessOf'); 288 | Category.validatesUniquenessOf.should.be.type('function'); 289 | }); 290 | 291 | it('#validateAsync', function () { 292 | Category.should.be.have.property('validateAsync'); 293 | Category.validateAsync.should.be.type('function'); 294 | }); 295 | 296 | }); 297 | 298 | describe('hook methods:', function () { 299 | 300 | it('#afterInitialize', function () { 301 | Category.should.be.have.property('afterInitialize'); 302 | // Category.afterInitialize.should.be.type('function'); 303 | }); 304 | 305 | it('#beforeValidation', function () { 306 | Category.should.be.have.property('beforeValidation'); 307 | // Category.afterInitialize.should.be.type('function'); 308 | }); 309 | 310 | it('#afterValidation', function () { 311 | Category.should.be.have.property('afterValidation'); 312 | }); 313 | 314 | it('#beforeSave', function () { 315 | Category.should.be.have.property('beforeSave'); 316 | }); 317 | 318 | it('#afterSave', function () { 319 | Category.should.be.have.property('afterSave'); 320 | }); 321 | 322 | it('#beforeCreate', function () { 323 | Category.should.be.have.property('beforeCreate'); 324 | }); 325 | 326 | it('#afterCreate', function () { 327 | Category.should.be.have.property('afterCreate'); 328 | }); 329 | 330 | it('#beforeUpdate', function () { 331 | Category.should.be.have.property('beforeUpdate'); 332 | }); 333 | 334 | it('#afterUpdate', function () { 335 | Category.should.be.have.property('afterUpdate'); 336 | }); 337 | 338 | it('#beforeDestroy', function () { 339 | Category.should.be.have.property('beforeDestroy'); 340 | }); 341 | 342 | it('#afterDestroy', function () { 343 | Category.should.be.have.property('afterDestroy'); 344 | }); 345 | }); 346 | */ 347 | }); 348 | -------------------------------------------------------------------------------- /lib/adapters/mongodb.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | */ 4 | var utils = require('../utils'); 5 | var safeRequire = utils.safeRequire; 6 | var mongodb = safeRequire('mongodb'); 7 | var mongoClient = mongodb.MongoClient; 8 | var ObjectID = mongodb.ObjectID; 9 | var url = require('url'); 10 | 11 | exports.initialize = function initializeSchema(schema, callback) { 12 | 'use strict'; 13 | if (!mongodb) { 14 | return; 15 | } 16 | var s = schema.settings; 17 | 18 | if (schema.settings.rs) { 19 | s.rs = schema.settings.rs; 20 | if (schema.settings.url) { 21 | var uris = schema.settings.url.split(','); 22 | s.hosts = []; 23 | s.ports = []; 24 | uris.forEach(function (uri) { 25 | var durl = url.parse(uri); 26 | 27 | s.hosts.push(durl.hostname || 'localhost'); 28 | s.ports.push(parseInt(durl.port || '27017', 10)); 29 | 30 | if (!s.database) 31 | s.database = durl.pathname.replace(/^\//, ''); 32 | if (!s.username) 33 | s.username = durl.auth && durl.auth.split(':')[0]; 34 | if (!s.password) 35 | s.password = durl.auth && durl.auth.split(':')[1]; 36 | }); 37 | } 38 | s.database = s.database || 'test'; 39 | } else { 40 | if (schema.settings.url) { 41 | var durl = url.parse(schema.settings.url); 42 | s.host = durl.hostname; 43 | s.port = durl.port; 44 | s.database = durl.pathname.replace(/^\//, ''); 45 | s.username = durl.auth && durl.auth.split(':')[0]; 46 | s.password = durl.auth && durl.auth.split(':')[1]; 47 | } 48 | s.host = s.host || 'localhost'; 49 | s.port = parseInt(s.port || '27017', 10); 50 | s.database = s.database || process.env.USER || 'test'; 51 | if (!s.url) { 52 | s.url = 'mongodb://' + s.host + ':' + s.port + '/' + s.database; 53 | } 54 | } 55 | 56 | s.safe = s.safe || false; 57 | schema.adapter = new MongoDB(s, schema, callback); 58 | schema.ObjectID = ObjectID; 59 | }; 60 | 61 | function MongoDB(s, schema, callback) { 62 | var self = this; 63 | self.name = 'mongodb'; 64 | self._models = {}; 65 | self._db = null; 66 | self.collections = {}; 67 | self.schema = schema; 68 | self.s = s; 69 | 70 | mongoClient.connect(s.url, function (err, client) { 71 | if (err) { console.log(err); } 72 | self.db = client.db(s.database); 73 | self.client = client; 74 | self.schema = schema; 75 | self.connection() 76 | .then(callback) 77 | .catch(callback); 78 | }.bind(this)); 79 | } 80 | 81 | MongoDB.prototype.connection = function () { 82 | var t = this; 83 | return new Promise(function (resolve, reject) { 84 | if (t.s.username && t.s.password) { 85 | t.client.authenticate(t.s.username, t.s.password, function (err, result) { 86 | if (err) { 87 | reject(err); 88 | } else { 89 | t.schema.client = t.client; 90 | resolve(); 91 | } 92 | }); 93 | } else { 94 | t.schema.client = t.client; 95 | resolve(); 96 | } 97 | }); 98 | }; 99 | 100 | MongoDB.prototype.define = function (descr) { 101 | var self = this; 102 | if (!descr.settings) { 103 | descr.settings = {}; 104 | } 105 | self._models[descr.model.modelName] = descr; 106 | self.connection().then(function (db) { 107 | Object.keys(descr.properties).forEach(function (k) { 108 | if (typeof descr.properties[k].index !== 'undefined' || typeof descr.properties[k].unique !== 'undefined') { 109 | var fields = {}, params = {}; 110 | fields[k] = 1; 111 | params['name'] = '_' + k + '_'; 112 | if (typeof descr.properties[k].unique !== 'undefined') { 113 | params['unique'] = true; 114 | } 115 | if (db) { 116 | self.db = db; 117 | } 118 | self.ensureIndex(descr.model.modelName, fields, params); 119 | } 120 | }); 121 | }) 122 | .catch(function (err) { 123 | console.log('define err:', self.db, err); 124 | }); 125 | }; 126 | 127 | MongoDB.prototype.defineProperty = function (model, prop, params) { 128 | this._models[model].properties[prop] = params; 129 | }; 130 | 131 | MongoDB.prototype.collection = function (name) { 132 | var collection = this._models[name].settings.collection || name; 133 | if (!this.collections[collection] && this.db) { 134 | this.collections[collection] = this.db.collection(collection); 135 | } 136 | return this.collections[collection]; 137 | }; 138 | 139 | MongoDB.prototype.ensureIndex = function (model, fields, params, callback) { 140 | var collection = this.collection(model); 141 | if (collection && collection.ensureIndex) { 142 | collection.ensureIndex(fields, params); 143 | } 144 | return callback && callback(null); 145 | }; 146 | 147 | MongoDB.prototype.create = function (model, data, callback) { 148 | if (data.id === null) { 149 | delete data.id; 150 | } 151 | this.collection(model).insert(data, {}, function (err, m) { 152 | var inserted; 153 | inserted = m[0] && m[0]._id ? m[0]._id : null; 154 | inserted = m.ops && m.ops[0] && m.ops[0]._id ? m.ops[0]._id : inserted; 155 | callback(err, err ? null : inserted); 156 | }); 157 | }; 158 | 159 | MongoDB.prototype.save = function (model, data, callback) { 160 | var id = data.id; 161 | id = getObjectId(id); 162 | this.collection(model).update({ _id: id }, data, function (err) { 163 | callback(err); 164 | }); 165 | }; 166 | /** 167 | * Update rows 168 | * @param {String} model 169 | * @param {Object} filter 170 | * @param {Object} data 171 | * @param {Function} callback 172 | */ 173 | MongoDB.prototype.update = function (model, filter, data, callback) { 174 | if ('function' === typeof filter) { 175 | return filter(new Error("Get parametrs undefined"), null); 176 | } 177 | if ('function' === typeof data) { 178 | return data(new Error("Set parametrs undefined"), null); 179 | } 180 | filter = filter.where ? filter.where : filter; 181 | if (filter.id) { 182 | var id = getObjectId(filter.id); 183 | filter.id = id; 184 | } 185 | this.collection(model).update(filter, { '$set': data }, { w: 1, multi: true }, function (err) { 186 | return callback && callback(err, 0); 187 | }); 188 | }; 189 | 190 | MongoDB.prototype.exists = function (model, id, callback) { 191 | id = getObjectId(id); 192 | this.collection(model).findOne({ _id: id }, function (err, data) { 193 | return callback && callback(err, !err && data); 194 | }); 195 | }; 196 | 197 | MongoDB.prototype.findById = function findById(model, id, callback) { 198 | var self = this; 199 | id = getObjectId(id); 200 | self.collection(model).findOne({ _id: id }, function (err, data) { 201 | if (data) { 202 | data.id = id; 203 | data = self.fromDatabase(model, data); 204 | } 205 | callback(err, data); 206 | }); 207 | }; 208 | 209 | MongoDB.prototype.updateOrCreate = function updateOrCreate(model, data, callback) { 210 | var self = this; 211 | if (!data.id) { 212 | return self.create(data, callback); 213 | } 214 | self.find(model, data.id, function (err, inst) { 215 | if (err) 216 | return callback(err); 217 | if (inst) { 218 | self.updateAttributes(model, data.id, data, callback); 219 | } else { 220 | delete data.id; 221 | self.create(model, data, function (err, id) { 222 | if (err) 223 | return callback(err); 224 | if (id) { 225 | data.id = id; 226 | delete data._id; 227 | callback(null, data); 228 | } else { 229 | callback(null, null); // wtf? 230 | } 231 | }); 232 | } 233 | }); 234 | }; 235 | 236 | MongoDB.prototype.destroy = function destroy(model, id, callback) { 237 | id = getObjectId(id); 238 | this.collection(model).remove({ _id: id }, callback); 239 | }; 240 | 241 | MongoDB.prototype.remove = function remove(model, filter, callback) { 242 | var cond = buildWhere(filter.where); 243 | this.collection(model).remove(cond, callback); 244 | }; 245 | 246 | MongoDB.prototype.all = MongoDB.prototype.find = function all(model, filter, callback) { 247 | if (!filter) { 248 | filter = {}; 249 | } 250 | var query = {}; 251 | if (filter.where) { 252 | query = buildWhere(filter.where); 253 | } 254 | var self = this, cursor = self.collection(model).find(query); 255 | 256 | if (filter.order) { 257 | var keys = filter.order; 258 | if (typeof keys === 'string') { 259 | keys = keys.split(','); 260 | } 261 | var args = {}; 262 | for (var index in keys) { 263 | var m = keys[index].match(/\s+(A|DE)SC$/); 264 | var key = keys[index]; 265 | key = key.replace(/\s+(A|DE)SC$/, '').trim(); 266 | if (m && m[1] === 'DE') { 267 | args[key] = -1; 268 | } else { 269 | args[key] = 1; 270 | } 271 | } 272 | cursor.sort(args); 273 | } 274 | if (filter.limit) { 275 | cursor.limit(filter.limit); 276 | } 277 | if (filter.skip || filter.offset) { 278 | cursor.skip(filter.skip || filter.offset); 279 | } 280 | cursor.toArray(function (err, data) { 281 | if (err) { 282 | return callback(err); 283 | } 284 | callback(null, data.map(function (o) { 285 | return self.fromDatabase(model, o); 286 | })); 287 | }); 288 | }; 289 | 290 | MongoDB.prototype.destroyAll = function destroyAll(model, callback) { 291 | this.collection(model).remove({}, callback); 292 | }; 293 | 294 | MongoDB.prototype.count = function count(model, callback, filter) { 295 | var cond = {}; 296 | if (filter && filter.where) { 297 | cond = buildWhere(filter.where); 298 | } else { 299 | cond = buildWhere(filter); 300 | } 301 | this.collection(model).count(cond, callback); 302 | }; 303 | 304 | MongoDB.prototype.updateAttributes = function updateAttrs(model, id, data, callback) { 305 | id = getObjectId(id); 306 | this.collection(model).findAndModify({ _id: id }, [['_id', 'asc']], { $set: data }, {}, callback); 307 | }; 308 | 309 | MongoDB.prototype.fromDatabase = function (model, data) { 310 | var props = this._models[model].properties; 311 | var clean = {}; 312 | Object.keys(data).forEach(function (key) { 313 | if (!props[key]) { 314 | return; 315 | } 316 | if (props[key].type.name.toString().toLowerCase() === 'date') { 317 | if (data[key]) { 318 | clean[key] = new Date(data[key]); 319 | } else { 320 | clean[key] = data[key]; 321 | } 322 | } else { 323 | clean[key] = data[key]; 324 | } 325 | }); 326 | clean.id = data._id; 327 | return clean; 328 | }; 329 | 330 | MongoDB.prototype.disconnect = function () { 331 | this.client.close(); 332 | }; 333 | 334 | function getObjectId(id) { 335 | if (typeof id === 'string') { 336 | id = new ObjectID(id); 337 | } else if (typeof id === 'object' && id.constructor === Array) { 338 | id = new ObjectID(id[0]); 339 | } 340 | return id; 341 | } 342 | 343 | function buildWhere(filter) { 344 | var query = {}; 345 | Object.keys(filter).forEach(function (k) { 346 | var cond = filter[k]; 347 | var spec = false; 348 | if (k === 'id') { 349 | k = '_id'; 350 | } 351 | 352 | if (k === 'or') { 353 | var arrcond = []; 354 | Object.keys(cond).forEach(function (k2) { 355 | var nval = {}; 356 | nval[k2] = cond[k2] 357 | arrcond.push(nval); 358 | }); 359 | query['$or'] = arrcond; 360 | return; 361 | } 362 | 363 | if (cond && cond.constructor.name === 'Object') { 364 | spec = Object.keys(cond)[0]; 365 | cond = cond[spec]; 366 | } 367 | if (spec) { 368 | if (spec === 'between') { 369 | query[k] = { $gte: cond[0], $lte: cond[1] }; 370 | } else { 371 | query[k] = {}; 372 | spec = spec === 'inq' ? 'in' : spec; 373 | spec = spec === 'like' ? 'regex' : spec; 374 | if (spec === 'nlike') { 375 | query[k]['$not'] = new RegExp(cond, 'i'); 376 | } else { 377 | query[k]['$' + spec] = cond; 378 | } 379 | } 380 | } else { 381 | if (cond === null) { 382 | query[k] = { $type: 10 }; 383 | } else { 384 | query[k] = cond; 385 | } 386 | } 387 | }); 388 | return query; 389 | } 390 | -------------------------------------------------------------------------------- /test/model/article.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Article Integration Test 3 | * Created by caminte-cli script 4 | **/ 5 | /*global 6 | describe, before, after, it 7 | */ 8 | if (!process.env.NODE_ENV) { 9 | process.env.NODE_ENV = 'test'; 10 | } 11 | var driver = process.env.CAMINTE_DRIVER || 'sqlite'; 12 | var should = require('should'); 13 | var caminte = require('../../'); 14 | var config = require('./../lib/database'); 15 | var samples = require('./../lib/data'); 16 | var dbConf = config[driver]; 17 | var articleModel = require('./../lib/Article'); 18 | var Schema = caminte.Schema; 19 | dbConf.host = process.env.DB_HOST || dbConf.host || ''; 20 | var schema = new Schema(dbConf.driver, dbConf); 21 | var Article = articleModel(schema); 22 | 23 | describe(driver + ' - Article model:', function () { 24 | 'use strict'; 25 | var id, newArticle = samples.articles[1]; 26 | 27 | before(function (done) { 28 | schema.autoupdate(function(){ 29 | return done && done(); 30 | }); 31 | }); 32 | 33 | after(function (done) { 34 | Article.destroyAll(function(){ 35 | return done && done(); 36 | }); 37 | }); 38 | 39 | it('#create', function (done) { 40 | Article.create(newArticle, function (err, created) { 41 | should.not.exist(err); 42 | created.should.be.have.property('id'); 43 | created.id.should.not.eql(null); 44 | created.category_id.should.eql(1); 45 | created.alias.should.eql(newArticle.alias); 46 | created.title.should.eql(newArticle.title); 47 | created.language.should.eql(newArticle.language); 48 | id = created.id; 49 | done(); 50 | }); 51 | }); 52 | 53 | it('#exists', function (done) { 54 | Article.exists(id, function (err, exists) { 55 | should.not.exist(err); 56 | exists.should.be.true; 57 | done(); 58 | }); 59 | }); 60 | 61 | it('#findById', function (done) { 62 | Article.findById(id, function (err, found) { 63 | should.not.exist(err); 64 | found.id.should.deepEqual(id); 65 | done(); 66 | }); 67 | }); 68 | 69 | it('#findOne', function (done) { 70 | Article.findOne({ 71 | where: { 72 | alias: newArticle.alias 73 | } 74 | }, function (err, found) { 75 | should.not.exist(err); 76 | should.deepEqual(found.id, id); 77 | found.alias.should.eql(newArticle.alias); 78 | done(); 79 | }); 80 | }); 81 | 82 | it('#find', function (done) { 83 | Article.find({}, function (err, founds) { 84 | should.not.exist(err); 85 | founds.should.length(1); 86 | done(); 87 | }); 88 | }); 89 | 90 | it('#all', function (done) { 91 | Article.all({}, function (err, founds) { 92 | should.not.exist(err); 93 | founds.should.length(1); 94 | done(); 95 | }); 96 | }); 97 | 98 | it('#update', function (done) { 99 | var title = 'Article_2'; 100 | Article.update({ 101 | alias: newArticle.alias 102 | }, { 103 | title: title, 104 | mainpage: 1 105 | }, function (err, affected) { 106 | should.not.exist(err); 107 | should.exist(affected); 108 | Article.findById(id, function (err, found) { 109 | should.not.exist(err); 110 | should.exist(found); 111 | found.alias.should.be.equal(newArticle.alias); 112 | found.title.should.be.exactly(title); 113 | found.mainpage.should.eql(1); 114 | done(); 115 | }); 116 | }); 117 | }); 118 | 119 | it('#findOrCreate', function (done) { 120 | Article.findOrCreate({ 121 | title: 'Article_3' 122 | }, { 123 | language: 'ru', 124 | category_id: 2, 125 | alias: 'my-article-3', 126 | mainpage: 0 127 | }, function (err, created) { 128 | should.not.exist(err); 129 | should.exist(created); 130 | Article.all({ 131 | where: { 132 | title: 'Article_3' 133 | } 134 | }, function (err, founds) { 135 | should.not.exist(err); 136 | founds.should.length(1); 137 | done(); 138 | }); 139 | }); 140 | }); 141 | 142 | it('#updateOrCreate', function (done) { 143 | Article.updateOrCreate({ 144 | title: 'Article_3' 145 | }, { 146 | alias: 'my-article-4', 147 | mainpage: 1 148 | }, function (err, updated) { 149 | should.not.exist(err); 150 | should.exist(updated); 151 | Article.all({ 152 | where: { 153 | alias: 'my-article-4' 154 | } 155 | }, function (err, founds) { 156 | should.not.exist(err); 157 | founds.should.length(1); 158 | done(); 159 | }); 160 | }); 161 | }); 162 | 163 | it('#count', function (done) { 164 | Article.count({}, function (err, count) { 165 | should.not.exist(err); 166 | count.should.equal(2); 167 | done(); 168 | }); 169 | }); 170 | 171 | it('#destroyById', function (done) { 172 | Article.destroyById(id, function (err) { 173 | should.not.exist(err); 174 | Article.findById(id, function (err, found) { 175 | should.not.exist(err); 176 | should.not.exist(found); 177 | done(); 178 | }); 179 | }); 180 | }); 181 | 182 | it('#destroyAll', function (done) { 183 | Article.destroyAll(function (err) { 184 | should.not.exist(err); 185 | Article.find({}, function (err, founds) { 186 | should.not.exist(err); 187 | founds.should.length(0); 188 | done(); 189 | }); 190 | }); 191 | }); 192 | 193 | 194 | /* 195 | describe('properties methods:', function () { 196 | 197 | it('#toString', function () { 198 | Article.should.be.have.property('toString'); 199 | Article.toString.should.be.type('function'); 200 | }); 201 | 202 | it('#forEachProperty', function () { 203 | Article.should.be.have.property('forEachProperty'); 204 | Article.forEachProperty.should.be.type('function'); 205 | }); 206 | 207 | it('#registerProperty', function () { 208 | Article.should.be.have.property('registerProperty'); 209 | Article.registerProperty.should.be.type('function'); 210 | }); 211 | 212 | }); 213 | 214 | describe('scope methods:', function () { 215 | 216 | it('#scope', function () { 217 | Article.should.be.have.property('scope'); 218 | Article.scope.should.be.type('function'); 219 | }); 220 | 221 | }); 222 | 223 | describe('query methods:', function () { 224 | 225 | it('#create', function () { 226 | Article.should.be.have.property('create'); 227 | Article.create.should.be.type('function'); 228 | }); 229 | 230 | it('#exists', function () { 231 | Article.should.be.have.property('exists'); 232 | Article.exists.should.be.type('function'); 233 | }); 234 | 235 | it('#count', function () { 236 | Article.should.be.have.property('count'); 237 | Article.count.should.be.type('function'); 238 | }); 239 | 240 | it('#findOrCreate', function () { 241 | Article.should.be.have.property('findOrCreate'); 242 | Article.findOrCreate.should.be.type('function'); 243 | }); 244 | 245 | it('#findById', function () { 246 | Article.should.be.have.property('findById'); 247 | Article.findById.should.be.type('function'); 248 | }); 249 | 250 | it('#findOne', function () { 251 | Article.should.be.have.property('findOne'); 252 | Article.findOne.should.be.type('function'); 253 | }); 254 | 255 | it('#find', function () { 256 | Article.should.be.have.property('find'); 257 | Article.find.should.be.type('function'); 258 | }); 259 | 260 | it('#all', function () { 261 | Article.should.be.have.property('all'); 262 | Article.all.should.be.type('function'); 263 | }); 264 | 265 | it('#run', function () { 266 | Article.should.be.have.property('run'); 267 | Article.run.should.be.type('function'); 268 | }); 269 | 270 | it('#exec', function () { 271 | Article.should.be.have.property('exec'); 272 | Article.exec.should.be.type('function'); 273 | }); 274 | 275 | it('#update', function () { 276 | Article.should.be.have.property('update'); 277 | Article.update.should.be.type('function'); 278 | }); 279 | 280 | it('#updateOrCreate', function () { 281 | Article.should.be.have.property('updateOrCreate'); 282 | Article.updateOrCreate.should.be.type('function'); 283 | }); 284 | 285 | it('#upsert', function () { 286 | Article.should.be.have.property('upsert'); 287 | Article.upsert.should.be.type('function'); 288 | }); 289 | 290 | it('#destroyAll', function () { 291 | Article.should.be.have.property('destroyAll'); 292 | Article.destroyAll.should.be.type('function'); 293 | }); 294 | 295 | it('#destroyById', function () { 296 | Article.should.be.have.property('destroyById'); 297 | Article.destroyById.should.be.type('function'); 298 | }); 299 | 300 | it('#remove', function () { 301 | Article.should.be.have.property('remove'); 302 | Article.remove.should.be.type('function'); 303 | }); 304 | 305 | }); 306 | 307 | describe('relations methods:', function () { 308 | it('#hasMany', function () { 309 | Article.should.be.have.property('hasMany'); 310 | Article.hasMany.should.be.type('function'); 311 | }); 312 | it('#belongsTo', function () { 313 | Article.should.be.have.property('belongsTo'); 314 | Article.hasMany.should.be.type('function'); 315 | }); 316 | }); 317 | 318 | describe('validations methods:', function () { 319 | 320 | it('#validate', function () { 321 | Article.should.be.have.property('validate'); 322 | Article.validate.should.be.type('function'); 323 | }); 324 | 325 | it('#validatesPresenceOf', function () { 326 | Article.should.be.have.property('validatesPresenceOf'); 327 | Article.validatesPresenceOf.should.be.type('function'); 328 | }); 329 | 330 | it('#validatesLengthOf', function () { 331 | Article.should.be.have.property('validatesLengthOf'); 332 | Article.validatesLengthOf.should.be.type('function'); 333 | }); 334 | 335 | it('#validatesNumericalityOf', function () { 336 | Article.should.be.have.property('validatesNumericalityOf'); 337 | Article.validatesNumericalityOf.should.be.type('function'); 338 | }); 339 | 340 | it('#validatesInclusionOf', function () { 341 | Article.should.be.have.property('validatesInclusionOf'); 342 | Article.validatesInclusionOf.should.be.type('function'); 343 | }); 344 | 345 | it('#validatesInclusionOf', function () { 346 | Article.should.be.have.property('validatesInclusionOf'); 347 | Article.validatesInclusionOf.should.be.type('function'); 348 | }); 349 | 350 | it('#validatesFormatOf', function () { 351 | Article.should.be.have.property('validatesFormatOf'); 352 | Article.validatesFormatOf.should.be.type('function'); 353 | }); 354 | 355 | it('#validatesUniquenessOf', function () { 356 | Article.should.be.have.property('validatesUniquenessOf'); 357 | Article.validatesUniquenessOf.should.be.type('function'); 358 | }); 359 | 360 | it('#validateAsync', function () { 361 | Article.should.be.have.property('validateAsync'); 362 | Article.validateAsync.should.be.type('function'); 363 | }); 364 | 365 | }); 366 | 367 | describe('hook methods:', function () { 368 | 369 | it('#afterInitialize', function () { 370 | Article.should.be.have.property('afterInitialize'); 371 | // Article.afterInitialize.should.be.type('function'); 372 | }); 373 | 374 | it('#beforeValidation', function () { 375 | Article.should.be.have.property('beforeValidation'); 376 | // Article.afterInitialize.should.be.type('function'); 377 | }); 378 | 379 | it('#afterValidation', function () { 380 | Article.should.be.have.property('afterValidation'); 381 | }); 382 | 383 | it('#beforeSave', function () { 384 | Article.should.be.have.property('beforeSave'); 385 | }); 386 | 387 | it('#afterSave', function () { 388 | Article.should.be.have.property('afterSave'); 389 | }); 390 | 391 | it('#beforeCreate', function () { 392 | Article.should.be.have.property('beforeCreate'); 393 | }); 394 | 395 | it('#afterCreate', function () { 396 | Article.should.be.have.property('afterCreate'); 397 | }); 398 | 399 | it('#beforeUpdate', function () { 400 | Article.should.be.have.property('beforeUpdate'); 401 | }); 402 | 403 | it('#afterUpdate', function () { 404 | Article.should.be.have.property('afterUpdate'); 405 | }); 406 | 407 | it('#beforeDestroy', function () { 408 | Article.should.be.have.property('beforeDestroy'); 409 | }); 410 | 411 | it('#afterDestroy', function () { 412 | Article.should.be.have.property('afterDestroy'); 413 | }); 414 | }); 415 | */ 416 | }); 417 | --------------------------------------------------------------------------------