Google App Maker ー Drive Tables のバックアップ方法

一番基本の DB である Drive Tables のデータバックアップ方法について記載。

そもそも App Maker ってなに？という人は、以下の記事を参照してください。
[Google App Maker とは？ 短期間で Web アプリケーション構築を可能にした理由](https://www.topgate.co.jp/what-is-google-app-maker)


# TL;DR
日付フィールドがないなら標準のエクスポートを使え。
日付フィールドがあるなら頑張ってスクリプトを書く。
日付フィールドがあってRelationも使ってる場合は超頑張ってスクリプトを書く。

金があるならCloud SQLを使う。

# 検証データの作成

次のようなモデルを作成
![image.png](https://qiita-image-store.s3.amazonaws.com/0/35595/8a0268f1-405e-9c2c-e951-69ce2e7aa98c.png)


また、サーバースクリプトを用意して検証用データを作成する。
※スクリプトを利用したデータの投入方法は次の記事で解説している
[Google App Maker ー Drive Tables へのデータ投入方法 - Qiita](https://qiita.com/howdy39/items/194a15db0529d4b70913#%E8%87%AA%E5%88%86%E3%81%A7%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%82%92%E6%9B%B8%E3%81%84%E3%81%A6%E3%83%87%E3%83%BC%E3%82%BF%E6%8A%95%E5%85%A5)

```javascript:サーバースクリプト
function _createData() {
  var records = [];

  for (var i = 0; i < 24; i++) {
    var retiredAt = new Date('2017/10/1');
    retiredAt.setHours(i);
    
    var record = app.models.User.newRecord();
    record.Name = 'data' + i;
    record.Retired = true;
    record.Age = 40;
    record.RetiredAt = retiredAt;
    
    records.push(record);
  }
  app.saveRecords(records);  
}
```


## テーブルウィジェットを使って表示すると次のようになる
![image.png](https://qiita-image-store.s3.amazonaws.com/0/35595/b2b42c10-eaae-c014-e286-0d8101bd7555.png)

ポイントは退職日の時間表示の部分。
バインディング時に `formatDate` を使って時間まで表示している。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/35595/626d0e07-6bec-1474-9305-3abb489b6227.png)


# バックアップ方法その1 ー エクスポート機能を使う
Deployments の画面から行う

![image.png](https://qiita-image-store.s3.amazonaws.com/0/35595/911e6a3e-c28f-aa9b-84b5-944de29cd7f0.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/35595/f7cc9e19-5d3a-d0f0-899d-4f2444268621.png)

スプレッドシートを開くと次のようになる。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/35595/739cd33a-5467-ef36-3d60-986301e783db.png)

重要なポイントは２点ある。

1. **`日付フィールド` はUTCが基準になる**
2. **`日付フィールド` は時間部が削られる**

**これは単純にデータがぶっ壊れるということを意味している。**
例えば Date Box ウィジェットを使って日付を登録するとブラウザのタイムゾーンの0時が設定される。
つまり、日本で `10月1日` `10月2日` を登録するとこうなる。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/35595/d1c81a2e-2bac-f4d7-0d84-f5ad1c5e1b30.png)

これをエクスポートするとUTCにされてから時間が削られるため、かならず1日前の値になる。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/35595/266e1e22-b021-9f6e-3b2a-4666ea37dbf1.png)

なので日付フィールドがある場合は、
次の2点が確約されていないとエクスポートをデータのバックアップとしては使えない。

1. **日本でしか使ってない（ブラウザのタイムゾーンがJST固定）**
2. **日付フィールドは Date Box ウィジェットでしか使っていない(時分秒は使っていない)**


# バックアップ方法その2 ー スクリプトを用いてエクスポートする
次のようなスクリプトを書いてスプレッドシートにエクスポートする

```javascript
function _exportAllData() {
  var records = app.models.User.newQuery().run();
  var saveValues = [];
  saveValues.push(['_key', 'Name', 'Age', 'Retired', 'RetiredAt']);
  
   for (var i=0; i < records.length; i++) {
    var record = records[i];
    var values = [1,2,3,4,5];
    values[0] = record._key;
    values[1] = record.Name;
    values[2] = record.Age;
    values[3] = record.Retired;
    values[4] = record.RetiredAt;
    console.log(values);
    saveValues.push(values);
  }
  
  var ss = SpreadsheetApp.openById('1kR0XNntqwwusSBQUCVdl-Agc3cEWtP3PmBmbCn9Qgzw');
  var sheet = ss.getSheetByName('Sheet1');
  var range = sheet.getRange(1, 1, saveValues.length, saveValues[0].length);
  range.setValues(saveValues);
}
```

結果はこんなかんじ。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/35595/f2cc511a-e2a6-7606-449d-e7aad28eb40d.png)

次にインポート処理のスクリプトを書いてインポートしたい環境で実行する。

```
function _importAllData() {
  var ss = SpreadsheetApp.openById('1kR0XNntqwwusSBQUCVdl-Agc3cEWtP3PmBmbCn9Qgzw');
  var sheet = ss.getSheetByName('Sheet1');
  var dataRangeValues = sheet.getDataRange().getValues();
  
  var records = [];
  for (var i=1; i < dataRangeValues.length; i++) {
    var values = dataRangeValues[i];
    console.log(values);
    var record = app.models.User.newRecord();
//     record._key = values[0];
    record.Name = values[1];
    record.Age = values[2];
    record.Retired = values[3];
    record.RetiredAt = values[4];
    
    records.push(record);
  }
  app.saveRecords(records);  
}
```
`_key`は読み取り専用フィールドなので捨てて新しく採番されたやつを使う。

Relationを使ったモデルだったらこのインポートスクリプトが複雑になると思うけど、できなくはないと思う。


# バックアップ方法 番外 ー Drive Tables を捨てて Cloud SQLを使う
金があるなら、これが一番楽。