matsko
matsko

Reputation: 22183

Rails static html template files in the asset pipeline and caching in development mode

I'm building a website using AngularJS and Rails. The HTML files that I'm using for templates are stored under /app/assets/templates and each time I update a route or change something inside of a nested partial inside of a template I need to "touch" the highest level file in the /app/assets/templates directory for the html file I'm changing.

So if I have a page "edit.html" which loads a partial "_form.html" then whenever I update a route or change something in _form.html I need to make sure that edit.html is touched.

This is annoying and very finicky. Is there any way to inform the asset pipeline/sprockets to avoid caching for the app/assets/templates directory?

Upvotes: 10

Views: 9831

Answers (6)

nicolas
nicolas

Reputation: 920

Cleanest solution is to precompile your html assets using ejs and serve them as other javascript files.

  • No http query
  • minification
  • Possibility to pass js data to you templates to make them dynamic (ejs is a port from underscore template and basically behave like erb for javascript)

It basically work like that :

    #in you gemfile
    gem 'ejs'

    #in app/assets/javascripts/templates/my_template.jst.ejs
    <p>my name is <%= name %> !<p>

    #in your application.coffee
    #= require_tree ./templates

    JST['templates/my_template'](name: 'itkin')
    => '<p>my name is itkin !<p>'

Upvotes: 0

superluminary
superluminary

Reputation: 49162

To my mind, several things are needed here:

  1. The templates should be namespaced, so people templates go in the app/views/people/templates directory
  2. The templates are entirely static, so no before filters should be called.
  3. The templates should be cached, making them very fast.

Here's a possible solution using a Rails concern:

# Allows static content to be served from the templates
# directory of a controller
module HasTemplates

  extend ActiveSupport::Concern

  included do
    # Prepend the filter
    prepend_before_filter :template_filter, only: [:templates]
    # Let's cache the action
    caches_action :templates, :cache_path => Proc.new {|c| c.request.url }
  end

  # required to prevent route from baulking
  def templates;end

  # Catch all template requests and handle before any filters
  def template_filter
    render "/#{params[:controller]}/templates/#{params[:template]}", layout: 'blank'
    rescue ActionView::MissingTemplate
      not_found layout: 'blank'
    false
  end
end

Notice we are returning the template in a prepended filter. This allows us to return the static content without hitting any other filters.

You can then create a route, something like this:

resources :people do
  collection do
    get 'templates/:template' => 'people#templates', as: :templates
  end
end

Your controller becomes simply:

class PeopleController < ApplicationController
  include HasTemplates
end

Now any file placed in the /app/views/people/templates can be served at speed from a url.

Upvotes: 3

zaur
zaur

Reputation: 46

You can try gem js_assets (https://github.com/kavkaz/js_assets).

This allows you to function asset_path in javascript code that emulates a similar method of sprockets.

Upvotes: 1

dzuc
dzuc

Reputation: 760

Expanding on RandallB's answer a bit; this is mentioned explicitly in the documentation on the Asset Pipeline: http://guides.rubyonrails.org/asset_pipeline.html

Note that you have to append the extension .erb to your .coffee file to have this work. (e.g., application.js.coffee.erb)

Upvotes: 1

matsko
matsko

Reputation: 22183

The best solution I've found to this is not to use the asset pipeline for HTML template files.

Instead make a controller called TemplatesController and create only one action. Then map all template URLs to that using a route such as:

get /templates/:path.html => 'templates#page', :constraints => { :path => /.+/  }

Then move all the template files into app/views/templates

Then inside the controller, setup the following:

caches_page :page

def page
  @path = params[:path]
  render :template => 'templates/' + @path, :layout => nil
end

This way all of your template files will be served from the controller and then will be cached into public/templates. To avoid cache problems, you can create a timestamp path into the template route so that your cached files are delivered with a version:

get '/templates/:timestamp/:path.html' => 'templates#page', :constraints => { :path => /.+/ }

This way you can have a new timestamp each time you upload the website and you can store the templates folder anywhere you like. You can even store the templates folder on S3 and have an assets URL for that. Then wherever your template files are addressed, you can use a custom asset method:

templateUrl : <%= custom_asset_template_url('some/file.html') %>

Where:

def custom_asset_template_url(path)
  "http://custom-asset-server.website.com/templates/#{$some_global_timestamp}/#{path}"
end

Then just make the asset redirect to the Rails server if it's not found and it will be generated. Or all template files can be pre-generated once uploaded.

Upvotes: 21

RandallB
RandallB

Reputation: 5565

There's a much (much!) better way to deal with this.

<%= path_to_asset("template_name.html") %>

That will return a fully working file from the asset pipeline, which can use ERB, etc. It's undocumented, but it's a part of sprockets / the asset pipeline.

Upvotes: 6

Related Questions