The Issue

In yesterday’s article, I described our need for latest article in a category, and a little plug-in that Bill Tozier had found and adopted. I had built some rspec around that, which sucked me in to thinking that I knew what I was doing.

Faithful readers (both of you) will recall that we wanted to pull the “view” part of what we’d done back into the definition of a page, and leave only the finding of the article in question to the plug-in. Once the day was over, I came to my senses and realized that, for me at least, using TDD or BDD ideas to find out how some legacy code works is not a good use of those ideas. I use TDD/BDD to create new code. To me it is almost as if they help me discover what the new code should be. And I use the tools to lock in behavior: once something works, I might write some tests that make sure it’ll keep working. Finally, those tests can serve as useful documentation.

What they’re not good for, for me, is digging around to understand legacy code.

So I spent much of yesterday afternoon and evening digging and experimenting, and Bill and I worked on it this morning. And now, the results of that trial:

The Result

I’ll cut to what we got. There were some iterations to get there, all in a morning, but here’s the result.

We have a new plug-in, named RegardingCategoryBlock. It implements a block tag named regarding that you use like this:

{% regarding %}
    {% for article in category %}
    <p>The blurb for {{ article.title }} is {{ article.blurb }}.</p>
{% endregarding %}

This little patch of Liquid will define an array variable category which contains all the articles in category, sorted newest first. The variable is automatically and always named category. There’s no way as yet to change it. Inside a block regarding a category, the variable category contains all the articles in that category. Voila.

Then we do a standard Liquid for loop over that array, printing a line for each article. We’d use this, typically, in something like an index page.

It turns out you can get a specific article, such as the most recent, this way:

{% regarding %}
    {% assign most_recent = category | first %}
    <p>The blurb for the newest article, {{ most_recent.title }}, is {{ most_recent.blurb }}.</p>
{% endregarding %}

So that locution can build the “most recent” bits at the top of the index page, while other Liquid list functions will let us display all the articles, or the third through seventh, or whatever.

Very neat.

The Code

I’ll show you the code in a moment, but a story goes with it. I spent hours yesterday trying to make this work. I was able to get a string defined, such as "lasttitle", that you could substitute in using {{ lasttitle }}. At first, even that was beyond me. I was printing all kinds of things out of the Jekyll/Liquid context but couldn’t see how to put anything back.

Then, Googling desperately, as one does, I found an article by Daniel Cooper, that showed me the trick shown here:

class RegardingCategoryBlock < Liquid::Block
  def initialize(tag_name, block_parameter, tokens)
    @category_name = block_parameter.strip

  def render(context)
    pages = context.registers[:site].pages
    labeled_pages = pages.find_all {|page| !page["categories"].nil?}
    filtered_pages = labeled_pages.find_all {|page|  page["categories"].include? @category_name}
    filtered_pages.sort_by! {|page| page["date"]}

    context.stack do
      context['category'] = filtered_pages
      render_all(@nodelist, context)

Liquid::Template.register_tag('regarding', RegardingCategoryBlock)

The big finding is the context.stack do. Apparently, that pushes down the stack that Liquid uses to understand things, and in our case adds a new key-value pair, 'category' => filtered_pages and then renders the @nodelist. I found out elsewhere that @nodelist contains a parsed representation of what’s inside our block tag. As you can see from the code just above that do, filtered_pages is just an array of pages. We used standard Ruby code to get all the pages, that have categories, that have our desired category, sorted by date, and reversed.

Like so many other things when using some inherited system like this one, this is simply cargo cult. If you arrange the runway just right, the planes come. It’s a magic incantation. If you say it just right, it works.

You’ll note that member variable @category_name, which is just the second input parameter from the initialize. It turns out to be whatever string of characters follows the regarding keyword. We strip the ends of extra whitespace and use it.

Speaking of cargo cult, I’m pretty sure that render_all returns the text that’s supposed to go on the page, and that it’s just returned, as things are in Ruby, by dropping off the end. I’m pretty sure that you could capture that string and do something else to it first, like converting all occurrences of “blue” to “azure” with a substitute. I emphasize pretty sure: I’ve not tried it.

But WOW!

This is downright cool. We can get a list of all articles in a category, and use it in an index, at least a simple one. We can get the most recent article, or the oldest, or a range of articles. As it stands, this is pretty handy.

Wait! Don’t answer yet! With a little more work, you also get the ability to list articles in several categories and who knows what all, just by parsing the input parameter. And I suspect we can also do a lot at the Liquid level. We haven’t tried nesting these deals. What if you did regarding, snatched category into another variable, then did a nested regarding Kate Oneal? Could you join those two arrays, re-sort them and print a merged list? Perhaps.

Suffice to say that this finding supports more than we need right now, and the technique itself opens doors for other tags that do useful things.

Being the Agile kinda guy that I am, I’m not going to “generalize” this capability until I have a story that requires it. But doors are definitely open.

So, a good finding. And if you’re in need of a new block tag while using Jekyll and Liquid, this article might help you. And with that sentence I just wrote, Google will probably index it soon!

One more thing to be thankful for, and tomorrow is US Thanksgiving! Nom!