はじめに
Railsでトップページや管理画面のダッシュボードなど、表示するコンテンツが多いページを作るときに1つのコントローラーで全てを取得するように作ると物によっては遅くなってしまうのではないでしょうか。
対策としてページ表示後にコンテンツを読み込むように Stimulus
を使って実装してみようと思います。
こういった手法を遅延読み込み
や遅延ロード
と言ったりします。英語だとlazy load
です。
よく画像の表示で使われたりします。
デモ
ソースコードはこちら
実装
環境
- Rails 6.1.3.1
- Ruby 2.7.2
- Yarn 1.22.5
- Node 14
- Docker
流れ
!!!注意!!!
今回Dockerを使っていますが、ここではDockerについては割愛しています。なのでdocker-composeなどのコマンドを省略しています。実際に再現する場合は各自の環境に合わせてコマンドや設定を変更してください。
今回Stimulusを使うのでwebpackでインストールします。
rails new my_app --webpack=stimulus
localhost:3000を確認します。
rails s
この時点でapp/javascript
配下に作成されたファイルを確認します。
rails newするときに--webpack=stimulus
を指定したのでStimulusのcontrollerを読み込む設定が作成されています。
import Rails from "@rails/ujs"
import Turbolinks from "turbolinks"
import * as ActiveStorage from "@rails/activestorage"
import "channels"
Rails.start()
Turbolinks.start()
ActiveStorage.start()
import "controllers" // これ
import { Application } from "stimulus"
import { definitionsFromContext } from "stimulus/webpack-helpers"
const application = Application.start()
const context = require.context("controllers", true, /_controller\.js$/)
application.load(definitionsFromContext(context))
import { Controller } from "stimulus"
export default class extends Controller {
static targets = [ "output" ]
connect() {
this.outputTarget.textContent = 'Hello, Stimulus!'
}
}
まずはCRUD機能を作成していきます。
デモにあるように、今回は投稿とコメントの2つを作成して、投稿詳細ページを表示後にコメントを読み込むといった形を実装してみます。
$ rails g scaffold posts title body:text
$ rails g model comment post:references body:text
$ rails db:migrate
投稿詳細ページでコメントを閲覧・作成できるように変更します。
Rails.application.routes.draw do
resources :posts do
resources :comments, only: %i(index create destroy)
end
end
class Post < ApplicationRecord
has_many :comments, dependent: :destroy
end
class CommentsController < ApplicationController
before_action :set_post
def create
@comment = @post.comments.new(comment_params)
respond_to do |format|
if @comment.save
format.html { redirect_to @post, notice: "コメントしました" }
format.json { render :show, status: :created, location: @post }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @comment.errors, status: :unprocessable_entity }
end
end
end
def destroy
@comment = @post.comments.find(params[:id])
@comment.destroy
respond_to do |format|
format.html { redirect_to @post, notice: "コメントを削除しました" }
format.json { head :no_content }
end
end
private
def set_post
@post = Post.find(params[:post_id])
end
def comment_params
params.require(:comment).permit(:body)
end
end
p#notice = notice
p
strong タイトル:
p = @post.title
p
strong 本文:
= simple_format @post.body
=> link_to 'Edit', edit_post_path(@post)
'|
=< link_to 'Back', posts_path
/ 以下を追加
h4 コメント
- if @comments.present?
ul
- @comments.each do |comment|
li
= simple_format comment.body
span = link_to "削除", [@post, comment], data: { confirm: 'Are you sure?' }, method: :delete
- else
p コメントはありません。
hr
= form_with model: [@post, Comment.new] do |f|
.field
= f.label :body, "本文"
br
= f.text_area :body, required: true
.actions = f.submit
ここまでで、投稿詳細ページでコメントできるようになりました。
ただ、コメントはページ時に取得しているので、これをページ表示後に取得するようにStimulusを使っていきます。
JSを書いていってもいいですが、以下のcomponentを使えばさくっと実装できそうなのでこれを使ってみます。
$ yarn add stimulus-content-loader
次にimportします。
import { Application } from "stimulus"
import { definitionsFromContext } from "stimulus/webpack-helpers"
import ContentLoader from "stimulus-content-loader" // 追加
const application = Application.start()
const context = require.context("controllers", true, /_controller\.js$/)
application.load(definitionsFromContext(context))
application.register("content-loader", ContentLoader) // 追加
コントローラーを変更します。
stimulus-content-loader
ではページ表示後に非同期でコンテンツを取得するので、実行したいアクションを追加します。
# 省略...
# 追加
def index
render partial: 'posts/comments', locals: { post: @post, comments: @post.comments }
end
# 省略...
- if comments.present?
ul
- comments.each do |comment|
li
= simple_format comment.body
span = link_to "削除", [post, comment], data: { confirm: 'Are you sure?' }, method: :delete
- else
p コメントはありません。
/ 省略...
h4 コメント
div[data-controller="content-loader" data-content-loader-url-value="#{ post_comments_path(@post) }"]
p ローディング中...
/ 省略...
これでOKです。
実行してみるとデモのような動きになるはずです。
ログを確認してみます。
最初に投稿詳細ページを表示したあとに、コメントを取得しているのが分かります。
以上で完成です。
Stimulus Content Loaderが便利でJSを書かなくても実装できました。
よかったら試してみて下さい。
補足
- Stimulus Content Loaderには
data-content-loader-refresh-interval-value
を指定することで、表示したコンテンツをN秒ごとに再ロードする設定ができるようです。 - ソースコードも公開されているのでJSの勉強にも使えそうです。今回のStimulus Content Loaderのコードはこちら
- Stimulus Componentsには、他にも多くのcomponentがあるので便利そうです。