Johan Sørensen

Rails tip: validating Markdown

I’ve been doing a bunch of work on Collaboa in the past few nights, mainly bringing it up to date with the latest Rails and a general major overhaul.

One of the problems with previous versions was the there was no validation of user inputted Markdown and since BlueCloth has a habit of blowing up in the face of users whenever the syntax isn’t complete. Left out backticks, for inline code, are in particular troublesome.

So I wanted to check that the syntax was proper through Rails’ validation methods before saving anything and giving the user he ability to fix his syntax errors. The following was the simplest solution I came up with.


protected
  def validate
    errors.add("content", "has #{invalid_markdown_syntax(self.content)}") »
          unless invalid_markdown_syntax(self.content).nil?
  end
  
private
  def invalid_markdown_syntax(text)               
    begin
      BlueCloth.new(text).to_html
      return nil
    rescue BlueCloth::FormatError => e
      return e
    end
  end	

Since I want to display the syntax error to the user, I’ll have to return the error that BlueCloth raised so I can display it in the validation failure message and return nothing (nil) if BlueCloth didn’t choke on anything. This way the following text will be displayed on Markdown formatting errors (typing ``foo` is something BlueCloth will usually choke on): "Content has bad markdown format near “foo”: No “`” found before end".

UPDATE Jeremy stepped up and offered a much better solution to the problem, which follows the “Ruby Way” (and/or Rails way) of dealing with it, see the comments below


Comments:

  1. Jeremy Kemper Says:


    Introduce a class method you can reuse to declare other Markdown validations:

    
    def self.validates_markdown(*attrs)
      validates_each(*attrs) do |attr|
        begin
          BlueCloth.new(text).to_html
        rescue BlueCloth::FormatError => e
          errors.add attr, "has #{e}"
        end
      end
    end
    
    validate_markdown :content, :excerpt, :copyright
    

    The validation is slow because it has to do the Markdown render to check for validity. TODO: check validity only if the source has changed since the last render; cache generated HTML.

  2. Jeremy Kemper Says:


    Imagine that code beautifully indented and correctly marked up. Imagine all the people.

  3. johan Says:


    Jeremy, I imagined it so hard I edited your comment to look slightly better (with the code formatting).

    This is really a better solution for the problem, and I wouldn’t have to repeat myself, nor step outside of the valides_* pattern. Thanks man!

  4. Adam Keys Says:


    Were you to mix in some of Rails’ Ajax helpers here, I think you’d have a big leap forward in usability. I’m thinking along the lines of hooking the preview up to an Ajax action that sends the Markdown to the server and displays validation errors and a preview in the form. This would also allow you to set an “is valid” flag on the client, preventing embarassingly formatted posts.

    For bonus points, you could throw in something like the quicklink bar in Wordpress, except with shortcuts for whatever markup language you’re using. Besides the whole browser crashing and nuking your work, I think this would be a vast step forward in usability.