Titanium Mobile + Alloy における Backbone.js の扱われ方


# 前置き

Titanium Mobile + Alloy では、ModelをBackbone.jsを使って書きます。
などと言われているようだが、実際にはBackbone.jsそのままというわけではないようだ。
そんなわけで、ちょっと調べてみた。

ちなみにここでは、Todoリストアプリっぽいものを書いていると思ってください。

# モデルの書き方

Titanium Mobile + Alloy の新しいプロジェクトを作成すると、appフォルダ以下に、controllers, models, views など、いかにもMVCなんです的なフォルダ構成になっている。

![スクリーンショット 0025-07-30 22.58.24.png](https://qiita-image-store.s3.amazonaws.com/0/20722/93e25cb6-e8e6-bb69-f604-480d92fa4d5b.png "スクリーンショット 0025-07-30 22.58.24.png")


この中の、modelsフォルダに「なんちゃら.js」というファイル名でモデルを書いてやると、
Titanium Mobileが自動的にBackbone.jsを使ってくれる。
**強制である。**
使わないという選択肢は（おそらく）存在しない。

ためしに、ネットでサンプルを読みつつ、Todoリストを扱うモデルを書いてみた。

```javascript:app/models/todo.js
exports.definition = {
    config: {
        "columns": {
            "description": "TEXT",
            "limitTime": "TEXT",
            "done": "INTEGER"
        },
        "defaults": {
            "description": "-",
            "limitTime": "",
            "done": "false"
        },
        "adapter": {
            "type": "sql",
            "collection_name": "todos",
            "idAttribute": "id"
        }
    },
    extendModel: function(Model) {		
        _.extend(Model.prototype, {
            // Extend Backbone.Model
        });
        return Model;
    },
    extendCollection: function(Collection) {        
        _.extend(Collection.prototype, {
            // extended functions and properties go here
        });
        return Collection;
    }
}
```

とりあえず、Backbone.jsとUnderscore.jsは、とくにrequire()とかしなくても自動的に読み込んでくれる。

# モデルはどのように変換されているのか？

ここで疑問。
exports.definition って何？
Backbone.jsにはないよね？

気になったので、どんな風に変換されるのか追ってみた。
iPhoneシミュレータ用に"Debug"あるいは"Run"すると、

（ホームディレクトリ）/Library/Application Support/iPhone Simulator/（バージョン）/Applications/

ディレクトリ以下に、数字とアルファベットが羅列された名前のディレクトリが出来る。


![スクリーンショット 0025-07-30 23.13.01.png](https://qiita-image-store.s3.amazonaws.com/0/20722/a995fdb4-f33f-fe73-1566-58a8caeea249.png "スクリーンショット 0025-07-30 23.13.01.png")


その中のどこかのフォルダに（この場合は）Todo.appというファイルが見つかる。
で、Todo.appを右クリック->「パッケージの内容を表示」で、中身を見てみる。


![スクリーンショット 0025-07-30 23.16.48.png](https://qiita-image-store.s3.amazonaws.com/0/20722/1a8a36e2-c738-504e-15f1-66dde641f92f.png "スクリーンショット 0025-07-30 23.16.48.png")


パッケージの中で重要なのは、alloyフォルダっぽい。
alloyフォルダ以下には、controllers, models フォルダがある。

![スクリーンショット 0025-07-30 23.20.56.png](https://qiita-image-store.s3.amazonaws.com/0/20722/1ab08e6c-bcf2-a837-785f-7a326452579f.png "スクリーンショット 0025-07-30 23.20.56.png")


どうやら、alloy/modelsフォルダにあるTodo.jsというのが、上で書いたtodo.jsが変換されたもののようだ。

その中身は以下。

```javascript:alloy/models/Todo.js
exports.definition = {
    config: {
        columns: {
            description: "TEXT",
            limitTime: "TEXT",
            done: "INTEGER"
        },
        defaults: {
            description: "-",
            limitTime: "-",
            done: "false"
        },
        adapter: {
            type: "sql",
            collection_name: "todos",
            idAttribute: "id"
        }
    },
    extendModel: function(Model) {
        _.extend(Model.prototype, {});
        return Model;
    },
    extendCollection: function(Collection) {
        _.extend(Collection.prototype, {});
        return Collection;
    }
};

var Alloy = require("alloy"), _ = require("alloy/underscore")._, model, collection;
model = Alloy.M("todo", exports.definition, []);
collection = Alloy.C("todo", exports.definition, model);
exports.Model = model;
exports.Collection = collection;
```

最後の５行以外は書いたままなので、最後だけ見ていく。

```javascript:alloy/models/Todo.js
var Alloy = require("alloy"), _ = require("alloy/underscore")._, model, collection;
model = Alloy.M("todo", exports.definition, []);
collection = Alloy.C("todo", exports.definition, model);
exports.Model = model;
exports.Collection = collection;
```

１行目は、alloyやunderscoreを読み込んでいるだけなので無視。
４〜５行目は、exportsしてるだけなので無視。
２〜３行目で、exports.definitionで定義したものを、Alloy.M()やらAlloy.C()という名前の関数に渡しているのが怪しい。

Alloyなんちゃらは、パッケージを開いたルートディレクトリにあるalloy.jsで定義されている。
M()とC()の定義だけ抜き出してみる。

>どうでもいいが、MはModel、CはCollectionの頭文字と思われる。

```javascript:alloy.js
exports.M = function(name, modelDesc, migrations) {
    var config = modelDesc.config;
    var type = (config.adapter ? config.adapter.type : null) || "localDefault";
    "localDefault" === type && (type = "sql");
    var adapter = require("alloy/sync/" + type);
    var extendObj = {
        defaults: config.defaults,
        sync: function(method, model, opts) {
            var config = model.config || {};
            var type = (config.adapter ? config.adapter.type : null) || "localDefault";
            "localDefault" === type && (type = "sql");
            require("alloy/sync/" + type).sync(method, model, opts);
        }
    };
    var extendClass = {};
    migrations && (extendClass.migrations = migrations);
    _.isFunction(adapter.beforeModelCreate) && (config = adapter.beforeModelCreate(config, name) || config);
    var Model = Backbone.Model.extend(extendObj, extendClass);
    Model.prototype.config = config;
    _.isFunction(modelDesc.extendModel) && (Model = modelDesc.extendModel(Model) || Model);
    _.isFunction(adapter.afterModelCreate) && adapter.afterModelCreate(Model, name);
    return Model;
};

exports.C = function(name, modelDesc, model) {
    var extendObj = {
        model: model,
        sync: function(method, model, opts) {
            var config = model.config || {};
            var type = (config.adapter ? config.adapter.type : null) || "localDefault";
            "localDefault" === type && (type = "sql");
            require("alloy/sync/" + type).sync(method, model, opts);
        }
    };
    var Collection = Backbone.Collection.extend(extendObj);
    var config = Collection.prototype.config = model.prototype.config;
    var type = (config.adapter ? config.adapter.type : null) || "localDefault";
    var adapter = require("alloy/sync/" + type);
    _.isFunction(adapter.afterCollectionCreate) && adapter.afterCollectionCreate(Collection);
    _.isFunction(modelDesc.extendCollection) && (Collection = modelDesc.extendCollection(Collection) || Collection);
    return Collection;
};
```

さきに、M()のほうを見ていく。
２番目の引数modelDescに、exports.definitionが入っているのを頭に入れておく。
そうすると、２〜３行目の

```javascript:
    var config = modelDesc.config;
    var type = (config.adapter ? config.adapter.type : null) || "localDefault";
```

は、変数configにexports.definition.configを代入し、
変数typeにexports.definition.adapterを元に求めた値を代入していることになる。

次。
alloy.jsの７行目あたり。

```javascript:
    var extendObj = {
        defaults: config.defaults,
```

として、extendObj.defaultsにconfig.defaults(=exports.definition.config.defaults)を代入している。
結構バラバラにされているようです　＜　exports.definition

その後も紆余曲折を経ながら最終的に、１８〜１９行目

```javascript:
    var Model = Backbone.Model.extend(extendObj, extendClass);
    Model.prototype.config = config;
```

で、Backbone.Modelに結びつけられている。
結局、extendObj.defautlsに結びつけられたexports.definition.defaultsが
Backbone.Model.extend()に渡され、また、
exports.definition.configが、Model.prototype.configに代入されている。
さらに、extendObj.sync()の中では、config.adapterから求めたtypeが使用されている（typeはシリアライズのとき"sql"を使うのか、"localStorage"を使うのか、みたいなことを表している）。

ということになる。

でこれから何がわかるかというと…Backbone.jsを勉強するべき、ということになる（？）

C()のほうは以下同様で略。

# おまけ

それだけでは何なので、ちょっとだけ。

## config.defaults

exports.definitionで定義されたconfig.defaultsが、Backbone.Model.Extend()に渡されるので、

```javascript:
exports.definition = {
    config: {
        "defaults": {
            "description": "-",
            "limitTime": "",
            "done": "false"
        }
    }
}
```

と書いておけば、

```javascript:
var Model = Backbone.Model.extend({
    "defaults": {
        "description": "-",
        "limitTime": "",
        "done": "false"
    }
});
```

と書いたのと同じことになる。

## extendModel

extendModelプロパティに、以下のような記述をすることでモデルを拡張出来る。

```javascript:
exports.definition = {
    extendModel: function(Model) {      
        _.extend(Model.prototype, {
            // Implement the validate method
            validate: function (attrs) {
    	        for (var key in attrs) {
                    var value = attrs[key];
                    if (key === "description") {
                        if (value.length <= 0) {
                            return "Error: No description!";
                        }	
                    }	
                }
            }
        });
        return Model;
    }
}
```

## extendCollection

Backbone.jsを使う場合、Backbone.Modelを拡張したオブジェクトを、Backbone.Collectionを拡張したオブジェクトにいくつもいれておくことがある（ようだ）が、
exports.definitionに、extendModelとextendCollectionを同時に記述することで、
ModelとCollectionを一度に記述できる（ような気がする…）。

>すみません。Backbone.jsは勉強不足なので…。

```javascript:
exports.definition = {
    extendModel: function(Model) {      
        _.extend(Model.prototype, {
            // こっちはBackbone.Model関係
        });
    },
    extendCollection: function(Collection) {        
        _.extend(Collection.prototype, {
            // こっちはBackbone.Collection関係
        });
        return Collection;
    }
}
```