ViewComponentGemの紹介
Reactに触発されたViewComponentsは、ビューをレンダリングするためのマークアップを構築するために使用されるRubyオブジェクトです。 ViewComponentは、Railsで再利用可能、テスト可能、およびカプセル化されたビューコンポーネントを構築するためのフレームワークです。通常、再利用可能なビューは、パーシャルを使用してRailsで作成され、必要に応じてさまざまなビューでレンダリングされますが、ViewComponent gemの導入により、パーシャルをビューコンポーネントと交換できるため、より多くの利点が得られます。これらの利点について詳しく見ていきましょう。
いつ、なぜViewComponentsを使用する必要がありますか?
前に述べたように、ビューコンポーネントは再利用可能でテスト可能です。したがって、ビューを再利用する場合、または直接テストすることでメリットが得られる場合はいつでも適用できます。ドキュメントに記載されているように、ビューコンポーネントの利点には、次のようなものがあります。
- パーシャルよりも約10倍高速です。
- これらはRubyオブジェクトです。したがって、それらのinitializeメソッドは、ビューをレンダリングするために必要なものを明確に定義します。これは、他のいくつかのビューで理解して再利用しやすいことを意味します。さらに、Rubyコードの品質基準を適用できるため、バグのリスクが軽減されます。
- これらは、ビューに加えてルーティングレイヤーとコントローラーレイヤーも実行する統合テストを必要とするRailsの従来のビューとは対照的に、単体テストを行うことができます。
ViewComponentの実装
ViewComponentsは、ViewComponent::Base
のサブクラスです。 app/components
に住んでいます 。それらの名前は- Component
で終わります 、およびそれらは、受け入れるものではなく、レンダリングするものに基づいて名前を付ける必要があります。ビューコンポーネントは手動またはコンポーネントジェネレーターを介して生成できますが、gemファイルにgemを追加し、bundle install
を実行する必要があります。 最初。
gem "view_component", require: "view_component/engine"
ジェネレータを使用するには、次のコマンドを実行します。
rails generate component <Component name> <arguments needed for initialization>
たとえば、学習プラットフォームで利用可能なクラス内コースのリストを表示するコンポーネントを生成する場合は、次のコマンドを使用できます。
rails g component Course course
course
を通過します このRubyオブジェクトを、表示する予定のコースで初期化し、Course
という名前を付けたため、コマンドの引数 コースをレンダリングするからです。なんて偶然だ!
上記のように、コンポーネントとそれに対応するビューはapp/components
に作成されます。 フォルダとテストファイル。
ViewComponentには、erb
のテンプレートジェネレーターが含まれています 、haml
、およびslim
テンプレートエンジンですが、デフォルトではconfig.generators.template_engine
で指定されているテンプレートエンジンが使用されます。 。それでも、以下を使用して、好みのテンプレートエンジンを指定できます。
rails generate component Course course --template-engine <your template engine>
コースモデルと表示するいくつかのコースの作成に進みましょう。
rails g model Course title:string price:decimal location:string
rails db:migrate
コンソールでは、2つの新しいコースをすばやく作成できます。
Course.create(title: 'The Art of Learning', price: 125.00, location: 'Denmark')
Course.create(title: 'Organizing your Time', price: 55.00, location: 'London')
course_component.rb
以下に示すように、ファイルはinitializeメソッドを使用して生成されます。
コースのリストにルーティングするコースコントローラーを作成する必要があります。
rails g controller Courses index
routes.rb
で ファイルの場合、以下を追加してルートルートを示します。
root 'courses#index'
これですべての設定が完了したので、次のステップはビューの作成です。これは、すでに生成されたcourse_component.html.erb
で行われます。 。
<div>
<h2><%= @course.title %></h2>
<h4><%= number_to_currency(@course.price, :unit => "€") %></h4>
<h4><%= @course.location %></h4>
</div>
私たちのビューでは、@course
を使用して、コースのタイトル、価格、場所を表示します 変数。CourseComponentのinitializeメソッドですでに定義されています。これは、コントローラーメソッドで変数を作成し、ビューで使用できる場合と似ています。
コントローラがどのように機能するかを知っていると、対応するindex.html.erb
にルーティングされます。 私たちのインデックスメソッドの。したがって、ここでコンポーネントをレンダリングします。
#app/views/courses/index.html.erb
<%= render(CourseComponent.new(course: Course.find(1))) %>
上記のように、CourseComponentの新しいインスタンスを、ビューでレンダリングする予定のコースで初期化することによってレンダリングします。このコースは@course
になります course_component.html.erb
で利用できるようになった変数 ファイル。
このコンポーネントをコントローラーから直接レンダリングして、インデックスファイルビューをバイパスすることもできます。
class CoursesController < ApplicationController
def index
render(CourseComponent.new(course: Course.find(1)))
end
end
選択した方法に関係なく、これはサーバーに表示されます:
次のいずれかの方法で、追加のコンテンツをコンポーネントに渡すこともできます。
<%= render(CourseComponent.new(course: Course.find(1))) do %>
container
<% end %>
<%= render(CourseComponent.new(course: Course.find(1)).with_content("container")) %>
ビューコンポーネントファイルでは、必要な場所にコンテンツを含めることができます。この場合、divを次のように編集して、クラスとして含めます。
<div class=<%= content %>>
追加コンテンツをレンダリングする上記のすべての方法により、以下の画像が生成されます。
コースのリスト全体をレンダリングしたい場合はどうなりますか? ViewComponentは、with_collection
を使用してこれを行う非常に簡単な方法を提供します 鬼ごっこ。 .new
を使用してコンポーネントを初期化する代わりに 、.with_collection
を使用して初期化されます 、およびコレクションは、以下に示すように変数として渡されます。
CourseComponent.with_collection(Course.all)
これにより、次のようになります。
with_collection_parameter
もあります コレクションを別の名前でアドレス指定したい場合に利用できるタグ。
class CourseComponent < ViewComponent::Base
with_collection_parameter :item
def initialize(item:)
@item = item
end
end
上記の場合、コースパラメータはitem
としてアドレス指定されています 。したがって、対応するビューでは、@course
@item
に置き換えられます 同じ結果が得られます。
追加のパラメーターをコレクションに追加することもできます。これらのパラメータは、コレクション内のアイテムごとに表示されます。 Buy Me
を追加しましょう このメソッドを介して各アイテムにテキストを送信します。
#app/views/courses/index.html.erb
<%= render(CourseComponent.with_collection(Course.all, notice: "Buy Me")) %>
# app/components/course_component.rb
class CourseComponent < ViewComponent::Base
with_collection_parameter :item
def initialize(item:, notice:)
@item = item
@notice = notice
end
end
app/components/course_component.html.erb
に新しい段落を追加します 新しく追加された通知変数のテキストを示すファイル。
<p><a href='#'> <%= @notice %> </a></p>
これにより、次のようになります。
最後に、コレクションの下に、ビュー内のアイテムに番号を付けるために有効にできるカウンター変数があります。 _counter
を追加することで有効になります コレクションパラメータに追加し、initializeメソッドを介してビューで使用できるようにします。
#app/components/course_component.rb
def initialize(item:, notice:, item_counter:)
@item = item
@notice = notice
@counter = item_counter
end
ビューでは、アイテムのタイトルの横にカウンターを追加します:
<h2><%= @counter %>. <%= @item.title %></h2>
カウンター現象をよりよく理解するために、コンソールから3番目のコースを生成しましょう。
Course.create(title: 'Understanding Databases', price: '100', location: 'Amsterdam')
これにより、次のようになります
ViewComponentにはrender?
があります フック。使用すると、ビューをレンダリングするかどうかを決定します。これを実装するために、100ユーロ以上の価格のコースに10%の割引を提供します。この目的のためのコンポーネントを作成しましょう。
rails generate component Discount item
このコンポーネントは、以下に示すように、割引を表示する必要があるアイテムですでに自動的に初期化されています。
したがって、discount_component.html.erb
ファイルに、表示するテキストを追加します。
<p class="green"> A 10% discount is available on this course </p>
クラスgreen
を追加することを躊躇しないでください cssファイルに追加し、好きな緑の色合いを割り当てます。さらに、discount_component.rb
で ファイル、render?
を追加します このコンポーネントをいつレンダリングするかを決定するメソッド。
def render?
@item.price >= 100
end
これで、先に進んで、各コースをレンダリングするビュー内に割引コンポーネントをレンダリングできます。
# app/components/course_component.html.erb
<%= render(DiscountComponent.new(item: @item)) %>
これにより、次のようになります。
それは素晴らしいことではありませんか?
従来のRailsビューでは、ビューでメソッド名を呼び出すことでヘルパーを簡単にプラグインできますが、ビューコンポーネントでは動作が異なります。ビューコンポーネントでは、ヘルパーメソッドをビューで直接呼び出すことはできませんが、コンポーネントに含めることができます。すでにcourses_helper.rb
があります CoursesControllerの作成時に自動的に生成されたファイルなので、活用してみましょう。まず、これまでに何人の人がコースに登録したかを示すヘルパーメソッドを作成しましょう。値を価格の4分の1にしましょう:)
module CoursesHelper
def count_enrollees(course)
count = (course.price / 4).round()
tag.p "#{count} enrollees so far"
end
end
次に、ヘルパーを呼び出すコンポーネントを作成します。これは、ビューにレンダリングされるコンポーネントです。その中に、include
を追加します ヘルパーを含むステートメント。これにより、このコンポーネント内のヘルパー内の任意のメソッドを呼び出すことができます。
# app/components/enrollee_component.rb
class EnrolleeComponent < ViewComponent::Base
include CoursesHelper
def total_enrollees(course)
count_enrollees(course)
end
end
最後のステップは、コースを表示するビューにEnrolleeComponentを追加することです。
# app/components/course_component.html.erb
<%= EnrolleeComponent.new.total_enrollees(@item) %>
EnrolleeComponentにはビューがないため、レンダリングワードを使用していないことに注意してください。その出力は、呼び出されたヘルパーメソッドの出力になります。これにより、次のようになります。
ヘルパーは、アイコン、Gravitar、または選択したものに使用できます。 ViewComponentはヘルパーの使用を変更しません。コンポーネントでの呼び出し方法を変更するだけです。
before_renderメソッド
ViewComponentはbefore_render
を提供します コンポーネントがレンダリングされる前に呼び出すことができるメソッド。割引通知の横に星を追加しましょう。まず、スターをフェッチするヘルパーメソッドを追加します。 star.png
画像もダウンロードされ、app/assets/images
に配置されています。 フォルダ。
#app/helpers/courses_helper.rb
def star_icon
image_tag("/assets/star.png", width: "1%")
end
before_render
を追加しましょう このヘルパーメソッドを呼び出すDiscountコンポーネントへのメソッド。
# app/components/discount_component.rb
def before_render
@star_icon = helpers.star_icon
end
上記のように、ヘルパーを呼び出す別の方法が導入されています。ヘルパーは、helpers.method
を使用して呼び出すこともできます component.rbファイル内。次に、DiscountComponentのrenderメソッドで、スターアイコンを入力します。これは、@star_icon
から利用できるようになりました。 変数。
# app/components/discount_component.html.erb
<p class="green"> <%= @star_icon %> A 10% discount is available on this course </p>
これにより、次のようになります。
before_render
に必ずしもヘルパーを使用する必要はありません 働く方法。ヘルパーを使用して、ヘルパーメソッドを呼び出すための別のアプローチを紹介しました。
アクションメーラーと同様に、ViewComponentを使用するとコンポーネントをプレビューできます。これは、最初にconfig/application.rb
で有効にする必要があります ファイル。
config.view_component.preview_paths << "#{Rails.root}/lib/component_previews"
プレビューコンポーネントはtest/components/previews
にあります いくつかの異なる入力が渡されたコンポーネントをプレビューするために使用できます。DiscountComponentをプレビューします。
# test/components/previews/discount_component_preview.rb
class DiscountComponentPreview < ViewComponent::Preview
def with_first_course
render(DiscountComponent.new(item: Course.find(1)))
end
def with_second_course
render(DiscountComponent.new(item: Course.find(2)))
end
end
さまざまなシナリオでDiscountComponentをプレビューするために、2つのメソッドが追加されました。これを表示するには、https://localhost:3000/rails/view_components
にアクセスしてください 、作成されたすべてのプレビューコンポーネントとそのメソッドが表示されます。以下に示すように、それらのいずれかをクリックして、それらがどのように見えるかを表示できます。
上のビデオでわかるように、最初のコースでは割引コンポーネントがレンダリングされますが、2番目のコースでは何もレンダリングされません。なぜこれが起こったのか知っていますか? render?
メソッドチェックが発生しました。最初のシナリオは、原価が100ユーロ(第1コース)を超えているが、第2コースのコストが100ユーロ未満の場合です。メソッド名は、ここで強調表示される前に原因を特定できるようにするために、よりわかりやすいものではありませんでした:)
プレビューは、show_previews
を使用して、どの環境でも有効または無効にできます オプションですが、開発およびテスト環境では、デフォルトで有効になっています。
# config/environments/test.rb
config.view_component.show_previews = false
JSとCSSの包含
「サイドカー」アセットまたはファイルと呼ばれることもあるコンポーネントと一緒にJavaScriptとCSSを含めることができます。これはまだ実験的な機能です。したがって、この記事ではその内部の仕組みについては詳しく説明しませんが、このViewComponent機能の詳細についてはこちらをご覧ください。
ビューコンポーネントのテンプレートは、いくつかの方法で定義できます。より簡単なオプションは、以下に示すように、ビューとコンポーネントを同じフォルダーに挿入することです。
ここに表示されているように、すべてのコンポーネントとビューがapp/components
にあります。 フォルダ。
別のオプションは、ビューと他のアセットをコンポーネントと同じ名前のサブディレクトリに配置することです。したがって、app/components
フォルダには、component.rb
があります コンポーネントを格納するファイルと、別のcourse_component
ビューcourse_component.html.erb
を格納するフォルダー およびcourse_componentに関連する他のすべてのアセット。
コマンドラインからこの方法でコンポーネントファイルを生成するには、--sidecar
フラグが必要です:
rails g component Example name --sidecar
これにより、cssファイルとjsファイルをコンポーネントフォルダーに追加できます。ViewComponentsは、call
を定義することにより、テンプレートファイルなしでレンダリングすることもできます。 方法。この例は、次のセクションで提供されます。ここでは、スロットについて説明します。 。
slot を使用して、コンテンツの複数のブロックを1つのViewComponentに渡すことができます。 。 has_one
に似ています およびhas_many
Railsモデルの属性、スロットはrenders_one
で定義されます およびrenders_many
:
- renders_oneは、コンポーネントごとに最大1回レンダリングされるスロットを定義します:renders_one:header。
- renders_manyは、コンポーネントごとに複数回レンダリングできるスロットを定義します:renders_many:titles。
利用可能なすべてのコースのヘッダーとタイトルをレンダリングするページが必要だと想像してみてください。これは、スロットを使用して実現できます 。 ListComponentを作成しましょう 、これには1回だけレンダリングされるヘッダーと、 TitleComponent が含まれます 、多くのタイトルをレンダリングします。
#app/components/list_component.rb
class ListComponent < ViewComponent::Base
renders_one :header, "HeaderComponent"
# `HeaderComponent` is defined within this component, so we refer to it using a string.
renders_many :titles, TitleComponent
# `titleComponent` will be defined in another file, so we can refer to it by class name.
class HeaderComponent < ViewComponent::Base
def call
content_tag :h1, content
end
end
end
上記のコンポーネントでは、ヘッダーは1回レンダリングされると述べましたが、このページには多くのタイトルが含まれるため、タイトルは何度もレンダリングされます。 ListComponent内にHeaderComponentも定義しました。はい、これはViewComponentで可能です。クラスは別のクラス内で定義できます。また、「テンプレート」セクションで前述した呼び出しメソッドと、h1タグをレンダリングするためにHeaderComponentでどのように使用されているかにも注意してください。これにより、対応するビュー(html.erbファイル)が不要になります。 ListComponentに対応するHTMLファイルには、次のものが含まれます。
#app/components/list_component.html.erb
<div>
<%= header %> <!-- renders the header component -->
<% titles.each do |title| %>
<div>
<%= title %> <!-- renders an individual course title -->
</div>
<% end %>
</div>
htmlファイルには、ヘッダーが含まれており、コンポーネントに渡されたすべてのタイトルを繰り返し処理してレンダリングしました。ご覧のとおり、リストビューファイルでレンダリングするコンポーネントの名前を指定する必要はありません。私たちのスロットはそれを処理しました。したがって、それらをheader
として識別します。 およびtitle
。
次のステップは、TitleComponentとそれに対応するHTMLファイルを作成することです。これは、渡されるすべてのタイトルに対してレンダリングされるものです。
# app/components/title_component.rb
class TitleComponent < ViewComponent::Base
def initialize(title:)
@title = title
end
end
# app/components/title_component.html.erb
<div>
<h3> <%= @title %> </h3>
</div>
最後に、index.html
で ファイル、持っているものを一時的に消去して、ListComponentレンダリングに置き換えましょう。
#app/views/courses/index.html.erb
<%= render ListComponent.new do |c| %>
<% c.header do %>
<%= link_to "List of Available Courses", root_path %>
<% end %>
<%= c.title(title: "First title") %>
<%= c.title(title: "Second title!") %>
<% end %>
それでは、コースをコレクションとしてこのビューに渡しましょう。コレクションをスロットに渡すには、初期化に必要な変数を含むオブジェクトの配列としてコレクションを渡す必要があります。ご覧のとおり、渡されるすべてのコースは、title引数で初期化する必要があります。データベース内のすべてのコースのタイトルをハッシュの配列に抽出してレンダリングできます。
これで、複数のタイトルのリストを1つのc.titles
に置き換えることができます。 コレクションの場合は、変数course_titles
を使用して定義したタイトルのハッシュに渡します。 。
# app/views/courses/index.html.erb
<% course_titles = Course.all.pluck(:title).map { |title| {title: title}} %>
<% c.titles(course_titles) %>
これにより、次のようになります。
これはまさにスロットの仕組みです。単一のViewComponent内で複数のコンポーネントをレンダリングします。スロットは他のいくつかの方法でレンダリングできます。詳細については、こちらをご覧ください。
ビューコンポーネントのテストは、テストファイルに「view_component / test_case」を要求し、render_inline
を使用して実行されます。 レンダリングされた出力に対してアサーションが作成されるようにヘルパーをテストします。まず、DiscountComponentのテストから始めましょう。
require "test_helper"
require "view_component/test_case"
class DiscountComponentTest < ViewComponent::TestCase
def test_render_component
render_inline(DiscountComponent.new(item: "my item"))
end
end
コマンドrails test test/components/discount_component_test.rb
を使用してこのテストを実行すると 、次のエラーが発生します:
これは、テストが適切なコンポーネントに到達していることを証明していますが、合格したアイテムは文字列ではなく価格プロパティを持つコースでなければならないため、適切な小道具が不足しています。また、render?
があることも示しています。 このコンポーネントがレンダリングされる前にチェックされるメソッド。今すぐ正しい変数を渡しましょう。
def test_render_component
course = Course.create(title: 'Organizing your Time', price: 55.00, location: 'London')
render_inline(DiscountComponent.new(item: course))
end
これは正常に実行されます。このテストにアサーションを追加してみましょう。
def test_render_component
course = Course.create(title: 'Organizing your Time', price: 155.00, location: 'London')
render_inline(DiscountComponent.new(item: course))
assert_selector 'p[class="green"]'
assert_text "10% discount"
end
このテストも合格です。このコンポーネントにはレンダリング条件があることを思い出してください。ただし、ViewComponentには、refute_component_rendered
を使用して、コンポーネントがレンダリングされていないことをテストする方法も用意されているため、心配する必要はありません。 。 100ユーロ未満の価格のコースを使用してこれをテストできます。
def test_component_not_rendered
course = Course.create(title: 'Organizing your Time', price: 55.00, location: 'London')
render_inline(DiscountComponent.new(item: course))
refute_component_rendered
end
このテストも合格です。
CourseComponentの別のテストを作成して、その中にネストされたすべてのコンポーネントをレンダリングすることをテストしましょう。
require "test_helper"
require "view_component/test_case"
class CourseComponentTest < ViewComponent::TestCase
def test_component_renders_all_children
course = Course.create(title: 'Organizing your Time', price: 155.00, location: 'London')
render_inline(CourseComponent.new(item: course, notice: 'A new test', item_counter: 1))
assert_selector("h2", text: "Organizing your Time")
assert_selector("h4", text: "€155.00")
assert_selector("h4", text: "London")
assert_text("enrollees")
assert_text("discount")
end
end
このテストも合格です。 EnrolleeコンポーネントとDiscountコンポーネントも適切にレンダリングされていることをテストします。
スロットコンポーネントがあることを思い出してください。下の画像に示すように、1つのヘッダーと多くのタイトルがレンダリングされます。
これをテストするために、レンダリングするヘッダーとタイトルを含むコードのブロックをコンポーネントに渡し、レンダリングされたコンポーネントに対してアサートできます。
require "test_helper"
require "view_component/test_case"
class ListComponentTest < ViewComponent::TestCase
def test_renders_slots_with_content
render_inline(ListComponent.new) do |component|
component.header { "A Test List" }
component.titles [{title: 'Test title 1'}, {title: 'Test title 2'}]
end
assert_selector("h1", text: "A Test List")
assert_text("Test title 1")
assert_text("Test title 2")
end
end
このテストも合格します:)
Rspecテスト
テストについて上記で述べたすべてに加えて、推奨されるテストフレームワークがRSpecである場合、ViewComponentsのRSpecを有効にするためにいくつかの追加の構成を実行する必要があります。
# spec/rails_helper.rb
require "view_component/test_helpers"
require "capybara/rspec"
RSpec.configure do |config|
config.include ViewComponent::TestHelpers, type: :component
config.include Capybara::RSpecMatchers, type: :component
end
以下に示すように、DiscountComponentテストは、Rspecを使用して書き直し、再テストできます。
require "rails_helper"
RSpec.describe DiscountComponent, type: :component do
it "renders the component correctly" do
course = Course.create(title: 'Organizing your Time', price: 155.00, location: 'London')
render_inline(DiscountComponent.new(item: course))
expect(rendered_component).to have_css "p[class='green']", text: "10% discount"
expect(rendered_component).to have_css "img[src*='/assets/star.png']"
end
end
このテストはエレガントに合格します。ええ、私たちは星のアイコンを見ることができます。
Railsアプリ用にいくつかのビューコンポーネントを作成すると、コードが読みやすくなり、不要な複雑さによるエラーが発生しにくくなるだけでなく、HTTPリクエスト中だけでなく、ビューを個別にテストできるようになります。ビューコンポーネントは既存のRailsアプリに簡単に組み込むことができ、ほとんどが再利用されるビューから始めるのが最善です。これまでにすべてを学んだので、これは簡単な作業になるはずです。それでも、ViewComponentの詳細については、ドキュメントまたはRubyDoc情報を参照してください。
-
SOLID 原則の紹介
Kriptofolio アプリ シリーズ - パート 1 ソフトウェアは常に変化の状態にあります。各変更は、プロジェクト全体に悪影響を与える可能性があります。したがって、重要なことは、すべての新しい変更を実装する際に発生する可能性のある損害を防ぐことです. 「Kriptofolio」(以前の「My Crypto Coins」)アプリを使用して、新しいコードを段階的に作成していきますが、それを良い方法で開始したいと考えています。私は自分のプロジェクトが確かな品質であることを望んでいます。まず、最新のソフトウェアを作成するための基本原則を理解する必要があります。それらはSOLID原則と呼ばれま
-
Google の Project Fi:通話の未来の紹介
ワイヤレス ネットワークの市場は過密状態であり、まだ多くの情報を見つけることができません。実際、携帯電話会社の評判は悪く、必ずしも間違った理由があるわけではありません。これは、それほど無制限ではないデータプラン、ますます上昇するデータ料金、およびデータ上限の減少に関連する論争によるものです.データプランに大金を費やすこととは別に、最大のカバレッジを提供するキャリアを把握する必要もあります.すでにたくさんのオプションが利用可能で、リストに追加されています。もう 1 つ、Google の Project Fi があります。 Google の Project Fi とは Project Fi は、