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

в 4:06, , рубрики: backbone.js, mongodb, 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.

Автор: amiqrious

Источник

Поделиться

* - обязательные к заполнению поля