Safety in Numbers
Brighter Planet's blog
Sharing Rails views with Jekyll
In my last post I discussed how we share a single layout between Rails apps. This has been a lifesaver for us as we manage a half-dozen production apps. But a couple of our sites—our developer hub and this here blog—don’t use Rails. They’re both Jekyll sites running on GitHub Pages.
Obviously we can’t rely on the Rails Engine features in our shared layout gem to load the layout into the right places. What are we to do?
One step at a time
Luckily Jekyll already includes the basic building blocks of our solution: layouts and includes. Layouts, described here are Liquid templates, and Jekyll ships with a custom Liquid extension that enables includes.
All we need to do then is transform our Rails layout into a Jekyll layout and use includes instead of partials. Ready, set, go.
When in Rome
Jekyll is a static site generator. Following this lead, our transformations will be manually executed and staticly stored within your Jekyll site. The easiest way to get started is to set up a task in your Rakefile:
require 'net/http'
require 'uri'
require 'erb'
require 'lib/stubs'
namespace :layout do
task :build do
File.open File.join(File.dirname(__FILE__), '_layouts', 'default.html'), 'w' do |f|
f.puts ERB.new(Net::HTTP.get(URI.parse('http://github.com/brighterplanet/brighter_planet_layout/raw/master/app/views/layouts/brighter_planet.html.erb'))).result(Layout.new.get_binding { |*pages| '{ { content } }' if pages.empty? })
end
end
end
What’s going on here? Rake is fetching the raw ERB of your layout from the gem’s repository, sending it to ERB for processing, and then storing the result as your Jekyll site’s default
layout.
I should call your attention to a couple of tricky bits here.
Bindings
First, this business about bindings. ERB needs a “binding” to work–that is, a context within which it can access instance methods, variables, etc. Rails takes care of this for you, but since we’re invoking ERB here directly, we have to tell it where to bind. Why is this important? Your layout probably uses methods like stylesheet_link_tag
or render
to get its job done. If we don’t provide those methods in ERB’s context, we’ll get NoMethodError
all over the place. The easiest way to fool ERB is with a fake context, which we’ll put in lib/stubs.rb
:
class Layout
def stylesheet_link_tag(*sheets)
sheets.collect do |sheet|
"<link rel=\"stylesheet\" type=\"text/css\" href=\"/stylesheets/#{sheet}.css\" />"
end
end
def javascript_include_tag(*args); end
def render(options = {})
"{٪ include #{options[:partial][/[a-z_]*$/]}.html ٪}"
end
def get_binding
binding
end
end
You can see how we re-interpret these method calls in a way that’s meaningful to Jekyll. (Note that since binding
is a private method we have to publicize it with the get_binding
wrapper.)
Yields
The second tricky bit is dealing with your standard Rails layout’s multiple yield
calls, the consequence of using content_for
blocks in your views. We have to anticipate this and set up ERB to act accordingly. Where do we even capture arguments to yield? Turns out the correct place to do this on get_binding
, our wrapper to the private binding
method. Now the yield
we’re interested in—the one where we want Jekyll content to go—is the one called without any arguments. So we set the block to output the content
Liquid tag when it sees yield
called with an empty argument set. Other yield
calls—to dump content_for
material, which could never be prepared by Jekyll anyways—are simply ignored.
And we’re done
Your complete layout package will probably include several partials—each with its own fake context class in stubs.rb
—as well as asset files. To build your layout from its source in the cloud, just
$ rake layout:build
Check out our developer hub’s Rakefile
and stubs.rb
for all the details.
What blog is this?
Safety in Numbers is Brighter Planet's blog about climate science, Ruby, Rails, data, transparency, and, well, us.
Who's behind this?
We're Brighter Planet, the world's leading computational sustainability platform.