What Does Assert Template Mean In Rails
Inspired by React, ViewComponents are Ruby objects used to build markup for rendering views. ViewComponent is a framework for building re-usable, testable, and encapsulated view components in Rail. Typically, reusable views are created in Rails using partials and then rendered in different views as required, but with the introduction of the ViewComponent gem, partials can be swapped for view components, as they offer more than advantages. Allow's dive into what these advantages are.
When and Why should I employ ViewComponents?
As stated earlier, view components are reusable and testable. Hence, they tin be applied whenever a view is to be reused or benefits from existence tested straight. Some of the benefits of view components, every bit stated in its documentation, include the post-obit:
- They are ~10x faster than partials.
- They are Ruddy objects; hence, their initialize method conspicuously defines what is required to render a view. This means that they are easier to understand and reuse in several other views. Furthermore, Red code's quality standards tin be enforced, thereby offering a reduced run a risk of bugs.
- They can be unit tested every bit opposed to Rails' traditional views, which require integration tests that also do routing and controller layers in add-on to the view.
ViewComponent Implementation
ViewComponents are subclasses of ViewComponent::Base of operations
and live in app/components
. Their names terminate in - Component
, and they should be named for what they render and non what they have. Nosotros can generate a view component either manually or via the component generator, just non without adding the gem to our gemfile and running a bundle install
get-go.
gem "view_component" , require: "view_component/engine"
To use the generator, run the following command:
rails generate component < Component proper noun > < arguments needed for initialization >
For example, if we wanted to generate a component responsible for showcasing a list of in-form courses available on a learning platform, we could apply this control:
rails g component Form form
We're passing the class
argument to the command considering we would initialize this Ruby object with the course nosotros look information technology to display, and nosotros named it Form
because it renders a course. What a coincidence!
As we can come across above, the component and its corresponding view are created in the app/components
binder, along with a test file.
ViewComponent includes template generators for the erb
, haml
, and slim
template engines just will default to whatsoever template engine is specified in config.generators.template_engine
. Nevertheless, yous can indicate your preferred template engine using the following:
rails generate component Course form -- template - engine < your template engine >
Let'due south proceed to create our Course model and some courses to display.
track grand model Class title :cord price :decimal location :cord rail db :migrate
In our console, we tin quickly create two new courses:
Course . create ( title: 'The Art of Learning' , price: 125.00 , location: 'Denmark' ) Course . create ( title: 'Organizing your Time' , price: 55.00 , location: 'London' )
The course_component.rb
file is generated with the initialize method in place, every bit shown below.
Nosotros need to create a course controller that routes u.s. to the list of courses.
rails g controller Courses alphabetize
In our routes.rb
file, we signal our root route past adding the following:
Now that we're all set, the side by side step is view creation. This is done in the already generated course_component.html.erb
.
<div> <h2> <%= @course . championship %> </h2> <h4> <%= number_to_currency ( @course . price , :unit => "€" ) %> </h4> <h4> <%= @course . location %> </h4> </div>
In our view, we display the course championship, price, and location using the @course
variable, which was already defined in the initialize method of our CourseComponent. This is similar to when y'all create a variable in a controller method, and and so it is available in a view.
Knowing how controllers work, nosotros would be routed to the corresponding alphabetize.html.erb
of our index method. Hence, this is where we return our component.
#app/views/courses/index.html.erb <%= render ( CourseComponent . new ( course: Class . observe ( one ))) %>
Every bit seen to a higher place, we render a new instance of our CourseComponent past initializing it with the class nosotros intend it to render in its view. This form becomes the @form
variable fabricated bachelor to the course_component.html.erb
file.
It is also possible to render this component directly from our controller, thereby bypassing the index file view:
grade CoursesController < ApplicationController def index render ( CourseComponent . new ( course: Class . observe ( 1 ))) end end
Regardless of which method yous choose, this will show up on the server:
Boosted content can too exist passed to the component in one of the post-obit means:
<%= return ( CourseComponent . new ( course: Course . find ( 1 ))) do %> container <% terminate %>
<%= return ( CourseComponent . new ( course: Form . find ( ane )). with_content ( "container" )) %>
In our view component file, we can include the content wherever we desire. In this case, we'll include it every bit a course by editing our div to wait similar this:
<div course= <%= content %> >
All of the in a higher place methods of rendering the additional content yield the image below:
Rendering a Collection
What if nosotros wanted to render our unabridged list of courses? ViewComponent provides a very straight frontwards manner of doing this using the with_collection
tag. Instead of initializing the component using .new
, it is initialized using .with_collection
, and the collection is passed to it as a variable, as shown below.
CourseComponent . with_collection ( Course . all )
This yields the post-obit:
In that location is as well a with_collection_parameter
tag available in case we wish to address the drove past a unlike name.
course CourseComponent < ViewComponent :: Base with_collection_parameter :particular def initialize ( item :) @particular = item end cease
In the in a higher place case, the course parameter has been addressed as item
. Hence, in the corresponding view, @course
will be replaced with @item
to yield the same result.
Additional parameters can also exist added to the collection. These parameters will exist displayed per particular in the collection. Let's add a Buy Me
text to each detail via this method.
#app/views/courses/alphabetize.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 = detail @notice = observe end terminate
We add a new paragraph to the app/components/course_component.html.erb
file to indicate the text for the newly added notice variable.
<p><a href= '#' > <%= @observe %> </a></p>
This yields the following:
Lastly, nether collections, we have a counter variable that can be enabled to number the items in a view. Information technology is enabled past adding _counter
to the drove parameter and making it available to the views via the initialize method.
#app/components/course_component.rb def initialize ( particular :, notice :, item_counter :) @detail = item @notice = discover @counter = item_counter terminate
In our views, beside the item title, we add together our counter:
<h2> <%= @counter %>. <%= @item . title %> </h2>
Let's generate a third course from the panel to better empathise the counter phenomenon.
Class . create ( title: 'Understanding Databases' , cost: '100' , location: 'Amsterdam' )
This yields the post-obit
Conditional Rendering
ViewComponent has a render?
hook, which, when used, determines whether a view should be rendered. To implement this, we're going to give a ten% disbelieve for courses with prices equal to or greater than 100 Euros. Let'south create a component for this purpose.
rails generate component Discount item
This component is already automatically initialized with the item information technology should brandish a discount for, as seen below.
Hence, in the discount_component.html.erb
file, we add together the text nosotros intend to display.
<p form= "green" > A ten% discount is bachelor on this class </p>
Don't hesitate to add the class green
to your css file and assign it any shade of dark-green you lot prefer. Furthermore, in our discount_component.rb
file, nosotros add the render?
method to determine when this component should exist rendered.
def render? @particular . price >= 100 finish
Now, we tin can become ahead and render the discount component within the view that renders each form.
# app/components/course_component.html.erb <%= render ( DiscountComponent . new ( item: @item )) %>
This yields the following:
Isn't that crawly?
Helpers
In the traditional Rails views, we can hands plug in our helpers by calling the method name in our views, just information technology works differently with view components. In view components, helper methods cannot be called direct in the views but tin be included in a component. We already have a courses_helper.rb
file that was automatically generated when the CoursesController was created, and then let's take reward of it. First, permit'southward create a helper method that tells us how many people have enrolled in a course and so far. Permit'due south brand the value a quarter of the toll :).
module CoursesHelper def count_enrollees ( form ) count = ( class . cost / 4 ). round () tag . p " #{ count } enrollees so far" end cease
Adjacent, nosotros'll create a component in which we'll call the helper. This is the component that volition exist rendered in our view. In it, we'll add together an include
argument, including the helper, and then we can phone call whatsoever method in the helper inside this component.
# app/components/enrollee_component.rb class EnrolleeComponent < ViewComponent :: Base include CoursesHelper def total_enrollees ( class ) count_enrollees ( form ) end end
The last step is adding the EnrolleeComponent to the view that displays our courses.
# app/components/course_component.html.erb <%= EnrolleeComponent . new . total_enrollees ( @item ) %>
Notation that nosotros are not using the return word for the EnrolleeComponent, equally it doesn't have a view, and its output will be that of the helper method chosen. This yields the following:
Helpers tin can be used for icons, gravitars, or whatever you lot might choose. ViewComponent doesn't alter the use of helpers; it just changes how we telephone call them in our components.
The before_render method
ViewComponent offers a before_render
method that can be called before a component is rendered. Let's add a star beside our discount discover. We start past adding a helper method that fetches the star; a star.png
image has also been downloaded and placed in the app/assets/images
folder.
#app/helpers/courses_helper.rb def star_icon image_tag ( "/assets/star.png" , width: "1%" ) terminate
Let's add together a before_render
method to our Discount component that calls this helper method.
# app/components/discount_component.rb def before_render @star_icon = helpers . star_icon terminate
As nosotros can come across to a higher place, some other manner of calling helpers is introduced. Helpers can also exist called using helpers.method
in the component.rb file. Then, in our return method for the DiscountComponent, we input our star icon, which is now fabricated available via the @star_icon
variable.
# app/components/discount_component.html.erb <p grade= "green" > <%= @star_icon %> A 10% disbelieve is available on this course </p>
This yields the post-obit:
We do not necessarily have to use helpers for the before_render
method to work. I have used helpers to introduce another approach to calling helper methods.
Previews
Like Action Mailer, ViewComponent makes it possible to preview components. This has to get-go be enabled in the config/application.rb
file.
config . view_component . preview_paths << " #{ Rails . root } /lib/component_previews"
Preview components are located in test/components/previews
and tin be used to preview a component with several different inputs passed in. Nosotros'll be previewing our DiscountComponent.
# exam/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
Two methods accept been added to preview the DiscountComponent in different scenarios. To view this, visit http://localhost:3000/rails/view_components
, where we discover all the preview components created and their methods. We can click on whatever of them to view what they look like, as shown below.
As you can see in the video above, the get-go class renders the discount component, but zero gets rendered for the 2d grade. Practice you know why this happened? The render?
method bank check happened. The kickoff scenario is when the cost price is more than than 100 euros(the 1st grade), but the cost is less than 100 euros for the second course. The method names were not more descriptive to enable you lot effigy out the cause earlier information technology is highlighted here :).
Previews tin be enabled or disabled in whatsoever environs using the show_previews
option, but in development and testing environments, they are enabled by default.
# config/environments/test.rb config . view_component . show_previews = fake
JS AND CSS Inclusion
It's possible to include JavaScript and CSS alongside components, sometimes chosen "sidecar" assets or files. This is still an experimental feature. Hence, nosotros won't dive into its inner workings in this article, but you tin can detect more about this ViewComponent feature hither.
Templates
The template of a view component can be defined in several ways. The simpler option is inserting the view and component in the aforementioned binder, as shown below.
As nosotros tin can see here, we take every component and view in the app/components
folder.
Some other option is to place the view and other avails in a subdirectory with the same name equally the component. Thus, in the app/components
folder, we take the component.rb
file, which houses the component, and and so a split up course_component
binder, which houses the view course_component.html.erb
and every other asset related to the course_component.
To generate component files in this way from the command line, the --sidecar
flag is required:
rail g component Example name -- sidecar
This enables you add your css and js files to the component folder. ViewComponents can also render without a template file by defining a call
method. An case of this is provided in the next section, where nosotros talk over slots.
Slots
Multiple blocks of content can be passed to a unmarried ViewComponent using slots. Similar to the has_one
and has_many
attributes in Runway models, slots are defined with renders_one
and renders_many
:
- renders_one defines a slot that volition be rendered at almost once per component: renders_one :header.
- renders_many defines a slot that can exist rendered multiple times per-component: renders_many :titles.
Imagine that we want to accept a page that renders a header and the titles of all the courses nosotros have available; this can be achieved using slots. Let's create a ListComponent, which will contain a header that is only rendered in one case, and a TitleComponent, which renders many titles.
#app/components/list_component.rb class ListComponent < ViewComponent :: Base renders_one :header , "HeaderComponent" # `HeaderComponent` is defined within this component, so nosotros refer to it using a cord. renders_many :titles , TitleComponent # `titleComponent` will be defined in another file, so we tin refer to it by class name. form HeaderComponent < ViewComponent :: Base of operations def call content_tag :h1 , content terminate stop terminate
In the component above, we stated that the header is rendered once, but the titles are rendered many times, equally this page would contain many titles. Nosotros have also divers the HeaderComponent within the ListComponent. Yes, this is possible with ViewComponent; a class can be defined inside another class. Let's also take annotation of the call method discussed earlier under the Templates section and how information technology'due south being used in the HeaderComponent to render an h1 tag, thereby eliminating the demand for a corresponding view(html.erb file). The respective HTML file for the ListComponent would comprise the following;
#app/components/list_component.html.erb <div> <%= header %> <!-- renders the header component --> <% titles . each practise | title | %> <div> <%= title %> <!-- renders an individual course championship --> </div> <% end %> </div>
In the html file, we take included the header, iterated through all the titles passed into the component, and rendered them. Every bit you can see, nosotros practice non have to specify the name of the component to be rendered in the list view file; our slots take taken care of that. Hence, we just place them as header
and title
.
The next footstep is to create our TitleComponent and its corresponding HTML file, every bit this is what will be rendered for every championship passed.
# app/components/title_component.rb class TitleComponent < ViewComponent :: Base def initialize ( title :) @title = title end finish
# app/components/title_component.html.erb <div> <h3> <%= @title %> </h3> </div>
Finally, in our alphabetize.html
file, let'south temporarily erase what we have and supervene upon it with the ListComponent render.
#app/views/courses/index.html.erb <%= render ListComponent . new practice | c | %> <% c . header do %> <%= link_to "List of Available Courses" , root_path %> <% end %> <%= c . title ( title: "Kickoff title" ) %> <%= c . championship ( title: "2d championship!" ) %> <% end %>
Now, allow's pass our courses into this view as a collection. To pass a drove into a slot, nosotros accept to pass the collection as an array of objects containing the variable needed for initialization. As we can see, every grade passed in should be initialized with the title statement. We can extract the titles of all the courses in our db into an assortment of hashes and render information technology.
Nosotros tin can now supervene upon our list of several titles with a unmarried c.titles
for a collection and pass information technology to our hash of titles, which we define using the variable course_titles
.
# app/views/courses/index.html.erb <% course_titles = Course . all . pluck ( :championship ). map { | title | { title: title }} %> <% c . titles ( course_titles ) %>
This yields the following:
This is exactly how slots work: rendering several components inside a single ViewComponent. Slots tin be rendered in several other means, and you can discover more info here.
Testing
Testing view components is washed by requiring "view_component/test_case" in the test file and using the render_inline
exam helper so that assertions are made against the rendered output. Permit'southward begin by testing our DiscountComponent.
require "test_helper" require "view_component/test_case" class DiscountComponentTest < ViewComponent :: TestCase def test_render_component render_inline ( DiscountComponent . new ( item: "my item" )) finish terminate
When we run this test using the command rails exam exam/components/discount_component_test.rb
, we get the post-obit error:
This proves to us that the examination is getting to the right component but is lacking a suitable prop, as the detail must be a grade with a price holding and not a string, as nosotros passed. It as well tells united states that there is a render?
method being checked before this component renders. Permit'southward laissez passer in the right variables now.
def test_render_component class = Course . create ( championship: 'Organizing your Time' , price: 55.00 , location: 'London' ) render_inline ( DiscountComponent . new ( item: grade )) end
This runs successfully. Let's keep to add assertions to this exam.
def test_render_component form = Course . create ( title: 'Organizing your Fourth dimension' , cost: 155.00 , location: 'London' ) render_inline ( DiscountComponent . new ( detail: course )) assert_selector 'p[class="green"]' assert_text "10% discount" end
This test also passes. Recall that at that place is a render condition for this component. Don't worry, though, because ViewComponent also provides a way to examination that a component is not rendered, by using refute_component_rendered
. We tin can test this using a class with a price below 100 euros.
def test_component_not_rendered course = Class . create ( title: 'Organizing your Time' , toll: 55.00 , location: 'London' ) render_inline ( DiscountComponent . new ( item: grade )) refute_component_rendered end
This exam as well passes.
Let's write some other test for our CourseComponent to test that it renders all the components nested within it.
crave "test_helper" require "view_component/test_case" class CourseComponentTest < ViewComponent :: TestCase def test_component_renders_all_children course = Grade . create ( title: 'Organizing your Time' , price: 155.00 , location: 'London' ) render_inline ( CourseComponent . new ( item: class , notice: 'A new test' , item_counter: ane )) 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
This test also passes. It tests that the Enrollee and Discount components are also rendering properly.
Recall that nosotros have a slot component, and as shown in the image below, information technology renders i header and many titles.
To test this, nosotros pass the component a block of code containing the header and titles it should return, and and then we can assert against the rendered component.
require "test_helper" require "view_component/test_case" form ListComponentTest < ViewComponent :: TestCase def test_renders_slots_with_content render_inline ( ListComponent . new ) practice | component | component . header { "A Test List" } component . titles [{ title: 'Exam championship ane' }, { title: 'Test title 2' }] finish assert_selector ( "h1" , text: "A Test List" ) assert_text ( "Examination title 1" ) assert_text ( "Test title 2" ) end stop
This test also passes :).
Rspec Testing
In addition to all that has been said higher up almost testing, if the preferred testing framework is RSpec, some additional configurations must exist carried out to enable RSpec for ViewComponents.
# spec/rails_helper.rb crave "view_component/test_helpers" require "capybara/rspec" RSpec . configure do | config | config . include ViewComponent :: TestHelpers , type: :component config . include Capybara :: RSpecMatchers , type: :component end
Our DiscountComponent examination can be re-written and retested using Rspec, as shown below:
require "rails_helper" RSpec . describe DiscountComponent , type: :component exercise it "renders the component correctly" do course = Course . create ( championship: 'Organizing your Time' , price: 155.00 , location: 'London' ) render_inline ( DiscountComponent . new ( particular: grade )) expect ( rendered_component ). to have_css "p[grade='dark-green']" , text: "10% discount" look ( rendered_component ). to have_css "img[src*='/assets/star.png']" terminate end
This test passes elegantly. Hell aye, nosotros can see our star icon.
Conclusion
Writing several view components for your Track app not only makes your code more readable and less decumbent to errors from unwanted complications merely also makes it possible to examination your views in isolation and not only during HTTP requests. View components are easy to inculcate into an existing Rails app, and its best to start with views that are mostly reused. With everything learned and so far, this should be an easy-peasy task. Nevertheless, if you would like more information on ViewComponent, do not hesitate to go through its documentation or the RubyDoc info.
Honeybadger has your back when it counts.
Nosotros're the only fault tracker that combines exception monitoring, uptime monitoring, and cron monitoring into a single, elementary to use platform. Our mission: to tame production and make y'all a better, more productive developer.
Learn moreWhat Does Assert Template Mean In Rails,
Source: https://www.honeybadger.io/blog/ruby-view-components/
Posted by: jamesinaboust.blogspot.com
0 Response to "What Does Assert Template Mean In Rails"
Post a Comment