lib/roda/plugins/render.rb in roda-3.21.0 vs lib/roda/plugins/render.rb in roda-3.22.0

- old
+ new

@@ -118,33 +118,31 @@ # have either +:template+, +:inline+, +:path+, or +:content+ (for +view+) as # one of the keys. # # = Speeding Up Template Rendering # - # By default, determining the cache key to use for the template can be a lot - # of work. If you specify the +:cache_key+ option, you can save Roda from - # having to do that work, which will make your application faster. However, - # if you do this, you need to make sure you choose a correct key. + # The render/view method calls are optimized for usage with a single symbol/string + # argument specifying the template name. So for fastest rendering, pass only a + # symbol/string to render/view. # - # If your application uses a unique template per path, in that the same - # path never uses more than one template, you can use the +view_options+ plugin - # and do: - # - # set_view_options cache_key: r.path_info - # - # at the top of your route block. You can even do this if you do have paths - # that use more than one template, as long as you specify +:cache_key+ - # specifically when rendering in those paths. - # - # If you use a single layout in your application, you can also make layout - # rendering faster by specifying +:cache_key+ inside the +:layout_opts+ - # plugin option. + # If you must pass a hash to render/view, either as a second argument or as the + # only argument, you can speed things up by specifying a +:cache_key+ option in + # the hash, making sure the +:cache_key+ is unique to the template you are + # rendering. module Render + # Support for using compiled methods directly requires Ruby 2.3 for the + # method binding to work, and Tilt 1.2 for Tilt::Template#compiled_method. + COMPILED_METHOD_SUPPORT = RUBY_VERSION >= '2.3' && + defined?(Tilt::VERSION) && + Tilt::VERSION >= '1.2' && + (Tilt::Template.instance_method(:compiled_method).arity rescue false) == 1 + # Setup default rendering options. See Render for details. def self.configure(app, opts=OPTS) if app.opts[:render] orig_cache = app.opts[:render][:cache] + orig_method_cache = app.opts[:render][:template_method_cache] opts = app.opts[:render][:orig_opts].merge(opts) end app.opts[:render] = opts.dup app.opts[:render][:orig_opts] = opts @@ -161,10 +159,24 @@ else ENV['RACK_ENV'] == 'development' end end + if opts[:check_template_mtime] + opts.delete(:template_method_cache) + elsif COMPILED_METHOD_SUPPORT + opts[:template_method_cache] = orig_method_cache || (opts[:cache_class] || RodaCache).new + begin + app.const_get(:RodaCompiledTemplates, false) + rescue NameError + compiled_templates_module = Module.new + app.send(:include, compiled_templates_module) + app.const_set(:RodaCompiledTemplates, compiled_templates_module) + end + opts[:template_method_cache] = orig_method_cache || (opts[:cache_class] || RodaCache).new + end + opts[:cache] = orig_cache || (opts[:cache_class] || RodaCache).new opts[:layout_opts] = (opts[:layout_opts] || {}).dup opts[:layout_opts][:_is_layout] = true if opts[:layout_opts][:views] @@ -180,10 +192,12 @@ when true opts[:layout_opts][:template] ||= 'layout' else opts[:layout_opts][:template] = layout end + + opts[:optimize_layout] = (opts[:layout_opts][:template] if opts[:layout_opts].keys.sort == [:_is_layout, :template]) end opts[:layout_opts].freeze template_opts = opts[:template_opts] = (opts[:template_opts] || {}).dup template_opts[:outvar] ||= '@_out_buf' @@ -247,10 +261,13 @@ # them as necessary to prevent changes in the subclass # affecting the parent class. def inherited(subclass) super opts = subclass.opts[:render] = subclass.opts[:render].dup + if COMPILED_METHOD_SUPPORT + opts[:template_method_cache] = (opts[:cache_class] || RodaCache).new + end opts[:cache] = opts[:cache].dup opts.freeze end # Return the render options for this class. @@ -259,36 +276,92 @@ end end module InstanceMethods # Render the given template. See Render for details. - def render(template, opts = OPTS, &block) - opts = render_template_opts(template, opts) - retrieve_template(opts).render((opts[:scope]||self), (opts[:locals]||OPTS), &block) + def render(template, opts = (optimized_template = _cached_template_method(template); OPTS), &block) + if optimized_template + send(optimized_template, OPTS, &block) + else + opts = render_template_opts(template, opts) + retrieve_template(opts).render((opts[:scope]||self), (opts[:locals]||OPTS), &block) + end end # Return the render options for the instance's class. def render_opts self.class.render_opts end # Render the given template. If there is a default layout # for the class, take the result of the template rendering # and render it inside the layout. See Render for details. - def view(template, opts=OPTS) - opts = parse_template_opts(template, opts) - content = opts[:content] || render_template(opts) + def view(template, opts = (optimized_template = _cached_template_method(template); OPTS)) + if optimized_template + content = send(optimized_template, OPTS) + render_opts = self.class.opts[:render] + if layout_template = render_opts[:optimize_layout] + method_cache = render_opts[:template_method_cache] + unless layout_method = method_cache[:_roda_layout] + retrieve_template(:template=>layout_template, :cache_key=>nil, :template_method_cache_key => :_roda_layout) + layout_method = method_cache[:_roda_layout] + end + + if layout_method + return send(layout_method, OPTS){content} + end + end + else + opts = parse_template_opts(template, opts) + content = opts[:content] || render_template(opts) + end + if layout_opts = view_layout_opts(opts) content = render_template(layout_opts){content} end content end private + if COMPILED_METHOD_SUPPORT + # If there is an instance method for the template, return the instance + # method symbol. This optimization is only used for render/view calls + # with a single string or symbol argument. + def _cached_template_method(template) + case template + when String, Symbol + if (method_cache = render_opts[:template_method_cache]) + _cached_template_method_lookup(method_cache, template) + end + end + end + + # The key to use in the template method cache for the given template. + def _cached_template_method_key(template) + template + end + + # Return the instance method symbol for the template in the method cache. + def _cached_template_method_lookup(method_cache, template) + method_cache[template] + end + else + # :nocov: + def _cached_template_method(template) + nil + end + + def _cached_template_method_key(template) + nil + end + # :nocov: + end + + # Convert template options to single hash when rendering templates using render. def render_template_opts(template, opts) parse_template_opts(template, opts) end @@ -311,11 +384,11 @@ end # Given the template name and options, set the template class, template path/content, # template block, and locals to use for the render in the passed options. def find_template(opts) - render_opts = render_opts() + render_opts = self.class.opts[:render] engine_override = opts[:engine] engine = opts[:engine] ||= render_opts[:engine] if content = opts[:inline] path = opts[:path] = content template_class = opts[:template_class] ||= ::Tilt[engine] @@ -330,17 +403,19 @@ if (cache = opts[:cache]).nil? cache = content || !opts[:template_block] end if cache - template_block = opts[:template_block] unless content - template_opts = opts[:template_opts] + unless opts.has_key?(:cache_key) + template_block = opts[:template_block] unless content + template_opts = opts[:template_opts] - opts[:cache_key] ||= if template_class || engine_override || template_opts || template_block - [path, template_class, engine_override, template_opts, template_block] - else - path + opts[:cache_key] = if template_class || engine_override || template_opts || template_block + [path, template_class, engine_override, template_opts, template_block] + else + path + end end else opts.delete(:cache_key) end @@ -351,10 +426,13 @@ def parse_template_opts(template, opts) opts = Hash[opts] if template.is_a?(Hash) opts.merge!(template) else + if opts.empty? && (key = _cached_template_method_key(template)) + opts[:template_method_cache_key] = key + end opts[:template] = template opts end end @@ -370,10 +448,11 @@ if !opts[:cache_key] || cache == false found_template_opts = opts = find_template(opts) end cached_template(opts) do opts = found_template_opts || find_template(opts) + render_opts = self.class.opts[:render] template_opts = render_opts[:template_opts] if engine_opts = render_opts[:engine_opts][opts[:engine]] template_opts = template_opts.merge(engine_opts) end if current_template_opts = opts[:template_opts] @@ -381,10 +460,31 @@ end if render_opts[:check_template_mtime] && !opts[:template_block] && !cache TemplateMtimeWrapper.new(opts[:template_class], opts[:path], 1, template_opts) else - opts[:template_class].new(opts[:path], 1, template_opts, &opts[:template_block]) + template = opts[:template_class].new(opts[:path], 1, template_opts, &opts[:template_block]) + + if COMPILED_METHOD_SUPPORT && + (method_cache_key = opts[:template_method_cache_key]) && + (method_cache = render_opts[:template_method_cache]) && + (method_cache[method_cache_key] != false) && + !opts[:inline] && + cache != false + + begin + unbound_method = template.send(:compiled_method, OPTS) + rescue ::NotImplementedError + method_cache[method_cache_key] = false + else + method_name = :"_roda_template_#{self.class.object_id}_#{method_cache_key}" + self.class::RodaCompiledTemplates.send(:define_method, method_name, unbound_method) + self.class::RodaCompiledTemplates.send(:private, method_name) + method_cache[method_cache_key] = method_name + end + end + + template end end end # The name to use for the template. By default, just converts the :template option to a string.