行動すれば次の現実

テック中心の個人ブログ

Rails7+esbuild+TypeScriptで開発環境を構築する

Rails7のフロントエンド開発環境としてjsbundlingのesbuildを採用した際にTypeScriptの導入に少し手こずりましたので備忘録として記事にしました。参考になれば幸いです。

esbuildではTypeScriptの型チェックは行ってくれない

esbuildはデフォルトでTypeScriptのコンパイルをサポートしています。

そのため、特段何かインストールしたりや設定する必要はありません。 ただしサポートしているのはコンパイルのみで、型チェックまでは行ってくれません。そのため別途tsc -noEmit等のコマンドを並行して実行する必要があります。

それを踏まえると以下のようなnpm scriptsになるかと思います。

package.json(抜粋)

"scripts": {
    "build:js": "esbuild ./app/javascript/application.ts --bundle --sourcemap --outdir=app/assets/builds",
    "build:css": "sass ./app/assets/stylesheets/application.bootstrap.scss ./app/assets/builds/application.css --no-source-map --load-path=node_modules",
    "type-check": "tsc -w --noEmit"
}

これでも良いのですが、build:jsとtype-checkがそれぞれ別で動いてしまいますので、型チェック(type-check)が通ったらコンパイル(build:js)を実行するようなことができません。

そのため今回はtsc-watchという型チェックの監視プロセスの結果によって挙動を制御するnpmパッケージがありましたので、それを使用します。

 yarn add -D tsc-watch

tsc-watchをインストールしましたら、package.jsonとProcfile.devを以下のように書き換えます。

package.json(抜粋)

"scripts": {
  "build:js": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds",
  "build:css": "tailwindcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/tailwind.css",
  "failure:js": "rm ./app/assets/builds/application.js && rm ./app/assets/builds/application.js.map",
  "dev:js": "tsc-watch --noClear -p tsconfig.json --onSuccess \"yarn build:js\" --onFailure \"yarn failure:js\""
}

また、Procfile.devでのjsコマンドもyarn dev:jsに変更します。

Procfile.dev

web: bin/rails server -b 0.0.0.0 -p 3000
js: yarn dev:js
css: yarn build:css --watch

このようにすることでTypeScriptの型チェックが成功すればコンパイルを実行し、失敗すれば前回のコンパイルファイルを削除させるような分岐をすることができます。

ちなみに、tsc-watchは内部的にtscコマンドを使用していますので、開発環境にTypeScriptがインストールされていない状態だとcannot find module 'typescript/bin/tsc'というエラーが出てしまいます。そのため、別途TypeScriptをインストールする必要がありますのでご注意ください。

 yarn add -D typescript

参考

https://esbuild.github.io/content-types/#typescript

現場主義の企業がどのようにDX化を進めていけばよいか

コロナ禍によって企業のDX化(デジタル・トランスフォーメーション)推進の動きが加速しております。 弊社でも今までシステム導入を見送っていた企業のシステム開発を請け負う機会が多くなりました。

DX化の案件を進めていく上で、どのようにDX化を進めていけばよいのかというのは永遠の課題だと思います。 今回は現場主義の企業がどのようにDX化を進めていけばよいのか弊社のナレッジを含めて考えを展開したいと思います。

エクセル管理の限界がDX化のサイン

DX化されていない企業の多くはエクセルを使用して日々の業務を管理しているケースがとても多いです。

簡易なエクセルマクロで業務の補助ツールとして利用している企業もあれば、コアなデータをシートに貼り付けて、そのデータを自動的に計算するような作り込みがされている職人芸のようなエクセルマクロを作って作業の効率化を図っている企業もあります。

エクセルマクロはシステマチックなことも実装できますので、非エンジニアであってもプログラミング思考を持っていれば複雑で高機能なツールを作ることが可能です。

ただし、このような高機能なエクセルマクロは作りが複雑であるがゆえに、特定の担当者でなければ修正できない状態(属人化)してしまったり、一つの修正により他の箇所が連鎖的に壊れてしまったりと様々な問題が生じてきます。

このような状態になることは考え方によっては、DX化を進める上でのサインであると捉えることもできます。 ツールで扱おうとする業務範囲が拡大し、エクセルのような簡易システムで管理できる範疇を超えてしまった状態です。

エクセル管理での限界を感じ始めたら、それがDX化を考えるスタートラインです。 まずはどのような解決策があるのか、インターネットや他企業から情報収集から始めるのが良いでしょう。

いきなり100%を目指すのではなく、1歩ずつDX化を進める

DX化する上で重要な考え方があります。

それはマネジメント層と現場スタッフとではDXに対する考え方が全く異なるという点です。 マネジメント層はすぐにでもDX化を推し進めようと考えますが、現場スタッフはそれに対して反発する傾向が強いように感じます。

人間というのは変化を恐れて現状維持のバイアスがかかります。現場スタッフもそれは例外では有りません。

日々ルーチンワークとしてある居心地良く回している業務が、新たなシステムの導入により抜本的に変化してしまうと、スタッフにとって大きなストレス要因となります。 また、一時的に作業効率が大きく落ちたり、新しいシステムの操作を覚えるためにスタッフへの負荷が高まり、他作業に影響が派生してしまうことも少なくありません。

DX化が重要だとは分かっていても最初からそれを理解していくれるスタッフは多くはありません。

そのため、いきなり0から100へのDX化を進めるではなく、0から1を目指すように、まずは一部の業務から移行したり、一部のスタッフから導入を促してシステムに慣れてもらうような考慮が大切になります。

PCやインターネットに対して苦手意識が低いスタッフや、比較的年齢層の若いスタッフを中心に広めていくと良いかと思います。

良いシステムであれば、使い慣れ始めると必ず賛同してくれます。賛同者が増えれば、また新しいスタッフへ伝播してくれる役割にもなりますので、ゆっくりでも1歩ずつ確実に浸透させていく心掛けが重要です。

Railsを5.2から6.0にバージョンアップする方法

この記事では、Railsをバージョンアップ(アップグレード)する方法を説明します。 バージョン5.2から6.0にアップグレードする手順を例にとって説明しています。

テストや確認の工程などは省き、なるべくシンプルな内容にしています。

Gemを最新状態にアップグレードする

Railsのアップグレードの前に、使用しているGemを最新状態にアップグレードしておきます。 こうすることによりアップグレード作業がスムーズになります。

bundle update

Railsのバージョンを上げる

Gemfileに記載されているRailsのバージョンを書き換えます。

gem 'rails', '~> 5.2.0'
  ↓
gem 'rails', '~> 6.0.0'

その後、Rails本体および関連Gemを含めアップグレードを実行します。

bundle update

rails app:updateタスクを実行する

bundle updateが正常に完了したら、下記のコマンドを実行します。

rails app:update

対話形式で設定ファイルを上書きするかどうか聞かれますので、ひとまず全てYで上書きします。

上書きが完了したら、ファイルの差分を一つずつチェックします。旧ファイルで必要な設定を適宜反映させて設定ファイルの内容を確定させます。

なお、Rails6.0にアップグレードする場合は、新たにmigrationファイルも生成されますので、rails db:migrateタスクも実行する必要があります。

rails db:migrete

古いバージョンとの差分を確認する

この状態で一旦railsを起動させて、動作確認をします。

動作確認に問題なければapplication.rbの以下の項目を変更します。

config.load_defaults 5.2
  ↓
config.load_defaults 6.0

また、新たに作成されたnew_framework_defaults_6_0.rbというファイルも不要ですので削除します。

最終確認

上記書き換え後に再度動作確認をします。問題なければアップグレード作業はこれで完了になります。

当記事では最低限の作業のみ記載しておりますが、RSpecで問題なくテストがパスするか、DEPRECATIONメッセージが出力されていないか、ステージング環境で問題なく動作するか等の作業が発生します。

また、バージョンによっては独自の作業が必要な場合もありますので、必ずRails アップグレードガイド等の内容もチェックすることをオススメします。

React+Reduxのアクション名のコンフリクトを避ける方法

React+Reduxのパターンで開発しているとアクション名の衝突を避けるために管理が煩雑になってきます。

例えば、ある機能でINCREMENTというアクション名をすでに使用している場合、他の機能で同じような振る舞いのアクションを定義する場合はINCREMENTという命名を避けなければなりません。

これは、アクション名が衝突するとそれぞれに定義されたreducerが発火してしまい意図しない動作を引き起こしてしまうためです。

そのため、XXX/INCREMENTZZZ/INCREMENTのように機能ごとのprefixをつけてアクション名の衝突を避ける工夫が必要になります。

redux-actionsを使ってアクション名の衝突を避ける方法

アクションの定義にはredux-actionsというライブラリを使用しているケースが多いかと思います。

redux-actionsを使用することで上記のようなアクション名の衝突を避けるような命名をすることが出来ます。

const { increment, decrement } = createActions(
  'INCREMENT', 
  'DECREMENT', 
  { prefix: 'XXX' },
);

createActionsのオプションにprefixを設定することで、アクション名がXXX/INCREMENTXXX/DECREMENTという命名に変更されます。

同様にreducer側で使用するhandleActionsメソッドにおいてもprefixを指定する必要があります。

export default handleActions({
  [actionTypes.INCREMENT]: (prevState, action) => {
    return {
      ...prevState,
      count: count + 1,
    };
  },
  [actionTypes.DECREMENT]: (prevState, action) => {
    return {
      ...prevState,
      count: count - 1,
    };
  },
}, defaultState, { prefix: XXX });

github.com

また、注意点としてredux-sagaを使用している場合、takeEvery等でアクション名のパターンマッチングをしていますので、そこの考慮も必要になります。 redux-actionsとは別のライブラリですので、仕様のギャップは吸収しなければなりません。

export default function* () {
  yield takeEvery(`XXX/${actionTypes.INCREMENT}`), increment);
  yield takeEvery(`XXX/${actionTypes.DECREMENT}`), decrement);
}

Herokuアプリの監視サービス「Pingdom」のススメ

Herokuで本格的にアプリを運用するためには、アプリがダウンせずに動き続けているのかを監視する必要があります。

Herokuアプリの死活監視アドオンとして有名なのがPingdomですが、実は死活監視以外に様々な機能を提供しており、かなりコスパが高いアドオンです。 恥ずかしながら私自身は1年間ほどPingdomを死活監視のみで使用していました。

この記事ではPingdomでできる以下の4つの機能を事例を交えながら紹介します。

死活監視(Uptime)

Pingdomではアプリが利用できる状態であるか死活監視することができます。

Heroku単体でも監視することは出来ますが、Heroku自体が落ちてしまったら監視することは困難になります。 そのため、監視サーバーは基本的には外形監視(外部のサーバーから監視する)にするのが望ましいです。

Uptimeでは指定したURLに対して細かな条件を指定してアプリの死活監視することが出来ます。

  • 死活監視の頻度(1分~60分の間)を設定できる
  • サーバーのタイプをWeb(http・https)、メールサーバー、ネットワークアクセス(ping等)の3種類から選べる
  • どの国からアクセスするか選択できる
  • 問題があった場合の通知方法と復帰した場合の通知方法(webhook、メール) など...

Uptimeのダッシュボード画面イメージ

アプリがダウンした場合にエンドユーザーからの連絡により判明するというのは絶対に避けなければなりません。なるべく早くダウンを検知することで復旧時間を早めることも可能ですし、ユーザーにとってクリティカルな業務の代替案を提案することも可能になります。

ユーザーアクセス解析(Real User Monitoring)

PingdomではReal User Monitoring(RUI)という機能により、エンドユーザーがアプリをどのような操作・体験をしているのかを可視化することができます。イメージ的にはGoogle Analyticsの高機能版という感じです。

  • アプリにユーザー何人訪れているかリアルタイムおよび履歴が確認できる
  • ユーザーが使用しているデバイスやブラウザの割合
  • ページ閲覧時のユーザーの快適度をApdexという指標で確認できる
  • ページごとのアクセス数のランキングを表示できる
  • ページのロード時間の履歴を確認できる など...

RUIのダッシュボード画面イメージ

アクティブユーザー数をリアルタイムに監視できるので、どのくらいのユーザーが同時アクセスしているのか履歴を追って確認できます。

サーバーに負荷がある時間帯とアクティブユーザー数の相関関係を確認するデータとして有益に利用できます。また、リリースする際になるべく影響の少ない時間帯は何時なのか把握することが出来ますので、ユーザー数の少ない時間帯を狙ったリリース作業を行うことが可能です。

アクセス数ランキングでは、自分たちが想定していなかったページが意外と使われていたりすることがあり、今後の改善や新機能の提案に結びつくこともあります。

E2Eテスト(Transactions)

Transactionsという機能を使うことで、エンドユーザーがアプリ上で行う操作をシナリオを組むことで自動的にシミュレーションして、シナリオが正しく動くかどうかをテストすることが出来ます。 いわゆるE2Eテストをpingdom上で簡単に組むことが出来ます。

Transactionsの編集画面イメージ

E2Eテストというと導入と保守のハードルが高いというイメージがありますが、Transactionsを使用することでアプリ側には特段何も設定せずに、シナリオテストを簡単に構築することが出来ます。

複雑なシナリオも組もうと思えば組めるとは思いますが、あくまでもUI上での操作になりますので、弊社ではクリティカルな導線のみ取り入れております。

スピードテスト(Page Speed)

Page Speedを利用することで、様々な観点からページのスピードテストを行い、ページのパフォーマンス改善をするための材料を提供します。

弊社ではSaaSを運用しているため、ログイン後のページがメインになります。 Page Speedではオープンなページに対してスピードテストを提供していますので、弊社のようなプライベートなSaaSにおいてはあまり効果がないと判断し、使用を見送りました。

ちなみにPage Speedの簡易版は無償でも利用することができます。

tools.pingdom.com

異業界からWebエンジニアに転身するために必要なこと

コロナ禍により、リモートワークと相性が良いと言われているエンジニアの転職市場が加速しているように感じています。

SNSの世界にはエンジニアになるためにプログラミング勉強中系のアカウントをよく見かけます。 私の周りにも異業界からエンジニアに転身したいという人が多くいましたが、実際にエンジニアになれた人は半分も満たないというのが実情です。

「なぜ半分以上の人はエンジニアになれなかったのか?」を私なりに分析してみました。本記事ではエンジニアになれた人の共通点を元に私なりの持論を展開したいと思います。

アウトプットせざるを得ない環境をいかに作るかが重要

エンジニアになるために机上の勉強をすることはとても重要ですが、いつまでも勉強だけをしているのは危険です。どこかで区切りをつける必要があります。 プログラミング勉強中という人の中にはいつまでも勉強の域を超えられていない人が多く見受けられるように感じます。

インプットとアウトプットの割合は3:7が良いと言われています。

インプットだけしていては何も生まれません。インプットは続けようと思えばいつまでも続けられるゴールのないマラソンのようなものです。

目的なくインプットだけ続けていては次第に勉強を続けるモチベーションが低下してしまいます。勉強を続けるためにもアウトプットをする必要があるのです。

そのため、まずはアウトプットをせざるを得ない環境に身を投じることが重要です。例えば以下のようアウトプットが挙げられます。

  • スタートアップ企業にインターンシップとしてジョインする
  • プログラミング学習塾でアウトプットする場を設ける
  • 勉強したことをブログに記載してアウトプットする
  • ベンダー資格や基本情報技術者試験等を取得する

最近は、平日は自分の仕事をして週末だけインターンするという複業的な働き方もありますので、それも有効です。

反対に、学習したことをSNSで日報形式で投稿することはあまり有効ではないと思います。 あくまでも学習したことを活かして何かを生み出すことに意味がありますので、報告形式のアウトプットはあまり意味がないと感じています。

アウトプットをすることで自分に足りていない部分が明確になりますので、インプットの質も向上し、相乗効果が期待できます。 また、アウトプットというのは学習を続ける原動力になりますので、途中で投げ出すリスクも減らすことができます。

独学はダメ。なによりもメンターの存在が大きい

プログラミング学習を進めていくと必ず自分だけでは解決できない部分が出てきます。

書籍やブログを参考にして、なんとか動くまで持ってきたが「なぜ動いているのか?」「これが本当に正しい実装方法なのか?」と感じた経験は誰しもあるかと思います。 誰にも相談せずに、これが正しいのだと自分の体に刻んでしまうと、変な癖のある可読性の悪いプログラムを実装し続けることになります。

独学のみでプログラミング学習をステップアップしていくことはオススメできません。

右に行くか左に行くか道に迷ったときに間違った方向を選んでしまうと到達する場所も全く別の場所になります。 スポーツの世界でもコーチのもと正しいフォームでトレーニングを行うことで実力を伸ばせられるのと同じように、エンジニアの世界にもコーチ(メンター)の存在は絶対に必要です。

このコードが正しいのかレビューしてもらうことで新しい技術を習得する機会が与えられますので、確実な成長が見込めます。 独学で学習している方は、一旦学習を止めてメンターを探しに注力したほうが良いです。

メンターを探す方法としておすすめなのが、知人から現役エンジニアを紹介してもらうことです。

「エンジニアに転職したい人がいるから相談に乗って欲しい」という旨を伝えればほとんどのエンジニアは喜んで話を聞いてくれるでしょう。 これは不思議なことで、人は自分の喜びよりも他人の喜びのほうが幸福や充足感を感じる傾向があるので、意外と快く引き受けてくれます。

紹介してもらった現役エンジニアからメンターにしたい人を選ぶと良いでしょう。 現に私自身も現役エンジニアとしての立場から相談を受けてメンターになったことが経験が多くあります。

どうしても周りにエンジニアの知人がいないという方はMENTAというマッチングサービスもありますので、これを使用してみるのも有効かと思います。

終わりに

どのような業界でもはじめの一歩を踏み出すことはとても勇気が必要です。

アウトプットが重要だと理屈では分かっていてもインプットばかりしてしまうのは、変化を嫌う人間にとって当たり前の思考だと思います。 しかし、エンジニアになりたいと本気で思うのならば勇気を持って踏み込むことを強くおすすめします。

  • 強制的にアウトプットする機会を設ける
  • メンターを作る

まずはこの2点を厳守するだけでもエンジニアになれる確率が格段に上がると私は確信しております。

エンジニアになりたかったのに途中で挫折してしまうのはとても残念なことです。 そのような人をなるべく生み出さないためにも、私自身も微力ながらこのような発信を続けていきたいと思います。

【Axlsx】Cellのtypeに:dateを指定しているのに日付型のセル書式にならない

前回同様、またAxlsx関連のマニアック話ですが、なぞ仕様に嵌ってしまったので記録として残しておきます。 同じように悩んでしまっている方の一助になれば幸いです。

Cellのtypeに:dateを指定しているのに日付型のセル書式にならない

以下のようにadd_cellで日付型オブジェクト(Date.today)、typeに:dateを指定しているのにも関わらず、セルの書式が日付型として入力されない事象が発生しました。

p = Axlsx::Package.new
ws = p.workbook.add_worksheet

header_style = ws.styles.add_style(border: { style: :thin, color: '000000' }, bg_color: 'E4E4E4')
value_style = ws.styles.add_style(border: { style: :thin, color: '000000' })

row = ws.add_row
row.add_cell('日付', style: header_style, type: :string)
row.add_cell(Date.today, style: value_style, type: :date)

▼日付型ではなく数値データ入力されている

なお、add_cellメソッドというのはCellをnewしているだけのシンプルなプログラムです。

# Adds a single cell to the row based on the data provided and updates the worksheet's autofit data.
# @return [Cell]
def add_cell(value = '', options = {})
  c = Cell.new(self, value, options)
  self << c
  worksheet.send(:update_column_info, self, [])
  c
end

axlsx/row.rb at 8e7b4b3b7259103452c191f73ce0bf64f66033fe · randym/axlsx · GitHub

ちなみにこの事象は、以下のようにstyleは未指定、type :dateのみ指定する場合だと正しく日付型で入力されます。

p = Axlsx::Package.new
ws = p.workbook.add_worksheet

row = ws.add_row
row.add_cell('日付', type: :string)
row.add_cell(Date.today, type: :date)

▼日付型として入力されている

どうやら、Cellをnewする時にstyleとtype :dateを併用するとセルの書式設定が日付型として認識されないという動きのようです。

styleに明示的にnum_fmtを指定すると日付型の書式が適用される

小一時間ほど悩んでいましたが、以下のドキュメントに答えが記載されていました。

Method: Axlsx::Styles#add_style — Documentation for randym/axlsx (master)

styleにnum_fmtというオプションで日付フォーマットを指定することで、セルの書式に日付型が適用されるようです。

p = Axlsx::Package.new
ws = p.workbook.add_worksheet

header_style = ws.styles.add_style(border: { style: :thin, color: '000000' }, bg_color: 'E4E4E4')
date_style = ws.styles.add_style(num_fmt: Axlsx::NUM_FMT_YYYYMMDD, border: { style: :thin, color: '000000' })

row = ws.add_row
row.add_cell('日付', style: header_style, type: :string)
row.add_cell(Date.today, style: date_style, type: :date)

▼日付型として入力されている

Cellをnewする際に、styleとtype :dateを併用する場合は、明示的にnum_fmtを指定する必要があるようです。

なぜこのような動きになるかというと、Cellクラスのコードを見るとわかるように、デフォルトの日付用style(STYLE_DATE)がオプションで指定したstyleで上書きされてしまうからです。

def cast_value(v)
  return v if v.is_a?(RichText) || v.nil?
  case type
  when :date
    self.style = STYLE_DATE if self.style == 0
    v

axlsx/cell.rb at 8e7b4b3b7259103452c191f73ce0bf64f66033fe · randym/axlsx · GitHub

Cellに対してオプションでstyleを指定してしまうと、typeごとに定義されたデフォルトのstyleが上書きされてしまい、セルの書式設定が無効となるようです。