ServiceNowでレコードを参照するときのデータ型を意識する (3)

:::note
この記事は以下の記事の続編です。
**ServiceNowでレコードを参照するときのデータ型を意識する (2)**
https://qiita.com/yujiarakitokyo/items/adc0e5644ebff5636ee9
:::

## はじめに

ServiceNowの実装で、スクリプトから何気なくテーブル上の値を取得しているけれど、それって厳密には何を見ているんだっけという疑問から始まったシリーズ第3回目です。

前回はかなり闇の深い話になってしまいましたが、今回はあっさりした内容です。結論としては、`GlideQuery`は型の扱いという観点ではとても扱いやすくて便利ですよという話になります。なのでさっさと始めましょう。

## ケース

前回の続きということで、こちらのサンプル画面をベースに考えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3491737/2deeb9ff-e8ec-79d9-911f-aa73ebfce603.png)

この画面上に作られたサンプル選択肢（`sample_choice`）とかサンプル数値（`sample_integer`）とかサンプル真偽値（`sample_boolean`）の値を取得して、そのデータ型がどうなっているのかを見ていきたいと思います。

## 検証

### 選択肢フィールド

まずは選択肢フィールドから。前回との違いは、`GlideRecord`の代わりに`GlideQuery`を使うことです。最初はとっつきにくいと思うかも知れませんが、慣れるとBuilderパターンのおかげで書くテンポ感が良いので、`GlideRecord`には戻れなくなること請け合いです。[^1]

```JavaScript: Sample Script of GQ 1 (Scheduled Script Execution)
(function() {
    new global.GlideQuery('x_snc_qiita_sample_qiita_task')
        .where('number', 'QTASK0001001')
        .selectOne('sample_boolean', 'sample_choice', 'sample_integer')
        .ifPresent((e) => {
            gs.info(`Test1-1 Value of sample_choice: ${e.sample_choice}`);
            gs.info(`Test1-2 Type of sample_choice: ${typeof e.sample_choice}`);
			gs.info(`Test1-3 sample_choice equals to: ${e.sample_choice === '1'}`);
        });
})();
```

`GlideQuery`そのものの解説はまた別に書きたいので、掘り下げはしませんが、簡単なポイントは以下にまとめておきます。

:::note
**ポイント**
* 1件だけ取得したい場合は、`selectOne`メソッドを使う。同メソッドの引数に渡したフィールドおよびシステムIDを取得します。（`GlideRecord`と違って指定したフィールドだけが返されます）
* クエリが成功するか否かに依らず、`Optional`のオブジェクトが返される。
* `Optional`の`ifPresent`メソッドを使うと、クエリが成功して`Optional`に値があるときはメソッドの引数に渡したコールバック関数を実行し、逆に値が取れていないときは何もしない[^2]。
* コールバック関数に対して引数から渡されるのは1レコード分のデータで、今回の場合、`selectOne`の引数に渡した3つのフィールドとシステムID、ここでは合計4フィールドの値を持ったオブジェクトが渡されます。なので、そこからドットを挟んでフィールド名を与えると、フィールドの値が取得できます。
:::

結果は以下の通り。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3491737/ce05644f-9ae8-2354-552e-94f518f14796.png)

上のプログラムで`e.sample_choice`という記述で取り出した値はそのまんま、文字列リテラルの「1」でした。機能は豊富なものの直感的ではなかった`GlideElement`みたいな中間的なオブジェクトを介さないので、そのまま`'1'`と厳密に比較しても構いません。

### 数値フィールド

数値はこちらのコードで試してみましょう。

```JavaScript: Sample Script of GQ 2 (Scheduled Script Execution)
(function() {
    new global.GlideQuery('x_snc_qiita_sample_qiita_task')
        .where('number', 'QTASK0001001')
        .selectOne('sample_boolean', 'sample_choice', 'sample_integer')
        .ifPresent((e) => {
            gs.info(`Test2-1 Value of sample_integer: ${e.sample_integer}`);
            gs.info(`Test2-2 Type of sample_integer: ${typeof e.sample_integer}`);
			gs.info(`Test2-3 sample_integer equals to 42: ${e.sample_integer === 42}`);
        });
})();
```

こちらも結果はこの通り。そのまま過ぎて何も面白くありませんが、数字を数字として扱えるのは事後の処理を考えると大きなメリットがあります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3491737/7402752a-a972-28b4-e10d-91611480b8fb.png)

### True/Falseフィールド

真偽値（True/False）も同じです。

```JavaScript
(function() {
    new global.GlideQuery('x_snc_qiita_sample_qiita_task')
        .where('number', 'QTASK0001001')
        .selectOne('sample_boolean', 'sample_choice', 'sample_integer')
        .ifPresent((e) => {
            gs.info(`Test3-1 Value of sample_boolean: ${e.sample_boolean}`);
            gs.info(`Test3-2 Type of sample_boolean: ${typeof e.sample_boolean}`);
        });
})();
```

結果はこの通り。真偽値が真偽値として取得できるのは、前回の挙動と比べると格段のシンプルさであることがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3491737/e3a2e2c7-6432-edeb-c865-f3c19c1dcf50.png)

## まとめ

今回は、`GlideRecord`ではなく、`GlideQuery`を使ってクエリを投げたときの結果の型について調べてみました。`GlideRecord`の各フィールドは`GlideElement`で取得できたのと異なり、`GlideQuery`を使うとシンプルにプリミティブ型で値を返してくるので、その後の処理の見通しが良いです。

これが便利さの全てではなく他にも面白い機能がたくさんあります。完全に`GlideRecord`を置き換えるかというと、そうでもない[^3]のですが、コールバック関数を多用する仕組みは、Tokyoから使えるようになったECMAScript 2021（ES12）のラムダ式との相性が抜群ですので、ぜひ活用してみたいものです。


[^1]: もちろん、現場ではある程度の開発標準は設けるべきですし、メンバーのスキルによっては使わない方向で決めておいた方がいいこともあるかも知れません。しかし、クエリ結果の型の扱いを厳密にしやすいこと、`GlideQuery`からの戻り値を扱うための`Stream`クラスと`Optional`クラスのメソッドに便利なものが多くて事後処理を書きやすいことなどから、生産性は高いと思います。

[^2]: 惜しいのが、ServiceNowにはJavaのStream APIのような`ifPresentOrElse`メソッドがないことで、このため、値が**ないとき**に何かをしたくてもうまく書くことができず、結局値を取り出して回りくどい処理を書く必要があります。

[^3]: 例えば、ビジネスルールの組み込みグローバル変数`current`と`previous`は`GlideRecord`なので、否応なしに使うことになります。