Backbone.ioBindのSyncで起こっていること(Colleciton編)


ここまでにBackbone.jsのModelとCollection、またBackbone.ioBindのModelについてSyncがどのような値を通信しているか見て来ました。千秋楽としてBackbone.ioBindのCollectionのSyncについて見ていきたいと思います。

感想から言うと、「標準Syncのルールと違うところがあるので戸惑う」って感じですかねー。いかんせんSocket.ioを使っているので、標準のように通信先は一つ(Collection.url)でidをパラメーターとして受け取るってことができないんでしょうね。たぶん。

 

違うところ その1

Collectionのurl属性の他に、ModelにもurlRootを指定する必要がある。
標準ではCollectionのurlさえ指定してあれば、Collection内のモデル1つ1つもSyncすることができました。ioBindはSocket.ioの仕様上、CollectionはCollectionのurl、ModelはModelのurlRootと分けて通信する必要がありそうです。




違うところ その2

Collection.createはCollection.urlに通信するのではなく、Model.urlRootに対して通信する。
ModelのurlRootを指定するとCollection.urlより優先されます。よって、標準のSyncではCollectionのcreateに処理を書いていたところを、Modelのcreateに書き換えなければなりません。

あとは、まー、ほぼ標準と同じような感覚で使える感じでしょうか。サーバー側ではidをパラーメーターとして受け取れないので、そこは標準のSyncと違いますが。

以下、テストした時のソースコードを掲載します。
こちらのソースをもとに差し替えて下さい。

・サーバー:lib/socket.js

module.exports.listen = function(server){
var io = require('socket.io').listen(server);

// ioBind、Collectionのテスト
io.sockets.on('connection', function (socket) {
console.log('connected');

// Read(Collection)
socket.on('foo2:read', function (data, callback) {
console.log('read: ', data);
// -> read: []
// ここまでは標準のSyncと同じ動作。

if(callback) callback(null, [
{id: 1, data: 'before'},
{id: 2, data: 'before'},
{id: 3, data: 'before'}
]);
});

// Read(Model)
socket.on('foo:read', function (data, callback) {
console.log('read: ', data);
// -> read: { id: 1, data: 'before', hoge: 'hoge' }
// 標準のSyncのreadは、Collectionのurl属性+idを通信先とするが、
// Socket.ioなのでイベント名からIDを取り出せない。
// そのためModelのurlRootに対して通信しなければならないのが
// 標準と違うところ。
if(callback) callback(null,
{data: 'after', data2: 'add'}
);
});

// Create
socket.on('foo:create', function (data, callback) {
console.log('create: ',data);
// -> create: { data: 'create', hoge: 'hoge' }
// 標準のSyncのCreateは、Collectionのurl属性を通信先とするが、
// こちらはModelのurlRootを通信先とするのが違うところ。
if(callback) callback(null, {id: 4});
});

// Update
socket.on('foo:update', function (data, callback) {
console.log('update :',data);
// -> update : { data: 'update', hoge: 'hoge', id: 4 }
// 同じくこちらもModelのurlRootを通信先とする。
if(callback) callback(null, {});
});

socket.on('foo:delete', function (data, callback) {
console.log('delete: ',data);
// -> delete: { hoge: 'hoge', id: 0, foo: 'foo', bar: 'bar' }
// 同じくこちらもModelのurlRootを通信先とする。
if(callback) callback(null, {});
});
});

return io;
};


・クライアント:client.js

(function() {
var socket = io.connect(null, {port: locals.port});

// ioBindのCollectionのSyncについてテスト
var TestModel = Backbone.Model.extend({
// 標準のSyncとは違いModelにもurlRootを指定する必要がある。
urlRoot: '/foo',
defaults:{
hoge: 'hoge'
},
// socketはModelもCollectionも指定する必要がある。
socket: socket
});

var TestCollection = Backbone.Collection.extend({
model: TestModel,
url: '/foo2',
socket: socket,
parse: function(response){
console.log('parse');
// 標準と同様parseも活用できる。
return response;
}
});

var testCollection = new TestCollection();

// 例によって非同期を順番に行うため、async.jsを活用する。
async.series([
function(callback){

// GET(read)
testCollection.fetch({
success: function(collection, response, options){
console.log('-----Fetch: Collection-----');
console.log('success: ', collection.toJSON());
// サーバーからのデータがCollection内に保存されている。
// -> success: [
// {id: 1, data: 'before', hoge: "hoge"},
// {id: 2, data: 'before', hoge: "hoge"},
// {id: 3, data: 'before', hoge: "hoge"}
// ]

// asyncの次の関数へ移る。
callback(null);
},
error: function(collection, response, options){
// エラーの場合(ステータスコードが200以外の場合?)
}
});
},

function(callback){
// GET(read)
// Collection内のモデル1つについてfetchする場合
testCollection.at(0).fetch({
success: function(model, response, options){
console.log('---Fetch: Model---');
console.log('success: ', model.toJSON());
// サーバーからの結果はマージされる。
// -> success: Object {id: 1, data: "after",
// hoge: "hoge", data2: "add"}

callback(null);
}
});
},

function(callback){
// POST(create)
// Collectionにmodelを追加しつつ、サーバー側にも保存の指示を出す。
testCollection.create({
data: 'create'
},{
success: function(model, response, options){
console.log('---Create: Model---');
console.log('success: ', model.toJSON());
// -> success: Object {data: "create",
// hoge: "hoge", id: 4}

callback(null);
}
});
},

function(callback){
// PUT(update)
// 先のCreateでidを受け取っているので、
// save()するとPUT(update)メソッドで通信する
testCollection.at(3).save({
data: 'update'
},{
success: function(model, response, options){
console.log('---Update: Model---');
console.log('success: ', model.toJSON());
// -> success: Object {data: "update",
// hoge: "hoge", id: 4}

callback(null);
}
});
},

function(callback){
// DELETE(delete)
testCollection.at(0).destroy({
success: function(model, response, options){
console.log('---Delete: Model---');
console.log('success: ', model.toJSON());
// -> success: Object {id: 1, data: "after",
// hoge: "hoge", data2: "add"}

callback(null);
}
});
},

function(callback){
// modelをdestroyするとサーバー側へ削除命令と
// collection内の該当のmodelを削除する。
console.log(testCollection.toJSON());
// -> [
// {id: 2, data: 'before', hoge: "hoge"},
// {id: 3, data: 'before', hoge: "hoge"},
// {id: 4, data: 'update', hoge: "hoge"}
// ]
callback(null);
},

function(callback){
testCollection.fetch({
reset: true,
success: function(collection, response, options){
console.log('----Reset: Collection----');
console.log('success: ', collection.toJSON());
// resetオプションをつけると、Collectionが刷新される。
// -> success: [
// {id: 1, data: 'before', hoge: "hoge"},
// {id: 2, data: 'before', hoge: "hoge"},
// {id: 3, data: 'before', hoge: "hoge"}
// ]

callback(null);
}
});
}
],

// asyncの最後に実行される関数。
function(err, results){
if(err) console.log(err);
else console.log('Completed!');
});

})();


また、socket.ioを使っているので、あるクライアントが変更した内容を、他のクライアントにも通信し同期させることができます。こちらはBackbone.ioBind(Github)を見て頂ければ良いと思います。