Sucking in the header and footer from a remote site

How do you share a common header and footer across multiple apps? For example, you might be prototyping a new app, and want to borrow the main sites header and footer. Obviously you can do crazy stuff like have JavaScript pull something via the client, or maybe have iframes or frames. But what if you have to massage the content as it comes in, maybe only keeping part of it?

Well fortunately Ruby on Rails makes that very easy. We simply created a Model object that uses HPricot to pull in remote content and grab the portion we want:

require 'hpricot&';require 'open-uri'class CatalogContentcattr_accessor :remote_urldef self.footerurl = remote_urlraise "Remote URL was not set" if url.nil?puts "Pulling remote content footer from #{url}"begindoc = Hpricot(open(url))footer_content ="//div[@id=footer]")raise "Didn't find footer content in site #{url}" if footer_content.nil?footer_contentrescue Errno::EHOSTUNREACHfooter_content = "Can't connect to #{url}"rescue Errno::ETIMEDOUTfooter_content = "Can't connect to #{url}"rescue SocketErrorfooter_content = "Can't connect to #{url}"endendend

In this case we are loading up a website and pulling out the footer section in the HTML. We have the option here while manipulating the HPricot XML DOM to do any other types of changes we want such as:

  • Remapping URLS
  • Snipping out bits of content
  • Changing colors or stylesheet classes
  • Conditional inclusion of content

And then, we have a ApplicationHelper.rb method that creates the model object, populates the remote url and displays it:

def remote_footerCatalogContent.remote_url = AppConfig.catalog_remote_urlCatalogContent.footerend

Of course, people are going to jump on the fact that this means that every page is making an extra URL call right? Fortunately caching comes to the rescue in our layout:

<div class="footer">  < % cache(:controller=> 'remote_content', :action => 'remote_footer') do %>
< %= render(:partial=>'/partial/bottom_panel') %><br /> < % end %>div>

Note, however, we don’t actually have a controller called remote_content with an action called remote_footer, you just have to have those bits so the caching support knows what keys to store the cached content under! Could have been:

< % cache(:controller=> 'foo', :action => 'bar') do %>< %= render(:partial=>'/partial/bottom_panel') %>< % end %>

And in our partial we just have:

< %= remote_footer %>

So, the only downside is that in development we are constantly pulling in the content because the caching is turned off, which slows things down a bit, and that the only way (out of the box) to update the cache is to “sweep” out the contents of the /tmp/cache directory.

Also, be aware that if what you are importing has lots of relative paths or references relative JavaScripts and CSS that you are going to have to make those parts work with your app nicely!