こんにちは。 Qiita Team の開発に参画している @tomoasleep です。
巷では、Rails 7 で盛り上がってますが、今回は Rails 6.1 で導入された Renderable objects (正式名が無いのですが開発中の名称からこの記事ではこう呼びます) についての紹介していきたいと思います。
React 側に View のロジックが寄っていて、あまり Rails の View にロジックをもたせていなかったケースなどでは、これを用いて汎用的な View を用意することで、ERB や Slim での View 実装を減らせる可能性があります。そういった例も紹介していこうと思います。
Rails 6.1 で render
に #render_in
を実装したオブジェクトを渡せるようになった
Rails 6.1 では以下のように、View や Controller の render メソッドに render_in
を実装したオブジェクト (Renderable object) を渡すことができるようになりました。
<h1>Header</h1>
<%= render HelloComponent.new %>
class HelloComponent
def render_in(view_context)
# view_context を介して helper method などの view で使える method を呼び出せる
view_context.content_tag(:p, "Hello, World!")
end
end
この場合、 render_in
メソッドの返り値が描画され、以下のような表示になります。
Controller の render
にも渡すことができる (※ format
メソッドの実装も必要)
Renderable object は View 内の render
だけでなく、 Controller 内の render
にも渡すことができます。
class HelloComponentController
def index
render SampleComponent.new
end
end
ただし、Controller の render メソッドに渡すオブジェクトには format
メソッドも併せて実装する必要があります。(実装されていないと、以下のようなエラーとなります)
class HelloComponentComponent
def render_in(view_context)
view_context.content_tag(:p, "Hello, World!")
end
# Response の Content-Type を何にするかを symbol で返す
def format
:html
end
end
また、 Renderable object を渡した場合も、Layout は使用されます。なので、以下の通りに、 content_for
と併用して、タイトルなどを Renderable object から制御することも可能になっています。
class HelloComponentComponent
def render_in(view_context)
view_context.content_for(:title, "Title from Renderable objects")
view_context.content_tag(:p, "Hello, World!")
end
def format
:html
end
end
<!DOCTYPE html>
<html>
<head>
<title><%= content_for(:title) || "RenderableExample" %></title>
<meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
<%= yield %>
</body>
</html>
これにより、 Action の View に ERB を書かない形で置き換えることが可能です。
(元々は ViewComponent を意識して導入された機能だった)
この機能は、Introduce support for 3rd-party component frameworks by joelhawksley · Pull Request #36388 · rails/rails で導入されました。
この機能は GitHub が開発した、 ViewComponent という (class ベースで) View を構築するためのフレームワークを想定して実装されました。
この機能で、ViewComponent が、 render
メソッドをオーバーライドせずとも組み込めるようにすることを意図されていました。
が、実は ViewComponent を必ずしも利用しなくても、 render_in
というメソッドを実装しているオブジェクトを用意すれば渡せる、という仕様になっています。
応用例: React に描画を任せているような View を Renderable object に置き換えられる
実際に、これを応用する例として、例えば、 View の描画の大部分を React などに任せていて、Rails の View が持つ責務が小さい例などが挙げられます。
例えば、shakacode/react_on_rails を利用している例だと、以下のような ERB を書くことになります。
<% content_for(:title, 'Hello World') %>
<%= react_component("HelloWorld", props: { name: @name }) %>
class HelloWorldController < ApplicationController
def index
@name = 'Stranger'
end
end
const HelloWorld = ({ name }) => {
return (
<div>
<h3>Hello, {name}!</h3>
</div>
)
}
これを、Renderable object に置き換えると以下のような実装になり、Action 毎の View を利用しない形に置き換えることができるようになります。
class ReactPage
def initialize(component_name, title:, props: {})
@component_name = component_name
@title = title
@props = props
end
def render_in(view_context)
view_context.content_for(:title, @title)
view_context.react_component(@component_name, props: @props)
end
def format
:html
end
end
class HelloWorldController < ApplicationController
def index
render ReactPage.new('HelloWorld', title: 'Hello World', props: { name: 'Stranger' })
end
end
Renderable object で置き換えることで、以下のメリットが考えられます。
- View にわたすパラメータのチェックが行いやすい、値の依存関係の追跡がしやすい
- コンストラクタを介した値の受け渡しを行うので、インスタンス変数、locals を用いる場合よりも、意図しないパラメータでエラーを出したりなどのチェックがしやすかったり、どこから渡されているかを明確にしやすいです。
- 入力値のチェック、加工などの実装も ERB などよりも Ruby の class としてのほうが書きやすいことが多いです。
- ActionView ではなく React 側に View の実装を寄せていきたい場合、それを強制させやすくなる
Action 毎の View を書かなくて良いメリットは、持たせるロジックが小さい場合に有効に働きやすいです。
また、パラメータの追跡、チェックのしやすさなどは、View の構造化をする上で有効に働きやすく、partial だとやりにくいところだと思うので、 View をまるごと置き換えるだけでなく、一部の partial を置き換えるというやり方でも有用だったりします。
(view_context のメソッドを呼び出すことで、頑張れば様々な View を構築できる)
一応 render_in
に渡される view_context から View helper のメソッドは呼び出せるので、一応 template で出来ることは Renderable でも出来ます。
…が、ある程度複雑な View を構築したくなったら ViewComponent を使うのを検討したほうが良いと思います。
ViewComponent は、Template Engine のサポートや i18n サポート、テスト用のヘルパーなど、View を実装する際に役立つ様々な機能を持っているので、既存の ERB でガリガリ書かれている View や partial を置き換えたりする用途では、こちらがおすすめです。
おわりに
Renderable objects を利用して、簡単な View を ERB などを利用しない形で置き換えられる例を紹介しました。高度な機能を利用しなければ ViewComponent などのライブラリ無しでも十分なので、試しに使ってみるのも良いかもしれません。
明日の Qiita株式会社 Advent Calendar 2021 は @phigasui が担当します!