Rails の github レポジトリを見ていたら
Introduce support for ActionView::Component( https://github.com/rails/rails/pull/36388 )
というプルリクを見つけてなかなか面白そうだったので読んでみました。
ActionView::Component を一言でまとめると、ビューコンポーネントを Rails のオブジェクトとして扱えるようにする、というもの。
3月から既に Github のプロダクション環境で使われているとか。
RailsConf でも発表された内容のようです。
https://www.youtube.com/watch?v=y5Z5a6QdA-M
以下、プルリクの内容をかいつまんで翻訳したものです。
背景
Rails アプリのビュー開発において次のような問題があった。
テスト
Rails ではビューのテストは統合テストかシステムテストが推奨されているが、これだとコストが大きいのですべてのビューをテストするというわけにもいかない。しかもこのテストの仕方だと、せっかくパーシャルに切り分けて共通化しても、テストは DRY じゃなくなって嬉しくない。
カバレッジ
テストのカバー率を求める一般的なツールではビューのカバー率を完全には判断できないのでテストがどれくらい書かれているか判断しづらいし、カバー率に実態とのギャップが生まれる。
データの流れ
オブジェクトの宣言と違い、ビューでは予期する値についての情報が無いので、そのビューが何をするものなのか理解が難しい。この問題は同じビューを異なる場所で使おうとしたときによく起きる。
基準
Ruby のコーディング基準のようなものをビューでは設定しづらい。
実装
コンポーネントの構築
-
app/components
配下にActionView::Component
の子クラスとして定義する。 - クラスメソッドとして
template
メソッドを定義し、 ERB をヒアドキュメントで書く。 -
initialize
も定義できるが、その他はすべてプライベートメソッドとする。 - バリデータが使える。
initialize
後、render
前にバリデーションがかかる。
class TestComponent < ActionView::Component
validates :content, :title, presence: true
def initialize(title:)
@title = title
end
def self.template
<<~'erb'
<span title="<%= title %>"><%= content %></span>
erb
end
private
attr_reader :title
end
コンポーネントの呼び出し
- ERB 内で
TestComponent.new(title: "my title")
などとしてインスタンス化し、render
に渡す。 - ブロック引数を与えると、コンポーネント内で
content
として扱うことができる。
<%= render(TestComponent.new(title: "my title")) do %>
Hello, World!
<% end %>
これは次のような HTML を返す。
<span title="my title">Hello, World!</span>
エラー
上の例では title に対して presence: true しているので、title が空だと怒られる。
<%= render(TestComponent.new(title: "")) do %>
Hello, World!
<% end %>
ActiveModel::ValidationError: Validation failed: Title can't be blank
テスト
コンポーネントはユニットテストできて、テストヘルパーとして render_component
が用意されている。
def test_render_component
assert_equal(
%(<span title="my title">Hello, World!</span>),
render_component(TestComponent.new(title: "my title")) { "Hello, World!" }.css("span").to_html
)
end
利点
テスト
ActionView::Component
によってビューがユニットテスト可能になる。1テストあたり25ミリ秒で終わるのに対し、統合テストだと最大で6秒かかる。
カバレッジ
ActionView::Component
は一部のコードカバレッジツールで互換性がある。SimpleCov ではある程度うまくいっているっぽい。
データフロー
ビューのレンダリングに必要なコンテキストを定義できるので、 partial
よりも再利用しやすい。
パフォーマンス
ベンチマークテストにおいて、 component
は partial
よりも5倍の性能を発揮した。
Comparison:
component: 6515.4 i/s
partial: 1251.2 i/s - 5.21x slower
こちらのデモレポジトリで試せる。
https://github.com/joelhawksley/actionview-component-demo
引用ここまで。
まとめ
ActionView::Component を使うと切り出したビューをオブジェクトとして定義でるようになります。
それによって、変数の検証が可能、ユニットテストができる、さらに partial よりも高速、といった恩恵を受けられます。
ちなみにこの内容は Rails 6.1 から使えるようになりそうです。