LoginSignup
69
32

More than 1 year has passed since last update.

「なんかかっこいいから!!」【Rails7 Hotwire】を使ってSPA化した簡単なアプリを作ってみた(浅っ!!!)

Last updated at Posted at 2022-12-13

はじめに

この記事はRUNTEQ Advent Calendar 2022 14日目のものです。

はじめまして!プログラミングスクールRUNTEQにて、未経験からエンジニア転職を目指して学習中の柴犬です。🐶

「これからポートフォリオを作成するぞ!!」「Hotwireって何ができるの?」となってる方々が興味を持ってくれたらいいなぁと思って書いてみました。

Rails7やHotwireについては下記の「事前知識」に軽くまとめました。必要ない方はスキップして下さい🙏

事前知識

Rails6から7の変更点

  • JavaScriptバンドラーの変更
    Rails6でデフォルトだったwebpackerからRails7ではimportmap-railsを使うことになりました。

    importmap-railsによってJavaScriptのトランスパイルやビルドを必要とせず、CDN経由でライブラリを取得できるようになりました。
    つまりWebpackやYarn、npmに依存せず開発ができる。一方でReactやTypeScriptの相性は良くないみたいです:sob:

    importmapを試してみたい方はこちらが参考になります。

  • Hotwireの導入
    Hotwireを簡単に説明すると、JavaScriptをあまり使わずにSPA(Single Page Application)を実現するための仕組みです。

    ※その他の変更点は自身で調べてみて下さい🙇‍♂️
      Rails 7: importmap-rails gem README(翻訳)
      Rails 7.0 で標準になった importmap-rails とは何なのか?

Hotwireとは?

Html Over The WIREの略です。必殺技みたいでかっこいいですね!!

ReactやVueではクライアントからのリクエストに対して、サーバー側がJSONを返し、それを受け取ったクライアントがDOMを構築してHTMLをレンダリングします。

一方、Hotwireではクライアントからのリクエストに対して、サーバー側がHTMLを返すので、クライアント側でレンダリングする必要はありません。

つまり、開発者はサーバーサイドのみに集中できます。🙌

Hotwireは以下の技術の総称です。(※今回はTurboだけ使いました。)

  • Turbo(Web開発向け)
    • Turbo Drive
    • Turbo Frames
    • Turbo Streams
    • Turbo Native(モバイルアプリ開発向け)
  • Stimulus(Web開発向け)
  • Strada(モバイルアプリ開発向け)

※詳細は割愛します。気になる方はこちら
Hotwireとは?
Hotwireとは何なのか?

作ったもの

RUNTEQ Advent Calendar 2022 12/7日担当の方の記事
とテーマが被ってしまった(いや、ほんと偶然です笑)ため、一部割愛します。

以下の機能をもった画像投稿アプリ

・投稿の編集
Image from Gyazo
・投稿の削除
Image from Gyazo
・ページネーション(無限スクロール)
Image from Gyazo
・検索機能
Image from Gyazo

開発環境・使用技術

・M1 macbookAir 2020
・ruby3.1.0            
・rails 7.0.4
・esbuild
・node 16.18.1
・yarn 1.3.2
・tailwind css
・daisyUI

セットアップ(ほぼ自分用のメモです🙇‍♂️)

rails new

rails new post-hotwire -c tailwind -j esbuild -d postgresql -skip-jbuilder –skip-action-mailbox –skip-action-text -MCT
rails new時のオプション

-j esbuild
importmapではdaisyUIが使えません。(2022/12/14時点)
webpacker(shakapacker)やesbuildを使う必要があるのですが、今回は後者を使いました。(脱Node.jsならず:cry:)

JavaScriptバンドラーを指定せずに、-c tailwindとするとデフォルトでimportmap-railsが使われます。
今回はesbuildを指定しているのでcssbuilding-rails経由でtailwind cssをインストールしています。
cssを指定せずにrails new後、tailwindcss-railsをインストールするとscaffold実行時に勝手にレイアウトを整えてくれるのでより簡単です。

-C
Turbo Streams(WebSocket)を使う際は内部的にRedisとAction Cableを使うので注意しましょう。

参考
Rails 7 : rails newのフロントエンド関連オプションの組み合わせを調べてみた

DB作成

bin/rails db:create
Created database 'post-hotwire_development'
Created database 'post-hotwire_test'

tailwindの公式プラグインをインストール

yarn add @tailwindcss/typography @tailwindcss/forms @tailwindcss/line-clamp @tailwindcss/aspect-ratio

daisyuiをインストール

yarn add daisyui

tailwindの設定を追加

tailwind.config.js
module.exports = {
  content: [
    './app/views/**/*.html.erb',
    './app/helpers/**/*.rb',
    './app/assets/stylesheets/**/*.css',
    './app/javascript/**/*.js'
  ],
  // 以下を追記
  plugins: [
    require('@tailwindcss/forms'),
    require('@tailwindcss/line-clamp'),
    require('@tailwindcss/aspect-ratio'),
    require('@tailwindcss/typography'),
    require("daisyui")
  ]
}

各機能の説明

さて、ここからが本番です。(前置きが長い?アーニャさんのように大人の余裕を持ちましょう!)
各機能でURLが変わっていないことにも注目してくださいね!🔎

1. 投稿の編集

Image from Gyazo

app/controllers/posts_controller.rb
  # 一部省略
  def edit; end

  def update
    if @post.update(post_params)
      flash.now.notice = '投稿を更新しました。'
    else
      flash.now.alert = '投稿を更新できませんでした。'
      render :edit, status: :unprocessable_entity
    end
  end

if節がtrueのとき、Railsがよしなにupdate.turbo_stream.erbrenderしてくれます。

update.turbo_stream.erb
<%= turbo_stream.replace @post %>
<%= turbo_stream_flash %>

上記を省略せずに書くと

update.turbo_stream.erb
<%= turbo_stream.replace dom_id(@post) do %>  <%#= dom_id(@post)で"post_○"を指す =%>
  <%= render partial: "posts/post", locals: { payment: @payment } %>
<% end %>
<%= turbo_stream_flash %>

こうなります。Railsのよしな力高すぎんか?
replaceでid="post_○"が一致するものを書き換えます。

app/views/posts/_post.html.erb
<%# 長くなるので省略 %>
<%= turbo_frame_tag post do %>
  <%= image_tag post.image %>
  <%= post.title %>
  <%= post.content %>
<% end %>
<!-- 上記コードで生成されるHTML -->
<turbo_frame id="post_○">
  <img src="○○">
  タイトル
  内容
</turbo_frame>

turbo_stream_flashはフラッシュメッセージを表示する独自メソッドとして実装しました。

app/helpers/application_helper.rb
module ApplicationHelper
  def turbo_stream_flash
    turbo_stream.update 'flash', partial: 'shared/flash'
  end
end

2. 投稿の削除

Image from Gyazo

app/controllers/posts_controller.rb
  def destroy
    @post.destroy!
    flash.now.notice = '投稿を削除しました。'
  end
app/views/posts/destroy.turbo_stream.erb
<%= turbo_stream.remove @post %>
<%= turbo_stream_flash %>

理屈は編集のときと同じです。
removeで指定したidがつけられているturb_frameタグで囲まれた投稿を削除します。

※ちなみに新規投稿の際は同じ理屈でprependメソッドを使いました。

3. ページネーション(無限スクロール)

Image from Gyazo

gem kaminariを使って実装しました。

app/views/posts/index.html.erb
<%= turbo_frame_tag "posts-page-#{@posts.current_page}" do %>
  <% if @search_posts %>
    <%= render @search_posts %>    
  <% else %>
    <%= render @posts %>
  <% end %>
  <%= turbo_frame_tag "posts-page-#{@posts.next_page}", loading: :lazy, src: path_to_next_page(@posts) %>
<% end %>

path_to_nex_page(@posts)はkaminariのメソッドで/posts2/など次のページのパスを返します。
src属性を指定すると、/posts2/のページロード時ににマッチした部分を置き換えます。
さらにloadingオプションをlazyにするとページロード時ではなくタグがページに現れたときに読み込むようになります。
以上の流れで無限スクロールを実装しています。(難しい…)
https://techracho.bpsinc.jp/hachi8833/2021_09_17/111148
https://turbo.hotwired.dev/reference/frames#lazy-loaded-frame

4. 検索

Image from Gyazo

gem ransackを使って実装しました。

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :set_search

  def set_search
    @search = Post.ransack(params[:q])
    @search_posts = @search.result.order(created_at: :desc).page(params[:page]).per(12)
  end
end
app/views/shared/_header.html.erb
<%# 一部抜粋&省略 %>
<%= search_form_for @search, html: { data: { turbo_frame: "posts-list" } }, url: posts_path do |f| %>
  <%= f.search_field :title_or_content_cont, placeholder: "検索ワード" ,class: "input input-bordered" %>
  <%= f.submit class: "btn btn-ghost btn-circle" %>
<% end %>
index.html.erb
<%= turbo_frame_tag "posts-list" do %>
  <%# 全てのコードをturbo_frame_tagで囲む %>
<% end %>

index.html.erb内のコードは全てturbo_frame_tagで囲み、id="posts-list"としているので、検索を実行するとindex.html全てが検索結果の投稿に置き換わります。

最後に

今回は「なんかかっこいいから!!」全部SPAにしてみました。(浅っ!!!)
実際には開発スピード重視で一部にHotwireを導入する使い方で良いと思います。

VueやReactほど細かいことはできませんが、Hotwierを使えばある程度同じ機能は簡単に実現できそうです。
これこそRailsの思想に基づいたフロントエンド開発って感じがして個人的に好きです。笑

「バックエンドはRails、フロントエンドはReact(Next.js)でSPA作ってやるぜ!!」「だけど、なるべく時間をかけたくない…」「開発効率もUI/UXも重視ししたい…」

そこのあなた、Hotwireを使って快適なRailsライフを送ってみませんか?

69
32
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
69
32