DEV Community

Cover image for Implementing custom liquid tags
Rafi
Rafi

Posted on

Implementing custom liquid tags

Liquid tags are awesome dev.to uses some custom liquid tags to embed posts, gist, github repos, twitch streams and lot more. To understand how liquid tag works in dev.to I tried to implement similar custom liquid tag.

We will build a gist liquid tag. That renders a gist when provided with url like below

You can get the boiler plate for this project from here in boilerplate branch

$ git clone https://github.com/Rafi993/custom-liquid-tags $ cd custom-liquid-tags $ git checkout boilerplate $ bin/setup 
Enter fullscreen mode Exit fullscreen mode

It provides you with basic routing, layout and CSS.

Liquid template language

Liquid is templating language created by shopify (similar to erb). We will include liquid gem in the project by adding liquid in the Gemfile and running bundle install

gem 'liquid', '~> 4.0', '>= 4.0.3' 
Enter fullscreen mode Exit fullscreen mode

to parse liquid tags in user content we can do the following in the preview action

 def preview @template = Liquid::Template.parse(params[:text]) @parsed_html = @template.render() render "preview" end 
Enter fullscreen mode Exit fullscreen mode

and liquid tags should work now

abs tag

abs tag output

we will disable these default tags later

Custom tags

First we will create a app/liquid_tags/liquid_tag_base.rb
from which all the liquid tags will inherit from.

class LiquidTagBase < Liquid::Tag def self.script "" end def initialize(_tag_name, _content, parse_context) super end end 
Enter fullscreen mode Exit fullscreen mode

If you have some common code that needs to be shared across all liquid tags you can place it here.

Then we will create our gist liquid tag app/liquid_tags/gist_tag.rb we could copy the code from Forem repo for this :)

class GistTag < LiquidTagBase PARTIAL = "liquids/gist".freeze VALID_LINK_REGEXP = %r{\Ahttps://gist\.github\.com/([a-zA-Z0-9](-?[a-zA-Z0-9]){0,38})/([a-zA-Z0-9]){1,32}(/[a-zA-Z0-9]+)?\Z} .freeze def initialize(_tag_name, link, _parse_context) super raise StandardError, "Invalid Gist link: You must provide a Gist link" if link.blank? @uri = build_uri(link) end def render(_context) ApplicationController.render( partial: PARTIAL, locals: { uri: @uri, }, ) end private def build_uri(link) link = ActionController::Base.helpers.strip_tags(link) link, option = link.split(" ", 2) link = parse_link(link) uri = "#{link}.js" uri += build_options(option) unless option&.empty? uri end def parse_link(link) input_no_space = link.delete(" ").gsub(".js", "") if valid_link?(input_no_space) input_no_space else raise StandardError, "Invalid Gist link: #{link} Links must follow this format: https://gist.github.com/username/gist_id" end end def build_options(option) option_no_space = option.strip return "?#{option_no_space}" if valid_option?(option_no_space) raise StandardError, "Invalid Filename" end def valid_link?(link) (link =~ VALID_LINK_REGEXP)&.zero? end def valid_option?(option) (option =~ /\Afile=[^\\]*(\.(\w+))?\Z/)&.zero? end end Liquid::Template.register_tag("gist", GistTag) 
Enter fullscreen mode Exit fullscreen mode

The above code parses provided argument to gist liquid tag and generates proper uri that it can use. It also specifies which partial to use for the liquid tag.

we will create that partial file that says how to render this tag app/views/liquids/_gist.html.erb

<div> <script id="gist-ltag" src="<%= uri %>"></script> </div> 
Enter fullscreen mode Exit fullscreen mode

If we actually try to use the gist liquid tag now it will throw error saying tag gist is not defined

Initializing liquid tag

We can create our initializer in config/initializers/liquid.rb that loads our custom liquid tags

Rails.application.config.to_prepare do # Custom Liquid tags are loaded here Dir.glob(Rails.root.join("app/liquid_tags/*.rb")).sort.each do |filename| require_dependency filename end end 
Enter fullscreen mode Exit fullscreen mode

The above code looks for liquid_tags in the specified path and loads them.

Now when we try our gist liquid tag it should work

Alt Text

Disabling default tags

You may not want default tags like assign, raw to work. You could disable those by adding the following code to config/initializers/liquid.rb

 # Tags to disable Dir.glob(Rails.root.join("lib/liquid/*.rb")).sort.each do |filename| require_dependency filename end 
Enter fullscreen mode Exit fullscreen mode

and defining those tags there inside lib/liquid folder

# lib/liquid/raw.rb module Liquid class Raw < Block remove_const(:FullTokenPossiblyInvalid) if defined?(FullTokenPossiblyInvalid) FullTokenPossiblyInvalid = /\A(.*)#{TagStart}\s*(\w+)\s*#{TagEnd}\z/om.freeze # rubocop:disable Naming/ConstantName end Template.register_tag("raw", Raw) end 
Enter fullscreen mode Exit fullscreen mode
# lib/liquid/variables.rb module Liquid class Variable def initialize(_markup, _parse_context) raise StandardError, "Liquid variables are disabled" end end end 
Enter fullscreen mode Exit fullscreen mode

Now you should not able to use default tags like assign, abs ...

You can find the source for the project here

Note:

  1. Forem does little more when by checking if the user can access a liquid tag using pundit gem

cover image by Vitaly Vlasov from Pexels

Top comments (3)

Collapse
 
potentialstyx profile image
PotentialStyx

Thanks for the tutorial

Collapse
 
rafi993 profile image
Rafi

Glad you found it useful

Collapse
 
feliperaul profile image
flprben

Came here to see how you solved the autoloading of your custom tags. I knew I had to use #to_prepare but didn't know about require_dependency