Introduction

Liquid is a templating engine that allows you to enable your users to quickly and easily customize your views at run-time while maintaining the safety, security, and integrity of your servers. Liquid can be used for everything from customizable email templates to entire websites. This article will provide you with a brief introduction to Liquid and show you how to integrate it with your Ruby on Rails application.

Liquid Syntax

In liquid, there are two different types of markup. The first, output markup, is denoted by double sets of curly braces, for example:


Hello {{ user.name }}

The second type of markup, tag markup, is typically used for logic and control structures such as loops. For example:


<ul>
{% for user in users %}
<li>{{ user.name }}</li>
{% endfor %}
</ul>

Also demonstrated in the above example is a loop. the above code loops through each user in the users collection and prints the user's name. You can also do basic if statements. For example:


{% if user.name != 'Rich' %}
  Hi Rich!
{% elsif user.name == 'Bob' %}
  Hey Bob!!!!
{% else %}
  Hello {{ user.name }}
{% endif %}

The above code prints a variation of hello based on the user that is returned. Below is a list of all of the tags available in Liquid. For a complete breakdown of the Liquid Templating engine, be sure to check out the Liquid For Designers page.

  • assign - Assigns some value to a variable
  • capture - Block tag that captures text into a variable
  • case - Block tag, its the standard case...when block
  • comment - Block tag, comments out the text in the block
  • cycle - Cycle is usually used within a loop to alternate between values, like colors or DOM classes.
  • for - For loop
  • if - Standard if/else block
  • include - Includes another template; useful for partials
  • raw - temporarily disable tag processing to avoid syntax conflicts.
  • unless - Mirror of if statement

Liquid on Rails

Now that we have a basic grasp on the Liquid template engine itself, let's learn how to utilize it in our Ruby on Rails application. First we need to add the gem to our gemfile. Add the following line to your gemfile now.

Gemfile:

gem 'liquid', '~> 2.6.1'

Now run a bundle install to install the liquid gem.

Terminal Commands:

bundle install

Now let's create a model called Page. The Page model will be used to represent a page owned by a specific user. For simplicity's sake (since this is just an example app) we will have a string called 'user' instead of a dedicated user model. Run the commands below to create this model now.

Terminal Commands:

rails g model Page user:string template:text
rake db:migrate

Now we need a bit of seed data, open up your seeds file and modify it so that it looks like code listed below.

db/seeds.rb:

Page.delete_all

Page.create(id: 1, user: "Rich", template: %{
  Hello {{ user }}, here is your shopping list.
  <ul>
    {% for item in list %}
      <li>{{ item.name }}</li>
    {% endfor %}
  </ul>
}

)

Page.create(id: 2, user: "Bob", template: %{
  What is up my man?!? Here is your shopping list.
  <ul>
    {% for item in list %}
      <li>{{ item.name }}</li>
    {% endfor %}
  </ul>
}
)

Page.create(id: 3, user: "Chris", template: %{
  HTTP 200:  Shopping List Found

  Items in your list:
  <ul>
    {% for item in list %}
      <li>{{ item.name }}</li>
    {% endfor %}
  </ul>
}

)

Now run the rake db:seed command to seed the database with your example data.

Terminal Commands:

rake db:seed

Great, now let's create a homes controller that will allow us to play with liquid. Run the command below to create the homes controller now.

Terminal Commands:

rails g controller Homes show

Next let's create a pages controller. This controller will be used to display a user's page. Run the commands below to create the pages controller now.

Terminal Commands:

rails g controller Pages show

Next let's set up our routes. Modify your routes file so that it looks like the code listed below.

config/routes.rb:

Rails.application.routes.draw do
  resource :home, only: [:show]
  resources :pages, only: [:show]
  root to: "homes#show"
end

Excellent, now let's modify our homes controller to pull down a list of all the user's pages. Open up your homes controller and modify it so that it looks like the code listed below.

app/controllers/homes_controller.rb:

class HomesController < ApplicationController
  def show
    @pages = Page.all
  end
end

Great, now let's modify our pages controller to pull down the correct page for the specified user. Open up your pages controller and modify it so that it looks like the code listed below.

app/controllers/pages_controller.rb:

class PagesController < ApplicationController
  def show
    @page = Page.find(params[:id])
  end
end

Excellent, now let's create a few helper methods for use with this example. Open up your pages helper and modify it so that it looks like the list below.

app/helpers/pages_helper.rb:

module PagesHelper
  def shopping_list(user)
    {"user" => user, "list" => shopping_list_items}
  end

  def shopping_list_items
    [
      { "name" => 'Apple', "quantity_needed" => "2"},
      { "name" => 'Pear', "quantity_needed" => "1"},
      { "name" => 'Banana', "quantity_needed" => "3"},
    ]
  end
end

The code above merely gives us some example data to play around with. We could just as easily use a model or some other data source. Now modify your pages show view to look like the code listed below.

app/views/pages/show.html.erb:

<h1><%= @page.user %>'s Personal Page</h1>

<% template = Liquid::Template.parse(@page.template) %>

<%= template.render(shopping_list(@page.user)).html_safe %>

The code above tells Rails to render your template using the Liquid Template Engine. Finally, open up the show view for your home controller and add in the code listed below to finish things up.

app/views/homes/show.html.erb:

<h1>User Pages</h1>
<ul>
  <% @pages.each do |page| %>
    <li><%= link_to "#{page.user}'s page", page_path(page) %></li>
  <% end %>
</ul>

Now if you fire up a rails server and visit your development site, you'll notice that you can browse each user's pages and get a customized version of each one. However, what if you want to use the Liquid templating engine as a replacement for erb itself? This is easy, first, create a new file in the lib folder called liquid_view.rb and add in the code listed below.

lib/liquid_view.rb:

class LiquidView
  def self.call(template)
    "LiquidView.new(self).render(#{template.source.inspect}, local_assigns)"
  end

  def initialize(view)
    @view = view
  end

  def render(template, local_assigns = {})
    @view.controller.headers["Content-Type"] ||= 'text/html; charset=utf-8'

    assigns = @view.assigns

    if @view.content_for?(:layout)
      assigns["content_for_layout"] = @view.content_for(:layout)
    end
    assigns.merge!(local_assigns.stringify_keys)

    controller = @view.controller
    filters = if controller.respond_to?(:liquid_filters, true)
                controller.send(:liquid_filters)
              elsif controller.respond_to?(:master_helper_module)
                [controller.master_helper_module]
              else
                [controller._helpers]
              end

    liquid = Liquid::Template.parse(template)
    liquid.render(assigns, :filters => filters, :registers => {:action_view => @view, :controller => @view.controller})
  end

  def compilable?
    false
  end
end

Next, create an initializer called liquid_template_handler and add in the code listed below.

config/initializers/liquid_template_handler.rb:

require 'liquid_view'
ActionView::Template.register_template_handler :liquid, LiquidView

Upon restarting your Rails server you will be able to create actual views with the .liquid extension that uses the liquid templating engine.

For more information on Liquid, be sure to check out their website at http://liquidmarkup.org/. That's it! Thanks for reading!