banner



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!

CourseComponent Generated

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.

CourseComponent Initialized

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:

First course displayed

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:

Additional content being rendered

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:

The Course collection rendered

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:

Courses rendered with an additional notice parameter

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

Counter added to each course

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                      

DiscountComponent generated

This component is already automatically initialized with the item information technology should brandish a discount for, as seen below.

DiscountComponent initialized

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:

DiscountComponent rendered

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:

Number of enrollees shown using Helpers

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:

Star icon shown using the before render method

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.

DiscountComponent previewed

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.

A simple template for view components

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.

ViewComponent template for asset inclusion

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              %>                      

Individual titles rendered using Slots

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.

A hash containing the titles of all the courses

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:

A collection of titles rendered using Slots

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:

DiscountComponent Test Error Message

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.

ListComponent using slots to render other Components

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 more

What 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

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel