#2 Listing articles in Hanami and Dry-View

In the previous episode I've created a new Hanami application using Template repository.

At the moment, It only has the home page implemented, but I'd love to transfer it into a blog application. In this episode I'll show you how to list the objects in Hanami template. We will focus on understanding the Hanami::View part of the architecture.

To start, let's start by adding a new route, under the /articles url.

Adding static HTML rendering endpoint

First let's visit the routes.rb file stored in the config directory. The router distributes the incoming requests and decides which action should handle that.

So let's add a a route to handle get requests to /articles URL and write the handler to it. The handler in Hanami, can be literally anything that responds to a call method, accepting the rack env as an argument, and returning the serialized rack response, so for a very minimal example, we can even use a raw proc.

# /config/routes.rb

Hanami.application.routes do
  slice :main, at: "/" do
    root to: "home.show"

    get '/articles', to: ->(env) { [200, {}, ['<h1>Articles</articles>']] }
  end
end

This will render the level one header HTML tag with the Articles string in the browser.

Static Page renderingStatic Page rendering

Rendering

This is super simple and elastic in use, because as those are the only requirements, we can replace this proc with any object that meets the router expectations!

If we'd have to render more complicated templates, however, it'd be nice to have a class that takes care of preparing the HTML to keep our routes clean and simple.

This is when #Hanami/Action come in. I can replace the proc written directly in the routes by a path pointing into the action I want to call instead.

# /config/routes.rb

Hanami.application.routes do
  slice :main, at: "/" do
    root to: "home.show"

    # get '/articles', to: ->(env) { [200, {}, ['<h1>Articles</articles>']] }
    get '/articles', to: "blog.articles"
  end
end

By default Hanami assumes that actions for a given slice are placed in the actions folder inside this slice, and the rest of the path matches what we've written in the route.

Let me then create a new action named articles, in the blog folder.

Remember: It can be any ruby object, that responds to a call, accepting rack env as an argument and returning the rack response.

So let's define the call method, and return the standard rack response, but with different string in the h1 tag, just to be sure it works.

# /slices/main/lib/main/actions/blog/articles.rb

module Main
  module Actions
    module Blog
      class Articles
        def call(env)
          [200, {}, ['<h1>Articles rendered by class</h1>']]
        end
      end
    end
  end
end

Now when I'll visit the browser, You should see the updated text. Awesome!

Note: If you're trying it out before the Hanami 2.0 is officially released, you may need to restart the server!

Static Page renderingStatic Page rendering

Using views and templates

While this works fine, usually we'd like to write our templates in the html or slim files, or serialize our JSON responses using serializers instead of using raw strings everywhere.

This is why in #Hanami, except actions, we have also views and templates.

Static Page renderingStatic Page rendering

The whole request flow starts from the router, then it's distributed into a proper action.

  • Action parses the request to extract the params and headers. The action then calls the proper view object with prepared arguments.
  • The view, based on the given arguments, prepares data for template to render, and then renders the proper template with certain local variables exposed.
  • The template, however, only specifies, how the structure of the rendered document looks like. Then it's rendered by a view.

Having this in mind, let's leverage this architecture, starting by using the Hanami::Action.

When I'll remove the custom call method and inherit from the Main::Action, it'll look for a specific view and try to call it with the prepared parameters.


# /slices/main/lib/main/actions/blog/articles.rb

module Main
  module Actions
    module Blog
      class Articles < Main::Action
      end
    end
  end
end

Please notice that we inherit here for an action specific for the given slice - as there may be a situation, where each slice will have different authorization strategy, or other request transformations.

Now let's create a template for the articles listing of our blog. I create the articles.html.slim template file in the templates directory and inside let's list some articles' titles.

# /slices/main/web/templates/blog/articles.html.slim

h1 Blog articles

ul
  - articles.each do |article|
    li = article.title

Then I need a view that will render this template, which will be stored under the same path in the views directory of the main slice.

# /slices/main/lib/main/views/blog/articles.rb

Article = Struct.new(:title)

module Main
  module Views
    module Blog
      class Articles < View::Base
        expose :articles do
          %w[article1 article2 article3].map do |title|
            Article.new(title)
          end
        end
      end
    end
  end
end

Within the view lays the logic of preparing the data for a template. My template need articles collection that it can iterate by, so let's quickly expose articles method, and within generate a simple array of structs to be returned.

I need the article definition yet, and we're ready to go! Quick look at the browser, and Voila! Here are our articles listed!

Static Page renderingStatic Page rendering

This is still just a rendering - to style it up you'll need to make use of Hanami::Assets which I cover in the next episode

Summary

In this episode we've went through the basic rendering flow in Hanami applications, and understood the view-related part of the Hanami Architecture.

It may be a lot comparing to simplified, MVC approach, where There are only three parts of the system - Model, View, and Controller, but history proved that MVC just does not scale well.

The extended Hanami architecture, where each class has it's dedicated purpose definitely allows to reduce coupling in bigger systems and helps to better manage complexity.

Special Thanks