Google Apps Scriptでニフクラ mobile backendのSDKを作った話

これは[Google Apps Script Advent Calendar 2018 - Qiita](https://qiita.com/advent-calendar/2018/gas)4日目の記事です。

[ニフクラ mobile backend](https://mbaas.nifcloud.com)はいわゆるmBaaS（mobile Backend as a Service）と呼ばれるサービスで、モバイルアプリのバックエンドサービスを提供します。例えばデータベース、認証、ファイルストレージ、プッシュ通知といった機能になります。元々SDKはJavaScript向けにも提供していますが、Google Apps Script向けは一から作成しています（非公式です）。そんなGAS SDKを作った際のTipsを紹介します。

## 既存のJavaScript SDKは使えない

GASはあくまでもJavaScript風に書ける言語なので、素のJavaScriptはそのままでは動きません。NCMBのJavaScript SDKも動かなかったので、一から作り直しています。機能的には限定的ではあります。

## ライブラリはライブラリ名で公開される

GASは通常のJavaScriptのように利用する変数名が固定化されません。利用者がどうライブラリの識別子を設定するかによって変数が変わります。GAS SDKについてはNCMBを推奨していますが、実際のところは分かりません。そのため若干ドキュメントが書きづらいです。

```js
var ncmb = NCMB.init(application_key, client_key);
```

## constの挙動が違う

例えばこんなコードが実行時のエラーになりません。

```js
function myFunction() {
  const Item = null;
  Item = 0;
  Logger.log(Item);
}
```

しかもログには null と出ます。というのもあって、あまりモダンな書き方はしない方がいいように感じました。SDKもすべてvarで変数宣言しています。

## ネットワークが同期

通常のJavaScriptはネットワーク処理が非同期ですが、GASは同期です。場合によってはこちらの方が書きやすいと感じますが、慣れないと逆に不便に感じてしまったりします。

```js
var response = UrlFetchApp.fetch(url, params);
var contents = response.getContentText();
```

## 独自のユーティリティライブラリ

GASではimportやrequireが使えないので crypt などはどうしたものかと思っていたのですが、[Utilitiesという便利なライブラリ群](https://developers.google.com/apps-script/reference/utilities/utilities)があります。 `Utilities.computeHmacSha256Signature` で署名を作ってくれたり、 `Utilities.base64Encode` でBase64化も行えます。GAS SDKは `Utilities` がなかったら実現しなかったでしょう。

## テストはCode.gsに書く

これは行儀はよくないですが、デバッグ時にブレークポイントを設定している際、別なファイルでテストを書いて実行するよりも Code.gs 上にテストコードがあった方がデバッグしやすいです。検証が終わった後、テスト用ファイルにコードを移動するのが簡単です。

## 利用用途

NCMBでは管理画面を提供していますが、それでもExcelなどに比べると機能が見劣りします。そこでGAS SDKを使うと管理画面で行うデータメンテナンスをGoogleスプレッドシート上で行えるようになります。ユーザ認証して、スプレッドシートのデータを順番に処理します。

```js
// データの登録および更新処理
var rowIndex = 2;
while (true) {
  // クラスのインスタンスを用意
  var item = new Item;
  // すべての行が空であれば、処理終了とします
  var range = sheet.getRange(rowIndex, 1, 1, fields.length);
  if (range.isBlank()) break;
  // 各行を処理します
  for (var i = 0; i < fields.length; i += 1) {
    // 表示されている値と、そこで使われている計算式を取ります
    var value = sheet.getRange(rowIndex, i + 1).getValue();
    var formula = sheet.getRange(rowIndex, i + 1).getFormula();
    // 値がない場合には処理対象外です
    if (value != '') {
      // 計算式がある場合
      if (formula != '') {
        // A列（objectId）が処理対象か否か
        var pClassName = formula.replace(/=(.*?)!A[0-9]+.*$/, "$1");
        // objectIdが計算式にある場合はポインターとします
        if (pClassName !== formula) {
          value = {
            "__type":"Pointer",
            "className": pClassName,
            "objectId": value
          };
        }
      }
      // 値をセットします
      item.set(fields[i], value);
    }
  }
  // ACLは決め打ちです。ワークフローに合わせて変更してください
  item.set('acl', {
    "*": {
      "read": true, // 全員に読み込み権限
    },
    "role:Admin":{  // Adminというロールに書き込み権限
      "read": true, "write": true
    }
  });
  
  // オブジェクトIDの有無によって登録/更新を変えています
  var error = item.get('objectId') ? item.update() : item.save();
  // エラー判定
  if (!error) {
    // エラーがなければ行のA列の値をobjectIdにします
    sheet.getRange(rowIndex, 1).setValue(item.get('objectId'));
    // ここで処理されたobjectIdは削除対象外のデータとします
    objectIds.push(item.get('objectId'));
  }
  rowIndex += 1;
}
```

一番左側がキー（objectId）固定としていて、その値の有無によって新規追加と更新処理を分けています。

データの追加と更新処理が終わったら、次は不要なデータを削除します。NCMB側のデータに存在しないのに、スプレッドシートにあるデータは不要といった判断です。つまりスプレッドシート側を常に真としています。

```js
// 現在クラスにある全データ（最大1000件）を取得します
var items = Item.limit(1000).fetchAll();
for (var i = 0; i < items.length; i += 1) {
  // 処理対象だったかどうかチェックします
  if (objectIds.indexOf(items[i].get('objectId')) > -1 ) {
  } else {
   // 処理対象でなかったのでデータを消します
   items[i].destroy();
  }
}
```

これでGoogleスプレッドシートをマスターメンテナンス用のツールにできます。後はスプレッドシートにボタンを置いて、特定のシートを同期するアクションを設定すればOKです。

----

GAS SDKのスクリプトIDは `1yWnb7GfYsBCR-MZvi6r-TOYv_y-AV4le5P7kaG5B3iZ5VNnSk9Q55pSJ` になります。気になる方はチェックしてみてください。