ruby

Render a Component Preview In Showcase for Ruby on Rails

Alexandre Ruban

Alexandre Ruban on

Render a Component Preview In Showcase for Ruby on Rails

In part one of this series, we walked through how to use Showcase in a Rails app.

It's now time to read some Ruby code written by experienced Rails developers. To do this without getting lost, we'll choose one feature of the showcase engine and analyze how it works: rendering a preview of a component.

Let's get started!

About Showcase's Button Component Preview

If we visit http://localhost:3000/docs/showcase/previews/components/button in the browser, we should see the complete documentation for a button component:

Showcase button documentation

According to the showcase documentation, this view is generated by the following code in the test/dummy application:

erb
<%# test/dummy/app/views/showcase/previews/components/_button.html.erb %> <% showcase.badge :partial, :component %> <% showcase.description "Button is our standard element for what to click on" %> <% showcase.sample "Basic" do %> <%= render "components/button", content: "Button content", mode: :small %> <% end %> <% showcase.sample "Large", description: "This is our larger button" do %> <%= render "components/button", content: "Button content", mode: :large %> <% end %> <% showcase.options do |o| %> <% o.required :content, "The content to output as the button text" %> <% o.optional :mode, "We support three modes", default: :small, options: %i[ small medium large ] %> <% end %>

But how does it work? That's what we are going to find out!

Rendering the Title, Badges, and Description

First, we'll modify our preview file to only render the title, badges, and description for now, making things easier to follow. Let's remove everything else from the preview file:

erb
<%# test/dummy/app/views/showcase/previews/components/_button.html.erb %> <% showcase.badge :partial, :component %> <% showcase.description "Button is our standard element for what to click on" %>

If we refresh the page, we should now see a much simpler page:

Showcase simplified button preview

To understand how such a view is generated, we can examine our Rails server logs, just as we would in a regular Rails application. If we refresh the button preview page, we should see the following logs in our terminal:

Shell
Started GET "/docs/showcase/previews/components/button" Processing by Showcase::PreviewsController#show as HTML Parameters: {"id" => "components/button"}

As we can see, the request is routed to the Showcase::PreviewsController#show action, with the parameter id set to "components/button". If we take a look at the routes, we can see that any route starting with previews/ will be routed to the Showcase::PreviewsController#show action:

Ruby
# config/routes.rb Showcase::Engine.routes.draw do get "previews/*id", to: "previews#show", as: :preview end

Showcase::PreviewsController#show looks like this:

Ruby
# app/controllers/showcase/previews_controller.rb class Showcase::PreviewsController < Showcase::EngineController def show @preview = Showcase::Path.new(params[:id]).preview_for view_context end end

As we can see, the show action instantiates a new Showcase::Path object with a "components/button" extracted from the params as an argument.

It then calls the preview_for method on this instance, passing the view_context as an argument. The result of this method call is assigned to the @preview instance variable, which will be used later when rendering the view.

About view_context

If you are not familiar with view_context, it is a Rails method that provides access to view helpers and other view-related methods. We will use it later to render HTML from models by calling view_context.render.

Let's break down this code. The Showcase::Path initializer looks like this:

Ruby
# app/models/showcase/path.rb class Showcase::Path attr_reader :id, :segments, :basename def initialize(path) @id = path.split(".").first.delete_prefix("_").sub(/\/_/, "/") @basename = File.basename(@id) @segments = File.dirname(@id).split("/") end end

This means that Showcase::Path.new("components/button") will create a new Showcase::Path object with the following attributes:

Ruby
Showcase::Path.new("components/button") # => @basename = "button" # => @id = "components/button" # => @segments = ["components"]

The controllers then call the Showcase::Path#preview_for method on this instance, with the view_context as an argument:

Ruby
# app/models/showcase/path.rb class Showcase::Path def preview_for(view_context) Showcase::Preview.new(view_context, id: id, title: basename.titleize).tap(&:render_associated_partial) end end

This method first instantiates a new Showcase::Preview object with the view_context, the id, and the basename as arguments. In our case, the id is "components/button" and the basename is "button".

About the Showcase::Preview Initializer

Let's have a look at the Showcase::Preview initializer:

Ruby
# app/models/showcase/preview.rb class Showcase::Preview attr_reader :id, :badges, :samples def initialize(view_context, id:, title: nil) @view_context, @id = view_context, id @badges, @samples = [], [] title title end def title(content = nil) @title = content if content @title end end

As we can see, the initializer stores those arguments in instance variables. Here is the current state of our Showcase::Preview instance:

Ruby
Showcase::Preview.new(view_context, id: "components/button", title: "Button") # => @title = "Button" # => @id = "components/button" # => @samples = [] # => @badges = [] # => @view_context = The view context helper

The Showcase::Preview#render_associated_partial method is then called on the Showcase::Preview instance:

Ruby
# app/models/showcase/preview.rb class Showcase::Preview def render_associated_partial @view_context.render "showcase/previews/#{id}", showcase: self nil end end

This method calls the render method on the @view_context. The render method is the one we use every day in our Rails views to render a template or a partial and return some HTML.

In our case, the partial that we want to render is the showcase/previews/components/button partial. The showcase local is passed to the partial, which allows us to access our Showcase::Preview instance in the partial:

erb
<%# test/dummy/app/views/showcase/previews/components/_button.html.erb %> <% showcase.badge :partial, :component %> <% showcase.description "Button is our standard element for what to click on" %>

In the partial, we call two methods on the showcase local variable. The first method is badge, which adds badges to the preview:

Ruby
# app/models/showcase/preview.rb class Showcase::Preview def badge(*badges) @badges.concat badges end end # => @badges = [:partial, :component]

The second method is description, which sets the preview description:

Ruby
# app/models/showcase/preview.rb class Showcase::Preview def description(content = nil, &block) @description = content || @view_context.capture(&block) if content || block_given? @description end end # => @description = "Button is our standard element for what to click on"

All of this code exists just to populate the @badges, @description, and @title instance variables of our Showcase::Preview instance.

This instance is returned by the Showcase::Path#preview_for method and assigned to the @preview instance variable in the Showcase::PreviewsController#show action:

Ruby
# app/controllers/showcase/previews_controller.rb class Showcase::PreviewsController < Showcase::EngineController def show @preview = Showcase::Path.new(params[:id]).preview_for view_context end end

Now, just like any controller, the Showcase::PreviewsController will render the corresponding view. By convention, the view should be located at app/views/showcase/previews/show.html.erb. To my surprise, it is located at app/views/showcase/engine/show.html.erb, which isn't very conventional. However, it works because the Showcase::PreviewsController inherits from Showcase::EngineController.

If we have a look at the view, it contains the following code:

erb
<%# app/views/showcase/engine/show.html.erb %> <%= render "showcase/engine/preview", preview: @preview %>

This view renders a partial called showcase/engine/preview and passes our @preview instance variable.

If we have a look at this view, we can see that it will render the title, badges, and description of the preview in between the Tailwind CSS classes:

erb
<%# app/views/showcase/engine/_preview.html.erb %> <% if preview.title %> <div class="..."> <h2 class="..."><%= preview.title %></h2> <% preview.badges.each do |badge| %> <span class="..."><%= badge.to_s.titleize %></span> <% end %> </div> <% end %> <% if preview.description %> <div class="..."><%= preview.description %></div> <% end %>

If we take a step back and reflect on the code architecture, it makes a lot of sense. The Showcase::Preview model is responsible for storing the preview data, such as the title, badges, and description. The trick here is that this data is populated while rendering a preview template:

erb
<%# test/dummy/app/views/showcase/previews/components/_button.html.erb %> <% showcase.badge :partial, :component %> <% showcase.description "Button is our standard element for what to click on" %>

As users of the showcase engine, it feels like we are writing a view, but in reality, we are populating a model that will be used to render the final view. And look at how clean the interface is! We experience a great developer experience when using this gem!

Next Up: Rendering Samples using Showcase for Ruby on Rails

In this post, we've seen how the title, badges, and description of a preview are rendered in Showcase.

Now that we have a good understanding of how showcase works, we can get our hands dirty with something a bit more meaty in the third and final part of our series: rendering samples!

See you next time!

Wondering what you can do next?

Finished this article? Here are a few more things you can do:

  • Share this article on social media
Alexandre Ruban

Alexandre Ruban

Our guest author Alexandre is a freelance Ruby on Rails developer and author of the Turbo Rails tutorial. He enjoys writing about Ruby on Rails and reading open source code. He has contributed to Ruby on Rails, Hotwire, and Phlex.

All articles by Alexandre Ruban

Become our next author!

Find out more

AppSignal monitors your apps

AppSignal provides insights for Ruby, Rails, Elixir, Phoenix, Node.js, Express and many other frameworks and libraries. We are located in beautiful Amsterdam. We love stroopwafels. If you do too, let us know. We might send you some!

Discover AppSignal
AppSignal monitors your apps