Rubyで簡単なCSVパーサーを作ってみる

こんにちは、開発メンバーの岡部です。
先日は社員総会で箱根に向かい、旅館の温泉でHPを全回復することができました。
子供の頃は旅行といえば、テーマパークなどが目当てだったのに、気づけば料理や温泉目当てになっていて歳をとったなぁ...と感じます。

突然ですが、最近は個人的にパーサーを実装するのにハマっています。
きっかけになったのはRui Ueyamaさんの低レイヤを知りたい人のためのCコンパイラ作成入門です。
内容については割愛しますが、簡単な演算しかコンパイルできなかったものが最終的に、馴染みのあるプログラムのコンパイルができるようになっていく過程を体験できるのでオススメです。
(完走したとは言ってない)

とはいえ、サクッとパーサー(もしくはコンパイラ)を作りたい人には、嬉しい悲鳴かなボリュームが多いかもしれません。 ただ、自分としてはぜひ皆さんにもこの体験をしてみてほしいので、今回は比較的簡単に実装できるCSVのパーサーを作っていきたいと思います。

CSVの形式の確認

パーサーを作るにあたって、最初にやるべきことはパース対象の形式を熟知することです。
まずはCSVRFCを確認して、どういった形式になるのかを確認してみます。

datatracker.ietf.org

今回は簡単のためDefinition of the CSV Formatを遵守せず、部分的に抜粋した形式を採用します。

  • 各行の最後にはCRLF(改行)を含む
  • 1行目: 必ずヘッダー情報を含む
  • 2行目: ヘッダーの項目数と同じ分だけ項目を持つ
  • 以降、繰り返し...
aaa,bbb,ccc CRLF(※改行)
ddd,eee,fff CRLF(※改行)
ggg,hhh,iii CRLF(※改行)

処理の流れについて

今回、実装するパーサーは2つのステップで構成します。

  • 字句解析: CSVファイルから1文字ずつ読み取り、トークン(構造体)に変換する
  • データ変換: トークンを二次元配列に変換する

多くのパーサー実装では字句解析の後に構文解析を行うことが多いです。
しかし、今回のCSV形式の場合、ただテキストが,区切りで登場するのみで構造が非常にシンプルなのでAST(抽象構文木)を生成するメリットがほとんどありません。なので、構文解析は行いません。

最終的にトークンはRubyの標準ライブラリcsvに則って二次元の配列に変換します。

# ファイルから一度に
p CSV.read("sample.csv")
# => [["Ruby", "1995"], ["Rust", "2010"]]

class CSV (Ruby 3.2 リファレンスマニュアル)

いざ実装

前提知識も紹介したところで、いよいよ実装に取り掛かっていきます。
まずは以下のプロジェクトを作成します。メインの処理はparser.rbに記述していきます。
開発をスムーズに行うために簡単なテストを実行可能なparser_test.rbを用意しました。Rspecを使っても良いですが、標準ライブラリのみで実装したいので、Rspecライクな記述が可能なように簡単な関数を定義してあります。

├── csv
│   └── simple.csv
├── parser.rb
└── parser_test.rb

csv/simple.csv

aaa,bbb,ccc
ddd,eee,fff
ggg,hhh,iii

parser_test.rb

# frozen_string_literal: true

require_relative 'parser'

def assert(result, expect)
  if result == expect
    [:pass, result, expect]
  else
    [:fail, result, expect]
  end
end

def it(label, &block)
  raise 'Block is required' unless block_given?

  status, result, expect = yield block
  if status == :pass
    puts "[OK] #{label}"
  else
    puts "[FAIL] #{label} (result=#{result}, expect=#{expect})"
  end
end

1. 字句解析(トークンへの変換)

ファイルから1文字ずつ読み取る

ファイルから1文字ずつ読み取るにはファイルクラスのインスタンスに対して.getcを呼び出すことで可能です。他にも1行ずつ読み込む.getsも用意されています。一度にファイル全体を読み込まないため、メモリに優しいのが嬉しいです。

f = File.open('csv/simple.csv', 'r')
puts f.getc # a
puts f.getc # a
puts f.getc # a
puts f.getc # ,
puts f.getc # b

.getcは何度も呼び出せますが、ファイルの最後まで読み取った後に呼び出すとnilが返ります。
つまり、読み込んだ情報がnilであるかを判定すれば、ファイルの最後まで読み取ったかどうかを判定することが可能ですが、すでにFileクラスに.eof?(end of file)たる便利なメソッドが定義されているので、こちらを使用します。
untilと組み合わせることで、ファイルの最後まで簡単に読み取ることができます。

f = File.open('csv/simple.csv', 'r')
until f.eof?
  puts f.getc
end

# a
# a
# :
# i

トークンの作成

まずは構造体が作成できるように、ファイルの上部でTokenを定義しておきます。

parser.rb

# frozen_string_literal: true

Token = Struct.new('Token', :kind, :value, :next)

トークンは連結リストとして作成していきます。
nextはそのために定義したフィールドです。以下の図のようにトークン同士をnextフィールドを利用して連結させていきます。

トークンを作成する処理をまとめてtokenize関数として定義したものが以下になります。

parser.rb

# frozen_string_literal: true

Token = Struct.new('Token', :kind, :value, :next)

def tokenize(file)
  head = Token.new(:start, '', nil)
  cur_token = head

  until file.eof?
    value = file.getc
    token = case value
            when ','
              Token.new(:comma, ',', nil)
            when "\n"
              Token.new(:crlf, "\n", nil)
            else
              Token.new(:value, value, nil)
            end
    cur_token.next = token
    cur_token = token
  end

  head.next
end

連結リストの作成にあたり、先頭のトークンをheadと現在のトークンを管理するcur_tokenに代入しておきます。 あとはcur_token.nextに作成したトークンを記録した後にcur_tokenを更新すると、面白いように連結リストが出来上がります。

# a,b,cの場合
#<struct Struct::Token kind=:value, value="a",
  next=#<struct Struct::Token kind=:comma, value=",",
    next=#<struct Struct::Token kind=:value, value="b",
      next=#<struct Struct::Token kind=:comma, value=",",
        next=#<struct Struct::Token kind=:value, value="c"
          next=#<struct Struct::Token kind=:crlf, value="\n",
            next=nil>>>>>>

2. データ変換(二次元配列への変換)

二次元配列として記録するために、各行の全要素を配列に格納する必要があります。

[
  ['aaa', 'bbb', 'ccc'], # 1行目
  ['ddd', 'eee', 'fff'], # 2行目
]

先にコードを見た方が理解が早いと思うので、完成形を以下に添付します。
二次元配列への変換を行う処理をまとめて token2csvとして定義しました。2toを表現しています。

parser.rb

def token2csv(token)
  cur_token = token
  rows = []
  row = []

  until cur_token.nil?
    case cur_token.kind
    when :comma
      cur_token = cur_token.next
    when :crlf
      rows.push(row)
      row = []
      cur_token = cur_token.next
    when :value
      value = ''
      while cur_token.kind == :value
        value += cur_token.value
        cur_token = cur_token.next
      end
      row.push(value)
    end
  end

  rows
end

現在のトークンを管理するためにcur_tokenを宣言しています。
何かしらの処理が完了して次のトークンを参照するには、都度、cur_tokenを更新していきます。

cur_token = cur_token.next

cur_tokennilになった場合、次のトークンが存在しないことを示しています。
この特性を利用して、先ほどと同じようにuntilを使うことで、全トークンの参照が行えます。

二次元配列を作るには

トークンは「a->a->a->,」というように値を保持しているで、まず一行の一要素(eg: aaa)をまとめる必要があります。
rowは各行の全要素を格納する一時領域として使用しています。

# value以外のトークンが出現するまで文字列を結合しまくる
when :value
  value = ''
  while cur_token.kind == :value
    value += cur_token.value
    cur_token = cur_token.next
  end
  row.push(value)

,\nが登場するまでが一要素となりますが、\nが登場した場合、それはCSVの一行が終了したことを示すフラグとなります。 この時点で、パースした全要素を配列に格納します。
続けて、全体を格納している配列に追加すれば、最終的に二次元配列が完成します。

# 全体を格納しているrowsにrowを追加
# 合わせて一時領域rowをクリアして次の行に備える
when :crlf
  rows.push(row)
  row = []
  cur_token = cur_token.next

これでパーサーの実装が完了しました!

csv-parser/parser.rb at master · okabe-yuya/csv-parser · GitHub

動作確認

それでは作成したパーサーの動作確認をしてみます。
tokenizetoken2csvをまとめて実行するcsv_parse(file)を定義しました。
さて、無事にパースできているでしょうか...。

parser.rb

def csv_parse(file)
  token = tokenize(file)
  token2csv(token)
end

f = File.open('csv/simple.csv', 'r')
res = csv_parse(f)
puts "result: #{res}"

# result: [["aaa", "bbb", "ccc"], ["ddd", "eee", "fff"], ["ggg", "hhh", "iii"]]

おぉ!期待通り、CSVの構造を表す二次元配列になっていますね。
思ったようにパースできているようです。

テストの追加

何度も実行結果を手で確認するのは手間なので、テストを追加します。
他にも気になるパターンがあれば、同様にテストを追加して試してみてください。

parser_test.rb

it 'simple.csv' do
  f = File.open('csv/simple.csv', 'r')
  result = csv_parse(f)
  expect = [
    ['aaa', 'bbb', 'ccc'],
    ['ddd', 'eee', 'fff'],
    ['ggg', 'hhh', 'iii'],
  ]

  assert(result, expect)
end
$ ruby parser_test.rb
[OK] simple.csv

無事にテストがパスしました。

最後に

今回は簡単なCSVパーサーをRubyで実装してみました。
最終的には、50行程度のコードでCSVのパーサーが作ることができました。
たったこれだけのコードで、こんなに面白いものが作れるのは、驚きと喜びしかありません(個人的感想...)。

github.com

コードを書きたいけど作りたいものが特にないという方には、コードがゴリゴリと書けるパーサーがオススメです。 パーサーを作るという過程を楽しんで頂けたのなら何よりです。

【初心者向け】ソフトウェア開発の「テスト」って何?テストの役割について考える

ソフトウェア開発の「テスト」って何?
ソフトウェア開発の「テスト」って何?

こんにちは、開発チームでエンジニアをしている和田です。

最近は、フロントエンドのプロジェクトを Vue2 から Vue3 に移行する作業に奮闘している毎日です🔥

そんな移行作業の真っ只中、社内の QA さんとお話しする機会があり「ソフトウェア開発に関するテストとはなにか?」について理解を深める良い機会となったため、今回は

  • 「ソフトウェア開発におけるテストってなに?👀」
  • 「そもそもテストってなんでやらないといけないの??🤔」

と疑問に感じられる方のために、筆者なりにテストに関する解説をさせていただきます。

ソフトウェア開発における「テスト」とは一体なんなのか?

皆さんは「テスト」という単語を聞くとどんなものをイメージするでしょうか?

20代の筆者は最初にこの言葉を聞いたとき、学校のペーパーテストの試験が脳内に浮かび上がりました 笑

当然、ソフトウェア開発におけるテストとは学校のテストとは全く異なるものです。
テストとはなんなのか ChatGPT に質問してみます。

テストの定義について ChatGPT に聞いてみる

筆者の質問

ソフトウェア開発におけるテストとはなんですか?

ChatGPT の回答

ソフトウェア開発におけるテストとは、開発されたソフトウェアが正しく動作するか、要求された仕様や条件を満たしているかを検証するプロセスです。

このように回答となりました。
つまり、ソフトウェア開発におけるテストとは、開発したソフトウェアが期待する動作をしているか検証することですね。

品質保証という考えにおいてテストが重要なのは考えるまでもないでしょう。
品質保証以外にもテストの役割はあるのでしょうか?


テストがなぜ必要なのか? テストの役割について考える

一般的にテストの役割としては下記のようなものが挙げられます。

  1. 品質の確保
  2. コストの削減
  3. ドキュメンテーションとしての役割
  4. 実施後の証拠品としての役割

それぞれ見ていきます。

1. 品質の確保

テストを通じてエラーやバグを早期に発見することで、ソフトウェアの安定性や信頼性を向上させる役割があります。 テストの専門家である QA (Quality Assurance) が「品質保証」を意味するようにテストの代表的な役割です。

2. コストの削減

ソフトウェアのバグというものは、後から発見されるよりも開発初期の段階で発見されたほうが修正コストが低くなります。 早期にテストを行うことによって、開発の過程で問題が修正されて長期的なコスト削減に繋がります。

3. ドキュメンテーションとしての役割

テストケースやテスト結果は、ソフトウェアの動作や仕様を示します。
すなわちテスト結果をきちんと記録しておけば、仕様書の役割も果たしてくれるのです。 今後、改修や新機能を実装する際に改めてテストを行うとき、過去に行ったテスト結果が大いに役立ちます。

4. 実施後の証拠品としての役割

また開発段階以外でもテストは役に立ちます。
それは証拠品として機能するという側面です。 正確に記録されたテストは、クライアントやステークホルダーに対してソフトウェアの品質を確認・保証するための物的な証拠となり、機能をリリースするか判断する際の重要な資料となります。


【まとめ】テストってめっちゃ大事

いかがでしたでしょうか?
今回はソフトウェア開発におけるテストについて筆者なりに学んだことをまとめさせていただきました。

社内の QA さんとテストに関して会話する中で特に学びとして感じられたことは「テストが証拠品として機能すること」です。開発以外の現場でも活躍する資料になるのは驚きですね。

筆者自身エンジニアとして開発に携わる中で「テスト書いてて良かった〜😌」と思わされるようなバグやエラーを発見した経験が度々あります。ただプログラムが書けるだけじゃなく、テストに関する正しい知識を持ち合わせたエンジニアを目指して参ります。

【私の作業環境】N2i の古株エンジニア 田中さんの作業環境を覗いてみた!!

私の作業環境

こんにちは、N2i Tech Blog 編集長の和田です。

エンジニアのみんなの作業環境について紹介していくシリーズ「私の作業環境」😆

物理的な環境・お気に入りのツール・ワークフローなんでも構わず社員のみんなのこだわりポイントを紹介させていただきます。

今回はエンジニアの田中さんです!


今回は、私の開発環境をご紹介させていただきます。

デスク全体はこのような感じになっています。

エンジニア 田中の開発環境 R2-D2がお気に入り

目次

  • JAPANNEXT 4K IPS 55インチモニター

  • 環境音を作る事もできるZoom用iPad

  • 自宅ではメインPCはChatGPT専用として使用

  • ハイレゾ電波時計・巨大電卓

  • 自動昇降式テーブルと赤いマット

1. JAPANNEXT 4K IPS 55インチモニター

一度に4つの画面を、大きさそのままで確認できる巨大4Kモニターです。

左の15インチMacBookProと比べると、27インチモニター4枚分なので巨大さがわかるかと思います。よく2つ目のモニターを縦にしてソースを見る使い方をよく見ますが、この画面ならそのまま縦に伸ばして、リファクタリングや差分を確認することができます。

27インチモニター4枚分の55インチモニター

JAPNNEXT 55インチモニター

https://japannext.net/jn-ips5500tuhdr/

2. 環境音を作る事もできるZoom用iPad

iPadでは、Zoom に入ったり、普段使わないときは環境音として流しています。

作業中の環境音は川の音ですが、このTopRelacMusicさんの川の音は、耳障りにならない自然な環境音に近いのでおすすめです。

夏の音も仕事のやる気がなぜか出ます。是非聞いてみてください。

iPadは環境音を作るのに操作性もよくておすすめ

おすすめ環境音

(川の音)

https://www.youtube.com/watch?v=5lCRsLjMeso

(夏の音)

https://www.youtube.com/watch?v=OLshMqlgmyA

3. 自宅ではメインPCはChatGPT専用として使用

会社にいくとメインPCとして作業してますが、リモート環境では、ChatGPT音声専用機になっています。Chrome拡張機能の「Voice In」を使えば、いつでもChatGPTで調べたい時に音声で調べられます。使い方は簡単で、

  1. インストールしたら、ChatGPTに聞きたいことを話します。
  2. 「かくてい」と言うと自動でエンターキーを押します。

【補足】「かくてい」以外にも、「 まる(。)」とか、「あたらしいぎょう(改行)」とか言うとその通り声で指示できます。

自宅では音声入力のChatGPT専用となっている

Voice In

Voice In - Speech-To-Text Dictation - Chrome ウェブストア

ビルトインボイスコマンド

日本語 Speech Recognition Commands - Voice In

4. ハイレゾ電波時計・巨大電卓

SONYのモバイルスピーカー、ハイレゾ対応で聞けます。環境音を流しています。

電波時計は時間を正確に見ることができますし、電卓はいつでも計算できるように巨大な押しやすい電卓を手元においています。

左からSONY SRS-HG1、電波時計、CASIOの巨大電卓

5. 自動昇降式テーブルと赤いマット

実は自動昇降式テーブルに最近変えました。1400✕700あり、自動でデスクの高さを変えられるので非常に体に優しいです。マットは光学式マウスをどこでも使えますし、赤い色にしてるのは、ものがどこに追いてあっても視認できるように赤色にしています。

自動昇降式デスクのシンプルなスイッチ

まとめ

最後まで読んでくださりありがとうございます!

色々とこだわりがありますが、自分の作業しやすい環境を作るのは、仕事がはかどるのでいいですよね!みなさんのこだわりで良かったら真似させていただきたいと思います。

おまけ画像 愛用のHHKB TypeS Bluetooth 無刻印キーボード

【Nuxt3 + Vite + ngrok】Nuxt3でngrokを使ったフロントエンド開発におけるホットリロード(hmr/)問題の解決方法

Nuxt3 と ngrok を使ったフロントエンド開発で
HMR 問題を解決する方法
Nuxt3 と ngrok を使ったフロントエンド開発で
HMR 問題を解決する方法

Nuxt3 は、ポートフォワーディングをせずに内部の local だけで開発が完結する場合は、何も考えずに WebSocket を使ってホットリロードしてくれます。

下記の左図は、通常のシンプルな開発環境(localhost:3000)における開発の図で、右図が ngrok を介した場合の Nuxt3 の開発です。

ngrok を介した開発では、local の任意のポートをポートフォワーディングしているため、https 付きのアドレスが吐き出されます。

Nuxt3の開発例

しかし、Nuxt3 と ngrok を組み合わせて開発を行う際、ホットリロード(Hot Module Replacement, HMR)がうまく動作しないという問題に遭遇することがあります。

この記事では、そのような問題を解決する方法を紹介します。

設定失敗の例

Nuxt3 と Vite を使用している場合、nuxt.config.js で Vite の設定を行います。

当初、ngrok にポートフォワーディングしている状態で、ホットリロードを行う際に以下のように誤った設定をしていました。

// nuxt.config.js
vite: {
  server: {
    https: true,
    hmr: {
      protocol: 'wss',
      host: 'localhost',
      clientPort: 443,
      path: 'hmr/',
    },
  },
},

この設定ではHMRが正常に動作しない問題が発生します。

path に設定されている hmr/ が、指定されている clientPort では解決できずに、延々とリトライを繰り返してしまう結果になります。

また、path を消去したとしてもエラーが発生します。

HTTPS 通信の場合のプロトコルは、確かに protocol: 'wss' で間違いないのですが、port: 443 で通信しようとしているため、こちらはすでに利用されているポートとして弾かれる結果になります。

解決方法

問題の解決には、nuxt.config.js の Vite の設定をシンプルにすることが有効でした。

// nuxt.config.js
vite: {
  server: {
    hmr: {
      host: 'localhost',
    },
  },
},

詳しい設定を書くと下記のようになります。 ※この書き方をわざわざしなくても上記の設定だけで十分ですが、今回は説明のために敢えて詳細の設定(デフォルト値)を記載しています。

// nuxt.config.js
vite: {
    server: {
      host: '0.0.0.0',
      port: 3000,
      hmr: {
        protocol: 'ws',
        host: 'localhost',
        port: 24678,
      },
    },
  },
  1. protocol: 'wss'clientPort: 443 を削除することで、不必要なセキュリティ設定が排除されました。

  2. ホスト設定 host: 'localhost' を残していますが、これは ngrok がローカルホストにトンネリングするためです。

  3. hmr のポート設定は、デフォルトの24678を設定することです。ここで誤って3000を指定しないようにしてください。

ポイント

ngrok は自動的に WebSocket もトンネリングしてくれるので、HTTPS 通信など考えず、特別な設定は不要です。

以上のように、設定をシンプルにすることで HMR の問題が解決しました。 Nuxt3 と ngrok を使った開発を行う際には、このような設定が有効です。

参考にしたサイト

Vite公式ドキュメント(server.hmr設定)

https://ja.vitejs.dev/config/server-options.html#server-hmr

ngrok公式ドキュメント(WebSocketのトンネリングについて)

https://ngrok.com/docs/using-ngrok-with/websockets/

Vite GitHubリポジトリ(Sever モデルの選択について)

GitHub - sapphi-red/vite-setup-catalogue: This repository contains several example of Vite setups.

【私の作業環境】エンジニアの作業環境を紹介するコーナーを新設しました!!

【私の作業環境】エンジニアの作業環境を紹介するコーナーを新設しました!!
【私の作業環境】エンジニアの作業環境を紹介するコーナーを新設しました!!

こんにちは、N2i Tech Blog 編集長の和田です。

N2i Tech Blog が発起して以来、毎週投稿できていることに驚きを隠せません🥳

それもこれも投稿するための記事を書いてくださっているみんなのおかげですね🙌

Tech Blog の運営に当たって月に一回の定例会を設けているのですが、新たな施策のアイデアを頂いたので今回はその第1回目の記事として投稿させていただきます。

その施策とはズバリ...

新シリーズ【私の作業環境】!!

ということで、N2i の社員のみんなのことをもっと知るべく、「私の作業環境」というシリーズ記事を執筆することになりました!!

簡単に説明すると、エンジニアのみんなの作業環境について紹介していくシリーズです🤓

物理的な環境・お気に入りのツール・ワークフローなんでも構いません。

皆さんの作業環境のこだわりポイントを紹介させていただきます!


【私の作業環境】エンジニア 和田

和田の作業環境
和田の作業環境

ということで、初回は Tech Blog 編集長を務めさせていただいる 和田の作業環境紹介です。
デスク全体はこんな感じです。
筆者自身、自分の作業環境に関してはかなりこだわりを持っているのですが、全部説明すると一冊の本が書けそうなので今回は下記について紹介させていただきます。

  1. 配線の美しさ
  2. 視界を妨げないモニターライト
  3. 疲労軽減トラックボールマウス
  4. 万が一のための蓋付きマグカップ

1. 配線の美しさ

デスク全体を見て分かる通り、デスク上から見える配線ができるかぎり少なくなるように配線にこだわっています。

モニター・モニターライト・マイク・スピーカー・キーボードなどなど、有線で繋げているものが複数ある中、視認できる配線はたった2本だけ😏

デスク上がごちゃごちゃしていると、集中力がかき乱されるのであまりものを置かないように心がけています。 配線はデスク下に格納していたり、モニターアーム内に忍ばせていたりと見せないための努力をしています💪

2. 視界を妨げないモニターライト

視界を妨げないモニターライト
視界を妨げないモニターライト

次にモニターライトです。先程のデスクにあまり物を置きたくないというこだわりにも通ずるのですが、このモニターライトはモニターの上に載せるだけ🙌

ライトの光が直接視界に入り込まないようになっているので眩しくないです。

部屋を暗くして作業する
部屋を暗くして作業する

筆者は夜はこんな感じでモニターライトのみの光とヘッドホンを付けて作業するのが好きです。

3. 疲労軽減トラックボールマウス

疲労軽減トラックボールマウス
疲労軽減トラックボールマウス

マウスをトラックボール式のものに変えてから全く疲れなくなりました。
マウスを動かす必要がないため、画面を見ている最中に右腕だけマウスを探しにいくこともなくなりストレスフリーで作業ができます。

4. 万が一のための蓋付きマグカップ

万が一のための蓋付きマグカップ
万が一のための蓋付きマグカップ

電子機器以外でこだわっているのがこの蓋付きのマグカップです。
卓上の電子機器に万が一飲み物がこぼれたらコワイので蓋付きの物を使っています。
倒れたとしても被害が最小限に抑えられる上に、保温の効果もあり大活躍中なのでオススメです!
アウトドア用の容量多いマグカップだとコーヒーを淹れる回数も少なくて済むのが嬉しいですね。


まとめ

最後まで読んでくださりありがとうございました!

いかがでしたでしょうか?第一回「私の作業環境」シリーズとして、N2i Tech Blog 編集長の和田の作業環境を紹介させていただきました。

キーボードがたくさんあることや、その他のこだわりポイントは、今後の記事で改めて紹介させていただければと思います😅

Vue2 の公式サポート終了に伴い、Options API から Composition API に書き換えてみる。

Vue2 の公式サポート終了に伴い、Options API から Composition API に書き換えてみる
Vue2 の公式サポート終了に伴い、Options API から Composition API に書き換えてみる

Web illustrations by Storyset

こんにちは、開発チームでエンジニアをしている和田です。

最近は、Vue2 で記述されたプロジェクトを Vue3 および Nuxt3 に書き換える毎日です👨‍💻

ということで今回は、Options API で記述された Vue2 のコンポーネントを Composition API で記述する過程について記事にしたいと思います。

Vue2からVue3・Nuxt3 へ移行する背景

Vue2 のプロジェクトを Nuxt3 へと移行するに至った背景としては、主に Vue2 の公式サポートが終了することにあります。

2023年12月に Vue2 の公式サポートが終了することが発表されています。このままプロジェクトを放置すると、Vue2 に依存するライブラリなどもアップデートできず脆弱性を抱え続けることになるため、一念発起してプロジェクト全体の変更をすることになりました🤓

  1. Options API で記述された Vue2 コンポーネントを Composition API に書き換える
  2. Composition API で記述されたプロジェクト全体を Vue3 に引き上げる
  3. Vue3 のプロジェクトをベースに Nuxt3 に移行する

このような流れで作業をしていきます。

この記事の対象読者: Vue2からVue3への乗り換えを検討している人

今回の記事では、Vue2 の Options API で記述されたコンポーネントを Composition API に書き換えるまでを解説します。

Composition API で記述されたコンポーネントを Nuxt3 のプロジェクトで使えるようにする方法については、今後の記事で解説させていただきます🙇‍♂️


Vue2とVue3、Options APIとComposition APIの関係について

前提知識として Vue2, Vue3, Options API, Composition API の関係性について述べておきます。

Options API について

Options API とは、Vue2 の代表的な記法であり下記のようなサンプルコードで書けます。

<script>
export default {
    name: 'Counter',
    data: () => {
        return {
            count: 0,
        };
    },

    methods: {
        countUp() {
            this.count++;
        },
        reset() {
            this.count = 0;
        },
    },
};
</script>

data の中でリアクティブな値 count を管理し、リアクティブに値を操作するための処理を methods 内に記述するという形式です。

この記法の問題として、this を使ってリアクティブに値を操作することになるため、View と処理を分離することができないということが挙げられます。

Composition API について

Composition API とは、Vue3 の代表的な記法です。
Vue2 のプロジェクトにも導入することができ、リアクティブな値の操作と View を切り離せるのが特徴です。

import { ref } from "vue";

export const useCounter = () => {
    const count = ref(0);

    const addCounter = () => {
        count.value++;
    };
    const resetCounter = () => {
        count.value = 0;
    };

    return {
        count,
        addCounter,
        resetCounter,
    };
};
import { useCounter } from '@/ ... /useCounter'

export default defineComponent({
    name: 'Counter'
    setup() {
        const { count, addCounter, resetCounter } = useCounter();
    }
});

composables というディレクトリに、リアクティブな値の処理を記述しておけば、どのコンポーネントからでも簡単に呼び出すことができます。


Vue2 Options API で記述されたコンポーネント

今回は Options API で記述された下記のコンポーネントについて見ていきます。
説明の都合上、<style /> の記述は省略します。

<template>
  <button
    :class="{ [`btn-${buttonType}`]: true }"
    :disabled="hasPermission === false || isDisabled === true"
  >
    <span>{{ buttonText }}</span>
  </button>
</template>

<script>
import { mapGetters } from 'vuex';

export default {
  name: 'ButtonBase',
  props: {
    buttonText: { type: String, required: true },
    buttonType: { type: String, default: 'primary' },
    isDisabled: { type: Boolean, default: false },
    rejectedRoleIds: { type: Array, default: () => [] },
  },
  computed: {
    ...mapGetters('staff', ['staff']),
    hasPermission() {
      return this.judgeHasPermission(
        this.staff,
        this.rejectedRoleIds,
      );
    },
  },
  methods: {
    judgeHasPermission(staffObj, rejectedRoleIds) {
      const staffObject = staffObj;
      if (typeof staffObject !== 'object') return false;

      if (rejectedRoleIds.length === 0) return true;

      return (
        staffObject.role &&
        rejectedRoleIds.includes(staffObject.role.id) === false
      );
    },
  },
};
</script>

それぞれの props の説明は下記のようになります。

props 用途
buttonText ボタン中央に表示するテキスト
buttonType ボタンの class を利用するための文字列
isDisabled ボタンの非活性フラグ
rejectedRoleIds ボタンの押下制限を行うユーザーの role ID の配列

Composition API に書き換える

先程のコードの仕様をほとんど変更せずに Composition API に書き換えると次のようになります。

<template>
  <button
    :class="{ [`btn-${buttonType}`]: true }"
    :disabled="hasPermission === false || isDisabled === true"
  >
    <span>{{ buttonText }}</span>
  </button>
</template>

<script>
import { computed, defineComponent } from '@vue/composition-api';

export default defineComponent({
  name: 'ButtonBase',
  props: {
    buttonText: { type: String, required: true },
    buttonType: { type: String, default: 'primary' },
    isDisabled: { type: Boolean, default: false },
    rejectedRoleIds: { type: Array, default: () => [] },
  },

  setup(props, context) {
    const store = context.root.$store;
    const staff = store.getters['staff/staff'];

    const hasPermission = computed(() =>
      judgeHasPermission(staff, props.rejectedRoleIds),
    );

    const judgeHasPermission = (staffObj, rejectedRoleIds) => {
      const staffObject = staffObj;
      if (typeof staffObject !== 'object') return false;

      if (rejectedRoleIds.length === 0) return true;

      return (
        staffObject.role &&
        rejectedRoleIds.includes(staffObject.role.id) === false
      );
    };

    return {
      hasPermission,
    };
  },
});
</script>

変更前は、this.staff としてユーザーの情報を取得していたところを const staff = store.getters['staff/staff']; として this を使わないので簡単です。
setup の中に関数をシンプルに記述できる点でも便利ですね。

今回の変更について

今回、変更したのはシンプルなコンポーネントだったため、劇的に変わった点はありませんでした😅

defineComponent() を使った記法ではなく、<script setup /> を使った setup 構文というものを使えば、もっとシンプルな記述ができるようなので次回以降の記事で解説していきたいと思います!

【作業効率化】エンジニアが使う開発ツールまとめてみた!おすすめを厳選して紹介

おすすめ開発ツール一覧
おすすめ開発ツール一覧

Business illustrations by Storyset

こんにちは、開発チームでエンジニアをしている和田です。

先日、コードを書いている最中にふと

「エンジニアって、めっちゃたくさん開発ツール使うよな... 何個くらい使ってんだろ 🤔」

と疑問に感じました。

ということで、今回の記事では、普段使っている開発ツールを一覧にしてまとめてみようと思います。

この記事の中で、読者のエンジニアライフに役立つような便利な開発ツールを見つけていただけると嬉しいです🙏

Warp

www.warp.dev

まずは、ターミナルアプリより Warp です。
Rust で作られた高速なターミナルで、モダンなターミナルでは当たり前のコマンドの補完・履歴検索・マルチカーソル・画面分割などはもちろん、AIが打ち間違えたコマンドから正しいコマンドを予測してくれたりと超便利です。

筆者は以前、後述の iTerm を使っていましたが、Warp の速さと利便性を実感してからこちらに乗り換えました🙌

デフォルトで用意されているカラーテーマも豊富で、おしゃれにターミナルを使いたい人にもおすすめです!

brew install --cask warp でインストールできます。

iTerm

iterm2.com

言わずと知れた有名なターミナル iTerm 。
設定ファイルをカスタムすれば、自分好みの見た目や機能を盛り込めるので便利です。
Mac に元から入っている Terminal よりも設定がわかりやすかったり、機能が豊富な定番のターミナルですね。

brew install --cask iterm2 でインストールできます。

Visual Studio Code

code.visualstudio.com

エンジニアなら知らない人はいない無料のコードエディタ VS Code です。
豊富な拡張機能や外部ツールとの連携が強みですよね。使わない日はないくらいお世話になってます🙇‍♂️

brew install --cask visual-studio-codeでインストールできます。

Google Chrome

www.google.com

VS Code 以上に毎日使うのが Chrome です。Web の開発なら使わないことはないでしょう。

brew install --cask google-chrome でインストールできます👍

Docker Desktop

www.docker.com

これが無いと何も始まらん...というくらい重宝している Docker のデスクトップアプリです。
開発の都合上、Docker が立ち上がってないと開発ができないので終始起動してます🐳

brew install --cask docker でインストールできます。

Beekeeper Studio

www.beekeeperstudio.io

モダンな DBMS のクライアントツールです。
主要な DBMS にマルチ対応しており、Beekeeper Studio の中で SQL の補完、フォーマットをしてくれたりとかなり便利。

Beekeeper Studio の開発者の方は、DB関連のツールに古めかしい外観のものが多いことに疑問を持ち、開発に至ったそうです。brew install --cask beekeeper-studio でコミュニティ版をインストールできます。

コミュニティ版は、データの一括ダウンロードができないのがちょっと残念😢

Sequel Ace

Sequel Ace

Sequel Ace

  • Moballo, LLC
  • 開発ツール
  • 無料
apps.apple.com

DBMS クライアントツールの定番である Sequel Pro からフォークして作られたツールです。

本家の Sequel Pro はなかったダークモードが Sequel Ace には搭載されており、UI も改善されています。使い勝手は全く同じなのでおすすめです👍

brew install --cask sequel-ace でインストールできます。

pgAdmin4

www.pgadmin.org

PostgreSQL 専用のクライアントツールです。
過去に PostgreSQL の geojson 形式のデータに対応しているツールを探していたときに見つけました。

brew install --cask pgadmin4 でインストールできます🐘

Medis

getmedis.com

Redis を使っている開発者にはおすすめのクライアントツールです。
ポートを接続すれば、一般的な DBMS のクライアントツールと同様に Redis に保存されているデータを GUI から確認することができます。

brew install --cask medis でインストール。

Postman

www.postman.com

エンドポイントを登録しておけば、ボタン1つで簡単に実行できるようにしてくれるツール。
バックエンドの開発はもちろん、フロントエンドの開発でも重宝します。
繰り返し実行機能をうまく活用すれば、ダミーデータの大量登録に活用できたりとかなり便利です🙌

brew install --cask postman でインストールできます🧑‍🚀

Fork

git-fork.com

チーム開発をしていると、 Git Branch を GUI 上で確認したいときってありますよね。
そんなときに役に立つのが Fork です。この手の Git のクライアントツール系は色々試しましたが、機能面・UI面で一番気に入ってます。

brew install --cask fork でインストール🍴

Raycast

www.raycast.com

開発ツールではなく、ランチャーアプリですが Raycastを使わない日はないので紹介させてください。
エンジニアフレンドリーなランチャーアプリとされており、プログラミング言語フレームワークの公式ドキュメントを Raycast から検索できたりします!

brew install --cask raycast でインストール。

最後に

最後まで読んでくださりありがとうございました🙇‍♂️

いかがだったでしょうか?

今回、紹介した他にも開発に利用しているアプリケーションはありますが、特に使っているアプリを選んで紹介させていただきました!

皆さんのエンジニアライフのお役に立てれば幸いです。