diff --git a/Readme.md b/Readme.md index 06152fa2..606d70fe 100755 --- a/Readme.md +++ b/Readme.md @@ -1,830 +1,36 @@ -## Object Relational Mapping +###Bug-fix patch for [dresende/node-orm2](https://github.com/dresende/node-orm2) v2.1.3 -[![Build Status](https://secure.travis-ci.org/dresende/node-orm2.png?branch=master)](http://travis-ci.org/dresende/node-orm2) -[![](https://badge.fury.io/js/orm.png)](https://npmjs.org/package/orm) -[![](https://gemnasium.com/dresende/node-orm2.png)](https://gemnasium.com/dresende/node-orm2) -[![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=dresende&url=https://github.com/dresende/node-orm2&title=ORM&language=&tags=github&category=software) +##Details -## Install +Mysql only -```sh -npm install orm -``` +Using "unique: true" causes "ER_DUP_KEYNAME" on sync: [here](https://github.com/dresende/node-orm2/issues/326) -## Node.js Version Support +I just do this for easier on npm deployment. -Tests are done using [Travis CI](https://travis-ci.org/) for node versions `0.6.x`, `0.8.x` and `0.10.x`. If you want you can run -tests locally. +##Installation -```sh -npm test -``` + npm install orm-2.1.3 -## DBMS Support +##License +The MIT License (MIT) -- MySQL -- PostgreSQL -- Amazon Redshift -- SQLite -- MongoDB (beta, missing aggregation for now) +Copyright (c) 2013 Diogo Resende and other contributors -## Features +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -- Create Models, sync, drop, bulk create, get, find, remove, count, aggregated functions -- Create Model associations, find, check, create and remove -- Define custom validations (several builtin validations, check instance properties before saving - see [enforce](http://github.com/dresende/node-enforce) for details) -- Model instance caching and integrity (table rows fetched twice are the same object, changes to one change all) -- Plugins: [MySQL FTS](http://dresende.github.io/node-orm-mysql-fts) , [Pagination](http://dresende.github.io/node-orm-paging) , [Transaction](http://dresende.github.io/node-orm-transaction), [Timestamps](http://github.com/SPARTAN563/node-orm-timestamps), [Migrations](https://github.com/locomote/node-migrate-orm2) +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -## Introduction - -This is a node.js object relational mapping module. - -An example: - -```js -var orm = require("orm"); - -orm.connect("mysql://username:password@host/database", function (err, db) { - if (err) throw err; - - var Person = db.define("person", { - name : String, - surname : String, - age : Number, - male : Boolean, - continent : [ "Europe", "America", "Asia", "Africa", "Australia", "Antartica" ], // ENUM type - photo : Buffer, // BLOB/BINARY - data : Object // JSON encoded - }, { - methods: { - fullName: function () { - return this.name + ' ' + this.surname; - } - }, - validations: { - age: orm.enforce.ranges.number(18, undefined, "under-age") - } - }); - - Person.find({ surname: "Doe" }, function (err, people) { - // SQL: "SELECT * FROM person WHERE surname = 'Doe'" - - console.log("People found: %d", people.length); - console.log("First person: %s, age %d", people[0].fullName(), people[0].age); - - people[0].age = 16; - people[0].save(function (err) { - // err.msg = "under-age"; - }); - }); -}); -``` - -## Express - -If you're using Express, you might want to use the simple middleware to integrate more easily. - -```js -var express = require('express'); -var orm = require('orm'); -var app = express(); - -app.use(orm.express("mysql://username:password@host/database", { - define: function (db, models, next) { - models.person = db.define("person", { ... }); - next(); - } -})); -app.listen(80); - -app.get("/", function (req, res) { - // req.models is a reference to models used above in define() - req.models.person.find(...); -}); -``` - -You can call `orm.express` more than once to have multiple database connections. Models defined across connections -will be joined together in `req.models`. **Don't forget to use it before `app.use(app.router)`, preferably right after your -assets public folder(s).** - -## Examples - -See `examples/anontxt` for an example express based app. - -## Documentation - -Documentation is moving to the [wiki](https://github.com/dresende/node-orm2/wiki/). - -## Settings - -See information in the [wiki](https://github.com/dresende/node-orm2/wiki/Settings). - -## Connecting - -See information in the [wiki](https://github.com/dresende/node-orm2/wiki/Connecting-to-Database). - -## Models - -A Model is an abstraction over one or more database tables. Models support associations (more below). The name of the model is assumed to match the table name. - -Models support behaviours for accessing and manipulating table data. - -## Defining Models - -See information in the [wiki](https://github.com/dresende/node-orm2/wiki/Defining-Models). - -### Properties - -See information in the [wiki](https://github.com/dresende/node-orm2/wiki/Model-Properties). - -### Instance Methods - -Are passed in during model definition. - -```js -var Person = db.define('person', { - name : String, - surname : String -}, { - methods: { - fullName: function () { - return this.name + ' ' + this.surname; - } - } -}); - -Person.get(4, function(err, person) { - console.log( person.fullName() ); -}) -``` - -### Model Methods - -Are defined directly on the model. - -```js -var Person = db.define('person', { - name : String, - height : { type: 'number', rational: false } -}); -Person.tallerThan = function(height, callback) { - this.find({ height: orm.gt(height) }, callback); -}; - -Person.tallerThan( 192, function(err, tallPeople) { ... } ); -``` - - -## Loading Models - -Models can be in separate modules. Simply ensure that the module holding the models uses module.exports to publish a function that accepts the database connection, then load your models however you like. - -Note - using this technique you can have cascading loads. - -```js -// your main file (after connecting) -db.load("./models", function (err) { - // loaded! - var Person = db.models.person; - var Pet = db.models.pet; -}); - -// models.js -module.exports = function (db, cb) { - db.load("./models-extra", function (err) { - if (err) { - return cb(err); - } - - db.define('person', { - name : String - }); - - return cb(); - }); -}; - -// models-extra.js -module.exports = function (db, cb) { - db.define('pet', { - name : String - }); - - return cb(); -}; -``` - -## Synchronizing Models - -See information in the [wiki](https://github.com/dresende/node-orm2/wiki/Synching-and-Dropping-Models). - -## Dropping Models - -See information in the [wiki](https://github.com/dresende/node-orm2/wiki/Synching-and-Dropping-Models). - -## Advanced Options - -ORM2 allows you some advanced tweaks on your Model definitions. You can configure these via settings or in the call to `define` when you setup the Model. - -For example, each Model instance has a unique ID in the database. This table column is -by default "id" but you can change it. - -```js -var Person = db.define("person", { - name : String -}, { - id : "person_id" -}); - -// or just do it globally.. -db.settings.set("properties.primary_key", "UID"); - -// ..and then define your Models -var Pet = db.define("pet", { - name : String -}); -``` - -**Pet** model will have 2 columns, an `UID` and a `name`. - -It is also possible to have multiple IDs for a model in the database, this is done by specifying an array of IDs to use. - -```js -var Person = db.define("person", { - firstname: String, - lastname: String -}, { - id: ['firstname', 'lastname'] -}); -``` - -Other options: - -- `cache` : (default: `true`) Set it to `false` to disable Instance cache ([Singletons](#singleton)) or set a timeout value (in seconds); -- `autoSave` : (default: `false`) Set it to `true` to save an Instance right after changing any property; -- `autoFetch` : (default: `false`) Set it to `true` to fetch associations when fetching an instance from the database; -- `autoFetchLimit` : (default: `1`) If `autoFetch` is enabled this defines how many hoops (associations of associations) - you want it to automatically fetch. - -## Hooks - -See information in the [wiki](https://github.com/dresende/node-orm2/wiki/Model-Hooks). - -## Finding Items - -### Model.get(id, [ options ], cb) - -To get a specific element from the database use `Model.get`. - -```js -Person.get(123, function (err, person) { - // finds person with id = 123 -}); -``` - -### Model.find([ conditions ] [, options ] [, limit ] [, order ] [, cb ]) - -Finding one or more elements has more options, each one can be given in no specific parameter order. Only `options` has to be after `conditions` (even if it's an empty object). - -```js -Person.find({ name: "John", surname: "Doe" }, 3, function (err, people) { - // finds people with name='John' AND surname='Doe' and returns the first 3 -}); -``` - -If you need to sort the results because you're limiting or just because you want them sorted do: - -```js -Person.find({ surname: "Doe" }, "name", function (err, people) { - // finds people with surname='Doe' and returns sorted by name ascending -}); -Person.find({ surname: "Doe" }, [ "name", "Z" ], function (err, people) { - // finds people with surname='Doe' and returns sorted by name descending - // ('Z' means DESC; 'A' means ASC - default) -}); -``` - -There are more options that you can pass to find something. These options are passed in a second object: - -```js -Person.find({ surname: "Doe" }, { offset: 2 }, function (err, people) { - // finds people with surname='Doe', skips the first 2 and returns the others -}); -``` - -You can also use raw SQL when searching. It's documented in the *Chaining* section below. - -### Model.count([ conditions, ] cb) - -If you just want to count the number of items that match a condition you can just use `.count()` instead of finding all -of them and counting. This will actually tell the database server to do a count (it won't be done in the node process itself). - -```js -Person.count({ surname: "Doe" }, function (err, count) { - console.log("We have %d Does in our db", count); -}); -``` - -### Model.exists([ conditions, ] cb) - -Similar to `.count()`, this method just checks if the count is greater than zero or not. - -```js -Person.exists({ surname: "Doe" }, function (err, exists) { - console.log("We %s Does in our db", exists ? "have" : "don't have"); -}); -``` - -### Aggregating Functions - -If you need to get some aggregated values from a Model, you can use `Model.aggregate()`. Here's an example to better -illustrate: - -```js -Person.aggregate({ surname: "Doe" }).min("age").max("age").get(function (err, min, max) { - console.log("The youngest Doe guy has %d years, while the oldest is %d", min, max); -}); -``` - -An `Array` of properties can be passed to select only a few properties. An `Object` is also accepted to define conditions. - -Here's an example to illustrate how to use `.groupBy()`: - -```js -//The same as "select avg(weight), age from person where country='someCountry' group by age;" -Person.aggregate(["age"], { country: "someCountry" }).avg("weight").groupBy("age").get(function (err, stats) { - // stats is an Array, each item should have 'age' and 'avg_weight' -}); -``` - -### Base `.aggregate()` methods - -- `.limit()`: you can pass a number as a limit, or two numbers as offset and limit respectively -- `.order()`: same as `Model.find().order()` - -### Additional `.aggregate()` methods - -- `min` -- `max` -- `avg` -- `sum` -- `count` (there's a shortcut to this - `Model.count`) - -There are more aggregate functions depending on the driver (Math functions for example). - -#### Chaining - -If you prefer less complicated syntax you can chain `.find()` by not giving a callback parameter. - -```js -Person.find({ surname: "Doe" }).limit(3).offset(2).only("name", "surname").run(function (err, people) { - // finds people with surname='Doe', skips first 2 and limits to 3 elements, - // returning only 'name' and 'surname' properties -}); -``` - -Chaining allows for more complicated queries. For example, we can search by specifying custom SQL: -```js -Person.find({ age: 18 }).where("LOWER(surname) LIKE ?", ['dea%']).all( ... ); -``` -It's bad practice to manually escape SQL parameters as it's error prone and exposes your application to SQL injection. -The `?` syntax takes care of escaping for you, by safely substituting the question mark in the query with the parameters provided. -You can also chain multiple `where` clauses as needed. - -You can also `order` or `orderRaw`: -```js -Person.find({ age: 18 }).order('-name').all( ... ); -// see the 'Raw queries' section below for more details -Person.find({ age: 18 }).orderRaw("?? DESC", ['age']).all( ... ); -``` - -You can also chain and just get the count in the end. In this case, offset, limit and order are ignored. - -```js -Person.find({ surname: "Doe" }).count(function (err, people) { - // people = number of people with surname="Doe" -}); -``` - -Also available is the option to remove the selected items. - -```js -Person.find({ surname: "Doe" }).remove(function (err) { - // Does gone.. -}); -``` - -You can also make modifications to your instances using common Array traversal methods and save everything -in the end. - -```js -Person.find({ surname: "Doe" }).each(function (person) { - person.surname = "Dean"; -}).save(function (err) { - // done! -}); - -Person.find({ surname: "Doe" }).each().filter(function (person) { - return person.age >= 18; -}).sort(function (person1, person2) { - return person1.age < person2.age; -}).get(function (people) { - // get all people with at least 18 years, sorted by age -}); -``` - -Of course you could do this directly on `.find()`, but for some more complicated tasks this can be very usefull. - -`Model.find()` does not return an Array so you can't just chain directly. To start chaining you have to call -`.each()` (with an optional callback if you want to traverse the list). You can then use the common functions -`.filter()`, `.sort()` and `.forEach()` more than once. - -In the end (or during the process..) you can call: -- `.count()` if you just want to know how many items there are; -- `.get()` to retrieve the list; -- `.save()` to save all item changes. - -#### Conditions - -Conditions are defined as an object where every key is a property (table column). All keys are supposed -to be concatenated by the logical `AND`. Values are considered to match exactly, unless you're passing -an `Array`. In this case it is considered a list to compare the property with. - -```js -{ col1: 123, col2: "foo" } // `col1` = 123 AND `col2` = 'foo' -{ col1: [ 1, 3, 5 ] } // `col1` IN (1, 3, 5) -``` - -If you need other comparisons, you have to use a special object created by some helper functions. Here are -a few examples to describe it: - -```js -{ col1: orm.eq(123) } // `col1` = 123 (default) -{ col1: orm.ne(123) } // `col1` <> 123 -{ col1: orm.gt(123) } // `col1` > 123 -{ col1: orm.gte(123) } // `col1` >= 123 -{ col1: orm.lt(123) } // `col1` < 123 -{ col1: orm.lte(123) } // `col1` <= 123 -{ col1: orm.between(123, 456) } // `col1` BETWEEN 123 AND 456 -{ col1: orm.not_between(123, 456) } // `col1` NOT BETWEEN 123 AND 456 -{ col1: orm.like(12 + "%") } // `col1` like '12%' -``` - -#### Raw queries - -```js -db.driver.execQuery("SELECT id, email FROM user", function (err, data) { ... }) - -// You can escape identifiers and values. -// For identifier substitution use: ?? -// For value substitution use: ? -db.driver.execQuery( - "SELECT user.??, user.?? FROM user WHERE user.?? LIKE ? AND user.?? > ?", - ['id', 'name', 'name', 'john', 'id', 55], - function (err, data) { ... } -) - -// Identifiers don't need to be scaped most of the time -db.driver.execQuery( - "SELECT user.id, user.name FROM user WHERE user.name LIKE ? AND user.id > ?", - ['john', 55], - function (err, data) { ... } -) -``` - -### Caching & Integrity - -Model instances are cached. If multiple different queries will result in the same result, you will -get the same object. If you have other systems that can change your database (or you're developing and need -to make some manual changes) you should remove this feature by disabling cache. This can be done when you're -defining the Model. - -```js -var Person = db.define('person', { - name : String -}, { - cache : false -}); -``` - -and also globally: - -```js -orm.connect('...', function(err, db) { - db.settings.set('instance.cache', false); -}); -``` - -The cache can be configured to expire after a period of time by passing in a number instead of a -boolean. The number will be considered the cache timeout in seconds (you can use floating point). - -**Note**: One exception about Caching is that it won't be used if an instance is not saved. For example, if -you fetch a Person and then change it, while it doesn't get saved it won't be passed from Cache. - -## Creating Items - -### Model.create(items, cb) - -To insert new elements to the database use `Model.create`. - -```js -Person.create([ - { - name: "John", - surname: "Doe", - age: 25, - male: true - }, - { - name: "Liza", - surname: "Kollan", - age: 19, - male: false - } -], function (err, items) { - // err - description of the error or null - // items - array of inserted items -}); -``` - -## Updating Items - -Every item returned has the properties that were defined to the Model and also a couple of methods you can -use to change each item. - -```js -Person.get(1, function (err, John) { - John.name = "Joe"; - John.surname = "Doe"; - John.save(function (err) { - console.log("saved!"); - }); -}); -``` - -Updating and then saving an instance can be done in a single call: - -```js -Person.get(1, function (err, John) { - John.save({ name: "Joe", surname: "Doe" }, function (err) { - console.log("saved!"); - }); -}); -``` - -If you want to remove an instance, just do: - -```js -// you could do this without even fetching it, look at Chaining section above -Person.get(1, function (err, John) { - John.remove(function (err) { - console.log("removed!"); - }); -}); -``` - -## Validations - -See information in the [wiki](https://github.com/dresende/node-orm2/wiki/Model-Validations). - -## Associations - -An association is a relation between one or more tables. - -### hasOne - -Is a **many to one** relationship. It's the same as **belongs to.**
-Eg: `Animal.hasOne('owner', Person)`.
-Animal can only have one owner, but Person can have many animals.
-Animal will have the `owner_id` property automatically added. - -The following functions will become available: -```js -animal.getOwner(function..) // Gets owner -animal.setOwner(person, function..) // Sets owner_id -animal.hasOwner(function..) // Checks if owner exists -animal.removeOwner() // Sets owner_id to 0 -``` - -**Chain Find** - -The hasOne association is also chain find compatible. Using the example above, we can do this to access a new instance of a ChainFind object: - -```js -Animal.findByOwner({ /* options */ }) -``` - -**Reverse access** - -```js -Animal.hasOne('owner', Person, {reverse: 'pets'}) -``` - -will add the following: - -```js -// Instance methods -person.getPets(function..) -person.setPets(cat, function..) - -// Model methods -Person.findByPets({ /* options */ }) // returns ChainFind object -``` - -### hasMany - -Is a **many to many** relationship (includes join table).
-Eg: `Patient.hasMany('doctors', Doctor, { why: String }, { reverse: 'patients' })`.
-Patient can have many different doctors. Each doctor can have many different patients. - -This will create a join table `patient_doctors` when you call `Patient.sync()`: - - column name | type - :-----------|:-------- - patient_id | Integer - doctor_id | Integer - why | varchar(255) - -The following functions will be available: - -```js -patient.getDoctors(function..) // List of doctors -patient.addDoctors(docs, function...) // Adds entries to join table -patient.setDoctors(docs, function...) // Removes existing entries in join table, adds new ones -patient.hasDoctors(docs, function...) // Checks if patient is associated to specified doctors -patient.removeDoctors(docs, function...) // Removes specified doctors from join table - -doctor.getPatients(function..) -etc... -``` - -To associate a doctor to a patient: - -```js -patient.addDoctor(surgeon, {why: "remove appendix"}, function(err) { ... } ) -``` - -which will add `{patient_id: 4, doctor_id: 6, why: "remove appendix"}` to the join table. - -#### getAccessor - -This accessor in this type of association returns a `ChainFind` if not passing a callback. This means you can -do things like: - -```js -patient.getDoctors().order("name").offset(1).run(function (err, doctors), { - // ... all doctors, ordered by name, excluding first one -}); -``` - -### extendsTo - -If you want to split maybe optional properties into different tables or collections. Every extension will be in a new table, -where the unique identifier of each row is the main model instance id. For example: - -```js -var Person = db.define("person", { - name : String -}); -var PersonAddress = Person.extendsTo("address", { - street : String, - number : Number -}); -``` - -This will create a table `person` with columns `id` and `name`. The extension will create a table `person_address` with -columns `person_id`, `street` and `number`. The methods available in the `Person` model are similar to an `hasOne` -association. In this example you would be able to call `.getAddress(cb)`, `.setAddress(Address, cb)`, .. - -**Note:** you don't have to save the result from `Person.extendsTo`. It returns an extended model. You can use it to query -directly this extended table (and even find the related model) but that's up to you. If you only want to access it using the -original model you can just discard the return. - -### Examples & options - -If you have a relation of 1 to n, you should use `hasOne` (belongs to) association. - -```js -var Person = db.define('person', { - name : String -}); -var Animal = db.define('animal', { - name : String -}); -Animal.hasOne("owner", Person); // creates column 'owner_id' in 'animal' table - -// get animal with id = 123 -Animal.get(123, function (err, animal) { - // animal is the animal model instance, if found - animal.getOwner(function (err, person) { - // if animal has really an owner, person points to it - }); -}); -``` - -You can mark the `owner_id` field as required in the database by specifying the `required` option: -```js -Animal.hasOne("owner", Person, { required: true }); -``` - -If you prefer to use another name for the field (owner_id) you can change this parameter in the settings. - -```js -db.settings.set("properties.association_key", "{field}_{name}"); // {name} will be replaced by 'owner' and {field} will be replaced by 'id' in this case -``` - -**Note: This has to be done before the association is specified.** - -The `hasMany` associations can have additional properties in the association table. - -```js -var Person = db.define('person', { - name : String -}); -Person.hasMany("friends", { - rate : Number -}); - -Person.get(123, function (err, John) { - John.getFriends(function (err, friends) { - // assumes rate is another column on table person_friends - // you can access it by going to friends[N].extra.rate - }); -}); -``` - -If you prefer you can activate `autoFetch`. -This way associations are automatically fetched when you get or find instances of a model. - -```js -var Person = db.define('person', { - name : String -}); -Person.hasMany("friends", { - rate : Number -}, { - autoFetch : true -}); - -Person.get(123, function (err, John) { - // no need to do John.getFriends() , John already has John.friends Array -}); -``` - -You can also define this option globally instead of a per association basis. - -```js -var Person = db.define('person', { - name : String -}, { - autoFetch : true -}); -Person.hasMany("friends", { - rate : Number -}); -``` - -Associations can make calls to the associated Model by using the `reverse` option. For example, if you have an -association from ModelA to ModelB, you can create an accessor in ModelB to get instances from ModelA. -Confusing? Look at the next example. - -```js -var Pet = db.define('pet', { - name : String -}); -var Person = db.define('person', { - name : String -}); -Pet.hasOne("owner", Person, { - reverse : "pets" -}); - -Person(4).getPets(function (err, pets) { - // although the association was made on Pet, - // Person will have an accessor (getPets) - // - // In this example, ORM will fetch all pets - // whose owner_id = 4 -}); -``` - -This makes even more sense when having `hasMany` associations since you can manage the *many to many* -associations from both sides. - -```js -var Pet = db.define('pet', { - name : String -}); -var Person = db.define('person', { - name : String -}); -Person.hasMany("pets", Pet, { - bought : Date -}, { - reverse : "owners" -}); - -Person(1).getPets(...); -Pet(2).getOwners(...); -``` +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/examples/anontxt/Readme.md b/examples/anontxt/Readme.md deleted file mode 100644 index 84c3dec9..00000000 --- a/examples/anontxt/Readme.md +++ /dev/null @@ -1,31 +0,0 @@ -# AnonTXT demo app - -## Getting started - -**Setup node-orm2!** - -```bash -git clone https://github.com/dresende/node-orm2.git -cd node-orm2 -npm install - -# You may work off master, or checkout a different version if master is broken: -git tag -git checkout v2.1.3 -``` - -**Setup AnonTXT** - -Edit `anontxt/config/settings.js` to set your database, user & password. - -```bash -cd examples/anontxt -npm install -node tasks/reset -./script/start -``` - -And then open up [localhost:3000](http://localhost:3000/) - -You can also just run it with `node server.js` however the script launches nodemon which -automatically restarts the server if you change any code. diff --git a/examples/anontxt/app/controllers/_helpers.js b/examples/anontxt/app/controllers/_helpers.js deleted file mode 100644 index 07646636..00000000 --- a/examples/anontxt/app/controllers/_helpers.js +++ /dev/null @@ -1,15 +0,0 @@ - -module.exports = { - formatErrors: function(errorsIn) { - var errors = {}; - var a, e; - - for(a = 0; a < errorsIn.length; a++) { - e = errorsIn[a]; - - errors[e.property] = errors[e.property] || []; - errors[e.property].push(e.msg); - } - return errors; - } -}; diff --git a/examples/anontxt/app/controllers/comments_controller.js b/examples/anontxt/app/controllers/comments_controller.js deleted file mode 100644 index 91e04d17..00000000 --- a/examples/anontxt/app/controllers/comments_controller.js +++ /dev/null @@ -1,33 +0,0 @@ -var _ = require('lodash'); -var helpers = require('./_helpers'); -var orm = require('../../../../'); - -module.exports = { - create: function (req, res, next) { - var params = _.pick(req.body, 'author', 'body'); - - req.models.message.get(req.params.messageId, function (err, message) { - if (err) { - if (err.code == orm.ErrorCodes.NOT_FOUND) { - res.send(404, "Message not found"); - } else { - return next(err); - } - } - - params.message_id = message.id; - - req.models.comment.create(params, function (err, message) { - if(err) { - if(Array.isArray(err)) { - return res.send(200, { errors: helpers.formatErrors(err) }); - } else { - return next(err); - } - } - - return res.send(200, message.serialize()); - }); - }); - } -}; diff --git a/examples/anontxt/app/controllers/home_controller.js b/examples/anontxt/app/controllers/home_controller.js deleted file mode 100644 index 31aef457..00000000 --- a/examples/anontxt/app/controllers/home_controller.js +++ /dev/null @@ -1,5 +0,0 @@ -var settings = require('../../config/settings'); - -module.exports = function (req, res, next) { - res.sendfile(settings.path + '/public/index.html'); -}; diff --git a/examples/anontxt/app/controllers/index.js b/examples/anontxt/app/controllers/index.js deleted file mode 100644 index 45b77ad8..00000000 --- a/examples/anontxt/app/controllers/index.js +++ /dev/null @@ -1,6 +0,0 @@ - -module.exports = { - home : require('./home_controller'), - messages : require('./messages_controller'), - comments : require('./comments_controller') -}; diff --git a/examples/anontxt/app/controllers/messages_controller.js b/examples/anontxt/app/controllers/messages_controller.js deleted file mode 100644 index 80d5801b..00000000 --- a/examples/anontxt/app/controllers/messages_controller.js +++ /dev/null @@ -1,35 +0,0 @@ -var _ = require('lodash'); -var helpers = require('./_helpers'); -var orm = require('../../../../'); - -module.exports = { - list: function (req, res, next) { - req.models.message.find().limit(4).order('-id').all(function (err, messages) { - if (err) return next(err); - - var items = messages.map(function (m) { - return m.serialize(); - }); - - res.send({ items: items }); - }); - }, - create: function (req, res, next) { - var params = _.pick(req.body, 'title', 'body'); - - req.models.message.create(params, function (err, message) { - if(err) { - if(Array.isArray(err)) { - return res.send(200, { errors: helpers.formatErrors(err) }); - } else { - return next(err); - } - } - - return res.send(200, message.serialize()); - }); - }, - get: function (req, res, next) { - - } -}; diff --git a/examples/anontxt/app/models/comment.js b/examples/anontxt/app/models/comment.js deleted file mode 100644 index f83a42a5..00000000 --- a/examples/anontxt/app/models/comment.js +++ /dev/null @@ -1,28 +0,0 @@ -var moment = require('moment'); - -module.exports = function (orm, db) { - var Comment = db.define('comment', { - body : { type: 'text', required: true }, - createdAt : { type: 'date', required: true, time: true } - }, - { - hooks: { - beforeValidation: function () { - this.createdAt = new Date(); - } - }, - validations: { - body : orm.enforce.ranges.length(1, 1024) - }, - methods: { - serialize: function () { - return { - body : this.body, - createdAt : moment(this.createdAt).fromNow() - } - } - } - }); - - Comment.hasOne('message', db.models.message, { required: true, reverse: 'comments', autoFetch: true }); -}; diff --git a/examples/anontxt/app/models/index.js b/examples/anontxt/app/models/index.js deleted file mode 100644 index b9684f70..00000000 --- a/examples/anontxt/app/models/index.js +++ /dev/null @@ -1,22 +0,0 @@ -var orm = require('../../../../'); -var settings = require('../../config/settings'); - -var connection = null; - -function setup(db, cb) { - require('./message')(orm, db); - require('./comment')(orm, db); - - return cb(null, db); -} - -module.exports = function (cb) { - if (connection) return cb(null, connection); - - orm.connect(settings.database, function (err, db) { - if (err) return cb(err); - - db.settings.set('instance.returnAllErrors', true); - setup(db, cb); - }); -}; diff --git a/examples/anontxt/app/models/message.js b/examples/anontxt/app/models/message.js deleted file mode 100644 index a011f464..00000000 --- a/examples/anontxt/app/models/message.js +++ /dev/null @@ -1,45 +0,0 @@ -var moment = require('moment'); - -module.exports = function (orm, db) { - var Message = db.define('message', { - title : { type: 'text', required: true }, - body : { type: 'text', required: true, big: true }, - createdAt : { type: 'date', required: true, time: true } - }, - { - hooks: { - beforeValidation: function () { - this.createdAt = new Date(); - } - }, - validations: { - title: [ - orm.enforce.ranges.length(1, undefined, "must be atleast 1 letter long"), - orm.enforce.ranges.length(undefined, 96, "cannot be longer than 96 letters") - ], - body: [ - orm.enforce.ranges.length(1, undefined, "must be atleast 1 letter long"), - orm.enforce.ranges.length(undefined, 32768, "cannot be longer than 32768 letters") - ] - }, - methods: { - serialize: function () { - var comments; - - if (this.comments) { - comments = this.comments.map(function (c) { return c.serialize(); }); - } else { - comments = []; - } - - return { - id : this.id, - title : this.title, - body : this.body, - createdAt : moment(this.createdAt).fromNow(), - comments : comments - }; - } - } - }); -}; diff --git a/examples/anontxt/config/environment.js b/examples/anontxt/config/environment.js deleted file mode 100644 index 5296603a..00000000 --- a/examples/anontxt/config/environment.js +++ /dev/null @@ -1,24 +0,0 @@ -var path = require('path'); -var express = require('express'); -var settings = require('./settings'); -var models = require('../app/models/'); - -module.exports = function (app) { - app.configure(function () { - app.use(express.static(path.join(settings.path, 'public'))); - app.use(express.logger({ format: 'dev' })); - app.use(express.bodyParser()); - app.use(express.methodOverride()); - app.use(function (req, res, next) { - models(function (err, db) { - if (err) return next(err); - - req.models = db.models; - req.db = db; - - return next(); - }); - }), - app.use(app.router); - }); -}; diff --git a/examples/anontxt/config/routes.js b/examples/anontxt/config/routes.js deleted file mode 100644 index 10b4464c..00000000 --- a/examples/anontxt/config/routes.js +++ /dev/null @@ -1,10 +0,0 @@ - -var controllers = require('../app/controllers') - -module.exports = function (app) { - app.get( '/' , controllers.home); - app.get( '/messages' , controllers.messages.list); - app.post('/messages' , controllers.messages.create); - app.get( '/message/:id' , controllers.messages.get); - app.post('/message/:messageId/comments', controllers.comments.create); -}; diff --git a/examples/anontxt/config/settings.js b/examples/anontxt/config/settings.js deleted file mode 100644 index d332b285..00000000 --- a/examples/anontxt/config/settings.js +++ /dev/null @@ -1,16 +0,0 @@ -var path = require('path'); - -var settings = { - path : path.normalize(path.join(__dirname, '..')), - port : process.env.NODE_PORT || 3000, - database : { - protocol : "postgresql", // or "mysql" - query : { pool: true }, - host : "127.0.0.1", - database : "anontxt_dev", - user : "anontxt", - password : "apassword" - } -}; - -module.exports = settings; diff --git a/examples/anontxt/package.json b/examples/anontxt/package.json deleted file mode 100644 index 3b925038..00000000 --- a/examples/anontxt/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "AnonTXT", - "description": "Post text snippets, quickly and easily", - "version": "0.1.0", - "repository" : "http://github.com/dresende/node-orm2.git", - "dependencies": { - "express": "3.3.*", - "lodash": "2.3.0", - "nodemon": "0.7.10", - "moment": "2.4.0", - "pg": "2.6.2", - "colors": "0.6.2" - }, - "scripts": { - "start": "script/start", - "reset": "script/reset" - } -} diff --git a/examples/anontxt/public/app.css b/examples/anontxt/public/app.css deleted file mode 100644 index 95a31cb7..00000000 --- a/examples/anontxt/public/app.css +++ /dev/null @@ -1,128 +0,0 @@ - -*, *:before, *:after { - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; - box-sizing: border-box; -} - -body { - background: #eee; - font-family: "Segoe UI", Frutiger, "Frutiger Linotype", "Dejavu Sans", "Helvetica Neue", Arial, sans-serif; - font-height: 17px; -} - -#container { - width: 980px; - margin: 0 auto; - background: #fff; - border-radius: 4px; -} - -#header { - padding: 2em; -} -#header h1 { - margin: 0; - font-size: 5em; -} -#header h1 em { - color: #ed5; -} - -#main { - padding: 0 2em 2em 2em; -} -#main section { - margin-bottom: 2em; -} -#main h3 { - margin-top: 0; -} -#main form .entry { - margin-bottom: 1em; - line-height: 1.5em; -} -#main form .entry input, -#main form .entry textarea { - padding: 0.3em; -} -#main form .entry label { - display: block; -} -#main form .entry label .title { - display: block; -} - -#main .new form .entry.title input { - width: 50em; -} -#main .new form .entry.body textarea { - width: 50em; - height: 10em; -} -#main .new form button { - font-size: 1.5em; - padding: 0.3em 1em; - border-radius: 0.4em; -} -#main .alerts { - display: none; -} -#main .alerts > div { - padding: 0 0 1em 0; -} -#main .alerts .error { - color: red; -} -#main .alerts .info { - color: #6d6; -} -#main .latest .texts .message { - margin: 1em 0; - padding-bottom: 0.7em; - border: 1px solid #ddd; -} -#main .latest .texts .message:nth-child(n+5) { - display: none; -} -#main .latest .texts .message > h4 { - margin: 0; - padding: 0.4em; - background: #eee; - border-bottom: 1px solid #ddd; -} -#main .latest .texts .message .meta { - float: right; - padding: 0.4em 0.4em 0 0; - color: #777; -} -#main .latest .texts .message p { - margin: 0.5em; -} -#main .latest .texts .message .comments { - width: 500px; - margin-left: 2em; - font-size: 85%; -} -#main .latest .texts .message .comments h4 { - margin: 0; - padding: 0.4em; -} -#main .latest .texts .message .comments .comment { - border-top: 1px dotted #aaa; -} -#main .latest .texts .message .comments .new-comment .entry { - margin-bottom: 0; -} -#main .latest .texts .message .comments .new-comment textarea { - width: 20em; - height: 4em; -} - -#footer { - margin-top: 2em; - padding: 0.5em; - background: #9b9; - color: #fff; - text-align: center; -} diff --git a/examples/anontxt/public/app.js b/examples/anontxt/public/app.js deleted file mode 100644 index 2f4880c6..00000000 --- a/examples/anontxt/public/app.js +++ /dev/null @@ -1,162 +0,0 @@ - -var entityMap = { - "&": "&", - "<": "<", - ">": ">", - '"': '"', - "'": ''', - "/": '/' -}; - -function escapeHtml(string) { - return String(string).replace(/[&<>"'\/]/g, function (s) { - return entityMap[s]; - }); -} - -$(document).ready(function () { - var $alerts = $('#main .alerts'); - var $texts = $('#main section.latest .texts'); - var $newMessageForm = $('#main section.new form'); - - function showAlert(type, content) { - $alerts.hide(100, function () { - $alerts.html( - '
' + content + '
' - ).show(250); - }); - } - - function renderComment(com) { - var html = ''; - html += '
'; - html += '
' + com.createdAt + '
'; - html += '

' + escapeHtml(com.body) + '

'; - html += '
'; - return html; - } - - function renderComments(comments) { - var html = ''; - html += '
'; - html += '

Comments

'; - - for (var a = 0; a < comments.length; a++) { - html += renderComment(comments[a]); - } - html += '
'; - html += '
'; - html += '
- -
-
- -
- - - - diff --git a/examples/anontxt/script/start b/examples/anontxt/script/start deleted file mode 100644 index e672ef12..00000000 --- a/examples/anontxt/script/start +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash - -items="app config ../../node_modules/sql-query ../../lib server.js" - -watch="" -for i in $items; do - watch="$watch --watch $i" -done - -nodemon $watch server.js diff --git a/examples/anontxt/server.js b/examples/anontxt/server.js deleted file mode 100644 index f543bde9..00000000 --- a/examples/anontxt/server.js +++ /dev/null @@ -1,34 +0,0 @@ -var path = require('path'); -var express = require('express'); -var colors = require('colors') -var settings = require('./config/settings'); -var environment = require('./config/environment'); -var routes = require('./config/routes'); -var models = require('./app/models/'); - -module.exports.start = function (done) { - var app = express(); - - environment(app); - routes(app); - - app.listen(settings.port, function () { - console.log( ("Listening on port " + settings.port).green ); - - if (done) { - return done(null, app, server); - } - }).on('error', function (e) { - if (e.code == 'EADDRINUSE') { - console.log('Address in use. Is the server already running?'.red); - } - if (done) { - return done(e); - } - }); -} - -// If someone ran: "node server.js" then automatically start the server -if (path.basename(process.argv[1],'.js') == path.basename(__filename,'.js')) { - module.exports.start() -} diff --git a/examples/anontxt/tasks/reset.js b/examples/anontxt/tasks/reset.js deleted file mode 100644 index 866d08f0..00000000 --- a/examples/anontxt/tasks/reset.js +++ /dev/null @@ -1,22 +0,0 @@ -var models = require('../app/models/'); - -models(function (err, db) { - if (err) throw err; - - db.drop(function (err) { - if (err) throw err; - - db.sync(function (err) { - if (err) throw err; - - db.models.message.create({ - title: "Hello world", body: "Testing testing 1 2 3" - }, function (err, message) { - if (err) throw err; - - db.close() - console.log("Done!"); - }); - }); - }); -}); diff --git a/lib/AggregateFunctions.js b/lib/AggregateFunctions.js index da8544c1..c8d5a882 100644 --- a/lib/AggregateFunctions.js +++ b/lib/AggregateFunctions.js @@ -1,14 +1,14 @@ -var ORMError = require("./Error"); +var ErrorCodes = require("./ErrorCodes"); var Utilities = require("./Utilities"); module.exports = AggregateFunctions; function AggregateFunctions(opts) { if (typeof opts.driver.getQuery !== "function") { - throw new ORMError('NO_SUPPORT', "This driver does not support aggregate functions"); + throw ErrorCodes.generateError(ErrorCodes.NO_SUPPORT, "This driver does not support aggregate functions"); } if (!Array.isArray(opts.driver.aggregate_functions)) { - throw new ORMError('NO_SUPPORT', "This driver does not support aggregate functions"); + throw ErrorCodes.generateError(ErrorCodes.NO_SUPPORT, "This driver does not support aggregate functions"); } var aggregates = [ [] ]; @@ -52,18 +52,16 @@ function AggregateFunctions(opts) { }, select: function () { if (arguments.length === 0) { - throw new ORMError('PARAM_MISMATCH', "When using append you must at least define one property"); - } - if (Array.isArray(arguments[0])) { - opts.properties = opts.properties.concat(arguments[0]); - } else { - opts.properties = opts.properties.concat(Array.prototype.slice.apply(arguments)); + throw ErrorCodes.generateError(ErrorCodes.PARAM_MISMATCH, "When using append you must at least define one property"); } + opts.properties = opts.properties.concat(Array.isArray(arguments[0]) ? + arguments[0] : + Array.prototype.slice.apply(arguments)); return this; }, as: function (alias) { if (aggregates.length === 0 || (aggregates.length === 1 && aggregates[0].length === 0)) { - throw new ORMError('PARAM_MISMATCH', "No aggregate functions defined yet"); + throw ErrorCodes.generateError(ErrorCodes.PARAM_MISMATCH, "No aggregate functions defined yet"); } var len = aggregates.length; @@ -88,13 +86,13 @@ function AggregateFunctions(opts) { }, get: function (cb) { if (typeof cb !== "function") { - throw new ORMError('MISSING_CALLBACK', "You must pass a callback to Model.aggregate().get()"); + throw ErrorCodes.generateError(ErrorCodes.MISSING_CALLBACK, "You must pass a callback to Model.aggregate().get()"); } if (aggregates[aggregates.length - 1].length === 0) { aggregates.length -= 1; } if (aggregates.length === 0) { - throw new ORMError('PARAM_MISMATCH', "Missing aggregate functions"); + throw ErrorCodes.generateError(ErrorCodes.PARAM_MISMATCH, "Missing aggregate functions"); } var query = opts.driver.getQuery().select().from(opts.table).select(opts.properties); diff --git a/lib/Associations/Extend.js b/lib/Associations/Extend.js index d9ff80aa..3df3c291 100644 --- a/lib/Associations/Extend.js +++ b/lib/Associations/Extend.js @@ -1,5 +1,5 @@ var _ = require('lodash'); -var ORMError = require("../Error"); +var ErrorCodes = require("../ErrorCodes"); var Settings = require("../Settings"); var Singleton = require("../Singleton"); var util = require("../Utilities"); @@ -59,7 +59,7 @@ exports.prepare = function (db, Model, associations, association_properties, mod } if (conditions === null) { - throw new ORMError(".findBy(" + assocName + ") is missing a conditions object", 'PARAM_MISMATCH'); + throw ErrorCodes.generateError(ErrorCodes.PARAM_MISMATCH, ".findBy(" + assocName + ") is missing a conditions object"); } options.__merge = { @@ -109,7 +109,7 @@ function extendInstance(Model, Instance, Driver, association, opts) { Object.defineProperty(Instance, association.hasAccessor, { value : function (cb) { if (!Instance[Model.id]) { - cb(new ORMError("Instance not saved, cannot get extension", 'NOT_DEFINED', { model: Model.table })); + cb(ErrorCodes.generateError(ErrorCodes.NOT_DEFINED, "Instance not saved, cannot get extension", { model: Model.table })); } else { association.model.get(util.values(Instance, Model.id), function (err, extension) { return cb(err, !err && extension ? true : false); @@ -127,7 +127,7 @@ function extendInstance(Model, Instance, Driver, association, opts) { } if (!Instance[Model.id]) { - cb(new ORMError("Instance not saved, cannot get extension", 'NOT_DEFINED', { model: Model.table })); + cb(ErrorCodes.generateError(ErrorCodes.NOT_DEFINED, "Instance not saved, cannot get extension", { model: Model.table })); } else { association.model.get(util.values(Instance, Model.id), opts, cb); } @@ -167,7 +167,7 @@ function extendInstance(Model, Instance, Driver, association, opts) { Object.defineProperty(Instance, association.delAccessor, { value : function (cb) { if (!Instance[Model.id]) { - cb(new ORMError("Instance not saved, cannot get extension", 'NOT_DEFINED', { model: Model.table })); + cb(ErrorCodes.generateError(ErrorCodes.NOT_DEFINED, "Instance not saved, cannot get extension", { model: Model.table })); } else { var conditions = {}; var fields = Object.keys(association.field); diff --git a/lib/Associations/Many.js b/lib/Associations/Many.js index cc9c996e..53b46a55 100644 --- a/lib/Associations/Many.js +++ b/lib/Associations/Many.js @@ -3,7 +3,7 @@ var InstanceConstructor = require("../Instance").Instance; var Hook = require("../Hook"); var Settings = require("../Settings"); var Property = require("../Property"); -var ORMError = require("../Error"); +var ErrorCodes = require("../ErrorCodes"); var util = require("../Utilities"); exports.prepare = function (Model, associations) { @@ -155,8 +155,7 @@ function extendInstance(Model, Instance, Driver, association, opts, createInstan }); return this; }, - enumerable: false, - writable: true + enumerable: false }); Object.defineProperty(Instance, association.getAccessor, { value: function () { @@ -229,28 +228,35 @@ function extendInstance(Model, Instance, Driver, association, opts, createInstan association.model.find(conditions, options, cb); return this; }, - enumerable: false, - writable: true + enumerable: false }); Object.defineProperty(Instance, association.setAccessor, { value: function () { - var items = _.flatten(arguments); - var cb = _.last(items) instanceof Function ? items.pop() : noOperation; + var Instances = Array.prototype.slice.apply(arguments); + var cb = (Instances.length && + typeof Instances[Instances.length - 1] == "function" ? Instances.pop() : noOperation); - Instance[association.delAccessor](function (err) { - if (err) return cb(err); + if (Instances.length === 0) { + throw ErrorCodes.generateError(ErrorCodes.PARAM_MISMATCH, "No associations defined", { model: Model.name }); + } + + if (Array.isArray(Instances[0])) { + // clone is used or else Instances will be just a reference + // to the array and the Instances.push(cb) a few lines ahead + // would actually change the user Array passed to the function + Instances = _.clone(Instances[0]); + } - if (items.length) { - Instance[association.addAccessor](items, cb); - } else { - cb(null); + Instance[association.delAccessor](function (err) { + if (err) { + return cb(err); } + Instances.push(cb); + Instance[association.addAccessor].apply(Instance, Instances); }); - return this; }, - enumerable: false, - writable: true + enumerable: false }); Object.defineProperty(Instance, association.delAccessor, { value: function () { @@ -302,8 +308,7 @@ function extendInstance(Model, Instance, Driver, association, opts, createInstan } return this; }, - enumerable: false, - writable: true + enumerable: false }); Object.defineProperty(Instance, association.addAccessor, { value: function () { @@ -311,7 +316,8 @@ function extendInstance(Model, Instance, Driver, association, opts, createInstan var opts = {}; var cb = noOperation; var run = function () { - var savedAssociations = []; + var savedAssociations = []; + var saveNextAssociation = function () { if (Associations.length === 0) { return cb(null, savedAssociations); @@ -389,7 +395,7 @@ function extendInstance(Model, Instance, Driver, association, opts, createInstan } if (Associations.length === 0) { - throw new ORMError("No associations defined", 'PARAM_MISMATCH', { model: Model.name }); + throw ErrorCodes.generateError(ErrorCodes.PARAM_MISMATCH, "No associations defined", { model: Model.name }); } if (this.saved()) { @@ -406,8 +412,7 @@ function extendInstance(Model, Instance, Driver, association, opts, createInstan return this; }, - enumerable: false, - writable: true + enumerable: false }); } diff --git a/lib/Associations/One.js b/lib/Associations/One.js index 63cda40a..ce71ccd3 100644 --- a/lib/Associations/One.js +++ b/lib/Associations/One.js @@ -1,7 +1,7 @@ -var _ = require("lodash"); -var util = require("../Utilities"); -var ORMError = require("../Error"); -var Accessors = { "get": "get", "set": "set", "has": "has", "del": "remove" }; +var _ = require("lodash"); +var Settings = require("../Settings"); +var util = require("../Utilities"); +var Accessors = { "get": "get", "set": "set", "has": "has", "del": "remove" }; exports.prepare = function (Model, associations, association_properties, model_fields) { Model.hasOne = function () { @@ -49,9 +49,6 @@ exports.prepare = function (Model, associations, association_properties, model_f associations.push(association); for (k in association.field) { - if (!association.field.hasOwnProperty(k)) { - continue; - } association_properties.push(k); if (!association.reversed) { Model.allProperties[k] = _.omit(association.field[k], 'klass'); @@ -90,7 +87,7 @@ exports.prepare = function (Model, associations, association_properties, model_f } if (conditions === null) { - throw new ORMError(".findBy(" + assocName + ") is missing a conditions object", 'PARAM_MISMATCH'); + throw ErrorCodes.generateError(ErrorCodes.PARAM_MISMATCH, ".findBy(" + assocName + ") is missing a conditions object"); } options.__merge = { @@ -101,7 +98,7 @@ exports.prepare = function (Model, associations, association_properties, model_f }; options.extra = []; - if (typeof cb === "function") { + if (typeof cb == "function") { return Model.find({}, options, cb); } return Model.find({}, options); @@ -111,9 +108,9 @@ exports.prepare = function (Model, associations, association_properties, model_f }; }; -exports.extend = function (Model, Instance, Driver, associations) { +exports.extend = function (Model, Instance, Driver, associations, opts) { for (var i = 0; i < associations.length; i++) { - extendInstance(Model, Instance, Driver, associations[i]); + extendInstance(Model, Instance, Driver, associations[i], opts); } }; @@ -136,10 +133,10 @@ exports.autoFetch = function (Instance, associations, opts, cb) { } }; -function extendInstance(Model, Instance, Driver, association) { +function extendInstance(Model, Instance, Driver, association, opts) { Object.defineProperty(Instance, association.hasAccessor, { value: function (opts, cb) { - if (typeof opts === "function") { + if (typeof opts == "function") { cb = opts; opts = {}; } @@ -154,12 +151,11 @@ function extendInstance(Model, Instance, Driver, association) { return this; }, - enumerable: false, - writable: true + enumerable: false }); Object.defineProperty(Instance, association.getAccessor, { value: function (opts, cb) { - if (typeof opts === "function") { + if (typeof opts == "function") { cb = opts; opts = {}; } @@ -174,9 +170,6 @@ function extendInstance(Model, Instance, Driver, association) { if (association.reversed) { if (util.hasValues(Instance, Model.id)) { - if (typeof cb !== "function") { - return association.model.find(util.getConditions(Model, Object.keys(association.field), Instance), opts); - } association.model.find(util.getConditions(Model, Object.keys(association.field), Instance), opts, saveAndReturn); } else { cb(null); @@ -198,8 +191,7 @@ function extendInstance(Model, Instance, Driver, association) { return this; }, - enumerable: false, - writable: true + enumerable: false }); Object.defineProperty(Instance, association.setAccessor, { value: function (OtherInstance, cb) { @@ -253,16 +245,13 @@ function extendInstance(Model, Instance, Driver, association) { return this; }, - enumerable: false, - writable: true + enumerable: false }); if (!association.reversed) { Object.defineProperty(Instance, association.delAccessor, { value: function (cb) { for (var k in association.field) { - if (association.field.hasOwnProperty(k)) { - Instance[k] = null; - } + Instance[k] = null; } Instance.save({}, { saveAssociations: false }, function (err) { if (!err) { @@ -274,8 +263,7 @@ function extendInstance(Model, Instance, Driver, association) { return this; }, - enumerable: false, - writable: true + enumerable: false }); } } @@ -285,7 +273,7 @@ function autoFetchInstance(Instance, association, opts, cb) { return cb(); } - if (!opts.hasOwnProperty("autoFetchLimit") || typeof opts.autoFetchLimit === "undefined") { + if (!opts.hasOwnProperty("autoFetchLimit") || typeof opts.autoFetchLimit == "undefined") { opts.autoFetchLimit = association.autoFetchLimit; } diff --git a/lib/ChainFind.js b/lib/ChainFind.js index a6e6f66d..0dc86229 100644 --- a/lib/ChainFind.js +++ b/lib/ChainFind.js @@ -1,6 +1,7 @@ -var _ = require("lodash"); -var ChainInstance = require("./ChainInstance"); -var Promise = require("./Promise").Promise; +var _ = require("lodash"); +var Singleton = require("./Singleton"); +var ChainInstance = require("./ChainInstance"); +var Promise = require("./Promise").Promise; module.exports = ChainFind; @@ -13,14 +14,14 @@ function ChainFind(Model, opts) { var args = Array.prototype.slice.call(arguments); opts.conditions = opts.conditions || {}; - if (typeof _.last(args) === "function") { + if (typeof _.last(args) === 'function') { cb = args.pop(); } - if (typeof args[0] === "object") { - _.extend(opts.conditions, args[0]); - } else if (typeof args[0] === "string") { - opts.conditions.__sql = opts.conditions.__sql || []; + if (typeof args[0] === 'object') { + _.extend(opts.conditions, args[0]); + } else if (typeof args[0] === 'string') { + opts.conditions.__sql = opts.conditions.__sql || []; opts.conditions.__sql.push(args); } @@ -91,22 +92,13 @@ function ChainFind(Model, opts) { } var ids = [], conditions = {}; - var or; - - if (!Array.isArray(opts.id)) { - opts.id = [ opts.id ]; - } - - conditions.or = []; for (var i = 0; i < data.length; i++) { - or = {}; - for (var j = 0; j < opts.id.length; j++) { - or[opts.id[j]] = data[i][opts.id[j]]; - } - conditions.or.push(or); + ids.push(data[i][opts.id]); } + conditions[opts.id] = ids; + return opts.driver.remove(opts.table, conditions, cb); }); return this; @@ -141,21 +133,6 @@ function ChainFind(Model, opts) { } return promise.fail(cb); }, - eager: function () { - // This will allow params such as ("abc", "def") or (["abc", "def"]) - var associations = _.flatten(arguments); - - // TODO: Implement eager loading for Mongo and delete this. - if (opts.driver.config.protocol == "mongodb:") { - throw new Error("MongoDB does not currently support eager loading"); - } - - opts.__eager = _.filter(opts.associations, function (association) { - return ~associations.indexOf(association.name); - }); - - return this; - }, all: function (cb) { opts.driver.find(opts.only, opts.table, opts.conditions, { limit : opts.limit, @@ -173,43 +150,13 @@ function ChainFind(Model, opts) { var pending = data.length; var createInstance = function (idx) { - opts.newInstance(data[idx], function (err, instance) { - data[idx] = instance; + opts.newInstance(data[idx], function (err, instance) { + data[idx] = instance; - if (--pending === 0) { - return (opts.__eager && opts.__eager.length ? eagerLoading : cb)(null, data); - } - }); - }; - - var eagerLoading = function (err, data) { - var pending = opts.__eager.length; - var idMap = {}; - var count = 0; - - var ids = _.map(data, function (instance) { - var id = instance[opts.id[0]]; - // Create the association arrays - for (var i = 0, association; association = opts.__eager[i]; i++) { - instance[association.name] = []; - } - - idMap[id] = count++; - return id; - }); - - _.map(opts.__eager, function (association) { - opts.driver.eagerQuery(association, opts, ids, function (err, instances) { - for (var i = 0, instance; instance = instances[i]; i++) { - // Perform a parent lookup with $p, and initialize it as an instance. - data[idMap[instance.$p]][association.name].push(association.model(instance)); - } - - if (--pending === 0) { - return cb(null, data); - } - }); - }); + if (--pending === 0) { + return cb(null, data); + } + }); }; for (var i = 0; i < data.length; i++) { @@ -227,16 +174,10 @@ function ChainFind(Model, opts) { } } for (var k in Model) { - if ([ - "hasOne", "hasMany", - "drop", "sync", "get", "find", "all", "count", "clear", "create", - "exists", "settings", "aggregate" - ].indexOf(k) >= 0) { - continue; - } - if (typeof Model[k] !== "function") { - continue; - } + if ([ "hasOne", "hasMany", + "drop", "sync", "get", "find", "all", "count", "clear", "create", + "exists", "settings", "aggregate" ].indexOf(k) >= 0) continue; + if (typeof Model[k] !== "function") continue; chain[k] = Model[k]; } @@ -256,23 +197,22 @@ function addChainMethod(chain, association, opts) { var assocIds = Object.keys(association.mergeAssocId); var ids = association.model.id; function mergeConditions(source) { - for (var i = 0; i < assocIds.length; i++) { - if (typeof conditions[assocIds[i]] === "undefined") { - conditions[assocIds[i]] = source[ids[i]]; - } else if (Array.isArray(conditions[assocIds[i]])) { - conditions[assocIds[i]].push(source[ids[i]]); - } else { - conditions[assocIds[i]] = [ conditions[assocIds[i]], source[ids[i]] ]; - } - } + for (var i = 0; i < assocIds.length; i++) { + if (typeof conditions[assocIds[i]] === 'undefined') + conditions[assocIds[i]] = source[ids[i]]; + else if(Array.isArray(conditions[assocIds[i]])) + conditions[assocIds[i]].push(source[ids[i]]); + else + conditions[assocIds[i]] = [conditions[assocIds[i]], source[ids[i]]]; + } } if (Array.isArray(value)) { - for (var i = 0; i < value.length; i++) { - mergeConditions(value[i]); - } + for (var i = 0; i < value.length; i++) { + mergeConditions(value[i]); + } } else { - mergeConditions(value); + mergeConditions(value); } opts.exists.push({ diff --git a/lib/Drivers/DDL/SQL.js b/lib/Drivers/DDL/SQL.js deleted file mode 100644 index 38202c40..00000000 --- a/lib/Drivers/DDL/SQL.js +++ /dev/null @@ -1,67 +0,0 @@ -var _ = require("lodash"); -var Sync = require("sql-ddl-sync").Sync; - -exports.sync = function (opts, cb) { - var sync = new Sync({ - driver : this, - debug : false//function (text) { console.log(text); } - }); - - var setIndex = function (p, v, k) { - v.index = true; - p[k] = v; - }; - var props = {}; - - if (this.customTypes) { - for (var k in this.customTypes) { - sync.defineType(k, this.customTypes[k]); - } - } - for (var k in opts.allProperties) { - if (typeof opts.id == "string" && opts.id == k) { - opts.allProperties[k].index = [ opts.table + "_pkey" ]; - } else if (Array.isArray(opts.id) && opts.id.indexOf(k) >= 0) { - opts.allProperties[k].index = [ opts.table + "_pkey" ]; - } - } - - sync.defineCollection(opts.table, opts.allProperties); - - for (i = 0; i < opts.many_associations.length; i++) { - props = {}; - - _.merge(props, opts.many_associations[i].mergeId); - _.merge(props, opts.many_associations[i].mergeAssocId); - props = _.transform(props, setIndex); - _.merge(props, opts.many_associations[i].props); - - sync.defineCollection(opts.many_associations[i].mergeTable, props); - } - - sync.sync(cb); - - return this; -}; - -exports.drop = function (opts, cb) { - var i, queries = [], pending; - - queries.push("DROP TABLE IF EXISTS " + this.query.escapeId(opts.table)); - - for (i = 0; i < opts.many_associations.length; i++) { - queries.push("DROP TABLE IF EXISTS " + this.query.escapeId(opts.many_associations[i].mergeTable)); - } - - pending = queries.length; - - for (i = 0; i < queries.length; i++) { - this.execQuery(queries[i], function (err) { - if (--pending === 0) { - return cb(err); - } - }); - } - - return this; -}; diff --git a/lib/Drivers/DDL/mysql.js b/lib/Drivers/DDL/mysql.js new file mode 100755 index 00000000..a8e655c0 --- /dev/null +++ b/lib/Drivers/DDL/mysql.js @@ -0,0 +1,192 @@ +var ErrorCodes = require("../../ErrorCodes"); + +exports.drop = function (driver, opts, cb) { + var i, queries = [], pending; + + queries.push("DROP TABLE IF EXISTS " + driver.query.escapeId(opts.table)); + + for (i = 0; i < opts.many_associations.length; i++) { + queries.push("DROP TABLE IF EXISTS " + driver.query.escapeId(opts.many_associations[i].mergeTable)); + } + + pending = queries.length; + + for (i = 0; i < queries.length; i++) { + driver.execQuery(queries[i], function (err) { + if (--pending === 0) { + return cb(err); + } + }); + } +}; + +exports.sync = function (driver, opts, cb) { + var queries = []; + var definitions = []; + var k, i, pending, prop; + var primary_keys = opts.id.map(function (k) { return driver.query.escapeId(k); }); + var keys = []; + + for (k in opts.allProperties) { + prop = opts.allProperties[k]; + definitions.push(buildColumnDefinition(driver, k, prop)); + } + + for (k in opts.allProperties) { + prop = opts.allProperties[k]; + if (prop.unique === true) { + definitions.push("UNIQUE (" + driver.query.escapeId(k) + ")"); + } else if (prop.index) { + definitions.push("INDEX (" + driver.query.escapeId(k) + ")"); + } + } +/* + for (k in opts.allProperties) { + prop = opts.allProperties[k]; + if (prop.unique === true) { + definitions.push("UNIQUE KEY " + driver.query.escapeId(k) + " (" + driver.query.escapeId(k) + ")"); + } else if (prop.index) { + definitions.push("INDEX (" + driver.query.escapeId(k) + ")"); + } + } +*/ + for (i = 0; i < opts.one_associations.length; i++) { + if (opts.one_associations[i].extension) continue; + if (opts.one_associations[i].reversed) continue; + for (k in opts.one_associations[i].field) { + definitions.push("INDEX (" + driver.query.escapeId(k) + ")"); + } + } + + for (i = 0; i < opts.indexes.length; i++) { + definitions.push("INDEX (" + opts.indexes[i].split(/[,;]+/).map(function (el) { + return driver.query.escapeId(el); + }).join(", ") + ")"); + } + + definitions.push("PRIMARY KEY (" + primary_keys.join(", ") + ")"); + + queries.push( + "CREATE TABLE IF NOT EXISTS " + driver.query.escapeId(opts.table) + + " (" + definitions.join(", ") + ")" + ); + + for (i = 0; i < opts.many_associations.length; i++) { + definitions = []; + + for (k in opts.many_associations[i].mergeId) { + definitions.push(buildColumnDefinition(driver, k, opts.many_associations[i].mergeId[k])); + } + + for (k in opts.many_associations[i].mergeAssocId) { + definitions.push(buildColumnDefinition(driver, k, opts.many_associations[i].mergeAssocId[k])); + } + + for (k in opts.many_associations[i].props) { + definitions.push(buildColumnDefinition(driver, k, opts.many_associations[i].props[k])); + } + + var index = null; + for (k in opts.many_associations[i].mergeId) { + if (index == null) index = driver.query.escapeId(k); + else index += ", " + driver.query.escapeId(k); + } + + for (k in opts.many_associations[i].mergeAssocId) { + if (index == null) index = driver.query.escapeId(k); + else index += ", " + driver.query.escapeId(k); + } + + + definitions.push("INDEX (" + index + ")"); + queries.push( + "CREATE TABLE IF NOT EXISTS " + driver.query.escapeId(opts.many_associations[i].mergeTable) + + " (" + definitions.join(", ") + ")" + ); + } + + pending = queries.length; + + for (i = 0; i < queries.length; i++) { + driver.execQuery(queries[i], function (err) { + if (--pending === 0) { + return cb(err); + } + }); + } +}; + +var colTypes = { + integer: { 2: 'SMALLINT', 4: 'INTEGER', 8: 'BIGINT' }, + floating: { 4: 'FLOAT', 8: 'DOUBLE' } +}; + +function buildColumnDefinition(driver, name, prop) { + var def = driver.query.escapeId(name); + var customType; + + switch (prop.type) { + case "text": + if (prop.big === true) { + def += " LONGTEXT"; + } else { + def += " VARCHAR(" + Math.min(Math.max(parseInt(prop.size, 10) || 255, 1), 65535) + ")"; + } + break; + case "serial": + def += " INT(10) UNSIGNED NOT NULL AUTO_INCREMENT"; + break; + case "number": + if (prop.rational === false) { + def += " " + colTypes.integer[prop.size || 4]; + } else { + def += " " + colTypes.floating[prop.size || 4]; + } + if (prop.unsigned === true) { + def += " UNSIGNED"; + } + break; + case "boolean": + def += " BOOLEAN"; + break; + case "date": + if (prop.time === false) { + def += " DATE"; + } else { + def += " DATETIME"; + } + break; + case "binary": + case "object": + if (prop.big === true) { + def += " LONGBLOB"; + } else { + def += " BLOB"; + } + break; + case "enum": + def += " ENUM (" + + prop.values.map(driver.query.escapeVal.bind(driver.query)) + + ")"; + break; + case "point": + def += " POINT"; + break; + default: + customType = driver.customTypes[prop.type]; + if (customType) { + def += " " + customType.datastoreType(prop); + } else { + throw ErrorCodes.generateError(ErrorCodes.NO_SUPPORT, "Unknown property type: '" + prop.type + "'", { + property : prop + }); + } + } + if (prop.required === true) { + def += " NOT NULL"; + } + if (prop.hasOwnProperty("defaultValue")) { + def += " DEFAULT " + driver.query.escapeVal(prop.defaultValue); + } + return def; +} diff --git a/lib/Drivers/DDL/postgres.js b/lib/Drivers/DDL/postgres.js new file mode 100755 index 00000000..860772f6 --- /dev/null +++ b/lib/Drivers/DDL/postgres.js @@ -0,0 +1,239 @@ +var ErrorCodes = require("../../ErrorCodes"); + +exports.drop = function (driver, opts, cb) { + var i, queries = [], pending; + + queries.push("DROP TABLE IF EXISTS " + driver.query.escapeId(opts.table)); + + for (i = 0; i < opts.many_associations.length; i++) { + queries.push("DROP TABLE IF EXISTS " + driver.query.escapeId(opts.many_associations[i].mergeTable)); + } + + pending = queries.length; + for (i = 0; i < queries.length; i++) { + driver.execQuery(queries[i], function (err) { + if (--pending === 0) { + return cb(err); + } + }); + } +}; + +exports.sync = function (driver, opts, cb) { + var tables = []; + var subqueries = []; + var typequeries = []; + var definitions = []; + var k, i, pending, prop; + var primary_keys = opts.id.map(function (k) { return driver.query.escapeId(k); }); + var keys = []; + + for (k in opts.allProperties) { + prop = opts.allProperties[k]; + definitions.push(buildColumnDefinition(driver, opts.table, k, prop)); + + if (prop.type == "enum") { + typequeries.push( + "CREATE TYPE " + driver.query.escapeId("enum_" + opts.table + "_" + k) + " AS ENUM (" + + prop.values.map(driver.query.escapeVal.bind(driver)) + ")" + ); + } + } + + for (k in opts.allProperties) { + prop = opts.allProperties[k]; + if (prop.unique === true) { + definitions.push("UNIQUE (" + driver.query.escapeId(k) + ")"); + } else if (prop.index) { + definitions.push("INDEX (" + driver.query.escapeId(k) + ")"); + } + } + + definitions.push("PRIMARY KEY (" + primary_keys.join(", ") + ")"); + + tables.push({ + name : opts.table, + query : "CREATE TABLE " + driver.query.escapeId(opts.table) + + " (" + definitions.join(", ") + ")", + typequeries: typequeries, + subqueries : subqueries + }); + + for (i = 0; i < opts.one_associations.length; i++) { + if (opts.one_associations[i].extension) continue; + if (opts.one_associations[i].reversed) continue; + for (k in opts.one_associations[i].field) { + tables[tables.length - 1].subqueries.push( + "CREATE INDEX ON " + driver.query.escapeId(opts.table) + + " (" + driver.query.escapeId(k) + ")" + ); + } + } + + for (i = 0; i < opts.indexes.length; i++) { + tables[tables.length - 1].subqueries.push( + "CREATE INDEX ON " + driver.query.escapeId(opts.table) + + " (" + opts.indexes[i].split(/[,;]+/).map(function (el) { + return driver.query.escapeId(el); + }).join(", ") + ")" + ); + } + + for (i = 0; i < opts.many_associations.length; i++) { + definitions = []; + typequeries = []; + + for (k in opts.many_associations[i].mergeId) { + definitions.push(buildColumnDefinition(driver, opts.many_associations[i].mergeTable, k, opts.many_associations[i].mergeId[k])); + } + + for (k in opts.many_associations[i].mergeAssocId) { + definitions.push(buildColumnDefinition(driver, opts.many_associations[i].mergeTable, k, opts.many_associations[i].mergeAssocId[k])); + } + + for (k in opts.many_associations[i].props) { + definitions.push(buildColumnDefinition(driver, opts.many_associations[i].mergeTable, + k, opts.many_associations[i].props[k])); + if (opts.many_associations[i].props[k].type == "enum") { + typequeries.push( + "CREATE TYPE " + driver.query.escapeId("enum_" + opts.many_associations[i].mergeTable + "_" + k) + " AS ENUM (" + + opts.many_associations[i].props[k].values.map(driver.query.escapeVal.bind(driver)) + ")" + ); + } + } + + var index = null; + for (k in opts.many_associations[i].mergeId) { + if (index == null) index = driver.query.escapeId(k); + else index += ", " + driver.query.escapeId(k); + } + + for (k in opts.many_associations[i].mergeAssocId) { + if (index == null) index = driver.query.escapeId(k); + else index += ", " + driver.query.escapeId(k); + } + + tables.push({ + name : opts.many_associations[i].mergeTable, + query : "CREATE TABLE IF NOT EXISTS " + driver.query.escapeId(opts.many_associations[i].mergeTable) + + " (" + definitions.join(", ") + ")", + typequeries: typequeries, + subqueries : [] + }); + tables[tables.length - 1].subqueries.push( + "CREATE INDEX ON " + driver.query.escapeId(opts.many_associations[i].mergeTable) + + " (" + index + ")" + ); + } + + pending = tables.length; + + for (i = 0; i < tables.length; i++) { + createTableSchema(driver, tables[i], function (err) { + if (--pending === 0) { + // this will bring trouble in the future... + // some errors are not avoided (like ENUM types already defined, etc..) + return cb(err); + } + }); + } +}; + +function createTableSchema(driver, table, cb) { + var pending = table.typequeries.length; + var createTable = function () { + driver.execQuery(table.query, function (err) { + if (err || table.subqueries.length === 0) { + return cb(err); + } + + var pending = table.subqueries.length; + + for (var i = 0; i < table.subqueries.length; i++) { + driver.execQuery(table.subqueries[i], function (err) { + if (--pending === 0) { + return cb(); + } + }); + } + }); + }; + + if (pending === 0) { + return createTable(); + } + + for (var i = 0; i < table.typequeries.length; i++) { + driver.execQuery(table.typequeries[i], function (err) { + if (--pending === 0) { + return createTable(); + } + }); + } +} + +var colTypes = { + integer: { 2: 'SMALLINT', 4: 'INTEGER', 8: 'BIGINT' }, + floating: { 4: 'REAL', 8: 'DOUBLE PRECISION' } +}; + +function buildColumnDefinition(driver, table, name, prop) { + var def = driver.query.escapeId(name); + var customType; + + switch (prop.type) { + case "text": + if (prop.big === true) { + def += " TEXT"; + } else { + def += " VARCHAR(" + Math.max(parseInt(prop.size, 10) || 255, 1) + ")"; + } + break; + case "serial": + def += " SERIAL"; + break; + case "number": + if (prop.rational === false) { + def += " " + colTypes.integer[prop.size || 4]; + } else { + def += " " + colTypes.floating[prop.size || 4]; + } + break; + case "boolean": + def += " BOOLEAN"; + break; + case "date": + if (prop.time === false) { + def += " DATE"; + } else { + def += " TIMESTAMP WITHOUT TIME ZONE"; + } + break; + case "binary": + case "object": + def += " BYTEA"; + break; + case "enum": + def += " " + driver.query.escapeId("enum_" + table + "_" + name); + break; + case "point": + def += " POINT"; + break; + default: + customType = driver.customTypes[prop.type]; + if (customType) { + def += " " + customType.datastoreType(prop); + } else { + throw ErrorCodes.generateError(ErrorCodes.NO_SUPPORT, "Unknown property type: '" + prop.type + "'", { + property : prop + }); + } + } + if (prop.required === true) { + def += " NOT NULL"; + } + if (prop.hasOwnProperty("defaultValue")) { + def += " DEFAULT " + driver.query.escapeVal(prop.defaultValue); + } + return def; +} diff --git a/lib/Drivers/DDL/sqlite.js b/lib/Drivers/DDL/sqlite.js new file mode 100644 index 00000000..08c5a9cd --- /dev/null +++ b/lib/Drivers/DDL/sqlite.js @@ -0,0 +1,176 @@ +var ErrorCodes = require("../../ErrorCodes"); + +exports.drop = function (driver, opts, cb) { + var i, queries = [], pending; + + queries.push("DROP TABLE IF EXISTS " + driver.query.escapeId(opts.table)); + + for (i = 0; i < opts.many_associations.length; i++) { + queries.push("DROP TABLE IF EXISTS " + driver.query.escapeId(opts.many_associations[i].mergeTable)); + } + + pending = queries.length; + for (i = 0; i < queries.length; i++) { + driver.db.all(queries[i], function (err) { + if (--pending === 0) { + return cb(err); + } + }); + } +}; + +exports.sync = function (driver, opts, cb) { + var queries = []; + var definitions = []; + var k, i, pending, prop; + var primary_keys = opts.id.map(function (k) { return driver.query.escapeId(k); }); + var keys = []; + + for (k in opts.allProperties) { + prop = opts.allProperties[k]; + definitions.push(buildColumnDefinition(driver, k, prop)); + } + + if (keys.length > 1) { + definitions.push("PRIMARY KEY (" + primary_keys.join(", ") + ")"); + } + + queries.push( + "CREATE TABLE IF NOT EXISTS " + driver.query.escapeId(opts.table) + + " (" + definitions.join(", ") + ")" + ); + for (k in opts.properties) { + if (opts.properties[k].unique === true) { + queries.push( + "CREATE UNIQUE INDEX IF NOT EXISTS " + driver.query.escapeId(k) + + " ON " + driver.query.escapeId(opts.table) + + " (" + driver.query.escapeId(k) + ")" + ); + } else if (opts.properties[k].index) { + queries.push( + "CREATE INDEX IF NOT EXISTS " + driver.query.escapeId(k) + + " ON " + driver.query.escapeId(opts.table) + + " (" + driver.query.escapeId(k) + ")" + ); + } + } + + for (i = 0; i < opts.one_associations.length; i++) { + if (opts.one_associations[i].extension) continue; + if (opts.one_associations[i].reversed) continue; + for (k in opts.one_associations[i].field) { + queries.push( + "CREATE INDEX IF NOT EXISTS " + driver.query.escapeId(opts.table + "_" + k) + + " ON " + driver.query.escapeId(opts.table) + + " (" + driver.query.escapeId(k) + ")" + ); + } + } + + for (i = 0; i < opts.indexes.length; i++) { + queries.push( + "CREATE INDEX IF NOT EXISTS " + driver.query.escapeId(opts.table + "_index" + i) + + " ON " + driver.query.escapeId(opts.table) + + " (" + opts.indexes[i].split(/[,;]+/).map(function (el) { + return driver.query.escapeId(el); + }).join(", ") + ")" + ); + } + + for (i = 0; i < opts.many_associations.length; i++) { + definitions = []; + + for (k in opts.many_associations[i].mergeId) { + definitions.push(buildColumnDefinition(driver, k, opts.many_associations[i].mergeId[k])); + } + + for (k in opts.many_associations[i].mergeAssocId) { + definitions.push(buildColumnDefinition(driver, k, opts.many_associations[i].mergeAssocId[k])); + } + + for (k in opts.many_associations[i].props) { + definitions.push(buildColumnDefinition(driver, k, opts.many_associations[i].props[k])); + } + + var index = null; + for (k in opts.many_associations[i].mergeId) { + if (index == null) index = driver.query.escapeId(k); + else index += ", " + driver.query.escapeId(k); + } + + for (k in opts.many_associations[i].mergeAssocId) { + if (index == null) index = driver.query.escapeId(k); + else index += ", " + driver.query.escapeId(k); + } + + queries.push( + "CREATE TABLE IF NOT EXISTS " + driver.query.escapeId(opts.many_associations[i].mergeTable) + + " (" + definitions.join(", ") + ")" + ); + queries.push( + "CREATE INDEX IF NOT EXISTS " + driver.query.escapeId(opts.many_associations[i].mergeTable) + + " ON " + driver.query.escapeId(opts.table) + + " (" + index + ")" + ); + } + + pending = queries.length; + for (i = 0; i < queries.length; i++) { + driver.db.all(queries[i], function (err) { + // if (err) console.log(err); + if (--pending === 0) { + return cb(err); + } + }); + } +}; + +function buildColumnDefinition(driver, name, prop) { + var def = driver.query.escapeId(name); + var customType; + + switch (prop.type) { + case "text": + def += " TEXT"; + break; + case "serial": + def += " INTEGER PRIMARY KEY AUTOINCREMENT"; + break; + case "number": + if (prop.rational === false) { + def += " INTEGER"; + } else { + def += " REAL"; + } + break; + case "boolean": + def += " INTEGER UNSIGNED"; + break; + case "date": + def += " DATETIME"; + break; + case "binary": + case "object": + def += " BLOB"; + break; + case "enum": + def += " INTEGER"; + break; + default: + customType = driver.customTypes[prop.type]; + if (customType) { + def += " " + customType.datastoreType(prop); + } else { + throw ErrorCodes.generateError(ErrorCodes.NO_SUPPORT, "Unknown property type: '" + prop.type + "'", { + property : prop + }); + } + } + if (prop.required === true) { + def += " NOT NULL"; + } + if (prop.hasOwnProperty("defaultValue")) { + def += " DEFAULT " + driver.query.escapeVal(prop.defaultValue); + } + return def; +} diff --git a/lib/Drivers/DML/mongodb.js b/lib/Drivers/DML/mongodb.js index 45fc3657..5b15f30c 100644 --- a/lib/Drivers/DML/mongodb.js +++ b/lib/Drivers/DML/mongodb.js @@ -353,15 +353,7 @@ Driver.prototype.clear = function (table, cb) { function convertToDB(obj, timeZone) { for (var k in obj) { - if ([ 'and', 'or', 'not' ].indexOf(k) >= 0) { - for (var j = 0; j < obj[k].length; j++) { - convertToDB(obj[k][j], timeZone); - } - obj['$' + k] = obj[k]; - delete obj[k]; - continue; - } - if (Array.isArray(obj[k]) && k[0] != '$') { + if (Array.isArray(obj[k])) { for (var i = 0; i < obj[k].length; i++) { obj[k][i] = convertToDBVal(k, obj[k][i], timeZone); } @@ -389,7 +381,7 @@ function convertFromDB(obj, timezone) { function convertToDBVal(key, value, timezone) { if (value && typeof value.sql_comparator == "function") { - var val = (key != "_id" ? value : new mongodb.ObjectID(value)); + var val = (key != "_id" ? value.val : new mongodb.ObjectID(value.val)); var comp = value.sql_comparator(); var condition = {}; @@ -399,10 +391,10 @@ function convertToDBVal(key, value, timezone) { case "lt": case "lte": case "ne": - condition["$" + comp] = val.val; + condition["$" + comp] = val; break; case "eq": - condition = val.val; + condition = val; break; case "between": condition["$min"] = val.from; diff --git a/lib/Drivers/DML/mysql.js b/lib/Drivers/DML/mysql.js old mode 100644 new mode 100755 index e00679a6..d73b5d08 --- a/lib/Drivers/DML/mysql.js +++ b/lib/Drivers/DML/mysql.js @@ -1,13 +1,11 @@ var _ = require("lodash"); var mysql = require("mysql"); var Query = require("sql-query").Query; -var shared = require("./_shared"); -var DDL = require("../DDL/SQL"); +var helpers = require("../helpers"); exports.Driver = Driver; function Driver(config, connection, opts) { - this.dialect = 'mysql'; this.config = config || {}; this.opts = opts || {}; this.customTypes = {}; @@ -15,7 +13,7 @@ function Driver(config, connection, opts) { if (!this.config.timezone) { this.config.timezone = "local"; } - this.query = new Query({ dialect: this.dialect, timezone: this.config.timezone }); + this.query = new Query({ dialect: "mysql", timezone: config.timezone }); this.reconnect(null, connection); @@ -28,7 +26,15 @@ function Driver(config, connection, opts) { "DISTINCT"]; } -_.extend(Driver.prototype, shared, DDL); +_.extend(Driver.prototype, helpers.sql); + +Driver.prototype.sync = function (opts, cb) { + return require("../DDL/mysql").sync(this, opts, cb); +}; + +Driver.prototype.drop = function (opts, cb) { + return require("../DDL/mysql").drop(this, opts, cb); +}; Driver.prototype.ping = function (cb) { this.db.ping(cb); @@ -133,24 +139,6 @@ Driver.prototype.find = function (fields, table, conditions, opts, cb) { this.execSimpleQuery(q, cb); }; -Driver.prototype.eagerQuery = function (association, opts, ids, cb) { - var desiredKey = Object.keys(association.field); - var assocKey = Object.keys(association.mergeAssocId); - - var where = {}; - where[desiredKey] = ids; - - var query = this.query.select() - .from(association.model.table) - .select(opts.only) - .from(association.mergeTable, assocKey, opts.id) - .select(desiredKey).as("$p") - .where(association.mergeTable, where) - .build(); - - this.execSimpleQuery(query, cb); -}; - Driver.prototype.count = function (table, conditions, opts, cb) { var q = this.query.select() .from(table) @@ -279,9 +267,7 @@ Driver.prototype.propertyToValue = function (value, property) { value = (value) ? 1 : 0; break; case "object": - if (value !== null) { - value = JSON.stringify(value); - } + value = JSON.stringify(value); break; case "point": return function() { return 'POINT(' + value.x + ', ' + value.y + ')'; }; diff --git a/lib/Drivers/DML/postgres.js b/lib/Drivers/DML/postgres.js index 874df631..12f13d74 100644 --- a/lib/Drivers/DML/postgres.js +++ b/lib/Drivers/DML/postgres.js @@ -1,18 +1,59 @@ var _ = require("lodash"); var pg = require("pg"); var Query = require("sql-query").Query; -var shared = require("./_shared"); -var DDL = require("../DDL/SQL"); +var helpers = require("../helpers"); exports.Driver = Driver; +function Driver(config, connection, opts) { + var functions = switchableFunctions.client; + this.config = config || {}; + this.opts = opts || {}; + + if (!this.config.timezone) { + this.config.timezone = "local"; + } + + this.query = new Query({ dialect: "postgresql", timezone: this.config.timezone }); + this.customTypes = {}; + + if (connection) { + this.db = connection; + } else { + if (this.config.query && this.config.query.ssl) { + config.ssl = true; + this.config = _.extend(this.config, config); + // } else { + // this.config = _.extend(this.config, config); + // this.config = config.href || config; + } + + pg.types.setTypeParser(20, Number); + + if (opts.pool) { + functions = switchableFunctions.pool; + this.db = pg; + } else { + this.db = new pg.Client(this.config); + } + } + + _.extend(this.constructor.prototype, functions); + + this.aggregate_functions = [ "ABS", "CEIL", "FLOOR", "ROUND", + "AVG", "MIN", "MAX", + "LOG", "EXP", "POWER", + "ACOS", "ASIN", "ATAN", "COS", "SIN", "TAN", + "RANDOM", "RADIANS", "DEGREES", + "SUM", "COUNT", + "DISTINCT" ]; +} + var switchableFunctions = { pool: { connect: function (cb) { this.db.connect(this.config, function (err, client, done) { - if (!err) { - done(); - } + if (!err) done(); cb(err); }); }, @@ -21,9 +62,7 @@ var switchableFunctions = { require("../../Debug").sql('postgres', query); } this.db.connect(this.config, function (err, client, done) { - if (err) { - return cb(err); - } + if (err) return cb(err); client.query(query, function (err, result) { done(); @@ -69,55 +108,15 @@ var switchableFunctions = { } }; -function Driver(config, connection, opts) { - var functions = switchableFunctions.client; - - this.dialect = 'postgresql'; - this.config = config || {}; - this.opts = opts || {}; +_.extend(Driver.prototype, helpers.sql); - if (!this.config.timezone) { - this.config.timezone = "local"; - } - - this.query = new Query({ dialect: this.dialect, timezone: this.config.timezone }); - this.customTypes = {}; - - if (connection) { - this.db = connection; - } else { - if (this.config.query && this.config.query.ssl) { - config.ssl = true; - this.config = _.extend(this.config, config); - // } else { - // this.config = _.extend(this.config, config); - // this.config = config.href || config; - } - - pg.types.setTypeParser(20, Number); - - if (opts.pool) { - functions = switchableFunctions.pool; - this.db = pg; - } else { - this.db = new pg.Client(this.config); - } - } - - _.extend(this.constructor.prototype, functions); - - this.aggregate_functions = [ - "ABS", "CEIL", "FLOOR", "ROUND", - "AVG", "MIN", "MAX", - "LOG", "EXP", "POWER", - "ACOS", "ASIN", "ATAN", "COS", "SIN", "TAN", - "RANDOM", "RADIANS", "DEGREES", - "SUM", "COUNT", - "DISTINCT" - ]; -} +Driver.prototype.sync = function (opts, cb) { + return require("../DDL/postgres").sync(this, opts, cb); +}; -_.extend(Driver.prototype, shared, DDL); +Driver.prototype.drop = function (opts, cb) { + return require("../DDL/postgres").drop(this, opts, cb); +}; Driver.prototype.ping = function (cb) { this.execSimpleQuery("SELECT * FROM pg_stat_activity LIMIT 1", function () { @@ -139,7 +138,8 @@ Driver.prototype.getQuery = function () { }; Driver.prototype.find = function (fields, table, conditions, opts, cb) { - var q = this.query.select().from(table).select(fields); + var q = this.query.select() + .from(table).select(fields); if (opts.offset) { q.offset(opts.offset); @@ -175,26 +175,10 @@ Driver.prototype.find = function (fields, table, conditions, opts, cb) { this.execSimpleQuery(q, cb); }; -Driver.prototype.eagerQuery = function (association, opts, ids, cb) { - var desiredKey = Object.keys(association.field); - var assocKey = Object.keys(association.mergeAssocId); - - var where = {}; - where[desiredKey] = ids; - - var query = this.query.select() - .from(association.model.table) - .select(opts.only) - .from(association.mergeTable, assocKey, opts.id) - .select(desiredKey).as("$p") - .where(association.mergeTable, where) - .build(); - - this.execSimpleQuery(query, cb); -}; - Driver.prototype.count = function (table, conditions, opts, cb) { - var q = this.query.select().from(table).count(null, 'c'); + var q = this.query.select() + .from(table) + .count(null, 'c'); if (opts.merge) { q.from(opts.merge.from.table, opts.merge.from.field, opts.merge.to.field); @@ -219,7 +203,10 @@ Driver.prototype.count = function (table, conditions, opts, cb) { }; Driver.prototype.insert = function (table, data, id_prop, cb) { - var q = this.query.insert().into(table).set(data).build(); + var q = this.query.insert() + .into(table) + .set(data) + .build(); this.execSimpleQuery(q + " RETURNING *", function (err, results) { if (err) { @@ -239,13 +226,20 @@ Driver.prototype.insert = function (table, data, id_prop, cb) { }; Driver.prototype.update = function (table, changes, conditions, cb) { - var q = this.query.update().into(table).set(changes).where(conditions).build(); + var q = this.query.update() + .into(table) + .set(changes) + .where(conditions) + .build(); this.execSimpleQuery(q, cb); }; Driver.prototype.remove = function (table, conditions, cb) { - var q = this.query.remove().from(table).where(conditions).build(); + var q = this.query.remove() + .from(table) + .where(conditions) + .build(); this.execSimpleQuery(q, cb); }; @@ -273,7 +267,6 @@ Driver.prototype.valueToProperty = function (value, property) { case "point": if (typeof value == "string") { var m = value.match(/\((\-?[\d\.]+)[\s,]+(\-?[\d\.]+)\)/); - if (m) { value = { x : parseFloat(m[1], 10) , y : parseFloat(m[2], 10) }; } @@ -294,15 +287,12 @@ Driver.prototype.valueToProperty = function (value, property) { case "number": if (typeof value != 'number' && value !== null) { v = Number(value); - if (!isNaN(v)) { - value = v; - } + if (!isNaN(v)) value = v; } break; default: customType = this.customTypes[property.type]; - - if (customType && 'valueToProperty' in customType) { + if(customType && 'valueToProperty' in customType) { value = customType.valueToProperty(value); } } @@ -314,9 +304,7 @@ Driver.prototype.propertyToValue = function (value, property) { switch (property.type) { case "object": - if (value !== null && !Buffer.isBuffer(value)) { - value = new Buffer(JSON.stringify(value)); - } + value = JSON.stringify(value); break; case "date": if (this.config.timezone && this.config.timezone != 'local') { @@ -337,8 +325,7 @@ Driver.prototype.propertyToValue = function (value, property) { break; default: customType = this.customTypes[property.type]; - - if (customType && 'propertyToValue' in customType) { + if(customType && 'propertyToValue' in customType) { value = customType.propertyToValue(value); } } @@ -350,12 +337,9 @@ Object.defineProperty(Driver.prototype, "isSql", { }); function convertTimezone(tz) { - if (tz == "Z") { - return 0; - } + if (tz == "Z") return 0; var m = tz.match(/([\+\-\s])(\d\d):?(\d\d)?/); - if (m) { return (m[1] == '-' ? -1 : 1) * (parseInt(m[2], 10) + ((m[3] ? parseInt(m[3], 10) : 0) / 60)) * 60; } diff --git a/lib/Drivers/DML/sqlite.js b/lib/Drivers/DML/sqlite.js index 63b6a043..023ffecf 100644 --- a/lib/Drivers/DML/sqlite.js +++ b/lib/Drivers/DML/sqlite.js @@ -2,13 +2,11 @@ var _ = require("lodash"); var util = require("util"); var sqlite3 = require("sqlite3"); var Query = require("sql-query").Query; -var shared = require("./_shared"); -var DDL = require("../DDL/SQL"); +var helpers = require("../helpers"); exports.Driver = Driver; function Driver(config, connection, opts) { - this.dialect = 'sqlite'; this.config = config || {}; this.opts = opts || {}; @@ -16,7 +14,7 @@ function Driver(config, connection, opts) { this.config.timezone = "local"; } - this.query = new Query({ dialect: this.dialect, timezone: this.config.timezone }); + this.query = new Query({ dialect: "sqlite", timezone: this.config.timezone }); this.customTypes = {}; if (connection) { @@ -25,7 +23,7 @@ function Driver(config, connection, opts) { // on Windows, paths have a drive letter which is parsed by // url.parse() as the hostname. If host is defined, assume // it's the drive letter and add ":" - if (process.platform == "win32" && config.host && config.host.match(/^[a-z]$/i)) { + if (process.platform == "win32" && config.host.match(/^[a-z]$/i)) { this.db = new sqlite3.Database(((config.host ? config.host + ":" : "") + (config.pathname || "")) || ':memory:'); } else { this.db = new sqlite3.Database(((config.host ? config.host : "") + (config.pathname || "")) || ':memory:'); @@ -39,7 +37,15 @@ function Driver(config, connection, opts) { "DISTINCT" ]; } -_.extend(Driver.prototype, shared, DDL); +_.extend(Driver.prototype, helpers.sql); + +Driver.prototype.sync = function (opts, cb) { + return require("../DDL/sqlite").sync(this, opts, cb); +}; + +Driver.prototype.drop = function (opts, cb) { + return require("../DDL/sqlite").drop(this, opts, cb); +}; Driver.prototype.ping = function (cb) { process.nextTick(cb); @@ -68,7 +74,7 @@ Driver.prototype.getQuery = function () { Driver.prototype.execSimpleQuery = function (query, cb) { if (this.opts.debug) { - require("../../Debug").sql('sqlite', query); + require("../../Debug").sql('mysql', query); } this.db.all(query, cb); }; @@ -117,24 +123,6 @@ Driver.prototype.find = function (fields, table, conditions, opts, cb) { this.db.all(q, cb); }; -Driver.prototype.eagerQuery = function (association, opts, ids, cb) { - var desiredKey = Object.keys(association.field); - var assocKey = Object.keys(association.mergeAssocId); - - var where = {}; - where[desiredKey] = ids; - - var query = this.query.select() - .from(association.model.table) - .select(opts.only) - .from(association.mergeTable, assocKey, opts.id) - .select(desiredKey).as("$p") - .where(association.mergeTable, where) - .build(); - - this.execSimpleQuery(query, cb); -}; - Driver.prototype.count = function (table, conditions, opts, cb) { var q = this.query.select() .from(table) @@ -285,9 +273,7 @@ Driver.prototype.propertyToValue = function (value, property) { value = (value) ? 1 : 0; break; case "object": - if (value !== null) { - value = JSON.stringify(value); - } + value = JSON.stringify(value); break; case "date": if (this.config.query && this.config.query.strdates) { diff --git a/lib/Drivers/DML/_shared.js b/lib/Drivers/helpers.js similarity index 92% rename from lib/Drivers/DML/_shared.js rename to lib/Drivers/helpers.js index fcc20f00..dce8a0e3 100644 --- a/lib/Drivers/DML/_shared.js +++ b/lib/Drivers/helpers.js @@ -1,5 +1,5 @@ -module.exports = { +module.exports.sql = { execQuery: function () { if (arguments.length == 2) { var query = arguments[0]; @@ -10,4 +10,4 @@ module.exports = { } return this.execSimpleQuery(query, cb); } -}; +} diff --git a/lib/Error.js b/lib/Error.js deleted file mode 100644 index b1013811..00000000 --- a/lib/Error.js +++ /dev/null @@ -1,39 +0,0 @@ -var codes = { - QUERY_ERROR : 1, - NOT_FOUND : 2, - NOT_DEFINED : 3, - NO_SUPPORT : 4, - MISSING_CALLBACK : 5, - PARAM_MISMATCH : 6, - CONNECTION_LOST : 10 -} - -function ORMError(message, code, extras) { - Error.call(this); - Error.captureStackTrace(this, this.constructor); - - this.message = message; - if (code) { - this.code = codes[code]; - this.literalCode = code; - if (!this.code) { - throw new Error("Invalid error code: " + code); - } - } - if (extras) { - for(var k in extras) { - this[k] = extras[k]; - } - } -} - -ORMError.prototype = Object.create(Error.prototype); -ORMError.prototype.constructor = ORMError; -ORMError.prototype.name = 'ORMError'; -ORMError.prototype.toString = function () { - return '[ORMError ' + this.literalCode + ': ' + this.message + ']'; -} - -ORMError.codes = codes; - -module.exports = ORMError; diff --git a/lib/ErrorCodes.js b/lib/ErrorCodes.js index e3ed8d8d..5df4fbeb 100644 --- a/lib/ErrorCodes.js +++ b/lib/ErrorCodes.js @@ -1,2 +1,32 @@ -// Moved to 'Error.js' -module.exports = require('./error').codes; +exports.QUERY_ERROR = 1; +exports.NOT_FOUND = 2; +exports.NOT_DEFINED = 3; +exports.NO_SUPPORT = 4; +exports.MISSING_CALLBACK = 5; +exports.PARAM_MISMATCH = 6; + +exports.CONNECTION_LOST = 10; + +// Deprecated, remove on next major release. +Object.defineProperty(exports, "PARAM_MISSMATCH", { + enumerable: true, get: function () { + console.log("PARAM_MISSMATCH spelling is deprecated. Use PARAM_MISMATCH instead"); + return exports.PARAM_MISMATCH; + } +}); + +Object.defineProperty(exports, "generateError", { + value: function (code, message, extra) { + var err = new Error(message); + err.code = code; + + if (extra) { + for (var k in extra) { + err[k] = extra[k]; + } + } + + return err; + }, + enumerable : false +}); diff --git a/lib/Express.js b/lib/Express.js index 8851d796..37bad496 100644 --- a/lib/Express.js +++ b/lib/Express.js @@ -47,11 +47,6 @@ module.exports = function (uri, opts) { req.db = _db; } - if (next === undefined && typeof res === 'function') - { - next = res; - } - if (_pending > 0) { _queue.push(next); return; diff --git a/lib/Instance.js b/lib/Instance.js index be1682ab..68f624b8 100755 --- a/lib/Instance.js +++ b/lib/Instance.js @@ -97,7 +97,7 @@ function Instance(Model, opts) { cb(err, instance); } }; - var saveInstance = function (saveOptions, cb) { + var saveInstance = function (cb, saveOptions) { // what this condition means: // - If the instance is in state mode // - AND it's not an association that is asking it to save @@ -118,7 +118,7 @@ function Instance(Model, opts) { return saveError(cb, err); } - return saveNew(saveOptions, getInstanceData(), cb); + return saveNew(cb, saveOptions, getInstanceData()); }); } else { waitHooks([ "beforeSave" ], function (err) { @@ -126,12 +126,21 @@ function Instance(Model, opts) { return saveError(cb, err); } - return savePersisted(saveOptions, getInstanceData(), cb); + if (opts.changes.length === 0) { + if (saveOptions.saveAssociations === false) { + return saveInstanceExtra(cb); + } + return saveAssociations(function (err) { + return afterSave(cb, false, err); + }); + } + + return savePersisted(cb, saveOptions, getInstanceData()); }); } }); }; - var runAfterSaveActions = function (cb, create, err) { + var afterSave = function (cb, create, err) { instance_saving = false; emitEvent("save", err, instance); @@ -141,7 +150,9 @@ function Instance(Model, opts) { } Hook.trigger(instance, opts.hooks.afterSave, !err); - cb(); + if (!err) { + saveInstanceExtra(cb); + } }; var getInstanceData = function () { var data = {}, prop; @@ -179,13 +190,8 @@ function Instance(Model, opts) { return nextHook(); }; - var saveNew = function (saveOptions, data, cb) { - var finish = function (err) { - runAfterSaveActions(function () { - if (err) return cb(err); - saveInstanceExtra(cb); - }, true); - } + var saveNew = function (cb, saveOptions, data) { + var next = afterSave.bind(this, cb, true); opts.driver.insert(opts.table, data, opts.id, function (save_err, info) { if (save_err) { @@ -199,56 +205,36 @@ function Instance(Model, opts) { opts.is_new = false; if (saveOptions.saveAssociations === false) { - return finish(); + return next(); } - return saveAssociations(finish); + return saveAssociations(next); }); }; - var savePersisted = function (saveOptions, data, cb) { + var savePersisted = function (cb, saveOptions, data) { + var next = afterSave.bind(this, cb, false); var changes = {}, conditions = {}; - var next = function (saved) { - var finish = function () { - saveInstanceExtra(cb); - } - - if(!saved && saveOptions.saveAssociations === false) { - finish(); - } else { - if (saveOptions.saveAssociations === false) { - runAfterSaveActions(function () { - finish(); - }, false); - } else { - saveAssociations(function (err) { - runAfterSaveActions(function () { - if (err) return cb(err); - finish(); - }, false, err); - }); - } - } + for (var i = 0; i < opts.changes.length; i++) { + changes[opts.changes[i]] = data[opts.changes[i]]; + } + for (i = 0; i < opts.id.length; i++) { + conditions[opts.id[i]] = data[opts.id[i]]; } - if (opts.changes.length === 0) { - next(false); - } else { - for (var i = 0; i < opts.changes.length; i++) { - changes[opts.changes[i]] = data[opts.changes[i]]; + opts.driver.update(opts.table, changes, conditions, function (save_err) { + if (save_err) { + return saveError(cb, save_err); } - for (i = 0; i < opts.id.length; i++) { - conditions[opts.id[i]] = data[opts.id[i]]; + + opts.changes.length = 0; + + if (saveOptions.saveAssociations === false) { + return next(); } - opts.driver.update(opts.table, changes, conditions, function (err) { - if (err) { - return saveError(cb, err); - } - opts.changes.length = 0; - next(true); - }); - } + return saveAssociations(next); + }); }; var saveAssociations = function (cb) { var pending = 1, errored = false, i, j; @@ -270,45 +256,45 @@ function Instance(Model, opts) { }; var _saveOneAssociation = function (assoc) { - if (!instance[assoc.name] || typeof instance[assoc.name] !== "object") return; - if (assoc.reversed) { - // reversed hasOne associations should behave like hasMany - if (!Array.isArray(instance[assoc.name])) { - instance[assoc.name] = [ instance[assoc.name] ]; - } - for (var i = 0; i < instance[assoc.name].length; i++) { - if (!instance[assoc.name][i].isInstance) { - instance[assoc.name][i] = new assoc.model(instance[assoc.name][i]); - } - saveAssociation(assoc.setAccessor, instance[assoc.name][i]); - } - return; - } - if (!instance[assoc.name].isInstance) { - instance[assoc.name] = new assoc.model(instance[assoc.name]); - } + if (!instance[assoc.name] || typeof instance[assoc.name] !== "object") return; + if (assoc.reversed) { + // reversed hasOne associations should behave like hasMany + if (!Array.isArray(instance[assoc.name])) { + instance[assoc.name] = [ instance[assoc.name] ]; + } + for (var i = 0; i < instance[assoc.name].length; i++) { + if (!instance[assoc.name][i].isInstance) { + instance[assoc.name][i] = new assoc.model(instance[assoc.name][i]); + } + saveAssociation(assoc.setAccessor, instance[assoc.name][i]); + } + return; + } + if (!instance[assoc.name].isInstance) { + instance[assoc.name] = new assoc.model(instance[assoc.name]); + } - saveAssociation(assoc.setAccessor, instance[assoc.name]); + saveAssociation(assoc.setAccessor, instance[assoc.name]); }; for (i = 0; i < opts.one_associations.length; i++) { - _saveOneAssociation(opts.one_associations[i]); + _saveOneAssociation(opts.one_associations[i]); } var _saveManyAssociation = function (assoc) { - if (!instance.hasOwnProperty(assoc.name)) return; - if (!Array.isArray(instance[assoc.name])) { - instance[assoc.name] = [ instance[assoc.name] ]; - } + if (!instance.hasOwnProperty(assoc.name)) return; + if (!Array.isArray(instance[assoc.name])) { + instance[assoc.name] = [ instance[assoc.name] ]; + } - for (j = 0; j < instance[assoc.name].length; j++) { - if (!instance[assoc.name][j].isInstance) { - instance[assoc.name][j] = new assoc.model(instance[assoc.name][j]); - } - } + for (j = 0; j < instance[assoc.name].length; j++) { + if (!instance[assoc.name][j].isInstance) { + instance[assoc.name][j] = new assoc.model(instance[assoc.name][j]); + } + } - return saveAssociation(assoc.setAccessor, instance[assoc.name]); + return saveAssociation(assoc.setAccessor, instance[assoc.name]); }; for (i = 0; i < opts.many_associations.length; i++) { @@ -342,12 +328,12 @@ function Instance(Model, opts) { } for (i = 0; i < opts.extra_info.id.length; i++) { - conditions[opts.extra_info.id_prop[i]] = opts.extra_info.id[i]; - conditions[opts.extra_info.assoc_prop[i]] = opts.data[opts.id[i]]; + conditions[opts.extra_info.id_prop[i]] = opts.extra_info.id[i]; + conditions[opts.extra_info.assoc_prop[i]] = opts.data[opts.id[i]]; } opts.driver.update(opts.extra_info.table, data, conditions, function (err) { - return cb(err); + if (cb) return cb(err, instance); }); }; var removeInstance = function (cb) { @@ -503,7 +489,7 @@ function Instance(Model, opts) { Object.defineProperty(instance, k, { value : opts.methods[k].bind(instance), enumerable : false, - writable : true + writeable : true }); } @@ -520,13 +506,12 @@ function Instance(Model, opts) { return this; }, - enumerable: false, - writable: true + enumerable: false }); Object.defineProperty(instance, "save", { value: function () { var arg = null, objCount = 0; - var data = {}, saveOptions = {}, cb = null; + var data = {}, saveOptions = {}, callback = null; while (arguments.length > 0) { arg = Array.prototype.shift.call(arguments); @@ -544,7 +529,7 @@ function Instance(Model, opts) { objCount++; break; case 'function': - cb = arg; + callback = arg; break; default: var err = new Error("Unknown parameter type '" + (typeof arg) + "' in Instance.save()"); @@ -559,24 +544,17 @@ function Instance(Model, opts) { } } - saveInstance(saveOptions, function (err) { - if (!cb) return; - if (err) return cb(err); - - return cb(null, instance); - }); + saveInstance(callback, saveOptions); return this; }, - enumerable: false, - writable: true + enumerable: false }); Object.defineProperty(instance, "saved", { value: function () { return opts.changes.length === 0; }, - enumerable: false, - writable: true + enumerable: false }); Object.defineProperty(instance, "remove", { value: function (cb) { @@ -584,8 +562,7 @@ function Instance(Model, opts) { return this; }, - enumerable: false, - writable: true + enumerable: false }); Object.defineProperty(instance, "isInstance", { value: true, @@ -595,8 +572,7 @@ function Instance(Model, opts) { value: function () { return !opts.is_new; }, - enumerable: false, - writable: true + enumerable: false }); Object.defineProperty(instance, "isShell", { value: function () { @@ -610,8 +586,7 @@ function Instance(Model, opts) { cb(null, errors || false); }); }, - enumerable: false, - writable: true + enumerable: false }); Object.defineProperty(instance, "__singleton_uid", { value: function (cb) { diff --git a/lib/Model.js b/lib/Model.js index feab1488..188ba3a9 100644 --- a/lib/Model.js +++ b/lib/Model.js @@ -8,7 +8,7 @@ var Property = require("./Property"); var Singleton = require("./Singleton"); var Utilities = require("./Utilities"); var Validators = require("./Validators"); -var ORMError = require("./Error"); +var ErrorCodes = require("./ErrorCodes"); var Hook = require("./Hook"); var AvailableHooks = [ "beforeCreate", "afterCreate", @@ -206,7 +206,7 @@ function Model(opts) { return this; } - return cb(new ORMError("Driver does not support Model.drop()", 'NO_SUPPORT', { model: opts.table })); + return cb(ErrorCodes.generateError(ErrorCodes.NO_SUPPORT, "Driver does not support Model.drop()", { model: opts.table })); }; model.sync = function (cb) { @@ -234,7 +234,7 @@ function Model(opts) { return this; } - return cb(new ORMError("Driver does not support Model.sync()", 'NO_SUPPORT', { model: opts.table })); + return cb(ErrorCodes.generateError(ErrorCodes.NO_SUPPORT, "Driver does not support Model.sync()", { model: opts.table })); }; model.get = function () { @@ -244,7 +244,7 @@ function Model(opts) { var cb = ids.pop(); if (typeof cb !== "function") { - throw new ORMError("Missing Model.get() callback", 'MISSING_CALLBACK', { model: opts.table }); + throw ErrorCodes.generateError(ErrorCodes.MISSING_CALLBACK, "Missing Model.get() callback", { model: opts.table }); } if (typeof ids[ids.length - 1] === "object" && !Array.isArray(ids[ids.length - 1])) { @@ -256,7 +256,7 @@ function Model(opts) { } if (ids.length !== opts.id.length) { - throw new ORMError("Model.get() IDs number mismatch (" + opts.id.length + " needed, " + ids.length + " passed)", 'PARAM_MISMATCH', { model: opts.table }); + throw ErrorCodes.generateError(ErrorCodes.PARAM_MISMATCH, "Model.get() IDs number mismatch (" + opts.id.length + " needed, " + ids.length + " passed)", { model: opts.table }); } for (var i = 0; i < opts.id.length; i++) { @@ -275,10 +275,10 @@ function Model(opts) { opts.driver.find(model_fields, opts.table, conditions, { limit: 1 }, function (err, data) { if (err) { - return cb(new ORMError(err.message, 'QUERY_ERROR', { originalCode: err.code })); + return cb(ErrorCodes.generateError(ErrorCodes.QUERY_ERROR, err.message, { originalCode: err.code })); } if (data.length === 0) { - return cb(new ORMError("Not found", 'NOT_FOUND', { model: opts.table })); + return cb(ErrorCodes.generateError(ErrorCodes.NOT_FOUND, "Not found", { model: opts.table })); } var uid = opts.driver.uid + "/" + opts.table + "/" + ids.join("/"); @@ -426,7 +426,7 @@ function Model(opts) { } if (cb === null) { - throw new ORMError("Missing Model.one() callback", 'MISSING_CALLBACK', { model: opts.table }); + throw ErrorCodes.generateError(ErrorCodes.MISSING_CALLBACK, "Missing Model.one() callback", { model: opts.table }); } // add limit 1 @@ -457,7 +457,7 @@ function Model(opts) { } if (typeof cb !== "function") { - throw new ORMError('MISSING_CALLBACK', "Missing Model.count() callback", { model: opts.table }); + throw ErrorCodes.generateError(ErrorCodes.MISSING_CALLBACK, "Missing Model.count() callback", { model: opts.table }); } if (conditions) { @@ -505,7 +505,7 @@ function Model(opts) { var cb = ids.pop(); if (typeof cb !== "function") { - throw new ORMError("Missing Model.exists() callback", 'MISSING_CALLBACK', { model: opts.table }); + throw ErrorCodes.generateError(ErrorCodes.MISSING_CALLBACK, "Missing Model.exists() callback", { model: opts.table }); } var conditions = {}, i; diff --git a/lib/ORM.js b/lib/ORM.js index c303c477..fff4dd7a 100644 --- a/lib/ORM.js +++ b/lib/ORM.js @@ -10,7 +10,7 @@ var Model = require("./Model").Model; var DriverAliases = require("./Drivers/aliases"); var Settings = require("./Settings"); var Singleton = require("./Singleton"); -var ORMError = require("./Error"); +var ErrorCodes = require("./ErrorCodes"); var Utilities = require("./Utilities"); // Deprecated, use enforce @@ -27,7 +27,7 @@ exports.settings = new Settings.Container(Settings.defaults()); exports.Property = require("./Property"); exports.Settings = Settings; -exports.ErrorCodes = ORMError.codes; +exports.ErrorCodes = ErrorCodes; exports.Text = Query.Text; for (var k in Query.Comparators) { @@ -63,11 +63,11 @@ exports.use = function (connection, proto, opts, cb) { exports.connect = function (opts, cb) { if (arguments.length === 0 || !opts) { - return ORM_Error(new ORMError("CONNECTION_URL_EMPTY", 'PARAM_MISMATCH'), cb); + return ORM_Error(ErrorCodes.generateError(ErrorCodes.PARAM_MISMATCH, "CONNECTION_URL_EMPTY"), cb); } if (typeof opts === "string") { if (opts.replace(/\s+/, "").length === 0) { - return ORM_Error(new ORMError("CONNECTION_URL_EMPTY", 'PARAM_MISMATCH'), cb); + return ORM_Error(ErrorCodes.generateError(ErrorCodes.PARAM_MISMATCH, "CONNECTION_URL_EMPTY"), cb); } opts = url.parse(opts, true); for(var k in opts.query) { @@ -81,7 +81,7 @@ exports.connect = function (opts, cb) { opts.database = (opts.pathname ? opts.pathname.substr(1) : ""); } if (!opts.protocol) { - return ORM_Error(new ORMError("CONNECTION_URL_NO_PROTOCOL", 'PARAM_MISMATCH'), cb); + return ORM_Error(ErrorCodes.generateError(ErrorCodes.PARAM_MISMATCH, "CONNECTION_URL_NO_PROTOCOL"), cb); } // if (!opts.host) { // opts.host = opts.hostname = "localhost"; @@ -112,8 +112,8 @@ exports.connect = function (opts, cb) { var debug = extractOption(opts, "debug"); var pool = extractOption(opts, "pool"); var driver = new Driver(opts, null, { - debug : (debug !== null ? ((debug === "false" || debug === "0") ? false : true) : settings.get("connection.debug")), - pool : (pool !== null ? ((pool === "false" || pool === "0") ? false : true) : settings.get("connection.pool")), + debug : (debug !== null ? Boolean(debug) : settings.get("connection.debug")), + pool : (pool !== null ? Boolean(pool) : settings.get("connection.pool")), settings : settings }); @@ -132,7 +132,7 @@ exports.connect = function (opts, cb) { }); } catch (ex) { if (ex.code === "MODULE_NOT_FOUND" || ex.message.indexOf('find module')) { - return ORM_Error(new ORMError("Connection protocol not supported - have you installed the database driver for " + proto + "?", 'NO_SUPPORT'), cb); + return ORM_Error(ErrorCodes.generateError(ErrorCodes.NO_SUPPORT, "CONNECTION_PROTOCOL_NOT_SUPPORTED"), cb); } return ORM_Error(ex, cb); } @@ -161,7 +161,7 @@ function ORM(driver_name, driver, settings) { var onError = function (err) { if (this.settings.get("connection.reconnect")) { if (typeof this.driver.reconnect === "undefined") { - return this.emit("error", new ORMError("Connection lost - driver does not support reconnection", 'CONNECTION_LOST')); + return this.emit("error", ErrorCodes.generateError(ErrorCodes.CONNECTION_LOST, "Connection lost - driver does not support reconnection")); } this.driver.reconnect(function () { this.driver.on("error", onError); diff --git a/lib/Property.js b/lib/Property.js index a859f68e..e8a00933 100644 --- a/lib/Property.js +++ b/lib/Property.js @@ -1,4 +1,4 @@ -var ORMError = require("./Error"); +var ErrorCodes = require("./ErrorCodes"); exports.normalize = function (prop, customTypes, Settings) { if (typeof prop === "function") { @@ -32,7 +32,7 @@ exports.normalize = function (prop, customTypes, Settings) { if ([ "text", "number", "boolean", "date", "enum", "object", "binary", "point" ].indexOf(prop.type) === -1) { if (!(prop.type in customTypes)) { - throw new ORMError("Unknown property type: " + prop.type, 'NO_SUPPORT'); + throw ErrorCodes.generateError(ErrorCodes.NO_SUPPORT, "Unknown property type: " + prop.type); } } @@ -40,10 +40,6 @@ exports.normalize = function (prop, customTypes, Settings) { prop.required = true; } - if (prop.type == "number" && !prop.hasOwnProperty("rational")) { - prop.rational = true; - } - return prop; }; diff --git a/lib/Settings.js b/lib/Settings.js index 47aeaee1..5737f948 100644 --- a/lib/Settings.js +++ b/lib/Settings.js @@ -16,7 +16,7 @@ var default_settings = { }, connection : { reconnect : true, - pool : false, + poll : false, debug : false } }; diff --git a/lib/TypeScript/orm.d.ts b/lib/TypeScript/orm.d.ts index 7ad22f86..5b65f94a 100644 --- a/lib/TypeScript/orm.d.ts +++ b/lib/TypeScript/orm.d.ts @@ -1,275 +1,240 @@ -/// - -declare module "orm" { - - import events = require('events'); - import sqlquery = require('sqlquery'); - - module orm { - - /** - * Parameter Type Interfaces - **/ - - export interface Model { - (): Instance; - (...ids: any[]): Instance; - - properties: { [property: string]: Property }; - settings: Settings; - - drop(callback?: (err: Error) => void): Model; - sync(callback?: (err: Error) => void): Model; - get(...args: any[]): Model; - find(conditions: { [property: string]: any }, callback: (err: Error, results: Instance[]) => void): Model; - find(conditions: { [property: string]: any }, options: { - limit?: number; - order?: any; - }, callback: (err: Error, results: Instance[]) => void): Model; - find(conditions: { [property: string]: any }, limit: number, order: string[], callback: (err: Error, results: Instance[]) => void): Model; - find(conditions: { [property: string]: any }): IChainFind; - - all(conditions: { [property: string]: any }, callback: (err: Error, results: Instance[]) => void): Model; - all(conditions: { [property: string]: any }, options: { - limit?: number; - order?: any; - }, callback: (err: Error, results: Instance[]) => void): Model; - all(conditions: { [property: string]: any }, limit: number, order: string[], callback: (err: Error, results: Instance[]) => void): Model; - - one(conditions: { [property: string]: any }, callback: (err: Error, result: Instance) => void): Model; - one(conditions: { [property: string]: any }, options: { - limit?: number; - order?: any; - }, callback: (err: Error, result: Instance) => void): Model; - one(conditions: { [property: string]: any }, limit: number, order: string[], callback: (err: Error, result: Instance) => void): Model; - - count(callback: (err: Error, count: number) => void): Model; - count(conditions: { [property: string]: any }, callback: (err: Error, count: number) => void): Model; - - aggregate(conditions: { [property: string]: any }): IAggregated; - aggregate(properties: string[]): IAggregated; - aggregate(conditions: { [property: string]: any }, properties: string[]): IAggregated; - - exists(id: any, callback: (err: Error, exists: boolean) => void): Model; - exists(...args: any[]): Model; - - create(data: { [property: string]: any; }, callback: (err: Error, instance: Instance) => void): Model; - create(...args: any[]): Model; - - clear(): Model; - - table: string; - id: string[]; - - [property: string]: any; - } - - export interface Instance { - on(event: string, callback): Instance; - save(): Instance; - save(data: { [property: string]: any; }, callback: (err: Error) => void): Instance; - save(data: { [property: string]: any; }, options: any, callback: (err: Error) => void): Instance; - saved: boolean; - remove(callback: (err: Error) => void): Instance; - isInstance: boolean; - isPersisted: boolean; - isShell: boolean; - validate(callback: (errors: Error[]) => void); - model: Model; - - [property: string]: any; - } - - export interface ModelOptions { - id?: string[]; - autoFetch?: boolean; - autoFetchLimit?: number; - cacheFetch?: boolean; - hooks?: { [property: string]: Hooks }; - methods?: { [name: string]: Function }; - } - - export interface Hooks { - beforeValidation?: (next?) => void; - beforeCreate?: (next?) => void; - afterCreate?: (next?) => void; - beforeSave?: (next?) => void; - afterSave?: (next?) => void; - afterLoad?: (next?) => void; - afterAutoFetch?: (next?) => void; - beforeRemove?: (next?) => void; - afterRemove?: (next?) => void; - } - - export interface IConnectionOptions { - protocol: string; - host?: string; - port?: number; - auth?: string; - username?: string; - password?: string; - database?: string; - pool?: boolean; - debug?: boolean; - } - - export interface IAggregated { - groupBy(...columns: string[]): IAggregated; - limit(limit: number): IAggregated; - limit(offset: number, limit: number): IAggregated; - order(...order: string[]): IAggregated; - select(columns: string[]): IAggregated; - select(...columns: string[]): IAggregated; - as(alias: string): IAggregated; - call(fun: string, args: any[]): IAggregated; - get(callback: (err: Error, instance: Instance) => void); - } - - export interface IChainFind { - find(conditions: { [property: string]: any }): IChainFind; - only(...args: string[]): IChainFind; - limit(limit: number): IChainFind; - offset(offset: number): IChainFind; - run(callback: (err: Error, results: Instance[]) => void): void; - count(callback: (err: Error, count: number) => void): void; - remove(callback: (err: Error) => void): void; - save(callback: (err: Error) => void): void; - each(callback: (result: Instance) => void): void; - each(): IChainFind; - filter(callback: (result: Instance) => boolean): IChainFind; - sort(callback: (a: Instance, b: Instance) => boolean): IChainFind; - get(callback: (results: Instance[]) => void): IChainFind; - } - - /* - * Classes - */ - - export class ORM extends events.EventEmitter { - validators: enforce; - enforce: enforce; - settings: Settings; - driver_name: string; - driver: any; - tools: any; - models: { [key: string]: Model }; - plugins: Plugin[]; - - use(plugin: string, options?: any): ORM; - use(plugin: Plugin, options?: any): ORM; - - define(name: string, properties: { [key: string]: Property }, opts?: ModelOptions): Model; - ping(callback: (err: Error) => void): ORM; - close(callback: (err: Error) => void): ORM; - load(file: string, callback: (err: Error) => void): any; - sync(callback: (err: Error) => void): ORM; - drop(callback: (err: Error) => void): ORM; - } - - export class enforce { - static required(message?: string); - static notEmptyString(message?: string); - static rangeNumber(min: number, max: number, message?: string); - static rangeLength(min: number, max: number, message?: string); - static insideList(inside: string[], message?: string); - static insideList(inside: number[], message?: string); - static outsideList(outside: string[], message?: string); - static outsideList(outside: number[], message?: string); - static password(conditions?: string, message?: string); - static patterns(expr: RegExp, message?: string); - static patterns(expr: string, flags: string, message?: string); - static equalToProperty(name: string, message?: string); - static unique(message?: string); - static unique(opts: { ignoreCase: boolean }, message?: string); - } - - export function equalToProperty(name: string, message?: string); - export function unique(message?: string); - export function unique(opts: { ignoreCase: boolean }, message?: string); - - export class singleton { - static clear(key?: string): singleton; - static get(key, opts: { - cache?: any; - save_check?: boolean; - }, createCb: Function, returnCb: Function); - } - - export class Settings { - static Container: any; - - static defaults(): { - properties: { - primary_key: string; - association_key: string; - required: boolean; - }; - - instance: { - cache: boolean; - cacheSaveCheck: boolean; - autoSave: boolean; - autoFetch: boolean; - autoFetchLimit: number; - cascadeRemove: boolean; - returnAllErrors: boolean; - }; - - connection: { - reconnect: boolean; - poll: boolean; - debug: boolean; - }; +/// +/// + +import http = require('http'); + +declare module orm { + export class ORM extends EventEmitter { + validators: enforce; + enforce: enforce; + settings: Settings; + driver_name: string; + driver: any; + tools: any; + models: { [key: string]: Model }; + plugins: Plugin[]; + + use(plugin: string, options?: any): ORM; + use(plugin: Plugin, options?: any): ORM; + + define(name: string, properties: { [key: string]: Property }, opts?: ModelOptions): Model; + ping(callback: (err: Error) => void): ORM; + close(callback: (err: Error) => void): ORM; + load(file: string, callback: (err: Error) => void): any; + sync(callback: (err: Error) => void): ORM; + drop(callback: (err: Error) => void): ORM; + } + + export class enforce { + static required(message?: string); + static notEmptyString(message?: string); + static rangeNumber(min: number, max: number, message?: string); + static rangeLength(min: number, max: number, message?: string); + static insideList(inside: string[], message?: string); + static insideList(inside: number[], message?: string); + static outsideList(outside: string[], message?: string); + static outsideList(outside: number[], message?: string); + static password(conditions?: string, message?: string); + static patterns(expr: RegExp, message?: string); + static patterns(expr: string, flags: string, message?: string); + static equalToProperty(name: string, message?: string); + static unique(message?: string); + static unique(opts: { ignoreCase: boolean }, message?: string); + } + + export function equalToProperty(name: string, message?: string); + export function unique(message?: string); + export function unique(opts: { ignoreCase: boolean }, message?: string); + + export class singleton { + static clear(key?: string): singleton; + static get(key, opts: { + cache?: any; + save_check?: boolean; + }, createCb: Function, returnCb: Function); + } + + export class Settings { + static Container: any; + + static defaults(): { + properties: { + primary_key: string; + association_key: string; + required: boolean; + }; + + instance: { + cache: boolean; + cacheSaveCheck: boolean; + autoSave: boolean; + autoFetch: boolean; + autoFetchLimit: number; + cascadeRemove: boolean; + returnAllErrors: boolean; + }; + + connection: { + reconnect: boolean; + poll: boolean; + debug: boolean; }; + }; + + constructor(settings: any); + + //[key: string]: { + // get: (key, def) => any; + // set: (key, value) => Settings; + // unset: (...keys: string[]) => Settings; + //} + + } + + export var settings: Settings; - constructor(settings: any); - - //[key: string]: { - // get: (key, def) => any; - // set: (key, value) => Settings; - // unset: (...keys: string[]) => Settings; - //} - - } - - export var settings: Settings; - - export class Property { - static normalize(property: string, settings: Settings): any; - static validate(value: any, property: string): any; - } - - export interface ErrorCodes { - QUERY_ERROR: number; - NOT_FOUND: number; - NOT_DEFINED: number; - NO_SUPPORT: number; - MISSING_CALLBACK: number; - PARAM_MISMATCH: number; - CONNECTION_LOST: number; - - generateError(code: number, message: string, extra: any): Error; - } - - export function Text(type: string): sqlquery.TextQuery; - export function eq(value: any): sqlquery.Comparator; - export function ne(value: any): sqlquery.Comparator; - export function gt(value: any): sqlquery.Comparator; - export function gte(value: any): sqlquery.Comparator; - export function lt(value: any): sqlquery.Comparator; - export function lte(value: any): sqlquery.Comparator; - export function like(value: string): sqlquery.Comparator; - export function between(a: number, b: number): sqlquery.Comparator; - export function not_between(a: number, b: number): sqlquery.Comparator; - export function express(uri: string, handlers: { - define(db: ORM, models: { [key: string]: Model }); - }): (req, res, next) => void; - export function use(connection, protocol: string, options, callback: (err: Error, db?: ORM) => void); - export function connect(uri: string): ORM; - export function connect(uri: string, callback: (err: Error, db: ORM) => void); - export function connect(options: IConnectionOptions): ORM; - export function connect(options: IConnectionOptions, callback: (err: Error, db: ORM) => void); + export class Property { + static normalize(property: string, settings: Settings): any; + static validate(value: any, property: string): any; } - export = orm; + export interface ErrorCodes { + QUERY_ERROR: number; + NOT_FOUND: number; + NOT_DEFINED: number; + NO_SUPPORT: number; + MISSING_CALLBACK: number; + PARAM_MISMATCH: number; + CONNECTION_LOST: number; + + generateError(code: number, message: string, extra: any): Error; + } + + export function Text(type: string): sqlquery.TextQuery; + export function express(uri: string, handlers: { + define(db: ORM, models: { [key: string]: Model }); + }): (req, res, next) => void; + export function use(connection, protocol: string, options, callback: (err: Error, db?: ORM) => void); + export function connect(uri: string): ORM; + export function connect(uri: string, callback: (err: Error, db: ORM) => void); + export function connect(options: IConnectionOptions): ORM; + export function connect(options: IConnectionOptions, callback: (err: Error, db: ORM) => void); + + + /** + * Parameter Type Interfaces + */ + + export interface Model { + (): Instance; + (...ids: any[]): Instance; + + properties: { [property: string]: Property }; + settings: Settings; + + drop(callback?: (err: Error) => void): Model; + sync(callback?: (err: Error) => void): Model; + get(...args: any[]): Model; + find(conditions: { [property: string]: any }, callback: (err: Error, results: Instance[]) => void): Model; + find(conditions: { [property: string]: any }, options: { + limit?: number; + order?: any; + }, callback: (err: Error, results: Instance[]) => void): Model; + find(conditions: { [property: string]: any }, limit: number, order: string[], callback: (err: Error, results: Instance[]) => void): Model; + + all(conditions: { [property: string]: any }, callback: (err: Error, results: Instance[]) => void): Model; + all(conditions: { [property: string]: any }, options: { + limit?: number; + order?: any; + }, callback: (err: Error, results: Instance[]) => void): Model; + all(conditions: { [property: string]: any }, limit: number, order: string[], callback: (err: Error, results: Instance[]) => void): Model; + + one(conditions: { [property: string]: any }, callback: (err: Error, result: Instance) => void): Model; + one(conditions: { [property: string]: any }, options: { + limit?: number; + order?: any; + }, callback: (err: Error, result: Instance) => void): Model; + one(conditions: { [property: string]: any }, limit: number, order: string[], callback: (err: Error, result: Instance) => void): Model; + + count(callback: (err: Error, count: number) => void): Model; + count(conditions: { [property: string]: any }, callback: (err: Error, count: number) => void): Model; + + aggregate(conditions: { [property: string]: any }): IAggregated; + aggregate(properties: string[]): IAggregated; + aggregate(conditions: { [property: string]: any }, properties: string[]): IAggregated; + + exists(id: any, callback: (err: Error, exists: boolean) => void): Model; + exists(...args: any[]): Model; + + create(data: { [property: string]: any; }, callback: (err: Error, instance: Instance) => void): Model; + create(...args: any[]): Model; + + clear(): Model; + + table: string; + id: string[]; + + [property: string]: any; + } + + export interface Instance { + on(event: string, callback): Instance; + save(): Instance; + save(data: { [property: string]: any; }, callback: (err: Error) => void): Instance; + save(data: { [property: string]: any; }, options: any, callback: (err: Error) => void): Instance; + saved: boolean; + remove(callback: (err: Error) => void): Instance; + isInstance: boolean; + isPersisted: boolean; + isShell: boolean; + validate(callback: (errors: Error[]) => void); + model: Model; + + [property: string]: any; + } + + export interface ModelOptions { + id?: string[]; + autoFetch?: boolean; + autoFetchLimit?: number; + cacheFetch?: boolean; + hooks?: { [property: string]: Hooks }; + methods?: { [name: string]: Function }; + } + + export interface Hooks { + beforeValidation?: (next?) => void; + beforeCreate?: (next?) => void; + afterCreate?: (next?) => void; + beforeSave?: (next?) => void; + afterSave?: (next?) => void; + afterLoad?: (next?) => void; + afterAutoFetch?: (next?) => void; + beforeRemove?: (next?) => void; + afterRemove?: (next?) => void; + } + + export interface IConnectionOptions { + protocol: string; + host?: string; + port?: number; + auth?: string; + username?: string; + password?: string; + database?: string; + pool?: boolean; + debug?: boolean; + } + + export interface IAggregated { + groupBy(...columns: string[]): IAggregated; + limit(limit: number): IAggregated; + limit(offset: number, limit: number): IAggregated; + order(...order: string[]): IAggregated; + select(columns: string[]): IAggregated; + select(...columns: string[]): IAggregated; + as(alias: string): IAggregated; + call(fun: string, args: any[]): IAggregated; + get(callback: (err: Error, instance: Instance) => void); + } } \ No newline at end of file diff --git a/lib/TypeScript/sql-query.d.ts b/lib/TypeScript/sql-query.d.ts index fe9fa7ad..8f18734a 100644 --- a/lib/TypeScript/sql-query.d.ts +++ b/lib/TypeScript/sql-query.d.ts @@ -1,79 +1,76 @@ -declare module "sqlquery" { - module sqlquery { - export class Query { - constructor(dialect: string); - constructor(options: { - dialect: string; - }); - static Text(type: string): TextQuery; +declare module sqlquery { + export class Query { + constructor(dialect: string); + constructor(options: { + dialect: string; + }); + static Text(type: string): TextQuery; - static Comparators: string[]; - static between(a: number, b: number): Comparator; - static not_between(a: number, b: number): Comparator; - static like(expression: string): Comparator; - static eq(value: any): Comparator; - static ne(value: any): Comparator; - static gt(value: any): Comparator; - static gte(value: any): Comparator; - static lt(value: any): Comparator; - static lte(value: any): Comparator; + static Comparators: string[]; + static between(a: number, b: number): Comparator; + static not_between(a: number, b: number): Comparator; + static like(expression: string): Comparator; + static eq(value: any): Comparator; + static ne(value: any): Comparator; + static gt(value: any): Comparator; + static gte(value: any): Comparator; + static lt(value: any): Comparator; + static lte(value: any): Comparator; - escapeId(id: string): string; - escapeId(id: string, table: string): string; - escapeVal(value: any): string; - select(): SelectQuery; - insert(): InsertQuery; - update(): UpdateQuery; - remove(): RemoveQuery; - } + escapeId(id: string): string; + escapeId(id: string, table: string): string; + escapeVal(value: any): string; + select(): SelectQuery; + insert(): InsertQuery; + update(): UpdateQuery; + remove(): RemoveQuery; + } - export interface Comparator { - sql_comparator(): string; - from?: any; - to?: any; - expr?: string; - value?: any; - } + export interface Comparator { + sql_comparator(): string; + from?: any; + to?: any; + expr?: string; + value?: any; + } - export interface TextQuery { - data: any; - type: string; - } + export interface TextQuery { + data: any; + type: string; + } - export interface SelectQuery { - select(fields: string): SelectQuery; - calculateFoundRows: SelectQuery; - as(alias: string): SelectQuery; - fun(fun: string, column: string, alias: string): SelectQuery; - from(table: string, from_id: string, to_id: string): SelectQuery; - from(table: string, from_id: string, to_table: string, to_id: string): SelectQuery; - where(...args: any[]): SelectQuery; - whereExists(table: string, table_link: string, link: string, conditions: { [column: string]: any }): SelectQuery; - groupBy(...columns: string[]): SelectQuery; - offset(offset: number): SelectQuery; - limit(limit: number): SelectQuery; - order(column: string, direction: string): SelectQuery; - build(): string; - } + export interface SelectQuery { + select(fields: string): SelectQuery; + calculateFoundRows: SelectQuery; + as(alias: string): SelectQuery; + fun(fun: string, column: string, alias: string): SelectQuery; + from(table: string, from_id: string, to_id: string): SelectQuery; + from(table: string, from_id: string, to_table: string, to_id: string): SelectQuery; + where(...args: any[]): SelectQuery; + whereExists(table: string, table_link: string, link: string, conditions: { [column: string]: any }): SelectQuery; + groupBy(...columns: string[]): SelectQuery; + offset(offset: number): SelectQuery; + limit(limit: number): SelectQuery; + order(column: string, direction: string): SelectQuery; + build(): string; + } - export interface InsertQuery { - into(table: string): InsertQuery; - set(values: { [key: string]: any }[]): InsertQuery; - build(): string; - } + export interface InsertQuery { + into(table: string): InsertQuery; + set(values: { [key: string]: any }[]): InsertQuery; + build(): string; + } - export interface UpdateQuery { - into(table: string): UpdateQuery; - set(values: { [column: string]: any }): UpdateQuery; - where(...conditions: { [column: string]: any }[]): UpdateQuery; - build(): string; - } + export interface UpdateQuery { + into(table: string): UpdateQuery; + set(values: { [column: string]: any }): UpdateQuery; + where(...conditions: { [column: string]: any }[]): UpdateQuery; + build(): string; + } - export interface RemoveQuery { - from(table: string): RemoveQuery; - where(...conditions: { [column: string]: any }[]): RemoveQuery; - build(): string; - } + export interface RemoveQuery { + from(table: string): RemoveQuery; + where(...conditions: { [column: string]: any }[]): RemoveQuery; + build(): string; } - export = sqlquery; } diff --git a/lib/Validators.js b/lib/Validators.js index f7d51084..563c3c19 100644 --- a/lib/Validators.js +++ b/lib/Validators.js @@ -23,10 +23,8 @@ var validators = { * checking). **/ validators.equalToProperty = function (name, msg) { - return function (v, next) { - if (v === this[name]) { - return next(); - } + return function (v, next, ctx) { + if (v === this[name]) return next(); return next(msg || 'not-equal-to-property'); }; }; @@ -51,24 +49,17 @@ validators.unique = function () { for (k in arguments) { arg = arguments[k]; - if (typeof arg === "string") { - msg = arg; - } else if (typeof arg === "object") { - opts = arg; - } + if (typeof arg === 'string') msg = arg; + else if (typeof arg === 'object') opts = arg; } return function (v, next, ctx) { - var s, scopeProp; + var s, scopeProp; - if (typeof v === "undefined" || v === null) { - return next(); - } + if (typeof v === 'undefined' || v === null) return next(); - //Cannot process on database engines which don't support SQL syntax - if (!ctx.driver.isSql) { - return next('not-supported'); - } + //Cannot process on database engines which don't support SQL syntax + if (!ctx.driver.isSql) return next('not-supported'); var chain = ctx.model.find(); diff --git a/package.json b/package.json index 8100d1ad..3486432d 100644 --- a/package.json +++ b/package.json @@ -1,56 +1,53 @@ { - "author" : "Diogo Resende ", - "name" : "orm", - "description" : "NodeJS Object-relational mapping", - "keywords" : [ - "orm", - "odm", - "database", - "mysql", - "postgres", - "redshift", - "sqlite", - "mongodb" - ], - "version" : "2.1.4", - "license" : "MIT", - "homepage" : "http://dresende.github.io/node-orm2", - "repository" : "http://github.com/dresende/node-orm2.git", - "contributors": [ - { "name" : "Bramus Van Damme", "email" : "bramus@bram.us" }, - { "name" : "Lorien Gamaroff", "email" : "lorien@gamaroff.org" }, - { "name" : "preslavrachev" }, - { "name" : "Chris Cowan", "email" : "me@chriscowan.us" }, - { "name" : "Paul Dixon", "email" : "paul.dixon@mintbridge.co.uk" }, - { "name" : "David Kosub" }, - { "name" : "Arek W", "email" : "arek01@gmail.com" }, - { "name" : "Joseph Gilley", "email" : "joe.gilley@gmail.com" }, - { "name" : "Benjamin Pannell", "email" : "admin@sierrasoftworks.com" } - ], - "main" : "./lib/ORM", - "scripts" : { - "test" : "make test" - }, - "engines" : { - "node" : "*" - }, - "analyse" : false, - "dependencies": { - "enforce" : "0.1.2", - "sql-query" : "0.1.16", - "sql-ddl-sync" : "git://github.com/dresende/node-sql-ddl-sync.git#v0.2.0", - "hat" : "0.0.3", - "lodash" : "2.4.1" - }, - "devDependencies": { - "mysql" : "2.0.0-alpha9", - "pg" : "2.6.2", - "sqlite3" : "2.1.7", - "async" : "*", - "mocha" : "1.13.0", - "should" : "1.2.2", - "mongodb" : "1.3.19", - "glob" : "3.2.8" - }, - "optionalDependencies": {} -} + "author" : "Diogo Resende ", + "name" : "orm-2.1.3", + "description" : "NodeJS Object-relational mapping", + "keywords" : [ + "orm", + "odm", + "database", + "mysql", + "postgres", + "redshift", + "sqlite", + "mongodb" + ], + "version" : "2.1.3", + "license" : "MIT", + "repository" : "https://ddo@github.com/ddo/node-orm2.git", + "contributors": [ + { "name" : "Bramus Van Damme", "email" : "bramus@bram.us" }, + { "name" : "Lorien Gamaroff", "email" : "lorien@gamaroff.org" }, + { "name" : "preslavrachev" }, + { "name" : "Chris Cowan", "email" : "me@chriscowan.us" }, + { "name" : "Paul Dixon", "email" : "paul.dixon@mintbridge.co.uk" }, + { "name" : "David Kosub" }, + { "name" : "Arek W", "email" : "arek01@gmail.com" }, + { "name" : "Joseph Gilley", "email" : "joe.gilley@gmail.com" }, + { "name" : "Benjamin Pannell", "email" : "admin@sierrasoftworks.com" } + ], + "main" : "./lib/ORM", + "scripts" : { + "test" : "make test" + }, + "engines" : { + "node" : "*" + }, + "analyse" : false, + "dependencies": { + "enforce" : "0.1.2", + "sql-query" : "0.1.15", + "hat" : "0.0.3", + "lodash" : "2.0.0" + }, + "devDependencies": { + "mysql" : "2.0.0-alpha9", + "pg" : "2.6.2", + "sqlite3" : "2.1.7", + "async" : "*", + "mocha" : "1.12.1", + "should" : "1.2.2", + "mongodb" : "1.3.19" + }, + "optionalDependencies": {} +} \ No newline at end of file diff --git a/test/integration/association-hasmany.js b/test/integration/association-hasmany.js index 737bda21..5dbc076d 100644 --- a/test/integration/association-hasmany.js +++ b/test/integration/association-hasmany.js @@ -1,4 +1,3 @@ -var _ = require('lodash'); var should = require('should'); var helper = require('../support/spec_helper'); var ORM = require('../../'); @@ -19,11 +18,11 @@ describe("hasMany", function () { name : String, surname : String, age : Number - }); + }, opts); Pet = db.define('pet', { name : String }); - Person.hasMany('pets', Pet, {}, { autoFetch: opts.autoFetchPets }); + Person.hasMany('pets', Pet); return helper.dropSync([ Person, Pet ], function () { /** @@ -292,59 +291,61 @@ describe("hasMany", function () { describe("addAccessor", function () { before(setup()); - if (common.protocol() != "mongodb") { - it("might add duplicates", function (done) { - Pet.find({ name: "Mutt" }, function (err, pets) { - Person.find({ name: "Jane" }, function (err, people) { + if (common.protocol() == "mongodb") return; + + it("might add duplicates", function (done) { + Pet.find({ name: "Mutt" }, function (err, pets) { + Person.find({ name: "Jane" }, function (err, people) { + should.equal(err, null); + + people[0].addPets(pets[0], function (err) { should.equal(err, null); - people[0].addPets(pets[0], function (err) { + people[0].getPets("name", function (err, pets) { should.equal(err, null); - people[0].getPets("name", function (err, pets) { - should.equal(err, null); - - should(Array.isArray(pets)); - pets.length.should.equal(2); - pets[0].name.should.equal("Mutt"); - pets[1].name.should.equal("Mutt"); + should(Array.isArray(pets)); + pets.length.should.equal(2); + pets[0].name.should.equal("Mutt"); + pets[1].name.should.equal("Mutt"); - return done(); - }); + return done(); }); }); }); }); - } + }); + }); + + describe("addAccessor", function () { + before(setup()); it("should keep associations and add new ones", function (done) { Pet.find({ name: "Deco" }).first(function (err, Deco) { Person.find({ name: "Jane" }).first(function (err, Jane) { should.equal(err, null); - Jane.getPets(function (err, janesPets) { - should.not.exist(err); - - var petsAtStart = janesPets.length; + Jane.addPets(Deco, function (err) { + should.equal(err, null); - Jane.addPets(Deco, function (err) { + Jane.getPets("name", function (err, pets) { should.equal(err, null); - Jane.getPets("name", function (err, pets) { - should.equal(err, null); - - should(Array.isArray(pets)); - pets.length.should.equal(petsAtStart + 1); - pets[0].name.should.equal("Deco"); - pets[1].name.should.equal("Mutt"); + should(Array.isArray(pets)); + pets.length.should.equal(2); + pets[0].name.should.equal("Deco"); + pets[1].name.should.equal("Mutt"); - return done(); - }); + return done(); }); }); }); }); }); + }); + + describe("addAccessor", function () { + before(setup()); it("should accept several arguments as associations", function (done) { Pet.find(function (err, pets) { @@ -366,44 +367,65 @@ describe("hasMany", function () { }); }); }); + }); + + describe("addAccessor", function () { + before(setup()); it("should accept array as list of associations", function (done) { - Pet.create([{ name: 'Ruff' }, { name: 'Spotty' }],function (err, pets) { + Pet.find(function (err, pets) { Person.find({ name: "Justin" }).first(function (err, Justin) { should.equal(err, null); - Justin.getPets(function (err, justinsPets) { + Justin.addPets(pets, function (err) { should.equal(err, null); - var petCount = justinsPets.length; - - Justin.addPets(pets, function (err) { + Justin.getPets(function (err, all_pets) { should.equal(err, null); - Justin.getPets(function (err, justinsPets) { - should.equal(err, null); - - should(Array.isArray(justinsPets)); - // Mongo doesn't like adding duplicates here, so we add new ones. - should.equal(justinsPets.length, petCount + 2); + should(Array.isArray(all_pets)); + all_pets.length.should.equal(pets.length); - return done(); - }); + return done(); }); }); }); }); }); + }); - it("should throw if no items passed", function (done) { - Person.one(function (err, person) { - should.equal(err, null); + describe("setAccessor", function () { + before(setup()); - (function () { - person.addPets(function () {}); - }).should.throw(); + it("clears current associations", function (done) { + Pet.find({ name: "Deco" }, function (err, pets) { + var Deco = pets[0]; - return done(); + Person.find({ name: "Jane" }).first(function (err, Jane) { + should.equal(err, null); + + Jane.getPets(function (err, pets) { + should.equal(err, null); + + should(Array.isArray(pets)); + pets.length.should.equal(1); + pets[0].name.should.equal("Mutt"); + + Jane.setPets(Deco, function (err) { + should.equal(err, null); + + Jane.getPets(function (err, pets) { + should.equal(err, null); + + should(Array.isArray(pets)); + pets.length.should.equal(1); + pets[0].name.should.equal(Deco.name); + + return done(); + }); + }); + }); + }); }); }); }); @@ -453,63 +475,22 @@ describe("hasMany", function () { }); }); - it("should remove all associations if an empty array is passed", function (done) { - Person.find({ name: "Justin" }).first(function (err, Justin) { + it("should throw if no items passed", function (done) { + Person.one(function (err, person) { should.equal(err, null); - Justin.getPets(function (err, pets) { - should.equal(err, null); - should.equal(pets.length, 2); - Justin.setPets([], function (err) { - should.equal(err, null); - - Justin.getPets(function (err, pets) { - should.equal(err, null); - should.equal(pets.length, 0); - - return done(); - }); - }); - }); - }); - }); - - it("clears current associations", function (done) { - Pet.find({ name: "Deco" }, function (err, pets) { - var Deco = pets[0]; - - Person.find({ name: "Jane" }).first(function (err, Jane) { - should.equal(err, null); - - Jane.getPets(function (err, pets) { - should.equal(err, null); - - should(Array.isArray(pets)); - pets.length.should.equal(1); - pets[0].name.should.equal("Mutt"); - - Jane.setPets(Deco, function (err) { - should.equal(err, null); - - Jane.getPets(function (err, pets) { - should.equal(err, null); - - should(Array.isArray(pets)); - pets.length.should.equal(1); - pets[0].name.should.equal(Deco.name); + (function () { + person.addPets(function () {}); + }).should.throw(); - return done(); - }); - }); - }); - }); + return done(); }); }); }); describe("with autoFetch turned on", function () { before(setup({ - autoFetchPets : true + autoFetch : true })); it("should fetch associations", function (done) { @@ -523,23 +504,5 @@ describe("hasMany", function () { return done(); }); }); - - it("should save existing", function (done) { - Person.create({ name: 'Bishan' }, function (err) { - should.not.exist(err); - - Person.one({ name: 'Bishan' }, function (err, person) { - should.not.exist(err); - - person.surname = 'Dominar'; - - person.save(function (err) { - should.not.exist(err); - - done(); - }); - }); - }); - }); }); }); diff --git a/test/integration/drivers/postgres_spec.js b/test/integration/drivers/postgres_spec.js deleted file mode 100644 index 337e6c97..00000000 --- a/test/integration/drivers/postgres_spec.js +++ /dev/null @@ -1,115 +0,0 @@ -var _ = require('lodash'); -var should = require('should'); -var Driver = require('../../../lib/Drivers/DML/postgres').Driver; -var helper = require('../../support/spec_helper'); -var common = require('../../common'); - -if (common.protocol() == "mongodb") return; - -describe("Postgres driver", function() { - describe("#propertyToValue", function () { - describe("type object", function () { - function evaluate (input) { - var driver = new Driver({}, {}, {}); - return driver.propertyToValue(input, { type: 'object' }); - } - - it("should not change null", function () { - should.strictEqual(evaluate(null), null); - }); - - it("should not change buffer", function () { - var b = new Buffer('abc'); - should.strictEqual(evaluate(b), b); - }); - - it("should encode everything else as a Buffer", function () { - var input = { abc: 123 }; - var out = evaluate(input); - - should(out instanceof Buffer); - should.equal(JSON.stringify(input), out.toString()); - }); - }); - - describe("date", function () { - function evaluate (input, opts) { - if (!opts) opts = {}; - var driver = new Driver(opts.config, {}, {}); - return driver.propertyToValue(input, { type: 'date' }); - } - - it("should do nothing when timezone isn't configured", function () { - var input = new Date(); - var inputStr = input.toString(); - var out = evaluate(input); - - should.strictEqual(input, out); - should.equal(inputStr, out.toString()); - }); - - it("should offset time by specified timezone amount for + timezones"); - - it("should offset time by specified timezone amount for + timezones"); - }); - - describe("type point", function () { - function evaluate (input) { - var driver = new Driver({}, {}, {}); - return driver.propertyToValue(input, { type: 'point' }); - } - - it("should encode correctly", function () { - var out = evaluate({ x: 5, y: 7 }); - - should(out instanceof Function); - should.equal(out(), "POINT(5, 7)"); - }); - }); - - describe("custom type", function () { - var customType = { - propertyToValue: function (input) { - return input + ' QWERTY'; - } - }; - - function evaluate (input, customTypes) { - var driver = new Driver({}, {}, {}); - if (customType) { - for (var k in customTypes) { - driver.customTypes[k] = customTypes[k]; - } - } - return driver.propertyToValue(input, { type: 'qwerty' }); - } - - it("should do custom type conversion if provided", function () { - var opts = { qwerty: customType }; - var out = evaluate('f', opts); - - should.equal(out, 'f QWERTY'); - }); - - it("should not do custom type conversion if not provided", function () { - var opts = { qwerty: {} }; - var out = evaluate('f', opts); - - should.equal(out, 'f'); - }); - }); - - it("should do nothing for other types", function () { - function evaluate (input, type) { - var driver = new Driver({}, {}, {}); - return driver.propertyToValue(input, { type: type }); - } - - should.strictEqual(evaluate('abc', { type: 'string' }), 'abc'); - should.strictEqual(evaluate(42, { type: 'number' }), 42); - should.strictEqual(evaluate(undefined, { type: 'bleh' }), undefined); - }); - - }); - -}); diff --git a/test/integration/error_spec.js b/test/integration/error_spec.js deleted file mode 100644 index eab26890..00000000 --- a/test/integration/error_spec.js +++ /dev/null @@ -1,56 +0,0 @@ -var should = require('should'); -var ORMError = require('../../lib/Error'); - -describe("Error", function () { - describe("constructor", function () { - it("should inherit from native Error", function () { - var e = new ORMError("Test message", 'PARAM_MISMATCH'); - should(e instanceof Error); - }); - - it("should have a valid stack", function () { - var e = new ORMError("Test message", 'PARAM_MISMATCH'); - var stackArr = e.stack.split('\n'); - // [0] is '' - should(stackArr[1].indexOf('test/integration/error_spec.js') > 0); - }); - - it("should have the right name", function () { - var e = new ORMError("Test message", 'PARAM_MISMATCH'); - should.equal(e.name, 'ORMError'); - }); - - it("should throw on invalid code", function () { - (function () { - var e = new ORMError("Test message", 'FLYING_SQUIRRELS'); - }).should.throw("Invalid error code: FLYING_SQUIRRELS"); - }); - - it("should assign the code", function () { - var e = new ORMError("Test message", 'PARAM_MISMATCH'); - should.equal(e.code, 6); - }); - - it("should assign literal code", function () { - var e = new ORMError("Test message", 'PARAM_MISMATCH'); - should.equal(e.literalCode, 'PARAM_MISMATCH'); - }); - - it("should assign extra params", function () { - var e = new ORMError("Test message", 'PARAM_MISMATCH', { details: "something" }); - should.equal(e.details, "something"); - }); - - it("should stringify nicely", function () { - var e = new ORMError("Test message", 'PARAM_MISMATCH'); - should.equal(e.toString(), "[ORMError PARAM_MISMATCH: Test message]"); - }); - }); - - describe("codes", function () { - it("should be exposed", function () { - should.exist(ORMError.codes); - should.equal(ORMError.codes['NOT_FOUND'], 2); - }); - }); -}); diff --git a/test/integration/event.js b/test/integration/event.js index f14c4830..8ade7b5f 100644 --- a/test/integration/event.js +++ b/test/integration/event.js @@ -79,19 +79,5 @@ describe("Event", function() { return done(); }); }); - - it("should be writable for mocking", function (done) { - var triggered = false; - var John = new Person(); - - John.on = function(event, cb) { - triggered = true; - }; - triggered.should.be.false; - - John.on("mocked", function (err) {} ); - triggered.should.be.true; - done(); - }); }); }); diff --git a/test/integration/hook.js b/test/integration/hook.js index 2db7299b..a1e1bc68 100644 --- a/test/integration/hook.js +++ b/test/integration/hook.js @@ -354,7 +354,7 @@ describe("Hook", function() { }); describe("afterSave", function () { - beforeEach(setup()); + before(setup()); it("should trigger after saving an instance", function (done) { Person.create([{ name: "John Doe" }], function () { @@ -365,19 +365,6 @@ describe("Hook", function() { return done(); }); }); - - it("should not trigger after saving an unchanged instance", function (done) { - Person.create({ name: "Edger" }, function (err, edger) { - should.not.exist(err); - - triggeredHooks = {}; - edger.save(function (err) { - should.not.exist(err); - should.not.exist(triggeredHooks.afterSave); - done(); - }); - }); - }); }); describe("beforeValidation", function () { diff --git a/test/integration/instance.js b/test/integration/instance.js index 5312ea41..87374d72 100644 --- a/test/integration/instance.js +++ b/test/integration/instance.js @@ -130,16 +130,6 @@ describe("Model instance", function() { it("should return false for new instances", function () { should.equal((new Person).isPersisted(), false); }); - - it("should be writable for mocking", function() { - var person = new Person() - var triggered = false; - person.isPersisted = function() { - triggered = true; - }; - person.isPersisted() - triggered.should.be.true; - }); }); describe("#isShell", function () { diff --git a/test/integration/model-find-chain.js b/test/integration/model-find-chain.js index 1bc44b09..83638c5e 100644 --- a/test/integration/model-find-chain.js +++ b/test/integration/model-find-chain.js @@ -6,7 +6,6 @@ var common = require('../common'); describe("Model.find() chaining", function() { var db = null; var Person = null; - var Dog = null; var setup = function () { return function (done) { @@ -37,30 +36,6 @@ describe("Model.find() chaining", function() { }; }; - var setup2 = function () { - return function (done) { - Dog = db.define("dog", { - name: String, - }); - Dog.hasMany("friends"); - Dog.hasMany("family"); - - ORM.singleton.clear(); // clear cache - - return helper.dropSync(Dog, function () { - Dog.create([{ - name : "Fido", - friends : [{ name: "Gunner" }, { name: "Chainsaw" }], - family : [{ name: "Chester" }] - }, { - name : "Thumper", - friends : [{ name: "Bambi" }], - family : [{ name: "Princess" }, { name: "Butch" }] - }], done); - }); - }; - }; - before(function (done) { helper.connect(function (connection) { db = connection; @@ -504,76 +479,6 @@ describe("Model.find() chaining", function() { }); }); - describe(".eager()", function () { - before(setup2()); - - // TODO: Remove this code once the Mongo eager loading is implemented - var isMongo = function () { - if (db.driver.config.protocol == "mongodb:") { - (function () { - Dog.find().eager("friends").all(function () { - // Should not ever run. - }); - }).should.throw(); - - return true; - } - return false; - }; - - it("should fetch all listed associations in a single query", function (done) { - if (isMongo()) { return done(); }; - - Dog.find({ name: ["Fido", "Thumper"] }).eager("friends").all(function (err, dogs) { - should.equal(err, null); - - should(Array.isArray(dogs)); - - dogs.length.should.equal(2); - - dogs[0].friends.length.should.equal(2); - dogs[1].friends.length.should.equal(1); - done(); - }); - }); - - it("should be able to handle multiple associations", function (done) { - if (isMongo()) { return done(); }; - - Dog.find({ name: ["Fido", "Thumper"] }).eager("friends", "family").all(function (err, dogs) { - should.equal(err, null); - - should(Array.isArray(dogs)); - - dogs.length.should.equal(2); - - dogs[0].friends.length.should.equal(2); - dogs[0].family.length.should.equal(1); - dogs[1].friends.length.should.equal(1); - dogs[1].family.length.should.equal(2); - done(); - }); - }); - - it("should work with array parameters too", function (done) { - if (isMongo()) { return done(); }; - - Dog.find({ name: ["Fido", "Thumper"] }).eager(["friends", "family"]).all(function (err, dogs) { - should.equal(err, null); - - should(Array.isArray(dogs)); - - dogs.length.should.equal(2); - - dogs[0].friends.length.should.equal(2); - dogs[0].family.length.should.equal(1); - dogs[1].friends.length.should.equal(1); - dogs[1].family.length.should.equal(2); - done(); - }); - }); - }); - describe(".success()", function () { before(setup()); diff --git a/test/integration/model-remove.js b/test/integration/model-remove.js deleted file mode 100644 index 6b65b476..00000000 --- a/test/integration/model-remove.js +++ /dev/null @@ -1,60 +0,0 @@ -var should = require('should'); -var helper = require('../support/spec_helper'); -var ORM = require('../../'); - -describe("Model.remove()", function() { - var db = null; - var Person = null; - - var setup = function () { - return function (done) { - Person = db.define("person", { - name : String - }); - - return helper.dropSync(Person, function () { - Person.create([{ - id : 1, - name: "Jeremy Doe" - }, { - id : 2, - name: "John Doe" - }, { - id : 3, - name: "Jane Doe" - }], done); - }); - }; - }; - - before(function (done) { - helper.connect(function (connection) { - db = connection; - - return done(); - }); - }); - - after(function () { - return db.close(); - }); - - describe("mockable", function() { - before(setup()); - - it("remove should be writable", function(done) { - var John = new Person({ - name: "John" - }); - var removeCalled = false; - John.remove = function(cb) { - removeCalled = true; - cb(null); - }; - John.remove(function(err) { - should.equal(removeCalled,true); - return done(); - }); - }); - }); -}); diff --git a/test/integration/model-save.js b/test/integration/model-save.js index 750eb058..2d17abcd 100644 --- a/test/integration/model-save.js +++ b/test/integration/model-save.js @@ -8,14 +8,12 @@ describe("Model.save()", function() { var Person = null; var setup = function (nameDefinition, opts) { - opts = opts || {}; - return function (done) { Person = db.define("person", { name : nameDefinition || String }, opts || {}); - Person.hasOne("parent", Person, opts.hasOneOpts); + Person.hasOne("parent"); return helper.dropSync(Person, done); }; @@ -200,119 +198,6 @@ describe("Model.save()", function() { }); }); - describe("with saveAssociations", function () { - var afterSaveCalled = false; - - if (common.protocol() == 'mongodb') return; - - beforeEach(function (done) { - function afterSave () { - afterSaveCalled = true; - } - var hooks = { afterSave: afterSave }; - - setup(null, { hooks: hooks, cache: false, hasOneOpts: { autoFetch: true } })(function (err) { - should.not.exist(err); - - Person.create({ name: 'Olga' }, function (err, olga) { - should.not.exist(err); - - should.exist(olga); - Person.create({ name: 'Hagar', parent_id: olga.id }, function (err, hagar) { - should.not.exist(err); - should.exist(hagar); - afterSaveCalled = false; - done(); - }); - }); - }); - }); - - it("off should not save associations but save itself", function (done) { - Person.one({ name: 'Hagar' }, function (err, hagar) { - should.not.exist(err); - should.exist(hagar.parent); - - hagar.parent.name = 'Olga2'; - hagar.save({name: 'Hagar2'}, { saveAssociations: false }, function (err) { - should.not.exist(err); - should.equal(afterSaveCalled, true); - - Person.get(hagar.parent.id, function (err, olga) { - should.not.exist(err); - should.equal(olga.name, 'Olga'); - done(); - }); - }); - }); - }); - - it("off should not save associations or itself if there are no changes", function (done) { - Person.one({ name: 'Hagar' }, function (err, hagar) { - should.not.exist(err); - - hagar.save({}, { saveAssociations: false }, function (err) { - should.not.exist(err); - should.equal(afterSaveCalled, false); - - Person.get(hagar.parent.id, function (err, olga) { - should.not.exist(err); - should.equal(olga.name, 'Olga'); - done(); - }); - }); - }); - }); - - it("unspecified should save associations and itself", function (done) { - Person.one({ name: 'Hagar' }, function (err, hagar) { - should.not.exist(err); - should.exist(hagar.parent); - - hagar.parent.name = 'Olga2'; - hagar.save({name: 'Hagar2'}, function (err) { - should.not.exist(err); - - Person.get(hagar.parent.id, function (err, olga) { - should.not.exist(err); - should.equal(olga.name, 'Olga2'); - - Person.get(hagar.id, function (err, person) { - should.not.exist(err); - should.equal(person.name, 'Hagar2'); - - done(); - }); - }); - }); - }); - }); - - it("on should save associations and itself", function (done) { - Person.one({ name: 'Hagar' }, function (err, hagar) { - should.not.exist(err); - should.exist(hagar.parent); - - hagar.parent.name = 'Olga2'; - hagar.save({name: 'Hagar2'}, { saveAssociations: true }, function (err) { - should.not.exist(err); - - Person.get(hagar.parent.id, function (err, olga) { - should.not.exist(err); - should.equal(olga.name, 'Olga2'); - - Person.get(hagar.id, function (err, person) { - should.not.exist(err); - should.equal(person.name, 'Hagar2'); - - done(); - }); - }); - }); - }); - }); - }); - describe("with a point property", function () { if (common.protocol() == 'sqlite' || common.protocol() == 'mongodb') return; @@ -332,38 +217,4 @@ describe("Model.save()", function() { }); }); }); - - describe("mockable", function() { - before(setup()); - - it("save should be writable", function(done) { - var John = new Person({ - name: "John" - }); - var saveCalled = false; - John.save = function(cb) { - saveCalled = true; - cb(null); - }; - John.save(function(err) { - should.equal(saveCalled,true); - return done(); - }); - }); - - it("saved should be writable", function(done) { - var John = new Person({ - name: "John" - }); - var savedCalled = false; - John.saved = function() { - savedCalled = true; - return true; - }; - - John.saved() - savedCalled.should.be.true; - done(); - }) - }); }); diff --git a/test/integration/orm-exports.js b/test/integration/orm-exports.js index ab445a94..d6175b5a 100644 --- a/test/integration/orm-exports.js +++ b/test/integration/orm-exports.js @@ -115,11 +115,7 @@ describe("ORM.connect()", function () { var db = ORM.connect("unknown://db"); db.on("connect", function (err) { - should.equal(err.literalCode, 'NO_SUPPORT'); - should.equal( - err.message, - "Connection protocol not supported - have you installed the database driver for unknown?" - ); + err.message.should.equal("CONNECTION_PROTOCOL_NOT_SUPPORTED"); return done(); }); @@ -130,7 +126,7 @@ describe("ORM.connect()", function () { db.on("connect", function (err) { should.exist(err); - should.equal(err.message.indexOf("Connection protocol not supported"), -1); + err.message.should.not.equal("CONNECTION_PROTOCOL_NOT_SUPPORTED"); err.message.should.not.equal("CONNECTION_URL_NO_PROTOCOL"); err.message.should.not.equal("CONNECTION_URL_EMPTY"); @@ -189,24 +185,11 @@ describe("ORM.connect()", function () { it("should return an error if unknown protocol is passed", function (done) { ORM.connect("unknown://db", function (err) { - should.equal(err.literalCode, 'NO_SUPPORT'); - should.equal( - err.message, - "Connection protocol not supported - have you installed the database driver for unknown?" - ); + err.message.should.equal("CONNECTION_PROTOCOL_NOT_SUPPORTED"); return done(); }); }); - - it("should allow pool and debug settings to be false", function(done) { - var connString = common.getConnectionString() + "debug=false&pool=false"; - ORM.connect(connString, function(err, db) { - db.driver.opts.pool.should.equal(false); - db.driver.opts.debug.should.equal(false); - done(); - }); - }); }); }); diff --git a/test/integration/validation.js b/test/integration/validation.js index 8482f0f7..30de4ef2 100644 --- a/test/integration/validation.js +++ b/test/integration/validation.js @@ -344,23 +344,4 @@ describe("Validations", function() { }); }); }); - - describe("mockable", function() { - before(setup()); - - it("validate should be writable", function(done) { - var John = new Person({ - name: "John" - }); - var validateCalled = false; - John.validate = function(cb) { - validateCalled = true; - cb(null); - }; - John.validate(function(err) { - should.equal(validateCalled,true); - return done(); - }); - }); - }); }); diff --git a/test/run.js b/test/run.js index 057880ae..da307f0d 100644 --- a/test/run.js +++ b/test/run.js @@ -1,9 +1,9 @@ var Mocha = require("mocha"); -var glob = require("glob"); +var fs = require("fs"); var path = require("path"); var common = require("./common"); var logging = require("./logging"); -var location = path.normalize(path.join(__dirname, "integration", "**", "*.js")); +var location = path.normalize(path.join(__dirname, "integration")); var mocha = new Mocha({ reporter: "progress" }); @@ -20,9 +20,14 @@ switch (common.hasConfig(common.protocol())) { runTests(); function runTests() { - glob.sync(location).forEach(function (file) { + fs.readdirSync(location).filter(function (file) { + return file.substr(-3) === '.js'; + }).forEach(function (file) { if (!shouldRunTest(file)) return; - mocha.addFile(file); + + mocha.addFile( + path.join(location, file) + ); }); logging.info("Testing **%s**", common.getConnectionString()); @@ -33,11 +38,11 @@ function runTests() { } function shouldRunTest(file) { - var name = path.basename(file).slice(0, -3) + var name = file.substr(0, file.length - 3); var proto = common.protocol(); - var exclude = ['model-aggregate','property-number-size','smart-types']; - if (proto == "mongodb" && exclude.indexOf(name) >= 0) return false; + if (proto == "mongodb" && [ "model-aggregate", + "property-number-size", "smart-types" ].indexOf(name) >= 0) return false; return true; }