diff --git a/LICENSE b/LICENSE index 736f6b3..cce6eba 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2010 Xavier Lacot +Copyright (c) 2010-2011 Xavier Lacot Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.markdown b/README.markdown index 08b14af..f73bd0f 100644 --- a/README.markdown +++ b/README.markdown @@ -9,18 +9,28 @@ joli.js is widely unit-tested. Go check [the demo application](https://github.co "joli" means in French "nice", "tiny". Just what joli.js tries to be. ## Download and install -Just grab joli.js (a single file), and include it in your Titanium project using +The source code of joli.js is [available on GitHub](https://github.com/xavierlacot/joli.js). Just grab it and include it in your Titanium project using either `Titanium.include()` or the CommonJS `require()`statement: Titanium.include('joli.js'); + +or (please note the missing ".js" suffix): -The sources are [available on GitHub](https://github.com/xavierlacot/joli.js). + var joli = require('path/to/joli').connect('your_database_name'); + +The latter integration mode must be prefered, as it helps sandboxing external libraries. Loading joli.js with `require()` will also allow to use several databases in the same application, which is not possible with `Ti.include()`: + + var joliLibrary = require('path/to/joli'); + var database1 = joliLibrary.connect('first_database'); + var database2 = joliLibrary.connect('second_database'); ## Configuration ### Database connection creation -There is one single required step in the configuration of joli.js: configuring the database name. This can be done in only one line, which has to be put before every call to joli.js's API: +If you included joli.js with `Titanium.include()`, there is one single required configuration step: configuring the database name. This can be done in only one line, which has to be put before every call to joli.js's API: joli.connection = new joli.Connection('your_database_name'); + +If you prefered to load joli.js as a CommonJS module, it is not necessary to write this configuration instruction. However, you still may want to change the database name of a connection, and in that case you'll want to use this command. ### Models configuration Prior inserting data and querying your models, you must declare these models. This is done by instantiating the class "joli.model": @@ -252,6 +262,13 @@ There are at the moment three open source applications using joli.js: * [joli.js-demo](https://github.com/xavierlacot/joli.js-demo/) is a demo application, which contains the unit-tests for joli.js; * [joli.api.js-demo](https://github.com/xavierlacot/joli.api.js-demo/) is an Iphone Addressbook-like application, which content gets synchronized with web services. This application was built in a couple of hours and was presented a a demo app at [CodeStrong](http://codestrong.com/) in 2011. +### Use several databases in the same application +It is possible to use several databases with joli.js by loading the library as a CommonJS module: + + var joliLibrary = require('path/to/joli'); + var database1 = joliLibrary.connect('first_database'); + var database2 = joliLibrary.connect('second_database'); + ## Credits and support joli.js has been developed by [Xavier Lacot](http://lacot.org/) and is licensed under the MIT license. @@ -261,6 +278,7 @@ Please use GitHub in order to report bugs, but you may also ask for help on how ## Changelog ### master +* turned joli.js as a commonjs module * added a .as() method for building queries with join() (thanks nicjansma) * fixed a bug in the query where() method, when a value was 0 or '' (thanks nicjansma) * added a toArray() method on record instances diff --git a/joli.js b/joli.js index 6b7c57c..eef5129 100644 --- a/joli.js +++ b/joli.js @@ -1,854 +1,819 @@ var joli = { - each: function(collection, iterator, bind) { - var i, l, property; - - switch (joli.getType(collection)) { - case "array": - for (i = 0, l = collection.length; i < l; i++) { - iterator.call(bind, collection[i], i); - } - break; - case "object": - for (property in collection) { - if (collection.hasOwnProperty(property)) { - iterator.call(bind, collection[property], property); - } - } - break; - } - }, - - extend: function(baseClass, options) { - var opt, prop; - - if (!this.options) { - this.options = {}; - } - - this.parent = new baseClass(options); - - for (prop in this.parent) { - this[prop] = this[prop] || this.parent[prop]; - } - - // copy base options over - for (opt in this.parent.options) { - this.options[opt] = this.options[opt] || this.parent.options[opt]; - } - }, - - getType: function(obj) { - if (typeof obj === "undefined" || obj === null || (typeof obj === "number" && isNaN(obj))) { - return false; - } else if (obj.constructor === Array) { - return "array"; - } else { - return typeof obj; - } - }, - - jsonParse: function(json) { - return JSON.parse(json); - }, - - merge: function() { - var i, l, prop; - - var mergedObject = {}; + each: function(collection, iterator, bind) { + var i, l, property; + + switch (joli.getType(collection)) { + case "array": + for ( i = 0, l = collection.length; i < l; i++) { + iterator.call(bind, collection[i], i); + } + break; + case "object": + for (property in collection) { + if (collection.hasOwnProperty(property)) { + iterator.call(bind, collection[property], property); + } + } + break; + } + }, + extend: function(baseClass, options) { + var opt, prop; - for (i = 0, l = arguments.length; i < l; i++) { - var object = arguments[i]; + if (!this.options) { + this.options = {}; + } - if (joli.getType(object) !== "object") { - continue; - } + this.parent = new baseClass(options); - for (prop in object) { - if (object.hasOwnProperty(prop)) { - var objectProp = object[prop], mergedProp = mergedObject[prop]; + for (prop in this.parent) { + this[prop] = this[prop] || this.parent[prop]; + } - if (mergedProp && joli.getType(objectProp) === "object" && joli.getType(mergedProp) === "object") { - mergedObject[prop] = joli.merge(mergedProp, objectProp); - } else { - mergedObject[prop] = objectProp; - } + // copy base options over + for (opt in this.parent.options) { + this.options[opt] = this.options[opt] || this.parent.options[opt]; } - } - } - return mergedObject; - }, + }, + getType: function(obj) { + if (typeof obj === "undefined" || obj === null || (typeof obj === "number" && isNaN(obj))) { + return false; + } else if (obj.constructor === Array) { + return "array"; + } else { + return typeof obj; + } + }, + jsonParse: function(json) { + return JSON.parse(json); + }, + merge: function() { + var i, l, prop; + var mergedObject = {}; + + for (i = 0, l = arguments.length; i < l; i++) { + var object = arguments[i]; + + if (joli.getType(object) !== "object") { + continue; + } - setOptions: function(options, defaults) { - var opt; + for (prop in object) { + if (object.hasOwnProperty(prop)) { + var objectProp = object[prop], mergedProp = mergedObject[prop]; - if (!options) { - options = {}; - } + if (mergedProp && joli.getType(objectProp) === "object" && joli.getType(mergedProp) === "object") { + mergedObject[prop] = joli.merge(mergedProp, objectProp); + } else { + mergedObject[prop] = objectProp; + } + } + } + } + return mergedObject; + }, + setOptions: function(options, defaults) { + var opt; - if (!this.options) { - this.options = {}; - } + if (!options) { + options = {}; + } - var mergedOptions = joli.merge(defaults, options); + if (!this.options) { + this.options = {}; + } - for (opt in defaults) { - this.options[opt] = mergedOptions[opt]; - } - }, + var mergedOptions = joli.merge(defaults, options); - toQueryString: function(obj) { - var queryStringComponents = []; + for (opt in defaults) { + this.options[opt] = mergedOptions[opt]; + } + }, + toQueryString: function(obj) { + var queryStringComponents = []; - if (!obj) { - obj = {}; - } + if (!obj) { + obj = {}; + } - joli.each(obj, function(val, key) { - var result = null; - - switch (joli.getType(val)) { - case "object": - result = joli.toQueryString(val); - break; - case "array": - result = joli.toQueryString(val); - break; - default: - result = encodeURIComponent(val); - } - - if (result) { - queryStringComponents.push(key + '=' + result); - } - }); + joli.each(obj, function(val, key) { + var result = null; + + switch (joli.getType(val)) { + case "object": + result = joli.toQueryString(val); + break; + case "array": + result = joli.toQueryString(val); + break; + default: + result = encodeURIComponent(val); + } - return '[' + queryStringComponents.join("&") + ']'; - }, - - typeValue: function(val) { - if (!joli.getType(val)) { - return "NULL"; - } else { - if (joli.getType(val) === "string") { - // escape single quotes and dollar signs. - // quotes are escaped for SQLite - val = val.replace(/'/g, "''"); - // dollar signs are escaped for use in calling str.replace in JazzRecord.replaceAndClean() - val = val.replace(/\$/g, "$$$$"); - val = "'" + val + "'"; - } else if (joli.getType(val) === "boolean") { - if (val) { - return "1"; + if (result) { + queryStringComponents.push(key + '=' + result); + } + }); + return '[' + queryStringComponents.join("&") + ']'; + }, + typeValue: function(val) { + if (!joli.getType(val)) { + return "NULL"; } else { - return "0"; + if (joli.getType(val) === "string") { + // escape single quotes and dollar signs. + // quotes are escaped for SQLite + val = val.replace(/'/g, "''"); + // dollar signs are escaped for use in calling str.replace in + // JazzRecord.replaceAndClean() + val = val.replace(/\$/g, "$$$$"); + val = "'" + val + "'"; + } else if (joli.getType(val) === "boolean") { + if (val) { + return "1"; + } else { + return "0"; + } + } } - } - } - return val; - } + return val; + } }; - /** * Connection */ joli.Connection = function(database) { - this.dbname = database; - this.database = Titanium.Database.open(this.dbname); - this.database.execute('PRAGMA read_uncommitted=true'); + this.dbname = database; + this.database = Titanium.Database.open(this.dbname); + this.database.execute('PRAGMA read_uncommitted=true'); }; joli.Connection.prototype = { - execute: function(query) { -// Titanium.API.log('debug', query); - return this.database.execute(query); - }, - - lastInsertRowId: function() { - return this.database.lastInsertRowId; - } + execute: function(query) { + // Titanium.API.log('debug', query); + return this.database.execute(query); + }, + lastInsertRowId: function() { + return this.database.lastInsertRowId; + } }; - /** * Migration description */ joli.migration = function(options) { - var defaults = { - tableName: 'migration' - }; + var defaults = { + tableName: 'migration' + }; - joli.setOptions.call(this, options, defaults); - this.table = this.options.tableName; + joli.setOptions.call(this, options, defaults); + this.table = this.options.tableName; }; joli.migration.prototype = { - getVersion: function() { - var q = new joli.query().select().from(this.table).order('version desc'); - var version = q.execute(); - - if (version.length > 0) { - return version[0].version; - } else { - q = new joli.query().insertInto(this.table).values({ version: 0 }); - q.execute(); - return 0; - } - }, + getVersion: function() { + var q = new joli.query().select().from(this.table).order('version desc'); + var version = q.execute(); - setVersion: function(version) { - var q = new joli.query().update(this.table).set({ version: version }); - q.execute(); - } + if (version.length > 0) { + return version[0].version; + } else { + q = new joli.query().insertInto(this.table).values({ + version: 0 + }); + q.execute(); + return 0; + } + }, + setVersion: function(version) { + var q = new joli.query().update(this.table).set({ + version: version + }); + q.execute(); + } }; /** * Model description */ joli.model = function(options) { - var defaults = { - table: '', - columns: {}, - objectMethods: {} - }; - - if (options.methods) { - joli.each(options.methods, function(method, name) { - this[name] = method; - }, this); - } - - joli.setOptions.call(this, options, defaults); - this.table = this.options.table; - - if (!joli.models.has(this.table)) { - joli.models.set(this.table, this); - } -}; - -joli.model.prototype = { - all: function(constraints) { - var q = new joli.query().select().from(this.table); - - if (!constraints) { - constraints = {}; - } - - if (constraints.where) { - joli.each(constraints.where, function(value, field) { - q.where(field, value); - }); - } - - if (constraints.order) { - q.order(constraints.order); - } - - if (constraints.limit) { - q.limit(constraints.limit); - } - - return q.execute(); - }, - - count: function(constraints) { - var q = new joli.query().count().from(this.table); - - if (!constraints) { - constraints = {}; - } + var defaults = { + table: '', + columns: {}, + objectMethods: {} + }; - if (constraints.where) { - joli.each(constraints.where, function(value, field) { - q.where(field, value); - }); + if (options.methods) { + joli.each(options.methods, function(method, name) { + this[name] = method; + }, this); } - return parseInt(q.execute(), 10); - }, + joli.setOptions.call(this, options, defaults); + this.table = this.options.table; - // no callbacks, more efficient - deleteRecords: function(id) { - var q = new joli.query().destroy().from(this.table); - - if (joli.getType(id) === 'number') { - q.where('id = ?', id); - } else if(joli.getType(id) === 'array') { - q.whereIn('id', id); + if (!joli.models.has(this.table)) { + joli.models.set(this.table, this); } +}; - return q.execute(); - }, - - exists: function(id) { - var count = new joli.query().count().from(this.table).where('id = ?', id).execute(); - return (count > 0); - }, +joli.model.prototype = { + all: function(constraints) { + var q = new joli.query().select().from(this.table); - findBy: function(field, value) { - return new joli.query().select().from(this.table).where(field + ' = ?', value).execute(); - }, + if (!constraints) { + constraints = {}; + } - findById: function(value) { - return this.findBy('id', value); - }, + if (constraints.where) { + joli.each(constraints.where, function(value, field) { + q.where(field, value); + }); + } - findOneBy: function(field, value) { - var result = new joli.query().select().from(this.table).where(field + ' = ?', value).limit(1).execute(); + if (constraints.order) { + q.order(constraints.order); + } - if (result.length === 0) { - return false; - } else { - return result[0]; - } - }, + if (constraints.limit) { + q.limit(constraints.limit); + } - findOneById: function(value) { - return this.findOneBy('id', value); - }, + return q.execute(); + }, + count: function(constraints) { + var q = new joli.query().count().from(this.table); - getColumns: function() { - return this.options.columns; - }, + if (!constraints) { + constraints = {}; + } - newRecord: function(values) { - if (!values) { - values = {}; - } + if (constraints.where) { + joli.each(constraints.where, function(value, field) { + q.where(field, value); + }); + } - var data = {}; + return parseInt(q.execute(), 10); + }, + // no callbacks, more efficient + deleteRecords: function(id) { + var q = new joli.query().destroy().from(this.table); - joli.each(this.options.columns, function(colType, colName) { - data[colName] = (values[colName] === undefined) ? null : values[colName]; - }); + if (joli.getType(id) === 'number') { + q.where('id = ?', id); + } else if (joli.getType(id) === 'array') { + q.whereIn('id', id); + } - var record = new joli.record(this).fromArray(data); + return q.execute(); + }, + exists: function(id) { + var count = new joli.query().count().from(this.table).where('id = ?', id).execute(); + return (count > 0); + }, + findBy: function(field, value) { + return new joli.query().select().from(this.table).where(field + ' = ?', value).execute(); + }, + findById: function(value) { + return this.findBy('id', value); + }, + findOneBy: function(field, value) { + var result = new joli.query().select().from(this.table).where(field + ' = ?', value).limit(1).execute(); + + if (result.length === 0) { + return false; + } else { + return result[0]; + } + }, + findOneById: function(value) { + return this.findOneBy('id', value); + }, + getColumns: function() { + return this.options.columns; + }, + newRecord: function(values) { + if (!values) { + values = {}; + } - record.isNew = function() { - return true; - }; + var data = {}; - // add object methods - if (this.options.objectMethods) { - joli.each(this.options.objectMethods, function(method, name) { - record[name] = method; - }); - } + joli.each(this.options.columns, function(colType, colName) { + data[colName] = (values[colName] === undefined) ? null : values[colName]; + }); + var record = new joli.record(this).fromArray(data); + + record.isNew = function() { + return true; + }; + // add object methods + if (this.options.objectMethods) { + joli.each(this.options.objectMethods, function(method, name) { + record[name] = method; + }); + } - return record; - }, + return record; + }, + save: function(data) { + if (data.data.length === 0) { + return; + } - save: function(data) { - if (data.data.length === 0) { - return; - } + var q = new joli.query(); - var q = new joli.query(); + if (data.originalData) { + // existing record + q.update(this.table).set(data.data).where('id = ?', data.originalData.id); + } else { + // new record + q.insertInto(this.table).values(data.data); + } - if (data.originalData) { - // existing record - q.update(this.table).set(data.data).where('id = ?', data.originalData.id); - } else { - // new record - q.insertInto(this.table).values(data.data); + return q.execute(); + }, + truncate: function() { + new joli.query().destroy().from(this.table).execute(); } - - return q.execute(); - }, - - truncate: function() { - new joli.query().destroy().from(this.table).execute(); - } }; joli.Models = function() { - this.models = {}; - this.migration = new joli.migration({ tableName: 'migration' }); + this.models = {}; + this.migration = new joli.migration({ + tableName: 'migration' + }); }; joli.Models.prototype = { - get: function(table) { - return this.models[table]; - }, - - has: function(table) { - if (this.models[table]) { - return true; - } else { - return false; - } - }, - - initialize: function() { - joli.each(this.models, function(model, modelName) { - var columns = []; - - joli.each(model.options.columns, function(type, name) { - columns.push(name + ' ' + type); - }); - var query = 'CREATE TABLE IF NOT EXISTS ' + modelName + ' (' + columns.join(', ') + ')'; - joli.connection.execute(query); - }); - }, - - migrate: function(version, migrationCallback) { - // create migration table - var query = 'CREATE TABLE IF NOT EXISTS ' + this.migration.table + ' (version)'; - joli.connection.execute(query); - - if (this.migration.getVersion() < version) { - joli.each(this.models, function(model, modelName) { - var query = 'DROP TABLE IF EXISTS ' + modelName; + get: function(table) { + return this.models[table]; + }, + has: function(table) { + if (this.models[table]) { + return true; + } else { + return false; + } + }, + initialize: function() { + joli.each(this.models, function(model, modelName) { + var columns = []; + + joli.each(model.options.columns, function(type, name) { + columns.push(name + ' ' + type); + }); + var query = 'CREATE TABLE IF NOT EXISTS ' + modelName + ' (' + columns.join(', ') + ')'; + joli.connection.execute(query); + }); + }, + migrate: function(version, migrationCallback) { + // create migration table + var query = 'CREATE TABLE IF NOT EXISTS ' + this.migration.table + ' (version)'; joli.connection.execute(query); - }); - // optional migration callback - if (migrationCallback && 'function' == joli.getType(migrationCallback)) { - migrationCallback({ - table: this.migration.table, - newVersion: version - }); - } + if (this.migration.getVersion() < version) { + joli.each(this.models, function(model, modelName) { + var query = 'DROP TABLE IF EXISTS ' + modelName; + joli.connection.execute(query); + }); + // optional migration callback + if (migrationCallback && 'function' === joli.getType(migrationCallback)) { + migrationCallback({ + table: this.migration.table, + newVersion: version + }); + } - // insert migration - this.migration.setVersion(version); + // insert migration + this.migration.setVersion(version); + } + }, + set: function(table, model) { + this.models[table] = model; } - }, - - set: function(table, model) { - this.models[table] = model; - } }; joli.models = new joli.Models(); joli.query = function() { - this.data = { - from: null, - join: [], - limit: null, - operation: null, - order: [], - select_columns: '*', - set: [], - values: [], - where: null, - as: null - }; + this.data = { + as: null, + from: null, + join: [], + limit: null, + operation: null, + order: [], + select_columns: '*', + set: [], + values: [], + where: null + }; }; joli.query.prototype = { - count: function() { - this.data.operation = 'count'; - return this; - }, - - destroy: function() { - this.data.operation = 'delete'; - return this; - }, - - execute: function(hydratationMode) { - return this.executeQuery(this.getQuery(), hydratationMode); - }, - - executeQuery: function(query, hydratationMode) { - var rows; - - switch (this.data.operation) { - case 'count': - rows = joli.connection.execute(query); - return this.getCount(rows); - case 'insert_into': - joli.connection.execute(query); - return joli.connection.lastInsertRowId(); - case 'select': - if (typeof hydratationMode === 'undefined') { - hydratationMode = 'object'; + count: function() { + this.data.operation = 'count'; + return this; + }, + destroy: function() { + this.data.operation = 'delete'; + return this; + }, + execute: function(hydratationMode) { + return this.executeQuery(this.getQuery(), hydratationMode); + }, + executeQuery: function(query, hydratationMode) { + var rows; + + switch (this.data.operation) { + case 'count': + rows = joli.connection.execute(query); + return this.getCount(rows); + case 'insert_into': + joli.connection.execute(query); + return joli.connection.lastInsertRowId(); + case 'select': + if (typeof hydratationMode === 'undefined') { + hydratationMode = 'object'; + } + rows = joli.connection.execute(query); + return this.hydrate(rows, hydratationMode); + default: + return joli.connection.execute(query); } + }, + from: function(table) { + this.data.from = table; + return this; + }, + as: function(table) { + this.data.as = table; + return this; + }, + getCount: function(rows) { + var result; - rows = joli.connection.execute(query); - return this.hydrate(rows, hydratationMode); - default: - return joli.connection.execute(query); - } - }, - - from: function(table) { - this.data.from = table; - return this; - }, - - as: function(table) { - this.data.as = table; - return this; - }, - - getCount: function(rows) { - var result; - - if (null === rows) { - return 0; - } - - if (0 === rows.rowCount) { - result = 0; - } else { - result = rows.fieldByName('total'); - } - - rows.close(); - return result; - }, - - getOperation: function() { - switch (this.data.operation) { - case 'count': - return 'select count(*) as total from ' + this.data.from; - case 'delete': - return 'delete from ' + this.data.from; - case 'insert_into': - return 'insert into ' + this.data.from + ' (' + this.data.set.join(', ') + ') values (' + this.data.values.join(', ') + ')'; - case 'select': - var join = ''; - - if (this.data.join.length > 0) { - joli.each(this.data.join, function(value, key) { - if (-1 === value[1].indexOf('.')) { - value[1] = value[0] + '.' + value[1]; - } - - join = join + ' left outer join ' + value[0] + ' on ' + value[1] + ' = ' + value[2]; - }); + if (null === rows) { + return 0; } - return 'select ' + this.data.select_columns + ' from ' + this.data.from + join; - case 'update': - return 'update ' + this.data.from + ' set ' + this.data.set.join(', '); - default: - throw("Operation type Error. joli.query operation type must be an insert, a delete, a select or an update."); - } - }, - - getQuery: function() { - var query = this.getOperation(); - - if (this.data.where) { - query += ' where ' + this.data.where; - } - - if (this.data.groupBy) { - query += ' group by ' + this.data.groupBy.join(', '); - } - - if (this.data.order.length > 0) { - query += ' order by ' + this.data.order.join(', '); - } - - if (this.data.limit) { - query += ' limit ' + this.data.limit; - } - - return query; - }, - - groupBy: function(group) { - if ('string' == joli.getType(group)) { - group = [group]; - } - - this.data.groupBy = group; - return this; - }, - - hydrate: function(rows, hydratationMode) { - var result = []; - - if (null === hydratationMode) { - hydratationMode = 'object'; - } - - if (!rows) { - return result; - } + if (0 === rows.rowCount) { + result = 0; + } else { + result = rows.fieldByName('total'); + } - var fieldCount; + rows.close(); + return result; + }, + getOperation: function() { + switch (this.data.operation) { + case 'count': + return 'select count(*) as total from ' + this.data.from; + case 'delete': + return 'delete from ' + this.data.from; + case 'insert_into': + return 'insert into ' + this.data.from + ' (' + this.data.set.join(', ') + ') values (' + this.data.values.join(', ') + ')'; + case 'select': + var join = ''; + + if (this.data.join.length > 0) { + joli.each(this.data.join, function(value, key) { + if (-1 === value[1].indexOf('.')) { + value[1] = value[0] + '.' + value[1]; + } + join = join + ' left outer join ' + value[0] + ' on ' + value[1] + ' = ' + value[2]; + }); + } + + return 'select ' + this.data.select_columns + ' from ' + this.data.from + join; + case 'update': + return 'update ' + this.data.from + ' set ' + this.data.set.join(', '); + default: + throw ("Operation type Error. joli.query operation type must be an insert, a delete, a select or an update."); + } + }, + getQuery: function() { + var query = this.getOperation(); - if (Titanium.Platform.name != 'android') { - fieldCount = rows.fieldCount(); - } else { - fieldCount = rows.fieldCount; - } + if (this.data.where) { + query += ' where ' + this.data.where; + } - switch (hydratationMode) { - case 'array': - result = this.hydrateArray(rows, fieldCount); - break; - case 'object': - result = this.hydrateObject(rows, fieldCount); - break; - default: - throw('Unknown hydratation mode "' + hydratationMode + '". hydratationMode must be "object" or "array"'); - } + if (this.data.groupBy) { + query += ' group by ' + this.data.groupBy.join(', '); + } - rows.close(); - return result; - }, + if (this.data.order.length > 0) { + query += ' order by ' + this.data.order.join(', '); + } - hydrateArray: function(rows, fieldCount) { - var result = []; - var i; - var rowData; + if (this.data.limit) { + query += ' limit ' + this.data.limit; + } - while (rows.isValidRow()) { - i = 0; - rowData = {}; + return query; + }, + groupBy: function(group) { + if ('string' === joli.getType(group)) { + group = [group]; + } - while (i < fieldCount) { - rowData[rows.fieldName(i)] = rows.field(i); - i++; - } + this.data.groupBy = group; + return this; + }, + hydrate: function(rows, hydratationMode) { + var result = []; - result.push(rowData); - rows.next(); - } + if (null === hydratationMode) { + hydratationMode = 'object'; + } - return result; - }, + if (!rows) { + return result; + } - hydrateObject: function(rows, fieldCount) { - var result = []; - var i; - var record; - var rowData; + var fieldCount; - // use the model specified by as() first, then from() - var model = joli.models.get(this.data.as || this.data.from); + if (Titanium.Platform.name !== 'android') { + fieldCount = rows.fieldCount(); + } else { + fieldCount = rows.fieldCount; + } - while (rows.isValidRow()) { - i = 0; - rowData = {}; + switch (hydratationMode) { + case 'array': + result = this.hydrateArray(rows, fieldCount); + break; + case 'object': + result = this.hydrateObject(rows, fieldCount); + break; + default: + throw ('Unknown hydratation mode "' + hydratationMode + '". hydratationMode must be "object" or "array"'); + } - while (i < fieldCount) { - rowData[rows.fieldName(i)] = rows.field(i); - i++; - } + rows.close(); + return result; + }, + hydrateArray: function(rows, fieldCount) { + var result = []; + var i; + var rowData; + + while (rows.isValidRow()) { + i = 0; + rowData = {}; + + while (i < fieldCount) { + rowData[rows.fieldName(i)] = rows.field(i); + i++; + } - result.push(model.newRecord().fromArray(rowData)); - rows.next(); - } + result.push(rowData); + rows.next(); + } - return result; - }, + return result; + }, + hydrateObject: function(rows, fieldCount) { + var result = []; + var i; + var record; + var rowData; - insertInto: function(table) { - this.data.operation = 'insert_into'; - this.data.from = table; - return this; - }, + // use the model specified by as() first, then from() + var model = joli.models.get(this.data.as || this.data.from); - join: function(table, local_id, foreign_id) { - this.data.join.push([table, local_id, foreign_id]); - return this; - }, + while (rows.isValidRow()) { + i = 0; + rowData = {}; - limit: function(limit) { - this.data.limit = limit; - return this; - }, + while (i < fieldCount) { + rowData[rows.fieldName(i)] = rows.field(i); + i++; + } - order: function(order) { - if ('string' == joli.getType(order)) { - order = [order]; - } + result.push(model.newRecord().fromArray(rowData)); + rows.next(); + } - this.data.order = order; - return this; - }, + return result; + }, + insertInto: function(table) { + this.data.operation = 'insert_into'; + this.data.from = table; + return this; + }, + join: function(table, local_id, foreign_id) { + this.data.join.push([table, local_id, foreign_id]); + return this; + }, + limit: function(limit) { + this.data.limit = limit; + return this; + }, + order: function(order) { + if ('string' === joli.getType(order)) { + order = [order]; + } - select: function(columns) { - this.data.operation = 'select'; + this.data.order = order; + return this; + }, + select: function(columns) { + this.data.operation = 'select'; - if (columns) { - this.data.select_columns = columns; - } + if (columns) { + this.data.select_columns = columns; + } - return this; - }, - - set: function(values) { - joli.each(values, function(expression, value) { - if (-1 === value.indexOf('=')) { - this.data.set.push(value + ' = ' + joli.typeValue(expression)); - } else { - // some particular expression containing "=" - this.data.set.push(value); - } - }, this); - return this; - }, - - update: function(table) { - this.data.operation = 'update'; - this.data.from = table; - return this; - }, - - values: function(values) { - joli.each(values, function(expression, value) { - this.data.set.push(value); - this.data.values.push(joli.typeValue(expression)); - }, this); - return this; - }, - - where: function(expression, value) { - if (null !== this.data.where) { - this.data.where += ' and '; - } else { - this.data.where = ''; - } + return this; + }, + set: function(values) { + joli.each(values, function(expression, value) { + if (-1 === value.indexOf('=')) { + this.data.set.push(value + ' = ' + joli.typeValue(expression)); + } else { + // some particular expression containing "=" + this.data.set.push(value); + } + }, this); + return this; + }, + update: function(table) { + this.data.operation = 'update'; + this.data.from = table; + return this; + }, + values: function(values) { + joli.each(values, function(expression, value) { + this.data.set.push(value); + this.data.values.push(joli.typeValue(expression)); + }, this); + return this; + }, + where: function(expression, value) { + if (null !== this.data.where) { + this.data.where += ' and '; + } else { + this.data.where = ''; + } - // handle replacing multiple values - if ('array' == joli.getType(value)) { - var i = 0; + // handle replacing multiple values + if ('array' === joli.getType(value)) { + var i = 0; - // replace question marks one at a time from the array - while (expression.indexOf('?') != -1 && value[i] !== undefined) { - expression = expression.replace(/\?/i, '"' + value[i] + '"'); - i++; - } + // replace question marks one at a time from the array + while (expression.indexOf('?') !== -1 && value[i] !== undefined) { + expression = expression.replace(/\?/i, '"' + value[i] + '"'); + i++; + } - this.data.where += expression; - } else { - this.data.where += expression.replace(/\?/gi, '"' + value + '"'); - } + this.data.where += expression; + } else { + this.data.where += expression.replace(/\?/gi, '"' + value + '"'); + } - return this; - }, + return this; + }, + whereIn: function(expression, value) { + if (null !== this.data.where) { + this.data.where += ' and '; + } else { + this.data.where = ''; + } - whereIn: function(expression, value) { - if (null !== this.data.where) { - this.data.where += ' and '; - } else { - this.data.where = ''; - } + if ('array' === joli.getType(value)) { + if (0 === value.length) { + return this; + } + value = '(\'' + value.join('\', \'') + '\')'; + } - if ('array' == joli.getType(value)) { - if (0 === value.length) { + this.data.where += expression + ' in ' + value; return this; - } - - value = '(\'' + value.join('\', \'') + '\')'; } - - this.data.where += expression + ' in ' + value; - return this; - } }; joli.record = function(table) { - this._options = { - table: table, - columns: table.getColumns() - }; - this._data = {}; - this._metadata = {}; + this._options = { + table: table, + columns: table.getColumns() + }; + this._data = {}; + this._metadata = {}; }; joli.record.prototype = { - destroy: function() { - if (!this.id) { - throw("Unsaved record cannot be destroyed"); - } else { - this._options.table.deleteRecords(this.id); - } - }, - - fromArray: function(data) { - if (data.id) { - this._originalData = { id: data.id }; - this.isNew = function() { - return false; - }; - } else { - this._originalData = null; - this.isNew = function() { - return true; - }; - } - - joli.each(this._options.columns, function(colType, colName) { - this[colName] = null; - this[colName] = data[colName]; - this._data[colName] = null; - this._data[colName] = data[colName]; + destroy: function() { + if (!this.id) { + throw ("Unsaved record cannot be destroyed"); + } else { + this._options.table.deleteRecords(this.id); + } + }, + fromArray: function(data) { + if (data.id) { + this._originalData = { + id: data.id + }; + this.isNew = function() { + return false; + }; + } else { + this._originalData = null; + this.isNew = function() { + return true; + }; + } - if (this._originalData && this.isNew()) { - this._originalData[colName] = data[colName]; - } - }, this); + joli.each(this._options.columns, function(colType, colName) { + this[colName] = null; + this[colName] = data[colName]; + this._data[colName] = null; + this._data[colName] = data[colName]; - return this; - }, + if (this._originalData && this.isNew()) { + this._originalData[colName] = data[colName]; + } + }, this); + return this; + }, + get: function(key) { + return this[key]; + }, + isChanged: function() { + if (this.isNew()) { + return false; + } - get: function(key) { - return this[key]; - }, + return !(this.id && joli.toQueryString(this._data) === joli.toQueryString(this._originalData)); + }, + save: function() { + var data = { + data: this._data, + metadata: this._metadata + }; + + if (this.isChanged()) { + data.originalData = this._originalData; + this._options.table.save(data); + } else if (this.isNew()) { + this._data.id = this._options.table.save(data); + } - isChanged: function() { - if (this.isNew()) { - return false; - } + // overwrite original data so it is no longer "dirty" OR so it is no + // longer new + this._originalData = {}; + var newData = {}; - return !(this.id && joli.toQueryString(this._data) === joli.toQueryString(this._originalData)); - }, + joli.each(this._options.columns, function(colType, colName) { + this._originalData[colName] = this._data[colName]; + newData[colName] = this._data[colName]; + this[colName] = this._data[colName]; + }, this); - save: function() { - var data = { - data: this._data, - metadata: this._metadata - }; + this._data = newData; - if (this.isChanged()) { - data.originalData = this._originalData; - this._options.table.save(data); - } else if(this.isNew()) { - this._data['id'] = this._options.table.save(data); + this.isNew = function() { + return false; + }; + return true; + }, + set: function(key, value) { + this[key] = value; + this._data[key] = value; + }, + toArray: function(data) { + var result = []; + + joli.each(this._options.columns, function(colType, colName) { + result[colName] = this._data[colName]; + }, this); + return result; } - - // overwrite original data so it is no longer "dirty" OR so it is no longer new - this._originalData = {}; - var newData = {}; - - joli.each(this._options.columns, function(colType, colName) { - this._originalData[colName] = this._data[colName]; - newData[colName] = this._data[colName]; - this[colName] = this._data[colName]; - }, this); - - this._data = newData; - - this.isNew = function() { - return false; - }; - - return true; - }, - - set: function(key, value) { - this[key] = value; - this._data[key] = value; - }, - - toArray: function(data) { - var result = []; - - joli.each(this._options.columns, function(colType, colName) { - result[colName] = this._data[colName]; - }, this); - - return result; - } }; + +/** + * In case joli.js is loaded as a CommonJS module + */ +if (typeof exports === 'object' && exports) { + exports = function() { + var api = { + connect: function(database) { + + if (database) { + joli.connection = new joli.Connection(database); + } + + return joli; + } + }; + + return api; + }(); +} \ No newline at end of file