--------------------------------------------------------------------------------
/test/base/tests/collection.js:
--------------------------------------------------------------------------------
1 | var when = require('when');
2 | var assert = require('assert');
3 | var equal = assert.equal;
4 | var _ = require('underscore');
5 |
6 | module.exports = function() {
7 |
8 | // This module is included into the `bookshelf` repository,
9 | // and run from the root of the directory.
10 | var path = require('path');
11 | var basePath = process.cwd();
12 |
13 | var CollectionBase = require(path.resolve(basePath + '/dialects/base/collection')).CollectionBase;
14 | var ModelBase = require(path.resolve(basePath + '/dialects/base/model')).ModelBase;
15 |
16 | describe('Collection', function() {
17 |
18 | var collection;
19 | var Collection = CollectionBase.extend({
20 | model: ModelBase.extend({
21 | tableName: 'test_table',
22 | idAttribute: 'some_id',
23 | invokedMethod: function() {
24 | return when(this.id);
25 | }
26 | })
27 | });
28 |
29 | beforeEach(function() {
30 | collection = new Collection([{some_id: 1, name: 'Test'}, {id: 2, name: 'No Id'}]);
31 | });
32 |
33 | it('should have a tableName method, returning the tableName of the model', function () {
34 |
35 | equal(collection.tableName(), 'test_table');
36 |
37 | });
38 |
39 | it('should have an idAttribute method, returning the idAttribute of the model', function() {
40 |
41 | equal(collection.idAttribute(), 'some_id');
42 |
43 | });
44 |
45 | it('should initialize the items passed to the constructor', function() {
46 |
47 | equal(collection.length, 2);
48 |
49 | equal(collection.at(0).id, 1);
50 |
51 | equal(collection.at(1).id, undefined);
52 |
53 | });
54 |
55 | it('should use the `set` method to update the collections, similar to Backbone', function() {
56 |
57 | collection.set([{some_id: 2, name: 'Test'}, {some_id: 3, name: 'Item'}]);
58 |
59 | equal(collection.length, 2);
60 |
61 | collection.set([{some_id: 2, name: 'Item'}], {remove: false});
62 |
63 | equal(collection.length, 2);
64 |
65 | });
66 |
67 | it('should use the `reset` method, to reset the collection', function() {
68 |
69 | collection.reset([]);
70 |
71 | equal(collection.length, 0);
72 |
73 | });
74 |
75 | it('should use _prepareModel to prep model instances', function() {
76 |
77 | var model = new ModelBase({id: 1});
78 |
79 | expect(model).to.equal(collection._prepareModel(model));
80 |
81 | var newModel = collection._prepareModel({some_id: 1});
82 |
83 | assert.ok((newModel instanceof collection.model));
84 |
85 | });
86 |
87 | it('contains a mapThen method, which calls map on the models, and returns a when.all promise', function() {
88 |
89 | var spyIterator = sinon.spy(function(model) {
90 | return model.id;
91 | });
92 |
93 | return collection.mapThen(spyIterator).then(function(resp) {
94 | spyIterator.should.have.been.calledTwice;
95 | expect(_.compact(resp)).to.eql([1]);
96 | });
97 |
98 | });
99 |
100 | it('contains an invokeThen method, which does an invoke on the models, and returns a when.all promise', function() {
101 |
102 | var spyIterator = sinon.spy(function(model) {
103 | return model.id;
104 | });
105 |
106 | return collection.invokeThen('invokedMethod').then(function(resp) {
107 | expect(_.compact(resp)).to.eql([1]);
108 | });
109 |
110 | });
111 |
112 | });
113 |
114 |
115 | };
--------------------------------------------------------------------------------
/dialects/sql/sync.js:
--------------------------------------------------------------------------------
1 | // Sync
2 | // ---------------
3 | (function(define) {
4 |
5 | "use strict";
6 |
7 | define(function(require, exports) {
8 |
9 | var _ = require('underscore');
10 | var when = require('when');
11 |
12 | // Sync is the dispatcher for any database queries,
13 | // taking the "syncing" `model` or `collection` being queried, along with
14 | // a hash of options that are used in the various query methods.
15 | // If the `transacting` option is set, the query is assumed to be
16 | // part of a transaction, and this information is passed along to `Knex`.
17 | var Sync = function(syncing, options) {
18 | options || (options = {});
19 | this.query = syncing.query();
20 | this.syncing = syncing.resetQuery();
21 | this.options = options;
22 | this._init(syncing, options);
23 | this.initialize(syncing, options);
24 | };
25 |
26 | _.extend(Sync.prototype, {
27 |
28 | initialize: function() {},
29 |
30 | // Select the first item from the database - only used by models.
31 | first: function() {
32 | var syncing = this.syncing;
33 | this.query.where(syncing.format(_.extend(Object.create(null), syncing.attributes))).limit(1);
34 | return this.select();
35 | },
36 |
37 | // Runs a `select` query on the database, adding any necessary relational
38 | // constraints, resetting the query when complete. If there are results and
39 | // eager loaded relations, those are fetched and returned on the model before
40 | // the promise is resolved. Any `success` handler passed in the
41 | // options will be called - used by both models & collections.
42 | select: function() {
43 | var columns, sync = this, syncing = this.syncing,
44 | options = this.options, relatedData = syncing.relatedData;
45 |
46 | // Inject all appropriate select costraints dealing with the relation
47 | // into the `knex` query builder for the current instance.
48 | if (relatedData) {
49 | relatedData.selectConstraints(this.query, options);
50 | } else {
51 | columns = options.columns;
52 | if (!_.isArray(columns)) columns = columns ? [columns] : [_.result(syncing, 'tableName') + '.*'];
53 | }
54 |
55 | // Set the query builder on the options, in-case we need to
56 | // access in the `fetching` event handlers.
57 | options.query = this.query;
58 |
59 | // Trigger a `fetching` event on the model, and then select the appropriate columns.
60 | return syncing.triggerThen('fetching', syncing, columns, options).then(function() {
61 | return sync.query.select(columns);
62 | });
63 | },
64 |
65 | // Issues an `insert` command on the query - only used by models.
66 | insert: function() {
67 | var syncing = this.syncing;
68 | return this.query
69 | .insert(syncing.format(_.extend(Object.create(null), syncing.attributes)), syncing.idAttribute);
70 | },
71 |
72 | // Issues an `update` command on the query - only used by models.
73 | update: function(attrs) {
74 | var syncing = this.syncing, query = this.query;
75 | if (syncing.id != null) query.where(syncing.idAttribute, syncing.id);
76 | if (query.wheres.length === 0) {
77 | return when.reject(new Error('A model cannot be updated without a "where" clause or an idAttribute.'));
78 | }
79 | return query.update(syncing.format(_.extend(Object.create(null), attrs)));
80 | },
81 |
82 | // Issues a `delete` command on the query.
83 | del: function() {
84 | var query = this.query, syncing = this.syncing;
85 | if (syncing.id != null) query.where(syncing.idAttribute, syncing.id);
86 | if (query.wheres.length === 0) {
87 | return when.reject(new Error('A model cannot be destroyed without a "where" clause or an idAttribute.'));
88 | }
89 | return this.query.del();
90 | },
91 |
92 | _init: function(syncing, options) {
93 | if (options.transacting) this.query.transacting(options.transacting);
94 | }
95 |
96 | });
97 |
98 | exports.Sync = Sync;
99 |
100 | });
101 |
102 | })(
103 | typeof define === 'function' && define.amd ? define : function (factory) { factory(require, exports); }
104 | );
--------------------------------------------------------------------------------
/dialects/sql/eager.js:
--------------------------------------------------------------------------------
1 | // EagerRelation
2 | // ---------------
3 | (function(define) {
4 |
5 | "use strict";
6 |
7 | define(function(require, exports) {
8 |
9 | var _ = require('underscore');
10 | var when = require('when');
11 |
12 | var Helpers = require('./helpers').Helpers;
13 | var EagerBase = require('../base/eager').EagerBase;
14 |
15 | // An `EagerRelation` object temporarily stores the models from an eager load,
16 | // and handles matching eager loaded objects with their parent(s). The `tempModel`
17 | // is only used to retrieve the value of the relation method, to know the constrains
18 | // for the eager query.
19 | var EagerRelation = exports.EagerRelation = EagerBase.extend({
20 |
21 | // Handles an eager loaded fetch, passing the name of the item we're fetching for,
22 | // and any options needed for the current fetch.
23 | eagerFetch: function(relationName, handled, options) {
24 | var relatedData = handled.relatedData;
25 |
26 | if (relatedData.type === 'morphTo') return this.morphToFetch(relationName, relatedData, options);
27 |
28 | // Call the function, if one exists, to constrain the eager loaded query.
29 | options.beforeFn.call(handled, handled.query());
30 |
31 | return handled
32 | .sync(_.extend({}, options, {parentResponse: this.parentResponse}))
33 | .select()
34 | .tap(eagerLoadHelper(this, relationName, handled, options));
35 | },
36 |
37 | // Special handler for the eager loaded morph-to relations, this handles
38 | // the fact that there are several potential models that we need to be fetching against.
39 | // pairing them up onto a single response for the eager loading.
40 | morphToFetch: function(relationName, relatedData, options) {
41 | var pending = [];
42 | var groups = _.groupBy(this.parent, function(m) {
43 | return m.get(relationName + '_type');
44 | });
45 | for (var group in groups) {
46 | var Target = Helpers.morphCandidate(relatedData.candidates, group);
47 | var target = new Target();
48 | pending.push(target
49 | .query('whereIn',
50 | _.result(target, 'idAttribute'), _.uniq(_.invoke(groups[group], 'get', relationName + '_id'))
51 | )
52 | .sync(options)
53 | .select()
54 | .tap(eagerLoadHelper(this, relationName, {
55 | relatedData: relatedData.instance('morphTo', Target, {morphName: relationName})
56 | }, options)));
57 | }
58 | return when.all(pending).then(function(resps) {
59 | return _.flatten(resps);
60 | });
61 | }
62 |
63 | });
64 |
65 | // Handles the eager load for both the `morphTo` and regular cases.
66 | var eagerLoadHelper = function(relation, relationName, handled, options) {
67 | return function(resp) {
68 | var relatedModels = relation.pushModels(relationName, handled, resp);
69 | var relatedData = handled.relatedData;
70 |
71 | // If there is a response, fetch additional nested eager relations, if any.
72 | if (resp.length > 0 && options.withRelated) {
73 | var relatedModel = relatedData.createModel();
74 |
75 | // If this is a `morphTo` relation, we need to do additional processing
76 | // to ensure we don't try to load any relations that don't look to exist.
77 | if (relatedData.type === 'morphTo') {
78 | var withRelated = filterRelated(relatedModel, options);
79 | if (withRelated.length === 0) return;
80 | options = _.extend({}, options, {withRelated: withRelated});
81 | }
82 | return new EagerRelation(relatedModels, resp, relatedModel).fetch(options).yield(resp);
83 | }
84 | };
85 | };
86 |
87 | // Filters the `withRelated` on a `morphTo` relation, to ensure that only valid
88 | // relations are attempted for loading.
89 | var filterRelated = function(relatedModel, options) {
90 | // By this point, all withRelated should be turned into a hash, so it should
91 | // be fairly simple to process by splitting on the dots.
92 | return _.reduce(options.withRelated, function(memo, val) {
93 | for (var key in val) {
94 | var seg = key.split('.')[0];
95 | if (_.isFunction(relatedModel[seg])) memo.push(val);
96 | }
97 | return memo;
98 | }, []);
99 | };
100 |
101 |
102 | });
103 |
104 | })(
105 | typeof define === 'function' && define.amd ? define : function (factory) { factory(require, exports); }
106 | );
--------------------------------------------------------------------------------
/dialects/base/eager.js:
--------------------------------------------------------------------------------
1 | // Eager Base
2 | // ---------------
3 | (function(define) {
4 |
5 | "use strict";
6 |
7 | // The EagerBase provides a scaffold for handling with eager relation
8 | // pairing, by queueing the appropriate related method calls with
9 | // a database specific `eagerFetch` method, which then may utilize
10 | // `pushModels` for pairing the models depending on the database need.
11 | define(function(require, exports) {
12 |
13 | var _ = require('underscore');
14 | var when = require('when');
15 | var Backbone = require('backbone');
16 |
17 | var EagerBase = function(parent, parentResponse, target) {
18 | this.parent = parent;
19 | this.target = target;
20 | this.parentResponse = parentResponse;
21 | _.bindAll(this, 'pushModels', 'eagerFetch');
22 | };
23 |
24 | EagerBase.prototype = {
25 |
26 | // This helper function is used internally to determine which relations
27 | // are necessary for fetching based on the `model.load` or `withRelated` option.
28 | fetch: function(options) {
29 | var relationName, related, relation;
30 | var target = this.target;
31 | var handled = this.handled = {};
32 | var withRelated = this.prepWithRelated(options.withRelated);
33 | var subRelated = {};
34 |
35 | // Internal flag to determine whether to set the ctor(s) on the `Relation` object.
36 | target._isEager = true;
37 |
38 | // Eager load each of the `withRelated` relation item, splitting on '.'
39 | // which indicates a nested eager load.
40 | for (var key in withRelated) {
41 |
42 | related = key.split('.');
43 | relationName = related[0];
44 |
45 | // Add additional eager items to an array, to load at the next level in the query.
46 | if (related.length > 1) {
47 | var relatedObj = {};
48 | subRelated[relationName] || (subRelated[relationName] = []);
49 | relatedObj[related.slice(1).join('.')] = withRelated[key];
50 | subRelated[relationName].push(relatedObj);
51 | }
52 |
53 | // Only allow one of a certain nested type per-level.
54 | if (handled[relationName]) continue;
55 |
56 | relation = target[relationName]();
57 |
58 | if (!relation) throw new Error(relationName + ' is not defined on the model.');
59 |
60 | handled[relationName] = relation;
61 | }
62 |
63 | // Delete the internal flag from the model.
64 | delete target._isEager;
65 |
66 | // Fetch all eager loaded models, loading them onto
67 | // an array of pending deferred objects, which will handle
68 | // all necessary pairing with parent objects, etc.
69 | var pendingDeferred = [];
70 | for (relationName in handled) {
71 | pendingDeferred.push(this.eagerFetch(relationName, handled[relationName], _.extend({}, options, {
72 | isEager: true,
73 | withRelated: subRelated[relationName],
74 | beforeFn: withRelated[relationName] || noop
75 | })));
76 | }
77 |
78 | // Return a deferred handler for all of the nested object sync
79 | // returning the original response when these syncs & pairings are complete.
80 | return when.all(pendingDeferred).yield(this.parentResponse);
81 | },
82 |
83 | // Prep the `withRelated` object, to normalize into an object where each
84 | // has a function that is called when running the query.
85 | prepWithRelated: function(withRelated) {
86 | if (!_.isArray(withRelated)) withRelated = [withRelated];
87 | return _.reduce(withRelated, function(memo, item) {
88 | _.isString(item) ? memo[item] = noop : _.extend(memo, item);
89 | return memo;
90 | }, {});
91 | },
92 |
93 | // Pushes each of the incoming models onto a new `related` array,
94 | // which is used to correcly pair additional nested relations.
95 | pushModels: function(relationName, handled, resp) {
96 | var models = this.parent;
97 | var relatedData = handled.relatedData;
98 | var related = [];
99 | for (var i = 0, l = resp.length; i < l; i++) {
100 | related.push(relatedData.createModel(resp[i]));
101 | }
102 | return relatedData.eagerPair(relationName, related, models);
103 | }
104 |
105 | };
106 |
107 | var noop = function() {};
108 |
109 | EagerBase.extend = Backbone.Model.extend;
110 |
111 | exports.EagerBase = EagerBase;
112 |
113 | });
114 |
115 | })(
116 | typeof define === 'function' && define.amd ? define : function (factory) { factory(require, exports); }
117 | );
--------------------------------------------------------------------------------
/test/integration/helpers/migration.js:
--------------------------------------------------------------------------------
1 | var _ = require('underscore');
2 | var when = require('when');
3 |
4 | var drops = [
5 | 'sites', 'sitesmeta', 'admins',
6 | 'admins_sites', 'authors', 'authors_posts',
7 | 'blogs', 'posts', 'tags', 'posts_tags', 'comments',
8 | 'users', 'roles', 'photos', 'users_roles', 'info',
9 | 'Customer', 'Settings', 'hostnames', 'instances'
10 | ];
11 |
12 | module.exports = function(Bookshelf) {
13 |
14 | var schema = Bookshelf.knex.schema;
15 |
16 | return when.all(_.map(drops, function(val) {
17 | return schema.dropTableIfExists(val);
18 | }))
19 | .then(function() {
20 |
21 | return when.all([
22 | schema.createTable('sites', function(table) {
23 | table.increments('id');
24 | table.string('name');
25 | }),
26 |
27 | schema.createTable('sitesmeta', function(table) {
28 | table.increments('id');
29 | table.integer('site_id');
30 | table.text('description');
31 | }),
32 |
33 | schema.createTable('info', function(table) {
34 | table.increments('id');
35 | table.integer('meta_id');
36 | table.text('other_description');
37 | }),
38 |
39 | schema.createTable('admins', function(table) {
40 | table.increments('id');
41 | table.string('username');
42 | table.string('password');
43 | table.timestamps();
44 | }),
45 |
46 | schema.createTable('admins_sites', function(table) {
47 | table.increments('id');
48 | table.integer('admin_id');
49 | table.integer('site_id');
50 | table.string('item').defaultTo('test');
51 | }),
52 |
53 | schema.createTable('blogs', function(table) {
54 | table.increments('id');
55 | table.integer('site_id');
56 | table.string('name');
57 | }),
58 |
59 | schema.createTable('authors', function(table) {
60 | table.increments('id');
61 | table.integer('site_id');
62 | table.string('first_name');
63 | table.string('last_name');
64 | }),
65 |
66 | schema.createTable('posts', function(table) {
67 | table.increments('id');
68 | table.integer('owner_id');
69 | table.integer('blog_id');
70 | table.string('name');
71 | table.text('content');
72 | }),
73 |
74 | schema.createTable('authors_posts', function(table) {
75 | table.increments('id');
76 | table.integer('author_id');
77 | table.integer('post_id');
78 | }),
79 |
80 | schema.createTable('tags', function(table) {
81 | table.increments('id');
82 | table.string('name');
83 | }),
84 |
85 | schema.createTable('posts_tags', function(table) {
86 | table.increments('id');
87 | table.integer('post_id');
88 | table.integer('tag_id');
89 | }),
90 |
91 | schema.createTable('comments', function(table) {
92 | table.increments('id');
93 | table.integer('post_id');
94 | table.string('name');
95 | table.string('email');
96 | table.text('comment');
97 | }),
98 |
99 | schema.createTable('users', function(table) {
100 | table.increments('uid');
101 | table.string('username');
102 | }),
103 |
104 | schema.createTable('roles', function(table) {
105 | table.increments('rid');
106 | table.string('name');
107 | }),
108 |
109 | schema.createTable('users_roles', function(table) {
110 | table.integer('rid');
111 | table.integer('uid');
112 | }),
113 |
114 | schema.createTable('photos', function(table) {
115 | table.increments('id');
116 | table.string('url');
117 | table.string('caption');
118 | table.integer('imageable_id');
119 | table.string('imageable_type');
120 | }),
121 |
122 | schema.createTable('Customer', function(table) {
123 | table.increments('id');
124 | table.string('name');
125 | }),
126 |
127 | schema.createTable('Settings', function(table) {
128 | table.increments('id');
129 | table.integer('Customer_id');
130 | table.string('data', 64);
131 | }),
132 |
133 | schema.createTable('hostnames', function(table){
134 | table.string('hostname');
135 | table.integer('instance_id');
136 | table.enu('route', ['annotate','submit']);
137 | }),
138 |
139 | schema.createTable('instances', function(table){
140 | table.bigIncrements('id');
141 | table.string('name');
142 | })
143 |
144 | ]);
145 |
146 | });
147 |
148 | };
--------------------------------------------------------------------------------
/bookshelf.js:
--------------------------------------------------------------------------------
1 | // Bookshelf.js 0.5.7
2 | // ---------------
3 |
4 | // (c) 2013 Tim Griesser
5 | // Bookshelf may be freely distributed under the MIT license.
6 | // For all details and documentation:
7 | // http://bookshelfjs.org
8 | (function(define) {
9 |
10 | "use strict";
11 |
12 | define(function(require, exports, module) {
13 |
14 | // All external libraries needed in this scope.
15 | var _ = require('underscore');
16 | var Knex = require('knex');
17 |
18 | // All local dependencies... These are the main objects that
19 | // need to be augmented in the constructor to work properly.
20 | var SqlModel = require('./dialects/sql/model').Model;
21 | var SqlCollection = require('./dialects/sql/collection').Collection;
22 | var SqlRelation = require('./dialects/sql/relation').Relation;
23 |
24 | // Finally, the `Events`, which we've supplemented with a `triggerThen`
25 | // method to allow for asynchronous event handling via promises. We also
26 | // mix this into the prototypes of the main objects in the library.
27 | var Events = require('./dialects/base/events').Events;
28 |
29 | // Constructor for a new `Bookshelf` object, it accepts
30 | // an active `knex` instance and initializes the appropriate
31 | // `Model` and `Collection` constructors for use in the current instance.
32 | var Bookshelf = function(knex) {
33 |
34 | // Allows you to construct the library with either `Bookshelf(opts)`
35 | // or `new Bookshelf(opts)`.
36 | if (!(this instanceof Bookshelf)) {
37 | return new Bookshelf(knex);
38 | }
39 |
40 | // If the knex isn't a `Knex` instance, we'll assume it's
41 | // a compatible config object and pass it through to create a new instance.
42 | if (!knex.client || !(knex.client instanceof Knex.ClientBase)) {
43 | knex = new Knex(knex);
44 | }
45 |
46 | // The `Model` constructor is referenced as a property on the `Bookshelf` instance,
47 | // mixing in the correct `builder` method, as well as the `relation` method,
48 | // passing in the correct `Model` & `Collection` constructors for later reference.
49 | var ModelCtor = this.Model = SqlModel.extend({
50 | _builder: function(tableName) {
51 | return knex(tableName);
52 | },
53 | _relation: function(type, Target, options) {
54 | return new Relation(type, Target, options);
55 | }
56 | });
57 |
58 | // The collection also references the correct `Model`, specified above, for creating
59 | // new `Model` instances in the collection. We also extend with the correct builder /
60 | // `knex` combo.
61 | var CollectionCtor = this.Collection = SqlCollection.extend({
62 | model: ModelCtor,
63 | _builder: function(tableName) {
64 | return knex(tableName);
65 | }
66 | });
67 |
68 | // Used internally, the `Relation` helps in simplifying the relationship building,
69 | // centralizing all logic dealing with type & option handling.
70 | var Relation = Bookshelf.Relation = SqlRelation.extend({
71 | Model: ModelCtor,
72 | Collection: CollectionCtor
73 | });
74 |
75 | // Grab a reference to the `knex` instance passed (or created) in this constructor,
76 | // for convenience.
77 | this.knex = knex;
78 | };
79 |
80 | // A `Bookshelf` instance may be used as a top-level pub-sub bus, as it mixes in the
81 | // `Events` object. It also contains the version number, and a `Transaction` method
82 | // referencing the correct version of `knex` passed into the object.
83 | _.extend(Bookshelf.prototype, Events, {
84 |
85 | // Keep in sync with `package.json`.
86 | VERSION: '0.5.7',
87 |
88 | // Helper method to wrap a series of Bookshelf actions in a `knex` transaction block;
89 | transaction: function() {
90 | return this.knex.transaction.apply(this, arguments);
91 | },
92 |
93 | // Provides a nice, tested, standardized way of adding plugins to a `Bookshelf` instance,
94 | // injecting the current instance into the plugin, which should be a module.exports.
95 | plugin: function(plugin) {
96 | plugin(this);
97 | return this;
98 | }
99 |
100 | });
101 |
102 | // Alias to `new Bookshelf(opts)`.
103 | Bookshelf.initialize = function(knex) {
104 | return new this(knex);
105 | };
106 |
107 | // The `forge` function properly instantiates a new Model or Collection
108 | // without needing the `new` operator... to make object creation cleaner
109 | // and more chainable.
110 | SqlModel.forge = SqlCollection.forge = function() {
111 | var inst = Object.create(this.prototype);
112 | var obj = this.apply(inst, arguments);
113 | return (Object(obj) === obj ? obj : inst);
114 | };
115 |
116 | // Finally, export `Bookshelf` to the world.
117 | module.exports = Bookshelf;
118 |
119 | });
120 |
121 | })(
122 | typeof define === 'function' && define.amd ? define : function (factory) { factory(require, exports, module); }
123 | );
--------------------------------------------------------------------------------
/test/integration/collection.js:
--------------------------------------------------------------------------------
1 | var when = require('when');
2 |
3 | module.exports = function(Bookshelf) {
4 |
5 | describe('Collection', function() {
6 |
7 | var Backbone = require('backbone');
8 |
9 | var Models = require('./helpers/objects')(Bookshelf).Models;
10 | var Collections = require('./helpers/objects')(Bookshelf).Collections;
11 |
12 | // Models
13 | var Site = Models.Site;
14 | var SiteMeta = Models.SiteMeta;
15 | var Admin = Models.Admin;
16 | var Author = Models.Author;
17 | var Blog = Models.Blog;
18 | var Post = Models.Post;
19 | var Comment = Models.Comment;
20 | var Tag = Models.Tag;
21 | var User = Models.User;
22 | var Role = Models.Role;
23 | var Photo = Models.Photo;
24 |
25 | // Collections
26 | var Sites = Collections.Sites;
27 | var Admins = Collections.Admins;
28 | var Blogs = Collections.Blogs;
29 | var Posts = Collections.Posts;
30 | var Comments = Collections.Comment;
31 | var Photos = Collections.Photos;
32 |
33 | describe('fetch', function() {
34 |
35 | it ('fetches the models in a collection', function() {
36 | return Bookshelf.Collection.extend({tableName: 'posts'})
37 | .forge()
38 | .logMe()
39 | .fetch();
40 | });
41 |
42 | });
43 |
44 | describe('fetchOne', function() {
45 |
46 | it ('fetches a single model from the collection', function() {
47 | return new Site({id:1})
48 | .authors()
49 | .fetchOne()
50 | .then(function(model) {
51 | return model.get('site_id', 1);
52 | });
53 | });
54 |
55 | it ('maintains a clone of the query builder from the current collection', function() {
56 |
57 | return new Site({id:1})
58 | .authors()
59 | .query({where: {id: 40}})
60 | .fetchOne()
61 | .then(function(model) {
62 | expect(model).to.be.null;
63 | });
64 |
65 | });
66 |
67 | it ('follows the typical model options, like require: true', function() {
68 |
69 | return expect(new Site({id:1})
70 | .authors()
71 | .query({where: {id: 40}})
72 | .fetchOne({require: true})).to.be.rejected;
73 |
74 | });
75 |
76 | });
77 |
78 | describe('sync', function() {
79 |
80 | it('creates a new instance of Sync', function(){
81 | var model = new Bookshelf.Model();
82 | expect(model.sync(model)).to.be.an.instanceOf(require('../../dialects/sql/sync').Sync);
83 | });
84 |
85 | });
86 |
87 | describe('create', function() {
88 |
89 | it('creates and saves a new model instance, saving it to the collection', function () {
90 |
91 | return new Sites().create({name: 'google.com'}).then(function(model) {
92 | expect(model.get('name')).to.equal('google.com');
93 | return model.destroy();
94 | });
95 |
96 | });
97 |
98 | it('should populate a `hasMany` or `morphMany` with the proper keys', function() {
99 |
100 | return new Site({id: 10})
101 | .authors()
102 | .create({first_name: 'test', last_name: 'tester'})
103 | .then(function(author) {
104 | expect(author.get('first_name')).to.equal('test');
105 | expect(author.get('last_name')).to.equal('tester');
106 | expect(author.get('site_id')).to.equal(10);
107 | return author.destroy();
108 | })
109 | .then(function() {
110 | return new Site({id: 10})
111 | .photos()
112 | .create({
113 | url: 'http://image.dev',
114 | caption: 'this is a test image'
115 | })
116 | .then(function(photo) {
117 | expect(photo.get('imageable_id')).to.equal(10);
118 | expect(photo.get('imageable_type')).to.equal('sites');
119 | expect(photo.get('url')).to.equal('http://image.dev');
120 | });
121 | });
122 |
123 | });
124 |
125 | it('should automatically create a join model when joining a belongsToMany', function() {
126 |
127 | return new Site({id: 1})
128 | .admins()
129 | .create({username: 'test', password: 'test'})
130 | .then(function(admin) {
131 | expect(admin.get('username')).to.equal('test');
132 | });
133 |
134 | });
135 |
136 | it('should maintain the correct constraints when creating a model from a relation', function() {
137 | var authors = new Site({id: 1}).authors();
138 | var query = authors.query();
139 |
140 | query.then = function(onFufilled, onRejected) {
141 | expect(this.values[0]).to.eql([['first_name', 'Test'], ['last_name', 'User'], ['site_id', 1]]);
142 | return when.resolve(this.toString()).then(onFufilled, onRejected);
143 | };
144 |
145 | return authors.create({first_name: 'Test', last_name: 'User'});
146 | });
147 |
148 |
149 | });
150 |
151 | });
152 |
153 | };
--------------------------------------------------------------------------------
/test/integration/helpers/logger.js:
--------------------------------------------------------------------------------
1 | var cwd = process.cwd();
2 | var isDev = parseInt(process.env.BOOKSHELF_DEV, 10);
3 |
4 | var _ = require('underscore');
5 |
6 | var Ctors = {};
7 |
8 | var Common = require('knex/lib/common').Common;
9 |
10 | Ctors.Raw = require('knex/lib/raw').Raw;
11 | Ctors.Builder = require('knex/lib/builder').Builder;
12 | Ctors.SchemaBuilder = require('knex/lib/schemabuilder').SchemaBuilder;
13 |
14 | Ctors.Model = require('../../../dialects/sql/model').Model;
15 | Ctors.Collection = require('../../../dialects/sql/collection').Collection;
16 |
17 | var fs = require('fs');
18 | var objectdump = require('objectdump');
19 |
20 | // This is where all of the info from the query calls goes...
21 | var output = {};
22 | var comparable = {};
23 | var counters = {};
24 |
25 | exports.setLib = function(context) {
26 |
27 | var logMe = function(logWhat) {
28 | this.isLogging = logWhat || 'result';
29 | return this;
30 | };
31 |
32 | var then = function(onFufilled, onRejected) {
33 |
34 | this._promise || (this._promise = this.client.query(this));
35 |
36 | var then = this;
37 |
38 | if (this.isLogging) {
39 |
40 | var title = context.test.title;
41 | var parent = generateTitle(context.test).pop();
42 | var dialect = this.client.dialect;
43 |
44 | if (!isDev && !comparable[parent]) {
45 | comparable[parent] = require(__dirname + '/../output/' + parent);
46 | }
47 |
48 | // If we're not only logging the result for this query...
49 | if (this.isLogging !== 'result') {
50 | var bindings = this.cleanBindings();
51 | checkIt('sql', title, parent, dialect, {sql: this.toSql(), bindings: this.cleanBindings()});
52 | }
53 | }
54 |
55 | return this._promise.tap(function(resp) {
56 |
57 | // If we're not only logging the sql for this query...
58 | if (then.isLogging && then.isLogging !== 'sql') {
59 | checkIt('result', title, parent, dialect, {result: resp});
60 | }
61 |
62 | }).then(onFufilled, onRejected);
63 |
64 | };
65 |
66 | var checkIt = function(type, title, parent, dialect, data) {
67 | output[parent] = output[parent] || {};
68 | output[parent][title] = output[parent][title] || {};
69 | var toCheck, target = output[parent][title][dialect] = output[parent][title][dialect] || {};
70 |
71 | try {
72 | toCheck = comparable[parent][title][dialect];
73 | } catch (e) {
74 | if (!isDev) throw e;
75 | }
76 |
77 | var items = type === 'sql' ? ['bindings', 'sql'] : ['result'];
78 |
79 | if (!isDev) {
80 |
81 | _.each(items, function(item) {
82 |
83 | var localData = toCheck[item];
84 | var newData = data[item];
85 |
86 | // Mutate the bindings arrays to not check dates.
87 | if (item === 'bindings') {
88 | parseBindingDates(newData, localData);
89 | } if (item === 'result') {
90 | parseResultDates(newData, localData);
91 | }
92 |
93 | expect(localData).to.eql(newData);
94 | });
95 |
96 |
97 | } else {
98 |
99 | _.each(items, function(item) {
100 |
101 | var newData = data[item];
102 |
103 | if (_.isObject(newData)) newData = JSON.parse(JSON.stringify(newData));
104 |
105 | target[item] = newData;
106 |
107 | });
108 |
109 | }
110 |
111 | };
112 |
113 | _.each(['Raw', 'SchemaBuilder', 'Builder'], function(Item) {
114 | Ctors[Item].prototype.logMe = logMe;
115 | Ctors[Item].prototype.then = then;
116 | });
117 |
118 | _.each(['Model', 'Collection'], function(Item) {
119 | var origFetch = Ctors[Item].prototype.fetch;
120 | Ctors[Item].prototype.fetch = function(options) {
121 | options = options || {};
122 | var dialect = this.query().client.dialect;
123 | return origFetch.apply(this, arguments).then(function(obj) {
124 | if (options.log) {
125 | if (options.debugging) debugger;
126 | var title = context.test.title;
127 | var parent = generateTitle(context.test).pop();
128 | if (!isDev && !comparable[parent]) {
129 | comparable[parent] = require(__dirname + '/../output/' + parent);
130 | }
131 | checkIt('result', title, parent, dialect, {result: JSON.parse(JSON.stringify(obj))});
132 | }
133 | return obj;
134 | });
135 | };
136 |
137 | Ctors[Item].prototype.logMe = function(val) {
138 | this.query('logMe', val);
139 | return this;
140 | };
141 | });
142 |
143 | var parseResultDates = function(newData, localData) {
144 | _.each([newData, localData], function(item) {
145 | _.each(item, function(row, i) {
146 | item[i] = _.omit(row, 'created_at', 'updated_at');
147 | });
148 | });
149 | };
150 |
151 | var parseBindingDates = function(newData, localData) {
152 | _.each(localData, function(item, index) {
153 | if (_.isDate(item)) {
154 | localData[index] = '_date_';
155 | newData[index] = '_date_';
156 | }
157 | });
158 | };
159 |
160 | var generateTitle = function(context, stack) {
161 | stack = stack || [];
162 | if (context.parent && context.parent.title.indexOf('Dialect') !== 0) {
163 | stack.push(context.parent.title);
164 | return generateTitle(context.parent, stack);
165 | }
166 | return stack;
167 | };
168 |
169 | };
170 |
171 | exports.writeResult = function() {
172 | if (!isDev) return;
173 | _.each(output, function(val, key) {
174 | fs.writeFileSync(__dirname + '/../output/' + key + '.js', 'module.exports = ' + objectdump(val) + ';');
175 | });
176 | };
177 |
178 |
--------------------------------------------------------------------------------
/dialects/sql/collection.js:
--------------------------------------------------------------------------------
1 | // Collection
2 | // ---------------
3 | (function(define) {
4 |
5 | "use strict";
6 |
7 | define(function(require, exports) {
8 |
9 | var _ = require('underscore');
10 | var when = require('when');
11 |
12 | var Sync = require('./sync').Sync;
13 | var Helpers = require('./helpers').Helpers;
14 | var EagerRelation = require('./eager').EagerRelation;
15 |
16 | var CollectionBase = require('../base/collection').CollectionBase;
17 |
18 | exports.Collection = CollectionBase.extend({
19 |
20 | // Used to define passthrough relationships - `hasOne`, `hasMany`,
21 | // `belongsTo` or `belongsToMany`, "through" a `Interim` model or collection.
22 | through: function(Interim, foreignKey, otherKey) {
23 | return this.relatedData.through(this, Interim, {throughForeignKey: foreignKey, otherKey: otherKey});
24 | },
25 |
26 | // Fetch the models for this collection, resetting the models
27 | // for the query when they arrive.
28 | fetch: function(options) {
29 | options = options || {};
30 | var collection = this, relatedData = this.relatedData;
31 | var sync = this.sync(options)
32 | .select()
33 | .tap(function(response) {
34 | if (!response || response.length === 0) {
35 | if (options.require) throw new Error('EmptyResponse');
36 | return when.reject(null);
37 | }
38 | })
39 |
40 | // Now, load all of the data onto the collection as necessary.
41 | .tap(this._handleResponse);
42 |
43 | // If the "withRelated" is specified, we also need to eager load all of the
44 | // data on the collection, as a side-effect, before we ultimately jump into the
45 | // next step of the collection. Since the `columns` are only relevant to the current
46 | // level, ensure those are omitted from the options.
47 | if (options.withRelated) {
48 | sync = sync.tap(this._handleEager(_.omit(options, 'columns')));
49 | }
50 |
51 | return sync.tap(function(response) {
52 | return collection.triggerThen('fetched', collection, response, options);
53 | })
54 | .otherwise(function(err) {
55 | if (err !== null) throw err;
56 | collection.reset([], {silent: true});
57 | })
58 | .yield(this);
59 | },
60 |
61 | // Fetches a single model from the collection, useful on related collections.
62 | fetchOne: function(options) {
63 | var model = new this.model;
64 | model._knex = this.query().clone();
65 | if (this.relatedData) model.relatedData = this.relatedData;
66 | return model.fetch(options);
67 | },
68 |
69 | // Eager loads relationships onto an already populated `Collection` instance.
70 | load: function(relations, options) {
71 | var collection = this;
72 | _.isArray(relations) || (relations = [relations]);
73 | options = _.extend({}, options, {shallow: true, withRelated: relations});
74 | return new EagerRelation(this.models, this.toJSON(options), new this.model())
75 | .fetch(options)
76 | .yield(this);
77 | },
78 |
79 | // Shortcut for creating a new model, saving, and adding to the collection.
80 | // Returns a promise which will resolve with the model added to the collection.
81 | // If the model is a relation, put the `foreignKey` and `fkValue` from the `relatedData`
82 | // hash into the inserted model. Also, if the model is a `manyToMany` relation,
83 | // automatically create the joining model upon insertion.
84 | create: function(model, options) {
85 | options = options || {};
86 |
87 | var collection = this;
88 | var relatedData = this.relatedData;
89 |
90 | model = this._prepareModel(model, options);
91 |
92 | // If we've already added things on the query chain,
93 | // these are likely intended for the model.
94 | if (this._knex) {
95 | model._knex = this._knex;
96 | this.resetQuery();
97 | }
98 |
99 | return Helpers
100 | .saveConstraints(model, relatedData)
101 | .save(null, options)
102 | .then(function() {
103 | if (relatedData && (relatedData.type === 'belongsToMany' || relatedData.isThrough())) {
104 | return collection.attach(model, options);
105 | }
106 | })
107 | .then(function() {
108 | collection.add(model, options);
109 | return model;
110 | });
111 | },
112 |
113 | // Reset the query builder, called internally
114 | // each time a query is run.
115 | resetQuery: function() {
116 | this._knex = null;
117 | return this;
118 | },
119 |
120 | // Returns an instance of the query builder.
121 | query: function() {
122 | return Helpers.query(this, _.toArray(arguments));
123 | },
124 |
125 | // Creates and returns a new `Bookshelf.Sync` instance.
126 | sync: function(options) {
127 | return new Sync(this, options);
128 | },
129 |
130 | // Handles the response data for the collection, returning from the collection's fetch call.
131 | _handleResponse: function(response) {
132 | var relatedData = this.relatedData;
133 | this.set(response, {silent: true, parse: true}).invoke('_reset');
134 | if (relatedData && relatedData.isJoined()) {
135 | relatedData.parsePivot(this.models);
136 | }
137 | },
138 |
139 | // Handle the related data loading on the collection.
140 | _handleEager: function(options) {
141 | var collection = this;
142 | return function(response) {
143 | return new EagerRelation(collection.models, response, new collection.model()).fetch(options);
144 | };
145 | }
146 |
147 | });
148 |
149 | });
150 |
151 | })(
152 | typeof define === 'function' && define.amd ? define : function (factory) { factory(require, exports); }
153 | );
--------------------------------------------------------------------------------
/test/integration/helpers/objects.js:
--------------------------------------------------------------------------------
1 |
2 | // All Models & Collections Used in the Tests
3 | // (sort of mimics a simple multi-site blogging engine)
4 | module.exports = function(Bookshelf) {
5 |
6 | var Info = Bookshelf.Model.extend({
7 | tableName: 'info'
8 | });
9 |
10 | var SiteMeta = Bookshelf.Model.extend({
11 | tableName: 'sitesmeta',
12 | site: function() {
13 | return this.belongsTo(Site);
14 | },
15 | info: function() {
16 | return this.hasOne(Info);
17 | }
18 | });
19 |
20 | var Site = Bookshelf.Model.extend({
21 | tableName: 'sites',
22 | defaults: {
23 | name: 'Your Cool Site'
24 | },
25 | authors: function() {
26 | return this.hasMany(Author);
27 | },
28 | photos: function() {
29 | return this.morphMany(Photo, 'imageable');
30 | },
31 | blogs: function() {
32 | return this.hasMany(Blog);
33 | },
34 | meta: function() {
35 | return this.hasOne(SiteMeta);
36 | },
37 | info: function() {
38 | return this.hasOne(Info).through(SiteMeta, 'meta_id');
39 | },
40 | admins: function() {
41 | return this.belongsToMany(Admin).withPivot('item');
42 | }
43 | });
44 |
45 | var Sites = Bookshelf.Collection.extend({
46 | model: Site
47 | });
48 |
49 | var Admin = Bookshelf.Model.extend({
50 | tableName: 'admins',
51 | hasTimestamps: true
52 | });
53 |
54 | // All admins for a site.
55 | var Admins = Bookshelf.Collection.extend({
56 | model: Admin
57 | });
58 |
59 | // Author of a blog post.
60 | var Author = Bookshelf.Model.extend({
61 | tableName: 'authors',
62 | site: function() {
63 | return this.belongsTo(Site);
64 | },
65 | photo: function() {
66 | return this.morphOne(Photo, 'imageable');
67 | },
68 | posts: function() {
69 | return this.belongsToMany(Post);
70 | },
71 | ownPosts: function() {
72 | return this.hasMany(Post, 'owner_id');
73 | },
74 | blogs: function() {
75 | return this.belongsToMany(Blog).through(Post, 'owner_id');
76 | }
77 | });
78 |
79 | var Authors = Bookshelf.Collection.extend({
80 | model: Author
81 | });
82 |
83 | // A blog for a site.
84 | var Blog = Bookshelf.Model.extend({
85 | tableName: 'blogs',
86 | defaults: {
87 | title: ''
88 | },
89 | site: function() {
90 | return this.belongsTo(Site);
91 | },
92 | posts: function() {
93 | return this.hasMany(Post);
94 | },
95 | validate: function(attrs) {
96 | if (!attrs.title) return 'A title is required.';
97 | },
98 | comments: function() {
99 | return this.hasMany(Comments).through(Post);
100 | }
101 | });
102 |
103 | var Blogs = Bookshelf.Collection.extend({
104 | model: Blog
105 | });
106 |
107 | // An individual post on a blog.
108 | var Post = Bookshelf.Model.extend({
109 | tableName: 'posts',
110 | defaults: {
111 | author: '',
112 | title: '',
113 | body: '',
114 | published: false
115 | },
116 | hasTimestamps: true,
117 | blog: function() {
118 | return this.belongsTo(Blog);
119 | },
120 | authors: function() {
121 | return this.belongsToMany(Author);
122 | },
123 | tags: function() {
124 | return this.belongsToMany(Tag);
125 | },
126 | comments: function() {
127 | return this.hasMany(Comment);
128 | }
129 | });
130 |
131 | var Posts = Bookshelf.Collection.extend({
132 | model: Post
133 | });
134 |
135 | var Comment = Bookshelf.Model.extend({
136 | tableName: 'comments',
137 | defaults: {
138 | email: '',
139 | post: ''
140 | },
141 | posts: function() {
142 | return this.belongsTo(Post);
143 | }
144 | });
145 |
146 | var Comments = Bookshelf.Collection.extend({
147 | model: Comment
148 | });
149 |
150 | var Tag = Bookshelf.Model.extend({
151 | tableName: 'tags',
152 | posts: function() {
153 | return this.belongsToMany(Post);
154 | }
155 | });
156 |
157 | var User = Bookshelf.Model.extend({
158 | tableName: 'users',
159 | idAttribute: 'uid',
160 | roles: function() {
161 | return this.belongsToMany(Role, 'users_roles', 'uid', 'rid');
162 | }
163 | });
164 |
165 | var Role = Bookshelf.Model.extend({
166 | tableName: 'roles',
167 | idAttribute: 'rid',
168 | users: function(){
169 | return this.belongsToMany(User, 'users_roles', 'rid', 'uid');
170 | }
171 | });
172 |
173 | var Photo = Bookshelf.Model.extend({
174 | tableName: 'photos',
175 | imageable: function() {
176 | return this.morphTo('imageable', Site, Author);
177 | }
178 | });
179 |
180 | var Photos = Bookshelf.Collection.extend({
181 | model: Photo
182 | });
183 |
184 | var Settings = Bookshelf.Model.extend({ tableName: 'Settings' });
185 |
186 | var Customer = Bookshelf.Model.extend({
187 | tableName: 'Customer',
188 | settings: function () {
189 | return this.hasOne(Settings);
190 | }
191 | });
192 |
193 | var Hostname = Bookshelf.Model.extend({
194 | tableName: 'hostnames',
195 | idAttribute: 'hostname',
196 | instance: function() {
197 | return this.belongsTo(Instance);
198 | }
199 | });
200 |
201 | var Instance = Bookshelf.Model.extend({
202 | tableName: 'instances',
203 | hostnames: function() {
204 | return this.hasMany(Hostname);
205 | }
206 | });
207 |
208 | return {
209 | Models: {
210 | Site: Site,
211 | SiteMeta: SiteMeta,
212 | Admin: Admin,
213 | Author: Author,
214 | Blog: Blog,
215 | Post: Post,
216 | Comment: Comment,
217 | Tag: Tag,
218 | User: User,
219 | Role: Role,
220 | Photo: Photo,
221 | Info: Info,
222 | Customer: Customer,
223 | Settings: Settings,
224 | Instance: Instance,
225 | Hostname: Hostname
226 | },
227 | Collections: {
228 | Sites: Sites,
229 | Admins: Admins,
230 | Posts: Posts,
231 | Blogs: Blogs,
232 | Comments: Comments,
233 | Photos: Photos,
234 | Authors: Authors
235 | }
236 | };
237 |
238 | };
--------------------------------------------------------------------------------
/test/integration/output/Collection.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'fetches the models in a collection': {
3 | mysql: {
4 | result: [{
5 | id: 1,
6 | owner_id: 1,
7 | blog_id: 1,
8 | name: 'This is a new Title!',
9 | content: 'Lorem ipsum Labore eu sed sed Excepteur enim laboris deserunt adipisicing dolore culpa aliqua cupidatat proident ea et commodo labore est adipisicing ex amet exercitation est.'
10 | },{
11 | id: 2,
12 | owner_id: 2,
13 | blog_id: 2,
14 | name: 'This is a new Title 2!',
15 | content: 'Lorem ipsum Veniam ex amet occaecat dolore in pariatur minim est exercitation deserunt Excepteur enim officia occaecat in exercitation aute et ad esse ex in in dolore amet consequat quis sed mollit et id incididunt sint dolore velit officia dolor dolore laboris dolor Duis ea ex quis deserunt anim nisi qui culpa laboris nostrud Duis anim deserunt esse laboris nulla qui in dolor voluptate aute reprehenderit amet ut et non voluptate elit irure mollit dolor consectetur nisi adipisicing commodo et mollit dolore incididunt cupidatat nulla ut irure deserunt non officia laboris fugiat ut pariatur ut non aliqua eiusmod dolor et nostrud minim elit occaecat commodo consectetur cillum elit laboris mollit dolore amet id qui eiusmod nulla elit eiusmod est ad aliqua aute enim ut aliquip ex in Ut nisi sint exercitation est mollit veniam cupidatat adipisicing occaecat dolor irure in aute aliqua ullamco.'
16 | },{
17 | id: 3,
18 | owner_id: 2,
19 | blog_id: 1,
20 | name: 'This is a new Title 3!',
21 | content: 'Lorem ipsum Reprehenderit esse esse consectetur aliquip magna.'
22 | },{
23 | id: 4,
24 | owner_id: 3,
25 | blog_id: 3,
26 | name: 'This is a new Title 4!',
27 | content: 'Lorem ipsum Anim sed eu sint aute.'
28 | },{
29 | id: 5,
30 | owner_id: 4,
31 | blog_id: 4,
32 | name: 'This is a new Title 5!',
33 | content: 'Lorem ipsum Commodo consectetur eu ea amet laborum nulla eiusmod minim veniam ullamco nostrud sed mollit consectetur veniam mollit Excepteur quis cupidatat.'
34 | }]
35 | },
36 | postgresql: {
37 | result: [{
38 | id: 1,
39 | owner_id: 1,
40 | blog_id: 1,
41 | name: 'This is a new Title!',
42 | content: 'Lorem ipsum Labore eu sed sed Excepteur enim laboris deserunt adipisicing dolore culpa aliqua cupidatat proident ea et commodo labore est adipisicing ex amet exercitation est.'
43 | },{
44 | id: 2,
45 | owner_id: 2,
46 | blog_id: 2,
47 | name: 'This is a new Title 2!',
48 | content: 'Lorem ipsum Veniam ex amet occaecat dolore in pariatur minim est exercitation deserunt Excepteur enim officia occaecat in exercitation aute et ad esse ex in in dolore amet consequat quis sed mollit et id incididunt sint dolore velit officia dolor dolore laboris dolor Duis ea ex quis deserunt anim nisi qui culpa laboris nostrud Duis anim deserunt esse laboris nulla qui in dolor voluptate aute reprehenderit amet ut et non voluptate elit irure mollit dolor consectetur nisi adipisicing commodo et mollit dolore incididunt cupidatat nulla ut irure deserunt non officia laboris fugiat ut pariatur ut non aliqua eiusmod dolor et nostrud minim elit occaecat commodo consectetur cillum elit laboris mollit dolore amet id qui eiusmod nulla elit eiusmod est ad aliqua aute enim ut aliquip ex in Ut nisi sint exercitation est mollit veniam cupidatat adipisicing occaecat dolor irure in aute aliqua ullamco.'
49 | },{
50 | id: 3,
51 | owner_id: 2,
52 | blog_id: 1,
53 | name: 'This is a new Title 3!',
54 | content: 'Lorem ipsum Reprehenderit esse esse consectetur aliquip magna.'
55 | },{
56 | id: 4,
57 | owner_id: 3,
58 | blog_id: 3,
59 | name: 'This is a new Title 4!',
60 | content: 'Lorem ipsum Anim sed eu sint aute.'
61 | },{
62 | id: 5,
63 | owner_id: 4,
64 | blog_id: 4,
65 | name: 'This is a new Title 5!',
66 | content: 'Lorem ipsum Commodo consectetur eu ea amet laborum nulla eiusmod minim veniam ullamco nostrud sed mollit consectetur veniam mollit Excepteur quis cupidatat.'
67 | }]
68 | },
69 | sqlite3: {
70 | result: [{
71 | id: 1,
72 | owner_id: 1,
73 | blog_id: 1,
74 | name: 'This is a new Title!',
75 | content: 'Lorem ipsum Labore eu sed sed Excepteur enim laboris deserunt adipisicing dolore culpa aliqua cupidatat proident ea et commodo labore est adipisicing ex amet exercitation est.'
76 | },{
77 | id: 2,
78 | owner_id: 2,
79 | blog_id: 2,
80 | name: 'This is a new Title 2!',
81 | content: 'Lorem ipsum Veniam ex amet occaecat dolore in pariatur minim est exercitation deserunt Excepteur enim officia occaecat in exercitation aute et ad esse ex in in dolore amet consequat quis sed mollit et id incididunt sint dolore velit officia dolor dolore laboris dolor Duis ea ex quis deserunt anim nisi qui culpa laboris nostrud Duis anim deserunt esse laboris nulla qui in dolor voluptate aute reprehenderit amet ut et non voluptate elit irure mollit dolor consectetur nisi adipisicing commodo et mollit dolore incididunt cupidatat nulla ut irure deserunt non officia laboris fugiat ut pariatur ut non aliqua eiusmod dolor et nostrud minim elit occaecat commodo consectetur cillum elit laboris mollit dolore amet id qui eiusmod nulla elit eiusmod est ad aliqua aute enim ut aliquip ex in Ut nisi sint exercitation est mollit veniam cupidatat adipisicing occaecat dolor irure in aute aliqua ullamco.'
82 | },{
83 | id: 3,
84 | owner_id: 2,
85 | blog_id: 1,
86 | name: 'This is a new Title 3!',
87 | content: 'Lorem ipsum Reprehenderit esse esse consectetur aliquip magna.'
88 | },{
89 | id: 4,
90 | owner_id: 3,
91 | blog_id: 3,
92 | name: 'This is a new Title 4!',
93 | content: 'Lorem ipsum Anim sed eu sint aute.'
94 | },{
95 | id: 5,
96 | owner_id: 4,
97 | blog_id: 4,
98 | name: 'This is a new Title 5!',
99 | content: 'Lorem ipsum Commodo consectetur eu ea amet laborum nulla eiusmod minim veniam ullamco nostrud sed mollit consectetur veniam mollit Excepteur quis cupidatat.'
100 | }]
101 | }
102 | }
103 | };
--------------------------------------------------------------------------------
/docs/dialects/base/sync.html:
--------------------------------------------------------------------------------
1 | dialects/base/sync
dialects/base/sync.js
Base Sync
(function(define){
2 |
3 | "use strict";
An example "sync" object which is extended
4 | by dialect-specific sync implementations,
5 | making Bookshelf effectively a data store
6 | agnostic "Data Mapper".
If there are no arguments, return the current object's
26 | query builder (or create and return a new one). If there are arguments,
27 | call the query builder with the first argument, applying the rest.
28 | If the first argument is an object, assume the keys are query builder
29 | methods, and the values are the arguments for the query.
(c) 2013 Tim Griesser
2 | Bookshelf may be freely distributed under the MIT license.
3 | For all details and documentation:
4 | http://bookshelfjs.org
5 |
Finally, the Events, which we've supplemented with a triggerThen
14 | method to allow for asynchronous event handling via promises. We also
15 | mix this into the prototypes of the main objects in the library.
Constructor for a new Bookshelf object, it accepts
16 | an active knex instance and initializes the appropriate
17 | Model and Collection constructors for use in the current instance.
varBookshelf=function(knex){
Allows you to construct the library with either Bookshelf(opts)
18 | or new Bookshelf(opts).
The Model constructor is referenced as a property on the Bookshelf instance,
24 | mixing in the correct builder method, as well as the relation method,
25 | passing in the correct Model & Collection constructors for later reference.
The collection also references the correct Model, specified above, for creating
33 | new Model instances in the collection. We also extend with the correct builder /
34 | knex combo.
Grab a reference to the knex instance passed (or created) in this constructor,
44 | for convenience.
this.knex=knex;
45 | };
A Bookshelf instance may be used as a top-level pub-sub bus, as it mixes in the
46 | Events object. It also contains the version number, and a Transaction method
47 | referencing the correct version of knex passed into the object.
_.extend(Bookshelf.prototype,Events,{
Keep in sync with package.json.
VERSION:'0.5.7',
Helper method to wrap a series of Bookshelf actions in a knex transaction block;
Provides a nice, tested, standardized way of adding plugins to a Bookshelf instance,
50 | injecting the current instance into the plugin, which should be a module.exports.
The forge function properly instantiates a new Model or Collection
58 | without needing the new operator... to make object creation cleaner
59 | and more chainable.
Sync is the dispatcher for any database queries,
9 | taking the "syncing" model or collection being queried, along with
10 | a hash of options that are used in the various query methods.
11 | If the transacting option is set, the query is assumed to be
12 | part of a transaction, and this information is passed along to Knex.
Runs a select query on the database, adding any necessary relational
28 | constraints, resetting the query when complete. If there are results and
29 | eager loaded relations, those are fetched and returned on the model before
30 | the promise is resolved. Any success handler passed in the
31 | options will be called - used by both models & collections.
Events