When allowing users to work with templates in your system, e.g. for a CRM where you might want email templates, Liquid is a very widely-used option. If it has worked for Shopify all this time then there’s a good chance it will work for our users.
In this post I’ll touch on how to use Liquid in your Rails application, but the main problem solved here is how to make the template syntax error messages friendlier for your users.
Here’s an example of a Liquid error message if you forget to close the double braces:
Variable '{{' was not properly terminated with regexp: /\}\}/
This is clear enough to a programmer, but it’s likely that your users are not programmers, so termination won’t be immediately clear as a concept and ‘regexp’ won’t be familiar at all.
This error message could instead read more like
Your template has a pair of open braces ('{{') with no corresponding closing braces ('}}'), these need to match up. For example, if you have “Dear {{ first_name,” then please alter it to be “Dear {{ first_name }},”.
This could be improved but you get the idea–you can present a message that will make more sense to your particular audience, perhaps using familiar variable names also.
Using Liquid in Rails
There is a liquid-rails gem but this appears to be more for using Liquid in your view templates on the server side, and less about simply using it for user content. At time of writing the last commit was three years ago (in March 2018), so I would suggest integrating Liquid directly in your project rather than using this gem.
Add gem 'liquid'
to your Gemfile
to pull in the latest version.
Now wherever you would like to render a Liquid template you can simply do the following:
template_content = "Dear {{ first_name }},\n\nYour message.\n\nKind regards,\n\n{{ sender }}."
template_data = { 'first_name' => 'John', 'sender' => 'Stefan' }
rendered = Liquid::Template.parse(template_content).render(template_data)
Which would result in:
"Dear John,\n\nYour message.\n\nKind regards,\n\nStefan."
Providing alternative error messages
I couldn’t find this in the documentation, and a search through the issues on the Liquid Github repository didn’t uncover people asking about this, which has me wondering how often people do this–I’ll admit that I have not checked Shopify to see what kind of errors they report when you fill in invalid syntax.
Without documentation I instead looked through the source itself (bundle open liquid
), which pointed to it having an I18n
class (added
here, back in 2013) which fetches
template strings from a locale YAML file in the repository.
At time of writing there is only one local provided, en
.
A first step to providing our own error messages is to pass in our own
(Rails-provided) I18n
module in place of the Liquid-owned one. Here we set up
a validation for the template body of an EmailTemplate
model:
class EmailTemplate < ApplicationRecord
has_rich_text :body
validates_presence_of :body
validate :body_is_valid_liquid_markup
private
def body_is_valid_liquid_markup
# The to_s.to_str is required to case the ActiveSuport::SafeBuffer type to
# a plain string, as Liquid 5 requires this.
#
# https://github.com/Shopify/liquid/pull/1421
template = Liquid::Template.parse(body.to_s.to_str, locale: I18n)
rescue Liquid::SyntaxError => e
errors.add(:body, e.to_s)
end
end
On introducing a template syntax error (for forgetting to close the braces) we get:
Liquid::SyntaxError: Liquid syntax error: translation missing: en.errors.syntax.variable_termination
which shows us how we should structure our own locale file, e.g. in our Rails
app in config/locales/en.yml
:
en:
errors:
syntax:
variable_termination: Your template has a pair of open braces ('%{token}') with no corresponding closing braces ('}}'), these need to match up. For example, if you have "Dear {{ first_name," then please alter it to be "Dear {{ first_name }},".
I have left one instance of %{token}
in there just to confirm that such
variable substitution still happens, which it does. As far as I know the only
delimiter for variables is {{ name }}
so I don’t see the need for
interpolating in this case but it’s good to be able to do so as necessary.
If you now introduce another type of error, e.g. an unterminated for ({% for
)
then you will get:
translation missing: en.errors.syntax.tag_termination
At this point it would make sense to copy over the contents of the liquid locale YAML file in the repository into your local locale file so that you have a value for all possible template syntax errors.
You can now tweak any of these error messages accordingly, and potentially provide translations if it would make sense to do so for your userbase.
When you upgrade the liquid gem in your project be sure to check the locale file for any new entries, though after ~8 years of production use I wouldn’t expect to see (m)any additions.