Skip to content

Commit

Permalink
Added tests to find collisons with backbone model/collection existing…
Browse files Browse the repository at this point in the history
… methods. Fixed those that were found.
  • Loading branch information
jthoms1 committed Mar 26, 2014
1 parent c55f960 commit 6b580e0
Show file tree
Hide file tree
Showing 8 changed files with 234 additions and 151 deletions.
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,21 @@ user.limit(10).fetch();
```

```JavaScript
// Fetch User model and include related role and organization
var user = new User({'id': 1});
user.include('role', 'organization').fetch();
user.includeRelated('role', 'organization').fetch();
```

```JavaScript
var user = new User({'id': 1});
user.where({
// Fetch User models when user's role is editor or author
var users = new UserCollection();
users.when({
role: ['editor', 'author']
}).fetch();
```

```JavaScript
// Fetch 10 User models that are on the
var users = new UserCollection();
users.limit(10).skip(2).fetch();
```
Expand Down
2 changes: 1 addition & 1 deletion dist/backbone.querybuilder.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

71 changes: 39 additions & 32 deletions src/backbone.querybuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,18 @@

var QueryBuilder = {
provider: null,

/*
* When provider is set then update Backbone Model and Collection prototypes
* to have new querybuilder methods.
*/
setProvider: function (provider) {
this.provider = provider;
_.extend(Backbone.Model.prototype, QueryBuilderMixin);
_.extend(Backbone.Collection.prototype, QueryBuilderMixin);
},
getMixin: function () {
return QueryBuilderMixin;
}
};

Expand All @@ -37,25 +47,25 @@
* Includes additional related objects with the returned data
*
* @example
* this.model.include('role', 'organization')
* this.model.include('organization.owner')
* this.model.includeRelated('role', 'organization')
* this.model.includeRelated('organization.owner')
* this.model.incldue('organization.owner.role', 'organization.staff')
*
* @method include
* @method includeRelated
* @return {object} Reference to current object
*/
include: function () {
includeRelated: function () {
var options = [].slice.call(arguments),
includeData = [];
includeRelatedData = [];

_.each(options, function (includeItem) {
includeData.push({
field: includeItem
_.each(options, function (includeRelatedItem) {
includeRelatedData.push({
field: includeRelatedItem
});
});

this._rbQueryData = this._rbQueryData || {};
this._rbQueryData['include'] = includeData;
this._rbQueryData['includeRelated'] = includeRelatedData;

return this;
},
Expand All @@ -64,36 +74,36 @@
* Includes additional related objects with the returned data
*
* @example
* this.model.where({
* this.model.when({
* role: {
* '=': 'editor'
* }
* })
* this.model.include('organization.owner')
* this.model.includeRelated('organization.owner')
* this.model.incldue('organization.owner.role', 'organization.staff')
*
* @method where
* @method when
* @param {object} options used to build the queries
* @return {object} Reference to current object
*/
where: function (options) {
var whereData = [];
when: function (options) {
var whenData = [];

_.each(options, function (value, key) {
if (typeof value === 'string' || typeof value === "number") {
whereData.push({
whenData.push({
field: key,
operator: '=',
value: value
});
} else if (toString.call(value) === '[object Array]') {
whereData.push({
whenData.push({
field: key,
operator: 'in',
value: value
});
} else if (typeof value === 'object') {
whereData.push({
whenData.push({
field: key,
operator: _.keys(value)[0],
value: _.values(value)[0]
Expand All @@ -102,7 +112,7 @@
});

this._rbQueryData = this._rbQueryData || {};
this._rbQueryData['where'] = whereData;
this._rbQueryData['when'] = whenData;

return this;
},
Expand All @@ -111,8 +121,8 @@
* Includes additional related objects with the returned data
*
* @example
* this.model.include('role', 'organization')
* this.model.include('organization.owner')
* this.model.includeRelatedRelated('role', 'organization')
* this.model.includeRelated('organization.owner')
* this.model.incldue('organization.owner.role', 'organization.staff')
*
* @method limit
Expand All @@ -129,8 +139,8 @@
* Includes additional related objects with the returned data
*
* @example
* this.model.include('role', 'organization')
* this.model.include('organization.owner')
* this.model.includeRelated('role', 'organization')
* this.model.includeRelated('organization.owner')
* this.model.incldue('organization.owner.role', 'organization.staff')
*
* @method skip
Expand All @@ -148,14 +158,14 @@
* can be called multiple times for each additonal sort item.
*
* @example
* this.model.include('role', 'organization')
* this.model.include('organization.owner')
* this.model.includeRelated('role', 'organization')
* this.model.includeRelated('organization.owner')
* this.model.incldue('organization.owner.role', 'organization.staff')
*
* @method sortBy
* @method orderBy
* @return {object} Reference to current object
*/
sortBy: function (options, order) {
orderBy: function (options, order) {
var fieldName,
direction;

Expand All @@ -168,8 +178,8 @@
}

this._rbQueryData = this._rbQueryData || {};
this._rbQueryData['sortBy'] = this._rbQueryData['sortBy'] || [];
this._rbQueryData['sortBy'].push({
this._rbQueryData['orderBy'] = this._rbQueryData['orderBy'] || [];
this._rbQueryData['orderBy'].push({
field: fieldName,
direction: direction
});
Expand All @@ -178,7 +188,7 @@
},

/**
* Overrides backbone sync to include data built by methods
* Overrides backbone sync to includeRelated data built by methods
*/
sync: function (method, item, options) {
var queryFields = QueryBuilder.provider.getFields(this._rbQueryData);
Expand All @@ -192,8 +202,5 @@
}
};

_.extend(Backbone.Model.prototype, QueryBuilderMixin);
_.extend(Backbone.Collection.prototype, QueryBuilderMixin);

return QueryBuilder;
}));
24 changes: 12 additions & 12 deletions src/earthling.dataprovider.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,18 @@
"use strict";

var conversionMethods = {
include: function (queryData) {
var includeFields = _.map(queryData['include'], function (obj) {
includeRelated: function (queryData) {
var includeRelatedFields = _.map(queryData['includeRelated'], function (obj) {
return _.values(obj)[0];
});

return {
'with': includeFields.join()
'with': includeRelatedFields.join()
};
},

where: function (queryData) {
var whereData = {},
when: function (queryData) {
var whenData = {},
comparisons = {
'=': 'eq',
'>': 'gt',
Expand All @@ -45,16 +45,16 @@
'like': 'like'
};

_.each(queryData['where'], function (item) {
_.each(queryData['when'], function (item) {

if (comparisons[item.operator]) {
whereData[item.field] = comparisons[item.operator] + ':' + item.value;
whenData[item.field] = comparisons[item.operator] + ':' + item.value;
} else {
whereData[item.field] = item.value;
whenData[item.field] = item.value;
}
});

return whereData;
return whenData;
},

limit: function (queryData) {
Expand All @@ -70,14 +70,14 @@
};
},

sortBy: function (queryData) {
var sortByData = _.map(queryData['sortBy'], function (item) {
orderBy: function (queryData) {
var orderByData = _.map(queryData['orderBy'], function (item) {
var direction = (item.direction === 'asc') ? '' : '-';
return direction + item.field;
});

return {
orderBy: sortByData.join()
orderBy: orderByData.join()
};
}
};
Expand Down
3 changes: 3 additions & 0 deletions test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@
<script src="../bower_components/underscore/underscore.js"></script>
<script src="../bower_components/backbone/backbone.js"></script>
<script src="sinon-1.7.3.js"></script>

<!-- include querybuilder code-->
<script src="../src/backbone.querybuilder.js"></script>
<script src="../src/earthling.dataprovider.js"></script>

<!-- Test scripts -->
<script src="spec/backbone-compatibility.js"></script>
<script src="spec/earthling.provider.js"></script>
<script src="spec/interface.js"></script>
</head>
Expand Down
71 changes: 71 additions & 0 deletions test/spec/backbone-compatibility.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/* global test, module, ok */
(function (Backbone, _, querybuilder) {
"use strict";

var user,
users,
User,
UserCollection,
bbMixin = querybuilder.getMixin(),
expectedCollisions = ['sync'];

module("Backbone Mixin", {
setup: function() {
User = Backbone.Model.extend({
urlRoot: '/api/user'
});

UserCollection = Backbone.Collection.extend({
model: User,
url: '/api/user'
});
}
});

test("Backbone Model: unexpected method collisions", _.keys(bbMixin).length - expectedCollisions.length, function () {
var prop;
user = new User();

_.indexOf(expectedCollisions, prop);

for (prop in bbMixin) {
if (_.indexOf(expectedCollisions, prop) !== -1) {
continue;
}

ok(typeof user[prop] === 'undefined', 'Backbone.Model already has method ' + prop);
}
});

test("Backbone Model: expected method collisions", expectedCollisions.length, function () {
var i = 0;
user = new User();

for (; i < expectedCollisions.length; i++) {
ok(expectedCollisions[i] !== 'undefined', 'Backbone.Model does not have method ' + expectedCollisions[i]);
}
});

test("Backbone Collection: unexpected method collisions", _.keys(bbMixin).length - expectedCollisions.length, function () {
var prop;
users = new UserCollection();

for (prop in bbMixin) {
if (_.indexOf(expectedCollisions, prop) !== -1) {
continue;
}

ok(typeof users[prop] === 'undefined', 'Backbone.Collection already has method ' + prop);
}
});

test("Backbone Collection: expected method collisions", expectedCollisions.length, function () {
var i = 0;
users = new UserCollection();

for (; i < expectedCollisions.length; i++) {
ok(expectedCollisions[i] !== 'undefined', 'Backbone.Model does not have method ' + expectedCollisions[i]);
}
});

})(window.Backbone, window._, window.querybuilder);

0 comments on commit 6b580e0

Please sign in to comment.