I recently needed to create arched text in an application that uses Cairo. Unfortunately, Cairo doesn't support warping text out of the box.

Luckily, the pycairo guys were cool enough to include an example of warped text in their distribution.

Here it is, ported to ruby:

Ok...If you really love SQL, this is not the article for you. I'm going to share a little trick I've learned that will let you do really complex queries without writing a lick of SQL.

Here's the setup

I've been working on a niche job site for a client. It's pretty straightforward, but it involves some monster queries.

A typical requirement might be to query all JobCandidates who:

  • Have any location, job_type or specialization matching those of any job belonging to a specific employer.
    • JobSeekers and Jobs both have multiple, arbitry values for location, job_type, etc...
  • Have not replied to any of the employer's previous job postings
  • Have been active in the last month

... and we need to to fulltext search on top of this.

That sounds like a lot of work

Why yes, it does.

Sure, you can implement all of that using AR/Mysql. But it sounds like a heck of a lot of work. And not very fun work.

The Solution

On a lark, I decided to see if this could be implemented any more easily using Solr, and the excellent Sunspot gem.

The basic trick is this:

  1. Use AR's serialization to store an array of tags in the model
  2. Tell Solr to index each tag.

An Example Model

# app/models/job.rb

class Job

  # job.specializations will hold an array of strings
  # like this: ['rails', 'javascript']
  serialize :specializations, Array

  # Solr will index each element of the array separately
  searchable do
    string :specializations, :multiple => true
  end

end

How to search

  
#
# You can also do Job.solr_search, but I like this syntax better
#

Sunspot.search(Job) do
  with(:specializations, ['rails', 'python', 'perl')
end.results

This will return all jobs where job.specializations includes one of 'rails', 'python' or 'perl'.

Bonus!

You don't have to do anything different in your controllers/views to take advantage of this approach.

Rails' views and controllers are set up to handle arrays like this just fine. In the example below, when the user submits the form, the job's specializations are set, and will be indexed by Solr on save.


<% form_for Job.new do |f| %>

  #
  # Here, we use a multiple select, but any forrm element named
  # "job_specializations[]" will populate the jobs.specializations
  # array
  #
  <%= f.select :specializations, 
                ["rails", "ruby", "js", "python], 
                :multiple => true %>

<% end %>

Wash, Rinse and Repeat

If you can add one solr-indexed tag attribute to your model, you can add two.

Also, since solr can index integers, there's no reason you can't define associations between models in terms of arrays of integers.

Here's a bit of production code, slightly cleaned up:


  #
  # Here, we query all JobSeekers where any of their "desired 
  # specializations" match any of the job's "required specializations"
  # AND any of their "desired positions" match any of the job's...etc
  #
  # We also only want JobSeekers who have NOT accepted this job.
  #
  # And we do a fulltext keyword search too, just for fun.
  #

  job = Job.find(...)

  Sunspot.search(JobSeeker) do
    keywords params[:s]
    with(:desired_specializations, job.required_specializations)
    with(:desired_positions, job.required_positions)
    with(:desired_locations, job.required_locations)
    without(:accepted_job_ids, job.id)
    paginate :page => params[:page]
  end.results

Caveats

  1. I'm pretty sure this won't scale well at all, since it requires you to update the solr index whenever a new record is created or updated. But this particular project is not going to be very high-traffic, so it's no big deal.

  2. Solr only promises 'eventual consistency', which could be an issure for some apps, but isn't here.

Mongodb is pretty sweet, and mongo mapper makes it really easy to integrate with your rails app.

Now, mongo isn't the solution to every problem. But for some things, like action tracking, it is close to perfect.

For example, the following track method will create a Metric record with a certain action if it doesn't exist. Then it will push the current time to the timestamps array.

class Metric
  include MongoMapper::Document

  key :timestamps, Array
  key :action, String

  def self.track(action)
    collection.update(
      { :action => action }, 
      { "$push" => { :timestamps => Time.now }}, 
      :upsert => true)
  end

end

The only caveat with this approach is that mongo documents have a max size of 5 megabytes. So if you were tracking pageviews on a site that received 1000s of hits a day, you'd exceed that limit pretty quickly.

So I have a client who needed a google voice widget embedded in their site.
If you're not familiar with it, it's a little widget that your site's visitors can use to call you.

It's really simple to set up...unless you want to customize the widget's graphics. But there is a way. Here's how you can do it:

A simple form post

The Google Voice widget does a simple form post. You can see it Here.

When there are no errors, google returns "ok=true". When there are problems, it returns "ok=false".

Easy

A Proxy

The only kink here is that we can't AJAX post to a 3rd party domain like google. So we need to implement a little proxy. You can do this by creating a controller named "phone_calls".

It's pretty simple. It just takes params[:phone_call] and does a https post to the google server.

class PhoneCallsController < ApplicationController
  def create
    http = Net::HTTP.new('clients4.google.com', 443)
    http.use_ssl = true
    path = '/voice/embed/webButtonConnect'

    request_params = params[:phone_call].map { |k, v| "#{ CGI.escape(k) }=#{ CGI.escape(v) }" }.join("&")
    request_headers = {}

    resp, data = http.post(path, request_params, request_headers)

    render :text => data
  end
end

The HTML

Now we just need to make a little form and add some javascript to submit it. You'll need to get your api key from the google voice widget's embed code.

<form id="google-voice" action="https://clients4.google.com/voice/embed/webButtonConnect" method="post">

  <input type="hidden" name="phone_call[buttonId]" value="<YOUR API KEY>" />
  <input type="hidden" name="phone_call[showCallerNumber]" value="1" />

  <ul>
    <li>
      <label>Your Name</label>
      <input type="text" name="phone_call[callerNumber]" />
    </li>
    <li>
      <label>Your Phone #</label>
      <input type="text" name="phone_call[callerNumber]" />
    </li>
    <li>
      <input type="submit" value="Call Me" />
    </li>
  </ul>

</form>

... and here's the javascript


  $("#google-voice").submit(function(e){
    e.preventDefault();

    $.post("/phone_calls", $(this).serialize(), function(data){
      if(data=="ok=true"){
        console.log("success");
      }
      else{
        console.log("failure");
      }
    }, "html");

  });

You can make it a little fancier by showing "success" and "failure". That's left as an exercise for the reader.

Normally sending emails with rails is a piece of cake. But today, it wasn't.

A client wanted to use their Fusemail account to send user registration confirmations, password resets, and so forth. But it just wouldn't work.

I tried every possible value of every option that you could send to Net::SMTP, with no luck.

So I tried telnetting in to the SMTP server, just to see if it existed:

> telnet smtp.fusemail.net 463
Trying 208.70.131.61...
Connected to smtp.fusemail.net.
Escape character is '^]'.

And that was it. Weird. No SMTP headers. Nothing.

> openssl s_client -crlf -connect smtp.fusemail.net:463
220 smtp-gw55.mailanyone.net MailAnyone extSMTP Wed, 24 Feb 2010 14:43:56 -0600

BINGO. It turns out fusemail uses an old version of TLS called "tls on connect"

With regular TLS, you connect to the SMTP server, send a "STARTTLS" command, and then initialize a SSL connection.

But with tls on connect, you have to connect using SSL.

With that bit of knowledge we can easily patch Net::SMTP to work with fusemail

require "openssl"
require "net/smtp"

Net::SMTP.class_eval do
  private
  def do_start(helodomain, user, secret, authtype)
    raise IOError, 'SMTP session already started' if @started
    raise 'openssl library not installed' unless defined?(OpenSSL)

    if RUBY_VERSION > "1.8.6"
      check_auth_args user, secret if user or secret
    else
      check_auth_args user, secret, authtype if user or secret
    end

    sock = timeout(@open_timeout) { TCPSocket.open(@address, @port) }

    # This is the important bit. We just open an SSL socket 
    # to use instead of the regular TCP socket.
    ssl = OpenSSL::SSL::SSLSocket.new(sock)
    ssl.sync_close = true
    ssl.connect
    @socket = Net::InternetMessageIO.new(ssl)
    @socket.read_timeout = 60 #@read_timeout

    check_response(critical { recv_response() })
    do_helo(helodomain)

    authenticate user, secret, authtype if user
    @started = true
  ensure
    unless @started
      # authentication failed, cancel connection.
      @socket.close if not @started and @socket and not @socket.closed?
      @socket = nil
    end
  end

  def do_helo(helodomain)
    begin
      if @esmtp
        ehlo helodomain
      else
        helo helodomain
      end
    rescue Net::ProtocolError
      if @esmtp
        @esmtp = false
        @error_occured = false
        retry
      end
      raise
    end
  end

  def quit
    begin
      getok('QUIT')
    rescue EOFError
    end
  end
end

So you're given an archive containing 10,000,000 .ttf files, most of which are named things like "bs019dfk.ttf". How do you extract human readable names from these things?

If you're like me you think, "No problem! There has to be a command line utility for that!"

...then about an hour later you think, "I hope to god there's a command line utility for this".

...then you open a .ttf file in a text editor just in case the name will somehow magically be there.

...then you start drinking.

Well, good news for your liver. While I couldn't find a stock utility to extract the name from the .ttf file, I was able to find a little code to do so.

It's in python, and you'll need the FontTools library installed. And you should know that this code is blatently copied from the TTFQuery project.

#!/usr/bin/python
import sys
from fontTools import ttLib

FONT_SPECIFIER_NAME_ID = 4
FONT_SPECIFIER_FAMILY_ID = 1
def shortName( font ):
        """Get the short name from the font's names table"""
        name = ""
        family = ""
        for record in font['name'].names:
                if record.nameID == FONT_SPECIFIER_NAME_ID and not name:
                        if '\000' in record.string:
                                name = unicode(record.string, 'utf-16-be').encode('utf-8')
                        else:
                                name = record.string
                elif record.nameID == FONT_SPECIFIER_FAMILY_ID and not family:
                        if '\000' in record.string:
                                family = unicode(record.string, 'utf-16-be').encode('utf-8')
                        else:
                                family = record.string
                if name and family:
                        break
        return name, family


tt = ttLib.TTFont(sys.argv[1])
print shortName(tt)[0]

To use it, just do this:

$ ttf_name somefont.ttf

Or to get all of the font names in a directory tree:

$ find . -iname "*.ttf" -exec ttf_name {} \;
Fruitopia
Pokemon Hollow Normal
Kitty Katt
BTSE + PS2 FONT
BatmanForeverOutline
Walt Disney Script v4.1
NewWGL4Font
Oreos 
Halo OutlineRegular

Yesterday I was working on a project, and found myself needing to create a bunch of color swatches. CSS wasn't an option. No. They had to be little 16x16 images.

In such situations, I often resort to a more-or-less socratic dialog:

"Is it not so that you are building this for a client"

"Yes."

"And do clients not inevitably change their minds"

"Very true, they usually do"

"Then would it not make sense to generate these swatches programmatically?"

The Rake Task

Heartily agreeing with myself, I devised the following rake task for creating color swatches, based on input from a YAML file. BTW, You'll need to have imagemagick installed.

# Rakefile
desc "Create new color swatches"
task :swatches do

        require "yaml"

        YAML.load_file("swatches.yml").each do |name, color|
                p "Creating: #{name} (#{color}) => images/colors/#{name}.png"
                %x[ convert -size 16x16 xc:white -draw "stroke #666666 fill white rectangle 0,0 15,15" -draw "stroke white fill #{color} rectangle 1,1 14,14" "images/colors/#{name}.png" ]
        end

end

The YAML File

Now create a "swatches.yml" file like so:

# swatches.yml
black:             "#0F0F0F"
blue:              "#3055A0"
gray:              "#949A9F"
green:             "#4D8756"
light_green:       "#92C14F"
navy:              "#304264"
orange:            "#DA7A2D"
red:               "#C31E38"
teal:              "#477D79"
white:             "#FFFFFF"
yellow:            "#FFFF00"

W00t!

Now you can just type in "rake swatches" and feel the magick.

$ rake swatches

"Creating: white (#FFFFFF) => images/colors/white.png"
"Creating: navy (#304264) => images/colors/navy.png"
"Creating: black (#0F0F0F) => images/colors/black.png"
"Creating: forest_green (#2E5234) => images/colors/forest_green.png"
"Creating: blue (#3055A0) => images/colors/blue.png"
"Creating: orange (#DA7A2D) => images/colors/orange.png"
"Creating: green (#4D8756) => images/colors/green.png"
"Creating: brick (#6F0028) => images/colors/brick.png"
"Creating: red (#C31E38) => images/colors/red.png"
"Creating: light_green (#92C14F) => images/colors/light_green.png"
"Creating: gray (#949A9F) => images/colors/gray.png"
"Creating: teal (#477D79) => images/colors/teal.png"
"Creating: yellow (#FFFF00) => images/colors/yellow.png"
"Creating: school_bus_yellow (#E9AF42) => images/colors/school_bus_yellow.png"

Javascript is sad

It seems like JavaScript just doesn't get much love. Think about it. We have so many great tools for organizing our databases, our controllers, our HTML. But what if you want to add a little JS to the mix? Then, you're faced with an unenviable choice:

You can put all of your JavaScript into one big, unmanageable file. Or, you can use multiple files and accept the slower load times and extra complexity.

But now, thanks to Sprockets, we can have the best of both options.

Javascript is happy!

Sprockets is a project by Sam Stephenson at 37signals. It's a ruby library for managing JavaScript dependencies. With Sprockets and sprockets-rails, you can break up your JavaScript code into multiple files and directories. (Hey! That's just like a real programming language!). Then, when a page is requested, the component JS files will be assembled on the fly and served as a single file.

But what about the CSS and image files that JavaScript libraries inevitable require? The normal way of handling those is to throw them in to a subdirectory of "public". But that can get messy fast.

That's why one of the most useful feature of Sprockets is the ability to create JavaScript "plugins". Located in vendor/sprockets, these plugins let you bundle your JavaScript and its supporting assets together. A simple rake task (rake sprockets:xxx) can copy all of the assets to your public direcory, making upgrades and changes easy.

Three Delicious Flavors

There are three ways that you can use Sprockets.

  1. Use it as a Ruby library, so you can create your own JavaScript build system.
  2. Use the 'sprocketize' utility from the command line, for easy shell scripting.
  3. Use the Sprockets-rails plugin, which makes integration with rails a snap.

We're going to focus on using the sprockets-rails plugin in this article. For more information, check out http://getsprockets.com

The Sprockets Syntax

Sprockets uses a special syntax to specify dependencies. The JavaScript interpreter simply reads the following lines as comments, but Sprockets knows better:

//= require <file>  Include a file located in one of the include paths in sprockets.yml
//= require "file" Include a file based in the current directory. You can use relative paths.
//= asset "asset/" Specify a path for all assets to be moved to /public
//= asset "somefile.png" Specify a single file to be moved.

Installing Sprockets

To install Sprockets and sprockets-rails, you'll need to make sure that you have git installed, and that you have an existing rails project to work with.

Sprockets is distributed as a gem, so installation is easy:

$ gem install --remote sprockets

Since sprockets-rails is a rails plugin, installing it is easy as well:

$ cd your_project_directory
$ script/plugin install git://github.com/sstephenson/sprockets-rails.git

Configure Routes

When you install sprockets-rails, a new controller is added to your application, called "SprocketsController". This controller has a single "show" method, which concatenates and serves your JavaScript files.

Before we can use the controller, however, we need to add a route. Once this is done, the routes.rb file should look something like this:

ActionController::Routing::Routes.draw do |map|

# Set up routes for the sprockets-rails plugin
SprocketsApplication.routes(map) 

Move your javascript files

In a normal Rails application, your JavaScript files live in the public/javascripts directory. In an application using Sprockets, they live in app/javascripts.

$ mv public/JavaScripts/* app/JavaScripts

Replace your old JavaScript include tags

Our old JavaScript files are going to be concatinated into a single file located at /sprockets.js. So we'll need to replace any old calls to "JavaScript_include_tag" with a single call to "sprockets_include_tag". When it's done, our layout looks like this:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
  <title>Rails Application With Built-In Helpdesk</title>
  <link rel="stylesheet" href="/stylesheets/main.css" type="text/css" media="screen, projection" />
  <%= sprockets_include_tag %>
  <%= yield :head %>
</head>

Now you can start a test server, and open "http://localhost:3000/sprockets.js" in a web browser. You should see that it contains all of your JavaScript files, only concatenated.

More control

If your as big of a JavaScript juggernaut as I am, you'll probably find yourself with certain large chunks of code that need to be included in one page but not in another. There's no reason to include your WISWYG editor in pages that don't use it.

Unfortunately, at the time of this writing, the sprockets-rails plugin only lets you specify one JavaScript build. Sure, you can still include the special content using plain old JS include tags. But that's not wat we want.

If you'd like to see how you can modify the sprockets-rails plugin to achieve this, you can find an example at http://github.com/starrhorne/sprockets-rails/tree/master

Creating a Sprockets Plugin

Imagine for a moment, that you're Sergey Brin, co-founder of Google. The market's in a slump. Your stock is down. You've decided that the only sure fire-way to bring it up is to reimplement Google in ruby on rails. (Hey - it could work!)

You need to implement an autocomplete feature. And you: need it to be able to easily share it between all of the Google products. In this case, a Sprockets plugin makes a lot of sense.

The anatomy of a Sprockets plugin Sprockets plugins are pretty simple. In its simplest form, a sprockets plugin consists of a single file in "vendor/sprockets/plugin_name/src"

The first thing we need to do is create a directory:

$mkdir -p vendor/sprockets/autocomplete/src

And now, I'll just move my autocomplete scripts there:

$mv <some directory>/autocomplete.js vendor/sprockets/autocomplete/src
$mv <some directory>/menu.js vendor/sprockets/autocomplete/src

Using the Sprockets plugin in your rails application We've created our plugin, but it's not much use until we tell sprockets to load it. To do this, we open "app/javascripts/application.js" and add the following line to the top:

//= require <autocomplete>

Declaring the plugin's dependencies Our imaginary autocomplete.js file depends on both prototype.js and menu.js. To declare these dependencies, we'll add a few lines to the top of autocomplete.js

//= require <prototype>
//= require "menu"

Now, whenever the autocomplete.js file is included by Sprockets, it will automatically include the other files as well.

Note that the <> syntax causes Sprockets to search all directories in the include path, while the "" syntax searches the source file's local directory.

Adding assets to the plugin Now it's time to set up the assets for the auto-complete plugin. These consist of two files: autocomplete.css and autocomplete_corners.png.

We'll create a directory to hold these. Sprockets doesn't require you to use any particular directory structure for assets, so we'll keep it simple:

$ mkdir vendor/sprockets/autocomplete/resources
$ mv <some directory>/autocomplete.css vendor/sprockets/autocomplete/resources
$ mv <some directory>/autocomplete_corners.png vendor/sprockets/autocomplete/resources

We'll add another line to autocomplete.js, to let Sprockets know about the resources

//= provide "../resources"

Now we can run "rake sprockets:install_assets" to copy all of the files in "vendor/sprockets/autocomplete/resources" to "public/home"

To learn more... I hope that this article has piqued your curiosity about Sprockets. Indeed, we've only scratched the surface. If you'd like to learn more, stop by the project home page at http://getsprockets.com

John Nunemaker posted a great article on using uploadify with rails.

Uploadify is a sweet jquery plugin that lets you do multiple file upload with progress indicators and everything, using the magic of flash.

But you know that magic always comes with a price. Specifically: flash sucks. It sucks in part because it doesn't let you send cookies back with your request. John's post showed you a way around that.

But it also sucks because it doesn't send HTTP request headers either. Which means no RJS, kiddies. Unless you use a workaround like so.

In your middleware:


require 'rack/utils'

class FlashSessionCookieMiddleware
  def initialize(app, session_key = '_session_id')
    @app = app
    @session_key = session_key
  end

  def call(env)
    if env['HTTP_USER_AGENT'] =~ /^(Adobe|Shockwave) Flash/
      params = ::Rack::Utils.parse_query(env['QUERY_STRING'])

      unless params[@session_key].nil?
        env['HTTP_COOKIE'] = "#{@session_key}=#{params[@session_key]}".freeze
      end

      # Here's the goods
      unless params['_http_accept'].nil?
        env['HTTP_ACCEPT'] = "#{params['_http_accept']}".freeze
      end
    end

    @app.call(env)
  end
end

And in your javascript, you'll need to add a bit of code to eval the response:

$("#uploadify").uploadify({

  // Eval the response
  'onComplete'      : function(a, b, c, response){ eval(response) },

  // And set the accept header to application/javascript
  'scriptData'      : {
    '<%= session_key_name %>' : '<$%= u cookies[session_key_name] %>',
    '_http_accept': "application/javascript"
  }, 

  ... Other options removed for clarity ...

});      

Now, bingo! You can just use responds_to like normal.

Static site gen is all the rage these days. And why not? Wordpress is horrible. It makes me gag just thinking about it.

Out of all the static site generators, Jekyll gets all the love. Atmittedly, it's a cool little app, perfect for Github Pages, and it couldn't be easier to use. But here's the problem: it was built to compile untrusted content.

Jekyll uses a combination of liquid templates, markdown, and html. While these are great for keeping the idiots at bay, they're not so much fun if you want to hack around, add categories, pull in your twitter feed, etc.

In fact, if you want extend to jekyll, you have to fork the entire project! Now where would we be if you had to fork rails every time you wanted to add a new feature. It's madness!!!

Now, that said, let me introduce you to my little friend, my BFF, my new soul-mate: Nanoc.

How Nanoc is Exactly Like a Ninja

Nanoc is a static site generator, a lot like Jekyll, but about a billion times more flexible. Why?

  • ERB! - Ruby in your pages. In case you need to generate a uuid or something. 2b0250e0-6cf4-012d-0934-60fb42ef776a. Holy @#$#$ I JUST DID!

  • Plugins!! - You get a nice /lib directory, just like rails.

  • Compilation Rules!!! - You have super fine-grained control over how your pages are laid out and compiled.

So enough propaganda, let's make a blog:

Install Nanoc

Nanoc comes as a gem, so it's a cinch to install.

  sudo gem install nanoc3 BlueCloth coderay

Take it For a Spin

Now that you have the nanoc gem installed, try typing "nanoc3" in the terminal. You should see a nice help screen.

starr@gummy ~> nanoc3
nanoc, a static site compiler written in Ruby.

Available commands:

    autocompile          start the autocompiler
    compile              compile items of this site
    create_item          create a item
    create_layout        create a layout
    create_site          create a site
    help                 show help for a command
    info                 show info about available plugins
    update               update the data stored by the data source to a newer version

Global options:

    -d --debug           enable debugging (set $DEBUG to true)
    -h --help            show this help message and quit
    -C --no-color        disable color
    -V --verbose         make nanoc output more detailed
    -v --version         show version information and quit
    -w --warn            enable warnings

Create the Empty Site

Much like rails, nanoc will create a project directory for you:


starr@gummy ~/d/projects> nanoc3 create_site blog
      create  config.yaml
      create  Rakefile
      create  Rules
      create  content/index.yaml
      create  content/index.html
      create  layouts/default.yaml
      create  layouts/default.html
Created a blank nanoc site at 'blog'. Enjoy!

That was easy. Now you can fire up the test server and try it out. When nanoc3 is run in 'autocompile' mode, it will detect any changes you make to the source files, and automatically recompile the site, placing the results in the "output" directory. It also provides a simple webrick server for serving up the compiled files, accessible at http://localhost:3000/

You can edit the index.html file and see the changes in your browser if you like.

Some Silly Config

You'll need to add a base_url to your config file like so:

--- 
data_sources: 
- items_root: /
layouts_root: /
type: filesystem_compact
output_dir: output
base_url: http://mysite.com

Help Me Out, Bro!

Nanoc comes with a nice system for creating extensions. It even comes with lots of them, but for some reason these aren't enabled by default. So lets enable them all. Edit your lib/default.rb file so that it looks like this:

# lib/default.rb

include Nanoc3::Helpers::Blogging
include Nanoc3::Helpers::Breadcrumbs
include Nanoc3::Helpers::Capturing
include Nanoc3::Helpers::Filtering
include Nanoc3::Helpers::HTMLEscape
include Nanoc3::Helpers::LinkTo
include Nanoc3::Helpers::Rendering
include Nanoc3::Helpers::Tagging
include Nanoc3::Helpers::Text
include Nanoc3::Helpers::XMLSitemap

# This is just some syntactical sugar that we use later
# Don't worry about it for now.
class Nanoc3::Item
  def content(opts = {})
    opts[:rep] ||= :default
    opts[:snapshot] ||= :last
    reps.find { |r| r.name == opts[:rep] }.content_at_snapshot(opts[:snapshot])
  end

  def name
    identifier.split("/").last 
  end
end

Nanoc will require every file in the lib directory, so that's where you can place any of your own hacks.

Creating your first post

If you were using wordpress (blech), blog posts would live in a database. With nanoc, it's all files. For each blog post or other page, there are two files: item_name.html, which includes the item content, and a file item_name.yaml, which includes metadata such as the title, date published, etc.

starr@gummy ~/d/p/blog> nanoc3 create_item posts/first_post
      create  content/posts/first_post.yaml
      create  content/posts/first_post.html
An item has been created at /posts/first_post/.

We'll be using the Blogging helper, so our yaml file has to contain certain data. Open it up and make it look like so:

--- 
kind: article
created_at: 9/20/2009
title: "Hello World!"

Laid out

Each blog post will be run through one or more "layouts", according to rules that you specify. We'll cover the details in a second, but for now, edit layouts/default.html.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title><%= @item[:title] %></title>
  </head>
  <body>

  <%= yield %>

  </body>
</html>

Great Scot! That looks like a rails layout!

Now, if you run "nanoc3 compile && nanoc3 aco', and navigate to http://localhost:3000/posts/first_post/ you should see your first post. It isn't pretty, but we're getting there.

Another Layout for Posts

It's probably not a good idea just to dump the post body into the main layout. We might want to show the author name, or comments. So lets make a new layout called "post".

starr@gummy ~/d/p/blog> nanoc3 create_layout post
      create  layouts/post.yaml
      create  layouts/post.html
A layout has been created at /post/.

Open up layouts/post.html, and make it so:

<h1><%= @item[:title] %></h1>
<h2>Written at: <%= @item[:created_at] %></h2>
<p><%= yield %></p>

Rules

We have to tell nanoc to use the post layout for any posts. There's a file called Rules in your project directory that handles all of that.

Open it, and add the following rule to the top of the file:

compile '/posts/*' do
  rep.filter :erb
  rep.filter :bluecloth
  rep.layout 'post'
  rep.layout 'default'
end

Let's unfold this, because it's pretty cool. This rule says that for all of the pages in the /post directory:

  1. Process any embedded ruby
  2. Process any markdown in the item
  3. Run the item through the "post" layout
  4. Run the result of #3 through the "default" layout.

You have complete control of over the layout and processing of each post. Want to run HTML Tidy on all of your output? No problem. Decided you want to convert all emoticons into rad animated gifs? It's cake.

And - oh yes - this is only the beginning.

The index (or is it?)

We need to make an index page. At this point, you might be thinking to yourself, "Wow, and index page. That's boring." You might think that...if you're a FOOL!

The index page is just a clever ploy to allow us to talk about another cool nanoc feature: Representations, or if you are cool, "reps".

Think about it like this. Your blog post is just a chunk of data. We've already defined one representation of that data, the "full post" page. For the index, we'll define a second, "post summary", representation of the data.

So the first thing to do is to make a new layout called "post_summary".

starr@gummy ~/d/p/blog> nanoc3 create_layout post_summary
      create  layouts/post_summary.yaml
      create  layouts/post_summary.html
A layout has been created at /post_summary/.

Open up post_summary.html, and add this:

<%= link_to(@item[:title], @item.content) %>
<%= @item[:created_at] %> 

Now we need to add a new rule to define the representation:

route '/posts/*', :rep => :summary do
  nil # Don't create files for summaries
end

compile '/posts/*', :rep => :summary do
  rep.filter :erb
  rep.filter :bluecloth
  rep.layout 'post_summary'
end

The "route" statement just tells nanoc that post summaries should not be written to disk. They're just for internal use.

And the "compile" statement works very similarly to the one for full posts. The only difference is that it doesn't run the results through the "default" layout. This makes sense, because the summary is just a little fragment of HTML that we'll use in our index.

Open up the existing index.html file and make it look like so

<% @site.sorted_articles[0, 10].each do |post| %>
  <%= post.content :rep => :summary %>
<% end %>

And BAM! There you have it. A blog.

Resources

Ok, so it's a pretty lame blog. But it's not too hard to flesh out. Be sure to check out: * The users guide * The API docs