Googleフォームで定員つきの複数アイテムを作る

こんにちは、bouchと申します。
初投稿です。

# はじめに

参考にした記事様:[【Googleフォームで定員のある選択肢を作る】](https://qiita.com/GMA/items/e672c18b09d84c753973)
こちらで書かれていることは、単一のアイテムで定員ありを実装しているコードになります。
私は現在大学院生でこういったフォームを作ることを手伝う機会があり、定員ありの複数アイテムの記事があまりなかったので、書いておくことにしました。

# 定員ありの単一アイテムの場合と何が違うの？
複数のアイテムがあることで、定員数が増え、1つのアイテムが定員超えた場合でもアンケートが終了しないということと
一番大事なことは、**実行速度が遅くなると定員オーバーが発生しやすい**ので、実行速度に気をつけたコーディングを心がける必要があります。

例として、60個の定員つきアイテムを扱う場合、実行速度に10秒かかったとします。ある人が残り1名で定員に達するタイミングで提出し、10秒以内に他の人が同じアイテムに提出すると定員を超過することができます。

なので、実行速度が速いと定員オーバーが発生しにくいのです。

※60個で確認したところ11.52秒かかってますね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153923/2d0df965-adbe-044c-b8bd-07a158727953.png)


# 遅くならないために
※ 私はJavaScriptsに疎い人間なので、大したこと言えないことをご了承ください
GoogleForms + GoogleSpreadSheet + GoogleAppsScriptの構成で考えると2つあると思います。
1.アイテムごとに関数を作成し、別々のトリガーで呼び出す
2.ループ文をなるべく使用しないように、SpreadSheet側をいい感じに活用して設計する

1.は実行速度を見ながら必要ならやったほうが良いと思います。今回は60くらいの想定で2.で頑張っていきます。

# 今回提供するもの

1. 実行速度は気にしない人向けの簡易な実装。
2. SpreadSheetsとスクリプトに工夫して実行速度を少し解消したもの。


# 1. 実行速度は気にしない人向けの簡易な実装
## フォームづくり
想定として時間帯ごとに企業との面談を予約する。というシチュエーションにしましょう。
企業はだいたい60社くらいにします。下の画像のように黙々と作っていきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153923/492b6a27-5386-25c6-2122-a3fcb38b8c06.png)

## スプレッドシート
[回答]からスプレッドシートを作成して、適当な名前をつけてシートを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153923/a45d8490-57aa-751a-f7b5-d410e02f4430.png)

次にこのシートに必要な情報をまとめていきます。
↓の画像のようになります。列ごとに説明していきますね
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153923/22aa5edf-f32c-5214-0b59-07df210104b6.png)

列A, 列Bはアイテムのタイトルとアイテムの個数を入力しています。タイトルが必要な理由はアイテムをユニークに指定できる方法がタイトルしか見つからなかったからですね。(他にあるなら教えてほしい)

列Cには、今回は企業名を入れます。参考のExcelがあれば、インポートできますが私は手作業でやりました。60社分ほど。

列Dはフォームの回答の結果から参加者を取るようにします。
E2の例では→の関数(?)で取得してます。`=COUNTIF('フォームの回答 1'!B$2:B$1052, "*" & C2 & "*")`

最後にJ1に全体の企業数を求めてます。`COUNTA(C2:C61)`

## Google App Scripts

フォームの「︙」(三点リーダ:その他)からスクリプトエディタをクリックすると記述画面に移ります
スプレッドシートが上記のようになっていたらこちらをコピペで動くと思います。

```javascript:code.gs
function endFormCheck() {  
  //1. 現在のスプレッドシートを取得
  //「https://docs.google.com/spreadsheets/d/xxx/edit」のxxxを入れてください
  var spreadsheet = SpreadsheetApp.openById('xxx');
  //2. 現在のシートを取得
  var sheet = spreadsheet.getSheetByName('シート名');;
  //3. 企業数を取得 
  var ANSWER_LENGTH = sheet.getRange("J1").getValue();
  // アイテムごとの企業数
  var company_list_nums = sheet.getRange("B2:B13").getValues(); 
  var item_titles = sheet.getRange("A2:A13").getValues(); //アイテムごとのタイトル数
  item_titles = [].concat(...item_titles) //convert 2d to 1d
  var length = ANSWER_LENGTH + 1 
  
  var columns = sheet.getRange("C2:C" + length).getValues(); //企業名
  var LIMIT_COUNT = sheet.getRange("D2:D" + length).getValues(); //定員上限を取得
  var answerCount = sheet.getRange("E2:E" + length).getValues(); //参加者数
  
  var form = FormApp.getActiveForm(); //アクティブフォームを取得
  
  // LIMIT_COUNTになっている選択肢があるかチェック
  var answerCheck = 0;
  var answerindex = 0;
  var choiceArrays = [];
  for(var m=0; m < company_list_nums.length; m++){
    
    var choiceArray = [];
    var arrayCount = 0;
    for(var i = 0; i < company_list_nums[m]; i++){
      if(answerCount[answerindex][0] >= LIMIT_COUNT[answerindex][0]){
        answerCheck++;
      }
      else{ // LIMIT_COUNTになっていない選択肢は残す
        choiceArray.push(columns[answerindex][0]);
      }
      answerindex++;
    }
    choiceArrays.push(choiceArray);
  }
  
  // 全てLIMIT_COUNTなら申込みフォームを受付終了
  if(answerCheck == ANSWER_LENGTH){
    form.setAcceptingResponses(false);
  }
  
  // LIMIT_COUNTになっていない選択肢を「参加する時間」の選択肢に設定する
  if( (answerCheck != ANSWER_LENGTH) && (answerCheck >= 1)){
    var arrayIndex = 0;
    var items = form.getItems();
    for (var i=0; i<items.length; i++ ){
      
      var item = items[i];
      if(item_titles.includes(item.getTitle())){
        if(choiceArrays[arrayIndex].length == 0){
          // 選択肢が消えたら代わりに入れる
          item.asMultipleChoiceItem().setChoiceValues(["こちらは定員に到達しましたため。終了しています。"]);
        }else{
          item.asMultipleChoiceItem().setChoiceValues(choiceArrays[arrayIndex]);
        }
        arrayIndex++;
      }
    }
  }
}
```

コピペしたら、編集→現在のプロジェクトトリガー→トリガーの追加で以下のように設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153923/ed6ee6b8-7635-2405-a7ff-b15f992420e8.png)

以上でおそらく終了です。

## 動作確認
スプレッドシートの出力
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153923/0074bf8a-26da-95e6-ebfb-e7efb5d6eacc.png)

フォームの出力
A0社は定員に達しているので消えていて、B0〜B4社まで定員に達しているので終了していることが確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153923/72a4e535-c7f9-2ae3-519f-f7b410692865.png)

だいたい10秒〜12秒くらいかかっています。これでも良い、使えるだろうという方は使ってください。

# 2. 工夫して実行速度を少し解消したもの

こちらアドベントカレンダーに投稿するので、一旦公開いたします。(時間をかけすぎました...)
後日に追記する形にしますmm