Edited at
PORTDay 2

部分テンプレートを遅延ロードさせるGem作った

More than 1 year has passed since last update.

この記事はPORT Advent Calendar2日目の穴埋めです。


概要

簡単に部分テンプレートを遅延ロードさせるRailsプラグイン作った。

View template内で、render 'partial'step_render 'partial'みたいに書き換えるだけ。

https://github.com/EastResident/step_render


なぜ作ったか

まず、部分テンプレートを遅延ロード(あるいは非同期読み込み)させる目的のGemはすでに存在します。探した限り、以下の二つが見つかりました。

https://github.com/renderedtext/render_async

https://github.com/vexus2/lazy_render

しかし、これらのGemはテンプレートごとにアクションやルーティングを作成する必要があったりと、手軽にプロジェクトに組み込める感じではありませんでした(堅実な作りとも言えますが・・・)。

今回作成したGemでは、renderメソッドで部分テンプレートを呼び出すところをstep_renderに変えるだけで遅延ロードを実現させるため、扱いが非常にシンプルです。まあ、いくつか細かい制約はありますが・・・


使用方法・解説


導入

まず、普通にbundle installします。

おそらくRails 4.2以上でしか動作しません。

リポジトリはここです

https://github.com/EastResident/step_render

gem 'step_render'

次に、jsのコードを挿入します。このプラグインはlazysizesというjsのライブラリを利用しているので、lazysizesをプロジェクトに導入してないなら、application.html.erb等に以下の記述すればCDNで読み込みます。

<head>

〜〜〜
〜〜〜
<%= import_step_render %>
〜〜〜
</head>

最後に、ルーティングを追加します。routes.rbmount_step_renderの記述を追加すれば、必要なルーティングが作成されます。

Rails.application.routes.draw do

〜〜〜
mount_step_render
〜〜〜

これで準備は完了です。


使用例

後は、renderメソッドと同じような感覚で使用できます。以下の例では、Articleというモデルのインスタンスを引数に渡しています。Aが通常のパターンで、BがGemを利用した遅延読み込みのパターンです。


A. renderの場合


app/views/articles/index.html.erb

<% @articles = Article.find(10) %>

<%= render 'article', article: @article %>


app/views/articles/_article.html.erb

<h2><%= article.title %></h2>

〜〜〜
〜〜〜


B. step_renderでの遅延ロード


app/views/articles/index.html.erb

<% @articles = Article.find(10) %>

<%= step_render 'articles/article', article: @article %>


app/views/articles/_article.html.erb

<h2><%= article.title %></h2>

〜〜〜
〜〜〜

step_renderでの遅延ロードを行う際の違いは、renderstep_renderに置き換えることと、部分テンプレートのファイルを絶対パス("app/views"からの相対パス)で指定することのみです。部分テンプレート側の変更は必要ありませんし、actionやroutingを追加する必要はありません。


仕組みの説明

どのようにこれを実現させているかですが、まず、部分テンプレートの代わりに以下のようなdivタグが挿入されます。

<div class="related-articles lazyloaded" data-include="/step_render/%04%08%5B%07I%22%0Carticle%06%3A%06ET%7B%07I%22%0Carticle%06%3B%00F%7B%06I%22%11_aj_globalid%06%3B%00TI%22%1Agid%3A%2F%2Fchai%2FArticle%2F32%06%3B%00TI%22%14_aj_symbol_keys%06%3B%00T%5B%06I%22%0Carticle%06%3B%00F">loading</div>

ここで、data-include属性の/step_render/以下の文字列は、step_renderメソッドに渡した引数をダンプしてCGIエスケープしたものです。ActiveRecordのモデルが引数に渡された場合は、Global ID化した上でダンプされます。

この/step_render/~~~~形式のURLにGETすることで本来のHTMLが返ってきます。

GETで渡せるURLには長さの制限があるので、これを超えるような引数を指定することはできません。

step_renderGemでは、2083文字を超える場合に、StepRender::RequestURITooLargeという独自例外が発生します。

試した感じだと、20~30個くらいのARインスタンスなら引数として渡せます。以下は実行可能です。

<%= step_render 'partial', articles: Article.limit(20).to_a %>


まとめ

このGemの使用用途としては以下のようなものが考えられます。


  • ページ読み込みが重い部分に適用

  • fragment cacheの内側で一部書き換えたい場合

  • 無限スクロール

いずれの用途でも最小限のコード変更で実現できますが、内部的には少し荒っぽいことをしてるので動作は少し不安です・・・

最後になりますが、PORT株式会社では自社サービスを支えてくれる優秀なRubyエンジニアを募集しています(Rubyエンジニア以外も)。

もくもく会も行なっていますので、ぜひ一緒にもくもくしましょう!

PORTもくもく会