Introducing Lotus::View


The missing part of all the Ruby web frameworks is the distinction between views and templates. A view is an object that encapsulates the presentation logic of a page. A template is a file that defines the semantic and visual elements of a page. In order to show a result to an user, a template must be rendered by a view.

Keeping things separated, helps to declutter templates and models from presentation logic. Also, since views are objects they are easily testable. If you ever used Mustache, you are already aware of the advantages.

Lotus::View is based on these simple concepts.

Views

Here how a view looks like:

require 'lotus/view'

module Articles
  class Index
    include Lotus::View
  end
end

This syntax follows the Lotus philosophy: include a module that injects a minimal interface. Before to illustrate how to use a view, I’d like to talk about a few conventions:

  • Templates are searched under Lotus::View.root, set this value according to your app structure (eg. "app/templates").
  • A view will look for a template with a file name that is composed by its full class name (eg. "articles/index").
  • A template must have two concatenated extensions: one for the format one for the engine (eg. ".html.erb").
  • The framework must be loaded before to render for the first time: Lotus::View.load!.

Usage

Suppose that we want to render a list of articles:

require 'lotus/view'

module Articles
  class Index
    include Lotus::View
  end
end

Lotus::View.root = 'app/templates'
Lotus::View.load!

path     = Lotus::View.root.join('articles/index.html.erb')
template = Lotus::View::Template.new(path)
articles = ArticleRepository.all

Articles::Index.new(template, articles: articles).render

While this code is working fine, it’s inefficient and verbose, because we are loading a template from the filesystem for each rendering attempt. Also, this is strictly related to the HTML format, what if we want to manage other formats?

require 'lotus/view'

module Articles
  class Index
    include Lotus::View
  end

  class AtomIndex < Index
    format :atom
  end
end

Lotus::View.root = 'app/templates'
Lotus::View.load!

articles = ArticleRepository.all

Articles::Index.render(format: :html, articles: articles)
  # => This will use Articles::Index
  #    and "articles/index.html.erb"

Articles::Index.render(format: :atom, articles: articles)
  # => This will use Articles::AtomIndex
  #    and "articles/index.atom.erb"

Articles::Index.render(format: :xml, articles: articles)
  # => This will raise a Lotus::View::MissingTemplateError

First of all, we are preloading templates according to the above conventions, they are cached internally for future use. This is a huge performance improvement.

A view is able to understand the given context and decide if render by itself or delegate to a subclass.

All the objects passed in the context are called locals, they are available both in the view and in the template:

require 'lotus/view'

module Articles
  class Show
    include Lotus::View

    def authors
      article.map(&:author).join ', '
    end
  end
end
<h1><%= article.title %></h1>
<article>
  <%= article.content %>
</article>

All the methods defined in the view are accessible in the template:

<h2><%= authors %></h2>

Custom rendering

Since a view is an object, you can override #render and provide your own rendering policy:

require 'lotus/view'

module Articles
  class Show
    include Lotus::View
    format :json

    def render
      ArticleSerializer.new(article).to_json
    end
  end
end

Articles::Show.render({format: :json, article: article})
  # => This will render from ArticleSerializer,
  #    without the need of a template

Other features

Lotus::View supports countless rendering engines, layouts, partials and it has lightweight presenters. They are explained in detail in the README and the API documentation.

Roadmap

As part of the Lotus roadmap, I will open source a framework each month. On April 23rd I will release Lotus::Model.

{% include _lotusml.html %}

Luca Guidi

Family man, software architect, Open Source indie developer, speaker.

Rome, Italy https://lucaguidi.com