Module: Strelka::App::Templating

Extended by:
Loggability, Plugin
Includes:
Constants
Defined in:
lib/strelka/app/templating.rb

Overview

A templated content-generation plugin for Strelka::Apps. It uses the Inversion templating system.

It adds:

  • a preloaded/cached template table
  • a mechanism for fetching templates from the table
  • a global layout template which is automatically wrapped around responses

Usage

To use it, just load the :templating plugin in your app:

plugins :templating

and declare one or more templates that your application will use:

templates :console =>   'views/console.tmpl',
        :proctable => 'partials/proctable.tmpl'

Then, inside your app, you can fetch a copy of one or more of the templates and return it as the reponse:

def handle_request( req )
  super do
      res = request.response

      proctable = template :proctable
      proctable.processes = ProcessList.fetch

      tmpl = template :console
      tmpl.message = "Everything's up."
      tmpl.proctable = proctable
      res.body = tmpl

      return res
  end
end

You can also just return the template if you don't need to do anything else to the response.

When returning a template, either in the body of the response or directly, it will automatically set a few attributes for commonly-used objects:

request

The current Strelka::HTTPRequest

app

The application object (Strelka::App instance).

strelka_version

Strelka.version_string( true )

mongrel2_version

Mongrel2.version_string( true )

ruby_version

The RUBY_VERSION of the running interpreter.

route

If the :routing plugin is loaded, this will be set to the 'routing_info' of the chosen route. See Strelka::Router#add_route for details.

If your app will only be loading and returning a template without doing anything with it, you can return just its name:

def handle_request( req )
  super { :console }
end

It will be loaded, set as the response body, and the above common objects added to it.

:TODO: Explain how returning things other than responses doesn't work well with :filters and maybe other plugins that run inside :templating.

Layouts

Very often, you'll want all or most of the views in your app to share a common page layout. To accomplish this, you can declare a layout template:

layout 'layout.tmpl'

Any template that you return will be set as the 'body' attribute of this layout template (which you'd place into the layout with ) and the layout rendered as the body of the response.

Note that if you want any of the "common objects" from above with a layout template, they'll be set on it since it's the top-level template, but you can still access them using the directive:

Template Locations

Inversion looks for templates in a load path much like Ruby does for libraries that you 'require'. It contains just the current working directory by default. You can add your own template directories via the config file (under template_paths in the templates section), or programmatically from your application, but very often you'll want to distribute templates with the application gem.

The plugin supports this by looking for a templates/ directory under your gem's data directory. If it finds such a directory for any loaded gem that has a Strelka dependency, it appends it to Inversion's template_paths. This also works for plugins, should you write your own, and want to provide some default templates. See the 'laika-fancyerrors' gem for an example of this.

Defined Under Namespace

Modules: ClassMethods

Instance Attribute Summary collapse

Attributes included from Plugin

#pluggable, #successors

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Plugin

extended, plugin_name, run_inside, run_outside

Instance Attribute Details

#layoutObject

The layout template (an Inversion::Template), if one was declared



208
209
210
# File 'lib/strelka/app/templating.rb', line 208

def layout
  @layout
end

#template_mapObject (readonly)

The map of template names to Inversion::Template instances.



205
206
207
# File 'lib/strelka/app/templating.rb', line 205

def template_map
  @template_map
end

Class Method Details

.discover_template_dirsObject

Return an Array of Pathnames to all directories named 'templates' under the data dirctories of loaded gems which have a dependency on Strelka.



128
129
130
131
132
133
134
135
136
137
138
# File 'lib/strelka/app/templating.rb', line 128

def self::discover_template_dirs
  directories = Strelka::Discovery.discover_data_dirs.values.flatten

  self.log.debug "Discovered data directories: %p" % [ directories ]

  return directories.inject( [] ) do |array, dir|
    pattern = File.join( dir, 'templates' )
    self.log.debug "  adding: %s" % [ pattern ]
    array += Pathname.glob( pattern )
  end
end

.included(mod) ⇒ Object

Inclusion callback -- add the plugin's templates directory right before activation so loading the config doesn't clobber it.



143
144
145
146
147
148
149
150
151
# File 'lib/strelka/app/templating.rb', line 143

def self::included( mod )

  # Add the plugin's template directory to Inversion's template path
  dirs = self.discover_template_dirs
  self.log.info "Discovered template directories: %p" % [ dirs ]
  Inversion::Template.template_paths.concat( dirs )

  super
end

Instance Method Details

#extract_template_from_response(response) ⇒ Object

Fetch the template from the response (if there is one) and return it. If response itself is a template.



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/strelka/app/templating.rb', line 268

def extract_template_from_response( response )

  # Response is a template name
  if response.is_a?( Symbol ) && self.template_map.key?( response )
    self.log.debug "  response is a template name (Symbol); using the %p template" % [ response ]
    return self.template( response )

  # Template object
  elsif response.respond_to?( :render )
    self.log.debug "  response is a #renderable %p; returning it as-is" % [ response.class ]
    return response

  # Template object already in a Response
  elsif response.is_a?( Mongrel2::Response ) && response.body.respond_to?( :render )
    self.log.debug "  response is a %p in the body of a %p" % [ response.body.class, response.class ]
    return response.body

  # Not templated; returned as-is
  else
    self.log.debug "  response isn't templated; returning nil"
    # :TODO: Return the response instead of nil
    return nil
  end
end

#handle_request(request, &block) ⇒ Object

Intercept responses on the way back out and turn them into a Mongrel2::HTTPResponse with a String for its entity body.



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/strelka/app/templating.rb', line 243

def handle_request( request, &block )
  response = super

  self.log.debug "Templating: examining %p response." % [ response.class ]
  template = self.extract_template_from_response( response ) or
    return response

  # Wrap the template in a layout if there is one
  template = self.wrap_in_layout( template, request )

  # Set some default stuff on the top-level template
  self.set_common_attributes( template, request )

  # Now render the response body
  self.log.debug "  rendering the template into the response body"
  response = request.response unless response.is_a?( Mongrel2::Response )
  response.body = template.render
  response.status ||= HTTP::OK

  return response
end

#initializeObject

Preload any templates registered with the template map.



193
194
195
196
197
# File 'lib/strelka/app/templating.rb', line 193

def initialize( * )
  super
  @template_map = self.load_template_map
  @layout = self.load_layout_template
end

#load_layout_templateObject

Load an Inversion::Template for the layout template and return it if one was declared. If none was declared, returns nil.



234
235
236
237
238
# File 'lib/strelka/app/templating.rb', line 234

def load_layout_template
  return nil unless ( lt_path = self.class.layout_template )
  enc = Encoding.default_internal || Encoding::UTF_8
  return Inversion::Template.load( lt_path, encoding: enc )
end

#load_template_mapObject

Load instances for all the template paths specified in the App's class and return them in a hash keyed by name (Symbol).



223
224
225
226
227
228
229
# File 'lib/strelka/app/templating.rb', line 223

def load_template_map
  return self.class.template_map.inject( {} ) do |map, (name, path)|
    enc = Encoding.default_internal || Encoding::UTF_8
    map[ name ] = Inversion::Template.load( path, encoding: enc )
    map
  end
end

#set_common_attributes(template, request) ⇒ Object

Set some default values from the request in the given top-level template.



309
310
311
312
313
314
315
316
# File 'lib/strelka/app/templating.rb', line 309

def set_common_attributes( template, request )
  template.request          = request
  template.app              = self
  template.strelka_version  = Strelka.version_string( true )
  template.mongrel2_version = Mongrel2.version_string( true )
  template.ruby_version     = RUBY_VERSION
  template.route            = request.notes[:routing][:route]
end

#template(name) ⇒ Object

Return the template keyed by the given name. :TODO: Add auto-reloading,



213
214
215
216
217
218
# File 'lib/strelka/app/templating.rb', line 213

def template( name )
  template = self.template_map[ name ] or
    raise ArgumentError, "no %p template registered!" % [ name ]
  template.reload if template.changed?
  return template.dup
end

#wrap_in_layout(content, request) ⇒ Object

Wrap the specified content template in the layout template and return it. If there isn't a layout declared, just return content as-is.



296
297
298
299
300
301
302
303
304
305
# File 'lib/strelka/app/templating.rb', line 296

def wrap_in_layout( content, request )
  return content unless self.layout

  self.layout.reload if self.layout.changed?
  l_template = self.layout.dup
  self.log.debug "  wrapping response in layout %p" % [ l_template ]
  l_template.body = content

  return l_template
end