SAKURUG TECHBLOG

gas-db ライブラリを利用してみた

timestampauthor-name
Kenji

はじめに

 本稿では、最近少しずつ触り出している Google Apps Script(通称 GAS)について、気づきや関連する便利ツールなどを記載しています。

 色んなサイトを参考にしながら、GAS を学んでいるなかで、今回は「gas-db」というライブラリを発見したので、実装に取り入れてみた経緯などを記事にさせて頂ければと思います。

前提条件

 本稿では、下記を前提にしています。

内容

 個人的に GAS で便利ツールを作成しようと日々奮闘しているのですが、一般的なデータベース管理システムで取り扱う程の大きなデータは必要としていないのですが、履歴管理や何かを実施した/してないのフラグ管理が必要なケースが出てきて、本当にスプレッドシートで管理できる程度のデータ管理が出来たらなと思っていたところ、gas-dbというライブラリというものに目が留まりました。

 こちらのライブラリを少しお試して、便利そうであれば、そのまま利用させて頂こうと今回は、そのお試しした内容を記事にさせて頂いた次第であります。

 上記にも記載していますが、簡易的な動作確認はライブラリの導入方法のサイトに記載されています。ここでは、もう少し実用性に近い感じで実装を試みたいと思います。

実装機能

 実装したい機能としては、

  • Google のスプレッドシートでデータを保管する用のシート「EMPLOYEE」
  • データを検索・登録・削除を操作する用のシート「操作」

の 2 つのシートを用いて、gas-db を利用したいと思います。

 まず、「操作」シートについては、以下の図のようなシートを用意しました。

 [検索]、[追加]、[削除]、[クリア]ボタン、検索する際の条件部のセル( C5:F6 辺り)、検索でヒットしたレコードを表示させるための結果部のセル( C9:F9 より下部)を作成しました。

 次に、「EMPLOYEE」シートでは、以下の図のような保存データを用意しました。

 あとは、「操作」シートで各ボタンをクリックしたときに動作する処理を、書き込んだコードが以下になります。

 細かい入力チェックやエラー処理は、割愛しています。

const red = "#ff0000";
const dbSheetName = "EMPLOYEE";
const operationSheetName = "操作";

/**
 * [検索]ボタンがクリックされた時、
 * 条件部の入力データからDBシートより条件検索する。
 * 条件部に入力データがない場合は、全件検索する。
 */
function onSearchButtonClicked() {
  // gas-db をインスタンス化
  const dbSheet = getDb();

  // 結果表示用のシート
  const operationSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(operationSheetName);

  // ステータスメッセージをクリア
  operationSheet.getRange(8, 4).clearContent();

  let resultRecord;
  if (operationSheet.getRange('C6:F6').isBlank()) {
    // 全件検索
    resultRecord = dbSheet.findAll();
  } else {
    // 条件検索
    const conditionlCells = operationSheet.getRange('C5:F6');
    const item = parseCondition(conditionlCells);
    resultRecord = dbSheet.find(item);
  }

  // 結果表示をクリア
  onClearButtonClicked();

  // 該当レコード0件を出力
  if (resultRecord.length == 0) {
    dispMessage(operationSheet, "レコードが見つかりませんでした。");
    return;
  }

  // 該当レコードを出力
  const cellBackColor = operationSheet.getRange('G1').getBackground();
  let startRow = 10;
  dispMessage(operationSheet, "レコードが " + resultRecord.length + "件見つかりました。");
  resultRecord.forEach(r => {
    operationSheet.getRange(startRow, 3).setValue(r.部署);
    operationSheet.getRange(startRow, 4).setValue(r.従業員名);
    operationSheet.getRange(startRow, 5).setValue(r.メールアドレス);
    operationSheet.getRange(startRow, 6).setValue(r.入社日);
    operationSheet.getRange(startRow, 6).setNumberFormat("@");         // セルの書式を文字列に設定
    operationSheet.getRange(startRow, 7).setValue(r.ID);
    operationSheet.getRange(startRow, 7).setFontColor(cellBackColor);  // フォントカラーを背景色と同色に設定
    startRow++;
  });
}

/**
 * [追加]ボタンがクリックされた時、
 * 条件部のデータをデータシートに登録する。
 */
function onAddButtonClicked() {
  // gas-db をインスタンス化
  const dbSheet = getDb();

  // 結果表示用のシート
  const operationSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(operationSheetName);

  // ステータスメッセージをクリア
  operationSheet.getRange(8, 4).clearContent();

  // 条件部に値がない場合は、警告メッセージ出して終了
  if (operationSheet.getRange('C6:F6').isBlank()) {
    dispMessage(operationSheet, "登録データが入力されていません。");
    return;
  }

  // 入力データ取得
  const conditionlCells = operationSheet.getRange('C5:F6');
  let item = parseCondition(conditionlCells);

  // IDを生成
  const rand = Math.floor(Math.random() * 100) + 1;
  item['ID'] = "A" + rand.toString().padStart(3, '0');

  // 重複レコードを検索
  if (dbSheet.find(item).length > 0) {
    dispMessage(operationSheet, "登録データが既に存在します。");
    return;
  }

  // レコード登録
  dbSheet.insert(item);
  dispMessage(operationSheet, "入力データが登録されました。");
}

/**
 * [削除]ボタンがクリックされた時、
 * 該当のデータをデータシートから削除する。
 */
function onDeleteButtonClicked() {
  // gas-db をインスタンス化
  const dbSheet = getDb();

  // 結果表示用のシート
  const operationSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(operationSheetName);

  // ステータスメッセージをクリア
  operationSheet.getRange(8, 4).clearContent();

  // 条件部に値がない場合は、警告メッセージ出力して終了
  if (operationSheet.getRange('C6:F6').isBlank()) {
    dispMessage(operationSheet, "条件部に入力がありません。");
    return;
  }

  // 条件取得
  const conditionlCells = operationSheet.getRange('C5:F6');
  const item = parseCondition(conditionlCells);

  // 該当レコード検索
  if (dbSheet.find(item).length == 0) {
    dispMessage(operationSheet, "該当データが見つかりませんでした。");
    return;
  }

  // 該当レコード削除
  dbSheet.delete(item);
  dispMessage(operationSheet, "該当データが削除されました。");
}

/**
 * [クリア]ボタンがクリックされた時、
 * 結果表示されたセルをクリアする。
 */
function onClearButtonClicked() {
  // 結果表示用のシート
  const operationSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(operationSheetName);

  // 範囲設定
  const startRow = 10;
  const startCol = 3;
  const lastRow = operationSheet.getLastRow();
  if (lastRow < startRow) return;
  const lastCol = operationSheet.getLastColumn();

  // セルクリア
  operationSheet.getRange(8, 4).clearContent();
  for(let row = startRow; row <= lastRow; row++) {
    operationSheet.getRange(row, startCol, lastRow - 9, lastCol - 2).clearContent();
  }
}

/**
 * 条件を抽出する。
 */
function parseCondition(condition) {
  const values = condition.getDisplayValues();
  const headers = values.shift();
  const item = {};

  for (const [index, value] of values.entries()) {
    headers.forEach((header, index) => {
      if (value[index].trim() !== "") {
        item[header] = value[index];
      }
    });
  }

  return item;
}

/**
 * メッセージ出力する。
 */
function dispMessage(sheet, message, fontColor = red) {
  sheet.getRange(8, 4).setValue(message);
  sheet.getRange(8, 4).setFontColor(fontColor);
}

/**
 * データ格納用のスプレッドシートおよびシートを取得する。
 */
function getDb() {
  try {
        return new gasdb.Spreadsheet()
        .from()
        .at(dbSheetName);
  } catch(e) {
    Logger.log(e.message);
  }
}

検索機能

全件検索( gasdb.findAll() )

 最初に、条件部に何も入力しない状態で[検索]ボタンをクリックした時の挙動は、こんな感じです。

 「EMPLOYEE」シートにある全データ( 4 件分)が取得できました。

条件検索( gasdb.find() )

 続いて、条件部の部署に「BBB」を入力して[検索]ボタンをクリックした時は、以下になります。

 部署が「BBB」のデータだけが抽出されて取得できています。 

 ちなみに「入社日」である日付のデータでは、特に考えもなしに実装したところ、文字列と日付の比較?となっているためか、想定していない挙動になっており、「入社日」列のセルの書式を一旦、テキストに統一しています(まあ、コードを直せばいいんですが、今回はそこが本題ではないので、お見逃しください・・・)。

登録機能( gasdb.insert() )

 データを登録するために、条件部に登録データを入力した状態で[追加]ボタンをクリックした時の挙動は、以下の様な感じです。

「EMPLOYEE」シートのほうには、入力したデータが登録されていることが確認できました。

「ID」については、登録時に自動的に生成して付与するように施しています。

削除機能( gasdb.delete() )

 最後に、登録データを削除するために、条件部に削除したいデータ(下図では、部署に「BBB」)を入力した状態で[削除]ボタンをクリックした時の挙動は、以下になります。

「EMPLOYEE」シートのほうには、指定したデータが削除されていることが確認できました。

 「操作」シートにある[クリア]ボタンについては、検索結果表示された結果部のレコードのセルをクリアしているだけのため、特に gas-db には触れていませんので、説明は割愛いたします。

 あとは、データの更新( gasdb.update() )やデータが存在しない場合は登録し、存在する場合は更新する Upsert ( gasdb.upsert() )もライブラリで用意されているので、作り込めば色々と応用ができそうです。

さいごに

 いかがでしたでしょうか。

 個人的な利用目的で利用する分には、データをスプレッドシートで管理する上では、便利だなと思いました。仮に gas-db ライブラリを利用しない場合、スプレッドシートでのデータの書き込みや読み込みは、セルの何行目の何列目といった具合に作り込まないといけませんしね。

 まだまだ、GAS について学ぶことが多いので、精進して参ります。その際に、気づきや関連する便利ツールがあれば、次回以降にも記事にさせて頂きます。

参考サイト

 

ーーーーーーーーーーーーーーーーーーーーーーー
株式会社SAKURUGは「寄付月間2025」に参画しています。
12月中のテックブログのpv数に応じて、アフリカの支援団体に寄付をおこないます。
https://giving12.jp/
ーーーーーーーーーーーーーーーーーーーーーーー

▼高校生向けインターン実施中!

弊社では高校生向けにインターンを行っております!
現役エンジニア指導の下、一緒に働いてみませんか?

高校生インターン応募フォーム

▼カジュアル面談実施中!

カジュアル面談では、会社の雰囲気や仕事内容についてざっくばらんにお話ししています。
履歴書は不要、服装自由、原則オンラインです。興味を持っていただけた方は、
ぜひ以下からお申し込みください。

皆さんにお会いできることをサクラグメンバー一同、心より楽しみにしております!

カジュアル面談応募フォーム

記事をシェアする

ABOUT ME

author-image
Kenji
2017年に中途入社。趣味は映画鑑賞・漫画(読む方)・ゲーム等々。「そろそろ運動して痩せなきゃ!」が口癖!?