Skip to content

Instantly share code, notes, and snippets.

@stex
Last active December 20, 2015 22:28
Show Gist options
  • Save stex/6204845 to your computer and use it in GitHub Desktop.
Save stex/6204845 to your computer and use it in GitHub Desktop.
Liquid Rendering Helper for Rails controllers and views. Simply include LiquidHelpers::Rendering in each controller you want liquid rendering to be available. The main method you'll have to change to get this working in your application is `get_template_content` which fetches the actual template content either from disk or database.
#This module contains all necessary methods to render liquid templates
#in a controller.
module LiquidHelpers
module Rendering
def self.included(base)
base.class_eval do
helper_method :render_liquid if respond_to?(:helper_method)
end
end
protected #Make these actions only available in controllers and views
# Renders a liquid template
# Parameters:
# - Path to a skin template or an already loaded template content (not yet parsed)
# - [optional] Layout for the template to be rendered in
# - [optional] Options-Hash
# Options may include:
# +:assigns+:: Assigns to be available to the rendered template
#
# +:layout+:: If the layout was not given as the second parameter
# it may be handed in through the options
#
# +:return_string+:: If set to true, the function will return the rendered content
# instead of sending it to the user's browser directly
#
# +:pre_rendered+:: If this is set to +true+, the template (first argument) will
# not be rendered through liquid any more and just be passed
# to the layout rendering (if any)
#
# +:content_given+:: If set to true, the first argument will be interpreted as an already
# loaded liquid template content and be passed to the rendering function
#
# +:partial+:: If set to true, a partial will be rendered.
# Only works if no layout was given.
#--------------------------------------------------------------
def render_liquid(*args)
template_path_or_content = args.first
layout_path = nil
assigns = nil
options = {}
if args.second.is_a?(Hash)
options = args.second
else
layout_path = args.second
options = args.third if args.third
end
layout_path ||= options[:layout]
pre_rendered_template = options[:pre_rendered]; pre_rendered_template = false if pre_rendered_template.nil?
return_string = options[:return_string]; return_string = false if return_string.nil?
rendering_options = {}
rendering_options[:content_given] = options[:content_given]
rendering_options[:partial] = options[:partial]
#Build the correct assigns hash
assigns ||= options[:assigns] || {}
assigns.stringify_keys!
#Run .to_liquid on assigns. This should usually not be necessary, but
# for some reason liquid fails to run it itself, especially for arrays.
assigns.each do |key, val|
if val.is_a?(Array)
assigns[key] = val.map(&:to_liquid)
end
end
#If the template is already rendered, just push it to the layout rendering (if any)
if pre_rendered_template
rendered_content = template_path_or_content
rendered_content = render_layout(template_path_or_content, layout_path, assigns) if layout_path
else
#Otherwise, render the template in the optional layout
rendered_content = layout_path ? render_template_in_layout(template_path_or_content, layout_path, assigns, rendering_options) : render_template(template_path_or_content, assigns, rendering_options)
end
return_string ? rendered_content : render(:text => rendered_content)
end
private
# Renders a template in the given layout
# TODO: Check, if we have to grab the {% assigns %} from the rendered template and push them to the layout.
#--------------------------------------------------------------
def render_template_in_layout(template, layout, assigns = {}, options = {})
rendered_template = render_template(template, assigns, options)
render_layout(rendered_template, layout, assigns)
end
# Renders a given template, returns the rendered content
#--------------------------------------------------------------
def render_template(template, assigns = {}, options = {})
set_virtual_file_system(template)
parsed_template = Liquid::Template.parse(get_template_content(template, options))
parsed_template.render(assigns, :registers => default_registers)
end
# Renders a layout file
#--------------------------------------------------------------
def render_layout(rendered_template, layout, assigns = {})
render_template(layout, assigns.merge({'yield' => rendered_template}))
end
# Sets liquid up to use the virtual filesystem for templates
#--------------------------------------------------------------
def set_virtual_file_system(path)
::Liquid::Template.file_system = LiquidHelpers::VirtualFileSystem.new(path, (user_session.skin rescue nil))
end
# Returns the template content for a given template path
#--------------------------------------------------------------
def get_template_content(path_or_content, options = {})
if options[:content_given]
return path_or_content
end
begin
user_session.skin.template_content(path_or_content, options) || raise(VirtualFileSystem::FileSystemError, path_or_content)
rescue
"<strong>The template '#{path_or_content}' could not be loaded correctly.</strong>"
end
end
def default_registers
registers = {}
registers[:controller] = self if self.class < ActionController::Base
registers
end
end
end
# This class is a replacement for the normal liquid file system
# which loads files from disk.
# This FileSystem gets its content from either cache or database.
module LiquidHelpers
class VirtualFileSystem
class FileSystemError < StandardError
end
attr_reader :root, :skin
def initialize(root, skin)
@root = root
@skin = skin
end
# Renders a liquid partial
#--------------------------------------------------------------
def read_template_file(template_path)
get_template_content(template_path)
end
private
# Returns the template content for a given template path
# Also validates the path
#--------------------------------------------------------------
def get_template_content(path)
raise FileSystemError, "Illegal template name '#{path}'" unless path =~ /^[^.\/][a-zA-Z0-9_\/]+$/
content = skin.template_content(path, :partial => true)
unless content
raise FileSystemError, "Include not found: '#{path}'"
end
content
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment