前回作ったdbdのマップログアプリを拡張したくなったので、いっそのことリザルトをまとめて記録するアプリを作りました。
![dbdresultlog.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148969/c05ff280-8f21-99d3-a62e-326459762df0.png)Githubにあげてみました。
扱う情報
- 勝敗
- キラー
- 残り発電機
- マップ
- キラー使用パーク
- サバイバーそれぞれの使用パーク
開発環境
- node.js
- Express
- pug
- PostgreSQL
詰まった点をまとめていきたいと思いますが、前回との重複点もあるため、そこは省かせていただきました。
特に使ったことがなかったPostgreSQLについての内容が多めです。
#もくじ
[1.PostgreSQL connectionの作成](#1-PostgreSQL connectionの作成)
2.queryの実行
3.行の挿入時にidを返す
4.取得した行のソート
[5.pug formの作成](#5-pug formの作成)
6.selectの作成
7.checkboxの作成
8.tabで整理
9.おわりに
#1.PostgreSQL connectionの作成
まずはデスクトップにPostgreSQLをインストールする必要があるため、以下のチュートリアルを実行します。
Install PostgreSQL on Windowsチュートリアルにあるshellのログイン情報をメモします。
次に以下のDocumentを参考に、メモの情報を入力してconnectionを作成します。
connection作成方法connectionはこのようになります。
//get db connection
const { Pool, Client } = require('pg')
const pool = new Pool({
user: 'postgres',
host: 'localhost',
database: 'databasename',
password: 'password',
port: 5432,
})
#2.queryの実行
PostgreSQLにはqueryの実行方法がいくつかあり、それぞれcallback, promise, async/awaitになります。
基本的にはcallbackが簡単です。
```index.js let killer_names = []; let sql = "SELECT * FROM killer_name; pool.query(sql, (err, res) => { killer_names = res["rows"]; }) ```帰ってくるデータ構造は(テーブルや列によって変化しますが)、以下のようになります。
```index.js [ {"id": 1, "name": "trapper"}, {"id": 2, "name": "wraith"},,, ] ```通常のdictと同じようにアクセスできます。
console.log(killer_name[0]["name"]);
//trapperが出力される。
※注意 レスポンスにはタイムラグがあるため、直後にsqlを操作することはできません。
//悪い例
let killer_names = [];
let sql = "SELECT * FROM killer_name;
pool.query(sql, (err, res) => {
killer_names = res["rows"];
})
//取得にはタイムラグがあるため参照できない。
console.log(killer_name);
そこで出てくるのがproses, async/awaitです。
詳細は私よりも他のdocumentを参考にした方が良いと思います。
一応プログラム内のコードを貼っておきます。
async function ins_result(killer, kpc, spc_lst, values) {
const insertText = "INSERT INTO result(\
result, rem_gen, map, killer, party) \
VALUES ($1, $2, $3, $4, $5);"
const killer_id = await ins_killer(killer, kpc);
values.push(killer_id.rows[0]["id"]);
var surviver_id_lst = []
var len = spc_lst.length;
for (let i = 0; i < len; i++) {
const surviver_id = await ins_surviver(spc_lst[i]);
surviver_id_lst.push(surviver_id.rows[0]["id"]);
}
var party_id = await ins_party(surviver_id_lst);
values.push(party_id.rows[0]["id"]);
pool.query(insertText, values);
}
ざっくりと解説いたしますと、async function内でawaitで呼び出された関数はその関数の処理が完全に終了するまで次の行を読み込まないという仕組みになっているようです。
awaitで呼び出される関数はprocessで宣言されたものや、今回のquery関数を含むものになります。
上記のコード内でawaitで呼び出された関数は以下のようにqueryを実行したものになっておりました。
function ins_killer(killer_id, kpc) {
const insertText_killer = "INSERT INTO killer VALUES(DEFAULT, $1, $2, $3, $4, $5) RETURNING id;";
while (kpc.length != 4) {
kpc.push(-1);
}
var val = [killer_id, kpc[0], kpc[1], kpc[2], kpc[3]];
const res = pool.query(insertText_killer, val);
return res;
}
#3.行の挿入時にidを返す
上記の項目で部分的に見せましたが、行の挿入を行う際に、queryの最後に"RETURNING 〇〇;"を追加することで、挿入後に挿入した行の列を返します。
主にテーブルのidを自動登録に設定している際に使うことになると思います。
//ここではidを返しております。
const insertText_killer = "INSERT INTO killer VALUES(DEFAULT, $1, $2, $3, $4, $5) RETURNING id;";
ちなみにidを自動登録する際のデータ型は'int'ではなく'SERIAL'にすると良いです。
そうすることで上記のINSERT文のように、idをわざわざ指定せずともDEFAULTを指定することで挿入が可能になります。
#4.取得した行のソートこのアプリでは多数のデータから選択を強いられます。一つ一つ確認するのは大変のため、使用頻度の高いデータ順にソートする必要がありました。
マップや使用キャラ(キラー)はリザルトログと一意の関係のため、比較的簡単んでした。
let sel_text_map = "SELECT map.id, map_name, count(map) FROM map LEFT JOIN result ON map.id=result.map GROUP BY map.id ORDER BY count(result.map) desc;";
let sel_text_kil_perk = "SELECT killer_perk.id, killer_perk.perk, count(*) FROM killer_perk LEFT JOIN killer ON killer_perk.id=killer.perk_1 OR killer_perk.id=killer.perk_2 OR killer_perk.id=killer.perk_3 OR killer_perk.id=killer.perk_4 WHERE killer_perk.id NOT IN (-1) GROUP BY killer_perk.id ORDER BY count(*) desc;";
解説
- "COUNT(map)": マップを多い順にソート
- "FROM map LEFT JOIN result": これによりmapテーブルを基準にresultテーブルを組み込む
- "ON map.id=result.map": それぞれの列を組み合わせる。事前にforeign keyを設定しておく必要がある。
- "killer_perk.id=killer.perk_1 OR killer_perk.id=killer.perk_2": ここはORで繋げることで複数行を扱うことができる。
- "GROUP BY map.id": COUNT等の関数を使った際に行をまとめる。
- "ORDER BY count(result.map)": SELECTで指定した列を基準にソートする。
- "desc": 降順を指定。
#5.pug formの作成
//formの宣言
form(action="/submit-form", method="post")
//submitボタンを押してフォームを送信
input(type="submit")
#6.selectの作成
labelで表記する。for属性は対象のidに依存する。
```js label(for="map") map: select(id="map", name="map") each val in map option(value=val["id"]) #{val["map_name"]} ```受けて側はselectタグのname属性にアクセスすることで、optionタグで選択された値を得ることができる。
const map = req.body.map;
#7.checkboxの作成
checkboxもselectと同様の仕組み。
```js input(type="checkbox", name="killer_perk_check", value=val["id"]) ```ただしcheckboxの値は複数選択できるため
- 空→null
- 単一→単一の変数
- 複数→配列
という特徴を持っている。
dbdは1vs4のゲームでそれぞれがパークというものを複数持ってゲームをスタートします。
そのため個々の人物のパークを選択しやすいようにtabを使いました。
ソースコードはこちらを参考にしました。
基本的な仕組みは
- ボタンとチェックボックス郡を用意する
- クラスで紐づける
- ボタンのイベントで「クリックされると紐づけられたチェックボックスを表示、またその他を非表示にする」というものを組み立てる。
というものになっています。
余談ですが、formタグ内のボタンはtype属性に"submit"を持ってしまい、期待通りに動いてくれません。そこで
```index.js button(class="tablinks", onclick="openCity(event, 'Killer')", type="button") Killer ```という風にtype="button"としてあげるときちんと機能します。
#9.おわりに
私にとって難しかった点の解説をメインに記事を執筆致しましたので、この記事に興味を持って下さった方にとっては既知の情報も多く含まれているかもしれません。
ここに記載した何かしらの解説が誰かの役に立たせて頂ければ幸いです。