Skip to content

Instantly share code, notes, and snippets.

@sr3d
Created February 4, 2011 02:26
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save sr3d/810643 to your computer and use it in GitHub Desktop.
Save sr3d/810643 to your computer and use it in GitHub Desktop.
My own version of ActiveRecord that actually works on Titanium.
(function() {
App.Models.Task = App.Models.Base.createModel('Task', 'tasks', {
name: 'TEXT',
note: 'TEXT',
due_at: 'DATE',
is_completed: 'TEXT',
completed_at: 'TEXT',
category_id: 'TEXT',
event_id: 'INTEGER',
has_reminders: 'TEXT',
reminder_type_ids: 'TEXT',
vendor_id: 'INTEGER',
updated_at: 'DATE',
created_at: 'DATE',
created: 'DATE'
}, {
// Instance Methods
/* return a list of reminders in CSV format */
getRemindersLabel: function() {
if( !this.reminder_type_ids ) { return ''; }
var ids = this.reminder_type_ids.split(',');
var value = [];
for(var i = 0; i < ids.length; i++ ) {
value.push( REMINDER_TYPES[ +ids[i] ] );
}
return value.join(', ');
}
}, {
// CLASS Methods
refreshLocal: function(options) {
options = _.extend({ onData: function(){} }, options || {} );
App.Models.refreshLocal( App.Models.Task, API_BASE + '/tasks.json', 'task', options);
}
,complete: function(taskIds, options) {
options = _.extend( {onData: function() {}}, options || {} ) ;
if( taskIds.length > 0 ) {
Request( API_BASE + '/tasks/complete.json', {
async: false,
method: 'POST',
parameters: { task_ids: taskIds.join(','), app_token: APP_TOKEN, api_token: API_TOKEN, email: EMAIL },
onSuccess: function(request) {
log('updating');
App.Models.executeNonSelect('UPDATE tasks SET is_completed = ? WHERE id IN (' + taskIds.join(',') + ')', true);
options.onData(request);
}
});
};
}
});
App.Models.Task.validatesPresenceOf('name','Name is required');
})();
(function() {
App.Models.Base = {};
App.Models.Base.InstanceMethods = {
getAttributes: function() {
var attr = {};
var model = App.Models[this.className];
var self = this;
_.each(model.fields, function(v,k) {
attr[ k ] = self[ k ];
});
return attr;
}
,setAttributes: function( attrs ) {
_.extend(this, attrs);
return this;
}
,updateAttributes: function( attrs) {
this.setAttributes(attrs);
this.save();
}
,save: function( skipValidation ) {
if( skipValidation === null ) { skipValidation = false; }
if( !skipValidation && !this.validate() ) { return false; };
var db = App.Models.connect();
var sql;
var fields = App.Models[ this.className ].fields;
var self = this;
if( this.persisted ) {
if(fields['updated']) { self.updated = (new Date()).toString(); };
sql = 'UPDATE '+this.tableName + ' SET ';
var values = [], cols = [];
_.each(fields, function(v, k) {
cols.push( '"' + k + '" = ?' );
values.push( fields[k] == 'DATE' ? ( _.isDate( self[ k ] ) ? self[k].valueOf() : Date.parse(self[k]).valueOf() ) : self[k] );
});
sql += cols.join(', ') + ' WHERE id = ' + this.id;
log(sql + ' ' + JSON.stringify(values) );
db.execute.apply(db, [sql].concat(values) );
} else {
sql = 'INSERT INTO ' + this.tableName; // + _.keys(fields).join(',') + ') VALUES ' + ;
var values = [], cols = [], placeHolders = [], self = this;
// add "created" magic column
if(fields['created']) { self.created = (new Date()).toString(); };
_.each(fields, function(v, k) {
cols.push('"' + k + '"' );
values.push( fields[k] == 'DATE' ? ( _.isDate( self[ k ] ) ? self[k].valueOf() : Date.parse(self[k]).valueOf() ) : self[k] );
placeHolders.push('?');
} );
sql += ' (' + cols.join(',') + ') ';
sql += ' VALUES ( ' + placeHolders.join(',') +' )';
log(sql + '[' + JSON.stringify(values) + ']' );
db.execute.apply(db, [sql].concat(values));
var id = db.lastInsertRowId;
this.id = id;
this.persisted = true;
}
this.errors = [];
return true;
}
,destroy: function() {
var sql = 'DELETE FROM '+ this.tableName + ' WHERE id = ? LIMIT 1';
log( sql + ' [' + this.id + ']');
(App.Models.connect()).execute(sql, this.id);
}
,validate: function() {
this.errors = {};
var validators = App.Models[this.className].validators;
for(var i = 0; i < validators.length; i++ ) {
if( !validators[i](this) ) {
return false;
}
}
return true;
}
,toString: function() {
return JSON.stringify(this.getAttributes());
}
,toRailsParamsHash: function(baseName) {
if( !baseName ) { baseName = this.className };
var hash = {};
var self = this;
_.each(App.Models[this.className].fields, function(v, k) {
// autoconvert date/ date in integer formats to Date object, then make sure it's in RailsISOString
hash[baseName + '[' + k +']' ] = ( v == 'DATE' ? ( _.isDate(self[k]) ? self[k].toRailsISOString() : (new Date(self[k])).toRailsISOString() ) : self[k] );
});
return hash;
}
};
// All methods in here receives the Model as the first argument
App.Models.Base.ClassMethods = {
fetchedAt: function(model) {
var lastFetch = DB.get(model.className + '_fetched_at');
if(!lastFetch){ return null; };
return new Date(lastFetch);
}
,setFetchedAt: function(model, date) {
DB.set( model.className + '_fetched_at', date || (new Date()) );
}
,find: function(model, id) {
var result;
var db = App.Models.connect();
var sql;
var fields = _.keys(model.fields);
if( _.isString(id) || _.isNumber(id) ) {
var rs = db.execute('SELECT * FROM '+ model.tableName +' WHERE id = ?', id);
if (rs.isValidRow()) {
result = new model();
result.persisted = true;
for(var i = 0; i < fields.length; i++ ) {
result[ fields[i] ] = rs.fieldByName( fields[i] );
};
}
} else { // isArray or null
var rs;
if( id == null ) {
sql ='SELECT * FROM '+ model.tableName;
log(sql);
rs = db.execute(sql);
} else { // find in specific id
sql = 'SELECT * FROM '+ model.tableName +' WHERE id IN (?)';
log( sql + '[' + id + ']' );
rs = db.execute(sql, id);
};
result = [];
while (rs.isValidRow()) {
var obj = new model();
obj.persisted = true;
for( var i = 0; i < fields.length; i++ ) {
obj[ fields[i] ] = rs.fieldByName( fields[i] );
};
result.push(obj);
rs.next();
};
}
rs.close();
return result;
}
/* WIP */
,findBySQL: function( model, /* String or []*/ sql, options ) {
options = _.extend({
all: false
}, options || {} );
var db = App.Models.connect();
var rs, result = [], fields = _.keys(model.fields);
log(sql);
if(_.isString(sql)) {
rs = db.execute(sql);
} else {
rs = db.execute.apply(db, sql);
};
result = [];
while (rs.isValidRow()) {
// log(rs);
var obj = new model();
obj.persisted = true;
for( var i = 0; i < fields.length; i++ ) {
obj[ fields[i] ] = rs.fieldByName( fields[i] );
};
result.push(obj);
if(!options.all) { break; };
rs.next();
};
return options.all ? result : result[0];
}
,create: function( model, json ) {
var obj = new model( json );
obj.save();
return obj;
}
,createTable: function(model) {
var sql = 'CREATE TABLE IF NOT EXISTS '+ model.tableName + ' (id INTEGER PRIMARY KEY';
var cols = [];
_.each(model.fields, function(v,k) {
cols.push( '"' + k + '" ' + (v == 'DATE' ? 'FLOAT' : v) );
});
sql += ', ' + cols.join(', ') + ')';
log(sql);
App.Models.db.execute(sql);
log('Done creating table ' + model.tableName);
}
,truncate: function(model) {
var sql = 'DELETE FROM ' + model.tableName;
log(sql);
(App.Models.connect()).execute(sql);
// log('Done truncating');
}
,addValidator: function( model, validator ) {
model.validators.push(validator);
}
,validatesPresenceOf: function(model, field, message) {
model.addValidator(function(instance) {
var value = (instance.getAttributes())[field];
if(!value || /^\s*$/.test(value)) {
instance.errors[ field ] = message || (field + ' ' + ' is required');
return false;
};
return true;
});
}
,validatesFormatOf: function(model, field, format, message) {
model.addValidator(function(instance) {
var value = instance.getAttributes()[field];
if(value && format.test(value)) { return true; };
instance.errors[ field ] = message || ('Wrong format for ' + field);
return false;
});
}
};
App.Models.Base.createModel = function(modelName, tableName, fields, instanceMethods, classMethods) {
var model = function( /* string or json object */ json, options) {
if( json ) {
if( _.isString(json) ) {
json = JSON.parse(json);
}
_.extend( this, json );
};
this.className = modelName;
this.tableName = tableName;
this.persisted = false;
this.errors = [];
}
_.extend(model.prototype, App.Models.Base.InstanceMethods);
_.extend(model.prototype, instanceMethods || {} );
_.each( _.extend({
tableName: tableName,
className: modelName,
validators: [],
fields: fields,
}, App.Models.Base.ClassMethods), function(v,k) {
var value = v;
// attach the class func to the model, appending the model as the first arg
if( _.isFunction(v) ) {
value = _.bind(v, model, model);
}
model[k] = value;
});
_.extend(model, classMethods);
// model.createTable();
if( !MIGRATE ) { model.createTable() ;}
// add the id column to the field list
model.fields['id'] = 'INTEGER';
return model;
};
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment