So my girlfriend wants to start a blog and being the awesome boyfriend I am, I offered to write it for her. Writing a simple blog in rails isn’t that much work and having an easy to set up blog app lying around can’t hurt, right? Today while trying to make posting as frictionless as possible I stumbled upon something really cool I wasn’t really aware of before. I’m kind of forcing my girlfriend to use Markdown1 and am using Redcarpet to parse it. On the bottom of the post form I’m enabling the user to upload pictures with Paperclip she want’s to use in the post but wasn’t really sure how to make easy to embed them in the post content. In come custom Redcarpet renderer. They basically enable you to rewrite the methods used by Redcarpet and give them your own spin.

But let’s start from the beginning and add all the gems we need to the Gemfile:

gem 'redcarpet'
gem 'paperclip', '~> 3.0'
gem 'paperclip-meta'

Redcarpet and Paperclip should be clear and Paperclip Meta writes the dimension of the Paperclip styles in an extra column which makes them nicely accessible as you’ll see later.

Now let’s create the models:

$ rails g model Post title:string content:text
$ rails g model Image name:string file:attachment file_meta:text post_id:integer

And edit them:

class Post < ActiveRecord::Base
  attr_accessible :content, :title, :images_attributes
  has_many :images, :dependent => :destroy
  accepts_nested_attributes_for :images, allow_destroy: true
end
class Image < ActiveRecord::Base
  attr_accessible :file, :post_id, :name
  belongs_to :post
  has_attached_file :file, :styles => { :thumb => "140>x140" }
end

Now normally an image would like like this in Markdown:

![title](path/to/image "alt text") 

I thought it would be cool if the user could give the image a name and I could use this name to generate the link. Furthermore it would be cool if the user could set a size and a class2 for the image while still using Markdown. That could look something like this:

![image.title](thumb|right "alt text") 

The size “thumb” of course meaning the Paperclip style thumb nad right being a html class, that floats the image to the right side.

Now we’ll create our custom Redcarpet renderer to process our new markup. Initially I had a helper method in the ApplicationHelper for Redcarpet:

module ApplicationHelper
  def markdown(text)
    Redcarpet::Markdown.new(Redcarpet::Render::HTML.new(:hard_wrap => true), :space_after_headers => true, :autolink => true, :strikethrough => true, :superscript => true).render(text).html_safe
  end
end

But now I’d rather put this and the custom renderer in it’s own helper module.

module RedcarpetHelper
  def markdown(text)
    Redcarpet::Markdown.new(HTMLBlockCode.new(:hard_wrap => true), :space_after_headers => true, :autolink => true, :strikethrough => true, :superscript => true).render(text).html_safe
  end
end

class HTMLBlockCode < Redcarpet::Render::HTML
  include Sprockets::Helpers::RailsHelper
  include Sprockets::Helpers::IsolatedHelper
  include ActionView::Helpers::UrlHelper

  def parse_media_link(link)
    matches = link.match(/^(\w+)?\|([\w\s\d]+)?/)
    {
      :size => (matches[1] || 'original').to_sym,
      :class => matches[2]
    } if matches
  end

  def image(link, alt_text, title)
    size = nil
    klass = nil

    if nil != (parse = parse_media_link(link))
      image = Image.find_by_name(title)
      if image
        size = image.file.image_size(parse[:size])
        link = image.file.url(parse[:size])
        klass = parse[:class]

        image_tag(link, :size => size, :title => title, :alt => alt_text, :class => klass)
      else
        ""
      end
    end
  end

  def link(link, title, content)
    klass = nil

    if nil != (parse = parse_media_link(link))
      image = Image.find_by_name(title)
      if image
        link = image.file.url(parse[:size])
        klass = parse[:class]

        link_to(content, link, :title => title, :class => klass)
      else
        ""
      end
    end
  end
end

Here we’ve overwritten how Redcarpet generates an image_tag. First it’s parsing the link for paperclip style3 and class, than it searches the image by title and generates an image_tag with the url of the image file, the dimensions of the chosen style4, the title, alt text and class. Since our new renderer inherits from the existing renderer and only changes one method, we now can just exchange the Redcarpet::Render::HTML bit in the markdown() helper method with our renderer HTMLBlockCodeand are done.

Helpful links:


  1. I see it as an educational journey for her… not sure if she’ll see it the same way. ↩︎

  2. For right and left float for instance. ↩︎

  3. or the original image size if left blank. ↩︎

  4. Paperclip Meta makes it possible to get the size like this. ↩︎