@z_ohnamiです。こんにちは。
railsの7から導入されたHotwireを使って、JavaScriptをできる限り書かずに正規表現チェッカーを作ってみました。
正規表現チェッカーといえば、Rublarというサイトが以前から存在していて私は日頃からお世話になっていたのですが、見た目はそちらを参考にして作りました。
名前は「RegExpress」にしました。
https://tools.bigwave.biz/regular_expressions
以下のように、正規表現のパターンとテスト用の文字列を入力した時点で結果が返ってきます。
Hotwireを使わずにrailsで作った場合はformのsubmitをしてもらわないと結果を返すことができません。
また、テキストエリアをポンポンと追加していますが、これもRailsのみで実現しています。
こうした動的かつ部分的な画面の変更自体は特に珍しいものではりませんが、
これをrailsのみで実現できているところがHotwireのGood pointになります。
Hotwireを使ってみよう
さて、ここからは自分でゼロから試してみたい方向けにハンズオン的な解説をしていこうと思います。
ここでは冗長なCSSやHTMLを省いたソースコードを例にHotwireの使い方を一部お伝えします。
今回はHotwireが提供している機能のひとつ、Turbo Frameを使います。
Turbo Frameを使うと、ユーザーからの何らかのリクエストに応じて画面を部分的に書き換えることが可能となります。
サンプルのソースコードはこちらです。
https://github.com/z-ohnami/hotwire-regexp
セットアップ
rails newするところからやっていきましょう。
コマンドを実行していきます。
$ cd {アプリのディレクトリへ}
$ echo 3.1.2 > .ruby-version
$ bundle init
$ bundle install # Gemfile中のgem "rails"をコメントを解除して実行
$ rails new . --css=sass --javascript=esbuild --skip-jbuilder --skip-action-mailbox --skip-action-mailer --skip-test --skip-active-storage --skip-action-text
$ bin/dev
これでとりあえず、http://localhost:3000 にアクセスするとrailsのデフォルト画面が表示されるはずです。
設定ファイルなど
rails newをした後に、以下のように設定ファイルを更新、追加しています。
これらはお好みで適宜整えてください。
viewのテンプレートにはslimを使っています。
もし、同様にslimで作る場合は、Gemfileを更新後にbundle installをお願いします。
更新
Gemfile
config/application.rb
config/routes.rb
app/assets/stylesheets/application.sass.scss
追加
config/locales/ja.yml
config/locales/model.ja.yml
modelを作る
RegularExpression
というmodelを作り、ここで正規表現の成否を判定します。
今回はデータベースを使いませんが、ActiveModelをincludeしてバリデーションなどは使えるようにしています。
app/models/regular_expression.rb
class RegularExpression
include ActiveModel::Model
# 正規表現のパターン文字列
attr_reader :expression
# 正規表現をかける対象となる文字列
attr_reader :test_string
validate :check_expression
def initialize(expression: '', test_string: '')
@expression = expression
@test_string = test_string
end
# 画面が初期表示、未入力の状態であるかを判定するのに使用。
def unready?
@expression.blank? || @test_string.blank?
end
private
def regexp
@regexp ||= Regexp.new(expression)
end
# この結果を正規表現の判定結果として表示している
def check_expression
return if regexp.match?(test_string)
errors.add(:base, :invalid, message: '一致しませんでした...')
end
end
controllerを作る
controllerは基本的にHotwireを使わないときに作るものと変わらないですね。
app/controllers/regular_expressions_controller.rb
class RegularExpressionsController < ApplicationController
def index
@regular_expression = RegularExpression.new
end
def create
@regular_expression = RegularExpression.new(expression: regexp_params[:expression], test_string: regexp_params[:test_string])
render :index
end
private
def regexp_params
params
.require(:regular_expression)
.permit(:expression, :test_string)
end
end
viewを作る
1枚っぺらのviewファイルを作ります。
submitのボタンがないのがポイントです。
テキストフィールドに文字列が打ち込まれるたびにsubmitが実行され、正規表現のチェック結果が返ってきます。
(先ほどのmodelで定義したバリデーションが実行される)
app/views/regular_expressions/index.html.slim
.container
h1 正規表現チェッカー
= form_for @regular_expression,
url: regular_expressions_path,
html: { data: { turbo_frame: 'regexp', controller: 'regexp', action: 'input->regexp#submit' } } do |f|
.field
= f.label :expression
= f.text_field :expression, size: 80, placeholder: ''
.field
= f.label :test_string
= f.text_area :test_string, size: '60x6', placeholder: '', class: ''
/ [ポイント]
/ 動的かつ部分的に変わる部分をturbo_frame_tagで囲む。
/ Turbo Frameは1ヶ所しか指定できない。
/ 複数箇所を同時に差し替えたいときはTurbo Streamを使う
ul.result_message
= turbo_frame_tag 'regexp' do
- if @regular_expression.unready?
li 結果はここに出力されます。
- elsif @regular_expression.valid?
li.result_message__success マッチしました!!
- else
- @regular_expression.errors.full_messages.each do |message|
li.result_message__fail = message
inputを検知してsubmitを実行する
submitボタンを実行してもらう代わりに、ユーザーの打ち込みをアプリ側で検知してsubmitをしなければなりません。
Hotwireではそうしたインプットイベントなどを検知するときにはStimulusを使用することがおすすめされているようです。
以下のようにコマンドを実行することでStimulusの雛形を生成することができます。
今回はregexpという名前を付けています。
$ rails g stimulus regexp
あとは、以下のように任意の処理を書き込んでいけばOKです。
submitという関数を書き、そこでformのsubmitを実行しています。
app/javascript/controllers/regexp_controller.js
import { Controller } from "@hotwired/stimulus"
// Connects to data-controller="regexp"
export default class extends Controller {
submit() {
clearTimeout(this.timeout)
this.timeout = setTimeout(() => {
this.element.requestSubmit()
}, 200)
}
}
submit関数の中で単純にthis.element.requestSubmit()
を実行するように書くとinputの中身が変わるたびに高頻度でバックエンドにPOSTが飛んでしまいます。
そこで実行回数を適当に減らすためにdebounceをしています。
debounceについてはこちらの記事が大変参考になりました。
(というか、そのまま利用させていただいております)
そして、このjsはviewファイルに書いた以下の設定で紐つけられています。
inputイベントが発火するときにregexp_controller.jsのsubmit関数が実行されるようにしています。
ほかにも、clickとかいくつか検知できるイベントがあり、同様に紐つけが可能ですね。
data: { turbo_frame: 'regexp', controller: 'regexp', action: 'input->regexp#submit' }
以上で最低限の正規表現チェッカーは動作すると思います!
Hotwireをキメると気持ちいい
Hotwireで得られる開発体験は自分にとってかなり快適に感じました。
ぶっちゃけた話、Railsでアプリを作るときはJavaSccriptを書きたくない(!)というのが本音で、そうしたストレスが大幅に減少されるのはかなり気持ちがいいです。
実戦レベルのプロダクトではjsをゼロにはできないと思いますが、Stimulusのルールに則り、子気味よく書けるのも自分にはありがたく感じました。
viewファイルが一箇所に同じ書き方でまとまりやすいのもいいところですね。
ReactとかVueを入れると、それらのコンポーネントはrailsのapp/views
とは別になってしまいますが、そうしたviewファイルの分離が発生しません。
また、既存のRailsのプロジェクトにHotwireを部分的に導入して試す、といったことが容易にできるのもいいところですね。
もし、Hotwireをまだ使ったことがないようでしたら、この素晴らしき開発体験をあなたも是非!!