– we create awesome web applications

It’s alive. Again ;)

This blog was dead for a while. It was running on an old Mephisto system and it’s admin interface just broke one day, throwing some cryptic exception.

It was probably not that hard to fix, but every time I thought to write a blog post it was like “oh, I have to dig into this antient codebase to figure out how to fix it before I actually get to write anything. I’ll pass…”

So days become weeks, and weeks become months, pretty fast its a year that passed by with no blog posts whatsoever.

Meanwhile I was scribing my thoughts on future blogposts in markdown files on my harddrive.

Not long ago we finally decided to do something about it.

Getting the data out.

First we had to get the data out of the mephisto databse. We had to write our own script as whatever we could find on the net was broken in one way or another.

We dumped all articles into simple text files.

Most of the articles were in markdown format but some had to be converted from textile.

Deciding where to have the new blog.

We couldn’t decide for a long time. There were a couple of options:

  1. standalone static site
  2. something under /blog/ directory in the current astrails.com site which was implemented in Rails.
  3. 3rd party service.

I was for option 2 since I think its the best one for SEO and also gives us complete control over the style, syntax coloring, urls, smart sidebar and footer and whatnot.

But we went with options 3 at first, as it seemed the fastest one, and created a blog on Tumblr.

Now, Tumblr is a nice service, even though we didn’t intend to use its community features, we just needed a blog. Quite a few high profile ruby people and companies use it, for example Thoughtbot and many others.

But we immediately hit a couple of problems with it.

First, it doens’t let you completely control your urls, so we would have to redirect old blog post urls to not break linkbacks and not to be punished by Google and friends. Fortunately it does allow to define redirects, but the process is rather cumbersome, and lets face it nice urls are, well, nice ;)

We did manage to import our old blogpost into it using their API but we had to manually fix some styling later and once we discovered that some blogposts had content problems and we had to re-run the import script again (which means repeating the manual formatting again too), we decided to look again at the option 2.

integrating blog into existing Rails app.

The hardest part was to convert the old app to Rails 3.2 as it was still running on Rails 2. After that it was not that hard at all.

Note: the code below is somewhat simplified, we did end up adding things like filtering by tags, authors and atom.xml support.

It all is built around model Post:

class Post

  def self.model_name
    ActiveModel::Name.new self
  end

  ...

end

model_name is needed so that we can just pass a post into link_to like so:

link_to post.title, post

What else is there?

We decided to just commit the markdown files under directory blog preserving the old url s tructure. So that, for example, a blogpost that was at http://blog.astrails.com/2008/6/4/being-lazy-with-ruby would go into blog/2008/6/4/being-lazy-with-ruby.html.markdown.

Enumerating the files.

Once the files were in place we needed a way to enumerate them all to build the blog’s index pages:

# This is just an quick and UGLY hack ;)
def self.filenames
  @@filenames ||=
    Dir['blog/20*/*/*/*.html.markdown'] .
    map {|f| f.gsub("blog/", "")} .
    map {|f| y, m, d, p = f.split("/", 4); [y.to_i, m.to_i, d.to_i, p.split(".", 2).first]} .
    sort .
    reverse .
    map {|f| f.join("/")}
end

Its a quick and dirty way to sort all the blogposts chronologically according to their path.

Post.find and friends

Next, lets try to make it behave somewhat like an ActiveModel (Note I didn’t use the actual ActiveModel since I really wanted to keep this to a bare minimum), I did ‘emulate’ the interfaces I was going to use so that it will be trivial to convert to ActiveModel later.

attr_reader :id
def initialize(id)
  @id = id
end

def self.index
  @@index ||= filenames.inject({}) do |all, file|
    all[file] = Post.new file
    all
  end
end

def self.all
  index.values
end

def self.find(id)
  index[id] or raise ActiveRecord::RecordNotFound.new(id)
end

def self.first
  find filenames.first
end

Post#content

Once we have the blog posts indexed, we need to get the content

def path
  @path ||= File.join(Rails.root, "blog/", "#{id}.html.markdown")
end

def content
  @content ||= File.read(path)
end

Metadata

Besides the content we also need things like title, author, tags etc.

We decided to use a YAML prefix to all markdown files, similar to what Jekyll does. The YAML is separated from the rest by an empty line:

def parse
  return if @body # already parsed
  meta, @body = content.split("\n\n", 2)
  @meta = YAML.load(meta).with_indifferent_access
end

def body
  parse
  @body
end

def meta
  parse
  @meta
end

Markdown rendering

Now that we have the content parsed we need to render the markdown and with syntax highlighting.

After some investigating we decided to use Redcarpet gem for markdown and Albino for syntax coloring.

application_helper.rb

def markdown
  @markdown ||= Redcarpet::Markdown.new(
    AlbinoRenderer,
    :space_after_headers => true,
    :fenced_code_blocks => true)
end

def format_markdown(text)
  markdown.render(text).html_safe
end

Syntax highlighting

AlbinoRenderer turned up a little more complicated then the standard examples found on the net as we wanted to:

  • support both code blocks and in-line code fragments
  • a simple syntax to declare language without using the fenced_code_blocks.

A fenced code block is a markdown extension of Redcarpet. The syntax is as following:

This is markdown text.

~~~~ruby
Here goes the ruby code
~~~~

The problem with it was mostly aesthetics ;). Vim markdown wouldn’t recognize it as code and would freak out on underscores and such as an invalid markdown syntax. So I wanted to use the standard 4 space indentation for code blocks, but still be able to declare the language.

The syntax that was implemented is like this:

This is markdown text.

    #!ruby
    This is a ruby code

albino_renderer.rb:

class AlbinoRenderer < Redcarpet::Render::HTML
  def detect_language(code)
    lang, rest = code.split("\n", 2)
    return code unless lang =~ /^#!\w+$/
    return rest, lang[2..-1]
  end

  def block_code(code, language)
    unless language
      code, language = detect_language(code)
    end
    if language.present?
      Albino.colorize(code, language.presence)
    else
      %Q{<div class="highlight"><pre>#{code}</pre></div>}
    end
  end

  def codespan(code)
    code = code.strip
    if code.starts_with? '#!'
      language, code = code.split ' ', 2
    elsif code.starts_with? '\#!'
      code = code[1..-1]
    end

    if language
      code = Albino.new(code, language[2..-1]).colorize(:P => "nowrap=true").strip
    end
    %Q{<span class="highlight">#{code}</span>}
  end
end

Controller and routing.

We now have all the pieces, lets glue it all together:

config/routes.rb:

match "/blog" => "posts#index"
match "/blog/:year/:month/:day/:id" => "posts#show"

posts_controller.rb:

class PostsController < ApplicationController
  caches_page :only => [:index, :show], :gzip => :best_compression
  layout "blog"

  def protect_against_forgery?;false;end

  def index
    @posts ||= Post.all
  end

  def show
    id ||= [params[:year], params[:month], params[:day], params[:id]].join("/")
    @post = Post.find id
  end
end

Redirecting the old blog urls

Now that the blog has moved to the new home, we need to redirect the old urls.

At first I tried with Rails 3 constraints (I know, its kind of long, but that was a debuggin version, where I wanted to print every segment):

# redirect from blog
constraints :subdomain => /blog\.?/ do
  match '(*path)' => redirect { |params, req|
    subdomain = req.subdomain.gsub(/^blog\.?/, '').presence
    domain    = req.domain
    path      = ['blog', params[:path].presence].compact * "/"
    query     = req.query_string.presence
    host      = [subdomain, domain].compact * "."
    url       = ["http://#{host}/#{path}", query].compact * "?"

    URI.escape url
  }
end

The problem with it was that whenever a page was page-cached, like public/index.html, it would be returned before it hits Rails routing, so http://blog.astrails.com/ would not redirect displaying http://astrails.com content instead.

So instead we ended up with a Rack redirector:

in config/application.rb:

config.middleware.insert_before 0, 'BlogRedirector'

blog_redirector.rb:

class BlogRedirector
  def initialize(app)
    @app = app
  end

  def call(env)
    request = Rack::Request.new(env)
    if handle?(request)
      [301, {"Location" => redirect_url(request)}, self]
    else
      @app.call(env)
    end
  end

  def each(&block)
  end

  protected

  def redirect_url(request)
    request.base_url.sub("//blog.", "//") + "/blog" + request.fullpath
  end

  def handle?(request)
    request.host.starts_with?("blog.") && "/robots.txt" != request.fullpath
  end
end

Making it look like a blog

Now, all this low level stuff is cool, no arguing about that. But someone will have to read it and that means it has to be readable and look like a blog.

We used twitter bootstrap for layout and general structure and styled it to our liking, added a photo of an author to make it personal, big fat date for each blog post so you can keep track, sidebar with tags filtering and some useful links etc.

Now, we no designers here, but i think it looks ok. Am i wrong here?

And we blog again

Now we can avoid using 3rd party blog admin interfaces, one stranger than another. We use Vim to throw down markdown blog posts, along with relevant code pieces and it’s all get rendered and cached as it should.

Writing posts is a nice and simple experience, using familiar tools is warm and cozy, so, we’ll see you soon.