<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.giraffesoft.ca/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">
 
  <title>giraffesoft, the blog</title>
  <subtitle>thoughts and musings by the giraffes</subtitle>
  
  <link href="http://http://giraffesoft.ca/" />
  <updated>2010-02-21T22:59:17-08:00</updated>
  <author>
    <name>James Golick, Daniel Haran and François Beausoleil</name>
    <email>hello@giraffesoft.ca</email>
  </author>
  <id>http://http://giraffesoft.ca/</id>
  
  <atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.giraffesoft.ca/giraffesoft-the-blog" /><feedburner:info uri="giraffesoft-the-blog" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry>
    <title>Label Your Checkboxes and Radio Buttons to Ease Your User's Woes</title>
    <link href="http://feeds.giraffesoft.ca/~r/giraffesoft-the-blog/~3/m55GgGQYFT4/label-your-checkboxes-and-radio-buttons-to-ease-your-users-woes.html" />
    <id>tag:http://giraffesoft.ca,2009-04-30:1241096835</id>
    <updated>2009-04-30T06:07:15-07:00</updated>
    <content type="html">&lt;p&gt;Whenever I build forms, I'm very careful to always label all my fields.  After all, it's good for accessibility, right? Well, it turns out it's also good for usability.  Very, very good.&lt;/p&gt;

&lt;p&gt;I'm getting older, and as I'm getting older, I get lazier.  I like things to be bold and easily reachable.  Small targets annoy me these days.  Let's take for example &lt;a href="http://campfirenow.com/"&gt;Campfire&lt;/a&gt;'s login page:&lt;/p&gt;
&lt;br/&gt;

&lt;div class=""&gt;
  &lt;img src="/blog/2009/04/30/campfire-login.png" width="520" height="207" alt="A screenshot of Campfire's login page"/&gt;
&lt;/div&gt;

&lt;p&gt;Let's say you login and want to be remembered.  Nothing easier, right?  Simply click the checkbox.  But you're lazy, and there's this huge label right next to it.  Go ahead, click the label.  What?  Nothing happens?  You're right: nothing happens.  Here's their code:&lt;/p&gt;

&lt;pre class="twilight"&gt;&lt;span class="Keyword"&gt;&amp;lt;&lt;/span&gt;dd&lt;span class="Keyword"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="Keyword"&gt;&amp;lt;&lt;/span&gt;input type&lt;span class="Keyword"&gt;=&lt;/span&gt;&lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;checkbox&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; name&lt;span class="Keyword"&gt;=&lt;/span&gt;&lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;remember&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; value&lt;span class="Keyword"&gt;=&lt;/span&gt;&lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;1&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class="Keyword"&gt;/&lt;/span&gt;&lt;span class="Keyword"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="Variable"&gt;Remember&lt;/span&gt; me
&lt;span class="Keyword"&gt;&amp;lt;&lt;/span&gt;&lt;span class="Keyword"&gt;/&lt;/span&gt;dd&lt;span class="Keyword"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;

&lt;p&gt;Do you see the problem?  I do.  The label is not a label.  It's not attached to the checkbox.  For fun, let's count the number of pixels available to click on: 12&amp;times;12, or 144 pixels.&lt;/p&gt;

&lt;p&gt;Compare that to the following image:&lt;/p&gt;

&lt;br/&gt;
&lt;div class=""&gt;
  &lt;img src="/blog/2009/04/30/remember-me-with-highlight.png" width="325" height="21" alt="Another remember me with the clickable area highlighted.  This shows 325&amp;times;21, or 6825 pixels."/&gt;
&lt;/div&gt;

&lt;p&gt;In this particular image, we have an area that's 325&amp;times;21, or 6825 pixels available for clicking on.  But this picture is a lie.  The full width of my browser's window is availble for clicking on.&lt;/p&gt;

&lt;p&gt;To be clear, don't do this:&lt;/p&gt;

&lt;pre class="twilight"&gt;&lt;span class="Keyword"&gt;&amp;lt;&lt;/span&gt;form action&lt;span class="Keyword"&gt;=&lt;/span&gt;&lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;#&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; method&lt;span class="Keyword"&gt;=&lt;/span&gt;&lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;get&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span class="Keyword"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="Keyword"&gt;&amp;lt;&lt;/span&gt;div &lt;span class="Keyword"&gt;class&lt;/span&gt;&lt;span class="Keyword"&gt;=&lt;/span&gt;&lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;row&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span class="Keyword"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="Keyword"&gt;&amp;lt;&lt;/span&gt;!&lt;span class="Keyword"&gt;-&lt;/span&gt;&lt;span class="Keyword"&gt;-&lt;/span&gt; &lt;span class="Variable"&gt;Don&lt;/span&gt;&lt;span class="String"&gt;&lt;span class="String"&gt;'&lt;/span&gt;t do this! --&amp;gt;&lt;/span&gt;
&lt;span class="String"&gt;    &amp;lt;p&amp;gt;Name&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="String"&gt;    &amp;lt;input id=&amp;quot;name&amp;quot; name=&amp;quot;name&amp;quot;/&amp;gt;&lt;/span&gt;
&lt;span class="String"&gt;  &amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="String"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/pre&gt;

&lt;p&gt;Instead, correctly set the field's &lt;code&gt;id&lt;/code&gt; and label's &lt;code&gt;for&lt;/code&gt; attributes:&lt;/p&gt;

&lt;pre class="twilight"&gt;&lt;span class="Keyword"&gt;&amp;lt;&lt;/span&gt;form action&lt;span class="Keyword"&gt;=&lt;/span&gt;&lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;#&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; method&lt;span class="Keyword"&gt;=&lt;/span&gt;&lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;get&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span class="Keyword"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="Keyword"&gt;&amp;lt;&lt;/span&gt;div &lt;span class="Keyword"&gt;class&lt;/span&gt;&lt;span class="Keyword"&gt;=&lt;/span&gt;&lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;row&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span class="Keyword"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="Keyword"&gt;&amp;lt;&lt;/span&gt;label &lt;span class="Keyword"&gt;for&lt;/span&gt;&lt;span class="Keyword"&gt;=&lt;/span&gt;&lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;name&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span class="Keyword"&gt;&amp;gt;&lt;/span&gt;&lt;span class="Variable"&gt;Name&lt;/span&gt;&lt;span class="Keyword"&gt;&amp;lt;&lt;/span&gt;&lt;span class="Keyword"&gt;/&lt;/span&gt;label&lt;span class="Keyword"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="Keyword"&gt;&amp;lt;&lt;/span&gt;input id&lt;span class="Keyword"&gt;=&lt;/span&gt;&lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;name&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; name&lt;span class="Keyword"&gt;=&lt;/span&gt;&lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;name&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span class="Keyword"&gt;/&lt;/span&gt;&lt;span class="Keyword"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="Keyword"&gt;&amp;lt;&lt;/span&gt;&lt;span class="Keyword"&gt;/&lt;/span&gt;div&lt;span class="Keyword"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="Keyword"&gt;&amp;lt;&lt;/span&gt;&lt;span class="Keyword"&gt;/&lt;/span&gt;form&lt;span class="Keyword"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;

&lt;p&gt;If you want to know how significant this is, please have a look at a toy page I made: &lt;a href="/blog/2009/04/30/form.html"&gt;example labelling versus clickable area&lt;/a&gt;.&lt;p&gt;

&lt;p&gt;Very important note: I did not select Campfire out of spite.  I contacted Campfire's support department and was told that I should simply click the checkbox.  To be fair, all other 37signals applications have correctly attached the label to the input.  Other applications and websites have the very same problem:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://useit.mondosearch.com/cgi-bin/MsmFind.exe?QUERY=label+html+element"&gt;Jakob Nielsen's UseIt.com search page&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://signin.ebay.ca/ws/eBayISAPI.dll?SignIn"&gt;eBay.ca Sign in page&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://accesd.desjardins.com/en/accesd"&gt;Desjardins' AccèsD&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="http://rubyforge.org/"&gt;RubyForge's delete a project form&lt;/a&gt;, although in this instance it might be argued that this is a &lt;em&gt;security&lt;/em&gt; feature&amp;hellip;  Pointed out by &lt;a href="http://twitter.com/jtrupiano/status/1652226283"&gt;John Trupiano through Twitter&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This list was compiled in April 2009, after 20 minutes of searching.&lt;/p&gt;

&lt;h3&gt;More Information on the LABEL element and its use&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://www.usability.com.au/resources/forms.cfm"&gt;Accessible Forms&lt;/a&gt;, specifically &lt;em&gt;Labelling Form Elements&lt;/em&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;a href="http://www.useit.com/alertbox/20040927.html"&gt;Checkboxes vs. Radio Buttons&lt;/a&gt;, specifically look for #11 titled &lt;em&gt;Let users select an option by clicking on either the button/box itself or its label&lt;/em&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;a href="http://webdesign.about.com/od/forms/a/aa050707.htm"&gt;What Makes a Web Form Usable or Unusable&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="http://www.w3.org/TR/html401/"&gt;HTML 4.01 Specification&lt;/a&gt;, particularly &lt;a href="http://www.w3.org/TR/html401/interact/forms.html#h-17.9"&gt;Section 17.9: Labels&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="http://en.wikipedia.org/wiki/Fitts'_law"&gt;Fitts' Law&lt;/a&gt; on Wikipedia, which states that the bigger and closer an object is, the faster it is to use&lt;/li&gt;
&lt;/ul&gt;
&lt;img src="http://feeds.feedburner.com/~r/giraffesoft-the-blog/~4/m55GgGQYFT4" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://giraffesoft.ca/blog/2009/04/30/label-your-checkboxes-and-radio-buttons-to-ease-your-users-woes.html</feedburner:origLink></entry>
  
  <entry>
    <title>Acceptance Test Driven Development (ATDD)</title>
    <link href="http://feeds.giraffesoft.ca/~r/giraffesoft-the-blog/~3/bcblRi7nWQU/acceptance-test-driven-development-atdd.html" />
    <id>tag:http://giraffesoft.ca,2009-03-15:1237141489</id>
    <updated>2009-03-15T11:24:49-07:00</updated>
    <content type="html">&lt;p&gt;
  Test Driven Development (TDD) is this idea that you should write a unit test before you write the code. You run the test to make sure that it fails. Then, you implement the code necessary to make it pass. Rinse and repeat until the software is finished.
&lt;/p&gt;

&lt;p&gt;
  One of the most popular themes among TDD nay-sayers is the ad-absurdum argument. They don't know what they building. They're agile. So, how can they write their tests first?
&lt;/p&gt;

&lt;p&gt;
  As any test-driven developer knows, though, you don't write &lt;i&gt;all&lt;/i&gt; your tests first. I know this. But, yet, when I first heard about ATDD, I, too, fell in to this fallacious trap.
&lt;/p&gt;

&lt;h3&gt;So, what is ATDD?&lt;/h3&gt;

&lt;p&gt;
  It's rather simple, really. Before you implement a feature (or piece of a feature), you write an acceptance test. Then, you drop down to lower level tests (unit, functional) to TDD the code necessary to make the acceptance test go green.
&lt;/p&gt; 

&lt;p&gt;
  I've only been ATDD'ing for a short while, but I'm already seeing a huge improvement in the quality of the stuff that I'm putting out. Much in the same way that unit testing can be looked at as a design practice for your code, ATDD can be looked at as a design practice for your high level functionality.
&lt;/p&gt;

&lt;p&gt;
  Starting with an acceptance test forces you to think at the level of the user before you think about your code. What is the workflow going to be? What should the user see? What buttons, fields, or other functional elements will be necessary and how will they be labeled? In my experience, these are often questions that are asked after the models and controllers (or equivalent units) are already built for that feature.
&lt;/p&gt;

&lt;p&gt;
  Answering those questions first seems to be a great way to better understand what you're building. Those are the &lt;i&gt;only&lt;/i&gt; relevant issues in the eyes of your users. The answers to those questions are the application. Everything else, including the unit tests, is just the implementation.
&lt;/p&gt;

&lt;p&gt;
  Oh yeah, and a comprehensive set of acceptance tests is pretty useful too.
&lt;/p&gt;

&lt;img src="http://feeds.feedburner.com/~r/giraffesoft-the-blog/~4/bcblRi7nWQU" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://giraffesoft.ca/blog/2009/03/15/acceptance-test-driven-development-atdd.html</feedburner:origLink></entry>
  
  <entry>
    <title>4 Core Competencies of Great Hackers</title>
    <link href="http://feeds.giraffesoft.ca/~r/giraffesoft-the-blog/~3/1qOKebKXeYQ/4-core-competencies-of-great-hackers.html" />
    <id>tag:http://giraffesoft.ca,2009-03-10:1236695966</id>
    <updated>2009-03-10T07:39:26-07:00</updated>
    <content type="html">&lt;p&gt;
  At giraffesoft we do a lot of &lt;strike&gt;pair-programming&lt;/strike&gt; collaborative development, both internally and when hired to coach or audit teams. After working closely with dozens of programmers, it's time to draw some conclusions.
&lt;/p&gt;

&lt;p&gt;
   Here are 4 traits that appear to be universal amongst great hackers:
&lt;/p&gt;

&lt;h3&gt;Typing over 60 wpm&lt;/h3&gt;

&lt;p&gt;
  Thousands of hours spent on IM and IRC make you a faster typist. It's a skill that can be learned in school or with specialized software in 20 to 40 hours. So why are so many programmers glancing at their keyboards for common symbols?
&lt;/p&gt;

&lt;p&gt;
  Bringing up typing speed in conversation meets some resistance amongst some programmers. I've tried arguing that the micro-interruptions to your programming can knock you out of your flow and kill your productivity. For those that do not listen, I offer the example of people that google hotmail. It seems noobish, doesn't it?
&lt;/p&gt;

&lt;h3&gt;Owning the command-line&lt;/h3&gt;

&lt;p&gt;
  Some developers need a pretty GUI or IDE to wrap every command-line utility. During one audit at a consulting company I saw a programmer wrestle with his IDE for 10 minutes in front of two CTOs. Verdict? His IDE didn't yet have support for the last version of Ruby on Rails, which we needed for specific functionality (ActiveResource). He was stuck.
&lt;/p&gt;

&lt;p&gt;
  In contrast, every great hacker we know has a customized environment, and they routinely compose unix commands. Even those stuck on Windows because of corporate policy still run Linux at home or on their servers.
&lt;/p&gt;

&lt;h3&gt;Knowing your editor&lt;/h3&gt;

&lt;p&gt;
  Let's not pick editor fights: the only 3 editors we know to be used by great hackers are TextMate, vim and emacs. The most productive hackers will often customize their editor heavily.
&lt;/p&gt;

&lt;p&gt;
  We haven't met a single great hacker that relied on an IDE, although we hear they exist.
&lt;/p&gt;

&lt;h3&gt;Reading code: owning your tools&lt;/h3&gt;

&lt;p&gt;
  We've noticed more people ignoring documentation and going straight to the source code. It's a great start.
&lt;/p&gt;

&lt;p&gt;
  Diving into a large code base to quickly understand what it does is a skill that can be practiced. People do get much faster at it with a little bit of practice. So fast that using a debugger to step through code seems slow by comparison; I've been chided by one hacker for using a debugger. "It's slow and it doesn't help you understand the code." Ouch.
&lt;/p&gt;

&lt;p&gt;
  Most hackers we know routinely read the source of plugins and frameworks before using them. By choosing better architected tools it's easier to add new functionality or discover security flaws. They also appear rather nonchalant about modifying framework code. They understand what it does, and feel free to modify it as if it was their code.
&lt;/p&gt;


&lt;h3&gt;But, but...&lt;/h3&gt;

&lt;p&gt;
  The list isn't final or perfect, just the result of observations and discussions. A check-list for self-improvement.
&lt;/p&gt;
  
&lt;p&gt;
  I don't pretend to fit the description of "great hacker" that I've offered up: I don't read enough code, haven't done much work customizing my editor and barely know some CLI utilities like awk or sed. While I won't claim that learning these 4 skills will make you a great hacker, it seems unlikely you can become one without them.
&lt;/p&gt;
&lt;img src="http://feeds.feedburner.com/~r/giraffesoft-the-blog/~4/1qOKebKXeYQ" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://giraffesoft.ca/blog/2009/03/10/4-core-competencies-of-great-hackers.html</feedburner:origLink></entry>
  
  <entry>
    <title>How to Use TimelineFu: Building an activity feed for a social application</title>
    <link href="http://feeds.giraffesoft.ca/~r/giraffesoft-the-blog/~3/hz72Hk6dlbw/how-to-use-timelinefu-building-an-activity-feed-for-a-social-application.html" />
    <id>tag:http://giraffesoft.ca,2009-02-26:1235657149</id>
    <updated>2009-02-26T06:05:49-08:00</updated>
    <content type="html">&lt;p&gt;In this article, I would like to show how to use TimelineFu to build a timeline.  This is the kind of list of events that we often see on dashboards, such as on GitHub:&lt;/p&gt;

&lt;p&gt;&lt;img src="http://giraffesoft.ca/blog/2009/02/26/github-timeline.png" alt="An example of a timeline from GitHub" /&gt;&lt;/p&gt;

&lt;p&gt;I will assume you already know your way around Rails, so I won't describe the basic steps in excruciating details.&lt;/p&gt;

&lt;p&gt;Let's start by making the application and models (I'm using Rails 2.2.2).  This is a social application, with people and relationships between people as the basic entities in the domain.&lt;/p&gt;

&lt;pre class="twilight"&gt;
$ rails myfriends
$ cd myfriends
$ git init
$ echo &amp;quot;*.log&amp;quot;     &amp;gt; log/.gitignore
$ echo &amp;quot;*.sqlite3&amp;quot; &amp;gt; db/.gitignore
$ echo &amp;quot;*&amp;quot;         &amp;gt; tmp/.gitignore
$ git add .
$ git commit --message &amp;quot;Initial commit&amp;quot;
$ script/generate scaffold person name:string
$ script/generate scaffold relationship person_id:integer friend_id:integer
$ rake db:migrate
$ git add .
$ git commit --message &amp;quot;Scaffolded models&amp;quot;
&lt;/pre&gt;


&lt;p&gt;Time to write a minimal set of tests.&lt;/p&gt;

&lt;pre class="twilight"&gt;
&lt;span class="Keyword"&gt;class&lt;/span&gt; &lt;span class="Entity"&gt;PersonTest&lt;span class="EntityInheritedClass"&gt; &lt;span class="EntityInheritedClass"&gt;&amp;lt;&lt;/span&gt; ActiveSupport::TestCase&lt;/span&gt;&lt;/span&gt;
  should_have_valid_fixtures
  should_require_attributes &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;name&lt;/span&gt;
  should_allow_mass_assignment_of &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;name&lt;/span&gt;
  should_not_allow_mass_assignment_of &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;relationships&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;friends&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;relationship_ids&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;friend_ids&lt;/span&gt;
  should_have_many &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;relationships&lt;/span&gt;
  should_have_many &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;friends&lt;/span&gt;
&lt;span class="Keyword"&gt;end&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;And make those pass:&lt;/p&gt;

&lt;pre class="twilight"&gt;
&lt;span class="Keyword"&gt;class&lt;/span&gt; &lt;span class="Entity"&gt;Person&lt;span class="EntityInheritedClass"&gt; &lt;span class="EntityInheritedClass"&gt;&amp;lt;&lt;/span&gt; ActiveRecord::Base&lt;/span&gt;&lt;/span&gt;
  validates_presence_of &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;name&lt;/span&gt;
  attr_accessible &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;name&lt;/span&gt;

  has_many &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;relationships&lt;/span&gt;
  has_many &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;friends&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;through&lt;/span&gt; =&amp;gt; &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;relationships&lt;/span&gt;
&lt;span class="Keyword"&gt;end&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;Turning to relationships:&lt;/p&gt;

&lt;pre class="twilight"&gt;
&lt;span class="Keyword"&gt;class&lt;/span&gt; &lt;span class="Entity"&gt;RelationshipTest&lt;span class="EntityInheritedClass"&gt; &lt;span class="EntityInheritedClass"&gt;&amp;lt;&lt;/span&gt; ActiveSupport::TestCase&lt;/span&gt;&lt;/span&gt;
  should_have_valid_fixtures
  should_require_attributes &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;friend_id&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;person_id&lt;/span&gt;
  should_allow_mass_assignment_of &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;friend&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;person&lt;/span&gt;
  should_not_allow_mass_assignment_of &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;friend_id&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;person_id&lt;/span&gt;

  should_belong_to &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;person&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;friend&lt;/span&gt;
&lt;span class="Keyword"&gt;end&lt;/span&gt;
&lt;/pre&gt;


&lt;pre class="twilight"&gt;
&lt;span class="Keyword"&gt;class&lt;/span&gt; &lt;span class="Entity"&gt;Relationship&lt;span class="EntityInheritedClass"&gt; &lt;span class="EntityInheritedClass"&gt;&amp;lt;&lt;/span&gt; ActiveRecord::Base&lt;/span&gt;&lt;/span&gt;
  belongs_to &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;person&lt;/span&gt;
  belongs_to &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;friend&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;class_name&lt;/span&gt; =&amp;gt; &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;Person&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;

  validates_presence_of &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;person_id&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;friend_id&lt;/span&gt;
  attr_accessible &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;person&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;friend&lt;/span&gt;
&lt;span class="Keyword"&gt;end&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;All pretty standard stuff, really.&lt;/p&gt;

&lt;h3&gt;Installation&lt;/h3&gt;

&lt;p&gt;Installation is pretty straightforward:&lt;/p&gt;

&lt;pre class="twilight"&gt;
$ script/plugin install git://github.com/giraffesoft/timeline_fu.git
&lt;/pre&gt;


&lt;p&gt;TimelineFu comes with a generator that will do the basics for you.  The generator is completely optional.  See the README for details.&lt;/p&gt;

&lt;pre class="twilight"&gt;
$ script/generate timeline_fu
     exists  db/migrate
     create  db/migrate/20090225144815_create_timeline_events.rb
     create  app/models/timeline_event.rb
&lt;/pre&gt;


&lt;p&gt;Let's see what TimelineFu generated for us:&lt;/p&gt;

&lt;pre class="twilight"&gt;
&lt;span class="Keyword"&gt;class&lt;/span&gt; &lt;span class="Entity"&gt;CreateTimelineEvents&lt;span class="EntityInheritedClass"&gt; &lt;span class="EntityInheritedClass"&gt;&amp;lt;&lt;/span&gt; ActiveRecord::Migration&lt;/span&gt;&lt;/span&gt;
  &lt;span class="Keyword"&gt;def&lt;/span&gt; &lt;span class="Entity"&gt;self.up&lt;/span&gt;
    create_table &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;timeline_events&lt;/span&gt; &lt;span class="Keyword"&gt;do &lt;/span&gt;|&lt;span class="Variable"&gt;t&lt;/span&gt;|
      t.&lt;span class="Entity"&gt;string&lt;/span&gt;   &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;event_type&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;subject_type&lt;/span&gt;,  &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;actor_type&lt;/span&gt;,  &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;secondary_subject_type&lt;/span&gt;
      t.&lt;span class="Entity"&gt;integer&lt;/span&gt;               &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;subject_id&lt;/span&gt;,    &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;actor_id&lt;/span&gt;,    &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;secondary_subject_id&lt;/span&gt;
      t.&lt;span class="Entity"&gt;timestamps&lt;/span&gt;
    &lt;span class="Keyword"&gt;end&lt;/span&gt;
  &lt;span class="Keyword"&gt;end&lt;/span&gt;
 
  &lt;span class="Keyword"&gt;def&lt;/span&gt; &lt;span class="Entity"&gt;self.down&lt;/span&gt;
    drop_table &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;timeline_events&lt;/span&gt;
  &lt;span class="Keyword"&gt;end&lt;/span&gt;
&lt;span class="Keyword"&gt;end&lt;/span&gt;

&lt;span class="Keyword"&gt;class&lt;/span&gt; &lt;span class="Entity"&gt;TimelineEvent&lt;span class="EntityInheritedClass"&gt; &lt;span class="EntityInheritedClass"&gt;&amp;lt;&lt;/span&gt; ActiveRecord::Base&lt;/span&gt;&lt;/span&gt;
  belongs_to &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;actor&lt;/span&gt;,              &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;polymorphic&lt;/span&gt; =&amp;gt; &lt;span class="Constant"&gt;true&lt;/span&gt;
  belongs_to &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;subject&lt;/span&gt;,            &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;polymorphic&lt;/span&gt; =&amp;gt; &lt;span class="Constant"&gt;true&lt;/span&gt;
  belongs_to &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;secondary_subject&lt;/span&gt;,  &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;polymorphic&lt;/span&gt; =&amp;gt; &lt;span class="Constant"&gt;true&lt;/span&gt;
&lt;span class="Keyword"&gt;end&lt;/span&gt;
&lt;/pre&gt;


&lt;h3&gt;TimelineFu's glossary / terminology&lt;/h3&gt;

&lt;p&gt;A basic timeline event looks like this:&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;François added you as a friend&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;The actor is the person or thing that did an action.  The subject is what was acted against.  The secondary subject is supporting documentation about the subject.  And the event type is pretty self-explanatory.  The example above looks like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Actor: François&lt;/li&gt;
&lt;li&gt;Subject: You (a Person instance)&lt;/li&gt;
&lt;li&gt;Secondary Subject: the Relationship instance&lt;/li&gt;
&lt;li&gt;Event Type: "added you as a friend" (friended)&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Another example:&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;François posted a new comment on "How to use TimelineFu to build timelines"&lt;/p&gt;&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Actor: François&lt;/li&gt;
&lt;li&gt;Subject: Comment&lt;/li&gt;
&lt;li&gt;Secondary Subject: the Post&lt;/li&gt;
&lt;li&gt;Event Type: "posted a new comment" (commented)&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Let's begin by writing a failing test.&lt;/p&gt;

&lt;pre class="twilight"&gt;
&lt;span class="Keyword"&gt;class&lt;/span&gt; &lt;span class="Entity"&gt;RelationshipTest&lt;span class="EntityInheritedClass"&gt; &lt;span class="EntityInheritedClass"&gt;&amp;lt;&lt;/span&gt; Test::Unit::TestCase&lt;/span&gt;&lt;/span&gt;
  context &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;Adding another person as a friend&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class="Keyword"&gt;do&lt;/span&gt;
    setup &lt;span class="Keyword"&gt;do&lt;/span&gt;
      &lt;span class="Variable"&gt;&lt;span class="Variable"&gt;@&lt;/span&gt;francois&lt;/span&gt; &lt;span class="Keyword"&gt;=&lt;/span&gt; &lt;span class="Entity"&gt;person&lt;/span&gt;(&lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;francois&lt;/span&gt;)
      &lt;span class="Variable"&gt;&lt;span class="Variable"&gt;@&lt;/span&gt;james&lt;/span&gt;    &lt;span class="Keyword"&gt;=&lt;/span&gt; &lt;span class="Entity"&gt;person&lt;/span&gt;(&lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;james&lt;/span&gt;)
      &lt;span class="Variable"&gt;&lt;span class="Variable"&gt;@&lt;/span&gt;francois&lt;/span&gt;.&lt;span class="Entity"&gt;friends&lt;/span&gt; &lt;span class="Keyword"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="Variable"&gt;&lt;span class="Variable"&gt;@&lt;/span&gt;james&lt;/span&gt;
    &lt;span class="Keyword"&gt;end&lt;/span&gt;

    should_change &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;TimelineEvent.count&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;by&lt;/span&gt; =&amp;gt; &lt;span class="Constant"&gt;1&lt;/span&gt;
  &lt;span class="Keyword"&gt;end&lt;/span&gt;
&lt;span class="Keyword"&gt;end&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;Run and see the test fail.  Open up TimelineFu's &lt;a href="http://github.com/giraffesoft/timeline_fu/tree/master"&gt;README&lt;/a&gt; and look at how timeline events are created.  Hint: it's called &lt;code&gt;#fires&lt;/code&gt;.&lt;/p&gt;

&lt;pre class="twilight"&gt;
&lt;span class="Keyword"&gt;class&lt;/span&gt; &lt;span class="Entity"&gt;Relationship&lt;span class="EntityInheritedClass"&gt; &lt;span class="EntityInheritedClass"&gt;&amp;lt;&lt;/span&gt; ActiveRecord::Base&lt;/span&gt;&lt;/span&gt;
  fires &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;friended&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;on&lt;/span&gt; =&amp;gt; &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;create&lt;/span&gt;
&lt;span class="Keyword"&gt;end&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;If you were to look at the attributes of the timeline_event as it was created, here's what you would find:&lt;/p&gt;

&lt;pre class="twilight"&gt;
&lt;span class="Keyword"&gt;-&lt;/span&gt;&lt;span class="Keyword"&gt;-&lt;/span&gt;&lt;span class="Keyword"&gt;-&lt;/span&gt; !&lt;span class="MetaTagAll"&gt;&lt;span class="MetaTagInline"&gt;ruby/object&lt;/span&gt;&lt;span class="MetaTagAll"&gt;:&lt;/span&gt;&lt;/span&gt;TimelineEvent&lt;span class="InvalidDeprecated"&gt; &lt;/span&gt;
&lt;span class="MetaTagAll"&gt;&lt;span class="MetaTagInline"&gt;attributes&lt;/span&gt;&lt;span class="MetaTagAll"&gt;:&lt;/span&gt; &lt;/span&gt;
  &lt;span class="MetaTagAll"&gt;&lt;span class="MetaTagInline"&gt;id&lt;/span&gt;&lt;span class="MetaTagAll"&gt;:&lt;/span&gt; &lt;/span&gt;&lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;1&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
  &lt;span class="String"&gt;&lt;span class="MetaTagInline"&gt;event_type&lt;span class="MetaTagInline"&gt;:&lt;/span&gt;&lt;/span&gt; &lt;span class="String"&gt;friended&lt;/span&gt;&lt;/span&gt;
  &lt;span class="MetaTagAll"&gt;&lt;span class="MetaTagInline"&gt;actor_type&lt;/span&gt;&lt;span class="MetaTagAll"&gt;:&lt;/span&gt; &lt;/span&gt;
  &lt;span class="MetaTagAll"&gt;&lt;span class="MetaTagInline"&gt;actor_id&lt;/span&gt;&lt;span class="MetaTagAll"&gt;:&lt;/span&gt; &lt;/span&gt;
  &lt;span class="String"&gt;&lt;span class="MetaTagInline"&gt;subject_type&lt;span class="MetaTagInline"&gt;:&lt;/span&gt;&lt;/span&gt; &lt;span class="String"&gt;Relationship&lt;/span&gt;&lt;/span&gt;
  &lt;span class="MetaTagAll"&gt;&lt;span class="MetaTagInline"&gt;subject_id&lt;/span&gt;&lt;span class="MetaTagAll"&gt;:&lt;/span&gt; &lt;/span&gt;&lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;1&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
  &lt;span class="MetaTagAll"&gt;&lt;span class="MetaTagInline"&gt;secondary_subject_type&lt;/span&gt;&lt;span class="MetaTagAll"&gt;:&lt;/span&gt; &lt;/span&gt;
  &lt;span class="MetaTagAll"&gt;&lt;span class="MetaTagInline"&gt;secondary_subject_id&lt;/span&gt;&lt;span class="MetaTagAll"&gt;:&lt;/span&gt; &lt;/span&gt;
  &lt;span class="String"&gt;&lt;span class="MetaTagInline"&gt;created_at&lt;span class="MetaTagInline"&gt;:&lt;/span&gt;&lt;/span&gt; &lt;span class="String"&gt;2009-02-25 15:39:54&lt;/span&gt;&lt;/span&gt;
  &lt;span class="String"&gt;&lt;span class="MetaTagInline"&gt;updated_at&lt;span class="MetaTagInline"&gt;:&lt;/span&gt;&lt;/span&gt; &lt;span class="String"&gt;2009-02-25 15:39:54&lt;/span&gt;&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;Notice that actor and secondary subject are &lt;code&gt;nil&lt;/code&gt;.  This is because we haven't specified any options to the &lt;code&gt;#fires&lt;/code&gt; call.  Let's remedy the situation with a new set of failing tests:&lt;/p&gt;

&lt;pre class="twilight"&gt;
&lt;span class="Keyword"&gt;class&lt;/span&gt; &lt;span class="Entity"&gt;RelationshipTest&lt;span class="EntityInheritedClass"&gt; &lt;span class="EntityInheritedClass"&gt;&amp;lt;&lt;/span&gt; ActiveSupport::TestCase&lt;/span&gt;&lt;/span&gt;
  context &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;Adding another person as a friend&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class="Keyword"&gt;do&lt;/span&gt;
    context &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;the timeline event&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class="Keyword"&gt;do&lt;/span&gt;
      setup &lt;span class="Keyword"&gt;do&lt;/span&gt;
        &lt;span class="Variable"&gt;&lt;span class="Variable"&gt;@&lt;/span&gt;event&lt;/span&gt; &lt;span class="Keyword"&gt;=&lt;/span&gt; &lt;span class="Support"&gt;TimelineEvent&lt;/span&gt;.&lt;span class="Entity"&gt;last&lt;/span&gt;
      &lt;span class="Keyword"&gt;end&lt;/span&gt;

      should &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;set the actor to be the person who created the friendship&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class="Keyword"&gt;do&lt;/span&gt;
        assert_equal &lt;span class="Variable"&gt;&lt;span class="Variable"&gt;@&lt;/span&gt;francois&lt;/span&gt;, &lt;span class="Variable"&gt;&lt;span class="Variable"&gt;@&lt;/span&gt;event&lt;/span&gt;.&lt;span class="Entity"&gt;actor&lt;/span&gt;
      &lt;span class="Keyword"&gt;end&lt;/span&gt;

      should &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;set the subject to the relationship&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class="Keyword"&gt;do&lt;/span&gt;
        assert_equal &lt;span class="Variable"&gt;&lt;span class="Variable"&gt;@&lt;/span&gt;francois&lt;/span&gt;.&lt;span class="Entity"&gt;relationships&lt;/span&gt;.&lt;span class="Entity"&gt;first&lt;/span&gt;, &lt;span class="Variable"&gt;&lt;span class="Variable"&gt;@&lt;/span&gt;event&lt;/span&gt;.&lt;span class="Entity"&gt;subject&lt;/span&gt;
      &lt;span class="Keyword"&gt;end&lt;/span&gt;

      should &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;set the secondary subject to the new friend&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class="Keyword"&gt;do&lt;/span&gt;
        assert_equal &lt;span class="Variable"&gt;&lt;span class="Variable"&gt;@&lt;/span&gt;james&lt;/span&gt;, &lt;span class="Variable"&gt;&lt;span class="Variable"&gt;@&lt;/span&gt;event&lt;/span&gt;.&lt;span class="Entity"&gt;secondary_subject&lt;/span&gt;
      &lt;span class="Keyword"&gt;end&lt;/span&gt;

      should &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;set the event type to be 'friended'&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class="Keyword"&gt;do&lt;/span&gt;
        assert_equal &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;friended&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class="Variable"&gt;&lt;span class="Variable"&gt;@&lt;/span&gt;event&lt;/span&gt;.&lt;span class="Entity"&gt;event_type&lt;/span&gt;
      &lt;span class="Keyword"&gt;end&lt;/span&gt;
    &lt;span class="Keyword"&gt;end&lt;/span&gt;
  &lt;span class="Keyword"&gt;end&lt;/span&gt;
&lt;span class="Keyword"&gt;end&lt;/span&gt;
&lt;/pre&gt;


&lt;pre class="twilight"&gt;
&lt;span class="Keyword"&gt;class&lt;/span&gt; &lt;span class="Entity"&gt;Relationship&lt;span class="EntityInheritedClass"&gt; &lt;span class="EntityInheritedClass"&gt;&amp;lt;&lt;/span&gt; ActiveRecord::Base&lt;/span&gt;&lt;/span&gt;
  fires &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;friended&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;on&lt;/span&gt; =&amp;gt; &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;create&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;actor&lt;/span&gt; =&amp;gt; &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;person&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;subject&lt;/span&gt; =&amp;gt; &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;friend&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;secondary_subject&lt;/span&gt; =&amp;gt; &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;self&lt;/span&gt;
&lt;span class="Keyword"&gt;end&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;It's nice to know when someone friends you, but isn't it even better to know when they don't want you as friends anymore?  This way, you'll know not to give them gifts when it's their birthday.  A new failing test:&lt;/p&gt;

&lt;pre class="twilight"&gt;
&lt;span class="Keyword"&gt;class&lt;/span&gt; &lt;span class="Entity"&gt;RelationshipTest&lt;span class="EntityInheritedClass"&gt; &lt;span class="EntityInheritedClass"&gt;&amp;lt;&lt;/span&gt; Test::Unit::TestCase&lt;/span&gt;&lt;/span&gt;
  context &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;Deleting the relationship&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class="Keyword"&gt;do&lt;/span&gt;
    setup &lt;span class="Keyword"&gt;do&lt;/span&gt;
      &lt;span class="Entity"&gt;relationships&lt;/span&gt;(&lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;james_to_francois&lt;/span&gt;).&lt;span class="Entity"&gt;destroy&lt;/span&gt;
    &lt;span class="Keyword"&gt;end&lt;/span&gt;

    should_change &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;TimelineEvent.count&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;by&lt;/span&gt; =&amp;gt; &lt;span class="Constant"&gt;1&lt;/span&gt;

    context &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;the timeline event&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class="Keyword"&gt;do&lt;/span&gt;
      setup &lt;span class="Keyword"&gt;do&lt;/span&gt;
        &lt;span class="Variable"&gt;&lt;span class="Variable"&gt;@&lt;/span&gt;event&lt;/span&gt; &lt;span class="Keyword"&gt;=&lt;/span&gt; &lt;span class="Support"&gt;TimelineEvent&lt;/span&gt;.&lt;span class="Entity"&gt;last&lt;/span&gt;
      &lt;span class="Keyword"&gt;end&lt;/span&gt;

      should &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;set the actor to the person who destroyed the friendship&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class="Keyword"&gt;do&lt;/span&gt;
        assert_equal &lt;span class="Entity"&gt;people&lt;/span&gt;(&lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;james&lt;/span&gt;), &lt;span class="Variable"&gt;&lt;span class="Variable"&gt;@&lt;/span&gt;event&lt;/span&gt;.&lt;span class="Entity"&gt;actor&lt;/span&gt;
      &lt;span class="Keyword"&gt;end&lt;/span&gt;

      should &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;set the subject to the relationship&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class="Keyword"&gt;do&lt;/span&gt;
&lt;span class="Comment"&gt;        &lt;span class="Comment"&gt;#&lt;/span&gt; It's been deleted...  Oops!&lt;/span&gt;
      &lt;span class="Keyword"&gt;end&lt;/span&gt;

      should &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;set the secondary subject to the old friend&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class="Keyword"&gt;do&lt;/span&gt;
        assert_equal &lt;span class="Entity"&gt;people&lt;/span&gt;(&lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;francois&lt;/span&gt;), &lt;span class="Variable"&gt;&lt;span class="Variable"&gt;@&lt;/span&gt;event&lt;/span&gt;.&lt;span class="Entity"&gt;secondary_subject&lt;/span&gt;
      &lt;span class="Keyword"&gt;end&lt;/span&gt;

      should &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;set the event type to 'unfriended'&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class="Keyword"&gt;do&lt;/span&gt;
        assert_equal &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;unfriended&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class="Variable"&gt;&lt;span class="Variable"&gt;@&lt;/span&gt;event&lt;/span&gt;.&lt;span class="Entity"&gt;event_type&lt;/span&gt;
      &lt;span class="Keyword"&gt;end&lt;/span&gt;
    &lt;span class="Keyword"&gt;end&lt;/span&gt;
  &lt;span class="Keyword"&gt;end&lt;/span&gt;
&lt;span class="Keyword"&gt;end&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;And the implementation:&lt;/p&gt;

&lt;pre class="twilight"&gt;
&lt;span class="Keyword"&gt;class&lt;/span&gt; &lt;span class="Entity"&gt;Relationship&lt;span class="EntityInheritedClass"&gt; &lt;span class="EntityInheritedClass"&gt;&amp;lt;&lt;/span&gt; ActiveRecord::Base&lt;/span&gt;&lt;/span&gt;
  fires &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;unfriended&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;on&lt;/span&gt; =&amp;gt; &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;destroy&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;actor&lt;/span&gt; =&amp;gt; &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;person&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;secondary_subject&lt;/span&gt; =&amp;gt; &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;friend&lt;/span&gt;
&lt;span class="Keyword"&gt;end&lt;/span&gt;
&lt;/pre&gt;


&lt;h3&gt;Rendering the events feed&lt;/h3&gt;

&lt;p&gt;We need a way of getting recent events for any person.  Let's analyze what we want to achieve.  If someone adds me as a friend, I want that event to appear in my timeline (&lt;code&gt;timeline_events WHERE subject == self&lt;/code&gt;).  I also want to see events of my friends, such as "James added Mat as a friend" (&lt;code&gt;timeline_events WHERE actor IN (my friends)&lt;/code&gt;).  Let's write a first failing test:&lt;/p&gt;

&lt;pre class="twilight"&gt;
&lt;span class="Keyword"&gt;class&lt;/span&gt; &lt;span class="Entity"&gt;PersonTest&lt;span class="EntityInheritedClass"&gt; &lt;span class="EntityInheritedClass"&gt;&amp;lt;&lt;/span&gt; ActiveSupport::TestCase&lt;/span&gt;&lt;/span&gt;
  context &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;A new person&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class="Keyword"&gt;do&lt;/span&gt;
    context &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;where James friends self&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class="Keyword"&gt;do&lt;/span&gt;
      setup &lt;span class="Keyword"&gt;do&lt;/span&gt;
        &lt;span class="Entity"&gt;people&lt;/span&gt;(&lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;james&lt;/span&gt;).&lt;span class="Entity"&gt;friends&lt;/span&gt; &lt;span class="Keyword"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="Variable"&gt;&lt;span class="Variable"&gt;@&lt;/span&gt;person&lt;/span&gt;
      &lt;span class="Keyword"&gt;end&lt;/span&gt;

      should_change &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;@person.recent_events.count&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;by&lt;/span&gt; =&amp;gt; &lt;span class="Constant"&gt;1&lt;/span&gt;
    &lt;span class="Keyword"&gt;end&lt;/span&gt;
  &lt;span class="Keyword"&gt;end&lt;/span&gt;
&lt;span class="Keyword"&gt;end&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;Let's do the simplest thing that could possibly work:&lt;/p&gt;

&lt;pre class="twilight"&gt;
&lt;span class="Keyword"&gt;class&lt;/span&gt; &lt;span class="Entity"&gt;Person&lt;span class="EntityInheritedClass"&gt; &lt;span class="EntityInheritedClass"&gt;&amp;lt;&lt;/span&gt; ActiveRecord::Base&lt;/span&gt;&lt;/span&gt;
  has_many &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;recent_events&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;as&lt;/span&gt; =&amp;gt; &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;subject&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;class_name&lt;/span&gt; =&amp;gt; &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;TimelineEvent&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;order&lt;/span&gt; =&amp;gt; &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;timeline_events.created_at DESC&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class="Keyword"&gt;end&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;That works just fine.  But how do I get to the events of my friends?  My friends events are the ones where actor_id is one of my friends.  Let's write another failing test:&lt;/p&gt;

&lt;pre class="twilight"&gt;
&lt;span class="Keyword"&gt;class&lt;/span&gt; &lt;span class="Entity"&gt;PersonTest&lt;span class="EntityInheritedClass"&gt; &lt;span class="EntityInheritedClass"&gt;&amp;lt;&lt;/span&gt; ActiveSupport::TestCase&lt;/span&gt;&lt;/span&gt;
  context &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;A new person&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class="Keyword"&gt;do&lt;/span&gt;
    context &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;where James friends self&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class="Keyword"&gt;do&lt;/span&gt;
      context &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;where James friends someone else when James is my friend&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class="Keyword"&gt;do&lt;/span&gt;
        setup &lt;span class="Keyword"&gt;do&lt;/span&gt;
          &lt;span class="Variable"&gt;&lt;span class="Variable"&gt;@&lt;/span&gt;person&lt;/span&gt;.&lt;span class="Entity"&gt;friends&lt;/span&gt; &lt;span class="Keyword"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="Entity"&gt;people&lt;/span&gt;(&lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;james&lt;/span&gt;)
          &lt;span class="Entity"&gt;people&lt;/span&gt;(&lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;james&lt;/span&gt;).&lt;span class="Entity"&gt;friends&lt;/span&gt; &lt;span class="Keyword"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="Support"&gt;Person&lt;/span&gt;.&lt;span class="Entity"&gt;create!&lt;/span&gt;(&lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;name&lt;/span&gt; =&amp;gt; &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;Daniel&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;)
        &lt;span class="Keyword"&gt;end&lt;/span&gt;

        should_change &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;@person.recent_events.count&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;to&lt;/span&gt; =&amp;gt; &lt;span class="Constant"&gt;2&lt;/span&gt;
      &lt;span class="Keyword"&gt;end&lt;/span&gt;
    &lt;span class="Keyword"&gt;end&lt;/span&gt;
  &lt;span class="Keyword"&gt;end&lt;/span&gt;
&lt;span class="Keyword"&gt;end&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;And since we want both where subject = X or actor = X, we must use the :finder_sql option of has_many:&lt;/p&gt;

&lt;pre class="twilight"&gt;
&lt;span class="Keyword"&gt;class&lt;/span&gt; &lt;span class="Entity"&gt;Person&lt;span class="EntityInheritedClass"&gt; &lt;span class="EntityInheritedClass"&gt;&amp;lt;&lt;/span&gt; ActiveRecord::Base&lt;/span&gt;&lt;/span&gt;
  &lt;span class="Variable"&gt;RECENT_EVENTS_CONDITION&lt;/span&gt; &lt;span class="Keyword"&gt;=&lt;/span&gt; 
    &lt;span class="String"&gt;&lt;span class="String"&gt;'&lt;/span&gt;(subject_id = #{id} AND subject_type = &lt;span class="StringConstant"&gt;\'&lt;/span&gt;Person&lt;span class="StringConstant"&gt;\'&lt;/span&gt;)&lt;/span&gt;
&lt;span class="String"&gt;    OR (actor_type = &lt;span class="StringConstant"&gt;\'&lt;/span&gt;Person&lt;span class="StringConstant"&gt;\'&lt;/span&gt;&lt;/span&gt;
&lt;span class="String"&gt;    AND actor_id IN (SELECT friend_id&lt;/span&gt;
&lt;span class="String"&gt;                    FROM relationships&lt;/span&gt;
&lt;span class="String"&gt;                    WHERE relationships.person_id = #{id}))&lt;span class="String"&gt;'&lt;/span&gt;&lt;/span&gt;
    has_many &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;recent_events&lt;/span&gt;,
      &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;class_name&lt;/span&gt;  =&amp;gt; &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;TimelineEvent&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;,
      &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;finder_sql&lt;/span&gt;  =&amp;gt; &lt;span class="String"&gt;&lt;span class="String"&gt;'&lt;/span&gt;SELECT timeline_events.* FROM timeline_events&lt;/span&gt;
&lt;span class="String"&gt;                      WHERE &lt;span class="String"&gt;'&lt;/span&gt;&lt;/span&gt; &lt;span class="Keyword"&gt;+&lt;/span&gt; &lt;span class="Variable"&gt;RECENT_EVENTS_CONDITION&lt;/span&gt; &lt;span class="Keyword"&gt;+&lt;/span&gt; &lt;span class="String"&gt;&lt;span class="String"&gt;'&lt;/span&gt;&lt;/span&gt;
&lt;span class="String"&gt;                      ORDER BY timeline_events.created_at DESC&lt;span class="String"&gt;'&lt;/span&gt;&lt;/span&gt;,
      &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;counter_sql&lt;/span&gt; =&amp;gt; &lt;span class="String"&gt;&lt;span class="String"&gt;'&lt;/span&gt;SELECT COUNT(*) FROM timeline_events&lt;/span&gt;
&lt;span class="String"&gt;                      WHERE &lt;span class="String"&gt;'&lt;/span&gt;&lt;/span&gt; &lt;span class="Keyword"&gt;+&lt;/span&gt; &lt;span class="Variable"&gt;RECENT_EVENTS_CONDITION&lt;/span&gt;
&lt;span class="Keyword"&gt;end&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;Now we turn our attention to the user interface.  How do we present this information in a nice way to the user?  At giraffesoft, we do not write view tests.  Views change too often for the tests to be useful.  We do use Cucumber though.  Anyway, do the simplest thing that could possibly work:&lt;/p&gt;

&lt;pre class="twilight"&gt;
&lt;span class="Keyword"&gt;&amp;lt;&lt;/span&gt;&lt;span class="Keyword"&gt;%=&lt;/span&gt; render_timeline &lt;span class="Variable"&gt;&lt;span class="Variable"&gt;@&lt;/span&gt;person&lt;/span&gt;.&lt;span class="Entity"&gt;recent_events&lt;/span&gt; &lt;span class="String"&gt;&lt;span class="String"&gt;%&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;Since TimelineEvent has a field called event_type, let's use that to render a different partial.&lt;/p&gt;

&lt;pre class="twilight"&gt;
&lt;span class="Keyword"&gt;module&lt;/span&gt; &lt;span class="Entity"&gt;RenderHelper&lt;/span&gt;
  &lt;span class="Keyword"&gt;def&lt;/span&gt; &lt;span class="Entity"&gt;render_timeline&lt;/span&gt;(&lt;span class="Variable"&gt;events&lt;/span&gt;)
    events.&lt;span class="Entity"&gt;map&lt;/span&gt; &lt;span class="Keyword"&gt;do &lt;/span&gt;|&lt;span class="Variable"&gt;event&lt;/span&gt;|
      &lt;span class="Entity"&gt;render&lt;/span&gt;(&lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;partial&lt;/span&gt; =&amp;gt; &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;timeline_events/&lt;span class="StringEmbeddedSource"&gt;&lt;span class="StringEmbeddedSource"&gt;#{&lt;/span&gt;event&lt;span class="StringEmbeddedSource"&gt;&lt;span class="StringEmbeddedSource"&gt;.&lt;/span&gt;&lt;span class="Entity"&gt;event_type&lt;/span&gt;&lt;/span&gt;&lt;span class="StringEmbeddedSource"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;object&lt;/span&gt; =&amp;gt; event)
    &lt;span class="Keyword"&gt;end&lt;/span&gt;.&lt;span class="Entity"&gt;join&lt;/span&gt;
  &lt;span class="Keyword"&gt;end&lt;/span&gt;
&lt;span class="Keyword"&gt;end&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;I cannot use &lt;code&gt;render :partial =&amp;gt; events&lt;/code&gt; here, because all the events have the same type, namely TimelineEvent.  I want to render a different partial depending on the &lt;strong&gt;value&lt;/strong&gt; of event_type.&lt;/p&gt;

&lt;h3&gt;Introducing the timeline_fu Example App&lt;/h3&gt;

&lt;p&gt;The code for this article is available on github, as the &lt;a href="http://github.com/giraffesoft/timeline_fu-example/tree/master"&gt;timeline_fu-example&lt;/a&gt; application.  It is a sample / starter application for rendering event feeds. Fork away!&lt;/p&gt;
&lt;img src="http://feeds.feedburner.com/~r/giraffesoft-the-blog/~4/hz72Hk6dlbw" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://giraffesoft.ca/blog/2009/02/26/how-to-use-timelinefu-building-an-activity-feed-for-a-social-application.html</feedburner:origLink></entry>
  
  <entry>
    <title>[FLOSS Week]: timeline_fu: activity feeds made awesome</title>
    <link href="http://feeds.giraffesoft.ca/~r/giraffesoft-the-blog/~3/RZL50XnHqRA/floss-week-timeline-fu-activity-feeds-made-awesome.html" />
    <id>tag:http://giraffesoft.ca,2009-02-20:1235155577</id>
    <updated>2009-02-20T10:46:17-08:00</updated>
    <content type="html">&lt;p&gt;
  &lt;i&gt;This release announcement is part of &lt;a href="http://giraffesoft.ca/blog/2009/02/16/floss-week.html"&gt;FLOSS week&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
  If you're working on a webapp these days, chances are you've needed to build an activity feed or two. There are a few approaches floating around &amp;mdash; mostly involving observers. Not a fan of observers and needing something reusable, we built our own.
&lt;/p&gt;

&lt;p&gt;
  To get started with &lt;a href="http://github.com/giraffesoft/timeline_fu/tree/master"&gt;timeline_fu&lt;/a&gt;, run the generator.
&lt;/p&gt;

&lt;pre class="twilight"&gt;script&lt;span class="Keyword"&gt;/&lt;/span&gt;generate timeline_fu &lt;span class="Keyword"&gt;&amp;amp;&amp;amp;&lt;/span&gt; rake db&lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;migrate&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;
  You'll get a model called TimelineEvent and the necessary migration. Then, anywhere you need to fire a timeline event, simply declare it.
&lt;/p&gt;

&lt;pre class="twilight"&gt;&lt;span class="Keyword"&gt;class&lt;/span&gt; &lt;span class="Entity"&gt;Post&lt;/span&gt;
  belongs_to &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;creator&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;class_name&lt;/span&gt; =&amp;gt; &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;User&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;

  fires &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;created&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;actor&lt;/span&gt; =&amp;gt; &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;creator&lt;/span&gt;,
                  &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;on&lt;/span&gt;    =&amp;gt; &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;create&lt;/span&gt;
&lt;span class="Keyword"&gt;end&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;
  This will fire a TimelineEvent called :created in an after_create callback on the Post model. It's properties will be as follows.
&lt;/p&gt;

&lt;pre class="twilight"&gt;&lt;span class="Variable"&gt;&lt;span class="Variable"&gt;@&lt;/span&gt;post&lt;/span&gt; &lt;span class="Keyword"&gt;=&lt;/span&gt; &lt;span class="Support"&gt;Post&lt;/span&gt;.&lt;span class="Entity"&gt;create&lt;/span&gt;(&lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;creator&lt;/span&gt; =&amp;gt; current_user, ...)

&lt;span class="Variable"&gt;&lt;span class="Variable"&gt;@&lt;/span&gt;timeline_event&lt;/span&gt;.&lt;span class="Entity"&gt;event_type&lt;/span&gt; &lt;span class="Comment"&gt;&lt;span class="Comment"&gt;#&lt;/span&gt;=&amp;gt; :created&lt;/span&gt;
&lt;span class="Variable"&gt;&lt;span class="Variable"&gt;@&lt;/span&gt;timeline_event&lt;/span&gt;.&lt;span class="Entity"&gt;actor&lt;/span&gt;      &lt;span class="Comment"&gt;&lt;span class="Comment"&gt;#&lt;/span&gt;=&amp;gt; @post.creator&lt;/span&gt;
&lt;span class="Variable"&gt;&lt;span class="Variable"&gt;@&lt;/span&gt;timeline_event&lt;/span&gt;.&lt;span class="Entity"&gt;subject&lt;/span&gt;    &lt;span class="Comment"&gt;&lt;span class="Comment"&gt;#&lt;/span&gt;=&amp;gt; @post&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;
  Then, to display the timeline_events, you'll need an association on your User model. We've frequently defined this as a has_many :through followed items, like how you might imagine it's implemented in the github activity feed. Then, in your dashboards/show.html.erb, you'd have something like this.
&lt;/p&gt;

&lt;pre class="twilight"&gt;&lt;span class="EmbeddedSourceBright"&gt;&lt;span class="EmbeddedSourceBright"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="Variable"&gt;&lt;span class="Variable"&gt;@&lt;/span&gt;user&lt;/span&gt;&lt;span class="EmbeddedSourceBright"&gt;&lt;span class="EmbeddedSourceBright"&gt;.&lt;/span&gt;&lt;span class="Entity"&gt;timeline_events&lt;/span&gt;&lt;/span&gt;&lt;span class="EmbeddedSourceBright"&gt;&lt;span class="EmbeddedSourceBright"&gt;.&lt;/span&gt;&lt;span class="Entity"&gt;each&lt;/span&gt;&lt;/span&gt; &lt;span class="Keyword"&gt;do &lt;/span&gt;&lt;span class="EmbeddedSourceBright"&gt;|&lt;/span&gt;&lt;span class="Variable"&gt;e&lt;/span&gt;&lt;span class="EmbeddedSourceBright"&gt;|&lt;/span&gt;  &lt;span class="EmbeddedSourceBright"&gt;-%&amp;gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class="EmbeddedSourceBright"&gt;&lt;span class="EmbeddedSourceBright"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="SupportFunction"&gt;render&lt;/span&gt; &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;partial&lt;/span&gt; &lt;span class="EmbeddedSourceBright"&gt;=&amp;gt;&lt;/span&gt; &lt;/span&gt;
&lt;span class="EmbeddedSourceBright"&gt;    &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;dashboards/timeline_events/&lt;span class="StringEmbeddedSource"&gt;&lt;span class="StringEmbeddedSource"&gt;#{&lt;/span&gt;e&lt;span class="StringEmbeddedSource"&gt;&lt;span class="StringEmbeddedSource"&gt;.&lt;/span&gt;&lt;span class="Entity"&gt;event_type&lt;/span&gt;&lt;/span&gt;&lt;span class="StringEmbeddedSource"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span class="EmbeddedSourceBright"&gt;,&lt;/span&gt; &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;object&lt;/span&gt; &lt;span class="EmbeddedSourceBright"&gt;=&amp;gt;&lt;/span&gt; e &lt;span class="EmbeddedSourceBright"&gt;%&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class="EmbeddedSourceBright"&gt;&lt;span class="EmbeddedSourceBright"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="Keyword"&gt;end&lt;/span&gt; &lt;span class="EmbeddedSourceBright"&gt;%&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;
  That's it! Just add fires declarations wherever you need to add something to the activity feed and the necessary templates and you're good to go.
&lt;/p&gt;

&lt;h3&gt;Get It!&lt;/h3&gt;

&lt;pre class="twilight"&gt;$ sudo gem install giraffesoft&lt;span class="Keyword"&gt;-&lt;/span&gt;timeline_fu
&lt;/pre&gt;
&lt;p&gt;Or fork it on &lt;a href="http://github.com/giraffesoft/timeline_fu"&gt;github&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;
  &lt;i&gt;This release announcement is part of &lt;a href="http://giraffesoft.ca/blog/2009/02/16/floss-week.html"&gt;FLOSS week&lt;/a&gt;. If you're interested in more open source software, consider &lt;a href="http://feeds.giraffesoft.ca/giraffesoft-the-blog"&gt;subscribing&lt;/a&gt; or &lt;a href="http://twitter.com/giraffesoft"&gt;following us on twitter&lt;/a&gt; to get the next announcements asap.&lt;/i&gt;
&lt;/p&gt;
&lt;img src="http://feeds.feedburner.com/~r/giraffesoft-the-blog/~4/RZL50XnHqRA" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://giraffesoft.ca/blog/2009/02/20/floss-week-timeline-fu-activity-feeds-made-awesome.html</feedburner:origLink></entry>
  
  <entry>
    <title>[FLOSS Week]: Cliaws: Easy Command-Line Access to EC2, SQS and S3</title>
    <link href="http://feeds.giraffesoft.ca/~r/giraffesoft-the-blog/~3/keIT3vsg6sw/floss-week-cliaws-easy-command-line-access-to-ec2-sqs-and-s3.html" />
    <id>tag:http://giraffesoft.ca,2009-02-19:1235076431</id>
    <updated>2009-02-19T12:47:11-08:00</updated>
    <content type="html">&lt;p&gt;
  &lt;i&gt;This release announcement is part of &lt;a href="http://giraffesoft.ca/blog/2009/02/16/floss-week.html"&gt;FLOSS week&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;What if you didn't have to find and setup X.509 certificates to quickly use Amazon's &lt;acronym title="Elastic Compute Cloud"&gt;EC2&lt;/acronym&gt;, &lt;acronym title="Simple Queue Service"&gt;SQS&lt;/acronym&gt; and &lt;acronym title="Simple Storage Service"&gt;S3&lt;/acronym&gt; from your applications?  Or from the command-line for that matter?  Well, search no more.&lt;/p&gt;

&lt;pre class="twilight"&gt;
$ clis3 list BUCKET
BUCKET/file0
BUCKET/file1
BUCKET/file2

$ clis3 put localfile BUCKET/newfile

&lt;span class="Comment"&gt;&lt;span class="Comment"&gt;#&lt;/span&gt; Get a secure URL valid for a couple of hours&lt;/span&gt;
$ clis3 url BUCKET/newfile

&lt;span class="Comment"&gt;&lt;span class="Comment"&gt;#&lt;/span&gt; Gets the content of the file, unmodified&lt;/span&gt;
$ clis3 get BUCKET/newfile

&lt;span class="Comment"&gt;&lt;span class="Comment"&gt;#&lt;/span&gt; Gets the contents of the file, but appends a single newline at the end&lt;/span&gt;
$ clis3 cat BUCKET/newfile
&lt;/pre&gt;

&lt;pre class="twilight"&gt;
$ cliec2 list
DNS Name                                           State        Groups
------------------------------------------------------------------------------------------------------------------------
ec2-97-162-23-218.compute-1.amazonaws.com          running      app, web
ec2-73-121-216-98.compute-1.amazonaws.com          running      db
&lt;/pre&gt;

&lt;pre class="twilight"&gt;
&lt;span class="Comment"&gt;&lt;span class="Comment"&gt;#&lt;/span&gt; Create a named queue&lt;/span&gt;
$ clisqs create giraffesoft_power
Queue giraffesoft_power was created.

&lt;span class="Comment"&gt;&lt;span class="Comment"&gt;#&lt;/span&gt; Push a single message to the queue&lt;/span&gt;
$ clisqs push --data &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;message&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; giraffesoft_power
Pushed 7 bytes to queue giraffesoft_power

&lt;span class="Comment"&gt;&lt;span class="Comment"&gt;#&lt;/span&gt; Retrieve one message from the named queue&lt;/span&gt;
$ clisqs pop giraffesoft_power
message
&lt;/pre&gt;

&lt;p&gt;Of course, this library wouldn't be complete if it wasn't also available from your Ruby code.&lt;/p&gt;

&lt;pre class="twilight"&gt;
&lt;span class="Comment"&gt;&lt;span class="Comment"&gt;#&lt;/span&gt; Returns an array of the files available in this bucket&lt;/span&gt;
&lt;span class="Support"&gt;Cliaws&lt;/span&gt;.&lt;span class="Entity"&gt;s3&lt;/span&gt;.&lt;span class="Entity"&gt;list&lt;/span&gt; &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;BUCKET&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;

&lt;span class="Comment"&gt;&lt;span class="Comment"&gt;#&lt;/span&gt; Puts the File, IO stream or String to the named file&lt;/span&gt;
&lt;span class="Support"&gt;Cliaws&lt;/span&gt;.&lt;span class="Entity"&gt;s3&lt;/span&gt;.&lt;span class="Entity"&gt;put&lt;/span&gt; &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;localfile&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;BUCKET/newfile&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;

&lt;span class="Comment"&gt;&lt;span class="Comment"&gt;#&lt;/span&gt; Retrieves the contents from S3&lt;/span&gt;
&lt;span class="Support"&gt;Cliaws&lt;/span&gt;.&lt;span class="Entity"&gt;s3&lt;/span&gt;.&lt;span class="Entity"&gt;get&lt;/span&gt; &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;BUCKET/newfile&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;

&lt;span class="Comment"&gt;&lt;span class="Comment"&gt;#&lt;/span&gt; Returns an Array of Cliaws::Ec2::Instance objects&lt;/span&gt;
&lt;span class="Support"&gt;Cliaws&lt;/span&gt;.&lt;span class="Entity"&gt;ec2&lt;/span&gt;.&lt;span class="Entity"&gt;list&lt;/span&gt;

&lt;span class="Comment"&gt;&lt;span class="Comment"&gt;#&lt;/span&gt; Instances have interesting methods such as #running?&lt;/span&gt;

&lt;span class="Comment"&gt;&lt;span class="Comment"&gt;#&lt;/span&gt; Push a new message to the named queue&lt;/span&gt;
&lt;span class="Support"&gt;Cliaws&lt;/span&gt;.&lt;span class="Entity"&gt;sqs&lt;/span&gt;.&lt;span class="Entity"&gt;push&lt;/span&gt; &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;giraffesoft_power&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, {&lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;adapter&lt;/span&gt; =&amp;gt; &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;mysql&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;username&lt;/span&gt; =&amp;gt; &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;root&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;password&lt;/span&gt; =&amp;gt; &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;tada!!!&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;}.&lt;span class="Entity"&gt;to_yaml&lt;/span&gt;

&lt;span class="Comment"&gt;&lt;span class="Comment"&gt;#&lt;/span&gt; Get said message from the queue&lt;/span&gt;
&lt;span class="Support"&gt;Cliaws&lt;/span&gt;.&lt;span class="Entity"&gt;sqs&lt;/span&gt;.&lt;span class="Entity"&gt;pop&lt;/span&gt; &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;giraffesoft_power&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;/pre&gt;

&lt;h3&gt;Get It!&lt;/h3&gt;

&lt;p&gt;Installation could not be simpler:&lt;/p&gt;

&lt;pre class="twilight"&gt;
$ gem install cliaws
&lt;/pre&gt;

&lt;p&gt;Cliaws depends on 2 environment variables:  &lt;code class="constant"&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; and &lt;code class="constant"&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt;.  Set them to the correct values before calling into the library.  I have mine setup in my &lt;code class="filename"&gt;~/.bashrc&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Fork Cliaws &lt;a href="http://github.com/francois/cliaws/tree/master"&gt;on github&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;
  &lt;i&gt;This release announcement is part of &lt;a href="http://giraffesoft.ca/blog/2009/02/16/floss-week.html"&gt;FLOSS week&lt;/a&gt;. If you're interested in more open source software, consider &lt;a href="http://feeds.giraffesoft.ca/giraffesoft-the-blog"&gt;subscribing&lt;/a&gt; or &lt;a href="http://twitter.com/giraffesoft"&gt;following us on twitter&lt;/a&gt; to get the next announcements asap.&lt;/i&gt;
&lt;/p&gt;
&lt;img src="http://feeds.feedburner.com/~r/giraffesoft-the-blog/~4/keIT3vsg6sw" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://giraffesoft.ca/blog/2009/02/19/floss-week-cliaws-easy-command-line-access-to-ec2-sqs-and-s3.html</feedburner:origLink></entry>
  
  <entry>
    <title>[FLOSS Week]: is_taggable: Tagging That Isn't on Drugs</title>
    <link href="http://feeds.giraffesoft.ca/~r/giraffesoft-the-blog/~3/2qLdzdTX6hY/floss-week-is-taggable-tagging-that-isnt-on-drugs.html" />
    <id>tag:http://giraffesoft.ca,2009-02-18:1234994364</id>
    <updated>2009-02-18T13:59:24-08:00</updated>
    <content type="html">&lt;p&gt;
  &lt;i&gt;This release announcement is part of &lt;a href="http://giraffesoft.ca/blog/2009/02/16/floss-week.html"&gt;FLOSS week&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
  At last! A simple tagging implementation.
&lt;/p&gt;

&lt;p&gt;
  There are several others &amp;mdash; many far more featureful. If you absolutely need a tag cloud and writing one sounds scary, is_taggable may not be for you. At least, not yet.
&lt;/p&gt;

&lt;p&gt;
  is_taggable is just over 100 lines of code (plus tests). It's simple and well-tested. So, extending it is easy.
&lt;/p&gt;

&lt;p&gt;
  So, why did we write it? We needed support for tag 'kinds'. Let's take our polyglot user example:
&lt;/p&gt;

&lt;pre class="twilight"&gt;&lt;span class="Keyword"&gt;class&lt;/span&gt; &lt;span class="Entity"&gt;User&lt;span class="EntityInheritedClass"&gt; &lt;span class="EntityInheritedClass"&gt;&amp;lt;&lt;/span&gt; ActiveRecord::Base&lt;/span&gt;&lt;/span&gt;
  is_taggable &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;tags&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;languages&lt;/span&gt;
&lt;span class="Keyword"&gt;end&lt;/span&gt;

&lt;span class="Support"&gt;User&lt;/span&gt;.&lt;span class="Entity"&gt;new&lt;/span&gt; &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;tag_list&lt;/span&gt; =&amp;gt; &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;rails, giraffesoft&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;language_list&lt;/span&gt; =&amp;gt; &lt;span class="String"&gt;&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;english, french, spanish, latin, esperanto, tlhIngan Hol&lt;span class="String"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;/pre&gt;

&lt;p&gt;
  Of course, it also supports the simplest case:
&lt;/p&gt;

&lt;pre class="twilight"&gt;&lt;span class="Keyword"&gt;class&lt;/span&gt; &lt;span class="Entity"&gt;Post&lt;span class="EntityInheritedClass"&gt; &lt;span class="EntityInheritedClass"&gt;&amp;lt;&lt;/span&gt; ActiveRecord::Base&lt;/span&gt;&lt;/span&gt;
  is_taggable
&lt;span class="Keyword"&gt;end&lt;/span&gt;

&lt;span class="Support"&gt;Post&lt;/span&gt;.&lt;span class="Entity"&gt;new&lt;/span&gt; &lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;tag_list&lt;/span&gt; =&amp;gt; &lt;span class="String"&gt;&lt;span class="String"&gt;'&lt;/span&gt;simple, sane defaults&lt;span class="String"&gt;'&lt;/span&gt;&lt;/span&gt;
&lt;/pre&gt;

&lt;h3&gt;Get it&lt;/h3&gt;

&lt;pre class="twilight"&gt; $ sudo gem install is_taggable 
&lt;/pre&gt;

&lt;p&gt;
  As a rails gem dependency:
&lt;/p&gt;

&lt;pre class="twilight"&gt; config.&lt;span class="Entity"&gt;gem&lt;/span&gt; &lt;span class="String"&gt;&lt;span class="String"&gt;'&lt;/span&gt;is_taggable&lt;span class="String"&gt;'&lt;/span&gt;&lt;/span&gt; 
&lt;/pre&gt;

&lt;p&gt;
  Or get the source from github:
&lt;/p&gt;

&lt;pre class="twilight"&gt; $ git clone git&lt;span class="Constant"&gt;&lt;span class="Constant"&gt;:&lt;/span&gt;/&lt;/span&gt;&lt;span class="Keyword"&gt;/&lt;/span&gt;github.&lt;span class="Entity"&gt;com&lt;/span&gt;&lt;span class="Keyword"&gt;/&lt;/span&gt;giraffesoft&lt;span class="Keyword"&gt;/&lt;/span&gt;is_taggable.&lt;span class="Entity"&gt;git&lt;/span&gt; 
&lt;/pre&gt;

&lt;p&gt;(or fork it at &lt;a href="http://github.com/giraffesoft/is_taggable"&gt;http://github.com/giraffesoft/is_taggable&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;
  &lt;i&gt;This release announcement is part of &lt;a href="http://giraffesoft.ca/blog/2009/02/16/floss-week.html"&gt;FLOSS week&lt;/a&gt;.
  If you're interested in more open source software, consider 
  &lt;a href="http://feeds.giraffesoft.ca/giraffesoft-the-blog"&gt;subscribing&lt;/a&gt;
  or &lt;a href="http://twitter.com/giraffesoft"&gt;following us on twitter&lt;/a&gt; to get the next announcements asap.&lt;/i&gt;
&lt;/p&gt;
&lt;img src="http://feeds.feedburner.com/~r/giraffesoft-the-blog/~4/2qLdzdTX6hY" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://giraffesoft.ca/blog/2009/02/18/floss-week-is-taggable-tagging-that-isnt-on-drugs.html</feedburner:origLink></entry>
  
  <entry>
    <title>[FLOSS Week]: enum_field</title>
    <link href="http://feeds.giraffesoft.ca/~r/giraffesoft-the-blog/~3/-N4GM8R1f1g/floss-week-day-2-the-enum-field-rails-plugin.html" />
    <id>tag:http://giraffesoft.ca,2009-02-17:1234916032</id>
    <updated>2009-02-17T16:13:52-08:00</updated>
    <content type="html">&lt;p&gt;&lt;i&gt;This release announcement is part of &lt;a href="http://giraffesoft.ca/blog/2009/02/16/floss-week.html"&gt;FLOSS week&lt;/a&gt;.&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;At GiraffeSoft, we love DRY code. That's why we're very quick at creating small plugins that encapsulate the way we code.&lt;/p&gt;

&lt;p&gt;Today I'll unveil one such plugin. It's a very simple one that lets you specify an enum field for an ActiveRecord model.&lt;/p&gt;

&lt;p&gt;The plugin of course encapsulates a standard validates_inclusion_of&lt;/p&gt;

&lt;pre class="code" style="background-color:#2B2B2B;color:#E6E1DC;padding:6px;overflow:auto;line-height:12px;font-size:12px;padding:6px;"&gt;&lt;code&gt;&lt;pre&gt;
&lt;span style="color:#CC7833"&gt;class &lt;/span&gt;&lt;span class="class"&gt;Computer&lt;/span&gt; &lt;span class="punct"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#DA4939"&gt;ActiveRecord&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span style="color:#DA4939"&gt;Base&lt;/span&gt;
  &lt;span class="ident"&gt;validates_inclusion_of&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:status&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:in&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="punct"&gt;['&lt;/span&gt;&lt;span style="color:#A5C261"&gt;on&lt;/span&gt;&lt;span class="punct"&gt;',&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;off&lt;/span&gt;&lt;span class="punct"&gt;',&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;standby&lt;/span&gt;&lt;span class="punct"&gt;',&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;sleep&lt;/span&gt;&lt;span class="punct"&gt;',&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;out of this world&lt;/span&gt;&lt;span class="punct"&gt;']&lt;/span&gt;
  &lt;span class="comment"&gt;#...&lt;/span&gt;
&lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;But then it adds a few nifty extras that just make sense. So using the enum_field plugin:&lt;/p&gt;

&lt;pre class="code" style="background-color:#2B2B2B;color:#E6E1DC;padding:6px;overflow:auto;line-height:12px;font-size:12px;padding:6px;"&gt;&lt;code&gt;&lt;pre&gt;
&lt;span style="color:#CC7833"&gt;class &lt;/span&gt;&lt;span class="class"&gt;Computer&lt;/span&gt; &lt;span class="punct"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#DA4939"&gt;ActiveRecord&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span style="color:#DA4939"&gt;Base&lt;/span&gt;
  &lt;span class="ident"&gt;enum_field&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:status&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt;
    &lt;span class="punct"&gt;['&lt;/span&gt;&lt;span style="color:#A5C261"&gt;on&lt;/span&gt;&lt;span class="punct"&gt;',&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;off&lt;/span&gt;&lt;span class="punct"&gt;',&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;standby&lt;/span&gt;&lt;span class="punct"&gt;',&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;sleep&lt;/span&gt;&lt;span class="punct"&gt;',&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;out of this world&lt;/span&gt;&lt;span class="punct"&gt;']&lt;/span&gt;
  &lt;span class="comment"&gt;#...&lt;/span&gt;
&lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;gives us a bunch useful things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add a validates_inclusion_of with a simple error message ("invalid #{field}")&lt;/li&gt;
&lt;li&gt;define the following query methods, in the name of expressive code:

&lt;ul&gt;
&lt;li&gt;on?&lt;/li&gt;
&lt;li&gt;off?&lt;/li&gt;
&lt;li&gt;standby?&lt;/li&gt;
&lt;li&gt;sleep?&lt;/li&gt;
&lt;li&gt;out_of_this_world?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;define the Computer::STATUSES constant, which contains all acceptable values&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;This plugin has been known to work very well with other plugins. For example,
we've used it successfully with Phusion's excellent
&lt;a href="http://blog.phusion.nl/2008/10/03/47/"&gt;default_value_for plugin&lt;/a&gt;.&lt;/p&gt;

&lt;pre class="code" style="background-color:#2B2B2B;color:#E6E1DC;padding:6px;overflow:auto;line-height:12px;font-size:12px;padding:6px;"&gt;&lt;code&gt;&lt;pre&gt;
&lt;span style="color:#CC7833"&gt;class &lt;/span&gt;&lt;span class="class"&gt;Computer&lt;/span&gt; &lt;span class="punct"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#DA4939"&gt;ActiveRecord&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span style="color:#DA4939"&gt;Base&lt;/span&gt;
  &lt;span class="ident"&gt;enum_field&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:status&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt;
    &lt;span class="punct"&gt;['&lt;/span&gt;&lt;span style="color:#A5C261"&gt;on&lt;/span&gt;&lt;span class="punct"&gt;',&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;off&lt;/span&gt;&lt;span class="punct"&gt;',&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;standby&lt;/span&gt;&lt;span class="punct"&gt;',&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;sleep&lt;/span&gt;&lt;span class="punct"&gt;',&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;out of this world&lt;/span&gt;&lt;span class="punct"&gt;']&lt;/span&gt;
  &lt;span class="ident"&gt;default_value_for&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:status&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;off&lt;/span&gt;&lt;span class="punct"&gt;'&lt;/span&gt;
  &lt;span class="comment"&gt;#...&lt;/span&gt;
&lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;If you don't like the default error message, you can of course override it with an additional :message option.&lt;/p&gt;

&lt;pre class="code" style="background-color:#2B2B2B;color:#E6E1DC;padding:6px;overflow:auto;line-height:12px;font-size:12px;padding:6px;"&gt;&lt;code&gt;&lt;pre&gt;
&lt;span class="ident"&gt;enum_field&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:status&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; 
  &lt;span class="punct"&gt;['&lt;/span&gt;&lt;span style="color:#A5C261"&gt;on&lt;/span&gt;&lt;span class="punct"&gt;',&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;off&lt;/span&gt;&lt;span class="punct"&gt;',&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;standby&lt;/span&gt;&lt;span class="punct"&gt;',&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;sleep&lt;/span&gt;&lt;span class="punct"&gt;',&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;out of this world&lt;/span&gt;&lt;span class="punct"&gt;'],&lt;/span&gt;
  &lt;span style="color:#6E9CBE"&gt;:message&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;incorrect status&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/code&gt;&lt;/pre&gt;


&lt;h3&gt;Get it&lt;/h3&gt;

&lt;p&gt;You can install it inside your app either as a plugin:&lt;/p&gt;

&lt;pre class="code" style="background-color:#2B2B2B;color:#E6E1DC;padding:6px;overflow:auto;line-height:12px;font-size:11.5px;padding:6px;"&gt;&lt;code&gt;&lt;pre&gt;
&lt;span class="global"&gt;$ &lt;/span&gt;&lt;span class="ident"&gt;script&lt;/span&gt;&lt;span class="punct"&gt;/&lt;/span&gt;&lt;span class="ident"&gt;plugin&lt;/span&gt; &lt;span class="ident"&gt;install&lt;/span&gt; &lt;span class="ident"&gt;git&lt;/span&gt;&lt;span class="punct"&gt;:/&lt;/span&gt;&lt;span class="regex"&gt;&lt;/span&gt;&lt;span class="punct"&gt;/&lt;/span&gt;&lt;span class="ident"&gt;github&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;com&lt;/span&gt;&lt;span class="punct"&gt;/&lt;/span&gt;&lt;span class="ident"&gt;giraffesoft&lt;/span&gt;&lt;span class="punct"&gt;/&lt;/span&gt;&lt;span class="ident"&gt;enum_field&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;git&lt;/span&gt;
&lt;/pre&gt;&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;or as a gem, add the following line in you config/environment.rb:&lt;/p&gt;

&lt;pre class="code" style="background-color:#2B2B2B;color:#E6E1DC;padding:6px;overflow:auto;line-height:12px;font-size:12px;padding:6px;"&gt;&lt;code&gt;&lt;pre&gt;
&lt;span class="ident"&gt;config&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;gem&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;giraffesoft-enum_field&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;,&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:lib&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;enum_field&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;,&lt;/span&gt; 
  &lt;span style="color:#6E9CBE"&gt;:source&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;http://gems.github.com&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;or of course, &lt;a href="http://github.com/giraffesoft/enum_field"&gt;fork it on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;This is a good general purpose plugin to include in one's customized Rails app generator.
We may add it to &lt;a href="http://github.com/giraffesoft/blank"&gt;blank&lt;/a&gt; in the future,
if we find we're frequently adding it to new projects.&lt;/p&gt;

&lt;p&gt;&lt;i&gt;This release announcement is part of &lt;a href="http://giraffesoft.ca/blog/2009/02/16/floss-week.html"&gt;FLOSS week&lt;/a&gt;.
If you're interested in more open source software, consider
&lt;a href="http://feeds.giraffesoft.ca/giraffesoft-the-blog"&gt;subscribing&lt;/a&gt;
or &lt;a href="http://twitter.com/giraffesoft"&gt;following us on twitter&lt;/a&gt; to get the next announcements asap.&lt;/i&gt;&lt;/p&gt;
&lt;img src="http://feeds.feedburner.com/~r/giraffesoft-the-blog/~4/-N4GM8R1f1g" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://giraffesoft.ca/blog/2009/02/17/floss-week-day-2-the-enum-field-rails-plugin.html</feedburner:origLink></entry>
  
  <entry>
    <title>[FLOSS week]: classy_resources - resource_controller for sinatra</title>
    <link href="http://feeds.giraffesoft.ca/~r/giraffesoft-the-blog/~3/6MLgBsCCUvg/floss-week-day-1-classy-resources-resource-controller-for-sinatra.html" />
    <id>tag:http://giraffesoft.ca,2009-02-16:1234820096</id>
    <updated>2009-02-16T13:34:56-08:00</updated>
    <content type="html">&lt;p&gt;
  &lt;i&gt;This release announcement is part of &lt;a href="http://giraffesoft.ca/blog/2009/02/16/floss-week.html"&gt;FLOSS week&lt;/a&gt;.&lt;/i&gt;
&lt;/p&gt;

&lt;p&gt;
  Sinatra is awesome. Lightweight web services have never been easier. But, even sinatra can get a little bit tedious when you have to build three or four full resources for consumption with active resource. Not anymore, though.
&lt;/p&gt;

&lt;h3&gt;Introducing Classy Resources&lt;/h3&gt;

&lt;p&gt;
  Classy resources adds a tiny declarative API to sinatra for building active_resource compliant REST APIs. Exposing a model is as simple as:
&lt;/p&gt;

&lt;pre style="background-color:#2B2B2B;color:#E6E1DC;padding:6px;overflow:auto;line-height:12px;font-size:12px;padding:3px;" class="code"&gt;&lt;code&gt;&lt;pre&gt;&lt;span class="ident"&gt;define_resource&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:posts&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:member&lt;/span&gt;     &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="punct"&gt;[&lt;/span&gt;&lt;span style="color:#6E9CBE"&gt;:get&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:put&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:delete&lt;/span&gt;&lt;span class="punct"&gt;],&lt;/span&gt;
                        &lt;span style="color:#6E9CBE"&gt;:collection&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="punct"&gt;[&lt;/span&gt;&lt;span style="color:#6E9CBE"&gt;:get&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:post&lt;/span&gt;&lt;span class="punct"&gt;],&lt;/span&gt;
                        &lt;span style="color:#6E9CBE"&gt;:formats&lt;/span&gt;    &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="punct"&gt;[&lt;/span&gt;&lt;span style="color:#6E9CBE"&gt;:xml&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:json&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:yaml&lt;/span&gt;&lt;span class="punct"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  The above declaration gets you a REST API, with all of the actions that active resource expects, in as many formats as you list. You can customize which actions get created by changing the contents of the :member and :collection arrays.
&lt;/p&gt;

&lt;p&gt;
  It's also possible to override nearly all of classy_resources' behaviours by simply overriding methods. If you're familiar with &lt;a href="http://github.com/giraffesoft/resource_controller"&gt;resource_controller&lt;/a&gt;, you'll recognize this pattern.&lt;/p&gt;

&lt;p&gt;
  For example, if your classes were namespaced in a module, you'd need to override the way that resource names, like :posts, are translated in to classes. It's easy.
&lt;/p&gt;

&lt;pre class="code" style="background-color:#2B2B2B;color:#E6E1DC;padding:6px;overflow:auto;line-height:12px;font-size:12px;padding:6px;"&gt;&lt;code&gt;&lt;pre&gt;&lt;span style="color:#CC7833"&gt;def &lt;/span&gt;&lt;span class="method"&gt;class_for&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;resource&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
  &lt;span style="color:#DA4939"&gt;MyModule&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;const_get&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;resource&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;to_s&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;singularize&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;classify&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;constantize&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
&lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
  For more overrides, see the &lt;a href="http://github.com/giraffesoft/classy_resources"&gt;README&lt;/a&gt;.
&lt;/p&gt;

&lt;h3&gt;Get It&lt;/h3&gt;

&lt;pre class="code" style="background: black;color:white;"&gt;&lt;pre&gt;$ sudo gem install giraffesoft-classy_resources&lt;/pre&gt;&lt;/pre&gt;

&lt;p&gt;Or fork it &lt;a href="http://github.com/giraffesoft/classy_resources"&gt;at github&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;
  &lt;i&gt;This release announcement is part of &lt;a href="http://giraffesoft.ca/blog/2009/02/16/floss-week.html"&gt;FLOSS week&lt;/a&gt;. If you're interested in more open source software, consider &lt;a href="http://feeds.giraffesoft.ca/giraffesoft-the-blog"&gt;subscribing&lt;/a&gt; or &lt;a href="http://twitter.com/giraffesoft"&gt;following us on twitter&lt;/a&gt; to get the next announcements asap.&lt;/i&gt;
&lt;/p&gt;
&lt;/p&gt;
  
&lt;img src="http://feeds.feedburner.com/~r/giraffesoft-the-blog/~4/6MLgBsCCUvg" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://giraffesoft.ca/blog/2009/02/16/floss-week-day-1-classy-resources-resource-controller-for-sinatra.html</feedburner:origLink></entry>
  
  <entry>
    <title>FLOSS week</title>
    <link href="http://feeds.giraffesoft.ca/~r/giraffesoft-the-blog/~3/Y9G1e91K6Aw/floss-week.html" />
    <id>tag:http://giraffesoft.ca,2009-02-16:1234813148</id>
    <updated>2009-02-16T11:39:08-08:00</updated>
    <content type="html">&lt;p&gt;
  We went over a year without even having a website. Then, we lasted a few months with a website, but no blog. That ends today, hopefully with something of a bang.
&lt;/p&gt;

&lt;p&gt;
  At &lt;a href="http://giraffesoft.ca"&gt;giraffesoft&lt;/a&gt;, we're big believers in extracting functionality sooner rather than later. This practice has resulted in a ton of code that we use in all of our projects &amp;mdash; nice and DRY. 
&lt;/p&gt;

&lt;p&gt;
  Only problem is, we never get around to releasing this stuff. Starting a blog seemed like a good excuse to spend some time polishing up some code and, you know, writing READMEs.
&lt;/p&gt;

&lt;p&gt;
  So, starting today, we'll be releasing an open source project every day this week. They'll probably be mostly rails plugins. But, you never know. Something else might float in.
&lt;/p&gt;

&lt;p&gt;
  If you're interested in following along, &lt;a href="http://feeds.giraffesoft.ca/giraffesoft-the-blog"&gt;subscribe to giraffesoft, the blog&lt;/a&gt;. Or, if like me, you don't really use your feed reader anymore, follow &lt;a href="http://twitter.com/giraffesoft"&gt;@giraffesoft&lt;/a&gt; on twitter or &lt;a href="http://github.com/giraffesoft"&gt;github&lt;/a&gt;! See you later with the first project.
&lt;/p&gt;

&lt;img src="http://feeds.feedburner.com/~r/giraffesoft-the-blog/~4/Y9G1e91K6Aw" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://giraffesoft.ca/blog/2009/02/16/floss-week.html</feedburner:origLink></entry>
  
</feed>
