-
-
Save snusnu/424658 to your computer and use it in GitHub Desktop.
require 'action_view' | |
require 'active_support' | |
require 'mustache' | |
class Mustache | |
# TODO - Think about allowing to overwrite layout methods in subclassing views | |
# | |
# http://github.com/defunkt/mustache/blob/master/lib/mustache/sinatra.rb#L79-82 | |
# http://github.com/defunkt/mustache/blob/master/lib/mustache/sinatra.rb#L96-102 | |
# Remember to use {{{yield}}} (3 mustaches) as this shouldn't be escaped. | |
# Using {{{tag}}} will skip escaping HTML so if your mustache methods return | |
# HTML, be sure to interpolate them using 3 mustaches. | |
# Subclass Mustache::Rails for your view files. You should place view files in | |
# app/views/:controller/:action.rb. Mustache::Rails registers a TemplateHandler | |
# for ".rb" files. Templates go to app/templates/:controller/:action.format.mustache | |
class Rails < Mustache | |
attr_accessor :view | |
def method_missing(method, *args, &block) | |
view.send(method, *args, &block) | |
end | |
def respond_to?(method, include_private=false) | |
super(method, include_private) || view.respond_to?(method, include_private) | |
end | |
# Redefine where Mustache::Rails templates locate their partials: | |
# | |
# (1) in the same directory as the current template file. | |
# (2) in the shared templates path (can be configured via Config.shared_path=(value)) | |
# | |
def partial(name) | |
partial_name = "#{name}.#{Config.template_extension}" | |
template_dir = Pathname.new(self.class.template_file).dirname | |
partial_path = File.expand_path("#{template_dir}/#{partial_name}") | |
unless File.file?(partial_path) | |
partial_path = "#{Config.shared_path}/#{partial_name}" | |
end | |
File.read(partial_path) | |
end | |
# You can change these defaults in, say, a Rails initializer or | |
# environment.rb, e.g.: | |
# | |
# Mustache::Rails::Config.template_base_path = Rails.root.join('app', 'templates') | |
module Config | |
def self.template_base_path | |
@template_base_path ||= ::Rails.root.join('app', 'templates') | |
end | |
def self.template_base_path=(value) | |
@template_base_path = value | |
end | |
def self.template_extension | |
@template_extension ||= 'html.mustache' | |
end | |
def self.template_extension=(value) | |
@template_extension = value | |
end | |
def self.shared_path | |
@shared_path ||= ::Rails.root.join('app', 'templates', 'shared') | |
end | |
def self.shared_path=(value) | |
@shared_path = value | |
end | |
end | |
class TemplateHandler < ActionView::Template::Handler | |
include ActionView::Template::Handlers::Compilable | |
self.default_format = :mustache | |
# @return [String] its evaled in the context of the action view | |
# hence the hack below | |
# | |
# @param [ActionView::Template] | |
def compile(template) | |
mustache_class = mustache_class_from_template(template) | |
mustache_class.template_file = mustache_template_file(template) | |
<<-MUSTACHE | |
mustache = ::#{mustache_class}.new | |
mustache.view = self | |
mustache[:yield] = content_for(:layout) | |
mustache.context.update(local_assigns) | |
mustache.render | |
MUSTACHE | |
end | |
private | |
# FIXME This doesn't currently work as it doesn't get called at all! | |
# def copy_instance_variables_to(mustache) | |
# variables = @view.controller.instance_variable_names | |
# variables -= %w[@template] | |
# | |
# if @view.controller.respond_to?(:protected_instance_variables) | |
# variables -= @view.controller.protected_instance_variables | |
# end | |
# | |
# variables.each do |name| | |
# mustache.instance_variable_set(name, @view.controller.instance_variable_get(name)) | |
# end | |
# | |
# # For an anonymous mustache, you probably want +attr_reader+ declared for | |
# # your instance variables. Otherwise there's no way you can access them on | |
# # the template. | |
# if mustache.class == Mustache | |
# mustache.class.class_eval do | |
# attr_reader *variables.select { |name| name =~ /^@[^_]/ } | |
# end | |
# end | |
# end | |
def mustache_class_from_template(template) | |
const_name = ActiveSupport::Inflector.camelize(template.virtual_path.to_s) | |
defined?(const_name) ? const_name.constantize : Mustache | |
end | |
def mustache_template_file(template) | |
virtual_path = template.virtual_path.sub('alfred/views/','') | |
"#{Config.template_base_path}/#{virtual_path}.#{Config.template_extension}" | |
end | |
end | |
end | |
end | |
::ActiveSupport::Dependencies.load_paths << Rails.root.join("app", "views") | |
::ActionView::Template.register_template_handler(:rb, Mustache::Rails::TemplateHandler) |
Mike,
Cool! I wanted to package it up into a gem at some point, but if you beat me to it that's awesome!
ad 1)
I believe I just copied that from one of the other snippets flying around, maybe from some older version of mustache on rails (not 3). I can't think of a reason why that would need to be that way.
ad 2)
Yes they would. Some additional info: If you want the view to be rendered in a layout, you should place the layout in app/views/layouts/application.rb, inherit from ::Mustache::Rails, and then just subclass this in your specific view classes.
ad 3)
I already had that commented in the app i'm using this gist as an initalizer. Currently it doesn't get called at all, and since it's an instance method on the TemplateHandler, i'm not sure how you could get at it manually, tho it should be possible
Lemme know once you have some somewhere to look at :)
Some additional info on 2) If you want the view to be rendered in a layout, you should place the layout in app/views/layouts/application.rb, inherit from ::Mustache::Rails, and then just subclass this for your specific views.
Martin, thank you! I'm trying to hack together mustache templating support and a mustache generator for Rails 3, and this gist looks as though it will help a lot. I do have a few questions. Would you be so kind as to reply when you have time?
Thanks again. I'm looking forward to using your code.
Cheers,
Michael