Skip to content

Instantly share code, notes, and snippets.

@aaronksaunders
Last active December 19, 2015 03:39
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aaronksaunders/aae1962d3b4076763ab2 to your computer and use it in GitHub Desktop.
Save aaronksaunders/aae1962d3b4076763ab2 to your computer and use it in GitHub Desktop.
using backbone associate for simple relationships with Appcelerator Titanium Alloy https://github.com/rjz/backbone.associate
/**
* backbone.associate.js v0.0.6
* (c) 2013, RJ Zaworski
*
* Presumptionless model relations for Backbone.js
* Released under the MIT License
*
* https://github.com/rjz/backbone.associate
*/
(function (root, factory) {
// CommonJS compatibilty | ADDED CODE HERE SO ALLOY UNDERSCORE AND BACKBONE WOULD BE USED
if (typeof window == 'undefined' && !Ti) {
factory(require('underscore'), require('backbone'));
}
// AMD compat would go here, but requirejs `shim` config is a better option
else {
factory(root._, root.Backbone);
}
})(this, function (_, Backbone) {
var
// Sift through a map of attributes and initialize any
// known associations
_filterAssociates = function (attributes) {
var self = this,
current = self.attributes,
key, association, associations = self._associations,
omit = [];
for (key in associations) {
association = associations[key];
if (current[key] instanceof association.type) {
if (attributes[key] instanceof association.type) {
current[key] = attributes[key];
omit.push(key);
}
else if (_.has(attributes, key))
{
if (current[key] instanceof Backbone.Model) {
current[key].set(attributes[key]);
omit.push(key);
}
else if (current[key] instanceof Backbone.Collection) {
current[key].reset(attributes[key]);
omit.push(key);
}
}
}
else if (!(attributes[key] instanceof association.type)) {
attributes[key] = new (association.type)(attributes[key]);
}
}
return _.omit(attributes, omit);
},
// Wraps a method, exposing an "unwrap" method for reverting it later
_wrapMethod = function (wrapper, key) {
var self = this,
original = self[key],
wrapped = _.wrap(original, wrapper);
wrapped.unwrap = function () {
self[key] = original;
};
self[key] = wrapped;
},
// Extensions to Backbone.Model for filtering associate data, etc, etc
_extensions = {
// Updates `set` to handle supplied attributes
set: function (original, key, val, options) {
var self = this,
attributes = {};
if (_.isObject(key)) {
_.extend(attributes, key);
}
else {
attributes[key] = val;
}
if (_.isObject(val) && (typeof options === "undefined" || options === null)) {
options = val;
}
return original.call(self, _filterAssociates.call(self, attributes), options);
},
// Updates `toJSON` to serialize associated objects
toJSON: function (original, options) {
var self = this,
key, associations = self._associations,
attributes = original.call(self, options);
for (key in associations) {
if (attributes[key] instanceof associations[key]['type']) {
attributes[key] = attributes[key].toJSON();
}
}
return attributes;
}
},
// Patch initialize method to setup associations and filter initial attributes
_initialize = function (original, attrs, options) {
var self = this,
extensions = _.clone(_extensions);
// Provide associate accessors
for (key in self._associations) {
extensions[key] = _.partial(self.get, key);
}
// Wrap extensions around existing class methods
_.each(extensions, _wrapMethod, self);
// Filter any attributes that slipped by without parsing
_filterAssociates.call(self, self.attributes);
// Pass control back to the original initialize method
return original.call(self, attrs, options);
};
// Define associations for a model
Backbone.associate = function (klass, associations) {
var proto = klass.prototype;
if (!proto._associations) {
// Patch initialize method in prototype
_wrapMethod.call(proto, _initialize, 'initialize');
// Add namespace for associations
proto._associations = {};
}
_.extend(proto._associations, associations);
};
// Remove model associations
Backbone.dissociate = function (klass) {
var proto = klass.prototype;
proto.initialize.unwrap();
proto._associations = null;
};
});
//
// EXAM MODEL OBJECT
//
// twitter: @aaronksaunders
// blog.clearlyinnovative.com
//
exports.definition = {
config : {
columns : {
},
adapter : {
type : "properties",
collection_name : 'exams',
}
},
extendModel : function(Model) {
_.extend(Model.prototype, {
// extended functions and properties go here
/**
* need this function to return the actual Class for the Model
* there currently is no other way that I know of to do this
*/
Klass : function() {
return Model;
}
});
return Model;
},
extendCollection : function(Collection) {
_.extend(Collection.prototype, {
/**
* need this function to return the actual Class for the Collection
* there currently is no other way that I know of to do this
*/
Klass : function() {
return Collection;
},
/**
* cleanup function to remove all of the objects.
*
* added this for testing purposes
*/
cleanup : function() {
var regex = new RegExp("^(" + this.config.adapter.collection_name + ")\\-(.+)$");
var TAP = Ti.App.Properties;
_.each(TAP.listProperties(), function(prop) {
var match = prop.match(regex);
if (match) {
TAP.removeProperty(prop);
Ti.API.info('deleting old model ' + prop);
}
});
}
});
return Collection;
}
}
//
// INDEX.JS
//
// twitter: @aaronksaunders
// blog.clearlyinnovative.com
//
require('backbone.associate');
$.index.open();
//
// if true then delete existing data and create new models and dump output
// if false just dump the existing output... this is to test that the associations
// are actually being persisted
//
var CREATE_SAMPLE_EXAM = true;
// create the variables
var question, midtermExam;
var Exam = Alloy.createModel('exam');
var Exams = Alloy.createCollection('exam');
var Questions = Alloy.createCollection('question');
// create the association between the Exam and the questions.
// notice the new function on the extended Alloy model object.
// it is used to return the class so backbone.associate can
// extend it
Backbone.associate(Exam.Klass(), {
questions : {
type : Questions.Klass()
}
})
// CLEAN UP OLD DATA - uncomment this code to clean up the models and
// run the test all over again
if ( CREATE_SAMPLE_EXAM === true ) {
Alloy.createCollection('exam').cleanup();
Alloy.createCollection('question').cleanup();
// create a new exam
midtermExam = createANewExam();
// dump the exam results
fetchExams();
}
/**
* query the exams collections and dump the contents
*/
function fetchExams() {
Alloy.createCollection('exam').fetch({
success : function(_model, _response) {
Ti.API.info("EXAM: " + JSON.stringify(_model.models[0].toJSON(), null, 2));
Ti.API.info("QUESTIONS: " + JSON.stringify(_model.models[0].questions().toJSON(), null, 2));
},
error : function() {
}
});
}
/**
* create an exam and associated the question objects
*/
function createANewExam() {
var midtermExam = Alloy.createModel('exam', {
name : "Midterm2013"
});
midtermExam.save();
_.each(['one', 'two', 'three', 'four'], function(_item, _index) {
question = Alloy.createModel('question', {
exam_id : midtermExam.id,
title : "Question number " + _item
});
midtermExam.questions().add(question);
});
midtermExam.save();
return midtermExam;
}
//
// QUESTION MODEL OBJECT
//
// twitter: @aaronksaunders
// blog.clearlyinnovative.com
//
exports.definition = {
config : {
columns : {},
adapter : {
type : "properties",
collection_name : 'questions',
}
},
extendModel : function(Model) {
_.extend(Model.prototype, {
// extended functions and properties go here
/**
* need this function to return the actual Class for the Model
* there currently is no other way that I know of to do this
*/
Klass : function() {
return Model;
}
});
return Model;
},
extendCollection : function(Collection) {
_.extend(Collection.prototype, {
/**
* need this function to return the actual Class for the Collection
* there currently is no other way that I know of to do this
*/
Klass : function() {
return Collection;
},
/**
* cleanup function to remove all of the objects.
*
* added this for testing purposes
*/
cleanup : function() {
var regex = new RegExp("^(" + this.config.adapter.collection_name + ")\\-(.+)$");
var TAP = Ti.App.Properties;
_.each(TAP.listProperties(), function(prop) {
var match = prop.match(regex);
if (match) {
TAP.removeProperty(prop);
Ti.API.info('deleting old model ' + prop);
}
});
}
});
return Collection;
}
}
@Marza
Copy link

Marza commented Jul 20, 2013

Which version of Titanium does this work on? Im running into this error with 3.1.1

'undefined' is not a function (evaluating '_.partial(self.get, key)')

@troscoe
Copy link

troscoe commented Sep 5, 2013

@Marza you need to use a new version of underscore
https://twitter.com/aaronksaunders/status/351016886974152709

@shekyboy
Copy link

Aaron,

Does this work well with tableview databinding in Alloy?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment