Module: Roda::RodaPlugins::Render

Defined in:
lib/roda/plugins/render.rb

Overview

The render plugin adds support for template rendering using the tilt library. Two methods are provided for template rendering, view (which uses the layout) and render (which does not).

plugin :render

route do |r|
  r.is 'foo' do
    view('foo') # renders views/foo.erb inside views/layout.erb
  end

  r.is 'bar' do
    render('bar') # renders views/bar.erb
  end
end

The render and view methods just return strings, they do not have side effects (unless the templates themselves have side effects). As Roda uses the routing block return value as the body of the response, in most cases you will call these methods as the last expression in a routing block to have the response body be the result of the template rendering.

Because render and view just return strings, you can call them inside templates (i.e. for subtemplates/partials), or multiple times in the same route and combine the results together:

route do |r|
  r.is 'foo-bars' do
    @bars = Bar.where(:foo).map{|b| render(:bar, locals: {bar: b})}.join
    view('foo')
  end
end

You can provide options to the plugin method:

plugin :render, engine: 'haml', views: 'admin_views'

Plugin Options

The following plugin options are supported:

:allowed_paths

Set the template paths to allow. Attempts to render paths outside of these paths will raise an error. Defaults to the :views directory.

:cache

nil/false to explicitly disable permanent template caching. By default, permanent template caching is disabled by default if RACK_ENV is development. When permanent template caching is disabled, for templates with paths in the file system, the modification time of the file will be checked on every render, and if it has changed, a new template will be created for the current content of the file.

:cache_class

A class to use as the template cache instead of the default.

:check_paths

Can be set to false to turn off template path checking.

:engine

The tilt engine to use for rendering, also the default file extension for templates, defaults to ‘erb’.

:escape

Use Erubi as the ERB template engine, and enable escaping by default, which makes <%= %> escape output and <%== %> not escape output. If given, sets the escape: true option for all template engines, which can break some non-ERB template engines. You can use a string or array of strings as the value for this option to only set the escape: true option for those specific template engines.

:layout

The base name of the layout file, defaults to ‘layout’. This can be provided as a hash with the :template or :inline options.

:layout_opts

The options to use when rendering the layout, if different from the default options.

:template_opts

The tilt options used when rendering all templates. defaults to: {outvar: '@_out_buf', default_encoding: Encoding.default_external}.

:engine_opts

The tilt options to use per template engine. Keys are engine strings, values are hashes of template options.

:views

The directory holding the view files, defaults to the ‘views’ subdirectory of the application’s :root option (the process’s working directory by default).

Render/View Method Options

Most of these options can be overridden at runtime by passing options to the view or render methods:

view('foo', engine: 'html.erb')
render('foo', views: 'admin_views')

There are additional options to view and render that are available at runtime:

:cache

Set to false to not cache this template, even when caching is on by default. Set to true to force caching for this template, even when the default is to not permantently cache (e.g. when using the :template_block option).

:cache_key

Explicitly set the hash key to use when caching.

:content

Only respected by view, provides the content to render inside the layout, instead of rendering a template to get the content.

:inline

Use the value given as the template code, instead of looking for template code in a file.

:locals

Hash of local variables to make available inside the template.

:path

Use the value given as the full pathname for the file, instead of using the :views and :engine option in combination with the template name.

:scope

The object in which context to evaluate the template. By default, this is the Roda instance.

:template

Provides the name of the template to use. This allows you pass a single options hash to the render/view method, while still allowing you to specify the template name.

:template_block

Pass this block when creating the underlying template, ignored when using :inline. Disables caching of the template by default.

:template_class

Provides the template class to use, instead of using Tilt or Tilt[:engine].

Here’s an example of using these options:

view(inline: '<%= @foo %>')
render(path: '/path/to/template.erb')

If you pass a hash as the first argument to view or render, it should have either :template, :inline, :path, or :content (for view) as one of the keys.

Speeding Up Template Rendering

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. Next best optimized are template calls with a single :locals option. Use of other options disables the compiled template method optimizations and can be significantly slower.

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.

Accepting Template Blocks in Methods

If you are used to Rails, you may be surprised that this type of template code doesn’t work in Roda:

<%= some_method do %>
  Some HTML
<% end %>

The reason this doesn’t work is that this is not valid ERB syntax, it is Rails syntax, and requires attempting to parse the some_method do Ruby code with a regular expression. Since Roda uses ERB syntax, it does not support this.

In general, these methods are used to wrap the content of the block and inject the content into the output. To get similar behavior with Roda, you have a few different options you can use.

Use Erubi::CaptureBlockEngine

Roda defaults to using Erubi for erb template rendering. Erubi 1.13.0+ includes support for an erb variant that supports blocks in <%= and <%== tags. To use it:

require 'erubi/capture_block'
plugin :render, template_opts: {engine_class: Erubi::CaptureBlockEngine}

See the Erubi documentation for how to capture data inside the block. Make sure the method call (some_method in the example) returns the output you want added to the rendered body.

Directly Inject Template Output

You can switch from a <%= tag to using a <% tag:

<% some_method do %>
  Some HTML
<% end %>

While this would output Some HTML into the template, it would not be able to inject content before or after the block. However, you can use the inject_erb_plugin to handle the injection:

def some_method
  inject_erb "content before block"
  yield
  inject_erb "content after block"
end

If you need to modify the captured block before injecting it, you can use the capture_erb plugin to capture content from the template block, and modify that content, then use inject_erb to inject it into the template output:

def some_method(&block)
  inject_erb "content before block"
  inject_erb capture_erb(&block).upcase
  inject_erb "content after block"
end

This is the recommended approach for handling this type of method, if you want to keep the template block in the same template.

Separate Block Output Into Separate Template

By moving the Some HTML into a separate template, you can render that template inside the block:

<%= some_method{render('template_name')} %>

It’s also possible to use an inline template:

<%= some_method do render(:inline=><<-END)
  Some HTML
  END
end %>

This approach is useful if it makes sense to separate the template block into its own template. You lose the ability to use local variable from outside the template block inside the template block with this approach.

Separate Header and Footer

You can define two separate methods, one that outputs the content before the block, and one that outputs the content after the block, and use those instead of a single call:

<%= some_method_before %>
  Some HTML
<%= some_method_after %>

This is the simplest option to setup, but it is fairly tedious to use.

Defined Under Namespace

Modules: ClassMethods, InstanceMethods Classes: TemplateMtimeWrapper

Constant Summary collapse

NO_CACHE =
{:cache=>false}.freeze
COMPILED_METHOD_SUPPORT =
RUBY_VERSION >= '2.3' && tilt_compiled_method_support && ENV['RODA_RENDER_COMPILED_METHOD_SUPPORT'] != 'no'

Class Method Summary collapse

Class Method Details

.configure(app, opts = OPTS) ⇒ Object

Setup default rendering options. See Render for details.



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
# File 'lib/roda/plugins/render.rb', line 245

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

  opts = app.opts[:render]
  opts[:engine] = (opts[:engine] || "erb").dup.freeze
  opts[:views] = app.expand_path(opts[:views]||"views").freeze
  opts[:allowed_paths] ||= [opts[:views]].freeze
  opts[:allowed_paths] = opts[:allowed_paths].map{|f| app.expand_path(f, nil)}.uniq.freeze
  opts[:check_paths] = true unless opts.has_key?(:check_paths)

  unless opts.has_key?(:check_template_mtime)
    opts[:check_template_mtime] = if opts[:cache] == false || opts[:explicit_cache]
      true
    else
      ENV['RACK_ENV'] == 'development'
    end
  end

  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
  opts[:template_method_cache][:_roda_layout] = nil if opts[:template_method_cache][:_roda_layout]
  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]
    opts[:layout_opts][:views] = app.expand_path(opts[:layout_opts][:views]).freeze
  end

  if layout = opts.fetch(:layout, true)
    opts[:layout] = true

    case layout
    when Hash
      opts[:layout_opts].merge!(layout)
    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'
  unless template_opts.has_key?(:default_encoding)
    template_opts[:default_encoding] = Encoding.default_external
  end

  engine_opts = opts[:engine_opts] = (opts[:engine_opts] || {}).dup
  engine_opts.to_a.each do |k,v|
    engine_opts[k] = v.dup.freeze
  end

  if escape = opts[:escape]
    require 'tilt/erubi'

    case escape
    when String, Array
      Array(escape).each do |engine|
        engine_opts[engine] = (engine_opts[engine] || {}).merge(:escape => true).freeze
      end
    else
      template_opts[:escape] = true
    end
  end

  template_opts.freeze
  engine_opts.freeze
  opts.freeze
end

.tilt_template_compiled_method(template, locals_keys, scope_class) ⇒ Object

:nocov:



238
239
240
# File 'lib/roda/plugins/render.rb', line 238

def self.tilt_template_compiled_method(template, locals_keys, scope_class)
  template.send(:compiled_method, locals_keys, scope_class)
end