How-To's Learn RubyMine Personal productivity RubyMine

RubyMine で Stimulus を Rails アプリに使用する

Read this post in other languages:

皆さん、こんにちは!

RubyMine チームは Ruby と Rails の新しいテクノロジーのサポートを提供するための継続的な取り組みを行っています。 Rails に最近追加された最も画期的な手法の 1 つは間違いなく Hotwire だと言えます。そこで、このフレームワーク一式の概要と RubyMine で最も重要な Turbo 機能と Stimulus 機能を Rails アプリに使用するためのチュートリアルを用意しました。 この記事では Stimulus について説明します。Turbo については、前のブログ記事をご覧ください。

Hotwire と Stimulus

Hotwire とは?

Hotwire は JSON の代わりに HTML をワイヤー越しに送信することでウェブ開発を単純化します(名称は「HTML over the wire」の略称です)。 これにより、JavaScript 開発者のコーディング量とアプリケーションからブラウザーへの送信データ量を減らしつつ、テンプレートのレンダリングをサーバー側で実行することができます。 HotwireTurboStimulus、および Strada という複数のフレームワークで構成されています。 この記事では、Stimulus について説明します。

Stimulus とは?

Stimulus は静的 HTML とその既存の DOM 要素と連携するように設計された JavaScript フレームワークです。 ユーザーは DOM 操作に使用できる Stimulus コントローラーに要素を接続することで、JavaScript の機能を DOM に追加することができます。 これは完全な JavaScript フロントエンドを提供することではなく、既存 HTML 要素の機能を強化することを目的としています。

stimulus-rails gem は Rails 7 にデフォルトで同梱されているため、アプリケーションですぐに使用し始めることができます!

RubyMine はコード補完、移動操作、Rename(名前の変更)リファクタリングなどの Stimulus のサポートを提供しています。ぜひこのチュートリアルを読みながらお試しください。

チュートリアル: RubyMine で Stimulus を Rails アプリに使用する

このチュートリアルでは、基本的な Stimulus のビルディングブロックを使用して JavaScript を簡単にアプリケーションに統合する方法を説明します。 ユーザーがアカウントを作成し、ミニ投稿を作成し、相互フォローを行い、フィードでミニ投稿を読むことのできるサンプル Rails アプリケーションを使用します。

サンプル Rails アプリをクローンする

以下の手順に従ってサンプルアプリをクローンし、実行してください。

  1. https://github.com/JetBrains/sample_rails_app_7th_ed/tree/hotwire_setup にあるサンプルアプリケーションをチェックアウトします(プロジェクトをクローンしたら必ず hotwire_setup ブランチに切り替えてください)。 Git リポジトリをセットアップする方法の詳細については、ドキュメントをご覧ください。
  2. Ruby インタープリターを指定して gem をインストールします。
サンプルアプリ内の Alice さんのフィード

簡単な Copy to clipboard(クリップボードにコピー)ボタンを作成して、Stimulus の実際の動作を詳しく見てみましょう。

Copy to clipboard ボタン

Delete(削除)リンクの横に Copy to clipboard ボタンを追加しましょう。 この新しいボタンが意図する機能は、クリックしたときにミニ投稿のテキストをコピーすることです。

1. _micropost.html.erb ファイルを開き、ボタンをビューに追加します。

<% if current_user?(micropost.user) %>
  <%= link_to "delete", micropost, data: { "turbo-method": :delete,
                                           turbo_confirm: "You sure?" } %>
<% end %>

<%= button_tag "copy", class: "btn btn-link button-link-aligned" %>

1 回のマウスクリックでテキストをコピーするには、JavaScript を使用する必要があります。 これを行うために、ボタンを Stimulus コントローラーに接続しましょう。

2. RubyMine の Run Anything(何でも実行)機能(Ctrl+Ctrl)を使って、次のコマンドを実行します: rails generate stimulus clipboard

Run Anything(何でも実行)で Stimulus コントローラーを生成する

このコマンドは DOM 要素と JavaScript 間の接続を作成できるコンポーネントである Stimulus コントローラーを生成します。

コントローラーの名前を clipboard とします。 このコマンドは app/javascript/controllers ディレクトリに clipboard_controller.js というファイルを生成します。 このディレクトリのコントローラーは自動的に読み込まれます(ファイル index.js をご覧ください)。

3. ファイル _micropost.html.erbdata-controller 属性を追加します。

 

これにより、ミニ投稿を clipboard コントローラーに接続できるようになります。 デバッグログの記録を connect メソッドに追加してからページの読み込み時にブラウザーコンソールを確認すると、コントローラーに正常に接続したかどうかを確認できます。

4. _micropost.html.erbcopy ボタンを更新します。

<%=button_tag "copy", class: "btn btn-link button-link-aligned", data: { action: "clipboard#copy"} %>

ボタンをクリックしたときにテキストをコピーするという特定のアクションを実行するようにします。 このテキストは特定のターゲット、つまりミニ投稿の本文から取得する必要があります。 アクションとターゲットは Stimulus の基本概念です。

アクションはコントローラーのメソッドと DOM イベントに接続します。 data-action 属性を使用してコントローラーのメソッドを指定することで、要素をクリックしたり、テキストを入力したり、フォームを送信したりする際に JavaScript コードを呼び出すことができます。 上記のコードのボタンではまさにこれを行いました。

5. clipboard コントローラーに copy メソッドを追加します。

export default class extends Controller {
  connect() {
  }
  
  copy() {    
  }
}

テキストをコピーするには navigator.clipboard.writeText メソッドを使用できますが、テキストがコピーされる要素を取得する方法が必要です。 そこでターゲットの出番です。

6. 以下の行をコントローラークラスに貼り付けて、ターゲットをコントローラーに追加します。

static targets = ["source"];

ターゲットを使うことで、コントローラーで操作する DOM 要素を参照できます。

コントローラークラスの静的フィールド内ではコントローラーが使用するターゲットを指定できます。

7. _micropost.html.erb でミニ投稿のコンテンツを div タグで囲みます。

  <%= micropost.content %>

HTML 内では次の例のように data-[controller-name]-target 属性を使用してコントローラーターゲットを参照できます: data-clipboard-target = "source"

8. copy メソッドの本体を完成させます。

// clipboard_controller.js

copy() {
  navigator.clipboard.writeText(this.sourceTarget.textContent);
}

JavaScript コントローラー内では this.sourceTarget を使用してターゲットを参照できます。

navigator.clipboard安全なコンテキスト内でしか使用できないことに注意してください。 ローカル開発の場合は localhost でアプリを実行できます。

以上です!  ボタンを 1 回クリックするだけでミニ投稿のコンテンツをクリップボードにコピーできるようになりました。

フィードからミニ投稿を非表示にする

Stimulus の他の重要な概念には、値、クラス、およびアウトレットがあります。

値をコントローラー要素の属性として渡し、その値を JavaScript コードで使用したい場合があります。 Stimulus のはまさにこの動作を実現するために設計されています。 次のように data-controller 属性の横に値を指定します。

  •  

ここで、clipboard はコントローラーの名前であり、copyable はコントローラーで読み取る(または書き込む)プロパティの名前です。 次に、以下のように特殊な静的オブジェクトのコントローラーで値の名前と型を定義します。

// clipboard_controller.js

static values = { copyable: Boolean };

使用できる型についての詳細は、ドキュメントをご覧ください。

JavaScript では値を this.[valueName]Value などのように参照できます。

// clipboard_controller.js

if (!this.copyableValue) {
  ...
}

アウトレットでは、コントローラー内から他の Stimulus コントローラーと要素を参照できます。こうすることで、プロジェクト内の様々なコントローラーが相互に通信することができます。

クラスは CSS クラスを参照しており、JavaScript コードからプログラムでクラスを操作できます。 ワンクリックでフィードのミニ投稿を非表示にできるコントローラーを実装し、クラスとどのように連携するのかを詳しく見てみましょう。 要素のスタイルを直接操作する(target.style.display = "none")ことで同じ効果を得られますが、ここでは、要素の CSS クラスを設定して達成してみましょう。 この手法はその他多数の視覚的効果にも再利用できます。

1. custom.scss ファイルを開き、次のクラスを追加します。

.invisible {
  display: none
}

2. 投稿の表示状態を操作するコントローラーを生成します: rails generate stimulus visibility

3. ファイル visibility_controller.js を開き、クラスとターゲットをコントローラーに追加します。

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static classes = ["hidden"];
  static targets = ["hide"];
}

4. コントローラーに hide メソッドを追加します。

export default class extends Controller {
  ...
   
  hide() {
    this.hideTarget.classList.add(this.hiddenClass);
  }
}

するとこのメソッドは CSS クラス hidden をターゲットの hide に追記します。

5. 次の HTML 属性を追加して、新しいコントローラーとクラスをミニ投稿ビューテンプレートに追加します。

  •  

まず、別のコントローラー(data-controller="clipboard visibility")を追加します。 次に、実際にどの CSS クラスがコントローラーの論理クラスに対応するかを指定します: data-visibility-hidden-class="invisible"。 クラスはそれが属するコントローラーと同じ要素に指定する必要があります。 さらに、非表示にするターゲットを指定します。ここではミニ投稿全体がターゲットです。

6. _micropost.html.erb で各ミニ投稿の右に hide ボタンを追加します。


  ...
  <%= button_tag "hide", class: "btn btn-link button-link-aligned", data: { action: "visibility#hide" }, style: "float: right;" %>

このボタンは Stimulus アクションを使用してコントローラーメソッドの hide に接続します。

このボタンをクリックすると、対応するミニ投稿が非表示になります。

…ページが再読み込みされるまでです。

ここで、ミニ投稿の非表示の状態を永続化する方法はたくさんあります。 たとえば、Stimulus の値を使用して hiddenというブール値を作成することができます。 すると、この値が true である要素に hidden クラスが自動的に追加されます。 または、単にデータベースからそのようなミニ投稿を読み込まないようにすることも可能です。 これを行うには Rails アプリに何かを送り返す必要がありますが、これは単純なリクエストを送信することで実現できます。 Stimulus コントローラーの hide メソッドをもう一度確認しましょう。

7. 次のコードを visibility_controller.jshide メソッドに追加します。

hide(e) {
  this.hideTarget.classList.add(this.hiddenClass);
 
  const id = e.target.dataset.id
  const csrfToken = document.querySelector("[name='csrf-token']").content
  fetch(`/microposts/${id}/hide`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-CSRF-Token': csrfToken
    },
    body: JSON.stringify({ hidden: true })
  }).then(response => response.json())
}

これで非常に基本的な microposts/:id/hide への POST リクエストを送信するようになりました。 リクエストの本文では何でも送信できますが、ここでは hidden が true であることを記述しましょう。

このコードはビューから受信する必要のあるミニ投稿の ID を使用していることに注意してください。

8. この ID を _micropost.html.erb でアクションの横のデータハッシュに渡します。

<%= button_tag "hide",
              class: "btn btn-link button-link-aligned",
              data: { action: "visibility#hide", id: micropost.id },
              style: "float: right;" %>

9. 次のルートを routes.rb に追加します。

post 'microposts/:id/hide', to: 'microposts#hide'

10. hide メソッドを microposts_controller.rb に追加します。

def hide
  @micropost = Micropost.find(params[:id])
  hidden_post = HiddenPost.new
  hidden_post.user = current_user
  hidden_post.micropost = @micropost
  hidden_post.save
end

11. Run Anything(何でも実行)を使用して、特定のユーザーに対して非表示の投稿に関する情報を保存するモデルを生成します。

rails generate model HiddenPost user:references micropost:references

12. user.rb ファイルで User#feed メソッドを更新します。

# user.rb

def feed
  following_ids = "SELECT followed_id FROM relationships
                  WHERE  follower_id = :user_id"
  hidden_posts_for_user = "SELECT micropost_id FROM hidden_posts WHERE user_id = :user_id OR user_id IN (#{following_ids})"
  Micropost.where("user_id IN (#{following_ids})
                  OR user_id = :user_id", user_id: id)
          .where("id NOT IN (#{hidden_posts_for_user})", user_id: id)
          .includes(:user, image_attachment: :blob)
end

非表示の投稿に関する情報をデータベースに保存したので、該当する投稿はページが再読み込みされた後でもユーザーフィードに表示されなくなります。

Hide ボタンの動作

まとめ

このチュートリアルでは、Stimulus フレームワークとその基本概念であるコントローラー、ターゲット、およびアクションを詳しく説明しました。 Rails アプリケーションで Stimulus を使って DOM 要素を JavaScript に簡単に接続する方法を学習しました。

Ruby および Rails 用の JetBrains IDE で Hotwire のサポートを活用しましょう。 最新の RubyMine バージョンは弊社ウェブサイトか無料の Toolbox App からダウンロードできます。

最新機能のリリース時にその内容を詳しく知るには、RubyMine の X をフォローしてください。

以下のコメント欄で皆さんのご感想をお聞かせください。また、新機能の提案と投票は課題トラッカーをご利用ください。

オリジナル(英語)ブログ投稿記事の作者:

Darya Sharkova

Darya Sharkova

image description