Google Apps Script で Google ドライブにフォルダを作成する

# はじめに
[前の記事](https://qiita.com/h-yuki/items/e00b8743968606582f99)では、[毎週行っている作業](https://qiita.com/h-yuki/items/4ba27e415a740d10b870)を自動化する Google Apps Script の基本設計と骨格について説明しました。チュートリアル風になってしまったので、今回もそののりで続けてみます。短く書く練習もしてみます。

基本設計に示した毎週実行する処理の2つ目のステップ「Google ドライブに作業用フォルダを作成する」について説明します。

# 関数仕様

- 関数名： createFolder
- 引数： contents オブジェクト
    - contents.date : 作業対象データの発行日(YYMMDD形式の文字列)
- 返り値： [Folder](https://developers.google.com/apps-script/reference/drive/folder)
- 振る舞い
    - 「qiita投稿用」フォルダの子に「作業フォルダ_YYMMDD」という名前のフォルダを作成する
    -  同じ名前のフォルダが既に存在する場合は、新たに作成せずに既存のフォルダを返す

# テストコード
真面目に TDDする必要もないので、テスティングフレームワークは調べていませんが、お奨めがあれば教えて下さい。意識高い振りをするために(?)、簡単な動作確認用コードを先に示します。「qiita投稿用」フォルダに、1つだけ「作業フォルダ_20190210」ができていればテスト成功です。

```javascript:test.gs
function testCreateFolder() {
  const contents = { date: '20190210' };

  //作業用フォルダを削除する
  const iter = DriveApp.getFoldersByName('作業フォルダ_20190210');
  while(iter.hasNext()) {
    iter.next().setTrashed(true);
  }
  
  //テスト1. 作業フォルダが作成される
  const folder1 = createFolder(contents);
  
  //テスト2. 既存のフォルダが返される
  const folder2 = createFolder(contents);
  
  if(folder1.getId() != folder2.getId()) {
    throw 'Assertion Failed.';
  }
}
```

## フォルダの名称とID
Windows や Linux とは異なり、Google ドライブでは同じ名称のフォルダやファイルを複数作ることができます。フォルダを一意に特定するには ID を参照する必要があります。フォルダやファイルを開いた時にブラウザのアドレスバーで URL を確認すると、パスの一部に ID が含まれています。例えば、フォルダであれば```https://drive.google.com/drive/folders/{folderId}```という URL になります。コードでは```getId()```メソッドで ID を取得することができます。

## アクセス許可
初めて実行するときに、APIが必要とする権限の許可を求められます。以下の順に進めます。

- 「承認が必要です」というダイアログが表示されるので、「許可を確認」をクリックします
- Google にログインします(アカウントを選択します)
- 「このアプリは確認されていません」というダイアログが表示されるので、「詳細」リンクをクリックして、「{スクリプトのプロジェクト名}(安全ではないページ)に移動」をクリックします(ここわかりにくいですね)
- APIが必要とする権限範囲(ここでは「Google ドライブのすべてのファイルの表示、編集、作成、削除」)の許可を求めるダイアログが表示されるので、「許可」をクリックします

現在のプロジェクトに許可されている権限は、スクリプトエディタの「ファイル」→「プロジェクトのプロパティ」を選択すると表示されるダイアログで、「スコープ」タグを選択すると表示されます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/354068/60783c75-23ca-b1cb-26be-e41787c0e7b3.png)

# 関数コード

```javascript:main.gs
function createFolder(contents) {
  Logger.log('Google ドライブに作業用フォルダを作成する');

  //親フォルダーを検索する
  //XXX 同じ名前のフォルダが複数存在することは想定していない
  const parentFolder = DriveApp.getFoldersByName('qiita投稿用').next();
  
  //作成するフォルダの名前
  const folderName = '作業フォルダ_' + contents.date;

  //フォルダを検索する
  const iter = parentFolder.getFoldersByName(folderName);
  if(iter.hasNext()) {
    //同じ名前のフォルダが既に存在する場合は、新たに作成せずに既存のフォルダを返す
    //XXX 同じ名前のフォルダが複数存在することは想定していない
    return iter.next();
  } else {
    //フォルダを作成する
    return parentFolder.createFolder(folderName);
  }
}
```

先にも説明したように、Google ドライブでは同じ名称のフォルダが複数作れますので、```getFoldersByName()```の返り値は [FolderIterator](https://developers.google.com/apps-script/reference/drive/folder-iterator) になります。このスクリプトでは、同じ名称のフォルダが存在しない前提で、```next()```で取得できる最初のアイテムを使っています。

## デプロイ時に決定するパラメータ
「qiita投稿用」フォルダが存在しない可能性についても考慮していません。このスクリプトをデプロイする際に、「qiita投稿用」フォルダを作成する必要があることを意味します。名称が固定なのは関数の引数にすることで可変にできますが(テストはしやすくなるかも知れません)、結局のところ呼ぶ側のコードのデプロイ時に決める必要があります。そのようなパラメータは、プロジェクトのプロパティとして定義するのがよいと思います。いやいやスクリプトを複製したり、他の人に共有するつもりないから、と思われるかも知れませんが、実際に運用を始めると、開発用と本番用を分ける必要が出てきます。デプロイ時に決定する情報は変動部として切り出しておくのがよいです。

# おわりに
今回のコード自体は、説明する必要もないほど簡単なものでしたが、相変わらずついうんちくを加えてしまいました。でも、少し短めに書けたかなと思ってます。