From 497b164b27756c74e5a74ef670c5cc03cae5fe26 Mon Sep 17 00:00:00 2001 From: syntacticx Date: Fri, 13 Feb 2009 14:50:34 -0800 Subject: [PATCH] added WHERE array support --- src/active_record/adapters.js | 19 +++++++++++++ src/active_record/adapters/in_memory.js | 23 +++++++++++----- src/active_record/adapters/sql.js | 10 ++++++- src/active_record/base.js | 6 ++++- test/active_record/date.js | 1 - test/active_record/finders.js | 5 ++++ test/active_view/binding.js | 36 +++++++++++++++++++++++++ test/test.js | 27 ++++++++++--------- 8 files changed, 105 insertions(+), 22 deletions(-) create mode 100644 test/active_view/binding.js diff --git a/src/active_record/adapters.js b/src/active_record/adapters.js index b6fe14e..a5554a0 100644 --- a/src/active_record/adapters.js +++ b/src/active_record/adapters.js @@ -94,6 +94,25 @@ ActiveRecord.execute = function execute() return ActiveRecord.connection.executeSQL.apply(ActiveRecord.connection, arguments); }; +/** + * Escapes a given argument for use in a SQL string. By default + * the argument passed will also be enclosed in quotes. + * @alias ActiveRecord.escape + * @param {mixed} argument + * @param {Boolean} [supress_quotes] Defaults to false. + * @return {mixed} + * ActiveRecord.escape(5) == 5 + * ActiveRecord.escape('tes"t') == '"tes\"t"'; + */ +ActiveRecord.escape = function escape(argument,supress_quotes) +{ + var quote = supress_quotes ? '' : '"'; + return typeof(argument) == 'number' + ? argument + : quote + (new String(argument)).toString().replace(/\"/g,'\\"').replace(/\\/g,'\\\\').replace(/\0/g,'\\0') + quote + ; +}; + Adapters.InstanceMethods = { setValueFromFieldIfValueIsNull: function setValueFromFieldIfValueIsNull(field,value) { diff --git a/src/active_record/adapters/in_memory.js b/src/active_record/adapters/in_memory.js index 23cc8bc..734a936 100644 --- a/src/active_record/adapters/in_memory.js +++ b/src/active_record/adapters/in_memory.js @@ -187,10 +187,7 @@ ActiveSupport.extend(Adapters.InMemory.prototype,{ var sql_args = ActiveSupport.arrayFrom(arguments).slice(1); for(var i = 0; i < sql_args.length; ++i) { - sql = sql.replace(/\?/,typeof(sql_args[i]) == 'number' - ? sql_args[i] - : '"' + (new String(sql_args[i])).toString().replace(/\"/g,'\\"').replace(/\\/g,'\\\\').replace(/\0/g,'\\0') + '"' - ); + sql = sql.replace(/\?/,ActiveRecord.escape(sql_args[i])); } var response = this.paramsFromSQLString(sql); table = response[0]; @@ -318,8 +315,18 @@ ActiveSupport.extend(Adapters.InMemory.prototype,{ } }, createWhere: function createWhere(where) - { - if(typeof(where) === 'string'){ + { + if(ActiveSupport.isArray(where)) + { + var where_fragment = where[0]; + for(var i = 1; i < where.length; ++i) + { + where_fragment = where_fragment.replace(/\?/,ActiveRecord.escape(where[i])); + } + where = where_fragment; + } + if(typeof(where) === 'string') + { return function json_result_where_processor(result_set) { var response = []; @@ -334,7 +341,9 @@ ActiveSupport.extend(Adapters.InMemory.prototype,{ } return response; }; - }else{ + } + else + { return function json_result_where_processor(result_set) { var response = []; diff --git a/src/active_record/adapters/sql.js b/src/active_record/adapters/sql.js index cce1566..3d9ba40 100644 --- a/src/active_record/adapters/sql.js +++ b/src/active_record/adapters/sql.js @@ -170,7 +170,15 @@ Adapters.SQL = { buildWhereSQLFragment: function buildWhereSQLFragment(fragment, args) { var where, keys, i; - if(fragment && typeof(fragment) !== "string") + if(fragment && ActiveSupport.isArray(fragment)) + { + for(i = 1; i < fragment.length; ++i) + { + args.push(fragment[i]); + } + return ' WHERE ' + fragment[0]; + } + else if(fragment && typeof(fragment) !== "string") { where = ''; keys = ActiveSupport.keys(fragment); diff --git a/src/active_record/base.js b/src/active_record/base.js index 19fdb1a..a9553e3 100644 --- a/src/active_record/base.js +++ b/src/active_record/base.js @@ -267,7 +267,7 @@ ActiveSupport.extend(ActiveRecord.ClassMethods,{ * @param {mixed} params * Can be an integer to try and find a record by id, a complete SQL statement String, or Object of params, params may contain: * select: Array of columns to select (default ['*']) - * where: String or Object + * where: String or Object or Array * joins: String * order: String * limit: Number @@ -284,6 +284,10 @@ ActiveSupport.extend(ActiveRecord.ClassMethods,{ * id: 5 * } * }); + * var user = User.find({ + * first: true, + * where: ['id = ?',5] + * }); * var users = User.find(); //finds all * var users = User.find({ * where: 'name = "alice" AND password = "' + md5('pass') + '"', diff --git a/test/active_record/date.js b/test/active_record/date.js index 0cb689e..b1f8840 100644 --- a/test/active_record/date.js +++ b/test/active_record/date.js @@ -63,7 +63,6 @@ ActiveTest.Tests.ActiveRecord.date = function(proceed) reload_test.reload(); reload_test.save(); reload_test.reload(); - //ActiveSupport.dateFormat(reload_test.get('updated'),'yyyy-mm-dd HH:MM:ss'); assert(reload_test.get('created').toString() == old_created.toString(),'created time is preserved on update'); if(proceed) diff --git a/test/active_record/finders.js b/test/active_record/finders.js index 399443c..7393c23 100644 --- a/test/active_record/finders.js +++ b/test/active_record/finders.js @@ -66,6 +66,11 @@ ActiveTest.Tests.ActiveRecord.finders = function(proceed) }).title == 'b','find({first: true, where: string})'); b = Comment.find('SELECT * FROM comments WHERE title = ? LIMIT 1','b'); assert(b[0] && b[0].title == 'b','find(SQL string with WHERE, LIMIT and param substituion)'); + b = Comment.find({ + where: ['title = ?','b'], + limit: 1 + }); + assert(b[0] && b[0].title == 'b','find(SQL string with WHERE, LIMIT and param substituion via find)'); assert(Comment.find().length == 3 && Comment.find({all: true}).length == 3,'find({all: true})'); diff --git a/test/active_view/binding.js b/test/active_view/binding.js new file mode 100644 index 0000000..3d7ddfe --- /dev/null +++ b/test/active_view/binding.js @@ -0,0 +1,36 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (c) 2009 Aptana, Inc. + * + * 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: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * 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. + * + * ***** END LICENSE BLOCK ***** */ + +ActiveTest.Tests.View.binding = function(proceed) +{ + with(ActiveTest) + { + + if(proceed) + proceed() + } +}; \ No newline at end of file diff --git a/test/test.js b/test/test.js index e994247..ab63a4a 100644 --- a/test/test.js +++ b/test/test.js @@ -497,22 +497,21 @@ ActiveTest.Tests.ActiveRecord.date = function(proceed) var a = ModelWithDates.create({ name: 'a' }); - assert(a.get('created').match(/^\d{4}/) && a.get('updated').match(/^\d{4}/),'created and updated set via date field'); + assert(ActiveSupport.dateFormat(a.get('created'),'yyyy-mm-dd HH:MM:ss').match(/^\d{4}/) && ActiveSupport.dateFormat(a.get('updated'),'yyyy-mm-dd HH:MM:ss').match(/^\d{4}/),'created and updated set via date field'); var old_date = a.get('updated'); a.set('updated',''); a.save(); var new_date = a.get('updated'); var saved_date = ModelWithDates.find(a.id).get('updated'); - if(saved_date instanceof Date){ - saved_date = ActiveSupport.dateFormat(saved_date,'yyyy-mm-dd HH:MM:ss',true); - } - assert(saved_date == new_date,'created and updated persist via date field'); + assert(saved_date.toString() == new_date.toString(),'created and updated persist via date field'); //make sure dates are preserved var reload_test = ModelWithDates.find(a.id); var old_created = reload_test.get('created'); reload_test.save(); reload_test.reload(); + reload_test.save(); + reload_test.reload(); assert(reload_test.get('created').toString() == old_created.toString(),'created time is preserved on update'); if(proceed) @@ -562,6 +561,11 @@ ActiveTest.Tests.ActiveRecord.finders = function(proceed) }).title == 'b','find({first: true, where: string})'); b = Comment.find('SELECT * FROM comments WHERE title = ? LIMIT 1','b'); assert(b[0] && b[0].title == 'b','find(SQL string with WHERE, LIMIT and param substituion)'); + b = Comment.find({ + where: ['title = ?','b'], + limit: 1 + }); + assert(b[0] && b[0].title == 'b','find(SQL string with WHERE, LIMIT and param substituion via find)'); assert(Comment.find().length == 3 && Comment.find({all: true}).length == 3,'find({all: true})'); @@ -1414,7 +1418,7 @@ ActiveTest.Tests.Routes.matching = function(proceed) var routes_without_params = new ActiveRoutes([ ['index','/home',{object: 'page',method: 'index'}], ['contact','pages/contact',{object: 'page', method: 'index'}], - ['/pages/about/',{object: 'page',method: 'about'}], + ['/pages/about/',{object: 'page',method: 'about'}] ],test_scope); assert(routes_without_params.match('/home').name == 'index','match() /home'); @@ -1434,7 +1438,7 @@ ActiveTest.Tests.Routes.matching = function(proceed) //test index handling var routes_without_params = new ActiveRoutes([ ['index','pages',{object: 'page',method: 'index'}], - ['contact','pages/contact',{object: 'page', method: 'index'}], + ['contact','pages/contact',{object: 'page', method: 'index'}] ],test_scope); assert(routes_without_params.match('pages').name == 'index','index match() pages'); @@ -1444,7 +1448,7 @@ ActiveTest.Tests.Routes.matching = function(proceed) var routes_without_params = new ActiveRoutes([ ['index','pages/index',{object: 'page',method: 'index'}], - ['contact','pages/contact',{object: 'page', method: 'index'}], + ['contact','pages/contact',{object: 'page', method: 'index'}] ],test_scope); assert(routes_without_params.match('pages').name == 'index','index match() pages'); @@ -1456,7 +1460,6 @@ ActiveTest.Tests.Routes.matching = function(proceed) var routes = new ActiveRoutes(test_valid_route_set,test_scope); var match; - match = routes.match('/blog/post/5'); assert(match.name == 'post' && match.params.id == 5 && match.params.method == 'post','complex match() /blog/post/5'); @@ -1656,8 +1659,8 @@ ActiveTest.Tests.View.builder = function(proceed) tr( td( ul( - li(), - li(span(b('test'))) + li(span(b('test'))), + li() ) ), td( @@ -1679,8 +1682,8 @@ ActiveTest.Tests.View.builder = function(proceed) }); var deep_instance = new DeepView(); var arguments_instance = new ArgumentsTestView(); - assert(arguments_instance.container.firstChild.firstChild.nodeValue == 'one' && arguments_instance.container.firstChild.childNodes[2].tagName == 'B','mix and match of text and elements'); + assert(deep_instance.container.firstChild.firstChild.firstChild.firstChild.firstChild.firstChild.firstChild.firstChild.firstChild.nodeValue == 'test','deep builder node test'); if(proceed) proceed()