Приветствую, уважаемые читатели. Хочу поделиться с вами своим опытом использования backbone.js под node.js. Ранее я активно поработал с backbone.js на клиенте, и эта библиотека оказалась крайне удобной для структурирования кода.
Сервис без работы с какой либо базой данных — не сервис. В моем случае, в качестве СУБД была выбрана mongodb. Я посмотрел существующие решения ORM для mongodb, и мне показалось более удобным пользоваться знакомыми инструментами, тем более они же будут использоваться и на клиенте, поэтому решено было попробовать применить для моделей класс Backbone.Model и заодно проверить, как все это можно кастомизировать под mongodb.
Итак, задача. Есть некий сервис, использующий базу mongodb. Необходимо иметь возможность создавать новые объекты и сохранять их в базу, из определенной коллекции доставать необходимые объекты. После изменения объекта должна быть возможность сохранять и удалять его из базы.
Backbone.js предоставляет нам 2 базовых объекта — модель и коллекцию. Мы напишем прослойку, от которой будем в дальнейшем наследовать все наши будущие классы. Сразу оговорюсь, что код соединения с базой данных и получения коллекции мы оставим в стороне и будем считать, что с ним все хорошо. Также пренебрегаем обработкой ошибок. Практически везде я использую Deferred. Поэтому, кому неизвестно, что это за зверь, можно почитать про него тут в подробностях. Запросы к mongodb делаются через этот node.js модуль — что является совсем не принципиальным. Код местами упрощен, поэтому если вы будете использовать его в своем проекте, кое-что может быть не совсем верно.
Базовая модель
Нам понадобится каким-то образом доставать одиночную модель из базы, думаю, этот метод хорошо вписывается в статическую часть класса:
var BaseModel = Backbone.Model.extend({
idAttribute: "_id",
},{
db_collection: null, //какую коллекцию использует данная модель
fetchFromDb: function(filter, options){
var deferred = new Deferred();
if (typeof options === 'undefined')
options = {};
db.getCollection(this.db_collection, function(err, collection){
collection.find(filter).toArray(function(err, items) {
ret = new this(_.defaults(items[0], options));
}
deferred.resolve(ret);
}.bind(this));
);
return deferred.promise;
}
});
Из кода также видно, что мы переопределили базовое поле класса Backbone.Model, которое определяет название ключевого поля в списке полей объекта, а в статическом поле db_collection класса мы определили коллекцию, к которой объект будет относиться.
После этого, если мы, например, определим класс пользователя:
var User = BaseModel.extend({},{ db_collection:"users"});
мы можем получить конкретного пользователя, например, так:
User.fetchFromDb({_id:1});
Теперь нужно научиться сохранять созданную модель. Для этого в backbone.js предусмотрен специальный метод sync, сигнатура которого выглядит следующим образом:
function sync(method, model, options)
где:
- method — метод сохранения, в зависимости от ситуации может возвращаться:
- create — создаем новую модель в базе;
- update — сохраняем данные существующей модели в базе;
- read — обновляем существующую модель из базы;
- delete — удаляем объект из базы.
- model — наш объект для сохранения;
- options – то, что мы передали дополнительными параметрами в момент сохранения.
sync: function(method, model, options){
var fltr = {};
var deferred = new Deferred();
db.getCollection(this.constructor.db_collection, collectionReady.bind(this));
return deferred.promise;
function collectionReady(err, collection){
function done(err, result){
if (options.success)
options.success(result); //таким образом мы сообщаем backbone.js об успешно выполненной операции
deferred.resolve({context: this, result: true});
}
fltr[this.idAttribute] = this.get(this.idAttribute);
switch (method){
case "update":
collection.update(
fltr,
this.toJSON(),
{multi:false, upsert: true},
done.bind(this)
);
break;
case "create":
collection.insert(
this.toJSON(),
{safe:true},
done.bind(this)
);
break;
case "read":
collection.find(fltr).toArray(function(err, items) {
done.call(this, false, items[0]);
}
}.bind(this));
break;
case "delete":
collection.findAndModify(fltr, [], {}, {remove:true}, function(err, items) {
deferred.resolve();
});
}
}
},
Тут тоже ничего сложного. На каждый конкретный метод изменения модели мы вешаем обработчики, умеющие делать это действие в самой базе.
Теперь, после создания объекта user, например, так:
var user = new User({name:"Danil"});
мы можем спокойно его сохранить:
user.save()
а также удалить после сохранения:
user.delete()
Базовая коллекция
Работать с одиночными моделями достаточно скучно, поэтому реализуем коллекции, тем-более базовый класс в backbone.js для этого есть. По аналогии с моделью переопределим базовый класс коллекции. Все наши будущие коллекции будут наследоваться от него. Для начала напишем извлечение списка «сырых данных» из базы:
var BaseCollection = Backbone.Collection.extend({
},{
db_collection: null,
__fetchCollection:function(filter, collection){
var deferred = new Deferred();
function collectionReady(err, collection){
collection.find(filter).toArray(function(err, items) {
deferred.resolve(items);
}.bind(this));
}
db.getCollection(collection_db, collectionReady, this);
return deferred.promise;
}
});
Здесь мы создаем статический метод класса, который позволит нам извлекать список «сырых данных» из базы, а также добавляем статическое поле db_collection, которое наследники будут переопределять, указывая, к какой коллекции в mongodb относится этот класс.
Осталось добавить инициализацию нужных моделей, добавляем еще один статический метод в наш базовый класс коллекции:
fetchFromDb:function(filter){
var deferred = new Deferred();
this.__fetchCollection(filter, this.db_collection).then(
function(models){
deferred.resolve(new this(models));
}.bind(this)
);
return deferred.promise;
},
Здесь мы просто получаем «сырые данные» и инициализируем модели, связанные с этой коллекцией, данными из базы. Конечно, само название коллекции в mongodb мы можем получать от связанной модели, но будем считать это упрощением.
Теперь, определив коллекцию пользователей, мы можем делать запросы в базу, получая объекты с пользователями:
var Users = BaseCollection.extend({model:User}, {db_collection: "users"});
Users.fetchFromDb({name:"Danil"}).then(function(users){
_.each(users,function(user){console.log(user.id)});
});
В итоге мы получили весь необходимый минимум для работы с mongodb и дальше можем пользоваться другими возможностями, которые нам дает backbone.js.
Автор: amiqrious