Используем backbone.js под node.js

Приветствую, уважаемые читатели Хабрахабра. Хочу поделиться с вами своим опытом использования 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.

ссылка на оригинал статьи http://habrahabr.ru/post/158569/

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *