Skip to content

Instantly share code, notes, and snippets.

@Sutto
Created October 15, 2009 19:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Sutto/211235 to your computer and use it in GitHub Desktop.
Save Sutto/211235 to your computer and use it in GitHub Desktop.
BEFORE TEMPLATE
<%= content_for_layout %>
AFTER TEMPLATE
BEFORE TEMPLATE TWO
= content_for_layout
AFTER TEMPLATE TWO
BEFORE TEMPLATE THREE
<%= yield %>
AFTER TEMPLATE THREE
BEFORE TEMPLATE FOUR
= yield
AFTER TEMPLATE FOUR
<%= @request.request_method %> to <%= @request.path_info %> with <%= @request.query_string %> - <%= content_for_layout %>
<%= request.request_method %> to <%= request.path_info %> with <%= request.query_string %> - <%= content_for_layout %>
require 'rack'
# sudo gem install tilt --source http://gemcutter.org/
require 'tilt'
# A simple Rack middleware which makes it super easy
# have a shared layout rendering system used across
# different rails applications.
#
# Examples:
#
# use Rack::LayoutWrapper, :template_dir => "required-path-to-template",
# :cache_listings => true, # Optional option to cache listings
# :default => "some-default-layout-name" # Optional default layout name
#
# Released under the MIT License
module Rack
class LayoutWrapper
class BodyWrapper
def initialize(before, original_body, after)
@before = before
@after = after
@original_body = original_body
end
def each
yield @before
@original_body.each { |c| yield c }
yield @after
end
end
@@template_path_cache = {}
PLACEHOLDER_CONTENTS = 'TEMPLATE-STUFF-GOES-HERE'.freeze
PLACEHOLDER_ENV_KEY = 'x-rack.layout_wrapper'.freeze
attr_accessor :template_dir, :default_layout
def initialize(app, opts = {})
@app = app
@render_layout = false
@layouts = nil
@layout_name = nil
@template_dir = opts[:template_dir]
@cache_listings = opts[:cache_listings] || true
@default_layout = opts[:default]
if @template_dir.to_s.strip == "" || !::File.directory?(@template_dir)
raise ArgumentError, "You need to provide a valid :template_dir directory"
end
end
def call(env)
available_layouts if @layouts.nil? && @cache_listings
dup._call(env)
end
def _call(env)
env[PLACEHOLDER_ENV_KEY] = self
status, headers, body = @app.call(env)
@request = (env['rack.request'] || Rack::Request.new(env))
headers, body = wrap_with_layout(headers, body) if @render_layout
[status, headers, body]
end
def render_layout?
@render_layout
end
def layout_name=(value)
@render_layout = !value.nil?
@layout_name = value
end
def render_layout=(value)
@render_layout = value
end
def layout_name
@layout_name ||= @default_layout
end
def available_layouts
@layouts ||= begin
layouts = Dir[::File.join(@template_dir, "**", "*")].select { |i| ::File.file?(i) }
layouts.map { |l| l.gsub(/^#{Regexp.escape(@template_dir)}\/(.*)\..*/, '\1') }.uniq
end
end
def request
@request
end
def full_template_path
default = @@template_path_cache[layout_name]
return default if default && ::File.exist?(default)
@@template_path_cache[@layout_name] = Dir[::File.join(@template_dir, "#{@layout_name}.*")].select { |i| ::File.file?(i) }.first
end
protected
def render_layout_parts
template_path = full_template_path
return "", "" if template_path.nil? || !::File.exist?(template_path)
locals = {:content_for_layout => PLACEHOLDER_CONTENTS, :request => @request}
template = Tilt.new(full_template_path).render(self, locals) { PLACEHOLDER_CONTENTS }
before_template, after_template = template.split(PLACEHOLDER_CONTENTS, 2)
before_template ||= ""
after_template ||= ""
return before_template, after_template
end
def wrap_with_layout(headers, body)
header_hash = Rack::Utils::HeaderHash.new(headers)
content_length = header_hash['Content-Length'].to_i
before_layout, after_layout = render_layout_parts
content_length += Rack::Utils.bytesize(before_layout)
content_length += Rack::Utils.bytesize(after_layout)
header_hash['Content-Length'] = content_length.to_s
body_wrapper = BodyWrapper.new(before_layout, body, after_layout)
return header_hash, body_wrapper
end
end
end
require 'rubygems'
require 'test/unit'
require 'redgreen' if RUBY_VERSION < '1.9'
require 'rack'
require 'erb'
require 'haml'
require File.join(File.dirname(__FILE__), "layout_wrapper")
class APIVersionMapperTest < Test::Unit::TestCase
def setup
@example_app = proc do |env|
req = Rack::Request.new(env)
headers = {
"Content-Type" => "text/html",
"Content-Length" => "8"
}
env[Rack::LayoutWrapper::PLACEHOLDER_ENV_KEY].layout_name = req.GET["layout_name"] if req.GET["layout_name"]
env[Rack::LayoutWrapper::PLACEHOLDER_ENV_KEY].render_layout = true if req.GET["render_layout"]
[200, headers, ["FROM APP"]]
end
@app = Rack::LayoutWrapper.new(@example_app, :template_dir => File.join(File.dirname(__FILE__), "layouts"))
end
def test_no_layouts
assert !@app.render_layout?
get '/'
assert_valid_response "FROM APP"
end
def test_simple_erb_layout
get '/?layout_name=example_a'
assert_valid_response "BEFORE TEMPLATE\nFROM APP\nAFTER TEMPLATE"
end
def test_simple_haml_layout
get '/?layout_name=example_b'
assert_valid_response "BEFORE TEMPLATE TWO\nFROM APP\nAFTER TEMPLATE TWO\n"
end
def test_yielded_erb_layout
get '/?layout_name=example_c'
assert_valid_response "BEFORE TEMPLATE THREE\nFROM APP\nAFTER TEMPLATE THREE"
end
def test_yielded_haml_layout
get '/?layout_name=example_d'
assert_valid_response "BEFORE TEMPLATE FOUR\nFROM APP\nAFTER TEMPLATE FOUR\n"
end
def test_default_layouts
@app.default_layout = "example_a"
get '/?render_layout=true'
assert_valid_response "BEFORE TEMPLATE\nFROM APP\nAFTER TEMPLATE"
# Now, override the default
get '/?layout_name=example_c'
assert_valid_response "BEFORE TEMPLATE THREE\nFROM APP\nAFTER TEMPLATE THREE"
end
def test_template_scope
get '/?layout_name=example_e'
assert_valid_response "GET to / with layout_name=example_e - FROM APP\n"
end
def test_template_request
get '/?layout_name=example_f'
assert_valid_response "GET to / with layout_name=example_f - FROM APP\n"
end
protected
def get(path, env = {})
@response = Rack::MockRequest.new(@app).get(path, env)
end
def assert_valid_response(body)
assert @response.ok?, "the response should be ok (was #{@response.status} instead)"
content_length = Rack::Utils.bytesize(body)
real_content_length = @response["Content-Length"].to_i
assert_equal content_length, real_content_length, "Expected a Content-Length of #{content_length}, got #{real_content_length}"
assert_equal body, @response.body, "Incorrect body in response"
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment