🐉 2024年、初めの記事となります。本年もよろしくお願いいたします!

私ごとなのですが...

気分転換に散歩していて、不思議な建築を見つけるとすぐググってしまう習慣を持っている のですが、Hackerの皆様はいかがお過ごしでしょうか..。

さて、今回は前回YAML化した設定ファイルを、並び替えできるように実装を変えていこうと思います。

前回記事はこちらから😀

よろしければ第1回の記事もご参照くださいませ。

参考記事:

モルドスプーンアイコン

おさらいと改修方針

前回のYAMLを振り返るとこのように書いていました。

storage/app/config.yaml
order:
  statuses:
    name:
      - '新規',
      - '梱包中'
      - '梱包完了'
      - '発送準備'
      - '発送開始'
      - '配送センターに向けて輸送中'
      - '配送センター到着'
      - '顧客に向けて配達中'
      - '配達完了'
      - '返送中'
      - '返送完了'

こちらを、前回ご説明したようにjs-yamlでパースし、配列としてhtmlに書き出せるようにした かと思います。

ただこのままでは、途中に新しい項目を追加した場合、配列のindexがずれてしまい、キー・IDとしては使えないという問題に当たることになります。

固定的なステータスで使うケースでなければ実用性がないのです。

具体的には以下のような感じです。

後から項目追加するケースに対応できない

第1回のこちらのselect boxに書き出していたものに、「返送前」というステータスを追加したとします。Next.jsのコンポーネントで書き出すと以下のようになります。

<select name="status">
{statuses.map((status, index) => {
  return (
    <option key={index} value={index}>{status}</option>
  )
})}

その結果、ステータス追加後に出力されるhtmlは以下のようになります。

<select name="status">
  <option value="1">新規</option>
  <option value="2">梱包中</option>
  <option value="3">梱包完了</option>
  <option value="4">発送準備</option>
  <option value="5">発送開始</option>
  <option value="6">配送センターに向けて輸送中</option>
  <option value="7">配送センター到着</option>
  <option value="8">顧客に向けて輸送中</option>
  <option value="9">到着</option>
  <option value="10">返送前</option>
  <option value="11">返送中</option> <!-- valueがずれちゃってる! -->
  <option value="12">返送完了</option> <!-- valueがずれちゃってる! -->
</select>

追加した「返送前」より後の項目に不都合がありそうです。

これではデータベースなどにこのstatusの登録処理を行った場合、登録後の 後続のステータスのID値が必ずずれてしまい、データ管理上とても支障をきたすことになります。

DBテーブル化すれば解決することもあるが、今回はしない

ぶっちゃけデータベースのテーブルに置き換えてしまえば解決できるかもしれません。 Next.jsで対応するのであれば、prismaから呼ぶなり、APIを呼び出すなりして参照できます。

ただ、データベースを使えないケースや、開発効率を考えるとわざわざテーブル化までしなくてもいいというケースもありうるので、それを想定して 今回はソートできるYAMLを考えてみたいと思います。


(なお、本筋から話がずれますがprismaについては以下で解説している記事が当ブログにございます🙇)

参考記事:

モルドスプーンアイコン

実装

ということで、早速始めていきます。

subtitleもキー追加しちゃおう

とその前に...

nameという構造だけでは要件上済まないことも多いですよね。例えば、「梱包中」というステータスはどんな状態か、解説するsubtitleという項目が必要になるケースってよくあると思います。

よって、今回のサンプルでもsubtitleというキーを追加し、丸ごとソートできるようにしちゃいましょう。

YAML構造の見直し

結果、YAML構造は以下のように変えます。

new.yaml
order:
  statuses:
    rank:
      - 1
      - 2
      - 3
      - 4
      - 5
      - 6
      - 7
      - 8
      - 101
      - 200
      - 100
    statusObjects:
      name:
        - '新規'
        - '梱包中'
        - '梱包完了'
        - '発送準備'
        - '発送開始'
        - '配送センターに向けて輸送中'
        - '配送センター到着'
        - '顧客に向けて配達中'
        - '配達完了'
        - '返送中'
        - '返送完了'
        - '返送前'
      subtitle:
        - '新規の解説'
        - '梱包中の解説'
        - '梱包完了の解説'
        - '発送準備の解説'
        - '発送開始の解説'
        - '配送センターに向けて輸送中の解説'
        - '配送センター到着の解説'
        - '顧客に向けて配達中の解説'
        - '配達完了の解説'
        - '返送中の解説'
        - '返送完了の解説'
        - '返送前の解説'

ポイントは以下の3点です

完成したソースコード

思ったより長くなってしまいました。一旦こちらで公開としますが、関数化をすればもう少しシンプルな処理にできると思いました。

今回は、サンプルであるということもありファイルが複数に分散するのもちょっと見にくいのでこのまま載せてしまいます。何卒ご了承ください。

src/app/test/page.tsx
import { getConfigData } from "@/utils/config"

const Page = () => {
  // configファイルを読み込み
  const config = getConfigData('config.yaml')


  const orderStatuses = config.order.statuses

  const ranks = orderStatuses.rank
  const sortedRanks = [...orderStatuses.rank].sort((a, b) => a - b)
  const sortedIndexArray = Array.from({ length: ranks.length }, (_, index) => index + 1)
  let rankHash: { [index: number]: number } = {}
  let sortedRankToIndexHash: { [index: number]: number } = {}
  let newIndexToOldIndexHash: { [index: number]: number } = {}

  // indexをキーに、rankを値にしたハッシュを作る
  ranks.forEach((rank: number, index: number) => {
    rankHash[rank] = index;
  });

  // ソート済みrank配列を使って、ソート済みrankから新indexを取れるハッシュを作る
  sortedRanks.forEach((sortedRank: number, index: number) => {
    sortedRankToIndexHash[sortedRank] = index
  })

  // 新 => 旧indexを確認できるハッシュを作る
  Object.entries(sortedRankToIndexHash).forEach(([sortedRank, newIndex]) => {
    const oldIndex = rankHash[Number(sortedRank)]
    newIndexToOldIndexHash[Number(newIndex)] = oldIndex
  });

  // ソートした状態で、元の「orderStatuses」と同じ構造で詰め直す
  let sortedNames: string[] = []
  let sortedSubtitles: string[] = []

  sortedRanks.forEach((sortedRank: number, index: number) => {
    const newIndex = sortedRankToIndexHash[sortedRank]
    const oldIndex = newIndexToOldIndexHash[newIndex]
    const name = orderStatuses.statusObjects.name[oldIndex]
    const subtitle = orderStatuses.statusObjects.subtitle[oldIndex]
    sortedNames.push(name)
    sortedSubtitles.push(subtitle)
  })

  // TODO:毎回手作業でキーパターンを追加しなければいけなくなりそうなので、抽象化するメソッドにしたい。
  const sortedData = {
    rank: sortedRanks,
    statusObjects: {
      name: sortedNames,
      subtitle: sortedSubtitles
    }
  }

  return (
    <div>
      {sortedData.statusObjects.name.map((name, index) => (
        <div key={index} className="m-2 px-4 py-2 shadow ">
          <h2>{name}</h2>
          <p className="text-xs text-gray-400">{sortedData.statusObjects.subtitle[index]}</p>
        </div>
      ))}
    </div>
  )
}

export default Page
補足: getConfigData()の実装

なお、yamlを取得するgetConfigData()前回 の記事でご確認をいただけますのでそちらをご利用くだされば幸いです。

まとめ

今回はDBなしでもソートできるYAMLということでちょっと無理した感じが強かったです...。すでに書かせていただきましたが、単純なデータの羅列からやることが抜け出した場合は、素直にデータベースを使うべきだと改めて感じた次第でした。


この記事が何かのお役に立てれば幸いです。
最後までお読みいただきありがとうございました!