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
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'
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
and liquid tags should work now
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
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)
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>
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
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
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
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
# lib/liquid/variables.rb
module Liquid
class Variable
def initialize(_markup, _parse_context)
raise StandardError, "Liquid variables are disabled"
end
end
end
Now you should not able to use default tags like assign, abs ...
You can find the source for the project here
Note:
- 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)
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 aboutrequire_dependency
Thanks for the tutorial
Glad you found it useful