1. Skip to navigation
  2. Skip to content

The ELC Community Blog

A knowledge exchange on Ruby on Rails and Agile Development


Liquid Template Tags

by Ryan Garver on April 20, 2008

I've been playing around with Liquid recently and have had a lot of fun extending it for a CMS that we're building. It wasn't obvious how to get started, but Liquid is a pretty lightweight code base and after some digging I was able to figure out how to create new custom tags for use in our templates.

The reason why I needed a custom tag was to build a Gravatar image url. This requires an email to be hashed and composed in to a URL. One of the nice things about Liquid is the fact that it protect you from templates that could do things that you don't want to allow. Unlike ERB, Liquid does not evaluate Ruby code directly. It will recognize tags and defer the evaluation to the tag definition which is usually parameterized. We want to be able to insert a line like this in our code:

   1  
   2    <img src="{% gravatar_image_url 'jdoe@example.com' size:40 %}" />

When Liquid evaluates that template we want the tag gravatar_image_url to take an email and a list of attributes and output a URL which will show the avatar for the specified icon. For this tag we will start off by creating a class inheriting from Liquid::Tag.

   1  
   2    module Liquid
   3      class GravatarImageUrl < Tag
   4        Syntax = /([^\s]+)\s+/
   5        def initialize(tag_name, markup, tokens)
   6          # ...
   7        end
   8      
   9        def render(context)
  10          # ...
  11        end
  12      end
  13    end

NOTE: I've seen some plugins and older versions of liquid that don't have the tag_name argument for initialize. In this example the parameter can be dropped and it should work fine.

There are two methods that we need to override: initialize and render. The initialize method is called to parse the arguments and prepare for rendering once the context is established (this would allow for a two pass evaluation and a possibility for some basic caching of state).

   1  
   2    Syntax = /([^\s]+)\s+/
   3    def initialize(markup, tokens)
   4      if markup =~ Syntax
   5        @email = $1
   6        @attributes = {}
   7        markup.scan(TagAttributes) do |key, value|
   8          @attributes[key] = value
   9        end
  10      else
  11        raise SyntaxError.new("Syntax Error in 'gravatar_image_url' - Valid syntax: gravatar_image_url [email]")
  12      end
  13    end

The Syntax trick here isn't normally my style, so I should give credit to the authors of Liquid for demonstrating it to me. It simplifies the process of parsing out important pieces from the input stream. The markup parameter is providing the string that follows the tag name. So if we put gravatar_image_url 'jdoe@example.com' size:40, markup would be set to 'jdoe@example.com' size:40. TagAttributes is provided by Liquid along with a number of other helper regular expressions.

   1  
   2    def render(context)
   3      base_url = "http://www.gravatar.com/avatar.php?gravatar_id=#{Digest::MD5.hexdigest(context[@email])}"
   4      extended_attrs = @attributes.map{|k,v| "#{URI.encode(k)}=#{URI.encode(v)}"}
   5      ([base_url]+extended_attrs).compact.join('&amp;')
   6    end

Here is where we get a real taste of the execution context. The first line we are running a MD5 hexdigest on the email, as specified by the Gravatar Docs. We allow for non-literal values here by asking the context tell us what the email actually is. This allows us to do things like: gravatar_image_url post.author.email size:40. The context has enough information to evaluate the post.author.email string and return the value. Incidentally this context trick also allows for some interesting tricks like doing basic math and such.

The last step is to register this Tag definition with a real name with the Liquid::Template handler.

   1  
   2    Template.register_tag('gravatar_image_url', GravatarImageURL)

And now you have your very own custom Liquid tag!

Comments

Add a comment


home | services | Ruby on Rails Development | code | blog | company