2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Hotwireを使って正規表現チェッカーを作ろう

Posted at

@z_ohnamiです。こんにちは。
railsの7から導入されたHotwireを使って、JavaScriptをできる限り書かずに正規表現チェッカーを作ってみました。

正規表現チェッカーといえば、Rublarというサイトが以前から存在していて私は日頃からお世話になっていたのですが、見た目はそちらを参考にして作りました。

名前は「RegExpress」にしました。
https://tools.bigwave.biz/regular_expressions
ogp_image.png
以下のように、正規表現のパターンとテスト用の文字列を入力した時点で結果が返ってきます。

69168d9afa163a0a1218155b7df228fb.gif

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

完成するとこんな感じに仕上がります。
d79c0a2392ca98222ad62bce96d9e2d6.gif

セットアップ

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をまだ使ったことがないようでしたら、この素晴らしき開発体験をあなたも是非!!

参考文献

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?