<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><rss 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/" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:creativeCommons="http://backend.userland.com/creativeCommonsRssModule" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" version="2.0">

<channel>
	<title>theAdmin</title>
	
	<link>http://theadmin.org</link>
	<description>Helping new entrepreneurs build a successful software business.</description>
	<lastBuildDate>Thu, 23 May 2013 18:48:52 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.5.1</generator>
		<atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/rss+xml" href="http://feeds.feedburner.com/theAdmin" /><feedburner:info uri="theadmin" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><geo:lat>45.49106</geo:lat><geo:long>-122.803594</geo:long><creativeCommons:license>http://creativecommons.org/licenses/by-nc-sa/2.0/</creativeCommons:license><image><link>http://www.feedburner.com</link><url>http://www.feedburner.com/fb/images/pub/fb_pwrd.gif</url><title>This Feed Powered by FeedBurner.com</title></image><feedburner:emailServiceId>theAdmin</feedburner:emailServiceId><feedburner:feedburnerHostname>http://feedburner.google.com</feedburner:feedburnerHostname><feedburner:feedFlare href="http://add.my.yahoo.com/rss?url=http%3A%2F%2Ffeeds.feedburner.com%2FtheAdmin" src="http://us.i1.yimg.com/us.yimg.com/i/us/my/addtomyyahoo4.gif">Subscribe with My Yahoo!</feedburner:feedFlare><feedburner:feedFlare href="http://www.newsgator.com/ngs/subscriber/subext.aspx?url=http%3A%2F%2Ffeeds.feedburner.com%2FtheAdmin" src="http://www.newsgator.com/images/ngsub1.gif">Subscribe with NewsGator</feedburner:feedFlare><feedburner:feedFlare href="http://feeds.my.aol.com/add.jsp?url=http%3A%2F%2Ffeeds.feedburner.com%2FtheAdmin" src="http://o.aolcdn.com/favorites.my.aol.com/webmaster/ffclient/webroot/locale/en-US/images/myAOLButtonSmall.gif">Subscribe with My AOL</feedburner:feedFlare><feedburner:feedFlare href="http://www.bloglines.com/sub/http://feeds.feedburner.com/theAdmin" src="http://www.bloglines.com/images/sub_modern11.gif">Subscribe with Bloglines</feedburner:feedFlare><feedburner:feedFlare href="http://www.netvibes.com/subscribe.php?url=http%3A%2F%2Ffeeds.feedburner.com%2FtheAdmin" src="http://www.netvibes.com/img/add2netvibes.gif">Subscribe with Netvibes</feedburner:feedFlare><feedburner:feedFlare href="http://fusion.google.com/add?feedurl=http%3A%2F%2Ffeeds.feedburner.com%2FtheAdmin" src="http://buttons.googlesyndication.com/fusion/add.gif">Subscribe with Google</feedburner:feedFlare><feedburner:feedFlare href="http://www.pageflakes.com/subscribe.aspx?url=http%3A%2F%2Ffeeds.feedburner.com%2FtheAdmin" src="http://www.pageflakes.com/ImageFile.ashx?instanceId=Static_4&amp;fileName=ATP_blu_91x17.gif">Subscribe with Pageflakes</feedburner:feedFlare><item>
		<title>Developing on a VPS</title>
		<link>http://feedproxy.google.com/~r/theAdmin/~3/5jGUaFFBjIo/</link>
		<comments>http://theadmin.org/articles/developing-on-a-vps/#comments</comments>
		<pubDate>Thu, 23 May 2013 18:48:52 +0000</pubDate>
		<dc:creator>Eric Davis</dc:creator>
				<category><![CDATA[General]]></category>
		<category><![CDATA[devops]]></category>
		<category><![CDATA[freelance]]></category>
		<category><![CDATA[infrastructure]]></category>
		<category><![CDATA[puppet]]></category>
		<category><![CDATA[servers]]></category>
		<category><![CDATA[sysadmin]]></category>
		<category><![CDATA[vps]]></category>

		<guid isPermaLink="false">http://theadmin.org/?p=2505</guid>
		<description><![CDATA[Everyone should take some time now and then to look for optimizations in their work environment. For a software developer like myself, this usually involves tools. Also, as a freelancer there are a lot of optimizations that can be done in getting a new client setup, both the business processes (onboarding) as well as getting [...]]]></description>
				<content:encoded><![CDATA[<p>Everyone should take some time now and then to look for optimizations in their work environment. For a software developer like myself, this usually involves tools. Also, as a freelancer there are a lot of optimizations that can be done in getting a new client setup, both the business processes (onboarding) as well as getting their application up and running.</p>
<p>One difficulty I had in the past was when I worked on multiple clients on my laptop, was setting one up could disrupt another. In the Ruby community this commonly was because different versions of gems were used and the different client applications wouldn&#8217;t specify them correctly. As you can imagine, this meant I had a huge productivity drain when I had to switch from project to project.</p>
<p>(This was pre-Bundler. But even now there are gem issues and other component issues that make this a time drain.)</p>
<h3>Virtual Private (Development) Server</h3>
<p>Then last Fall, in an effort to delay a new laptop purchase as long as possible, I decided to use a VPS as my development environment instead of my laptop. At the time my laptop was aging and I&#8217;d be running out of resources on a weekly basis (disk space, RAM). Add to that several years of client projects installed, uninstalled, and never fully purged and I was looking forward to using a clean VPS to develop on.</p>
<p>Since I have a puppet infrastructure setup already, including a CI server, taking the puppet configuration from other servers to spin up a development VPS was easy.</p>
<p>Seven months later, I can now say that <strong>using a dedicated VPS for a client project has been one of the best infrastructure decisions I&#8217;ve made in years</strong>. The benefits I get out of it are quite amazing.</p>
<h3>Writing code in the cloud</h3>
<p>With a VPS in the cloud, I can easily have other developers or my client connect to it to see the development versions of code I&#8217;m working on. I&#8217;ve had my client on the phone looking at my server while I made UI changes live, which is normally something you have to do in-person. I&#8217;ve also added a second account to do pair programming with another developer and it took all of five minutes.</p>
<p>Having the entire configuration (except for 2 manual steps) in puppet meant that I could destroy the server and get a fresh one with a little effort. One time my VPS started lagging pretty bad, even typing on the SSH console had a noticeable delay. So I make sure my changes were saved in Dropbox and rebuilt the server in under an hour (about 55 minutes of puppet and Ubuntu running, about 5 minutes of my actual time).</p>
<p>When doing my <a href="http://theadmin.org/articles/working-freelance-one-on-three-off/">one week on schedule</a>, I setup the server the Sunday before I start and destroy it at the end of the week. That not only decreases my costs but it also makes things a bit more secure since I don&#8217;t have a public server running with my clients&#8217; code.</p>
<h3>Scalibility</h3>
<p>Using a VPS makes it easy to scale it up if I need to. One client&#8217;s application went through a major growth period where our test suite increased three-fold over a few weeks. Before I was using a small 1GB server for them but that became so slow that upgrading to a 2GB saved me at least a few hours each week. With physical hardware on my desk, that wouldn&#8217;t have been possible or it would have resulted in a few hours or days off while I move everything over. Total time upgrading the VPS, about 30 minutes.</p>
<p>Having my development environment in the cloud also gives me a bit of resiliency. Since all I need is SSH and a web browser, if something happened to my laptop I can easily grab another computer and get right back to work. Worst case I could run to Best Buy and buy a $400 laptop to get by. Although I haven&#8217;t tried it with a client project yet, I could in theory use my ipad too.</p>
<p>I also have another form of resiliency. If the VPS crashes or dies, I can spin up a new one and get back to work with a small delay. Since all of my code is in Dropbox and git, the server configuration is in puppet, and I dump copies of the database regularly I don&#8217;t have to worry about losing anything.</p>
<h3>Isolation</h3>
<p>Each client gets their own VPS so their applications are completely isolated. Not only in terms of gems, libraries, or system services but also in terms of legal boundaries. I haven&#8217;t seen a client NDA or contract that specified their code needed to be on an isolated system yet, but I have heard stories from other consultants where they are only allowed to work on a locked down client workstation.</p>
<h3>Digital Ocean</h3>
<p>At the moment I&#8217;ve had some great success with <a href="https://www.digitalocean.com/">Digital Ocean&#8217;s servers</a>. Since they bill by the hour, I&#8217;ve been ordering a 4GB server for my week on and spending about $10 each time. Compared to a week&#8217;s revenue from consulting, $10 is a pittance.</p>
<h3>Vagrant and local VMs</h3>
<p>Now that I&#8217;ve upgraded my laptop and have a lot more spare resources, I&#8217;m beginning to explore using Vagrant for my own projects. But having a remote VPS is still useful because it&#8217;s publicly demonstrable to people who aren&#8217;t physically with you. For <a href="http://www.chirkhr.com">Chirk HR</a> and my other projects a local VM should work just fine. A nice bonus is that is I can use the same puppet configuration for both a VPS and a Vagrant VM.</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/theAdmin?a=5jGUaFFBjIo:mOTwleY_UfU:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/theAdmin?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/theAdmin?a=5jGUaFFBjIo:mOTwleY_UfU:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/theAdmin?i=5jGUaFFBjIo:mOTwleY_UfU:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/theAdmin?a=5jGUaFFBjIo:mOTwleY_UfU:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/theAdmin?i=5jGUaFFBjIo:mOTwleY_UfU:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/theAdmin?a=5jGUaFFBjIo:mOTwleY_UfU:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/theAdmin?i=5jGUaFFBjIo:mOTwleY_UfU:F7zBnMyn0Lo" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/theAdmin/~4/5jGUaFFBjIo" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://theadmin.org/articles/developing-on-a-vps/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://theadmin.org/articles/developing-on-a-vps/</feedburner:origLink></item>
		<item>
		<title>Working freelance one on, three off</title>
		<link>http://feedproxy.google.com/~r/theAdmin/~3/M3eDQuZ4p0U/</link>
		<comments>http://theadmin.org/articles/working-freelance-one-on-three-off/#comments</comments>
		<pubDate>Tue, 14 May 2013 17:30:30 +0000</pubDate>
		<dc:creator>Eric Davis</dc:creator>
				<category><![CDATA[General]]></category>
		<category><![CDATA[business]]></category>
		<category><![CDATA[freelance]]></category>
		<category><![CDATA[products]]></category>
		<category><![CDATA[projects]]></category>
		<category><![CDATA[scheduling]]></category>

		<guid isPermaLink="false">http://theadmin.org/?p=2500</guid>
		<description><![CDATA[Over the past few years freelancing I&#8217;ve been testing different styles of scheduling my client services. Typically freelance projects are either spread out (20/hours a week for 6 weeks) or are bunched up into large chunks (40/hours a week for 3 weeks). Neither of these have felt that good to me. Spread out or in [...]]]></description>
				<content:encoded><![CDATA[<p>Over the past few years freelancing I&#8217;ve been testing different styles of scheduling my client services. Typically freelance projects are either spread out (20/hours a week for 6 weeks) or are bunched up into large chunks (40/hours a week for 3 weeks). Neither of these have felt that good to me.</p>
<h3>Spread out or in large chunks</h3>
<p>The spread out projects almost always require that I have multiple client projects running at the same time. That causes quite a bit of switching costs when I jump from project to project. This switching cost isn&#8217;t billable so I end up losing that time, and feel panicked since I now have to make up that time.</p>
<p>The large chunk of time projects don&#8217;t have as many switching costs, but they have their own problems. The two big ones for me are</p>
<ol>
<li>burnout due to intense time demands and</li>
<li>fostering the feast-and-famine cycle (start/stop/start)</li>
</ol>
<p>The burnout problem I&#8217;ve been able to work around by not over-committing and keeping my days short (no 12-hour billable days for me, thank you very much). The start/stop/start problem is worse though.</p>
<p>I&#8217;ve finally figured out how to market my client services, at least well enough that I can find some great clients when work slows down. The problem is, it takes me a month or two to scale up my marketing from a standstill. With a large chunk project, that means I don&#8217;t have the time to keep my marketing running, so at the end of each project I&#8217;ve had to reinvigorate my marketing yet again.</p>
<h3>A better schedule</h3>
<p>I think I&#8217;ve found a better way to schedule client work though. One that works well with my schedule, my marketing, and my product development. That schedule is what <a href="http://blog.statuspage.io/funding-your-startup-with-a-one-week-on-three-weeks-off-setup">StatusPage</a> names the &#8220;one on / three off&#8221; schedule.</p>
<p>Basically, instead of working part-time on client projects and your product or doing full-time bursts of client projects with an idle gap in-between projects, you schedule projects so you work full-time but only for a week each month (the one on part). The other three weeks of the month you can work on other things: marketing, building products, vacations.</p>
<p>I&#8217;ve tried this for about two years now, with a few different clients. I have to say, I love it and it&#8217;s my favorite scheduling mechanism.</p>
<h3>Why?</h3>
<p>My clients like it too, despite the drawbacks at the first glance.</p>
<h4>The 3 weeks off</h4>
<p>As I mentioned earlier, the biggest benefit for me is that I can schedule around my week of client work. So if I need to build up <a href="http://www.chirkhr.com">Chirk HR</a> or another product, I have 3 weeks to work on it. Or if I&#8217;m going to be scaling up my client services marketing again, I&#8217;ll have time to get it going before it&#8217;s critical.</p>
<h4>Focus 100% on my client</h4>
<p>With a solid FT week of client work, it&#8217;s also easier for me to focus and get into the flow with them. Usually a few hours is needed on Monday to ramp back up and see what&#8217;s changed in a month but my mid-morning Monday I&#8217;m already cranking away. That usually doesn&#8217;t stop until Friday afternoon. This means my attention stays at it&#8217;s peak and I don&#8217;t get distracted by other clients or other projects.</p>
<h4>Time-boxed</h4>
<p>The nice thing about being on for a week from the client&#8217;s perspective, they will know when I will be working and (based on previous estimates) can predict how much I&#8217;ll get done during that week. That means the week before they can do all of their planning, get ready for me, and then just check back at the end of the week. Some clients will take a more active role in the project, wither it&#8217;s to attend a daily check-in meeting with me or to schedule open office hours for me to come to them with questions. Either way, since I&#8217;m only on for a week at a time their commitment levels are limited to that week too.</p>
<h4>Emergencies and fire-drills</h4>
<p>One downside I thought would hurt this schedule would be that my clients would need me during my off-weeks. Though they have needed help for emergencies, for the most part they haven&#8217;t had to contact me or schedule any non-standard days. With a few exceptions, most of the emergency fire-drills in projects are caused by someone rushing to do something that isn&#8217;t really urgent.</p>
<p>So that&#8217;s my one-on, three-off schedule I&#8217;ve been using. Since I already have a happy client, I&#8217;m not accepting anyone new right now but I might re-open it again this August or September (2013). If you&#8217;d like to know when I do open it back up or if you&#8217;d like to hear more about my business and what goes on behind the scenes, <a href="http://www.littlestreamsoftware.com/newsletter">signup for my newsletter</a>.</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/theAdmin?a=M3eDQuZ4p0U:BzggmYJjJYc:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/theAdmin?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/theAdmin?a=M3eDQuZ4p0U:BzggmYJjJYc:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/theAdmin?i=M3eDQuZ4p0U:BzggmYJjJYc:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/theAdmin?a=M3eDQuZ4p0U:BzggmYJjJYc:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/theAdmin?i=M3eDQuZ4p0U:BzggmYJjJYc:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/theAdmin?a=M3eDQuZ4p0U:BzggmYJjJYc:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/theAdmin?i=M3eDQuZ4p0U:BzggmYJjJYc:F7zBnMyn0Lo" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/theAdmin/~4/M3eDQuZ4p0U" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://theadmin.org/articles/working-freelance-one-on-three-off/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		<feedburner:origLink>http://theadmin.org/articles/working-freelance-one-on-three-off/</feedburner:origLink></item>
		<item>
		<title>Leaving Feedburner</title>
		<link>http://feedproxy.google.com/~r/theAdmin/~3/loNoARQtIa8/</link>
		<comments>http://theadmin.org/articles/leaving-feedburner/#comments</comments>
		<pubDate>Mon, 22 Apr 2013 18:38:19 +0000</pubDate>
		<dc:creator>Eric Davis</dc:creator>
				<category><![CDATA[General]]></category>
		<category><![CDATA[feedburner]]></category>
		<category><![CDATA[meta]]></category>
		<category><![CDATA[rss]]></category>

		<guid isPermaLink="false">http://theadmin.org/?p=2496</guid>
		<description><![CDATA[I have just a short post today. Due to the closure of Google Reader and the stagnation of Feedburner, theAdmin.org will no longer be using Feedburner for the RSS feed. The Feedburner url will still be active but the new url to subscribe via RSS is: http://theadmin.org/feed/ The very best way to keep updated on [...]]]></description>
				<content:encoded><![CDATA[<p>I have just a short post today. Due to the closure of Google Reader and the stagnation of Feedburner, theAdmin.org will no longer be using Feedburner for the RSS feed. The Feedburner url will still be active but the new url to subscribe via RSS is:</p>
<p><a href="http://theadmin.org/feed/">http://theadmin.org/feed/</a></p>
<p>The very best way to keep updated on any posts or events is to subscribe by email on the sidebar to the right. My email subscribers usually get more information that whats posted on the site alone.</p>
<p>Make sure to update your RSS subscription today.</p>
<p>Eric Davis</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/theAdmin?a=loNoARQtIa8:u9PtfNgnBSE:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/theAdmin?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/theAdmin?a=loNoARQtIa8:u9PtfNgnBSE:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/theAdmin?i=loNoARQtIa8:u9PtfNgnBSE:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/theAdmin?a=loNoARQtIa8:u9PtfNgnBSE:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/theAdmin?i=loNoARQtIa8:u9PtfNgnBSE:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/theAdmin?a=loNoARQtIa8:u9PtfNgnBSE:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/theAdmin?i=loNoARQtIa8:u9PtfNgnBSE:F7zBnMyn0Lo" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/theAdmin/~4/loNoARQtIa8" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://theadmin.org/articles/leaving-feedburner/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://theadmin.org/articles/leaving-feedburner/</feedburner:origLink></item>
		<item>
		<title>The ChirkHR Pivot</title>
		<link>http://feedproxy.google.com/~r/theAdmin/~3/fnX7FVLZjd4/</link>
		<comments>http://theadmin.org/articles/chirkhr-pivot/#comments</comments>
		<pubDate>Tue, 02 Apr 2013 17:06:53 +0000</pubDate>
		<dc:creator>Eric Davis</dc:creator>
				<category><![CDATA[General]]></category>
		<category><![CDATA[business]]></category>
		<category><![CDATA[chirkhr]]></category>
		<category><![CDATA[pivot]]></category>
		<category><![CDATA[startup]]></category>

		<guid isPermaLink="false">http://theadmin.org/?p=2482</guid>
		<description><![CDATA[Admitting you made a mistake is difficult. Doubly so when you aren&#8217;t sure that it was a mistake. But after a few months of running ChirkHR as an applicant tracking system, I think I made a mistake. The Sales Safari This lingering doubt caused me to go back to the market research to see what [...]]]></description>
				<content:encoded><![CDATA[<p>Admitting you made a mistake is difficult. Doubly so when you aren&#8217;t sure that it was a mistake.</p>
<p>But after a few months of running <a href="http://www.chirkhr.com">ChirkHR</a> as an applicant tracking system, I think I made a mistake.</p>
<h3>The Sales Safari</h3>
<p>This lingering doubt caused me to go back to the market research to see what my potential customers were actually doing (this is part of the 30&#215;500 Sales Safari, which I hope Amy Hoy releases outside of 30&#215;500&#8230;). Anyways, mid-February I spent a few dozen hours researching, reading, and digging though the HR market to see why ChirkHR wasn&#8217;t working as well as I hoped.</p>
<p>I eventually came to the realization that an applicant tracking system was useful to them (good) but it became clear that it wouldn&#8217;t be a good fit for me and my company (bad). Needless to say, that week was a very depressing week.</p>
<p>But there was a glimmer of hope&#8230;</p>
<p>As part of my research Amy taught me to mark down topics and pains that came up over and over. From this I started to spot a new topic that I was both energized about but (more importantly) was something that I felt that would be a great fit for me.</p>
<p>This topic was employee training and development. Specifically, how human resources manages and makes decisions about training.</p>
<h3>The Pivot</h3>
<p>With this new topic, I dived deeper into my research and refined my vision of what ChirkHR would become. After reviewing my notes, I decided to pursue this new opportunity and <a href="http://www.chirkhr.com/from-applicant-tracking-system-to-training-tracker-chirkhrs-pivot/">pivot ChirkHR</a>.</p>
<p>Code-wise this change was almost a complete rewrite of the software behind ChirkHR. The only parts I&#8217;ll be keeping from the old system was the account management system and the overall UI layout (present tense because I&#8217;m still running the old version for some customers behind a <a href="https://github.com/jnunemaker/flipper">feature flipper</a>).</p>
<p>Even though it&#8217;s a rewrite, since ChirkHR was little more than a MVP there wasn&#8217;t a lot of code I had to change for the pivot. This which allowed me to move fast and after a couple weeks of development, I released the new ChirkHR training features to the servers.</p>
<p>The business side of ChirkHR is pivoting too, but it&#8217;s less of a change than the software side. Yes, ChirkHR will be solving a different problem for a different set of customers but they are similar enough that much of the existing business systems will still work exactly as before. The biggest risk, as always with a young business, will be reaching potential customers.</p>
<h3>The Next 6 months</h3>
<p>I&#8217;ll be honest, I don&#8217;t know what the next six months will bring for ChirkHR. I have several changes I&#8217;m thinking about for the software and I&#8217;m starting to scale up the marketing machine again. But other than those vague directions, there is a lot of unknown out there for me.</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/theAdmin?a=fnX7FVLZjd4:IR00sR3fxsw:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/theAdmin?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/theAdmin?a=fnX7FVLZjd4:IR00sR3fxsw:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/theAdmin?i=fnX7FVLZjd4:IR00sR3fxsw:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/theAdmin?a=fnX7FVLZjd4:IR00sR3fxsw:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/theAdmin?i=fnX7FVLZjd4:IR00sR3fxsw:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/theAdmin?a=fnX7FVLZjd4:IR00sR3fxsw:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/theAdmin?i=fnX7FVLZjd4:IR00sR3fxsw:F7zBnMyn0Lo" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/theAdmin/~4/fnX7FVLZjd4" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://theadmin.org/articles/chirkhr-pivot/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		<feedburner:origLink>http://theadmin.org/articles/chirkhr-pivot/</feedburner:origLink></item>
		<item>
		<title>Three Book Diet</title>
		<link>http://feedproxy.google.com/~r/theAdmin/~3/f2oDebGZ0FA/</link>
		<comments>http://theadmin.org/articles/three-book-diet/#comments</comments>
		<pubDate>Tue, 20 Nov 2012 19:11:55 +0000</pubDate>
		<dc:creator>Eric Davis</dc:creator>
				<category><![CDATA[General]]></category>
		<category><![CDATA[books]]></category>
		<category><![CDATA[experiment]]></category>
		<category><![CDATA[reading]]></category>

		<guid isPermaLink="false">http://theadmin.org/?p=2474</guid>
		<description><![CDATA[I&#8217;ve mentioned it before that I really enjoy reading but have found myself consuming too much information without applying it. There are dozens of books that I&#8217;ve read, all of which had good ideas for my life or business, but I haven&#8217;t put the work in and took action on those ideas. With the birth [...]]]></description>
				<content:encoded><![CDATA[<p>I&#8217;ve mentioned it before that I really enjoy reading but have found myself consuming too much information without applying it. There are dozens of books that I&#8217;ve read, all of which had good ideas for my life or business, but I haven&#8217;t put the work in and took action on those ideas.</p>
<p>With the birth of Piper and entering a busy period in <a href="http://www.littlestreamsoftware.com">Little Stream Software&#8217;s client services</a>, I&#8217;m finding that I&#8217;m placing a higher standard on how I spend my time. Which means that my non-fiction reading list isn&#8217;t getting as much time as it used to.</p>
<p>But at the same time I don&#8217;t want to stop growing my knowledge&#8230;</p>
<h3>3 Book Diet</h3>
<p>Then I came across a post (event?) by Chris Brogan called the <a href="http://www.chrisbrogan.com/3bd/">3 Book Diet</a>. Basically for an entire year he is limiting himself to only reading 3 books. This means he will be reading each book multiple times and digging deeper into each one (and theoretically putting the book&#8217;s ideas into action more).</p>
<h3>My changes</h3>
<p>I&#8217;ve decided to do the 3 Book Diet, with a few modifications:</p>
<ul>
<li>I&#8217;ve picked 3 non-fiction books I can read.</li>
<li>I can only read those non-fiction books for the next few months (3? 6? I don&#8217;t know how long yet).</li>
<li>I can read unlimited fiction or novels. These are pleasure reading and how I fall asleep.</li>
<li>An exception will be made for two non-fiction baby books I have. I&#8217;ve already started them and one is a month-by-month book.</li>
</ul>
<h3>My 3 books</h3>
<p>After a few weeks of thinking about my picks, the three books I decided on:</p>
<ol>
<li><a href="http://www.amazon.com/Power-Habit-What-Life-Business/dp/1400069289">Power of Habit by Charles Duhigg</a>. I&#8217;ve read this book already and I really enjoyed the mechanical aspect of building habits. I&#8217;ve been adding and removing habits for a few years now but like most people I struggle with them. I&#8217;m hoping by digging deep into the Power of Habit, I can build the habit of habit change. #meta</li>
<li><a href="http://www.amazon.com/How-Win-Friends-Influence-People/dp/1439167346">How To Win Friends &amp; Influence People by Dale Carnegie</a>. This book is a classic but I last read it back in 2001 or 2002. The title is a bit off-putting but the content is great, especially if you have to communicate or work with humans.</li>
<li><a href="http://unicornfree.com/30x500/">30&#215;500 lessons by Amy Hoy</a>. Yes, this isn&#8217;t technically a book but there is more meaty content here than many &#8220;real&#8221; business books. I&#8217;m hoping by revisiting these lessons I&#8217;ll improve my product building skills and figure out why <a href="http://www.chirkhr.com">Chirk HR</a> isn&#8217;t growing like I planned.</li>
</ol>
<p>I have high hopes for this little experiment. Worst case, I&#8217;ll get bored of reading 3 great books and want to read something new when it&#8217;s over. Best case, by really mastering what each book teaches I&#8217;ll be able to make dramatic improvements in those areas of my life.</p>
<p>Stay tuned, I&#8217;d like to revisit this post with some results later.</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/theAdmin?a=f2oDebGZ0FA:Paw8LGYRv7k:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/theAdmin?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/theAdmin?a=f2oDebGZ0FA:Paw8LGYRv7k:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/theAdmin?i=f2oDebGZ0FA:Paw8LGYRv7k:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/theAdmin?a=f2oDebGZ0FA:Paw8LGYRv7k:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/theAdmin?i=f2oDebGZ0FA:Paw8LGYRv7k:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/theAdmin?a=f2oDebGZ0FA:Paw8LGYRv7k:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/theAdmin?i=f2oDebGZ0FA:Paw8LGYRv7k:F7zBnMyn0Lo" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/theAdmin/~4/f2oDebGZ0FA" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://theadmin.org/articles/three-book-diet/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		<feedburner:origLink>http://theadmin.org/articles/three-book-diet/</feedburner:origLink></item>
		<item>
		<title>The 3 Common Types of Code Reviews</title>
		<link>http://feedproxy.google.com/~r/theAdmin/~3/KedhoARfJ_g/</link>
		<comments>http://theadmin.org/articles/code-reviews/#comments</comments>
		<pubDate>Thu, 25 Oct 2012 18:51:00 +0000</pubDate>
		<dc:creator>Eric Davis</dc:creator>
				<category><![CDATA[General]]></category>
		<category><![CDATA[code]]></category>
		<category><![CDATA[code reviews]]></category>
		<category><![CDATA[code-reading]]></category>
		<category><![CDATA[development]]></category>
		<category><![CDATA[optimization]]></category>
		<category><![CDATA[process]]></category>

		<guid isPermaLink="false">http://theadmin.org/?p=2469</guid>
		<description><![CDATA[Much of my development involves reviewing and reading code, both formally and informally. You will only write code once, might modify it a few times, but you will read it over and over. I found code reviews are just a formalization of this code reading process. I&#8217;ve noticed a few different types of code reviews, [...]]]></description>
				<content:encoded><![CDATA[<p>Much of my development involves reviewing and reading code, both formally and informally. You will only write code once, might modify it a few times, but you will read it over and over. I found code reviews are just a formalization of this code reading process.</p>
<p>I&#8217;ve noticed a few different types of code reviews, all based on what the goal of the review is.</p>
<h2>Getting to Know You Code Review</h2>
<p>The Getting to Know You code review is done so you can understand what the code is doing in general. Typically these are done on an entire application or library as a whole, where there is a large volume of code to review.</p>
<p>The goal of this is to get to know the code and be able to navigate around. What I try to do is to get a good understanding of the major components and how they interact. For Ruby on Rails applications this means the relationship between:</p>
<ul>
<li>the urls (routes) and controllers &#8211; how the users will see the application&#8217;s workflows</li>
<li>the controllers and the models &#8211; how the data will be retrieved and updated</li>
<li>the models to other models &#8211; how the data is related</li>
</ul>
<p>A few things to watch out for are <a href="http://en.wikipedia.org/wiki/God_object">God Objects</a>, too many classes, two few classes (both symptoms of design problems), and a lack of tests.</p>
<h2>We Have a Problem Code Review</h2>
<p>The We Have a Problem code reviews are a different beast. These come up because of a specific problem in the application: maybe there is a bug, performance has degraded, or a (human) process has gone bad.</p>
<p>This type of code review is very similar to the process you use when trying to reproduce a bug, in fact I&#8217;d consider reproducing a bug to be a code review of sorts. Typically these code reviews follow a process like:</p>
<ol>
<li>Create a hypothesis of the cause of the problem,</li>
<li>attempt to either prove or disprove the hypothesis through experiments,</li>
<li>review the results,</li>
<li>Refine the hypothesis and iterate or discover of the bug&#8217;s root cause.</li>
</ol>
<p>(This is similar to the <a href="http://en.wikipedia.org/wiki/Scientific_method">Scientific Method</a>)</p>
<p>Typically you&#8217;d start with a very broad hypothesis and narrow it down as you cycle through the steps. Finally you&#8217;d hone in on the problem areas and then have enough to be able to come up with a solution.</p>
<p>When you compare this type of review to the Getting to Know You, the We Have a Problem has a few major differences:</p>
<ul>
<li>the goal of this is to find a problem so it can be fixed</li>
<li>the code needs to be understood just enough to solve the problem</li>
<li>the review starts very broadly and then narrows down its search quickly</li>
</ul>
<p>So in this case, the code review is merely a means to an end, fixing the problem.</p>
<h2>The Refresher Code Review</h2>
<p>The Refresher code review is designed to get back up to speed on how some code works. You might have written the code in the past, or maybe it is some code you&#8217;ve used before. In either case, the code is familiar but you need to do a quick review to become reacquainted with it. Typically these reviews are short, very informal, and most developers do them without thinking about them. Whenever you open up a class and scan through its methods and data, you&#8217;re doing a Refresher code review.</p>
<p>There isn&#8217;t much to say about the Refresher. It&#8217;s a lot like the Getting to Know You in that it can be broad, but it&#8217;s like the We Have a Problem in that you might only focus on a few classes or objects.</p>
<p>The important thing to remember is that the Refresher&#8217;s goal is to help you understand a section of code again so you can work on something.</p>
<h2>Many Subtypes</h2>
<p>These are a few different types of code reviews that I do and have seen other people do. They encompass many of the different reasons someone will want to do a code review and there are a few subtypes of code reviews that are used in specific cases. Like starting a rescue project (Getting to Know You) or a security audit (We Have a Problem). There are probably other major types of code reviews but these are the ones I come across again and again.</p>
<p>There is one final type of code review that I think developers should do more of (no, not a dramatic reading of the code)&#8230;</p>
<h2>One Last Type, For Fun</h2>
<p>That&#8217;s doing a code review for fun, enjoyment, and to learn. Sitting down with a chunk of code, a tasty beverage, and spending an hour reading and reasoning through some code just for the fun of it. I&#8217;ve done it with prototype.js, Rails, and capistranio in the past and that knowledge is still helping me today.</p>
<div class="woo-sc-box tick  rounded full">If you&#8217;ve never done a code review for enjoyment, post what you&#8217;re interested in (topic) and what programming languages you know in the comments below and I&#8217;ll try to make a recommendation for you.</div>
<p>They don&#8217;t take a lot of time and you can do them with very little equipment (I&#8217;ve printed out code before and also read some on my iPad).</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/theAdmin?a=KedhoARfJ_g:rssZkXN_7lE:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/theAdmin?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/theAdmin?a=KedhoARfJ_g:rssZkXN_7lE:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/theAdmin?i=KedhoARfJ_g:rssZkXN_7lE:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/theAdmin?a=KedhoARfJ_g:rssZkXN_7lE:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/theAdmin?i=KedhoARfJ_g:rssZkXN_7lE:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/theAdmin?a=KedhoARfJ_g:rssZkXN_7lE:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/theAdmin?i=KedhoARfJ_g:rssZkXN_7lE:F7zBnMyn0Lo" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/theAdmin/~4/KedhoARfJ_g" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://theadmin.org/articles/code-reviews/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://theadmin.org/articles/code-reviews/</feedburner:origLink></item>
		<item>
		<title>Postponing Tech Learning Series</title>
		<link>http://feedproxy.google.com/~r/theAdmin/~3/gSoSrE3oRzg/</link>
		<comments>http://theadmin.org/articles/postponing-tech-learning-series/#comments</comments>
		<pubDate>Fri, 12 Oct 2012 16:13:32 +0000</pubDate>
		<dc:creator>Eric Davis</dc:creator>
				<category><![CDATA[General]]></category>

		<guid isPermaLink="false">http://theadmin.org/?p=2459</guid>
		<description><![CDATA[I&#8217;ve been doing a weekly tech learning series every Friday for the past few weeks. Each morning I&#8217;d budget 1 hour to learn a new piece of technology and then write up my thoughts about it. Even though I only budget one hour to the learning, I end up using two to four hours for [...]]]></description>
				<content:encoded><![CDATA[<p>I&#8217;ve been doing a <a href="http://theadmin.org/articles/weekly-tech-learning/" title="Weekly Tech Learning">weekly tech learning series</a> every Friday for the past few weeks. Each morning I&#8217;d budget 1 hour to learn a new piece of technology and then write up my thoughts about it.</p>
<p>Even though I only budget one hour to the learning, I end up using two to four hours for everything. Reviewing my results, writing about the results, and recording a screencast of the final app takes a lot of time to put together.  It&#8217;s fun, just time consuming.</p>
<p>Just this week I booked my last client which effectively takes up all of my time for the next few months. Actually, I&#8217;m a bit overbooked. This means that I have to cut back on anything that isn&#8217;t 100% essential until I can get some things off my plate (I&#8217;m talking with a VA to have some help with administrative tasks).</p>
<p>Until I can get everything squared away, I&#8217;m going to postpone my <a href="http://theadmin.org/articles/weekly-tech-learning/" title="Weekly Tech Learning">tech learning series</a>. I&#8217;m hopeful I&#8217;ll be able to start it back up once things have settled down.</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/theAdmin?a=gSoSrE3oRzg:jnrN2Nf18KQ:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/theAdmin?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/theAdmin?a=gSoSrE3oRzg:jnrN2Nf18KQ:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/theAdmin?i=gSoSrE3oRzg:jnrN2Nf18KQ:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/theAdmin?a=gSoSrE3oRzg:jnrN2Nf18KQ:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/theAdmin?i=gSoSrE3oRzg:jnrN2Nf18KQ:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/theAdmin?a=gSoSrE3oRzg:jnrN2Nf18KQ:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/theAdmin?i=gSoSrE3oRzg:jnrN2Nf18KQ:F7zBnMyn0Lo" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/theAdmin/~4/gSoSrE3oRzg" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://theadmin.org/articles/postponing-tech-learning-series/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://theadmin.org/articles/postponing-tech-learning-series/</feedburner:origLink></item>
		<item>
		<title>Happy Birthday sale – Save $10 on all of my ebooks today</title>
		<link>http://feedproxy.google.com/~r/theAdmin/~3/UJr-qZqvT5E/</link>
		<comments>http://theadmin.org/articles/happy-birthday-sale/#comments</comments>
		<pubDate>Wed, 10 Oct 2012 14:01:13 +0000</pubDate>
		<dc:creator>Eric Davis</dc:creator>
				<category><![CDATA[General]]></category>
		<category><![CDATA[authoring ebooks]]></category>
		<category><![CDATA[business]]></category>
		<category><![CDATA[ebook]]></category>
		<category><![CDATA[redmine tips]]></category>
		<category><![CDATA[refactoring redmine]]></category>
		<category><![CDATA[sale]]></category>

		<guid isPermaLink="false">http://theadmin.org/?p=2453</guid>
		<description><![CDATA[It&#8217;s that time of the year. No, not when little ghouls and goblins are running around begging for candy (though that time is coming up). It&#8217;s time for my birthday and this year I decided to give you a gift instead. This gift is coupons. (You should imagine a cake here with life sized coupons [...]]]></description>
				<content:encoded><![CDATA[<div class="woo-sc-box alert  rounded full">The sale has ended. Thanks everyone who ordered. &#8211;Eric </div>
<p>It&#8217;s that time of the year. No, not when little ghouls and goblins are running around begging for candy (though that time is coming up).</p>
<p>It&#8217;s time for my birthday and this year I decided to give you a gift instead.</p>
<p>This gift is <strong>coupons</strong>.</p>
<p>(You should imagine a cake here with life sized coupons jumping out of it)</p>
<h2>Save $10 on any of my ebooks</h2>
<p>Today from 10am EDT until 10pm PDT you can any or all of my three ebooks for $10 off each.</p>
<ul>
<li><a href="http://www.refactoringredmine.com/book/">Refactoring Redmine</a> for $29 (use code BIRTHDAYRR)</li>
<li><a href="http://www.redminetips.com/">Redmine Tips</a> for $39 (use code BIRTHDAYTIPS)</li>
<li><a href="http://www.authoringebooks.com/">Authoring Ebooks</a> for $39 (use code BIRTHDAYBOOK)</li>
</ul>
<p><em>(Sorry the coupon codes are different. It&#8217;s complicated.)</em></p>
<p>If you&#8217;ve been waiting for the right time to pick up your copy of Refactoring Redmine, Redmine Tips, or Authoring Ebooks &#8212; then now would be a good time.</p>
<p>Already got your copy? First off, you&#8217;re awesome. Second, why not email this page to a friend? Friends like gifts too.</p>
<h2>Why all the 10s?</h2>
<p>You might have noticed all of the 10s above. Please allow a bit of backstory.</p>
<p>I was born on a stormy night (or a calm day, I don&#8217;t really know and my mom isn&#8217;t answering her phone) on October 10th (10th month). Weighing in at a scale-crushing 10 pounds and 10 ounces, I was one of the big boys in the nursery that night (or day). Ever since then, 10 has been my lucky number.</p>
<p>That&#8217;s also why I go by edavis10 everywhere. It&#8217;s my way of making a common name like Eric Davis be unique&#8230; in my own special way.</p>
<p>So yea. Today. Coupons. All ebooks $10 off. 10am EDT to 10pm PDT only. Links and coupons above. Enjoy.</p>
<p>Eric Davis</p>
<p>P.S. This page will self-destruct after 10pm October 10th. I will not be held liable if you left your browser open when it happened and you lost all 37 of your tabs in the process. (I will say sorry if that happens though&#8230; Sorry)</p>
<div class="woo-sc-box alert  rounded full">The sale has ended. Thanks everyone who ordered. &#8211;Eric </div>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/theAdmin?a=UJr-qZqvT5E:VGcLAFfzB5U:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/theAdmin?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/theAdmin?a=UJr-qZqvT5E:VGcLAFfzB5U:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/theAdmin?i=UJr-qZqvT5E:VGcLAFfzB5U:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/theAdmin?a=UJr-qZqvT5E:VGcLAFfzB5U:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/theAdmin?i=UJr-qZqvT5E:VGcLAFfzB5U:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/theAdmin?a=UJr-qZqvT5E:VGcLAFfzB5U:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/theAdmin?i=UJr-qZqvT5E:VGcLAFfzB5U:F7zBnMyn0Lo" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/theAdmin/~4/UJr-qZqvT5E" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://theadmin.org/articles/happy-birthday-sale/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://theadmin.org/articles/happy-birthday-sale/</feedburner:origLink></item>
		<item>
		<title>Test HTTP Basic Authentication in Rails 3 With Capybara</title>
		<link>http://feedproxy.google.com/~r/theAdmin/~3/xG2aoIsrAKQ/</link>
		<comments>http://theadmin.org/articles/test-http-basic-authentication-in-rails/#comments</comments>
		<pubDate>Wed, 03 Oct 2012 16:54:35 +0000</pubDate>
		<dc:creator>Eric Davis</dc:creator>
				<category><![CDATA[General]]></category>
		<category><![CDATA[authentication]]></category>
		<category><![CDATA[http basic]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[rails3]]></category>
		<category><![CDATA[security]]></category>

		<guid isPermaLink="false">http://theadmin.org/?p=2445</guid>
		<description><![CDATA[I&#8217;m using some HTTP basic authentication in Chirk HR as a simple way of preventing unauthorized access. It&#8217;s simple, fast, and easy to change to a more robust authentication later on. Ideal Authentication Test As part of my testing habits, I try to really exercise important methods. Authentication is definitely one of them. Authentication is [...]]]></description>
				<content:encoded><![CDATA[<p>I&#8217;m using some HTTP basic authentication in Chirk HR as a simple way of preventing unauthorized access. It&#8217;s simple, fast, and easy to change to a more robust authentication later on.</p>
<h2>Ideal Authentication Test</h2>
<p>As part of my testing habits, I try to really exercise important methods. Authentication is definitely one of them. Authentication is also tricky because many of the authentication libraries give you helper methods that bypass some of the actual logging in process (e.g. open login page, enter username, enter password, submit). For this reason I try to test my authentication at the integration level:</p>
<ol>
<li>Load login form</li>
<li>Enter authentication data and submit</li>
<li>Verify login was successful</li>
</ol>
<p>My integration tool of choice is capybara. Its DSL is high level enough for me to model how a visitor behaves without being too complex.</p>
<h2>Testing HTTP Basic Authentication</h2>
<p>One feature of capybara is that it supports different drivers. A driver is a way to actually request and parse the HTTP request/response. It&#8217;s best to think of them as separate browsers. There are text-only ones, ones that embed an entire WebKit browser with JavaScript support, and ones that drive an actual FireFox process inside the GUI.</p>
<p>The problem with supporting different drivers is that the capybara can only abstract so much, as each browser supports different features such as HTTP basic. Fortunately, with Ruby it is easy to inspect each driver&#8217;s methods and call its version of HTTP basic as needed. By abstracting these into a method, our tests now have a clean way to use HTTP basic with capybara.</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="ruby" style="font-family:monospace;"><span style="color:#CC0066; font-weight:bold;">require</span> <span style="color:#996600;">'test_helper'</span>
&nbsp;
<span style="color:#9966CC; font-weight:bold;">class</span> <span style="color:#6666ff; font-weight:bold;">Admin::CohortTest</span> <span style="color:#006600; font-weight:bold;">&lt;</span> <span style="color:#6666ff; font-weight:bold;">ActionDispatch::IntegrationTest</span>
  <span style="color:#9966CC; font-weight:bold;">def</span> basic_auth<span style="color:#006600; font-weight:bold;">&#40;</span>name, password<span style="color:#006600; font-weight:bold;">&#41;</span>
    <span style="color:#9966CC; font-weight:bold;">if</span> page.<span style="color:#9900CC;">driver</span>.<span style="color:#9900CC;">respond_to</span>?<span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#ff3333; font-weight:bold;">:basic_auth</span><span style="color:#006600; font-weight:bold;">&#41;</span>
      page.<span style="color:#9900CC;">driver</span>.<span style="color:#9900CC;">basic_auth</span><span style="color:#006600; font-weight:bold;">&#40;</span>name, password<span style="color:#006600; font-weight:bold;">&#41;</span>
    <span style="color:#9966CC; font-weight:bold;">elsif</span> page.<span style="color:#9900CC;">driver</span>.<span style="color:#9900CC;">respond_to</span>?<span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#ff3333; font-weight:bold;">:basic_authorize</span><span style="color:#006600; font-weight:bold;">&#41;</span>
      page.<span style="color:#9900CC;">driver</span>.<span style="color:#9900CC;">basic_authorize</span><span style="color:#006600; font-weight:bold;">&#40;</span>name, password<span style="color:#006600; font-weight:bold;">&#41;</span>
    <span style="color:#9966CC; font-weight:bold;">elsif</span> page.<span style="color:#9900CC;">driver</span>.<span style="color:#9900CC;">respond_to</span>?<span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#ff3333; font-weight:bold;">:browser</span><span style="color:#006600; font-weight:bold;">&#41;</span> <span style="color:#006600; font-weight:bold;">&amp;&amp;</span> page.<span style="color:#9900CC;">driver</span>.<span style="color:#9900CC;">browser</span>.<span style="color:#9900CC;">respond_to</span>?<span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#ff3333; font-weight:bold;">:basic_authorize</span><span style="color:#006600; font-weight:bold;">&#41;</span>
      page.<span style="color:#9900CC;">driver</span>.<span style="color:#9900CC;">browser</span>.<span style="color:#9900CC;">basic_authorize</span><span style="color:#006600; font-weight:bold;">&#40;</span>name, password<span style="color:#006600; font-weight:bold;">&#41;</span>
    <span style="color:#9966CC; font-weight:bold;">else</span>
      <span style="color:#CC0066; font-weight:bold;">raise</span> <span style="color:#996600;">&quot;I don't know how to log in!&quot;</span>
    <span style="color:#9966CC; font-weight:bold;">end</span>
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  test <span style="color:#996600;">&quot;should block access without invalid HTTP auth&quot;</span> <span style="color:#9966CC; font-weight:bold;">do</span>
    visit <span style="color:#996600;">'/admin'</span>
&nbsp;
    assert_equal <span style="color:#006666;">401</span>, page.<span style="color:#9900CC;">status_code</span>
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  test <span style="color:#996600;">&quot;should show the page&quot;</span> <span style="color:#9966CC; font-weight:bold;">do</span>
    basic_auth<span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#996600;">'edavis'</span>, <span style="color:#996600;">'password'</span><span style="color:#006600; font-weight:bold;">&#41;</span>
&nbsp;
    visit <span style="color:#996600;">'/admin'</span>
&nbsp;
    assert_equal <span style="color:#006666;">200</span>, page.<span style="color:#9900CC;">status_code</span>
    assert has_content?<span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#996600;">&quot;Cohorts&quot;</span><span style="color:#006600; font-weight:bold;">&#41;</span>
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
<span style="color:#9966CC; font-weight:bold;">end</span></pre></td></tr></table></div>

<p>Looking back at the ideal authentication test we can see that this is very close to fulfilling the behavior we want.</p>
<ol>
<li>The <code>basic_auth</code> method enters the authentication data and submits.</li>
<li><code>visit</code> tries to open a HTTP basic protected page, which opens the login form.</li>
<li>The final <code>assert has_content?("Cohorts")</code> verifies that the login was successful.</li>
</ol>
<p>Because of how HTTP basic works the flow is a bit different in that we are setting the authentication first and then loading the page. It&#8217;s not ideal but for something quick I think it&#8217;s understandable enough.</p>
<p>Ideally, you&#8217;d extract the <code>basic_auth</code> method to the test helper so you can reuse it in other tests.</p>
<h2>Gotchas</h2>
<p>There are a few gotchas to be aware of when using and testing HTTP basic this way:</p>
<ul>
<li>With HTTP Basic, authentication is <a href="http://en.wikipedia.org/wiki/Basic_access_authentication#Disadvantages">passed in the clear</a> to the server. Depending on your application this might be a problem. On Chirk HR I&#8217;m using SSL for everything so the HTTP Basic headers are encrypted there. For something stronger than HTTP Basic, try using <a href="http://en.wikipedia.org/wiki/Digest_access_authentication">HTTP Digest</a> or a full authentication system.</li>
<li>The test is calling methods on the underlying driver which could break if you switch capybara drivers. You&#8217;ll want to standardize on one or two drivers for you application to keep things simple.</li>
<li>Also if the drivers change their HTTP basic APIs in the future then your tests might start failing.</li>
</ul>
<p>Using and testing HTTP Basic authentication in Rails doesn&#8217;t need to be difficult. While it isn&#8217;t as fully featured as full authentication systems like devise or sorcery, HTTP Basic could be a good enough system for you to use in your application.</p>
<p>Thanks to the <a href="http://stackoverflow.com/questions/4329985/http-basic-auth-for-capybara">Stackoverflow</a> post where I learned this.</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/theAdmin?a=xG2aoIsrAKQ:ONEB0sAO3rg:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/theAdmin?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/theAdmin?a=xG2aoIsrAKQ:ONEB0sAO3rg:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/theAdmin?i=xG2aoIsrAKQ:ONEB0sAO3rg:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/theAdmin?a=xG2aoIsrAKQ:ONEB0sAO3rg:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/theAdmin?i=xG2aoIsrAKQ:ONEB0sAO3rg:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/theAdmin?a=xG2aoIsrAKQ:ONEB0sAO3rg:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/theAdmin?i=xG2aoIsrAKQ:ONEB0sAO3rg:F7zBnMyn0Lo" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/theAdmin/~4/xG2aoIsrAKQ" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://theadmin.org/articles/test-http-basic-authentication-in-rails/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		<feedburner:origLink>http://theadmin.org/articles/test-http-basic-authentication-in-rails/</feedburner:origLink></item>
		<item>
		<title>Learning Redis</title>
		<link>http://feedproxy.google.com/~r/theAdmin/~3/EApTYVRp-5I/</link>
		<comments>http://theadmin.org/articles/learning-redis/#comments</comments>
		<pubDate>Fri, 28 Sep 2012 20:26:16 +0000</pubDate>
		<dc:creator>Eric Davis</dc:creator>
				<category><![CDATA[General]]></category>
		<category><![CDATA[redis]]></category>
		<category><![CDATA[ruby]]></category>
		<category><![CDATA[screencast]]></category>
		<category><![CDATA[sinatra]]></category>
		<category><![CDATA[tdd]]></category>
		<category><![CDATA[tech learning]]></category>

		<guid isPermaLink="false">http://theadmin.org/?p=2437</guid>
		<description><![CDATA[This post is part of my weekly tech learning series, where I take one hour each week to try out a piece of technology that I&#8217;d like to learn. Make sure to read to the end, where I have a screencast overview of the final application. This week I decided to learn a bit more [...]]]></description>
				<content:encoded><![CDATA[<p><em>This post is part of my weekly <a href="http://theadmin.org/articles/weekly-tech-learning/" title="Weekly Tech Learning">tech learning</a> series, where I take one hour each week to try out a piece of technology that I&#8217;d like to learn. Make sure to read to the end, where I have a screencast overview of the final application.</em></p>
<p>This week I decided to learn a bit more about <a href="http://redis.io">Redis</a>. I&#8217;ve used Redis in <a href="http://www.chirkhr.com/">Chirk HR</a> already but only as part of a non-critical service to see how it performs under load (I&#8217;d like to screencast this later). Today I wanted to use Redis as an actual data store but first, what is Redis?</p>
<h2>What is Redis?</h2>
<p>Redis is an open source key-value store. I think of it as a light nosql database with a lot of flexibility.</p>
<h2>Today&#8217;s application</h2>
<p>My idea for today was to build my running journal but this time as a CRUD style web app. I want to stay close to the metal so I can really see how Redis works so I picked my second favorite web framework: Sinatra.</p>
<p>Basically I wanted to mimic a Rails scaffold inside Sinatra using Redis as the data storage. I&#8217;m also using the <a href="http://rubygems.org/gems/redis">redis</a> gem because it is very similar to the actual Redis API. This means I&#8217;ll be implementing the data layer myself, without an ORM.</p>
<p>(Normally I&#8217;d use ActiveRecord or Datamapper for production applications instance but I&#8217;m more concerned with learning and exploration with this.)</p>
<p>Since I will be doing a lot of data layer work I&#8217;m also opting for heavy TDD with this app, using minitest and autotest.</p>
<p>Here is the Gemfile I ended up with:</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="ruby" style="font-family:monospace;">source <span style="color:#ff3333; font-weight:bold;">:rubygems</span>
&nbsp;
gem <span style="color:#996600;">&quot;sinatra&quot;</span>, :<span style="color:#CC0066; font-weight:bold;">require</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;sinatra/base&quot;</span>
gem <span style="color:#996600;">&quot;shotgun&quot;</span>
gem <span style="color:#996600;">&quot;redis&quot;</span>
&nbsp;
group <span style="color:#ff3333; font-weight:bold;">:test</span> <span style="color:#9966CC; font-weight:bold;">do</span>
  gem <span style="color:#996600;">&quot;minitest&quot;</span>
  gem <span style="color:#996600;">&quot;rack-test&quot;</span>
  gem <span style="color:#996600;">&quot;autotest&quot;</span>
  gem <span style="color:#996600;">&quot;autotest-notification&quot;</span>
<span style="color:#9966CC; font-weight:bold;">end</span></pre></td></tr></table></div>

<h2>Use TDD for the application behavior</h2>
<p>After getting everything hooked up I decided to split the tests into two files:</p>
<ol>
<li>test_redis_running.rb to test the RedisRunning Sinatra application at the integration layer. This would be the controller and view if you&#8217;re thinking about it as a MVC app.</li>
<li>test_run.rb to test the Run class, which is used as the data layer between the application and Redis.</li>
</ol>
<p>I like to start with integration testing and write pending tests for each major workflow or feature. I came up with the following integration tests to get started with:</p>
<ul>
<li>test_get_root_response</li>
<li>test_get_root_list_runs</li>
<li>test_get_root_add_form_present</li>
<li>test_post_new_with_valid_run</li>
<li>test_post_new_with_invalid_run</li>
<li>test_get_edit_response</li>
<li>test_get_edit_show_form</li>
<li>test_put_update_with_valid_update</li>
<li>test_put_update_with_invalid_update</li>
<li>test_delete_response</li>
<li>test_delete_should_delete_record</li>
</ul>
<p>This is very similar to Rails scaffold except I decided to put the new form on the list of runs (:index) and to skip the details of a run (:show). With such a small data model, a list provides a perfect summary.</p>
<p>The first test I wrote was to check that getting the index was successful, which isn&#8217;t about Redis at all so I won&#8217;t bore you with the details of it.</p>
<p>The second test on the other hand (test_get_root_list_runs), is the important one (and as you&#8217;ll see later is all that I had time for). Here I wanted to do a few things:</p>
<ol>
<li>Create a few runs and save them to Redis</li>
<li>Load the index page</li>
<li>Check that the runs were displayed</li>
</ol>
<p>In order to implement this test I have to do a full cycle of saving and loading runs from Redis. This meant I&#8217;d need to build the data layer now.</p>
<p>Here is the full integration test.</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="ruby" style="font-family:monospace;">  <span style="color:#9966CC; font-weight:bold;">def</span> test_get_root_list_runs
    run1 = Run.<span style="color:#9900CC;">new</span><span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#ff3333; font-weight:bold;">:date</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;2012-09-28&quot;</span>,
                   <span style="color:#ff3333; font-weight:bold;">:distance</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;3mi&quot;</span>,
                   <span style="color:#ff3333; font-weight:bold;">:duration</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;30:00&quot;</span>,
                   <span style="color:#ff3333; font-weight:bold;">:pace</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;10:00&quot;</span>,
                   <span style="color:#ff3333; font-weight:bold;">:comment</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;A run&quot;</span><span style="color:#006600; font-weight:bold;">&#41;</span>
    assert run1.<span style="color:#9900CC;">save</span>, <span style="color:#996600;">&quot;Save failed&quot;</span>
&nbsp;
    run2 = Run.<span style="color:#9900CC;">new</span><span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#ff3333; font-weight:bold;">:date</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;2012-09-29&quot;</span>,
                   <span style="color:#ff3333; font-weight:bold;">:distance</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;3mi&quot;</span>,
                   <span style="color:#ff3333; font-weight:bold;">:duration</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;27:00&quot;</span>,
                   <span style="color:#ff3333; font-weight:bold;">:pace</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;9:00&quot;</span>,
                   <span style="color:#ff3333; font-weight:bold;">:comment</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;Another run&quot;</span><span style="color:#006600; font-weight:bold;">&#41;</span>
    assert run2.<span style="color:#9900CC;">save</span>, <span style="color:#996600;">&quot;Save failed&quot;</span>
&nbsp;
    get <span style="color:#996600;">'/'</span>
&nbsp;
    assert last_response.<span style="color:#9900CC;">body</span>.<span style="color:#9966CC; font-weight:bold;">include</span>?<span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#996600;">&quot;A run&quot;</span><span style="color:#006600; font-weight:bold;">&#41;</span>
    assert last_response.<span style="color:#9900CC;">body</span>.<span style="color:#9966CC; font-weight:bold;">include</span>?<span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#996600;">&quot;Another run&quot;</span><span style="color:#006600; font-weight:bold;">&#41;</span>
  <span style="color:#9966CC; font-weight:bold;">end</span></pre></td></tr></table></div>

<h2>RedisRunning Data Layer</h2>
<p>The integration test needed three things from my Run class:</p>
<ol>
<li>Create an object with some attributes</li>
<li>Save the object to Redis</li>
<li>Load the saved object out of Redis</li>
</ol>
<p>I started with the first, using standard Ruby accessors.</p>
<h2>Run accessors and attributes</h2>
<p>A run has five attributes that a user should be able to set:</p>
<ol>
<li>date</li>
<li>distance</li>
<li>duration</li>
<li>pace</li>
<li>comment</li>
</ol>
<p>Additionally, I&#8217;m going to need some way to uniquely identify each run. In Postgres I&#8217;d just use an id field as a primary key but from what I see, Redis doesn&#8217;t have primary keys. Only keys.</p>
<p>At first I thought of using the date as the primary key, but many runners will run multiple times per day so that won&#8217;t work. I could use a combination of the date and another field like distance or duration but there is also a chance those will conflict also (e.g. two runs per day, around a 1 mile track at a specific pace).</p>
<blockquote>
<p>Redis doesn&#8217;t have primary keys so you might need to roll your own.</p>
</blockquote>
<p>So I&#8217;ll need to be creating my own unique ids for each run. I&#8217;ve used UUIDs before and they seem like a good fit here.</p>
<p>So using attr_accessor and initialize I can easily fulfill these requirements.</p>
<p>The test:</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="ruby" style="font-family:monospace;"><span style="color:#9966CC; font-weight:bold;">class</span> RunTest <span style="color:#006600; font-weight:bold;">&lt;</span> <span style="color:#6666ff; font-weight:bold;">MiniTest::Unit::TestCase</span>
  <span style="color:#9966CC; font-weight:bold;">def</span> test_has_attributes
    run = Run.<span style="color:#9900CC;">new</span>
&nbsp;
    <span style="color:#006600; font-weight:bold;">&#91;</span><span style="color:#996600;">'date'</span>,<span style="color:#996600;">'distance'</span>,<span style="color:#996600;">'duration'</span>,<span style="color:#996600;">'pace'</span>,<span style="color:#996600;">'comment'</span><span style="color:#006600; font-weight:bold;">&#93;</span>.<span style="color:#9900CC;">each</span> <span style="color:#9966CC; font-weight:bold;">do</span> <span style="color:#006600; font-weight:bold;">|</span>attribute<span style="color:#006600; font-weight:bold;">|</span>
      assert run.<span style="color:#9900CC;">respond_to</span>?<span style="color:#006600; font-weight:bold;">&#40;</span>attribute<span style="color:#006600; font-weight:bold;">&#41;</span>, <span style="color:#996600;">&quot;Run does not respond to #{attribute}&quot;</span>
      assert run.<span style="color:#9900CC;">respond_to</span>?<span style="color:#006600; font-weight:bold;">&#40;</span>attribute <span style="color:#006600; font-weight:bold;">+</span> <span style="color:#996600;">&quot;=&quot;</span><span style="color:#006600; font-weight:bold;">&#41;</span>, <span style="color:#996600;">&quot;Run does not respond to #{attribute}=&quot;</span>
    <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">def</span> test_should_allow_setting_attributes_on_initialize
    run = Run.<span style="color:#9900CC;">new</span><span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#ff3333; font-weight:bold;">:date</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">'1'</span>, <span style="color:#ff3333; font-weight:bold;">:distance</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">'2'</span>, <span style="color:#ff3333; font-weight:bold;">:duration</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">'3'</span>, <span style="color:#ff3333; font-weight:bold;">:pace</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">'4'</span>, <span style="color:#ff3333; font-weight:bold;">:comment</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">'5'</span><span style="color:#006600; font-weight:bold;">&#41;</span>
&nbsp;
    assert_equal <span style="color:#996600;">'1'</span>, run.<span style="color:#9900CC;">date</span>
    assert_equal <span style="color:#996600;">'2'</span>, run.<span style="color:#9900CC;">distance</span>
    assert_equal <span style="color:#996600;">'3'</span>, run.<span style="color:#9900CC;">duration</span>
    assert_equal <span style="color:#996600;">'4'</span>, run.<span style="color:#9900CC;">pace</span>
    assert_equal <span style="color:#996600;">'5'</span>, run.<span style="color:#9900CC;">comment</span>
  <span style="color:#9966CC; font-weight:bold;">end</span>
<span style="color:#9966CC; font-weight:bold;">end</span></pre></td></tr></table></div>

<p>The implementation:</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="ruby" style="font-family:monospace;"><span style="color:#9966CC; font-weight:bold;">class</span> Run
  attr_accessor <span style="color:#ff3333; font-weight:bold;">:id</span>
  attr_accessor <span style="color:#ff3333; font-weight:bold;">:date</span>
  attr_accessor <span style="color:#ff3333; font-weight:bold;">:distance</span>
  attr_accessor <span style="color:#ff3333; font-weight:bold;">:duration</span>
  attr_accessor <span style="color:#ff3333; font-weight:bold;">:pace</span>
  attr_accessor <span style="color:#ff3333; font-weight:bold;">:comment</span>
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">def</span> initialize<span style="color:#006600; font-weight:bold;">&#40;</span>attributes=<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>
    attributes.<span style="color:#9900CC;">each</span> <span style="color:#9966CC; font-weight:bold;">do</span> <span style="color:#006600; font-weight:bold;">|</span>attribute, value<span style="color:#006600; font-weight:bold;">|</span>
      setter = <span style="color:#996600;">&quot;#{attribute}=&quot;</span>
      <span style="color:#0000FF; font-weight:bold;">self</span>.<span style="color:#9900CC;">send</span><span style="color:#006600; font-weight:bold;">&#40;</span>setter, value<span style="color:#006600; font-weight:bold;">&#41;</span> <span style="color:#9966CC; font-weight:bold;">if</span> <span style="color:#0000FF; font-weight:bold;">self</span>.<span style="color:#9900CC;">respond_to</span>?<span style="color:#006600; font-weight:bold;">&#40;</span>setter<span style="color:#006600; font-weight:bold;">&#41;</span>
    <span style="color:#9966CC; font-weight:bold;">end</span>
  <span style="color:#9966CC; font-weight:bold;">end</span>
<span style="color:#9966CC; font-weight:bold;">end</span></pre></td></tr></table></div>

<h2>Generating unique ids with UUID</h2>
<p>I&#8217;ve used some libraries to generate UUIDs in Ruby but I just found out that Ruby 1.9.3 now has UUID support in the standard library as part of <a href="http://rubydoc.info/stdlib/securerandom/1.9.2/SecureRandom#uuid-class_method">SecureRandom</a>. This makes the UUID code simple.</p>
<p>The test.</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="ruby" style="font-family:monospace;">  <span style="color:#9966CC; font-weight:bold;">def</span> test_id_should_be_a_uuid
    run = Run.<span style="color:#9900CC;">new</span>
&nbsp;
    assert run.<span style="color:#9900CC;">id</span>.<span style="color:#9900CC;">is_a</span>?<span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#CC0066; font-weight:bold;">String</span><span style="color:#006600; font-weight:bold;">&#41;</span>
    assert_equal <span style="color:#006666;">36</span>, run.<span style="color:#9900CC;">id</span>.<span style="color:#9900CC;">length</span>
    assert_equal <span style="color:#006666;">4</span>, run.<span style="color:#9900CC;">id</span>.<span style="color:#9900CC;">count</span><span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#996600;">'-'</span><span style="color:#006600; font-weight:bold;">&#41;</span>
  <span style="color:#9966CC; font-weight:bold;">end</span></pre></td></tr></table></div>

<p>The implementation.</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="ruby" style="font-family:monospace;"><span style="color:#008000; font-style:italic;"># Way up top outside the Run class...</span>
<span style="color:#CC0066; font-weight:bold;">require</span> <span style="color:#996600;">'securerandom'</span>
&nbsp;
<span style="color:#008000; font-style:italic;"># In the Run class...</span>
  <span style="color:#9966CC; font-weight:bold;">def</span> id
    <span style="color:#0066ff; font-weight:bold;">@id</span> <span style="color:#006600; font-weight:bold;">||</span>= SecureRandom.<span style="color:#9900CC;">uuid</span>
    <span style="color:#0066ff; font-weight:bold;">@id</span>
  <span style="color:#9966CC; font-weight:bold;">end</span></pre></td></tr></table></div>

<p>By using <code>||=</code> I can make sure that if a Run already has an id then I won&#8217;t generate it again (existing record).</p>
<p>I also wrote a brute force test to make sure Ruby&#8217;s UUIDs were unique and that I wasn&#8217;t misreading the docs. It&#8217;s not really production quality but good for exploration of a new API:</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="ruby" style="font-family:monospace;">  <span style="color:#9966CC; font-weight:bold;">def</span> test_id_should_be_unique
    <span style="color:#008000; font-style:italic;"># Lets create a bunch of runs and check their uniqueness.</span>
    <span style="color:#008000; font-style:italic;"># TODO: not the best solution but time is limited</span>
    runs = <span style="color:#006666;">100</span>.<span style="color:#9900CC;">times</span>.<span style="color:#9900CC;">collect</span> <span style="color:#006600; font-weight:bold;">&#123;</span> Run.<span style="color:#9900CC;">new</span>.<span style="color:#9900CC;">id</span> <span style="color:#006600; font-weight:bold;">&#125;</span>
&nbsp;
    assert_equal <span style="color:#006666;">100</span>, runs.<span style="color:#9900CC;">uniq</span>.<span style="color:#9900CC;">length</span>, <span style="color:#996600;">&quot;Duplicate ids found&quot;</span>
  <span style="color:#9966CC; font-weight:bold;">end</span></pre></td></tr></table></div>

<h2>Saving a run to Redis</h2>
<p>Now that I have a data format in Ruby, I need to save it to Redis. Since Redis is a key value store, I decided to save my runs where the key is the id (uuid) and the data is a hash of strings. This way it would be easy to load the data back into a Ruby object.</p>
<p>Since the integration test uses the <code>#save</code> method, that&#8217;s what I need to implement first. After writing two quick tests I&#8217;m ready to figure out how to save to Redis:</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="ruby" style="font-family:monospace;">  <span style="color:#9966CC; font-weight:bold;">def</span> test_save_with_valid_attributes
    run = Run.<span style="color:#9900CC;">new</span><span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#ff3333; font-weight:bold;">:date</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;2012-09-28&quot;</span>,
                  <span style="color:#ff3333; font-weight:bold;">:distance</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;3mi&quot;</span>,
                  <span style="color:#ff3333; font-weight:bold;">:duration</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;30:00&quot;</span>,
                  <span style="color:#ff3333; font-weight:bold;">:pace</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;10:00&quot;</span>,
                  <span style="color:#ff3333; font-weight:bold;">:comment</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;A run&quot;</span><span style="color:#006600; font-weight:bold;">&#41;</span>
&nbsp;
    assert run.<span style="color:#9900CC;">save</span>, <span style="color:#996600;">&quot;Save failed&quot;</span>
&nbsp;
    assert <span style="color:#ff6633; font-weight:bold;">$db</span>.<span style="color:#9900CC;">keys</span>.<span style="color:#9966CC; font-weight:bold;">include</span>?<span style="color:#006600; font-weight:bold;">&#40;</span>run.<span style="color:#9900CC;">id</span><span style="color:#006600; font-weight:bold;">&#41;</span>, <span style="color:#996600;">&quot;Run not found in Redis&quot;</span>
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">def</span> test_save_with_mostly_empty_attributes
    run = Run.<span style="color:#9900CC;">new</span><span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#ff3333; font-weight:bold;">:date</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;&quot;</span>,
                  <span style="color:#ff3333; font-weight:bold;">:distance</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;&quot;</span>,
                  <span style="color:#ff3333; font-weight:bold;">:duration</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;&quot;</span>,
                  <span style="color:#ff3333; font-weight:bold;">:pace</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;&quot;</span>,
                  <span style="color:#ff3333; font-weight:bold;">:comment</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;&quot;</span><span style="color:#006600; font-weight:bold;">&#41;</span>
&nbsp;
    assert run.<span style="color:#9900CC;">save</span>, <span style="color:#996600;">&quot;Save failed&quot;</span>
&nbsp;
    assert <span style="color:#ff6633; font-weight:bold;">$db</span>.<span style="color:#9900CC;">keys</span>.<span style="color:#9966CC; font-weight:bold;">include</span>?<span style="color:#006600; font-weight:bold;">&#40;</span>run.<span style="color:#9900CC;">id</span><span style="color:#006600; font-weight:bold;">&#41;</span>, <span style="color:#996600;">&quot;Run not found in Redis&quot;</span>
  <span style="color:#9966CC; font-weight:bold;">end</span></pre></td></tr></table></div>

<p>Redis uses a <code>#set</code> method to save data. It takes the key and value of the data and will return an &#8220;OK&#8221; when it succeeds.</p>
<p>One thing that tripped me up for a minute was that if I just sent a Ruby hash to Redis it would get saved but as the string interpolation of the hash like</p>
<pre><code>'{"f"=&gt;"g"}'
</code></pre>
<p>This wouldn&#8217;t work because I&#8217;d need to use <code>eval</code> to turn that back into a Ruby hash if I wanted to load it into a Run object.</p>
<p>So I turned to my old standby, JSON. By calling <code>#to_json</code> on the Ruby hash before sending it to Redis, I can be sure that I can unserialize it on the way back out. This is what Redis stored when I encoded the hash above into JSON.</p>
<pre><code>'{"f":"g"}'
</code></pre>
<p>That figured out, the <code>#save</code> method became easy. Just use <code>#set</code> to save the JSON using a run&#8217;s uuid as the key.</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="ruby" style="font-family:monospace;">  <span style="color:#9966CC; font-weight:bold;">def</span> save
    response = <span style="color:#ff6633; font-weight:bold;">$db</span>.<span style="color:#9900CC;">set</span><span style="color:#006600; font-weight:bold;">&#40;</span>id, <span style="color:#006600; font-weight:bold;">&#123;</span>
                         <span style="color:#996600;">&quot;id&quot;</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> id,
                         <span style="color:#996600;">&quot;date&quot;</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#0066ff; font-weight:bold;">@date</span>,
                         <span style="color:#996600;">&quot;distance&quot;</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#0066ff; font-weight:bold;">@distance</span>,
                         <span style="color:#996600;">&quot;duration&quot;</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#0066ff; font-weight:bold;">@duration</span>,
                         <span style="color:#996600;">&quot;pace&quot;</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#0066ff; font-weight:bold;">@pace</span>,
                         <span style="color:#996600;">&quot;comment&quot;</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#0066ff; font-weight:bold;">@comment</span>
                       <span style="color:#006600; font-weight:bold;">&#125;</span>.<span style="color:#9900CC;">to_json</span><span style="color:#006600; font-weight:bold;">&#41;</span>
&nbsp;
    <span style="color:#0000FF; font-weight:bold;">return</span> response == <span style="color:#996600;">&quot;OK&quot;</span>
  <span style="color:#9966CC; font-weight:bold;">end</span></pre></td></tr></table></div>

<h2>Why not use the native hash support in Redis?</h2>
<p>Redis natively supports hashes with operations like hset, hget, and hkeys. I probably should have use them instead of JSON but I forget all about them until after I was done. Switching to a native Redis hash wouldn&#8217;t be difficult at all, because all of that logic is hidden inside two methods of the Run class. The Sinatra application itself doesn&#8217;t even know that Redis is used at all.</p>
<h2>Loading a Ruby object out of Redis</h2>
<p>The next step in the integration test is to load an object out of Redis and into Ruby, to be used by erb when creating the HTML page.</p>
<p>Mimicking ActiveRecord&#8217;s API again, I decided to name this method <code>#find</code> and only support finding a run by it&#8217;s uuid. The test is simple:</p>
<ol>
<li>Create a run object</li>
<li>Save it to Redis</li>
<li>Load the run object out of Redis</li>
<li>Make sure its data is correct</li>
</ol>

<div class="wp_syntax"><table><tr><td class="code"><pre class="ruby" style="font-family:monospace;">  <span style="color:#9966CC; font-weight:bold;">def</span> test_find_existing_run
    run = Run.<span style="color:#9900CC;">new</span><span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#ff3333; font-weight:bold;">:date</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;2012-09-28&quot;</span>,
                  <span style="color:#ff3333; font-weight:bold;">:distance</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;3mi&quot;</span>,
                  <span style="color:#ff3333; font-weight:bold;">:duration</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;30:00&quot;</span>,
                  <span style="color:#ff3333; font-weight:bold;">:pace</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;10:00&quot;</span>,
                  <span style="color:#ff3333; font-weight:bold;">:comment</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;A run&quot;</span><span style="color:#006600; font-weight:bold;">&#41;</span>
&nbsp;
    assert run.<span style="color:#9900CC;">save</span>, <span style="color:#996600;">&quot;Save failed&quot;</span>
&nbsp;
    redis_run = Run.<span style="color:#9900CC;">find</span><span style="color:#006600; font-weight:bold;">&#40;</span>run.<span style="color:#9900CC;">id</span><span style="color:#006600; font-weight:bold;">&#41;</span>
    assert_equal run.<span style="color:#9900CC;">id</span>, redis_run.<span style="color:#9900CC;">id</span>
    assert_equal <span style="color:#996600;">&quot;2012-09-28&quot;</span>, redis_run.<span style="color:#9900CC;">date</span>
    assert_equal <span style="color:#996600;">&quot;3mi&quot;</span>, redis_run.<span style="color:#9900CC;">distance</span>
    assert_equal <span style="color:#996600;">&quot;30:00&quot;</span>, redis_run.<span style="color:#9900CC;">duration</span>
    assert_equal <span style="color:#996600;">&quot;10:00&quot;</span>, redis_run.<span style="color:#9900CC;">pace</span>
    assert_equal <span style="color:#996600;">&quot;A run&quot;</span>, redis_run.<span style="color:#9900CC;">comment</span>
  <span style="color:#9966CC; font-weight:bold;">end</span></pre></td></tr></table></div>

<p>Notice how I&#8217;m using an actual run object and its attributes this time instead of a raw hash from Redis. This is what the Run data model class is providing me.</p>
<p>Thinking about the <code>#find</code> method, it needs to do two things:</p>
<ol>
<li>Load the raw data from Redis, if it&#8217;s available</li>
<li>Convert the raw data into a Run object</li>
</ol>
<p>Loading the data is quite easy. Redis uses the <code>#get</code> method to fetch a single object, all you have to do is give it the key. If it finds an object, it is returned. If Redis doesn&#8217;t find the object, you&#8217;ll get a nil back. This makes it simple to use an <code>if</code> statement for control flow between these two cases.</p>
<p>Since the raw data is JSON, I needed to feed it through a JSON parser to convert it to a Ruby hash. This isn&#8217;t all I needed though, I wanted a Run class so I can interact with it right away. Since my earlier work created an <code>initialize</code> method that could set attributes based on a hash, I could easily send the data there and get a Run object back out.</p>
<p>In less words, here is the code:</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="ruby" style="font-family:monospace;">  <span style="color:#9966CC; font-weight:bold;">def</span> <span style="color:#0000FF; font-weight:bold;">self</span>.<span style="color:#9900CC;">find</span><span style="color:#006600; font-weight:bold;">&#40;</span>uuid<span style="color:#006600; font-weight:bold;">&#41;</span>
&nbsp;
    record = <span style="color:#ff6633; font-weight:bold;">$db</span>.<span style="color:#9900CC;">get</span><span style="color:#006600; font-weight:bold;">&#40;</span>uuid<span style="color:#006600; font-weight:bold;">&#41;</span>
&nbsp;
    <span style="color:#9966CC; font-weight:bold;">if</span> record
      <span style="color:#0000FF; font-weight:bold;">return</span> new<span style="color:#006600; font-weight:bold;">&#40;</span>JSON.<span style="color:#9900CC;">parse</span><span style="color:#006600; font-weight:bold;">&#40;</span>record<span style="color:#006600; font-weight:bold;">&#41;</span><span style="color:#006600; font-weight:bold;">&#41;</span>
    <span style="color:#9966CC; font-weight:bold;">else</span>
      <span style="color:#0000FF; font-weight:bold;">return</span> <span style="color:#0000FF; font-weight:bold;">nil</span>
    <span style="color:#9966CC; font-weight:bold;">end</span>
  <span style="color:#9966CC; font-weight:bold;">end</span></pre></td></tr></table></div>

<h2>Where are we now?</h2>
<p>Going back up the stack to the integration test, the data layer (Run) works for initialing, saving, and loading a record. That means all I have to do is to load all of the Runs from Redis and show them on the index page.</p>
<p><strong>But, I&#8217;m out of time now.</strong></p>
<p>I already have an idea of how I&#8217;d go about listing the Runs, using Redis&#8217;s <code>keys</code> method which returns all keys and then using <code>mget</code> to get multiple keys in one call. After that it&#8217;s just a delete action and the HTML.</p>
<h2>Summary</h2>
<p>Even though I didn&#8217;t get any user interface written, I created most of the data model and had some good tests to make sure the data was behaving the way I wanted. Finishing up the application would be straight-forward and would just take some more time than I have available.</p>
<p>Like I said in the introduction, I have Redis running in production for <a href="http://www.chirkhr.com">Chirk HR</a> while I test how easy it is to keep running. As of this writing, the Redis server has been online and receiving data for over 16 days and 16 hours with no downtime (probably longer, my monitoring system lost its network connection for a bit).</p>
<p>Given this exploration and my production test I&#8217;ve gotten very comfortable with Redis and will be adding it to my developer toolbox for future apps. I think it&#8217;s great when you need a simple and stable key/value store.</p>
<h2>Screencast</h2>
<!-- LeadPlayer video embed code start [ video: 50660374D8891 ] --><div><script type="text/javascript" src="http://s3.amazonaws.com/cdn.leadbrite.com/leadplayer/r0032/js/leadplayer.js"></script></div><div id="leadplayer_video_element_50660374D8891" style="width:683px;height:384px"></div><div><script type="text/javascript">jQLeadBrite("#leadplayer_video_element_50660374D8891").leadplayer(false, "eyJnYSI6dHJ1ZSwib3ZlcmxheSI6ZmFsc2UsInBvd2VyZWRfYnkiOmZhbHNlLCJwb3dlcmVkX2J5X2xpbmsiOiJodHRwOlwvXC93d3cubGVhZHBsYXllci5jb21cLyIsImNvbG9yMSI6IiMwMDA5REQiLCJjb2xvcjIiOiIjNzlCMDRDIiwiY29sb3IzIjoiI0Y1QkIwQyIsInR4dF9zdWJtaXQiOiJTVUJNSVQiLCJ0eHRfcGxheSI6IlBMQVkiLCJ0eHRfZW1sIjoiWW91ciBFbWFpbCBBZGRyZXNzIiwidHh0X25hbWUiOiJZb3VyIE5hbWUiLCJ0eHRfaW52YWxpZF9lbWwiOiJQbGVhc2UgZW50ZXIgYSB2YWxpZCBlbWFpbCIsInR4dF9pbnZhbGlkX25hbWUiOiJQbGVhc2UgZW50ZXIgeW91ciBuYW1lIiwibHBfc291cmNlIjoiV1AgUGx1Z2luIDEuNC4xLjQgVW5saW1pdGVkIiwiaWQiOiI1MDY2MDM3NEQ4ODkxIiwid2lkdGgiOjY4MywiaGVpZ2h0IjozODQsInRodW1ibmFpbCI6Imh0dHA6XC9cL3RoZWFkbWluLm9yZ1wvZmlsZXNcLzIwMTJcLzA5XC9yZWRpcy1zdGlsbC5wbmciLCJ0aXRsZSI6IkxlYXJuaW5nIFJlZGlzIiwiZGVzY3JpcHRpb24iOiIiLCJhdXRvcGxheSI6ZmFsc2UsInNob3dfdGltZWxpbmUiOnRydWUsImVuYWJsZV9oZCI6dHJ1ZSwib3B0Ijp7InRpbWUiOiJlbmQiLCJ0ZXh0MSI6IkpvaW4gbXkgTWFpbGluZyBMaXN0ISIsInRleHQyIjoiUmVjZWl2ZSBmcmVlIG5ld3MgJmFtcDsgdXBkYXRlcyIsInVybCI6Imh0dHA6XC9cL3RoZWFkbWluLm9yZyIsInNraXAiOnsidGV4dCI6InNraXAgdGhpcyBzdGVwIn0sImZvcm1fcHJvdmlkZXIiOiJhd2ViZXIiLCJmb3JtX2h0bWwiOiImbHQ7IS0tIEFXZWJlciBXZWIgRm9ybSBHZW5lcmF0b3IgMy4wIC0tJmd0OyZsdDtzdHlsZSB0eXBlPSZxdW90O3RleHRcL2NzcyZxdW90OyZndDsjYWYtZm9ybS0xOTQyNjY5ODkxIC5hZi1ib2R5IC5hZi10ZXh0V3JhcHt3aWR0aDo5OCU7ZGlzcGxheTpibG9jaztmbG9hdDpub25lO30jYWYtZm9ybS0xOTQyNjY5ODkxIC5hZi1ib2R5IGlucHV0LnRleHQsICNhZi1mb3JtLTE5NDI2Njk4OTEgLmFmLWJvZHkgdGV4dGFyZWF7YmFja2dyb3VuZC1jb2xvcjojRkZGRkZGO2JvcmRlci1jb2xvcjojQ0NDQ0NDO2JvcmRlci13aWR0aDoycHg7Ym9yZGVyLXN0eWxlOmluc2V0O2NvbG9yOiMwMDAwMDA7dGV4dC1kZWNvcmF0aW9uOm5vbmU7Zm9udC1zdHlsZTpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc2l6ZTppbmhlcml0O2ZvbnQtZmFtaWx5OmluaGVyaXQ7fSNhZi1mb3JtLTE5NDI2Njk4OTEgLmFmLWJvZHkgaW5wdXQudGV4dDpmb2N1cywgI2FmLWZvcm0tMTk0MjY2OTg5MSAuYWYtYm9keSB0ZXh0YXJlYTpmb2N1c3tiYWNrZ3JvdW5kLWNvbG9yOmluaGVyaXQ7Ym9yZGVyLWNvbG9yOiNDQ0NDQ0M7Ym9yZGVyLXdpZHRoOjJweDtib3JkZXItc3R5bGU6aW5zZXQ7fSNhZi1mb3JtLTE5NDI2Njk4OTEgLmFmLWJvZHkgbGFiZWwucHJldmlld0xhYmVse2Rpc3BsYXk6YmxvY2s7ZmxvYXQ6bm9uZTt0ZXh0LWFsaWduOmxlZnQ7d2lkdGg6YXV0bztjb2xvcjojMDAwMDAwO3RleHQtZGVjb3JhdGlvbjpub25lO2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXNpemU6aW5oZXJpdDtmb250LWZhbWlseTppbmhlcml0O30jYWYtZm9ybS0xOTQyNjY5ODkxIC5hZi1ib2R5e3BhZGRpbmctYm90dG9tOjE1cHg7YmFja2dyb3VuZC1yZXBlYXQ6bm8tcmVwZWF0O2JhY2tncm91bmQtcG9zaXRpb246aW5oZXJpdDtiYWNrZ3JvdW5kLWltYWdlOm5vbmU7Y29sb3I6IzAwMDAwMDtmb250LXNpemU6MTJweDtmb250LWZhbWlseTosIHNlcmlmO30jYWYtZm9ybS0xOTQyNjY5ODkxIC5hZi1xdWlya3NNb2Rle3BhZGRpbmctcmlnaHQ6MTVweDtwYWRkaW5nLWxlZnQ6MTVweDt9I2FmLWZvcm0tMTk0MjY2OTg5MSAuYWYtc3RhbmRhcmRzIC5hZi1lbGVtZW50e3BhZGRpbmctcmlnaHQ6MTVweDtwYWRkaW5nLWxlZnQ6MTVweDt9I2FmLWZvcm0tMTk0MjY2OTg5MSAuYnV0dG9uQ29udGFpbmVyIGlucHV0LnN1Ym1pdHtjb2xvcjojMDAwMDAwO3RleHQtZGVjb3JhdGlvbjpub25lO2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXNpemU6aW5oZXJpdDtmb250LWZhbWlseTppbmhlcml0O30jYWYtZm9ybS0xOTQyNjY5ODkxIC5idXR0b25Db250YWluZXIgaW5wdXQuc3VibWl0e3dpZHRoOmF1dG87fSNhZi1mb3JtLTE5NDI2Njk4OTEgLmJ1dHRvbkNvbnRhaW5lcnt0ZXh0LWFsaWduOmxlZnQ7fSNhZi1mb3JtLTE5NDI2Njk4OTEgYnV0dG9uLCNhZi1mb3JtLTE5NDI2Njk4OTEgaW5wdXQsI2FmLWZvcm0tMTk0MjY2OTg5MSBzdWJtaXQsI2FmLWZvcm0tMTk0MjY2OTg5MSB0ZXh0YXJlYSwjYWYtZm9ybS0xOTQyNjY5ODkxIHNlbGVjdCwjYWYtZm9ybS0xOTQyNjY5ODkxIGxhYmVsLCNhZi1mb3JtLTE5NDI2Njk4OTEgb3B0Z3JvdXAsI2FmLWZvcm0tMTk0MjY2OTg5MSBvcHRpb257ZmxvYXQ6bm9uZTtwb3NpdGlvbjpzdGF0aWM7bWFyZ2luOjA7fSNhZi1mb3JtLTE5NDI2Njk4OTEgZGl2e21hcmdpbjowO30jYWYtZm9ybS0xOTQyNjY5ODkxIGZvcm0sI2FmLWZvcm0tMTk0MjY2OTg5MSB0ZXh0YXJlYSwuYWYtZm9ybS13cmFwcGVyLC5hZi1mb3JtLWNsb3NlLWJ1dHRvbiwjYWYtZm9ybS0xOTQyNjY5ODkxIGltZ3tmbG9hdDpub25lO2NvbG9yOmluaGVyaXQ7cG9zaXRpb246c3RhdGljO2JhY2tncm91bmQtY29sb3I6bm9uZTtib3JkZXI6bm9uZTttYXJnaW46MDtwYWRkaW5nOjA7fSNhZi1mb3JtLTE5NDI2Njk4OTEgaW5wdXQsI2FmLWZvcm0tMTk0MjY2OTg5MSBidXR0b24sI2FmLWZvcm0tMTk0MjY2OTg5MSB0ZXh0YXJlYSwjYWYtZm9ybS0xOTQyNjY5ODkxIHNlbGVjdHtmb250LXNpemU6MTAwJTt9I2FmLWZvcm0tMTk0MjY2OTg5MSBzZWxlY3QsI2FmLWZvcm0tMTk0MjY2OTg5MSBsYWJlbCwjYWYtZm9ybS0xOTQyNjY5ODkxIG9wdGdyb3VwLCNhZi1mb3JtLTE5NDI2Njk4OTEgb3B0aW9ue3BhZGRpbmc6MDt9I2FmLWZvcm0tMTk0MjY2OTg5MSwjYWYtZm9ybS0xOTQyNjY5ODkxIC5xdWlya3NNb2Rle3dpZHRoOjMwMHB4O30jYWYtZm9ybS0xOTQyNjY5ODkxLmFmLXF1aXJrc01vZGV7b3ZlcmZsb3cteDpoaWRkZW47fSNhZi1mb3JtLTE5NDI2Njk4OTF7YmFja2dyb3VuZC1jb2xvcjp0cmFuc3BhcmVudDtib3JkZXItY29sb3I6IzAwMDAwMDtib3JkZXItd2lkdGg6MXB4O2JvcmRlci1zdHlsZTpub25lO30jYWYtZm9ybS0xOTQyNjY5ODkxe2Rpc3BsYXk6YmxvY2s7fSNhZi1mb3JtLTE5NDI2Njk4OTF7b3ZlcmZsb3c6aGlkZGVuO30uYWYtYm9keSAuYWYtdGV4dFdyYXB7dGV4dC1hbGlnbjpsZWZ0O30uYWYtYm9keSBpbnB1dC5pbWFnZXtib3JkZXI6bm9uZSFpbXBvcnRhbnQ7fS5hZi1ib2R5IGlucHV0LnN1Ym1pdCwuYWYtYm9keSBpbnB1dC5pbWFnZSwuYWYtZm9ybSAuYWYtZWxlbWVudCBpbnB1dC5idXR0b257ZmxvYXQ6bm9uZSFpbXBvcnRhbnQ7fS5hZi1ib2R5IGlucHV0LnRleHR7d2lkdGg6MTAwJTtmbG9hdDpub25lO3BhZGRpbmc6MnB4IWltcG9ydGFudDt9LmFmLWJvZHkuYWYtc3RhbmRhcmRzIGlucHV0LnN1Ym1pdHtwYWRkaW5nOjRweCAxMnB4O30uYWYtY2xlYXJ7Y2xlYXI6Ym90aDt9LmFmLWVsZW1lbnQgbGFiZWx7dGV4dC1hbGlnbjpsZWZ0O2Rpc3BsYXk6YmxvY2s7ZmxvYXQ6bGVmdDt9LmFmLWVsZW1lbnR7cGFkZGluZzo1cHggMDt9LmFmLWZvcm0td3JhcHBlcnt0ZXh0LWluZGVudDowO30uYWYtZm9ybXt0ZXh0LWFsaWduOmxlZnQ7bWFyZ2luOmF1dG87fS5hZi1xdWlya3NNb2RlIC5hZi1lbGVtZW50e3BhZGRpbmctbGVmdDowIWltcG9ydGFudDtwYWRkaW5nLXJpZ2h0OjAhaW1wb3J0YW50O30ubGJsLXJpZ2h0IC5hZi1lbGVtZW50IGxhYmVse3RleHQtYWxpZ246cmlnaHQ7fWJvZHkge30mbHQ7XC9zdHlsZSZndDsmbHQ7Zm9ybSBtZXRob2Q9JnF1b3Q7cG9zdCZxdW90OyBjbGFzcz0mcXVvdDthZi1mb3JtLXdyYXBwZXImcXVvdDsgYWN0aW9uPSZxdW90O2h0dHA6XC9cL3d3dy5hd2ViZXIuY29tXC9zY3JpcHRzXC9hZGRsZWFkLnBsJnF1b3Q7ICAmZ3Q7Jmx0O2RpdiBzdHlsZT0mcXVvdDtkaXNwbGF5OiBub25lOyZxdW90OyZndDsmbHQ7aW5wdXQgdHlwZT0mcXVvdDtoaWRkZW4mcXVvdDsgbmFtZT0mcXVvdDttZXRhX3dlYl9mb3JtX2lkJnF1b3Q7IHZhbHVlPSZxdW90OzE5NDI2Njk4OTEmcXVvdDsgXC8mZ3Q7Jmx0O2lucHV0IHR5cGU9JnF1b3Q7aGlkZGVuJnF1b3Q7IG5hbWU9JnF1b3Q7bWV0YV9zcGxpdF9pZCZxdW90OyB2YWx1ZT0mcXVvdDsmcXVvdDsgXC8mZ3Q7Jmx0O2lucHV0IHR5cGU9JnF1b3Q7aGlkZGVuJnF1b3Q7IG5hbWU9JnF1b3Q7bGlzdG5hbWUmcXVvdDsgdmFsdWU9JnF1b3Q7bGl0dGxlc3RyZWFtJnF1b3Q7IFwvJmd0OyZsdDtpbnB1dCB0eXBlPSZxdW90O2hpZGRlbiZxdW90OyBuYW1lPSZxdW90O3JlZGlyZWN0JnF1b3Q7IHZhbHVlPSZxdW90O2h0dHA6XC9cL3d3dy5hd2ViZXIuY29tXC90aGFua3lvdS1jb2kuaHRtP209dGV4dCZxdW90OyBpZD0mcXVvdDtyZWRpcmVjdF80OTU3Y2FhOTZjMWUyYmJhY2ZjOTYzMDRjYjk2MTFjNiZxdW90OyBcLyZndDsmbHQ7aW5wdXQgdHlwZT0mcXVvdDtoaWRkZW4mcXVvdDsgbmFtZT0mcXVvdDttZXRhX2FkdHJhY2tpbmcmcXVvdDsgdmFsdWU9JnF1b3Q7VEFMZWFkUGxheWVyJnF1b3Q7IFwvJmd0OyZsdDtpbnB1dCB0eXBlPSZxdW90O2hpZGRlbiZxdW90OyBuYW1lPSZxdW90O21ldGFfbWVzc2FnZSZxdW90OyB2YWx1ZT0mcXVvdDsxJnF1b3Q7IFwvJmd0OyZsdDtpbnB1dCB0eXBlPSZxdW90O2hpZGRlbiZxdW90OyBuYW1lPSZxdW90O21ldGFfcmVxdWlyZWQmcXVvdDsgdmFsdWU9JnF1b3Q7ZW1haWwmcXVvdDsgXC8mZ3Q7Jmx0O2lucHV0IHR5cGU9JnF1b3Q7aGlkZGVuJnF1b3Q7IG5hbWU9JnF1b3Q7bWV0YV90b29sdGlwJnF1b3Q7IHZhbHVlPSZxdW90O2VtYWlsfHxFbnRlciB5b3VyIGVtYWlsJnF1b3Q7IFwvJmd0OyZsdDtcL2RpdiZndDsmbHQ7ZGl2IGlkPSZxdW90O2FmLWZvcm0tMTk0MjY2OTg5MSZxdW90OyBjbGFzcz0mcXVvdDthZi1mb3JtJnF1b3Q7Jmd0OyZsdDtkaXYgaWQ9JnF1b3Q7YWYtYm9keS0xOTQyNjY5ODkxJnF1b3Q7ICBjbGFzcz0mcXVvdDthZi1ib2R5IGFmLXN0YW5kYXJkcyZxdW90OyZndDsmbHQ7ZGl2IGNsYXNzPSZxdW90O2FmLWVsZW1lbnQmcXVvdDsmZ3Q7Jmx0O2xhYmVsIGNsYXNzPSZxdW90O3ByZXZpZXdMYWJlbCZxdW90OyBmb3I9JnF1b3Q7YXdmX2ZpZWxkLTM5MzI0MzcyJnF1b3Q7Jmd0OyZsdDtcL2xhYmVsJmd0OyZsdDtkaXYgY2xhc3M9JnF1b3Q7YWYtdGV4dFdyYXAmcXVvdDsmZ3Q7Jmx0O2lucHV0IGNsYXNzPSZxdW90O3RleHQmcXVvdDsgaWQ9JnF1b3Q7YXdmX2ZpZWxkLTM5MzI0MzcyJnF1b3Q7IHR5cGU9JnF1b3Q7dGV4dCZxdW90OyBuYW1lPSZxdW90O2VtYWlsJnF1b3Q7IHZhbHVlPSZxdW90O0VudGVyIHlvdXIgZW1haWwmcXVvdDsgdGFiaW5kZXg9JnF1b3Q7NTAwJnF1b3Q7ICBvbmZvY3VzPSZxdW90OyBpZiAodGhpcy52YWx1ZSA9PSAnRW50ZXIgeW91ciBlbWFpbCcpIHsgdGhpcy52YWx1ZSA9ICcnOyB9JnF1b3Q7IG9uYmx1cj0mcXVvdDtpZiAodGhpcy52YWx1ZSA9PSAnJykgeyB0aGlzLnZhbHVlPSdFbnRlciB5b3VyIGVtYWlsJzt9ICZxdW90OyBcLyZndDsmbHQ7XC9kaXYmZ3Q7Jmx0O2RpdiBjbGFzcz0mcXVvdDthZi1jbGVhciZxdW90OyZndDsmbHQ7XC9kaXYmZ3Q7Jmx0O1wvZGl2Jmd0OyZsdDtkaXYgY2xhc3M9JnF1b3Q7YWYtZWxlbWVudCBidXR0b25Db250YWluZXImcXVvdDsmZ3Q7Jmx0O2lucHV0IG5hbWU9JnF1b3Q7c3VibWl0JnF1b3Q7IGNsYXNzPSZxdW90O3N1Ym1pdCZxdW90OyB0eXBlPSZxdW90O3N1Ym1pdCZxdW90OyB2YWx1ZT0mcXVvdDtTaWduIFVwJnF1b3Q7IHRhYmluZGV4PSZxdW90OzUwMSZxdW90OyBcLyZndDsmbHQ7ZGl2IGNsYXNzPSZxdW90O2FmLWNsZWFyJnF1b3Q7Jmd0OyZsdDtcL2RpdiZndDsmbHQ7XC9kaXYmZ3Q7Jmx0O1wvZGl2Jmd0OyZsdDtcL2RpdiZndDsmbHQ7ZGl2IHN0eWxlPSZxdW90O2Rpc3BsYXk6IG5vbmU7JnF1b3Q7Jmd0OyZsdDtpbWcgc3JjPSZxdW90O2h0dHA6XC9cL2Zvcm1zLmF3ZWJlci5jb21cL2Zvcm1cL2Rpc3BsYXlzLmh0bT9pZD1qSndzVEd4c25CeWNqQT09JnF1b3Q7IGFsdD0mcXVvdDsmcXVvdDsgXC8mZ3Q7Jmx0O1wvZGl2Jmd0OyZsdDtcL2Zvcm0mZ3Q7Jmx0O3NjcmlwdCB0eXBlPSZxdW90O3RleHRcL2phdmFzY3JpcHQmcXVvdDsmZ3Q7ICAgICZsdDshLS0gICAgKGZ1bmN0aW9uKCkgeyAgICAgICAgdmFyIElFID0gXC8qQGNjX29uIUAqXC9mYWxzZTsgICAgICAgIGlmICghSUUpIHsgcmV0dXJuOyB9ICAgICAgICBpZiAoZG9jdW1lbnQuY29tcGF0TW9kZSAmYW1wOyZhbXA7IGRvY3VtZW50LmNvbXBhdE1vZGUgPT0gJ0JhY2tDb21wYXQnKSB7ICAgICAgICAgICAgaWYgKGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCZxdW90O2FmLWZvcm0tMTk0MjY2OTg5MSZxdW90OykpIHsgICAgICAgICAgICAgICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJnF1b3Q7YWYtZm9ybS0xOTQyNjY5ODkxJnF1b3Q7KS5jbGFzc05hbWUgPSAnYWYtZm9ybSBhZi1xdWlya3NNb2RlJzsgICAgICAgICAgICB9ICAgICAgICAgICAgaWYgKGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCZxdW90O2FmLWJvZHktMTk0MjY2OTg5MSZxdW90OykpIHsgICAgICAgICAgICAgICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJnF1b3Q7YWYtYm9keS0xOTQyNjY5ODkxJnF1b3Q7KS5jbGFzc05hbWUgPSAmcXVvdDthZi1ib2R5IGlubGluZSBhZi1xdWlya3NNb2RlJnF1b3Q7OyAgICAgICAgICAgIH0gICAgICAgICAgICBpZiAoZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJnF1b3Q7YWYtaGVhZGVyLTE5NDI2Njk4OTEmcXVvdDspKSB7ICAgICAgICAgICAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCZxdW90O2FmLWhlYWRlci0xOTQyNjY5ODkxJnF1b3Q7KS5jbGFzc05hbWUgPSAmcXVvdDthZi1oZWFkZXIgYWYtcXVpcmtzTW9kZSZxdW90OzsgICAgICAgICAgICB9ICAgICAgICAgICAgaWYgKGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCZxdW90O2FmLWZvb3Rlci0xOTQyNjY5ODkxJnF1b3Q7KSkgeyAgICAgICAgICAgICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgmcXVvdDthZi1mb290ZXItMTk0MjY2OTg5MSZxdW90OykuY2xhc3NOYW1lID0gJnF1b3Q7YWYtZm9vdGVyIGFmLXF1aXJrc01vZGUmcXVvdDs7ICAgICAgICAgICAgfSAgICAgICAgfSAgICB9KSgpOyAgICAtLSZndDsmbHQ7XC9zY3JpcHQmZ3Q7Jmx0OyEtLSBcL0FXZWJlciBXZWIgRm9ybSBHZW5lcmF0b3IgMy4wIC0tJmd0OyIsImZvcm1faGFzaCI6IjcwMjU5MjA2MzZmODg1MDE3ZTQ0NzI4ODQ1OTVlOGNlIiwibmFtZV9lbmFibGVkIjpmYWxzZX0sImN0YSI6ZmFsc2UsInltIjoiUDVyMGI5ZlNNNHcifQ==");</script></div><!-- LeadPlayer video embed code end [ video: 50660374D8891 ] -->
<h2>Full code</h2>
<p>Gemfile</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="ruby" style="font-family:monospace;">source <span style="color:#ff3333; font-weight:bold;">:rubygems</span>
&nbsp;
gem <span style="color:#996600;">&quot;sinatra&quot;</span>, :<span style="color:#CC0066; font-weight:bold;">require</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;sinatra/base&quot;</span>
gem <span style="color:#996600;">&quot;shotgun&quot;</span>
gem <span style="color:#996600;">&quot;redis&quot;</span>
&nbsp;
group <span style="color:#ff3333; font-weight:bold;">:test</span> <span style="color:#9966CC; font-weight:bold;">do</span>
  gem <span style="color:#996600;">&quot;minitest&quot;</span>
  gem <span style="color:#996600;">&quot;rack-test&quot;</span>
  gem <span style="color:#996600;">&quot;autotest&quot;</span>
  gem <span style="color:#996600;">&quot;autotest-notification&quot;</span>
<span style="color:#9966CC; font-weight:bold;">end</span></pre></td></tr></table></div>

<p>RedisRunning Sinatra application</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="ruby" style="font-family:monospace;"><span style="color:#CC0066; font-weight:bold;">require</span> <span style="color:#996600;">'rubygems'</span>
<span style="color:#CC0066; font-weight:bold;">require</span> <span style="color:#996600;">'sinatra'</span>
<span style="color:#CC0066; font-weight:bold;">require</span> <span style="color:#996600;">'securerandom'</span>
<span style="color:#CC0066; font-weight:bold;">require</span> <span style="color:#996600;">'json'</span>
&nbsp;
<span style="color:#ff6633; font-weight:bold;">$db</span> = Redis.<span style="color:#9900CC;">new</span>
<span style="color:#ff6633; font-weight:bold;">$db</span>.<span style="color:#CC0066; font-weight:bold;">select</span><span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#006666;">1</span><span style="color:#006600; font-weight:bold;">&#41;</span> <span style="color:#008000; font-style:italic;"># To get a different database</span>
&nbsp;
<span style="color:#9966CC; font-weight:bold;">class</span> Run
  attr_accessor <span style="color:#ff3333; font-weight:bold;">:id</span>
  attr_accessor <span style="color:#ff3333; font-weight:bold;">:date</span>
  attr_accessor <span style="color:#ff3333; font-weight:bold;">:distance</span>
  attr_accessor <span style="color:#ff3333; font-weight:bold;">:duration</span>
  attr_accessor <span style="color:#ff3333; font-weight:bold;">:pace</span>
  attr_accessor <span style="color:#ff3333; font-weight:bold;">:comment</span>
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">def</span> initialize<span style="color:#006600; font-weight:bold;">&#40;</span>attributes=<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>
    attributes.<span style="color:#9900CC;">each</span> <span style="color:#9966CC; font-weight:bold;">do</span> <span style="color:#006600; font-weight:bold;">|</span>attribute, value<span style="color:#006600; font-weight:bold;">|</span>
      setter = <span style="color:#996600;">&quot;#{attribute}=&quot;</span>
      <span style="color:#0000FF; font-weight:bold;">self</span>.<span style="color:#9900CC;">send</span><span style="color:#006600; font-weight:bold;">&#40;</span>setter, value<span style="color:#006600; font-weight:bold;">&#41;</span> <span style="color:#9966CC; font-weight:bold;">if</span> <span style="color:#0000FF; font-weight:bold;">self</span>.<span style="color:#9900CC;">respond_to</span>?<span style="color:#006600; font-weight:bold;">&#40;</span>setter<span style="color:#006600; font-weight:bold;">&#41;</span>
    <span style="color:#9966CC; font-weight:bold;">end</span>
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">def</span> id
    <span style="color:#0066ff; font-weight:bold;">@id</span> <span style="color:#006600; font-weight:bold;">||</span>= SecureRandom.<span style="color:#9900CC;">uuid</span>
    <span style="color:#0066ff; font-weight:bold;">@id</span>
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  <span style="color:#008000; font-style:italic;"># Save a run to Redis</span>
  <span style="color:#9966CC; font-weight:bold;">def</span> save
    response = <span style="color:#ff6633; font-weight:bold;">$db</span>.<span style="color:#9900CC;">set</span><span style="color:#006600; font-weight:bold;">&#40;</span>id, <span style="color:#006600; font-weight:bold;">&#123;</span>
                         <span style="color:#996600;">&quot;id&quot;</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> id,
                         <span style="color:#996600;">&quot;date&quot;</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#0066ff; font-weight:bold;">@date</span>,
                         <span style="color:#996600;">&quot;distance&quot;</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#0066ff; font-weight:bold;">@distance</span>,
                         <span style="color:#996600;">&quot;duration&quot;</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#0066ff; font-weight:bold;">@duration</span>,
                         <span style="color:#996600;">&quot;pace&quot;</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#0066ff; font-weight:bold;">@pace</span>,
                         <span style="color:#996600;">&quot;comment&quot;</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#0066ff; font-weight:bold;">@comment</span>
                       <span style="color:#006600; font-weight:bold;">&#125;</span>.<span style="color:#9900CC;">to_json</span><span style="color:#006600; font-weight:bold;">&#41;</span>
&nbsp;
    <span style="color:#0000FF; font-weight:bold;">return</span> response == <span style="color:#996600;">&quot;OK&quot;</span>
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">def</span> <span style="color:#0000FF; font-weight:bold;">self</span>.<span style="color:#9900CC;">find</span><span style="color:#006600; font-weight:bold;">&#40;</span>uuid<span style="color:#006600; font-weight:bold;">&#41;</span>
&nbsp;
    record = <span style="color:#ff6633; font-weight:bold;">$db</span>.<span style="color:#9900CC;">get</span><span style="color:#006600; font-weight:bold;">&#40;</span>uuid<span style="color:#006600; font-weight:bold;">&#41;</span>
&nbsp;
    <span style="color:#9966CC; font-weight:bold;">if</span> record
      <span style="color:#0000FF; font-weight:bold;">return</span> new<span style="color:#006600; font-weight:bold;">&#40;</span>JSON.<span style="color:#9900CC;">parse</span><span style="color:#006600; font-weight:bold;">&#40;</span>record<span style="color:#006600; font-weight:bold;">&#41;</span><span style="color:#006600; font-weight:bold;">&#41;</span>
    <span style="color:#9966CC; font-weight:bold;">else</span>
      <span style="color:#0000FF; font-weight:bold;">return</span> <span style="color:#0000FF; font-weight:bold;">nil</span>
    <span style="color:#9966CC; font-weight:bold;">end</span>
  <span style="color:#9966CC; font-weight:bold;">end</span>
<span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
&nbsp;
<span style="color:#9966CC; font-weight:bold;">class</span> RedisRunning <span style="color:#006600; font-weight:bold;">&lt;</span> <span style="color:#6666ff; font-weight:bold;">Sinatra::Base</span>
  get <span style="color:#996600;">'/'</span> <span style="color:#9966CC; font-weight:bold;">do</span>
    <span style="color:#996600;">&quot;Hi&quot;</span>
  <span style="color:#9966CC; font-weight:bold;">end</span>
<span style="color:#9966CC; font-weight:bold;">end</span></pre></td></tr></table></div>

<p>Application tests</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="ruby" style="font-family:monospace;"><span style="color:#CC0066; font-weight:bold;">require</span> <span style="color:#996600;">'rubygems'</span>
<span style="color:#CC0066; font-weight:bold;">require</span> <span style="color:#996600;">'bundler'</span>
&nbsp;
Bundler.<span style="color:#CC0066; font-weight:bold;">require</span>
&nbsp;
<span style="color:#CC0066; font-weight:bold;">require</span> <span style="color:#996600;">'./redis_running'</span>
<span style="color:#CC0066; font-weight:bold;">require</span> <span style="color:#996600;">'minitest/autorun'</span>
<span style="color:#CC0066; font-weight:bold;">require</span> <span style="color:#996600;">'rack/test'</span>
&nbsp;
ENV<span style="color:#006600; font-weight:bold;">&#91;</span><span style="color:#996600;">'RACK_ENV'</span><span style="color:#006600; font-weight:bold;">&#93;</span> = <span style="color:#996600;">'test'</span>
&nbsp;
<span style="color:#9966CC; font-weight:bold;">class</span> RedisRunningTest <span style="color:#006600; font-weight:bold;">&lt;</span> <span style="color:#6666ff; font-weight:bold;">MiniTest::Unit::TestCase</span>
  <span style="color:#9966CC; font-weight:bold;">include</span> Rack::<span style="color:#CC00FF; font-weight:bold;">Test</span>::Methods
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">def</span> app
    RedisRunning
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">def</span> setup
    <span style="color:#008000; font-style:italic;"># Select a test database and clean it</span>
    <span style="color:#ff6633; font-weight:bold;">$db</span>.<span style="color:#CC0066; font-weight:bold;">select</span><span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#006666;">2</span><span style="color:#006600; font-weight:bold;">&#41;</span>
    assert_equal <span style="color:#996600;">&quot;OK&quot;</span>, <span style="color:#ff6633; font-weight:bold;">$db</span>.<span style="color:#9900CC;">flushdb</span>
    assert_equal <span style="color:#006600; font-weight:bold;">&#91;</span><span style="color:#006600; font-weight:bold;">&#93;</span>, <span style="color:#ff6633; font-weight:bold;">$db</span>.<span style="color:#9900CC;">keys</span>
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">def</span> test_sanity
    assert_equal <span style="color:#006666;">2</span>, <span style="color:#006666;">1</span> <span style="color:#006600; font-weight:bold;">+</span> <span style="color:#006666;">1</span>
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">def</span> test_get_root_response
    get <span style="color:#996600;">'/'</span>
&nbsp;
    assert last_response.<span style="color:#9900CC;">ok</span>?
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">def</span> test_get_root_list_runs
    run1 = Run.<span style="color:#9900CC;">new</span><span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#ff3333; font-weight:bold;">:date</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;2012-09-28&quot;</span>,
                   <span style="color:#ff3333; font-weight:bold;">:distance</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;3mi&quot;</span>,
                   <span style="color:#ff3333; font-weight:bold;">:duration</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;30:00&quot;</span>,
                   <span style="color:#ff3333; font-weight:bold;">:pace</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;10:00&quot;</span>,
                   <span style="color:#ff3333; font-weight:bold;">:comment</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;A run&quot;</span><span style="color:#006600; font-weight:bold;">&#41;</span>
    assert run1.<span style="color:#9900CC;">save</span>, <span style="color:#996600;">&quot;Save failed&quot;</span>
&nbsp;
    run2 = Run.<span style="color:#9900CC;">new</span><span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#ff3333; font-weight:bold;">:date</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;2012-09-29&quot;</span>,
                   <span style="color:#ff3333; font-weight:bold;">:distance</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;3mi&quot;</span>,
                   <span style="color:#ff3333; font-weight:bold;">:duration</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;27:00&quot;</span>,
                   <span style="color:#ff3333; font-weight:bold;">:pace</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;9:00&quot;</span>,
                   <span style="color:#ff3333; font-weight:bold;">:comment</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;Another run&quot;</span><span style="color:#006600; font-weight:bold;">&#41;</span>
    assert run2.<span style="color:#9900CC;">save</span>, <span style="color:#996600;">&quot;Save failed&quot;</span>
&nbsp;
    get <span style="color:#996600;">'/'</span>
&nbsp;
    assert last_response.<span style="color:#9900CC;">body</span>.<span style="color:#9966CC; font-weight:bold;">include</span>?<span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#996600;">&quot;A run&quot;</span><span style="color:#006600; font-weight:bold;">&#41;</span>
    assert last_response.<span style="color:#9900CC;">body</span>.<span style="color:#9966CC; font-weight:bold;">include</span>?<span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#996600;">&quot;Another run&quot;</span><span style="color:#006600; font-weight:bold;">&#41;</span>
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">def</span> test_get_root_add_form_present
    get <span style="color:#996600;">'/'</span>
&nbsp;
    assert last_response.<span style="color:#9900CC;">body</span>.<span style="color:#9966CC; font-weight:bold;">include</span>?<span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#996600;">&quot;form&quot;</span><span style="color:#006600; font-weight:bold;">&#41;</span>
    assert last_response.<span style="color:#9900CC;">body</span>.<span style="color:#9966CC; font-weight:bold;">include</span>?<span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#996600;">&quot;Add a new run&quot;</span><span style="color:#006600; font-weight:bold;">&#41;</span>
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">def</span> test_post_new_with_valid_run
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">def</span> test_post_new_with_invalid_run
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">def</span> test_get_edit_response
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">def</span> test_get_edit_show_form
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">def</span> test_put_update_with_valid_update
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">def</span> test_put_update_with_invalid_update
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">def</span> test_delete_response
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">def</span> test_delete_should_delete_record
  <span style="color:#9966CC; font-weight:bold;">end</span>
<span style="color:#9966CC; font-weight:bold;">end</span></pre></td></tr></table></div>

<p>Data storage tests</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="ruby" style="font-family:monospace;"><span style="color:#CC0066; font-weight:bold;">require</span> <span style="color:#996600;">'rubygems'</span>
<span style="color:#CC0066; font-weight:bold;">require</span> <span style="color:#996600;">'bundler'</span>
&nbsp;
Bundler.<span style="color:#CC0066; font-weight:bold;">require</span>
&nbsp;
<span style="color:#CC0066; font-weight:bold;">require</span> <span style="color:#996600;">'./redis_running'</span>
<span style="color:#CC0066; font-weight:bold;">require</span> <span style="color:#996600;">'minitest/autorun'</span>
<span style="color:#CC0066; font-weight:bold;">require</span> <span style="color:#996600;">'rack/test'</span>
&nbsp;
ENV<span style="color:#006600; font-weight:bold;">&#91;</span><span style="color:#996600;">'RACK_ENV'</span><span style="color:#006600; font-weight:bold;">&#93;</span> = <span style="color:#996600;">'test'</span>
&nbsp;
<span style="color:#9966CC; font-weight:bold;">class</span> RunTest <span style="color:#006600; font-weight:bold;">&lt;</span> <span style="color:#6666ff; font-weight:bold;">MiniTest::Unit::TestCase</span>
  <span style="color:#9966CC; font-weight:bold;">def</span> setup
    <span style="color:#008000; font-style:italic;"># Select a test database and clean it</span>
    <span style="color:#ff6633; font-weight:bold;">$db</span>.<span style="color:#CC0066; font-weight:bold;">select</span><span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#006666;">2</span><span style="color:#006600; font-weight:bold;">&#41;</span>
    assert_equal <span style="color:#996600;">&quot;OK&quot;</span>, <span style="color:#ff6633; font-weight:bold;">$db</span>.<span style="color:#9900CC;">flushdb</span>
    assert_equal <span style="color:#006600; font-weight:bold;">&#91;</span><span style="color:#006600; font-weight:bold;">&#93;</span>, <span style="color:#ff6633; font-weight:bold;">$db</span>.<span style="color:#9900CC;">keys</span>
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">def</span> test_has_attributes
    run = Run.<span style="color:#9900CC;">new</span>
&nbsp;
    <span style="color:#006600; font-weight:bold;">&#91;</span><span style="color:#996600;">'date'</span>,<span style="color:#996600;">'distance'</span>,<span style="color:#996600;">'duration'</span>,<span style="color:#996600;">'pace'</span>,<span style="color:#996600;">'comment'</span><span style="color:#006600; font-weight:bold;">&#93;</span>.<span style="color:#9900CC;">each</span> <span style="color:#9966CC; font-weight:bold;">do</span> <span style="color:#006600; font-weight:bold;">|</span>attribute<span style="color:#006600; font-weight:bold;">|</span>
      assert run.<span style="color:#9900CC;">respond_to</span>?<span style="color:#006600; font-weight:bold;">&#40;</span>attribute<span style="color:#006600; font-weight:bold;">&#41;</span>, <span style="color:#996600;">&quot;Run does not respond to #{attribute}&quot;</span>
      assert run.<span style="color:#9900CC;">respond_to</span>?<span style="color:#006600; font-weight:bold;">&#40;</span>attribute <span style="color:#006600; font-weight:bold;">+</span> <span style="color:#996600;">&quot;=&quot;</span><span style="color:#006600; font-weight:bold;">&#41;</span>, <span style="color:#996600;">&quot;Run does not respond to #{attribute}=&quot;</span>
    <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">def</span> test_should_allow_setting_attributes_on_initialize
    run = Run.<span style="color:#9900CC;">new</span><span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#ff3333; font-weight:bold;">:date</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">'1'</span>, <span style="color:#ff3333; font-weight:bold;">:distance</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">'2'</span>, <span style="color:#ff3333; font-weight:bold;">:duration</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">'3'</span>, <span style="color:#ff3333; font-weight:bold;">:pace</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">'4'</span>, <span style="color:#ff3333; font-weight:bold;">:comment</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">'5'</span><span style="color:#006600; font-weight:bold;">&#41;</span>
&nbsp;
    assert_equal <span style="color:#996600;">'1'</span>, run.<span style="color:#9900CC;">date</span>
    assert_equal <span style="color:#996600;">'2'</span>, run.<span style="color:#9900CC;">distance</span>
    assert_equal <span style="color:#996600;">'3'</span>, run.<span style="color:#9900CC;">duration</span>
    assert_equal <span style="color:#996600;">'4'</span>, run.<span style="color:#9900CC;">pace</span>
    assert_equal <span style="color:#996600;">'5'</span>, run.<span style="color:#9900CC;">comment</span>
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">def</span> test_id_should_be_a_uuid
    run = Run.<span style="color:#9900CC;">new</span>
&nbsp;
    assert run.<span style="color:#9900CC;">id</span>.<span style="color:#9900CC;">is_a</span>?<span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#CC0066; font-weight:bold;">String</span><span style="color:#006600; font-weight:bold;">&#41;</span>
    assert_equal <span style="color:#006666;">36</span>, run.<span style="color:#9900CC;">id</span>.<span style="color:#9900CC;">length</span>
    assert_equal <span style="color:#006666;">4</span>, run.<span style="color:#9900CC;">id</span>.<span style="color:#9900CC;">count</span><span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#996600;">'-'</span><span style="color:#006600; font-weight:bold;">&#41;</span>
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">def</span> test_id_should_be_unique
    <span style="color:#008000; font-style:italic;"># Lets create a bunch of runs and check their uniqueness.</span>
    <span style="color:#008000; font-style:italic;"># TODO: not the best solution but time is limited</span>
    runs = <span style="color:#006666;">100</span>.<span style="color:#9900CC;">times</span>.<span style="color:#9900CC;">collect</span> <span style="color:#006600; font-weight:bold;">&#123;</span> Run.<span style="color:#9900CC;">new</span>.<span style="color:#9900CC;">id</span> <span style="color:#006600; font-weight:bold;">&#125;</span>
&nbsp;
    assert_equal <span style="color:#006666;">100</span>, runs.<span style="color:#9900CC;">uniq</span>.<span style="color:#9900CC;">length</span>, <span style="color:#996600;">&quot;Duplicate ids found&quot;</span>
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">def</span> test_save_with_valid_attributes
    run = Run.<span style="color:#9900CC;">new</span><span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#ff3333; font-weight:bold;">:date</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;2012-09-28&quot;</span>,
                  <span style="color:#ff3333; font-weight:bold;">:distance</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;3mi&quot;</span>,
                  <span style="color:#ff3333; font-weight:bold;">:duration</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;30:00&quot;</span>,
                  <span style="color:#ff3333; font-weight:bold;">:pace</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;10:00&quot;</span>,
                  <span style="color:#ff3333; font-weight:bold;">:comment</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;A run&quot;</span><span style="color:#006600; font-weight:bold;">&#41;</span>
&nbsp;
    assert run.<span style="color:#9900CC;">save</span>, <span style="color:#996600;">&quot;Save failed&quot;</span>
&nbsp;
    assert <span style="color:#ff6633; font-weight:bold;">$db</span>.<span style="color:#9900CC;">keys</span>.<span style="color:#9966CC; font-weight:bold;">include</span>?<span style="color:#006600; font-weight:bold;">&#40;</span>run.<span style="color:#9900CC;">id</span><span style="color:#006600; font-weight:bold;">&#41;</span>, <span style="color:#996600;">&quot;Run not found in Redis&quot;</span>
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">def</span> test_save_with_mostly_empty_attributes
    run = Run.<span style="color:#9900CC;">new</span><span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#ff3333; font-weight:bold;">:date</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;&quot;</span>,
                  <span style="color:#ff3333; font-weight:bold;">:distance</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;&quot;</span>,
                  <span style="color:#ff3333; font-weight:bold;">:duration</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;&quot;</span>,
                  <span style="color:#ff3333; font-weight:bold;">:pace</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;&quot;</span>,
                  <span style="color:#ff3333; font-weight:bold;">:comment</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;&quot;</span><span style="color:#006600; font-weight:bold;">&#41;</span>
&nbsp;
    assert run.<span style="color:#9900CC;">save</span>, <span style="color:#996600;">&quot;Save failed&quot;</span>
&nbsp;
    assert <span style="color:#ff6633; font-weight:bold;">$db</span>.<span style="color:#9900CC;">keys</span>.<span style="color:#9966CC; font-weight:bold;">include</span>?<span style="color:#006600; font-weight:bold;">&#40;</span>run.<span style="color:#9900CC;">id</span><span style="color:#006600; font-weight:bold;">&#41;</span>, <span style="color:#996600;">&quot;Run not found in Redis&quot;</span>
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
  <span style="color:#9966CC; font-weight:bold;">def</span> test_find_existing_run
    run = Run.<span style="color:#9900CC;">new</span><span style="color:#006600; font-weight:bold;">&#40;</span><span style="color:#ff3333; font-weight:bold;">:date</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;2012-09-28&quot;</span>,
                  <span style="color:#ff3333; font-weight:bold;">:distance</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;3mi&quot;</span>,
                  <span style="color:#ff3333; font-weight:bold;">:duration</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;30:00&quot;</span>,
                  <span style="color:#ff3333; font-weight:bold;">:pace</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;10:00&quot;</span>,
                  <span style="color:#ff3333; font-weight:bold;">:comment</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> <span style="color:#996600;">&quot;A run&quot;</span><span style="color:#006600; font-weight:bold;">&#41;</span>
&nbsp;
    assert run.<span style="color:#9900CC;">save</span>, <span style="color:#996600;">&quot;Save failed&quot;</span>
&nbsp;
    redis_run = Run.<span style="color:#9900CC;">find</span><span style="color:#006600; font-weight:bold;">&#40;</span>run.<span style="color:#9900CC;">id</span><span style="color:#006600; font-weight:bold;">&#41;</span>
    assert_equal run.<span style="color:#9900CC;">id</span>, redis_run.<span style="color:#9900CC;">id</span>
    assert_equal <span style="color:#996600;">&quot;2012-09-28&quot;</span>, redis_run.<span style="color:#9900CC;">date</span>
    assert_equal <span style="color:#996600;">&quot;3mi&quot;</span>, redis_run.<span style="color:#9900CC;">distance</span>
    assert_equal <span style="color:#996600;">&quot;30:00&quot;</span>, redis_run.<span style="color:#9900CC;">duration</span>
    assert_equal <span style="color:#996600;">&quot;10:00&quot;</span>, redis_run.<span style="color:#9900CC;">pace</span>
    assert_equal <span style="color:#996600;">&quot;A run&quot;</span>, redis_run.<span style="color:#9900CC;">comment</span>
  <span style="color:#9966CC; font-weight:bold;">end</span>
&nbsp;
<span style="color:#9966CC; font-weight:bold;">end</span></pre></td></tr></table></div>

<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/theAdmin?a=EApTYVRp-5I:DhGHDTltWuw:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/theAdmin?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/theAdmin?a=EApTYVRp-5I:DhGHDTltWuw:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/theAdmin?i=EApTYVRp-5I:DhGHDTltWuw:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/theAdmin?a=EApTYVRp-5I:DhGHDTltWuw:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/theAdmin?i=EApTYVRp-5I:DhGHDTltWuw:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/theAdmin?a=EApTYVRp-5I:DhGHDTltWuw:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/theAdmin?i=EApTYVRp-5I:DhGHDTltWuw:F7zBnMyn0Lo" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/theAdmin/~4/EApTYVRp-5I" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://theadmin.org/articles/learning-redis/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		<feedburner:origLink>http://theadmin.org/articles/learning-redis/</feedburner:origLink></item>
	</channel>
</rss>
