<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Transparent Development &#124; TransFS</title>
	<atom:link href="http://transfs.com/devblog/feed/" rel="self" type="application/rss+xml" />
	<link>http://transfs.com/devblog</link>
	<description>Riding the Rails with TransFS.com...</description>
	<lastBuildDate>Wed, 05 May 2010 12:23:12 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0</generator>
		<item>
		<title>Tame your analytics libraries with Analytical</title>
		<link>http://transfs.com/devblog/2010/05/05/tame-your-analytics-libraries-with-analytical/</link>
		<comments>http://transfs.com/devblog/2010/05/05/tame-your-analytics-libraries-with-analytical/#comments</comments>
		<pubDate>Wed, 05 May 2010 12:19:07 +0000</pubDate>
		<dc:creator>josh</dc:creator>
				<category><![CDATA[rails]]></category>

		<guid isPermaLink="false">http://transfs.com/devblog/?p=381</guid>
		<description><![CDATA[We recently integrated KISSMetrics tracking into our website (more on that in a future post!)&#8230; and I was once again faced with the annoying task of carefully updating the mess of javascript that was cluttering up our page in various places.  We&#8217;re data &#38; analytics tracking freaks at TransFS.com&#8230; so we have a bunch of [...]]]></description>
			<content:encoded><![CDATA[<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Ftransfs.com%2Fdevblog%2F2010%2F05%2F05%2Ftame-your-analytics-libraries-with-analytical%2F"><br />
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Ftransfs.com%2Fdevblog%2F2010%2F05%2F05%2Ftame-your-analytics-libraries-with-analytical%2F&amp;source=joshatwork&amp;style=normal&amp;service=bit.ly" height="61" width="50" /><br />
			</a>
		</div>
<p>We recently integrated KISSMetrics tracking into our website (more on that in a future post!)&#8230; and I was once again faced with the annoying task of carefully updating the mess of javascript that was cluttering up our page in various places.  We&#8217;re data &amp; analytics tracking <em>freaks</em> at TransFS.com&#8230; so we have a bunch of different analytics packages in place on our site. Managing all of these packages and keeping the tracking javascript clean &amp; simple had finally become a real pain point for me.</p>
<p>At various times, we&#8217;ve had Google Analytics, Clicky, MixPanel, and our own internal <a href="http://github.com/jkrall/funnel_cake">FunnelCake</a> tracking code all monitoring our visitor traffic.  Each library requires specific javascript code to be added to the page; some insisting that the code be added to the top of the &lt;body&gt; element, while others suggesting that it be before the closing &lt;/body&gt; tag.  To further complicate matters, we want to be able to trigger a manual track() function to track a specific event.  However, it isn&#8217;t always convenient to declare these tracking calls at the bottom of the page, or in a controller, or in any single location.  What if we want to track an action in a view?  How can we be sure that the corresponding library has been initialized at the time when the javascript code is inserted?  What if we want to declare a tracking event in a controller?  How can we easily send the same tracking event to all of the different analytics packages we have installed?</p>
<p>So, in a fury of frustration, I banged out a new rails gem for helping with this exact problem.  I&#8217;d like to introduce you to <a href="http://rubygems.org/gems/analytical">Analytical</a>.  This gem is designed to be an all-purpose analytics front-end.  By providing a simple API to a variety of analytics libraries, it allows you to clean up your views and easily track events in your app without worrying about the details of the underlying analytics apis.  Even better, it gracefully deals with queuing up tracking commands for services, so that the tracking commands are always inserted after the initialization scripts have been added to your page.</p>
<p>So, how does it work?  You start off by adding a single line to your application controller:</p>

<div class="wp_syntax"><div class="code"><pre class="ruby" style="font-family:monospace;">    analytical <span style="color:#ff3333; font-weight:bold;">:modules</span><span style="color:#006600; font-weight:bold;">=&gt;</span><span style="color:#006600; font-weight:bold;">&#91;</span><span style="color:#ff3333; font-weight:bold;">:google</span>, <span style="color:#ff3333; font-weight:bold;">:clicky</span><span style="color:#006600; font-weight:bold;">&#93;</span></pre></div></div>

<p>This tells analytical which packages you intend to use.  You can define the api keys in config/analytical.yml, like so:</p>

<div class="wp_syntax"><div class="code"><pre class="yaml" style="font-family:monospace;">   google:
     key: UA-5555555-5
   clicky:
     key: 55555</pre></div></div>

<p>Then, in your layout file, you need to add a few hooks so that Analytical can insert any initialization code for your modules:</p>

<div class="wp_syntax"><div class="code"><pre class="rails" style="font-family:monospace;">    &lt;!DOCTYPE html&gt;
    &lt;html&gt;
      &lt;head&gt;
        &lt;title&gt;Example&lt;/title&gt;
        <span style="color:#006600; font-weight:bold;">&lt;%</span>= <span style="color:#5A0A0A; font-weight:bold;">stylesheet_link_tag</span> <span style="color:#ff3333; font-weight:bold;">:all</span> <span style="color:#006600; font-weight:bold;">%&gt;</span>
        <span style="color:#006600; font-weight:bold;">&lt;%</span>= <span style="color:#5A0A0A; font-weight:bold;">javascript_include_tag</span> <span style="color:#ff3333; font-weight:bold;">:defaults</span> <span style="color:#006600; font-weight:bold;">%&gt;</span>
        <span style="color:#006600; font-weight:bold;">&lt;%</span>= csrf_meta_tag <span style="color:#006600; font-weight:bold;">%&gt;</span>
        <span style="color:#006600; font-weight:bold;">&lt;%</span> analytical.<span style="color:#9900CC;">identify</span> current_user.<span style="color:#9900CC;">id</span>, <span style="color:#ff3333; font-weight:bold;">:email</span><span style="color:#006600; font-weight:bold;">=&gt;</span>current_user.<span style="color:#9900CC;">email</span> <span style="color:#9966CC; font-weight:bold;">if</span> current_user <span style="color:#006600; font-weight:bold;">%&gt;</span>
        <span style="color:#006600; font-weight:bold;">&lt;%</span>= raw analytical.<span style="color:#9900CC;">head_javascript</span> <span style="color:#006600; font-weight:bold;">%&gt;</span>
      &lt;/head&gt;
      &lt;body&gt;
        <span style="color:#006600; font-weight:bold;">&lt;%</span>= raw analytical.<span style="color:#9900CC;">body_prepend_javascript</span> <span style="color:#006600; font-weight:bold;">%&gt;</span>
        <span style="color:#006600; font-weight:bold;">&lt;%</span>= <span style="color:#9966CC; font-weight:bold;">yield</span> <span style="color:#006600; font-weight:bold;">%&gt;</span>
        <span style="color:#006600; font-weight:bold;">&lt;%</span>= raw analytical.<span style="color:#9900CC;">body_append_javascript</span> <span style="color:#006600; font-weight:bold;">%&gt;</span>
      &lt;/body&gt;
    &lt;/html&gt;</pre></div></div>

<p>As you can see, I&#8217;ve also added a simple <code>identify</code> call to the template, so that we can tell our analytics modules about the current user (if they support per-user identification, like Clicky/KISSMetrics).</p>
<p>From there, using analytical to track events is dead simple.  Track a url with:</p>

<div class="wp_syntax"><div class="code"><pre class="ruby" style="font-family:monospace;">    analytical.<span style="color:#9900CC;">track</span> <span style="color:#996600;">'/a/really/nice/url'</span></pre></div></div>

<p>And an event with:</p>

<div class="wp_syntax"><div class="code"><pre class="ruby" style="font-family:monospace;">    analytical.<span style="color:#9900CC;">event</span> <span style="color:#996600;">'Some Awesome Event'</span>, <span style="color:#ff3333; font-weight:bold;">:with</span><span style="color:#006600; font-weight:bold;">=&gt;</span>:data</pre></div></div>

<p>&#8230; and these can be used anyplace in your app: views, controllers, templates, whatever.  Since views are processed before templates, your commands will queued up and then inserted immediately after the corresponding analytics library has been initialized.  This produces javascript code on your page that might look something like:</p>

<div class="wp_syntax"><div class="code"><pre class="html" style="font-family:monospace;">    &lt;!-- Analytical Init: KissMetrics --&gt; 
    &lt;script type=&quot;text/javascript&quot;&gt; 
        var .... initialization javascript for KISSMetrics ...
    &lt;/script&gt; 
    &lt;script type='text/javascript'&gt; 
        _kmq.push([&quot;record&quot;, &quot;Visited Tour&quot;, {}]);
    &lt;/script&gt;</pre></div></div>

<p>In this example, <code>analytical.event 'Visited Tour'</code> was called in the view&#8230; and the event was queued up so it could be inserted immediately after the KISSMetrics library was initialized.</p>
<p>But what about triggering a specific command that is not one of the standard calls that Analytical knows about (track, event, identify)?  No problem&#8230; you can call any custom method on a module like so:</p>

<div class="wp_syntax"><div class="code"><pre class="ruby" style="font-family:monospace;">   analytical.<span style="color:#9900CC;">clicky</span>.<span style="color:#9900CC;">special_tracking_method</span> <span style="color:#ff3333; font-weight:bold;">:that_only</span><span style="color:#006600; font-weight:bold;">=&gt;</span>:clicky_supports</pre></div></div>

<p>If you need to output javascript immediately without queuing it up for later insertion (to use in a javascript callback, for instance)&#8230; you can do this using the .now accessor:</p>

<div class="wp_syntax"><div class="code"><pre class="ruby" style="font-family:monospace;">   <span style="color:#006600; font-weight:bold;">&lt;%</span>= analytical.<span style="color:#9900CC;">now</span>.<span style="color:#9900CC;">track</span> <span style="color:#996600;">'/some/url'</span> <span style="color:#006600; font-weight:bold;">%&gt;</span></pre></div></div>

<p>outputs:</p>

<div class="wp_syntax"><div class="code"><pre class="javascript" style="font-family:monospace;">   clicky.<span style="color: #660066;">log</span><span style="color: #009900;">&#40;</span><span style="color: #3366CC;">'/some/url'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
   googleAnalyticsTracker._trackPageview<span style="color: #009900;">&#40;</span><span style="color: #3366CC;">&quot;/some/url&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></div></div>

<p>In our app, we wrap this into a convenience javascript function in our <head> that looks like:</p>

<div class="wp_syntax"><div class="code"><pre class="javascript" style="font-family:monospace;">	<span style="color: #003366; font-weight: bold;">var</span> analytical_track <span style="color: #339933;">=</span> <span style="color: #003366; font-weight: bold;">function</span><span style="color: #009900;">&#40;</span>page<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#123;</span>
		<span style="color: #339933;">&lt;%=</span> analytical.<span style="color: #660066;">now</span>.<span style="color: #660066;">track</span><span style="color: #009900;">&#40;</span><span style="color: #3366CC;">'page'</span><span style="color: #009900;">&#41;</span>.<span style="color: #660066;">gsub</span><span style="color: #009900;">&#40;</span><span style="color: #009966; font-style: italic;">/&quot;page&quot;/</span><span style="color: #339933;">,</span><span style="color: #3366CC;">'page'</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">%&gt;</span>
	<span style="color: #009900;">&#125;</span><span style="color: #339933;">;</span>
	<span style="color: #003366; font-weight: bold;">var</span> analytical_event <span style="color: #339933;">=</span> <span style="color: #003366; font-weight: bold;">function</span><span style="color: #009900;">&#40;</span>event<span style="color: #339933;">,</span> data<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#123;</span>
		<span style="color: #000066; font-weight: bold;">if</span> <span style="color: #009900;">&#40;</span><span style="color: #339933;">!</span>data<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span> data <span style="color: #339933;">=</span> <span style="color: #009900;">&#123;</span><span style="color: #009900;">&#125;</span><span style="color: #339933;">;</span> <span style="color: #009900;">&#125;</span>
		<span style="color: #339933;">&lt;%=</span> analytical.<span style="color: #660066;">now</span>.<span style="color: #660066;">event</span><span style="color: #009900;">&#40;</span><span style="color: #3366CC;">'event'</span><span style="color: #339933;">,</span> <span style="color: #009900;">&#123;</span><span style="color: #009900;">&#125;</span><span style="color: #009900;">&#41;</span>.<span style="color: #660066;">gsub</span><span style="color: #009900;">&#40;</span><span style="color: #009966; font-style: italic;">/&quot;event&quot;/</span><span style="color: #339933;">,</span><span style="color: #3366CC;">'event'</span><span style="color: #009900;">&#41;</span>.<span style="color: #660066;">gsub</span><span style="color: #009900;">&#40;</span><span style="color: #009966; font-style: italic;">/\&quot;?\{\}\&quot;?/</span><span style="color: #339933;">,</span><span style="color: #3366CC;">'data'</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">%&gt;</span>
	<span style="color: #009900;">&#125;</span><span style="color: #339933;">;</span></pre></div></div>

<p>This allows us to call</p>

<div class="wp_syntax"><div class="code"><pre class="javascript" style="font-family:monospace;">analytical_track<span style="color: #009900;">&#40;</span><span style="color: #3366CC;">'/some/url'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></div></div>

<p> from our javascript, anywhere in the page, and it&#8217;ll hit every module that we have installed.  (Note: we do have to be sure that if we use this javascript helper, that we only call it in ajax situations where the page is certain to be fully baked and all analytics libraries are initialized.)</p>
<p>That&#8217;s about it.  We&#8217;ve had the gem in production for a while now, and it has really cleaned up our tracking code.  However, I&#8217;d love to get some feedback on how it could be improved!</p>
<p>As of now, I have implemented support for Google Analytics, <a href="http://getclicky.com">Clicky</a>, and <a href="http://kissmetrics.com">KISSMetrics</a>&#8230; but the library is extremely easy to extend, so I&#8217;m hopeful that the community will add support for other services that people would like to use.  You can find the code here:  <a href="http://github.com/jkrall/analytical">http://github.com/jkrall/analytical</a>, so fork away!</p>
<p>Lastly, I&#8217;d like to call out the cool <a href="http://github.com/iconara/snogmetrics">Snogmetrics</a> library for providing a lot of the inspiration for Analytical.  I stole some great ideas from that project, and adapted it for our broader needs.  Thanks <a href="http://github.com/iconara">Theo</a>!</p>
]]></content:encoded>
			<wfw:commentRss>http://transfs.com/devblog/2010/05/05/tame-your-analytics-libraries-with-analytical/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Ruby Version Manager Rocks!</title>
		<link>http://transfs.com/devblog/2010/03/30/ruby-version-manager-rocks/</link>
		<comments>http://transfs.com/devblog/2010/03/30/ruby-version-manager-rocks/#comments</comments>
		<pubDate>Tue, 30 Mar 2010 06:59:44 +0000</pubDate>
		<dc:creator>josh</dc:creator>
				<category><![CDATA[rails]]></category>

		<guid isPermaLink="false">http://transfs.com/devblog/?p=377</guid>
		<description><![CDATA[I know I&#8217;m a few months late to this party&#8230; but I finally jumped aboard the Ruby Version Manager (RVM) train today, and wow is it awesome! I had to wipe my main development machine clean today, and start from scratch&#8230; and I was dreading the prospect of digging through a million blog posts to [...]]]></description>
			<content:encoded><![CDATA[<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Ftransfs.com%2Fdevblog%2F2010%2F03%2F30%2Fruby-version-manager-rocks%2F"><br />
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Ftransfs.com%2Fdevblog%2F2010%2F03%2F30%2Fruby-version-manager-rocks%2F&amp;source=joshatwork&amp;style=normal&amp;service=bit.ly" height="61" width="50" /><br />
			</a>
		</div>
<p>I know I&#8217;m a few months late to this party&#8230; but I finally jumped aboard the <a href="http://rvm.beginrescueend.com/rvm/install/">Ruby Version Manager</a> (RVM) train today, and wow is it awesome!</p>
<p>I had to wipe my main development machine clean today, and start from scratch&#8230; and I was dreading the prospect of digging through a million blog posts to determine which ruby version I should install, grabbing tar files, making sure that I&#8217;m on the right patchlevel, etc.  Enter RVM&#8230;</p>
<p>Ruby Version Manager is a really simple set of scripts that manages your ruby environment, allowing you to install multiple versions of ruby with incredible ease.  The source lives here: <a href="http://github.com/wayneeseguin/rvm">http://github.com/wayneeseguin/rvm</a>, and you get up and running with the following simple command (copied directly from their great <a href="http://rvm.beginrescueend.com/rvm/install/">install page</a>):</p>

<div class="wp_syntax"><div class="code"><pre class="csh" style="font-family:monospace;">mkdir -p ~/.rvm/src/ &amp;&amp; cd ~/.rvm/src &amp;&amp; rm -rf ./rvm/ &amp;&amp; git clone --depth 1 git://github.com/wayneeseguin/rvm.git &amp;&amp; cd rvm &amp;&amp; ./install</pre></div></div>

<p>Once you install RVM&#8230; you can install a ruby version with:</p>

<div class="wp_syntax"><div class="code"><pre class="csh" style="font-family:monospace;">rvm install 1.8.7</pre></div></div>

<p>It&#8217;s that easy!  The ruby tar file will be downloaded on-the-fly, compiled, and installed into a sandbox area in your ~/.rvm.  You can pick macruby, jruby, 1.8.6, 1.8.7, whatever you want&#8230;</p>
<p>To begin using a specific installed version:</p>

<div class="wp_syntax"><div class="code"><pre class="csh" style="font-family:monospace;">rvm --default 1.8.7</pre></div></div>

<p>The &#8211;default flag makes it sticky so that your shells will use this version by default.  Now open a new shell, and ruby will be pointing at the correct version:</p>

<div class="wp_syntax"><div class="code"><pre class="csh" style="font-family:monospace;">% which ruby
/Users/krall/.rvm/rubies/ruby-1.8.7-p249/bin/ruby
% ruby -v
ruby 1.8.7 (2010-01-10 patchlevel 249) [i686-darwin10.2.0]</pre></div></div>

<p>Even better:  No more installing gems with &#8220;sudo gem install &#8230;&#8221;!  Each ruby version comes with its own set of gems, all residing in a tidy directory structure inside .rvm.  So, you cam install gems at-will, and they will link up with the correct ruby version at all times.</p>
<p>Great stuff.  Thanks <a href="http://www.workingwithrails.com/recommendation/for/person/7192-wayne-e-seguin">Wayne</a>!</p>
]]></content:encoded>
			<wfw:commentRss>http://transfs.com/devblog/2010/03/30/ruby-version-manager-rocks/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Announcing SEOpener, the rails engine for tracking your SEO progress!</title>
		<link>http://transfs.com/devblog/2009/12/15/announcing-seopener-the-rails-engine-for-tracking-your-seo-progress/</link>
		<comments>http://transfs.com/devblog/2009/12/15/announcing-seopener-the-rails-engine-for-tracking-your-seo-progress/#comments</comments>
		<pubDate>Tue, 15 Dec 2009 20:07:57 +0000</pubDate>
		<dc:creator>josh</dc:creator>
				<category><![CDATA[SEO]]></category>
		<category><![CDATA[background processing]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[engine]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[open source]]></category>
		<category><![CDATA[plugin]]></category>
		<category><![CDATA[seopener]]></category>

		<guid isPermaLink="false">http://transfs.com/devblog/?p=363</guid>
		<description><![CDATA[For the past year or so, we&#8217;ve been tracking our SEO progress with a tool that we developed internally. The idea was to keep an eye on the daily movement of our domain in the search results for various terms. We also wanted to collect some general statistics on these terms, like how much traffic [...]]]></description>
			<content:encoded><![CDATA[<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Ftransfs.com%2Fdevblog%2F2009%2F12%2F15%2Fannouncing-seopener-the-rails-engine-for-tracking-your-seo-progress%2F"><br />
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Ftransfs.com%2Fdevblog%2F2009%2F12%2F15%2Fannouncing-seopener-the-rails-engine-for-tracking-your-seo-progress%2F&amp;source=joshatwork&amp;style=normal&amp;service=bit.ly" height="61" width="50" /><br />
			</a>
		</div>
<p><img class="alignleft size-thumbnail wp-image-366" title="SEOpener, a can opener for SEO" src="http://transfs.com/devblog/wp-content/uploads/2009/12/30627-150x150.jpg" alt="SEOpener, a can opener for SEO" width="150" height="150" />For the past year or so, we&#8217;ve been tracking our SEO progress with a tool that we developed internally.  The idea was to keep an eye on the daily movement of our domain in the search results for various terms.  We also wanted to collect some general statistics on these terms, like how much traffic they receive, the PageRank of the top ranking site, and advertising CPC estimates.  All of this was rolled into a set of controllers, models, views, and background-processing code that I&#8217;ve now extracted out into a new rails engine plugin:  <a href="http://github.com/jkrall/seopener">SEOpener</a>!</p>
<p>SEOpener combines data from the Google Ajax Search API, with other datasources such as Yahoo&#8217;s keyword estimation tool, and google toolbar pagerank queries, to try to give you a complete picture of how you are positioned on a given search term.  It provides a simple admin interface for listing all of your terms, adding new ones, and viewing the top 64 search results for a given term.</p>
<p>SEOpener is designed to be a drop-in solution for existing apps, providing everything you might need to get started in tracking your sites search rankings.  For the most part, it should be fairly easy to install, however you will need to come up with a background processing solution (you can use <a href="http://transfs.com/devblog/2009/04/06/goodbye-backgroundrb-hello-workling-starling/">cron &amp; workling/starling like we do</a>, or something else) to run the data-scraping jobs at regular intervals.</p>
<div id="attachment_372" class="wp-caption figure alignnone" style="width: 300px"><img class="size-medium wp-image-372 " title="SEOpener Admin Interface" src="http://transfs.com/devblog/wp-content/uploads/2009/12/SEOpener-Admin-Interface-300x294.png" alt="SEOpener Admin Interface" width="300" height="294" /><p class="legend" style="width: 276px">SEOpener Admin Interface</p></div>
<p>We&#8217;ve been pretty happy with this tool, and have been running it in production for some time now&#8230;. but it would be great to get help from the community.  Leave a comment if you have any ideas, or even better, <a href="http://github.com/jkrall/seopener">fork us and hack away on it</a>!</p>
]]></content:encoded>
			<wfw:commentRss>http://transfs.com/devblog/2009/12/15/announcing-seopener-the-rails-engine-for-tracking-your-seo-progress/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Custom SMTP Settings for a specific ActionMailer subclass</title>
		<link>http://transfs.com/devblog/2009/12/03/custom-smtp-settings-for-a-specific-actionmailer-subclass/</link>
		<comments>http://transfs.com/devblog/2009/12/03/custom-smtp-settings-for-a-specific-actionmailer-subclass/#comments</comments>
		<pubDate>Fri, 04 Dec 2009 01:32:43 +0000</pubDate>
		<dc:creator>josh</dc:creator>
				<category><![CDATA[rails]]></category>

		<guid isPermaLink="false">http://transfs.com/devblog/?p=356</guid>
		<description><![CDATA[We use Google Apps for all of our emailing&#8230; and for the most part it works great. However, dealing with their outbound smtp relay can be a real pain. The biggest problem is that it only allows you to send an email if the From header matches the account that is used for authentication. (Note: [...]]]></description>
			<content:encoded><![CDATA[<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Ftransfs.com%2Fdevblog%2F2009%2F12%2F03%2Fcustom-smtp-settings-for-a-specific-actionmailer-subclass%2F"><br />
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Ftransfs.com%2Fdevblog%2F2009%2F12%2F03%2Fcustom-smtp-settings-for-a-specific-actionmailer-subclass%2F&amp;source=joshatwork&amp;style=normal&amp;service=bit.ly" height="61" width="50" /><br />
			</a>
		</div>
<p>We use Google Apps for all of our emailing&#8230; and for the most part it works great.  However, dealing with their outbound smtp relay can be a real pain.  The biggest problem is that it only allows you to send an email if the From header matches the account that is used for authentication.  (Note: I&#8217;m not suggesting that this is a bad practice, or something wrong with Google&#8230; it makes total sense.)</p>
<p>Because of this, I needed to set up different smtp account settings for one of our ActionMailer subclasses than those used by the rest of our site.  Unfortunately, because of the way ActionMailer is designed&#8230; this isn&#8217;t exactly an easy task!</p>
<p>I found <a href="http://broadcast.oreilly.com/2009/03/using-multiple-smtp-accounts-w.html">this article</a>, which gave me some clues about how to do this.  Here&#8217;s the solution I came up with:</p>

<div class="wp_syntax"><div class="code"><pre class="ruby" style="font-family:monospace;"><span style="color:#9966CC; font-weight:bold;">class</span> MailerWithCustomSmtp <span style="color:#006600; font-weight:bold;">&lt;</span> <span style="color:#6666ff; font-weight:bold;">ActionMailer::Base</span>
    SMTP_SETTINGS = <span style="color:#006600; font-weight:bold;">&#123;</span>
      <span style="color:#ff3333; font-weight:bold;">:address</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;smtp.gmail.com&quot;</span>,
      <span style="color:#ff3333; font-weight:bold;">:port</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#006666;">587</span>,
      <span style="color:#ff3333; font-weight:bold;">:authentication</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#ff3333; font-weight:bold;">:plain</span>,
      <span style="color:#ff3333; font-weight:bold;">:user_name</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;custom_account@transfs.com&quot;</span>,
      <span style="color:#ff3333; font-weight:bold;">:password</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">'password'</span>,
   <span style="color:#006600; font-weight:bold;">&#125;</span>
&nbsp;
   <span style="color:#9966CC; font-weight:bold;">def</span> awesome_email<span style="color:#006600; font-weight:bold;">&#40;</span>bidder, options=<span style="color:#006600; font-weight:bold;">&#123;</span><span style="color:#006600; font-weight:bold;">&#125;</span><span style="color:#006600; font-weight:bold;">&#41;</span>
      with_custom_smtp_settings <span style="color:#9966CC; font-weight:bold;">do</span>
         subject       <span style="color:#996600;">'Awesome Email D00D!'</span>
         recipients    <span style="color:#996600;">'someone@test.com'</span>
         from          <span style="color:#996600;">'custom_reply_to@transfs.com'</span>
         body          <span style="color:#996600;">'Hope this works...'</span>
      <span style="color:#9966CC; font-weight:bold;">end</span>
   <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  <span style="color:#008000; font-style:italic;"># Override the deliver! method so that we can reset our custom smtp server settings</span>
  <span style="color:#9966CC; font-weight:bold;">def</span> deliver!<span style="color:#006600; font-weight:bold;">&#40;</span>mail = <span style="color:#0066ff; font-weight:bold;">@mail</span><span style="color:#006600; font-weight:bold;">&#41;</span>
    out = <span style="color:#9966CC; font-weight:bold;">super</span>
    reset_smtp_settings <span style="color:#9966CC; font-weight:bold;">if</span> <span style="color:#0066ff; font-weight:bold;">@_temp_smtp_settings</span>
    out
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  private
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">def</span> with_custom_smtp_settings<span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#006600; font-weight:bold;">&amp;</span>block<span style="color:#006600; font-weight:bold;">&#41;</span>
    <span style="color:#0066ff; font-weight:bold;">@_temp_smtp_settings</span> = @@smtp_settings
    @@smtp_settings = SMTP_SETTINGS
    <span style="color:#9966CC; font-weight:bold;">yield</span>
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">def</span> reset_smtp_settings
    @@smtp_settings = <span style="color:#0066ff; font-weight:bold;">@_temp_smtp_settings</span>
    <span style="color:#0066ff; font-weight:bold;">@_temp_smtp_settings</span> = <span style="color:#0000FF; font-weight:bold;">nil</span>
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
<span style="color:#9966CC; font-weight:bold;">end</span></pre></div></div>

<p>The idea here is simple&#8230; before sending the mail, set the @@smtp_settings to whatever it needs to be.  Then, after delivering the email, set it back to whatever it was.  Hope this helps someone else!</p>
]]></content:encoded>
			<wfw:commentRss>http://transfs.com/devblog/2009/12/03/custom-smtp-settings-for-a-specific-actionmailer-subclass/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Nginx and Rails and WordPress, oh my&#8230;</title>
		<link>http://transfs.com/devblog/2009/11/23/nginx-and-rails-and-wordpress-oh-my/</link>
		<comments>http://transfs.com/devblog/2009/11/23/nginx-and-rails-and-wordpress-oh-my/#comments</comments>
		<pubDate>Tue, 24 Nov 2009 01:06:12 +0000</pubDate>
		<dc:creator>josh</dc:creator>
				<category><![CDATA[rails]]></category>

		<guid isPermaLink="false">http://transfs.com/devblog/?p=330</guid>
		<description><![CDATA[In our recent switch to EngineYard Cloud&#8230; one of the most annoying problems I ran into was getting our WordPress blog configured properly so that it would play nicely with our Rails app. In our previous hosting environment, we ran with an Apache setup&#8230; and we used a simple virtualhost config that looked something like [...]]]></description>
			<content:encoded><![CDATA[<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Ftransfs.com%2Fdevblog%2F2009%2F11%2F23%2Fnginx-and-rails-and-wordpress-oh-my%2F"><br />
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Ftransfs.com%2Fdevblog%2F2009%2F11%2F23%2Fnginx-and-rails-and-wordpress-oh-my%2F&amp;source=joshatwork&amp;style=normal&amp;service=bit.ly" height="61" width="50" /><br />
			</a>
		</div>
<p>In our recent switch to EngineYard Cloud&#8230; one of the most annoying problems I ran into was getting our WordPress blog configured properly so that it would play nicely with our Rails app.  In our previous hosting environment, we ran with an Apache setup&#8230; and we used a simple virtualhost config that looked something like this:</p>

<div class="wp_syntax"><div class="code"><pre class="apache" style="font-family:monospace;">  <span style="color: #00007f;">Alias</span> /blog /var/www/apps/tfs_blog
  &lt;<span style="color: #000000; font-weight:bold;">Directory</span> /var/www/apps/tfs_blog&gt;
  PassengerEnabled <span style="color: #0000ff;">off</span>
  <span style="color: #00007f;">allow</span> <span style="color: #00007f;">from</span> <span style="color: #00007f;">all</span>
  <span style="color: #00007f;">AllowOverride</span> <span style="color: #00007f;">All</span>
  &lt;/<span style="color: #000000; font-weight:bold;">Directory</span>&gt;</pre></div></div>

<p>With the move to EngineYard, we have made the switch to Nginx&#8230; because, after all, that&#8217;s what all the cool kids are using these days.  I admit that one reason I was looking forward to Nginx was to rid myself of the awful apache config file syntax.  However, I quickly learned that Nginx (or at least the version that is installed by default on the EY Cloud images) requires its own voodoo tricks in order to do seemingly simple things.  </p>
<p>In this case, all we want to do is host our blog, alongside our rails app, at /blog.  (and of course, this blog is hosted at /devblog)  Unfortunately, getting this working properly required endless hour of scouring google for nginx config snippets&#8230; until I finally landed on the following, working setup:</p>

<div class="wp_syntax"><div class="code"><pre class="apache" style="font-family:monospace;"><span style="color: #00007f;">location</span> /blog/ {
 <span style="color: #00007f;">alias</span> /data/tfs_blog/;
 index index.php index.html index.htm;
 if (-e $request_filename) {
 break;
 }
 rewrite ^/blog/(.+)$ /blog/index.php?q=$<span style="color: #ff0000;">1</span>;
}
<span style="color: #00007f;">location</span> = /blog {
 fastcgi_pass 127.0.0.1:<span style="color: #ff0000;">1027</span>;
 fastcgi_param SCRIPT_FILENAME /data/tfs_blog/index.php;
 <span style="color: #00007f;">include</span> /etc/nginx/common/fcgi.conf;
}
<span style="color: #00007f;">location</span> ~ /blog/?.*\.php$ {
 if ($fastcgi_script_name ~ /blog/?(.*)$) {
 set $valid_fastcgi_script_name /$<span style="color: #ff0000;">1</span>;
 }
 fastcgi_pass 127.0.0.1:<span style="color: #ff0000;">1027</span>;
 fastcgi_index index.php;
 fastcgi_param SCRIPT_FILENAME /data/tfs_blog$valid_fastcgi_script_name;
 <span style="color: #00007f;">include</span> /etc/nginx/common/fcgi.conf;
}</pre></div></div>

<p>The worst thing about this, aside from the complexity required to get such a simple thing working, is that the error condition you get when the nginx fastcgi setup is misconfigured is incredibly obscure:  &#8220;No input file specified.&#8221;  This apparently means that fastcgi is not receiving the SCRIPT_FILENAME parameter properly&#8230; but the actual cause of this can be anything from misconfigured permissions to location paths that aren&#8217;t grabbing the proper filename from the request uri.  As you can see, the final result requires manually capturing the php filename from the uri path using a regex, and then setting the SCRIPT_FILENAME env variable accordingly.</p>
<p>It&#8217;s also worth mentioning that this snippet:</p>

<div class="wp_syntax"><div class="code"><pre class="apache" style="font-family:monospace;"> if (-e $request_filename) {
 break;
 }
 rewrite ^/blog/(.+)$ /blog/index.php?q=$<span style="color: #ff0000;">1</span>;</pre></div></div>

<p> &#8230; from the first block is required to make wordpress&#8217;s pretty urls map correctly to the cgi handler.</p>
<p>That sure was a lot of work!  Hopefully this post will save someone else all the pain and headache&#8230; or maybe someone will stumble across this and show me how it can all be done in a couple of simple config lines.</p>
]]></content:encoded>
			<wfw:commentRss>http://transfs.com/devblog/2009/11/23/nginx-and-rails-and-wordpress-oh-my/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>The Switch to EngineYard Cloud</title>
		<link>http://transfs.com/devblog/2009/11/23/the-switch-to-engineyard-cloud/</link>
		<comments>http://transfs.com/devblog/2009/11/23/the-switch-to-engineyard-cloud/#comments</comments>
		<pubDate>Mon, 23 Nov 2009 14:24:14 +0000</pubDate>
		<dc:creator>josh</dc:creator>
				<category><![CDATA[rails]]></category>

		<guid isPermaLink="false">http://transfs.com/devblog/?p=334</guid>
		<description><![CDATA[We recently underwent a pretty big change here at TransFS.com&#8230; all entirely &#8220;under the hood&#8221;. We decided to migrate from our old hosting setup at Joyent, to our new home at EngineYard Cloud. We&#8217;re now running on Amazon EC2 instances, all transparently and easily managed by the excellent admin tools provided by Engine Yard. Why [...]]]></description>
			<content:encoded><![CDATA[<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Ftransfs.com%2Fdevblog%2F2009%2F11%2F23%2Fthe-switch-to-engineyard-cloud%2F"><br />
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Ftransfs.com%2Fdevblog%2F2009%2F11%2F23%2Fthe-switch-to-engineyard-cloud%2F&amp;source=joshatwork&amp;style=normal&amp;service=bit.ly" height="61" width="50" /><br />
			</a>
		</div>
<p><img class="size-full wp-image-343 alignleft" title="logo-engineyard" src="http://transfs.com/devblog/wp-content/uploads/2009/11/logo-engineyard.png" alt="Engine Yard" width="61" height="98" /></p>
<p>We recently underwent a pretty big change here at TransFS.com&#8230; all entirely &#8220;under the hood&#8221;.  We decided to migrate from our old hosting setup at Joyent, to our new home at EngineYard Cloud.  We&#8217;re now running on Amazon EC2 instances, all transparently and easily managed by the excellent admin tools provided by Engine Yard.</p>
<p><strong>Why the switch?</strong></p>
<p>I had several reasons for deciding that Joyent was no longer working for us.  First, the Solaris architecture that Joyent uses provided no obvious benefits, and a surprising number of headaches.  Solaris has <em>most</em> of the same tools as linux&#8230; but many of them take different command-line arguments or are annoyingly lacking in features.  For example:  &#8220;grep -R&#8221; for recursive search through files, doesn&#8217;t exist on Solaris.  Why?  I have no idea.  But these types of things can become a real pain over time, and while they seem small, they really do matter.</p>
<p>Secondly, Joyent provides no automatic backup solution for your data.  I was very surprised when we first switched to Joyent that I had to roll my own backup solution with mysqldump, rsync, and a script that uploaded to Amazon S3.  I never felt good about this, because backups are so important, and because it isn&#8217;t an area that I&#8217;m very confident in&#8230; and I was never able to properly test that my backups would save us in the case of massive data loss.  When selecting a hosting provider, I really want someone to hand me a pre-built backup solution that I can trust uses industry best-practices and has been carefully tested.  EngineYard provided me with much more confidence on this front, and automated daily or hourly backups are dead-simple to set up (no hacking of my own scripts!)</p>
<p>Finally, we have been having some real problems with the memory footprint of our app.  It has ballooned to over 150MB per rails instance, sometimes as high as 250MB.  Between our Passenger children, background workers, mysql, etc&#8230; we kept bumping up against the RAM limit on our Joyent slice.  This was obviously <em>our fault</em>, but the process for dealing with it with Joyent made it a real pain to solve.  Moving to a two-slice setup, where our mysql db lived on a separate server was an obvious solution&#8230; but provisioning and setting up a whole new server was something that I didn&#8217;t have time for, and all Joyent really offered was to clone my existing setup.  I would have had to deal with everything else myself, and suffer through a lot of downtime while I figured it out.  Even switching to a Joyent slice with more RAM was not an instantaneous process.  I would have needed to create a support ticket, and then wait for someone to get back to me and clone us over to a new &#8220;accelerator&#8221; slice.  I&#8217;m sure this would have worked&#8230; but it isn&#8217;t nearly as convenient as it should be, in the age of push-button admin interfaces like those provided by EngineYard Cloud.  In fact, with EY Cloud, I was able to switch to running our db on a separate server with a single checkbox and re-deploy.  Very cool.  We&#8217;re now running safely under the RAM threshold of the smallest EC2 instance, and I can very easily scale up to more servers or larger server by selecting a different server type in an EngineYard dropdown.</p>
<p><strong>Custom Chef Recipes</strong></p>
<p>It actually took us a lot longer to switch from Joyent than I had hoped.  I began the process of investigating EY Cloud a few months ago&#8230; and had delayed the process for a while because I had some real concerns about some parts of our setup.  Gems, packages, etc are all super-simple to set up in EY Cloud&#8230; but certain things are not as easy as a &#8220;checkbox&#8221;.  For instance, we run <a href="http://transfs.com/devblog/2009/04/06/goodbye-backgroundrb-hello-workling-starling/">Starling &amp; Workling for background processing</a> at TransFS.com.  Getting these set up in EY Cloud was non-trivial, because it required writing a custom chef recipe.  Chef is a really amazing server configuration tool that uses a ruby DSL to define changes to your server config.  EngineYard has some <a href="https://cloud-support.engineyard.com/faqs/chef/using-chef-to-customize-your-environment">decent chef documentation</a>, but it still took some time to learn how Chef works before I was comfortable enough with it to get our servers set up properly.</p>
<p>However, once you <a href="http://wiki.opscode.com/display/chef/Cooking+with+Chef">learn to be a master Chef</a>&#8230; it gives you amazing power to set up your server <em>exactly</em> the way you want, and in a totally repeatable way.  In fact, when I need to add a new application server to our cluster&#8230; it should be as simple as flipping a switch, and all of my custom configuration settings will be applied automatically to bring that new server into the proper state for TransFS.com.  Very cool.  Learning Chef is a necessity to getting up and running with EngineYard Cloud&#8230; but it is worth the effort.</p>
<p>There are a few other quirks to EY Cloud that took some work to figure out, such as <a href="https://cloud-support.engineyard.com/faqs/deployment/converting-capistrano-symlinks">migrating capistrano hooks</a>&#8230; but overall I was very pleased with the process.  I had us up-and-running on the new servers in about 30 hours, start to finish, and we only had about 30 minutes of real downtime while I rsync&#8217;d our data over.</p>
<p>Anyway, thanks EngineYard for a great experience so far.  And thanks as well to Joyent for hosting us through our  early days&#8230; we&#8217;ll miss you, but we&#8217;ve found a new home that is much better suited to our needs!</p>
<p>Coming up in the next post:  some lessons learned about Nginx, Rails, and WordPress&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://transfs.com/devblog/2009/11/23/the-switch-to-engineyard-cloud/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>A nice ruby regexp idiom</title>
		<link>http://transfs.com/devblog/2009/10/13/a-nice-ruby-regexp-idiom/</link>
		<comments>http://transfs.com/devblog/2009/10/13/a-nice-ruby-regexp-idiom/#comments</comments>
		<pubDate>Tue, 13 Oct 2009 19:10:23 +0000</pubDate>
		<dc:creator>josh</dc:creator>
				<category><![CDATA[rails]]></category>

		<guid isPermaLink="false">http://transfs.com/devblog/?p=326</guid>
		<description><![CDATA[I recently discovered a simple, but really great, ruby idiom&#8230; that I&#8217;m not sure how I&#8217;ve lived without for all this time. You can query strings with a regexp using the [] array operator: &#34;Grab the number 555 from this string&#34;&#91;/\d+/&#93; &#34;Grab the number 555 from this string&#34;&#91;/number &#40;\d+&#41;/, 1&#93; This will return nil, if [...]]]></description>
			<content:encoded><![CDATA[<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Ftransfs.com%2Fdevblog%2F2009%2F10%2F13%2Fa-nice-ruby-regexp-idiom%2F"><br />
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Ftransfs.com%2Fdevblog%2F2009%2F10%2F13%2Fa-nice-ruby-regexp-idiom%2F&amp;source=joshatwork&amp;style=normal&amp;service=bit.ly" height="61" width="50" /><br />
			</a>
		</div>
<p>I recently discovered a simple, but really great, ruby idiom&#8230; that I&#8217;m not sure how I&#8217;ve lived without for all this time.  You can query strings with a regexp using the [] array operator:</p>

<div class="wp_syntax"><div class="code"><pre class="ruby" style="font-family:monospace;"><span style="color:#996600;">&quot;Grab the number 555 from this string&quot;</span><span style="color:#006600; font-weight:bold;">&#91;</span><span style="color:#006600; font-weight:bold;">/</span>\d<span style="color:#006600; font-weight:bold;">+/</span><span style="color:#006600; font-weight:bold;">&#93;</span>
<span style="color:#996600;">&quot;Grab the number 555 from this string&quot;</span><span style="color:#006600; font-weight:bold;">&#91;</span><span style="color:#006600; font-weight:bold;">/</span>number <span style="color:#006600; font-weight:bold;">&#40;</span>\d<span style="color:#006600; font-weight:bold;">+</span><span style="color:#006600; font-weight:bold;">&#41;</span><span style="color:#006600; font-weight:bold;">/</span>, <span style="color:#006666;">1</span><span style="color:#006600; font-weight:bold;">&#93;</span></pre></div></div>

<p>This will return nil, if the regexp is not matched, and the matched text otherwise.  You can even supply an index number as a second argument, to return a particular matched substring.</p>
<p>So cool.  It is this kindof amazing syntactic sugar that makes Ruby a joy to work with.</p>
]]></content:encoded>
			<wfw:commentRss>http://transfs.com/devblog/2009/10/13/a-nice-ruby-regexp-idiom/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Git Tricks for using Submodules</title>
		<link>http://transfs.com/devblog/2009/09/09/git-tricks-for-using-submodules/</link>
		<comments>http://transfs.com/devblog/2009/09/09/git-tricks-for-using-submodules/#comments</comments>
		<pubDate>Wed, 09 Sep 2009 19:30:02 +0000</pubDate>
		<dc:creator>josh</dc:creator>
				<category><![CDATA[git]]></category>
		<category><![CDATA[gitconfig]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[submodules]]></category>

		<guid isPermaLink="false">http://transfs.com/devblog/?p=322</guid>
		<description><![CDATA[We use a lot of git submodules at TransFS.com. Nearly all of our rails plugins are installed via submodules, as is rails itself. This works very well for keeping multiple repositories synced up with each other&#8230;&#8230;. when it works. The biggest problem with git submodules, in our experience, is that they don&#8217;t automatically stay synced [...]]]></description>
			<content:encoded><![CDATA[<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Ftransfs.com%2Fdevblog%2F2009%2F09%2F09%2Fgit-tricks-for-using-submodules%2F"><br />
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Ftransfs.com%2Fdevblog%2F2009%2F09%2F09%2Fgit-tricks-for-using-submodules%2F&amp;source=joshatwork&amp;style=normal&amp;service=bit.ly" height="61" width="50" /><br />
			</a>
		</div>
<p>We use a lot of git submodules at TransFS.com.  Nearly all of our rails plugins are installed via submodules, as is rails itself.  This works very well for keeping multiple repositories synced up with each other&#8230;&#8230;. when it works.  The biggest problem with git submodules, in our experience, is that they don&#8217;t automatically stay synced up when you pull or checkout different branches.  Consider this scenario:</p>
<ol>
<li>I make a change to our <a href="http://github.com/jkrall/funnel_cake/tree/master">funnelcake plugin</a>, and push that change to the github repo</li>
<li>I then jump back to our main TransFS repo, and commit the updated funnel_cake submodule</li>
<li>I push that change to the TransFS repo&#8230; and call it a day</li>
<li>Later that day, one my fellow developers pulls the latest code from github</li>
<li><em>He forgets to run &#8220;git submodule update&#8221;</em></li>
<li>He codes away, commits, and pushes his changes back to github</li>
</ol>
<p>Now we have a problem&#8230; because the latest changes from my colleague have bumped the funnel_cake submodule <em>backwards</em> to its previous state.  So, even tho the funnel_cake repository has newer code in it, we&#8217;re pointing to the submodule commit hash with the older code.</p>
<p>The best trick we&#8217;ve found for solving this problem is simple:  use a git alias for all pulls and checkouts.  Here&#8217;s what I added to my .gitconfig:</p>

<div class="wp_syntax"><div class="code"><pre class="ini" style="font-family:monospace;"><span style="color: #000066; font-weight:bold;"><span style="">&#91;</span>alias<span style="">&#93;</span></span>
        <span style="color: #000099;">pullup</span> <span style="color: #000066; font-weight:bold;">=</span><span style="color: #660066;"> !git pull &amp;&amp; git submodule update &amp;&amp; git status</span>
        <span style="color: #000099;">checkup</span> <span style="color: #000066; font-weight:bold;">=</span><span style="color: #660066;"> !sh -c 'git checkout $<span style="">1</span> &amp;&amp; git submodule update &amp;&amp; git status' -</span></pre></div></div>

<p>Now, I can run:</p>
<pre>
git pullup transfs
</pre>
<p>instead of:</p>
<pre>
git pull transfs
</pre>
<p>&#8230; I can ensure that my submodules will be updated properly (and also get a handy stat of the repo at the same time)</p>
<p>Same thing with:</p>
<pre>
git checkup new_fancy_branch
</pre>
<p>instead of:</p>
<pre>
git checkout new_fancy_branch
</pre>
<p>It seems to work well!  Now if we could just get everyone to remember to use these commands&#8230;<br />
Do you have any good tricks for avoiding these problems with git submodules?</p>
]]></content:encoded>
			<wfw:commentRss>http://transfs.com/devblog/2009/09/09/git-tricks-for-using-submodules/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Selenium Testing and SWFUpload</title>
		<link>http://transfs.com/devblog/2009/09/03/selenium-testing-and-swfupload/</link>
		<comments>http://transfs.com/devblog/2009/09/03/selenium-testing-and-swfupload/#comments</comments>
		<pubDate>Fri, 04 Sep 2009 01:30:33 +0000</pubDate>
		<dc:creator>josh</dc:creator>
				<category><![CDATA[javascript]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[selenium]]></category>
		<category><![CDATA[testing]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[plugin]]></category>
		<category><![CDATA[selenium-on-rails]]></category>
		<category><![CDATA[tdd]]></category>

		<guid isPermaLink="false">http://transfs.com/devblog/?p=315</guid>
		<description><![CDATA[Here at TransFS.com, we use Selenium tests to verify most of the mission-critical parts of our website.  While a quality RSpec test suite is also very important, there is nothing like an end-to-end integration test that simulates real user behavior to give you confidence that your code is bug-free.  Since we run Ruby on Rails, [...]]]></description>
			<content:encoded><![CDATA[<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Ftransfs.com%2Fdevblog%2F2009%2F09%2F03%2Fselenium-testing-and-swfupload%2F"><br />
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Ftransfs.com%2Fdevblog%2F2009%2F09%2F03%2Fselenium-testing-and-swfupload%2F&amp;source=joshatwork&amp;style=normal&amp;service=bit.ly" height="61" width="50" /><br />
			</a>
		</div>
<p>Here at TransFS.com, we use <a href="http://seleniumhq.org/">Selenium</a> tests to verify most of the mission-critical parts of our website.  While a quality RSpec test suite is also very important, there is nothing like an end-to-end integration test that simulates real user behavior to give you confidence that your code is bug-free.  Since we run Ruby on Rails, we take advantage of the handy <a href="http://github.com/paytonrules/selenium-on-rails/tree">Selenium On Rails plugin</a>&#8230; or more specifically, <a href="http://github.com/jkrall/selenium-on-rails/tree/master">my fork</a> of this plugin with some of our own modifications.</p>
<p>Why do we run our own custom fork?  There are a few reasons, but originally it had to do with our need for some custom hooks that our CruiseControl.Rb continuous integration setup uses to take screenshots of the tests in-action.  (A topic for another post!)  Most recently, however, I needed to hack the plugin to provide a basic workaround to testing code that uses SWFUpload for file uploading.</p>
<p>For the uninitiated, <a href="http://swfupload.org/">SWFUpload</a> is a nice Flash app that provides javascript hooks for  file-selection and uploading with support for multiple files at once.  It is a nice solution for web forms that need to upload more than one file, if you are willing to deal with the added cost of adding Flash to your page.  It isn&#8217;t ideal in all cases, but it has worked OK for us thus far.  However, testing SWFUpload is a real pain&#8230; because Selenium cannot trigger events on flash movie elements.</p>
<p>One solution for this would be to simulate native-OS keyboard and mouse events, and add test commands to click the &#8220;upload&#8221; button and select a file.  In our CI test environment, however, this was not a good option.  Instead, I&#8217;ve opted to test the POST action on the server only&#8230; simulating the file upload without actually triggering SWFUpload to do the work.  To accomplish this, I added a custom selenium action that dynamically inserts a form and file field that are targetted at the corresponding SWFUpload endpoint url.</p>
<p>Here&#8217;s what it looks like:</p>

<div class="wp_syntax"><div class="code"><pre class="javascript" style="font-family:monospace;"><span style="color: #006600; font-style: italic;">// Custom method for uploading a file, simulating SWFUpload</span>
Selenium.<span style="color: #660066;">prototype</span>.<span style="color: #660066;">doSwfUpload</span> <span style="color: #339933;">=</span> <span style="color: #003366; font-weight: bold;">function</span><span style="color: #009900;">&#40;</span>selector<span style="color: #339933;">,</span> filename<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
&nbsp;
	<span style="color: #006600; font-style: italic;">// Grab the SWFUpload element from the DOM</span>
	<span style="color: #003366; font-weight: bold;">var</span> doc <span style="color: #339933;">=</span> <span style="color: #000066; font-weight: bold;">this</span>.<span style="color: #660066;">browserbot</span>.<span style="color: #660066;">getCurrentWindow</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span>.<span style="color: #660066;">document</span><span style="color: #339933;">;</span>
	<span style="color: #003366; font-weight: bold;">var</span> swf_uploader <span style="color: #339933;">=</span> Element.<span style="color: #660066;">select</span><span style="color: #009900;">&#40;</span>$<span style="color: #009900;">&#40;</span>doc.<span style="color: #660066;">body</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span> selector<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#91;</span><span style="color: #CC0000;">0</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">;</span>
&nbsp;
	<span style="color: #006600; font-style: italic;">// Slurp the list of params from SWFUload, and parse them into a Hash</span>
	<span style="color: #003366; font-weight: bold;">var</span> flashvars_s <span style="color: #339933;">=</span> swf_uploader.<span style="color: #660066;">down</span><span style="color: #009900;">&#40;</span><span style="color: #3366CC;">'param[name=flashvars]'</span><span style="color: #009900;">&#41;</span>.<span style="color: #660066;">value</span><span style="color: #339933;">;</span>
	<span style="color: #003366; font-weight: bold;">var</span> flashvars <span style="color: #339933;">=</span> <span style="color: #009900;">&#123;</span><span style="color: #009900;">&#125;</span><span style="color: #339933;">;</span>
	$A<span style="color: #009900;">&#40;</span>flashvars_s.<span style="color: #660066;">split</span><span style="color: #009900;">&#40;</span><span style="color: #009966; font-style: italic;">/&amp;/</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span>.<span style="color: #660066;">each</span><span style="color: #009900;">&#40;</span><span style="color: #003366; font-weight: bold;">function</span><span style="color: #009900;">&#40;</span>kv<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#123;</span>
		<span style="color: #003366; font-weight: bold;">var</span> key <span style="color: #339933;">=</span> unescape<span style="color: #009900;">&#40;</span>kv.<span style="color: #660066;">split</span><span style="color: #009900;">&#40;</span><span style="color: #009966; font-style: italic;">/=/</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#91;</span><span style="color: #CC0000;">0</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
		<span style="color: #003366; font-weight: bold;">var</span> value <span style="color: #339933;">=</span> unescape<span style="color: #009900;">&#40;</span>kv.<span style="color: #660066;">split</span><span style="color: #009900;">&#40;</span><span style="color: #009966; font-style: italic;">/=/</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#91;</span><span style="color: #CC0000;">1</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
		flashvars<span style="color: #009900;">&#91;</span>key<span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> value<span style="color: #339933;">;</span>
	<span style="color: #009900;">&#125;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
	<span style="color: #003366; font-weight: bold;">var</span> params_array <span style="color: #339933;">=</span> $A<span style="color: #009900;">&#40;</span><span style="color: #000066; font-weight: bold;">decodeURI</span><span style="color: #009900;">&#40;</span>flashvars.<span style="color: #660066;">params</span><span style="color: #009900;">&#41;</span>.<span style="color: #660066;">split</span><span style="color: #009900;">&#40;</span><span style="color: #009966; font-style: italic;">/&amp;amp;/</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
	<span style="color: #003366; font-weight: bold;">var</span> params <span style="color: #339933;">=</span> <span style="color: #003366; font-weight: bold;">new</span> Hash<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#123;</span><span style="color: #009900;">&#125;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
	params_array.<span style="color: #660066;">each</span><span style="color: #009900;">&#40;</span><span style="color: #003366; font-weight: bold;">function</span><span style="color: #009900;">&#40;</span>kv<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#123;</span>
		<span style="color: #003366; font-weight: bold;">var</span> key <span style="color: #339933;">=</span> <span style="color: #000066; font-weight: bold;">decodeURI</span><span style="color: #009900;">&#40;</span>kv.<span style="color: #660066;">split</span><span style="color: #009900;">&#40;</span><span style="color: #009966; font-style: italic;">/=/</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#91;</span><span style="color: #CC0000;">0</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
		<span style="color: #003366; font-weight: bold;">var</span> value <span style="color: #339933;">=</span> <span style="color: #000066; font-weight: bold;">decodeURI</span><span style="color: #009900;">&#40;</span>kv.<span style="color: #660066;">split</span><span style="color: #009900;">&#40;</span><span style="color: #009966; font-style: italic;">/=/</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#91;</span><span style="color: #CC0000;">1</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
		params.<span style="color: #660066;">set</span><span style="color: #009900;">&#40;</span>key<span style="color: #339933;">,</span> value<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
	<span style="color: #009900;">&#125;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
	params.<span style="color: #660066;">unset</span><span style="color: #009900;">&#40;</span><span style="color: #3366CC;">'format'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> <span style="color: #006600; font-style: italic;">// Remove the format param, since we don't want to request as json</span>
&nbsp;
	<span style="color: #006600; font-style: italic;">// Grab the SWFUpload form from the hidden IFrame,</span>
	<span style="color: #006600; font-style: italic;">// and insert the key/value params into the form</span>
	<span style="color: #003366; font-weight: bold;">var</span> faker_form <span style="color: #339933;">=</span> Element.<span style="color: #660066;">select</span><span style="color: #009900;">&#40;</span>$$<span style="color: #009900;">&#40;</span><span style="color: #3366CC;">'#selenium_fileupload_iframe'</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#91;</span><span style="color: #CC0000;">0</span><span style="color: #009900;">&#93;</span>.<span style="color: #660066;">contentDocument</span>.<span style="color: #660066;">body</span><span style="color: #339933;">,</span> <span style="color: #3366CC;">'#swfupload_faker_form'</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#91;</span><span style="color: #CC0000;">0</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">;</span>
	params.<span style="color: #660066;">each</span><span style="color: #009900;">&#40;</span><span style="color: #003366; font-weight: bold;">function</span><span style="color: #009900;">&#40;</span>kv<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
		Element.<span style="color: #660066;">insert</span><span style="color: #009900;">&#40;</span>faker_form<span style="color: #339933;">,</span> <span style="color: #009900;">&#123;</span> bottom<span style="color: #339933;">:</span> <span style="color: #3366CC;">'&lt;input type=&quot;hidden&quot; name=&quot;'</span><span style="color: #339933;">+</span>kv.<span style="color: #660066;">key</span><span style="color: #339933;">+</span><span style="color: #3366CC;">'&quot; value=&quot;'</span><span style="color: #339933;">+</span>kv.<span style="color: #660066;">value</span><span style="color: #339933;">+</span><span style="color: #3366CC;">'&quot; /&gt;'</span> <span style="color: #009900;">&#125;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
	<span style="color: #009900;">&#125;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
	<span style="color: #006600; font-style: italic;">// Assign the selected file to the file field</span>
	netscape.<span style="color: #660066;">security</span>.<span style="color: #660066;">PrivilegeManager</span>.<span style="color: #660066;">enablePrivilege</span><span style="color: #009900;">&#40;</span><span style="color: #3366CC;">&quot;UniversalFileRead&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
	<span style="color: #000066; font-weight: bold;">this</span>.<span style="color: #660066;">browserbot</span>.<span style="color: #660066;">replaceText</span><span style="color: #009900;">&#40;</span>faker_form.<span style="color: #660066;">down</span><span style="color: #009900;">&#40;</span><span style="color: #3366CC;">'#swfupload_faker_file'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span> filename<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
	<span style="color: #006600; font-style: italic;">// Assign the URL and submit the Form</span>
	faker_form.<span style="color: #660066;">action</span> <span style="color: #339933;">=</span> flashvars.<span style="color: #660066;">uploadURL</span><span style="color: #339933;">;</span>
	faker_form.<span style="color: #660066;">submit</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
	<span style="color: #006600; font-style: italic;">// Clean up the params</span>
	Element.<span style="color: #660066;">select</span><span style="color: #009900;">&#40;</span>faker_form<span style="color: #339933;">,</span> <span style="color: #3366CC;">'input[type=hidden]'</span><span style="color: #009900;">&#41;</span>.<span style="color: #660066;">each</span><span style="color: #009900;">&#40;</span><span style="color: #003366; font-weight: bold;">function</span><span style="color: #009900;">&#40;</span>e<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#123;</span>
		e.<span style="color: #660066;">remove</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
	<span style="color: #009900;">&#125;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
	<span style="color: #006600; font-style: italic;">// Retarget the IFrame back to the fileupload frame</span>
	$$<span style="color: #009900;">&#40;</span><span style="color: #3366CC;">'#selenium_fileupload_iframe'</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#91;</span><span style="color: #CC0000;">0</span><span style="color: #009900;">&#93;</span>.<span style="color: #660066;">contentWindow</span>.<span style="color: #660066;">location</span> <span style="color: #339933;">=</span> <span style="color: #3366CC;">&quot;TestRunner-fileupload.html&quot;</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span><span style="color: #339933;">;</span></pre></div></div>

<p>In order to make this work, we also need a new &lt;iframe&gt; added to the selenium-core frameset:</p>

<div class="wp_syntax"><div class="code"><pre class="html" style="font-family:monospace;">&lt;iframe name=&quot;selenium_fileupload_iframe&quot; id=&quot;selenium_fileupload_iframe&quot; src=&quot;TestRunner-fileupload.html&quot; style=&quot;width: 1px; height: 1px&quot;&gt;&lt;/iframe&gt;</pre></div></div>

<p>With these changes&#8230; we now have an .rsel command in our tests that looks like this:</p>

<div class="wp_syntax"><div class="code"><pre class="ruby" style="font-family:monospace;">swf_upload <span style="color:#996600;">'#SWFUpload_0'</span>, <span style="color:#996600;">&quot;#{RAILS_ROOT}/public/images/test-upload.png&quot;</span></pre></div></div>

<p>What this will do is:</p>
<ol>
<li>Query the SWFUpload &lt;object&gt; element, and grab the &lt;param&gt; elements from inside it</li>
<li>Parse out these parameters, adding them as &lt;input type=&#8221;hidden&#8221;&gt; tags to the hidden form IFrame</li>
<li>Submit the hidden form, POSTing the file contents and parameters to the same action as the SWFUpload form</li>
<li>&#8230; and clean itself up</li>
</ol>
<p>While not a perfect solution&#8230; this allows us to test the server-side response to file uploads, and that&#8217;s what we cared about most.  If you are using SWFUpload in your application, and have run into the same problem, I&#8217;d love to hear how you solved it!</p>
]]></content:encoded>
			<wfw:commentRss>http://transfs.com/devblog/2009/09/03/selenium-testing-and-swfupload/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>FunnelCake Update</title>
		<link>http://transfs.com/devblog/2009/08/30/funnelcake-update/</link>
		<comments>http://transfs.com/devblog/2009/08/30/funnelcake-update/#comments</comments>
		<pubDate>Sun, 30 Aug 2009 20:59:55 +0000</pubDate>
		<dc:creator>josh</dc:creator>
				<category><![CDATA[analytics]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[funnel]]></category>
		<category><![CDATA[funnelcake]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[plugin]]></category>

		<guid isPermaLink="false">http://transfs.com/devblog/?p=300</guid>
		<description><![CDATA[Last March, I wrote about an internal sales funnel visualization and tracking project that we call FunnelCake. In recent weeks, we&#8217;ve made some significant upgrades to this system&#8230; making it a much more comprehensive tool for visualizing our sales funnel. Much of the code was refactored (or rewritten from the ground up), and the codebase [...]]]></description>
			<content:encoded><![CDATA[<div class="tweetmeme_button" style="float: right; margin-left: 10px;">
			<a href="http://api.tweetmeme.com/share?url=http%3A%2F%2Ftransfs.com%2Fdevblog%2F2009%2F08%2F30%2Ffunnelcake-update%2F"><br />
				<img src="http://api.tweetmeme.com/imagebutton.gif?url=http%3A%2F%2Ftransfs.com%2Fdevblog%2F2009%2F08%2F30%2Ffunnelcake-update%2F&amp;source=joshatwork&amp;style=normal&amp;service=bit.ly" height="61" width="50" /><br />
			</a>
		</div>
<p>Last March, I <a href="http://transfs.com/devblog/2009/03/26/sales-funnel-visualization-and-tracking-with-funnelcake/">wrote about</a> an internal sales funnel visualization and tracking project that we call FunnelCake.</p>
<p>In recent weeks, we&#8217;ve made some significant upgrades to this system&#8230; making it a much more comprehensive tool for visualizing our sales funnel.  Much of the code was refactored (or rewritten from the ground up), and the codebase is now cleaner and more flexible.  In addition, we added a bunch of new features, including:</p>
<ul>
<li>Revised conversion stats logic to make numbers more consistent and easier to read</li>
<li>Viewing of funnel stats using weekly, bi-weekly, and monthly time windows (fixed to the calendar year, rather than the less-useful &#8220;past 14 days&#8221; window that was used previously)</li>
<li>RESTful design for funnel stages, individual states, visitors, etc</li>
<li>Dashboard view for at-a-glance view of primary funnel stage stats</li>
<li>Graphing!  All funnel stages now show graphs of historical conversions</li>
<li>Customized dashboards can be easily built using simple &#8220;widget&#8221; partials for viewing specific stats, graphs, diagrams, tables, etc</li>
<li>All widgets are fully ajax-updating, making the page much more responsive</li>
<li>Filtering!  All funnel stats can now be viewed through landing-page, referer, and visited-page filters.</li>
<li>Caching:  all stat calculation methods now use the Rails cache for storing commonly accessed data points&#8230; making switching between stats much more efficient.</li>
<li>Drill down to individual visitors and view their event page through the site</li>
</ul>
<p>Overall, these changes have made this a much more useful tool for TransFS.com.  We&#8217;re able to keep track of our funnel on a daily basis using the simple dashboard, while also having the ability to dig in and investigate specific visitor segments and campaigns.  FunnelCake will allow us to answer more complicated questions like:  &#8220;Do the customers who arrive at our site via google adwords behave different than those who arrive via organic search?&#8221; and &#8220;Which step in our signup process is the least effective?&#8221;</p>
<p>Here are some new screenshots of the TransFS.com FunnelCake dashboard:</p>
<div id="attachment_302" class="wp-caption figure alignnone" style="width: 150px"><a href="http://transfs.com/devblog/wp-content/uploads/2009/08/Picture-6.png"><img class="size-thumbnail wp-image-302  " title="Funnel Dashboard" src="http://transfs.com/devblog/wp-content/uploads/2009/08/Picture-6-150x150.png" alt="Funnel Dashboard" width="150" height="150" /></a><p class="legend" style="width: 126px">Funnel Dashboard</p></div>
<div id="attachment_303" class="wp-caption figure alignnone" style="width: 150px"><a href="http://transfs.com/devblog/wp-content/uploads/2009/08/Picture-7.png"><img class="size-thumbnail wp-image-303  " title="Funnel Stage Detail" src="http://transfs.com/devblog/wp-content/uploads/2009/08/Picture-7-150x150.png" alt="Funnel Stage Detail" width="150" height="150" /></a><p class="legend" style="width: 126px">Funnel Stage Detail</p></div>
<div id="attachment_304" class="wp-caption figure alignnone" style="width: 150px"><a href="http://transfs.com/devblog/wp-content/uploads/2009/08/Picture-8.png"><img class="size-thumbnail wp-image-304  " title="Funnel Overview" src="http://transfs.com/devblog/wp-content/uploads/2009/08/Picture-8-150x150.png" alt="Funnel Overview" width="150" height="150" /></a><p class="legend" style="width: 126px">Funnel Overview</p></div>
<p>FunnelCake is available as an open-source Rails plugin for anyone to investigate, use, and improve upon.  You can find my <a href="http://github.com/jkrall/funnel_cake/tree/master">GitHub repository</a> here.  If you have any ideas on how to make it better&#8230; fork away and send me a pull request!</p>
]]></content:encoded>
			<wfw:commentRss>http://transfs.com/devblog/2009/08/30/funnelcake-update/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
