3scale/porta

View on GitHub
app/lib/liquid/template_support.rb

Summary

Maintainability
A
2 hrs
Test Coverage
# frozen_string_literal: true

module Liquid
  module TemplateSupport
    extend ActiveSupport::Concern

    module ClassMethods
      # Usage:
      #   MyController < FrontendController
      #    liquify prefix: 'applications'
      #
      #    def show
      #     # will try to render liquid template
      #     # with system name 'applications/show'
      #    end
      #  end
      def liquify(options = {})
        layout :find_builtin_static_page_layout

        before_action(options) do
          add_liquid_view_paths(options)
        end

        after_action(options) do
          liquid_database_resolver.clear_cache
        end
      end
    end

    protected

    def add_liquid_view_paths(options = {})
      # liquid from the database
      prepend_view_path(liquid_database_resolver)

      # ERBs
      prepend_view_path(rails_without_liquid_resolver)

      # liquid from filesystem
      append_view_path(liquid_filesystem_resolver)

      append_view_path(liquid_filesystem_resolver_no_prefix)

      # TODO: remove this hack and move templates to right place
      if prefix = options[:prefix]
        define_singleton_method(:_prefixes) do
          super().unshift(prefix)
        end
      end

      ActionView::Renderer.class_eval do
        # RAILS: this overrides a Rails method. Beware when upgrading.
        # We overwrite ActionView methods to make Liquid work. Due to changes in ActionView, we cannot depend on
        # overwriting instances of ActionView::Renderer anymore, as those can be reinitialized during a page render.
        # Because of that, we need to overwrite this method in class level, which may cause odd behaviours without
        # the respond_to?(:liquify) check.
        # Maybe move this to an initializer in case we can't find a better solution?
        def render_template_to_object(context, options)
          if context.controller.class.respond_to?(:liquify)
            LiquidTemplateRenderer.new(@lookup_context).render(context, options)
          else
            ActionView::TemplateRenderer.new(@lookup_context).render(context, options)
          end
        end
      end
    end

    class LiquidTemplateRenderer < ::ActionView::TemplateRenderer
      def determine_template(options)
        super.tap do |template|
          unless template.respond_to?(:layout)
            Rails.logger.info "#{template.inspect} is not Liquid template and can't override layout"
            next
          end

          if overridden = template.layout
            original = options.delete(:layout)
            options[:layout] = overridden
            Rails.logger.info "Rendering #{template.inspect} with #{overridden} instead of #{original}"
          end
        end
      end
    end

    def assigns_for_liquify
      @_assigned_drops ||= {}
      @_template_assigns ||= {}

      report_and_supress_exceptions do
        overriden = @_template_assigns.keys & @_assigned_drops.keys

        raise "Assigning #{overriden.to_sentence} would override variables in template." if overriden.present?
      end
      @_assigned_drops
    end

    public :assigns_for_liquify

    def self.fetch_drop(name)
      Liquid::Drops.const_get(name.camelize, false)
    end

    private

    def rails_without_liquid_resolver
      Liquid::Template::WithoutLiquidResolver.new
    end

    def liquid_database_resolver
      Liquid::Template::Resolver.instance(site_account).tap do |resolver|
        # assign controller to resolver, so it can get liquid variables
        resolver.cms = cms
      end
    end

    def liquid_filesystem_resolver
      Liquid::Template::FallbackResolver.new
    end

    def liquid_filesystem_resolver_no_prefix
      Liquid::Template::FallbackResolverNoPrefix.new
    end

    def current_liquid_templates
      site_account.templates
    end

    def prepare_liquid_template(template)
      cms_toolbar.liquid(template)

      template.registers[:controller] ||= self
      template.registers[:request] ||= request
      template.registers[:current_account] ||= current_account
      template.registers[:site_account] ||= site_account

      template.registers[:draft] ||= cms.render_draft_content?
      template.registers[:escape_html] ||= cms.escape_html?

      template.registers[:content_for] ||= cms.content_for_store

      template.registers[:file_system] ||= CMS::DatabaseFileSystem.new(site_account, lookup_context)

      template_assigns template,
        :site    => Liquid::Drops::Site.new(site_account),
        :request => Liquid::Drops::Request.new(request),
        :urls    => Liquid::Drops::Urls.new(site_account, request),
        :site_account => Liquid::Drops::Provider.new(site_account),
        :provider     => Liquid::Drops::Provider.new(site_account),
        :forum        => Liquid::Drops::Forum.new(site_account),
        :today        => Liquid::Drops::Today.new,
        :i18n         => Liquid::Drops::I18n.new,
        content_of:      Liquid::Drops::ContentOf.new,
        flash:           Liquid::Drops::Flash.new(request.flash.to_a),
        :model => Liquid::Drops::NewSignup.new(site_account, request.params, current_account),

        # underscore prefix means it is "private" api and can be changed without further notice
        :_menu => Liquid::Drops::Menu.new(active_menus),
        :menu => Liquid::Drops::Menu.new(active_menus)
      if logged_in?
        template_assigns template,
          :current_account => Liquid::Drops::Account.new(current_account),
          :current_user => Liquid::Drops::CurrentUser.new(current_user)
      end
    end

    # WARNING! all keys to assigns have to be strings, not symbols!
    # Otherwise the Liquid render method won't find them when
    # evaluating the template.
    #
    def template_assigns(template, assigns)
      @_template_assigns = template.assigns
      @_template_assigns.merge! assigns.stringify_keys
    end
  end
end