<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">
  <title>Collective Idea Blog</title>
  
  <link href="http://collectiveidea.com/blog/" />
  <id>4b57a16edabe9d744d000001</id>
  <updated>2013-05-23T12:18:46-04:00</updated>
 
  
    <atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/collectiveidea" /><feedburner:info uri="collectiveidea" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry>
      <title>Batch Number Increment in Vim</title>
      <link href="http://feedproxy.google.com/~r/collectiveidea/~3/Ue_hROT_QtQ/" />
      <id>519e0d90aa707a23b50007be</id>
      <updated>2013-05-23T12:18:46-04:00</updated>
      <published>2013-05-23T12:00:00-04:00</published>
      <content type="html">&lt;p&gt;We&amp;#8217;re fans of Vim at Collective Idea, with the largest percentage of our staff using it as their primary editor.  We&amp;#8217;ve even added a &lt;a href="http://collectiveidea.com/about/#steve"&gt;few&lt;/a&gt; &lt;a href="http://collectiveidea.com/about/#jonathan"&gt;recent&lt;/a&gt; &lt;sup class="footnote" id="fnr1"&gt;&lt;a href="#fn1"&gt;1&lt;/a&gt;&lt;/sup&gt; &lt;a href="http://trestrantham.com"&gt;converts&lt;/a&gt;.  Go Vim!&lt;/p&gt;
&lt;h2&gt;Setting the Stage&lt;/h2&gt;
&lt;p&gt;This morning I needed to quickly increment multiple sets of numbers.  Too tedious to edit each number by hand, I wanted to figure out how to best do this with Vim.&lt;/p&gt;
&lt;p&gt;We&amp;#8217;ll use a simplified example to demonstrate the task at hand &amp;mdash; globally increment a series of numbers by a given value.  The first block of &lt;span class="caps"&gt;HTML&lt;/span&gt; is the original code and the second block of &lt;span class="caps"&gt;HTML&lt;/span&gt; is the desired output:&lt;/p&gt;
&lt;pre&gt;
&amp;lt;tr&amp;gt;
   &amp;lt;td&amp;gt;&amp;lt;img src="1.jpg" data-alternate="alternates/1.jpg"&amp;gt;
   &amp;lt;td&amp;gt;&amp;lt;img src="2.jpg" data-alternate="alternates/2.jpg"&amp;gt;
   &amp;lt;td&amp;gt;&amp;lt;img src="3.jpg" data-alternate="alternates/3.jpg"&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
   &amp;lt;td&amp;gt;&amp;lt;img src="1.jpg" data-alternate="alternates/1.jpg"&amp;gt;
   &amp;lt;td&amp;gt;&amp;lt;img src="2.jpg" data-alternate="alternates/2.jpg"&amp;gt;
   &amp;lt;td&amp;gt;&amp;lt;img src="3.jpg" data-alternate="alternates/3.jpg"&amp;gt;
&amp;lt;/tr&amp;gt;
&lt;/pre&gt;

&lt;pre&gt;
&amp;lt;tr&amp;gt;
   &amp;lt;td&amp;gt;&amp;lt;img src="1.jpg" data-alternate="alternates/1.jpg"&amp;gt;
   &amp;lt;td&amp;gt;&amp;lt;img src="2.jpg" data-alternate="alternates/2.jpg"&amp;gt;
   &amp;lt;td&amp;gt;&amp;lt;img src="3.jpg" data-alternate="alternates/3.jpg"&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
   &amp;lt;td&amp;gt;&amp;lt;img src="4.jpg" data-alternate="alternates/4.jpg"&amp;gt;
   &amp;lt;td&amp;gt;&amp;lt;img src="5.jpg" data-alternate="alternates/5.jpg"&amp;gt;
   &amp;lt;td&amp;gt;&amp;lt;img src="6.jpg" data-alternate="alternates/6.jpg"&amp;gt;
&amp;lt;/tr&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Here are the steps I took and what I found.&lt;/p&gt;
&lt;h2&gt;A First Pass&lt;/h2&gt;
&lt;p&gt;I initially reached for Vim&amp;#8217;s &lt;a href="http://vimdoc.sourceforge.net/cgi-bin/help?tag=CTRL-A"&gt;&lt;span class="caps"&gt;CTRL&lt;/span&gt;-A&lt;/a&gt;, a very handy tool for incrementing&lt;sup class="footnote" id="fnr2"&gt;&lt;a href="#fn2"&gt;2&lt;/a&gt;&lt;/sup&gt; numbers.  Anywhere on the line, &lt;code&gt;3&amp;lt;c-a&amp;gt;&lt;/code&gt; would increment by 3 the first occurrence of a number relative to the position of the cursor.&lt;/p&gt;
&lt;p&gt;Awesome!  Except that that only increments  the first number and Vim&amp;#8217;s &lt;a href="http://vimdoc.sourceforge.net/htmldoc/repeat.html#single-repeat"&gt;single-repeat&lt;/a&gt; repeater, instead of moving to the next number, re-increments that number again by 3.&lt;/p&gt;
&lt;p&gt;Perhaps one of Vim&amp;#8217;s other repeaters, a macro, or single-repeat with an added &lt;a href="http://vimdoc.sourceforge.net/htmldoc/motion.html"&gt;motion&lt;/a&gt; would have worked, but as with most tasks in Vim with excessive repeated keystrokes there&amp;#8217;s usually a better way.&lt;/p&gt;
&lt;h2&gt;Enter Sub-Replace-Expression&lt;/h2&gt;
&lt;p&gt;Vim&amp;#8217;s &lt;a href="http://vimdoc.sourceforge.net/cgi-bin/help?tag=sub-replace-expression"&gt;sub-replace-expression&lt;/a&gt; allows the second half of a substitution to contain logic to be evaluated.  Prepending a \= signals to Vim that what&amp;#8217;s to follow should first be evaluated before being used as the replacement value in the substitution.&lt;/p&gt;
&lt;p&gt;In our example, &lt;a href="http://vimdoc.sourceforge.net/htmldoc/visual.html#Visual"&gt;visual line select&lt;/a&gt; the second set of &lt;code&gt;&amp;lt;td&amp;gt;'s&lt;/code&gt; and execute the command &lt;code&gt;:'&amp;lt;,'&amp;gt;s/\d\+/\=submatch(0)+3/g&lt;/code&gt;.  Presto!&lt;/p&gt;
&lt;pre&gt;
# match against 1 or more digits
\d\+ 

# begin replace expression
\=

# submatch({nr})
#
# return the nth submatch of the matched text, 0 returns
# the whole matched text; add 3
submatch(0)+3

# perform the substitution globally
/g
&lt;/pre&gt;
&lt;p class="footnote" id="fn1"&gt;&lt;a href="#fnr1"&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt; A convert in progress.  I believe in you, Jonathan.&lt;/p&gt;
&lt;p class="footnote" id="fn2"&gt;&lt;a href="#fnr2"&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; &lt;span class="caps"&gt;CTRL&lt;/span&gt;-X handles decrementing.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/collectiveidea/~4/Ue_hROT_QtQ" height="1" width="1"/&gt;</content>
      <author>
        <name>Eric Milford</name>
      </author>
    <feedburner:origLink>http://collectiveidea.com/blog/archives/2013/05/23/batch-number-increment-in-vim/</feedburner:origLink></entry>
  
    <entry>
      <title>Turning Off</title>
      <link href="http://feedproxy.google.com/~r/collectiveidea/~3/oR73haFAefE/" />
      <id>5187b6c87a50722d5e000397</id>
      <updated>2013-05-06T11:16:33-04:00</updated>
      <published>2013-05-06T09:00:00-04:00</published>
      <content type="html">&lt;p&gt;Now is the time to disconnect. Not forever, not always, but we need to disconnect so we can reconnect with the world.&lt;/p&gt;
&lt;p&gt;There&amp;#8217;s lots of new writing on the idea of moving away from our always connected state, from &lt;a href="http://www.theverge.com/2013/5/1/4279674/im-still-here-back-online-after-a-year-without-the-internet"&gt;Paul Miller writing for The Verge&lt;/a&gt; about taking a year off of the Internet, to &lt;a href="http://www.rushkoff.com/present-shock/"&gt;Douglas Rushkoff&amp;#8217;s &lt;em&gt;Present Shock&lt;/em&gt;&lt;/a&gt; which argues that we&amp;#8217;re trying so hard to live in the moment that we&amp;#8217;re actually living in the past and missing the present.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m also becoming a fan of the &lt;a href="http://blog.idonethis.com/post/21267449208/the-slow-web-movement"&gt;Slow Web&lt;/a&gt; movement. Not everything we do needs to be instant; it instead needs to be timely. I fell into this trap this week, as we launched &lt;a href="http://www.downsideapp.com/"&gt;our new app&lt;/a&gt; and I was immediately refreshing Facebook, Twitter, and various analytics services trying to see how fast it would go viral. I couldn&amp;#8217;t turn off, because I wanted to see things happen in real time, even though I&amp;#8217;m actually seeing what just happened in the &amp;#8220;near past.&amp;#8221; Fortuntely, I was touristing in Belgium, so once I left the hotel I was free from my addiction, but I&amp;#8217;m sure I would have had mostly unproductive days had I been in the office looking at all the stats.&lt;/p&gt;
&lt;p&gt;One of the most frustrating statistics when launching an iOS app can actually be somewhat freeing: Download numbers. Apple only updates your download &amp;amp; sales numbers once per day, so you really only get to see what happened yesterday. At first I was frustrated that I couldn&amp;#8217;t keep checking and see live progress. After a couple days, I realized the delay meant I could stop obsessing. Minute-by-minute changes wouldn&amp;#8217;t affect our plans, and would only serve to distract me further.&lt;/p&gt;
&lt;p&gt;We (especially as developers) live in a world of push notifications, live-updating pages, and instant results. This speed gives us immense power but, most of the time, the distractions aren&amp;#8217;t worth the opportunity cost of slowing down. With an iOS app it takes us about a week to push an update out to our customers so, unlike a website, we have little need to be so reactionary. Maybe my vacation was well-timed after all.&lt;/p&gt;
&lt;p&gt;I think we&amp;#8217;re coming to a new phase in our tech lives where we discard our always-on lives for a more deliberate usage model. We&amp;#8217;ll applaud people who have super-productive spurts of connectivity more than the &amp;#8220;inbox zero&amp;#8221; champion who can respond to any message in seconds. I don&amp;#8217;t see us ever removing the phones from our pockets, but I see us leaving them in the pockets more often.&lt;/p&gt;
&lt;p&gt;We built &lt;a href="http://www.downsideapp.com/"&gt;Downside&lt;/a&gt; to combat the problem of people always being on their phones when in public, but our team has found it fairly easy to play. Something about having your phone in front of you helps remind you that you don&amp;#8217;t need it. You can check that fact on Wikipedia later. You can answer your email after you get back from lunch.&lt;/p&gt;
&lt;p&gt;I wonder if we&amp;#8217;ll see a split between the super-connected and the &amp;#8220;disconnecters&amp;#8221; coming soon. The super-connected are excited about Google Glass, where the disconnecters are skeptical. The disconnecters are the ones who have found productivity gains by closing their email &amp;amp; Twitter clients during the day, and have turned off most push notifications. The super-connected are feeding their appetite and can&amp;#8217;t go 5 minutes without checking at least one device. Here&amp;#8217;s a tip for disconnecters: play &lt;a href="http://www.downsideapp.com/"&gt;Downside&lt;/a&gt; with the super-connected and wager on the outcome. You&amp;#8217;ll get a lot of free lunches.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/collectiveidea/~4/oR73haFAefE" height="1" width="1"/&gt;</content>
      <author>
        <name>Daniel Morrison</name>
      </author>
    <feedburner:origLink>http://collectiveidea.com/blog/archives/2013/05/06/turning-off/</feedburner:origLink></entry>
  
    <entry>
      <title>Meet Downside, our First iPhone Game!</title>
      <link href="http://feedproxy.google.com/~r/collectiveidea/~3/Cj91WiuVGBE/" />
      <id>517336e8aa707a1a9100000a</id>
      <updated>2013-04-30T18:02:36-04:00</updated>
      <published>2013-04-30T18:00:00-04:00</published>
      <content type="html">&lt;p&gt;I&amp;#8217;m extremely excited to introduce &lt;a href="http://www.downsideapp.com/"&gt;Downside&lt;/a&gt;, our first foray into building a game for iOS.&lt;/p&gt;
&lt;p&gt;Downside started as most great ideas do: as a crazy idea at happy hour. We had played a version of the game for almost a year, but &lt;a href="/about/#tim"&gt;Tim&lt;/a&gt; and &lt;a href="/about/#brian"&gt;Brian&lt;/a&gt; realized that it would make a great App too and they&amp;#8217;re right.&lt;/p&gt;
&lt;p&gt;The premise of Downside is simple: Most of us are used to staring at our phones instead of talking to the people in the room. Downside makes a game out of keeping your hands off your phone. We&amp;#8217;ve been beta testing at lunch and happy hours for a few weeks and we usually go through an entire meal without anyone touching their phone. If we can do it, you can too.&lt;/p&gt;
&lt;p&gt;Downside uses Game Kit to find people in the same physical space as you, and you can quickly create a game with anyone around you without having to be friends on any kind of social network. Once you&amp;#8217;re in a game, everyone flips their phones face down and the game begins. As soon as you pick it up, you lose.&lt;/p&gt;
&lt;p&gt;We have lots of plans for additional game modes too, but we want you to see our first version and tell us what you think! &lt;a href="https://itunes.apple.com/us/app/downside/id604742184?ls=1&amp;amp;mt=8"&gt;Download it today free on the App Store&lt;/a&gt; and drop us a comment or a review. We&amp;#8217;d love to know how you like it.&lt;/p&gt;
&lt;p&gt;Some of our early users will wager a round of drinks, whereas families are getting their kids to pay attention around the dinner table. There&amp;#8217;s even been talk about using it in business meetings to focus on the task at hand. We know people are doing this already without Downside, but now there&amp;#8217;s an app for that!&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/51733ae78ad7ca68670011c6/fit_in_blog_post/downside_iphone4_lobby.png" alt="" /&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/collectiveidea/~4/Cj91WiuVGBE" height="1" width="1"/&gt;</content>
      <author>
        <name>Daniel Morrison</name>
      </author>
    <feedburner:origLink>http://collectiveidea.com/blog/archives/2013/04/30/meet-downside/</feedburner:origLink></entry>
  
    <entry>
      <title>Conference Season: 2013</title>
      <link href="http://feedproxy.google.com/~r/collectiveidea/~3/Zcneaf6OSKQ/" />
      <id>512e298c8ad7ca6f3f0021d5</id>
      <updated>2013-02-27T11:12:24-05:00</updated>
      <published>2013-02-27T10:00:00-05:00</published>
      <content type="html">&lt;p&gt;We&amp;#8217;re gearing up for Conference Season. Want to see us in the flesh? Avoid us? Here&amp;#8217;s the start of our schedule so you can plan accordingly. We hope to see you at these and many more!&lt;/p&gt;
&lt;h2&gt;&lt;a href="http://grdevday.org"&gt;GR Dev Day&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This coming Saturday, both Tim Bugai and Jason Carpenter will be presenting. Tickets are sold out, but maybe they&amp;#8217;ll start showing up on StubHub.&lt;/p&gt;
&lt;h2&gt;&lt;a href="http://sxsw.com/interactive"&gt;SXSWi&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Chris Gaffney, Jason Carpenter and I will be at the Interactive Festival this year, and though there will be far too many people, we&amp;#8217;d love you to say hi if you&amp;#8217;re going!&lt;/p&gt;
&lt;h2&gt;&lt;a href="http://mtnwestrubyconf.org"&gt;MountainWest RubyConf&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Jason Roelofs is speaking in the DevOps track at MountainWest, April 3–5.&lt;/p&gt;
&lt;h2&gt;&lt;a href="http://glsec.softwaregr.org"&gt;Great Lakes Software Excellence Conference&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Tim Bugai will be speaking at &lt;span class="caps"&gt;GLSEC&lt;/span&gt;, and you&amp;#8217;ll likely see a few more of us there on April 29.&lt;/p&gt;
&lt;h2&gt;&lt;a href="http://www.railsconf.com"&gt;RailsConf&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We&amp;#8217;re sending a big crew to RailsConf this year. Brian Ryckbost, Steve Richert, Chris Gaffney, David Genord, Ryan Glover, Eric Milford, and Jonathan Pichot will all be there, and looking to hang out April 29–May 2.&lt;/p&gt;
&lt;h2&gt;&lt;a href="http://2013.scottishrubyconference.com"&gt;Scottish Ruby Conference&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Finally, I&amp;#8217;ll be at the Scottish Ruby Conference May 12–13. I&amp;#8217;ll be the American looking for as much Haggis as I can get. See you there!&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/collectiveidea/~4/Zcneaf6OSKQ" height="1" width="1"/&gt;</content>
      <author>
        <name>Daniel Morrison</name>
      </author>
    <feedburner:origLink>http://collectiveidea.com/blog/archives/2013/02/27/conference-season-2013/</feedburner:origLink></entry>
  
    <entry>
      <title>Becoming a Rubyist</title>
      <link href="http://feedproxy.google.com/~r/collectiveidea/~3/CfkgPt3xaas/" />
      <id>5126a452aa707a3bbe0015b5</id>
      <updated>2013-02-25T10:00:23-05:00</updated>
      <published>2013-02-25T10:00:00-05:00</published>
      <content type="html">&lt;p&gt;I joined Collective Idea six months ago. In that time, I&amp;#8217;ve had the chance to throw myself into a new language. Just like learning a new spoken language, learning a new programming language means learning a new way of thinking. Here are some of my reflections on becoming a Rubyist.&lt;/p&gt;
&lt;h3&gt;Coding is Human&lt;/h3&gt;
&lt;p&gt;My initial attraction to Ruby was the community&amp;#8217;s emphasis on clear, human-readable code. You can only write so many nested foreach loops in &lt;span class="caps"&gt;PHP&lt;/span&gt; before you begin to look for a more succinct and rational language. When I began exploring Ruby, I was struck by Matz&amp;#8217;s &lt;a href="http://www.artima.com/intv/rubyP.html"&gt;human-centered philsophy&lt;/a&gt; of increasing progammer happiness, and of the &lt;a href="http://en.wikipedia.org/wiki/Principle_of_least_astonishment"&gt;principle of least surprise&lt;/a&gt;. I was also itching to program in an Object-Oriented language. (Though &lt;span class="caps"&gt;PHP&lt;/span&gt; does have objects, the &lt;a href="http://drupal.org"&gt;framework I worked in previously&lt;/a&gt; is almost exclusively procedural.) Even now, when I write an each loop in Ruby, I can&amp;#8217;t help enjoying how nice it looks.&lt;/p&gt;
&lt;p&gt;Reading Russ Olsen&amp;#8217;s excellect &lt;a href="http://www.amazon.com/Eloquent-Ruby-Addison-Wesley-Professional/dp/0321584104"&gt;Eloquent Ruby&lt;/a&gt;, I realized that programming could be an aesthetic exprience as well as a logical one. Steve Klabnik has spoken on these themes, putting programming in relation to &lt;a href="http://vimeo.com/31204088"&gt;literature&lt;/a&gt; and &lt;a href="http://vimeo.com/50733183"&gt;philsophy&lt;/a&gt;. It&amp;#8217;s very typical when talking to other Rubyists to hear code described as &amp;#8216;beautiful&amp;#8217; or &amp;#8216;clever&amp;#8217;. For me, coming to understand programming in these terms connects it with other human pursuits. After all, a community that produced &lt;a href="http://en.wikipedia.org/wiki/Why_the_lucky_stiff"&gt;_why&lt;/a&gt; must understand art as well as engineering.&lt;/p&gt;
&lt;h3&gt;Patterns are (educated) Opinions&lt;/h3&gt;
&lt;p&gt;I learned quickly after joining Collective Idea that there is rarely one right answer to a programming problem. It&amp;#8217;s been revealing to hear the differing opinions in the office over everything from Ruby conventions to Rails patterns. Simply listening to these conversations has made me a better programmer. It&amp;#8217;s also made me want to take the time to develop my own well-supported opinions.&lt;/p&gt;
&lt;p&gt;The freedom that comes from not having one answer (and a language that lets you open any class!) is incredibly empowering, but it can also be paralyzing. When I encounter decisions that have architectural implications, I&amp;#8217;m still uncertain on how best to move forward. I&amp;#8217;m afraid of making a decision that I regret later. I don&amp;#8217;t yet have the benefit of experience to help me weigh the pros and cons of any one approach.&lt;/p&gt;
&lt;p&gt;I encountered my first significant technical debt a few months after starting. I was working on an app that  functions primarily as a glue between several different services. On certain actions, it makes calls to at least 3 separate services, having to coordinate and process the responses from each. My controller had gotten out of hand, and I had mixed up some business logic in with my models. My tests were straining, edge cases were breaking the app, and I knew I was Doing It Wrong™.&lt;/p&gt;
&lt;p&gt;Discussing with colleagues how best to move forward, it was suggested I try a pattern one of them had &lt;a href="http://collectiveidea.com/blog/archives/2012/06/28/wheres-your-business-logic/"&gt;had recently written about&lt;/a&gt;. Called the Interactor pattern, it seperated all my interactions with outside services into a seperate class. It was beautiful, it just &lt;em&gt;felt&lt;/em&gt; better, and my tests agreed.&lt;/p&gt;
&lt;h3&gt;Testing is Craft&lt;/h3&gt;
&lt;p&gt;You can&amp;#8217;t spend much time working in Ruby without hearing about the importance of testing. At first, I had a typical reaction, wondering how writing more code would make me more productive. The more I did, though, the more I realized how good tests lead you to write better code. My first tests, of course, weren&amp;#8217;t very good. But they got better. And after watching Geoffrey Grosenbach &lt;a href="https://peepcode.com/products/rspec-i"&gt;excellent series on RSpec&lt;/a&gt; (he&amp;#8217;s recently updated it for RSpec 2), I began to fully grasp isolation and why small methods are awesome.&lt;/p&gt;
&lt;p&gt;I also began to understand what the industry buzzword around &amp;#8216;craftsmanship&amp;#8217; really means. I&amp;#8217;ve worked in software long enough to know firsthand how easy it is for projects to underperform and go over budget. Building software is unlike building almost anything else. It&amp;#8217;s essentially collaborative logic-making. Because its product is incredibly malliable, it needs clear and specific guidelines. Becoming a &amp;#8216;software craftsperson&amp;#8217; is having the humility to realize that one person cannot fully conceptualize the complexity of the entire software stack. We write tests because we know we&amp;#8217;ll make mistakes, because we&amp;#8217;ll have to maintain what we&amp;#8217;ve built, and because we don&amp;#8217;t know who might have to work with our code in the future. This may be old news to &lt;a href="http://en.wikipedia.org/wiki/Test-driven_development"&gt;&lt;span class="caps"&gt;TDD&lt;/span&gt;&lt;/a&gt; diehards, but that doesn&amp;#8217;t make the idea any less powerful.&lt;/p&gt;
&lt;p&gt;In short, becoming a Rubyist has been one of the most rewarding processes in my career. It&amp;#8217;s made me appreciate what I do on an aesthetic level, it&amp;#8217;s brought me a creative freedom I didn&amp;#8217;t expect from programming, and it&amp;#8217;s given me the tools to be proud of what I build.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/collectiveidea/~4/CfkgPt3xaas" height="1" width="1"/&gt;</content>
      <author>
        <name>Jonathan Pichot</name>
      </author>
    <feedburner:origLink>http://collectiveidea.com/blog/archives/2013/02/25/becoming-a-rubyist/</feedburner:origLink></entry>
  
    <entry>
      <title>Chrome Profile Settings and Capybara 1.1 Stable</title>
      <link href="http://feedproxy.google.com/~r/collectiveidea/~3/uaIgD1kiGrk/" />
      <id>5102ac023041327d5f0001cc</id>
      <updated>2013-01-25T11:51:50-05:00</updated>
      <published>2013-01-25T11:00:00-05:00</published>
      <content type="html">&lt;p&gt;We previously blogged about &lt;a href="http://collectiveidea.com/blog/archives/2012/01/27/testing-file-downloads-with-capybara-and-chromedriver/"&gt;how to test file downloads with capybara and chromedriver&lt;/a&gt; that explained how to create a profile that specified where to send downloaded files.&lt;/p&gt;
&lt;p&gt;We had take a similar approach with a recent project, except instead of declaring a different download directory we were setting the window placement to alter Chrome&amp;#8217;s size. This seems to have broken with a recent version of Chrome.&lt;/p&gt;
&lt;p&gt;Instead of relying on settings like this to adjust Chrome&amp;#8217;s window size:&lt;/p&gt;
&lt;pre class="ruby"&gt;profile['browser.window_placement.top'] = 0
profile['browser.window_placement.left'] = 0
profile['browser.window_placement.right'] = 10000
profile['browser.window_placement.bottom'] = 10000
&lt;/pre&gt;
&lt;p&gt;Set the &lt;code&gt;--window-size=width,height&lt;/code&gt; command-line switch by including the &lt;code&gt;args&lt;/code&gt; hash option:&lt;/p&gt;
&lt;pre class="ruby"&gt;Capybara::Selenium::Driver.new(app,
  :browser =&amp;gt; :chrome,
  :profile =&amp;gt; profile,
  :args =&amp;gt; ["--window-size=10000,10000"]
)
&lt;/pre&gt;&lt;img src="http://feeds.feedburner.com/~r/collectiveidea/~4/uaIgD1kiGrk" height="1" width="1"/&gt;</content>
      <author>
        <name>Brian Ryckbost</name>
      </author>
    <feedburner:origLink>http://collectiveidea.com/blog/archives/2013/01/25/chrome-profile-settings-and-capybara-11-stable/</feedburner:origLink></entry>
  
    <entry>
      <title>Long Lost Allocation</title>
      <link href="http://feedproxy.google.com/~r/collectiveidea/~3/ocx6Dk17kTc/" />
      <id>510026458ad7ca78410005cf</id>
      <updated>2013-01-23T19:57:02-05:00</updated>
      <published>2013-01-23T14:00:00-05:00</published>
      <content type="html">&lt;p&gt;There&amp;#8217;s a great little method in Ruby that you might not be familiar with. It&amp;#8217;s used all the time but it&amp;#8217;s hardly ever called directly. The method is &lt;code&gt;Class#allocate&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Even if you&amp;#8217;re not familiar with &lt;code&gt;Class#allocate&lt;/code&gt;, I&amp;#8217;m sure you&amp;#8217;ve seen &lt;code&gt;Class#new&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;me = Person.new("Steve")
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you&amp;#8217;ve ever been confused about how calling &lt;code&gt;Person.new&lt;/code&gt; ends up calling &lt;code&gt;Person#initialize&lt;/code&gt;, here&amp;#8217;s what&amp;#8217;s really happening behind the scenes:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;class Class
  def new(*args, &amp;amp;block)
    instance = allocate
    instance.initialize(*args, &amp;amp;block)
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;allocate&lt;/code&gt; method just builds a new instance of the class, completely skipping the call to &lt;code&gt;initialize&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;So what?&lt;/h2&gt;
&lt;p&gt;While you probably shouldn&amp;#8217;t call &lt;code&gt;allocate&lt;/code&gt; directly in your production code, I think it can be incredibly useful in testing. You &lt;em&gt;are&lt;/em&gt; unit testing… right?&lt;/p&gt;
&lt;p&gt;Consider the following class:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;class Animal
  def initialize(phylum, class_, order, family, genus, species)
    @phylum  = phylum
    @class   = class_
    @order   = order
    @family  = family
    @genus   = genus
    @species = species
  end

  def name
    "#{@genus} #{@species}"
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To test the &lt;code&gt;Animal#name&lt;/code&gt; method, I need an instance of &lt;code&gt;Animal&lt;/code&gt;. But for this method, I only care about the animal&amp;#8217;s genus and species.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;describe Animal do
  subject(:animal) { Animal.allocate }

  describe "#name" do
    it "combines the genus and species" do      
      animal.instance_variable_set(:@genus, "Homo")
      animal.instance_variable_set(:@species, "sapiens")

      expect(animal.name).to eql("Homo sapiens")
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rather than initializing a full animal instance to test only two of its internal values, I simply set those values directly.&lt;/p&gt;
&lt;p&gt;For me, using &lt;code&gt;allocate&lt;/code&gt; has been a great way to keep my unit tests short, clear and isolated. It&amp;#8217;s especially useful when a class&amp;#8217;s &lt;code&gt;initialize&lt;/code&gt; method requires many arguments and/or preprocesses those arguments.&lt;/p&gt;
&lt;h2&gt;What else?&lt;/h2&gt;
&lt;p&gt;I find that developers often don&amp;#8217;t test their own &lt;code&gt;initialize&lt;/code&gt; methods. And if they do, it might look like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;describe Animal do
  describe "#initialize" do
    it "sets the phylum, class, order, family, genus and species" do
      animal = Animal.new("Chordata", "Mammalia", "Primates", "Hominidae", "Homo", "sapiens")

      expect(animal.instance_varaible_get(:@phylum)).to eql("Chordata")
      # …
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What stands out to me is that while we&amp;#8217;re testing &lt;code&gt;initialize&lt;/code&gt;, we never call it directly. Using &lt;code&gt;allocate&lt;/code&gt; I can test &lt;code&gt;initialize&lt;/code&gt; like I would any other private method:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;describe Animal do
  subject(:animal) { Animal.allocate }

  describe "#initialize" do
    it "sets the phylum, class, order, family, genus and species" do
      animal.send(:initialize, "Chordata", "Mammalia", "Primates", "Hominidae", "Homo", "sapiens")

      expect(animal.instance_varaible_get(:@phylum)).to eql("Chordata")
      # …
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Allocating in RSpec&lt;/h2&gt;
&lt;p&gt;Currently, calling &lt;code&gt;subject&lt;/code&gt; within an RSpec unit test will give you the result of calling &lt;code&gt;new&lt;/code&gt; on the class you&amp;#8217;re describing. I&amp;#8217;d love to see this default change to use the &lt;code&gt;allocate&lt;/code&gt; method so that unit tests can be properly isolated.&lt;/p&gt;
&lt;p&gt;In addition, calling &lt;code&gt;allocate&lt;/code&gt; will always work, while calling &lt;code&gt;new&lt;/code&gt; only works if your class&amp;#8217;s &lt;code&gt;initialize&lt;/code&gt; method accepts no arguments, which I don&amp;#8217;t find to be the case very often.&lt;/p&gt;
&lt;p&gt;While my hopes aren&amp;#8217;t high that the default will change, at the least I&amp;#8217;d like the ability to configure RSpec to use &lt;code&gt;allocate&lt;/code&gt; rather than &lt;code&gt;new&lt;/code&gt; when calling &lt;code&gt;subject&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;What do &lt;em&gt;you&lt;/em&gt; think?&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/collectiveidea/~4/ocx6Dk17kTc" height="1" width="1"/&gt;</content>
      <author>
        <name>Steve Richert</name>
      </author>
    <feedburner:origLink>http://collectiveidea.com/blog/archives/2013/01/23/long-lost-allocation/</feedburner:origLink></entry>
  
    <entry>
      <title>Reverse Search with Elasticsearch</title>
      <link href="http://feedproxy.google.com/~r/collectiveidea/~3/TWndXAlvX68/" />
      <id>50edbf9c8ad7ca3d7000126e</id>
      <updated>2013-01-14T14:08:28-05:00</updated>
      <published>2013-01-14T14:00:00-05:00</published>
      <content type="html">&lt;p&gt;I am working on a Rails application that allows construction companies to manage a portfolio of projects (&lt;a href="http://www.ascribehq.com"&gt;AscribeHQ.com&lt;/a&gt;). It also has the ability for other users to create a &amp;#8220;Group Portfolio&amp;#8221; to display projects that were uploaded by other users, based on a potentially complicated criteria. (ex. &amp;#8216;City of Chicago Group Portfolio&amp;#8217; wants to show construction projects with budgets over $250k and are located within the city limits)&lt;/p&gt;
&lt;h2&gt;The problem&lt;/h2&gt;
&lt;p&gt;A Group Portfolio searches the existing projects to show the user which projects they can display, which we are using accomplishing with Elasticsearch. The real challenge is, we want the company user who uploads a new project to find all the Group Portfolios that matches their new project. Essentially we need a &amp;#8220;Reverse Search&amp;#8221;.&lt;/p&gt;
&lt;h2&gt;The solution&lt;/h2&gt;
&lt;p&gt;Elasticsearch has an amazing feature called Percolation, which allows us to save the complicated searches of the Group Portfolio into an index, then when a new project is added, we can &amp;#8216;search the searches&amp;#8217; to return the Group Portfolio ID.  Even better, there is a ruby gem called Tire that supports this percolation feature.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ll not go into all the set up of Elasticsearch itself as there are many great blog post on that part already, but this is how we set up the Percolation using the Tire gem:&lt;/p&gt;
&lt;h2&gt;UserProject model:&lt;/h2&gt;
&lt;pre class="ruby"&gt;
class UserProject &amp;lt; ActiveRecord::Base
  include Tire::Model::Search
  include Tire::Model::Callbacks

  # Magic method that returns all the search that match the new project
  def find_matching_groups
    Portfolio::Group.find(UserProject.index.percolate(self))
  end

  # The normal mapping that is required to set up ElasticSearch
  mapping do
    indexes :title,            :as =&amp;gt; 'title', :boost =&amp;gt; 2
    indexes :custom_title,     :as =&amp;gt; 'custom_title', :boost =&amp;gt; 2
    indexes :owner,            :as =&amp;gt; 'owner.company_name', :boost =&amp;gt; 2
    indexes :subtitle,         :as =&amp;gt; 'subtitle'
    indexes :street,           :as =&amp;gt; 'street'
    indexes :city,             :as =&amp;gt; 'city'
    indexes :state,            :as =&amp;gt; 'state'
    indexes :zip,              :as =&amp;gt; 'zip'
  end

end
&lt;/pre&gt;
&lt;h2&gt;Group Portfolio model:&lt;/h2&gt;
&lt;pre class="ruby"&gt;
class Portfolio::Group &amp;lt; Portfolio
  include Tire::Model::Search
  include Tire::Model::Callbacks

  after_save do |group_portfolio|
    if group_portfolio.project_criteria.present?
      group_portfolio.save_query
    end
  end

  # Adds and updates query in ElasticSearch database 
  def save_query
    UserProject.index.register_percolator_query(self.id) do |q|
      params = {}
      
      # project_criteria is saved on the Group Portfolio object.  ex: [{"filter_type": "state", "states": ["MI"]}, {"filter_type": "proj_type", "types": ["28", "29"]}]
      self.project_criteria.from_json.each do |criteria|
        params = params.merge(criteria)
      end

      q.filtered do
        query do
          boolean do
            must { terms :phase_id, params['phases']} if params['phases']
            must { terms :project_type_id, params['types']} if params['types']
            must { terms :green_id, params['greens']} if params['greens']
            must { terms :delivery_method_id, params['delivery_methods']} if params['delivery_methods']
            must { terms :project_definition_id, params['project_definitions']} if params['project_definitions']
            must { terms :state, params['states'].map(&amp;amp;:downcase) } if params['states']
            must { terms :city, params['cities']} if params['cities']
            must { terms :zip, params['zips']} if params['zips']
          end
        end
      end
    end
  end

end
&lt;/pre&gt;
&lt;h2&gt;Action returning Group Portfolios:&lt;/h2&gt;
&lt;pre class="ruby"&gt;
class Manage::PublishingsController &amp;lt; ApplicationController

  def index
    @project = Project.find(params[:project_id])
    @user_project = UserProject.where(:portfolio_id =&amp;gt; current_portfolio.id, :project_id =&amp;gt; @project.id).first
    @group_portfolios = Portfolio::Group.where(:id =&amp;gt; @user_project.find_matching_groups)
  end

end
&lt;/pre&gt;
&lt;h2&gt;Results&lt;/h2&gt;
&lt;p&gt;We were previously trying to accomplish this same type of results with Delayed Job and some very complicated code. It often took around 5 minutes to do this &amp;#8216;reverse search&amp;#8217;. Now the user sees the results in a half of a second and with simpler code. This is a big win for us and will help us offer better service to our customers.&lt;/p&gt;
&lt;h2&gt;Applications&lt;/h2&gt;
&lt;p&gt;There seems to be endless applications for this.  On dating sites, a new user can be told how many (and even who) searched for them before they signed up.  Auto dealerships could easily see if the car they are could buy matches any recent searches for vehicles on their site.  An advertising site could estimate how many views an ad will get based on previous queries.  All of these things could be done in other ways, but the code will likely be very complicated and slow.&lt;/p&gt;
&lt;p&gt;references: &lt;a href="http://www.elasticsearch.org/guide/reference/api/percolate.html"&gt;Percolation&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/collectiveidea/~4/TWndXAlvX68" height="1" width="1"/&gt;</content>
      <author>
        <name>Jason Carpenter</name>
      </author>
    <feedburner:origLink>http://collectiveidea.com/blog/archives/2013/01/14/reverse-search-with-elasticsearch-1/</feedburner:origLink></entry>
  
    <entry>
      <title>Running HAProxy behind Amazon's ELB</title>
      <link href="http://feedproxy.google.com/~r/collectiveidea/~3/50L42zr3BBs/" />
      <id>50f029648ad7ca64cd012799</id>
      <updated>2013-01-11T15:34:09-05:00</updated>
      <published>2013-01-11T14:05:00-05:00</published>
      <content type="html">&lt;p&gt;Amazon&amp;#8217;s &lt;a href="http://docs.aws.amazon.com/ElasticLoadBalancing/latest/APIReference/Welcome.html"&gt;Elastic Load Balancer&lt;/a&gt; (&lt;span class="caps"&gt;ELB&lt;/span&gt;) is a great tool for serving traffic across availability zones in an &lt;span class="caps"&gt;AWS&lt;/span&gt; region. It can do &lt;span class="caps"&gt;SSL&lt;/span&gt; termination, can handle any amount of traffic (in a ramp-up-over-time format), and your only variable cost is the bandwidth in question. However, it is a complete black box with no ability to shape or manipulate traffic for your application&amp;#8217;s needs (say, to send DDoS traffic into oblivion instead of blasting your web servers). For this reason it can make sense to run your own load balancer, such as &lt;a href="http://haproxy.1wt.eu/"&gt;HAProxy&lt;/a&gt;, right behind &lt;span class="caps"&gt;ELB&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;In general, configuring HAProxy to sit behind &lt;span class="caps"&gt;ELB&lt;/span&gt; is no different than any other hosting setup but there are two settings that can improve operational efficiency and prevent some common problems.&lt;/p&gt;
&lt;h2&gt;Monitoring&lt;/h2&gt;
&lt;p&gt;We are big fans of &lt;a href="https://www.phusionpassenger.com"&gt;Passenger&lt;/a&gt; for serving our Rails applications but until the release of Passenger Enterprise there was always a few second delay on deploys where Passenger shut down existing processes before starting up the new code, leading to queued requests waiting for the new processes to boot. When we had ELB&amp;#8217;s Health Check hitting a direct application &lt;span class="caps"&gt;URI&lt;/span&gt;, this pseudo downtime was registering as full downtime in &lt;span class="caps"&gt;ELB&lt;/span&gt;, removing the servers from the rotation, leading to actual downtime or reduced capacity until &lt;span class="caps"&gt;ELB&lt;/span&gt; picked up a valid request again.&lt;/p&gt;
&lt;p&gt;To work around this, we moved the monitoring &lt;span class="caps"&gt;URI&lt;/span&gt; to HAProxy itself using the &lt;a href="http://code.google.com/p/haproxy-docs/wiki/monitor_uri"&gt;monitor-uri&lt;/a&gt; option, and updated &lt;span class="caps"&gt;ELB&lt;/span&gt; to Health Check against this &lt;span class="caps"&gt;URI&lt;/span&gt;. To ensure that we aren&amp;#8217;t ever serving requests to a HAProxy server with no valid backends in a given availability zone (unless &lt;strong&gt;everything&lt;/strong&gt; is down), we configure every HAProxy server to talk to every Web server in our EC2 account, but we increase the weight of servers in different availability zones. For example, for a HAProxy box in us-east-1a we have the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
backend 
  server ec2-us-east-1a.server:80 weight 25
  server ec2-us-east-1c.server:80 weight 100
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this every level of the application in question has redundancy. From availability zones to &lt;span class="caps"&gt;ELB&lt;/span&gt; to HAProxy to our app servers, every layer can survive a failure without taking the application down.&lt;/p&gt;
&lt;h2&gt;Timeouts&lt;/h2&gt;
&lt;p&gt;In this setup, &lt;span class="caps"&gt;ELB&lt;/span&gt; is always going to be your &amp;#8220;client&amp;#8221; to HAProxy and if your &lt;a href="http://code.google.com/p/haproxy-docs/wiki/timeout_client"&gt;timeout client&lt;/a&gt; value is too small you are going to see a massive amount of &lt;code&gt;&amp;lt;BADREQ&amp;gt;&lt;/code&gt; &lt;code&gt;&amp;lt;NOSRV&amp;gt;&lt;/code&gt; errors flooding your HAProxy logs. It turns out that the monitoring side of &lt;span class="caps"&gt;ELB&lt;/span&gt; likes to open up a couple of sockets to the backend servers without sending any data down those sockets. These sockets have a timeout of 60s and reconnect immediately upon closing. We previously had a four second timeout on these connections. This resulted in logs getting spammed with the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
http http/&amp;lt;NOSRV&amp;gt; -1/-1/-1/-1/4000 408 212 - - cR-- 17/17/0/0/0 0/0 {} "&amp;lt;BADREQ&amp;gt;"
http http/&amp;lt;NOSRV&amp;gt; -1/-1/-1/-1/4001 408 212 - - cR-- 16/16/0/0/0 0/0 {} "&amp;lt;BADREQ&amp;gt;"
http http/&amp;lt;NOSRV&amp;gt; -1/-1/-1/-1/4001 408 212 - - cR-- 17/17/0/0/0 0/0 {} "&amp;lt;BADREQ&amp;gt;"
http http/&amp;lt;NOSRV&amp;gt; -1/-1/-1/-1/4049 408 212 - - cR-- 16/16/0/0/0 0/0 {} "&amp;lt;BADREQ&amp;gt;"
http http/&amp;lt;NOSRV&amp;gt; -1/-1/-1/-1/4001 408 212 - - cR-- 17/17/0/0/0 0/0 {} "&amp;lt;BADREQ&amp;gt;"
http http/&amp;lt;NOSRV&amp;gt; -1/-1/-1/-1/4001 408 212 - - cR-- 17/17/0/0/0 0/0 {} "&amp;lt;BADREQ&amp;gt;"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;-1/-1/-1/-1/4001 408&lt;/code&gt; is saying that the request timed out at 4001ms, and haproxy returned a 408 Request Timeout. The &lt;code&gt;cR--&lt;/code&gt; section tells us that HAProxy force closed the connection itself (&lt;code&gt;c&lt;/code&gt;) while waiting for headers that never came (&lt;code&gt;R&lt;/code&gt;). You can read more about the log format at the &lt;a href="http://code.google.com/p/haproxy-docs/wiki/Logging"&gt;official documentation&amp;#8217;s Logging page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After changing the timeout to 60 seconds, we saw the following drop in overall log volume (courtesy of &lt;a href="http://loggly.com/"&gt;Loggly&lt;/a&gt;):&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50f02b297a507223cb00ac61/log_events_big.png"&gt;&lt;img src="/assets/50f029388ad7ca64cd012763/fit_in_blog_post/log_events_annotated.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;These few changes have given us a very reliable server setup and is working to keep all logging as relevant as possible, and with HAProxy in the mix we have even more control over application scalability on top of what EC2 already provides.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/collectiveidea/~4/50L42zr3BBs" height="1" width="1"/&gt;</content>
      <author>
        <name>Jason Roelofs</name>
      </author>
    <feedburner:origLink>http://collectiveidea.com/blog/archives/2013/01/11/running-haproxy-behind-amazons-elb/</feedburner:origLink></entry>
  
    <entry>
      <title>Inter-Service Authentication with SSL</title>
      <link href="http://feedproxy.google.com/~r/collectiveidea/~3/XowQnVRFW80/" />
      <id>50ccbba0dabe9d34430127a5</id>
      <updated>2012-12-17T15:56:56-05:00</updated>
      <published>2012-12-17T09:00:00-05:00</published>
      <content type="html">&lt;p&gt;At Collective Idea, we love building web services. Oftentimes we also build the client applications that consume those services.&lt;/p&gt;
&lt;p&gt;One of the major challenges with a service-oriented architecture is authenticating communication between the client and the service as well as between services.&lt;/p&gt;
&lt;h2&gt;Oh, You Mean OAuth!&lt;/h2&gt;
&lt;p&gt;&lt;a href="http://oauth.net"&gt;OAuth&lt;/a&gt; is a good candidate for this type of authentication. It provides flexibility in terms of scoped authorization levels, securely signed requests, token expiration and all sorts of other goodies. And these things are great, especially for public-facing applications, where anybody could register through your OAuth provider.&lt;/p&gt;
&lt;p&gt;But if you&amp;#8217;re only dealing with a small number of controlled, trusted clients, implementing an OAuth provider for each service might be a bit much.&lt;/p&gt;
&lt;h2&gt;Then What?&lt;/h2&gt;
&lt;p&gt;Every request to the services needs to identify its client application and more importantly, it needs to be verified. What we need is a tried and tested way to communicate &lt;strong&gt;trust&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Enter: &lt;a href="http://en.wikipedia.org/wiki/Secure_Sockets_Layer"&gt;&lt;span class="caps"&gt;SSL&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We usually see &lt;span class="caps"&gt;SSL&lt;/span&gt; in the form of an &amp;#8220;https://&amp;#8221; &lt;span class="caps"&gt;URL&lt;/span&gt; in our browser, and a little lock icon to make us feel all warm and fuzzy. That feeling of security comes from the fact that our browser has identified the website and we can trust that they actually &lt;em&gt;are&lt;/em&gt; who they say they are.&lt;/p&gt;
&lt;p&gt;This sounds awfully close to what we&amp;#8217;re looking for. If an &lt;span class="caps"&gt;SSL&lt;/span&gt; certificate can verify the &lt;em&gt;recipient&lt;/em&gt; of a request, we should be able to reverse the process to verify the sender of a request.&lt;/p&gt;
&lt;h2&gt;&lt;span class="caps"&gt;SSL&lt;/span&gt; Authentication&lt;/h2&gt;
&lt;p&gt;To set up our &lt;span class="caps"&gt;SSL&lt;/span&gt; authentication, each client application needs its own &lt;span class="caps"&gt;SSL&lt;/span&gt; certificate. So first thing&amp;#8217;s first: we need a private key with which to sign all of these &lt;span class="caps"&gt;SSL&lt;/span&gt; certificates… one key to rule them all. Signing all of the client certificates with the same private key will allow us to verify their authenticity later.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;openssl genrsa -out master.key 1024
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that we have our master key, we can start generating &lt;span class="caps"&gt;SSL&lt;/span&gt; certificates. Each certificate will require a new &lt;span class="caps"&gt;CSR&lt;/span&gt; (certificate signing request). &lt;span class="caps"&gt;CSR&lt;/span&gt; generation will prompt for information about the certificate and we&amp;#8217;ll take that opportunity to identify the client application that this certificate belongs to.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# Enter "web-client" for "Organizational Unit Name."
# The remaining attributes aren't important.
openssl req -new -key master.key -out web-client.csr
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the &lt;span class="caps"&gt;CSR&lt;/span&gt; in hand, generating a signed certificate is easy.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;openssl x509 -req -in web-client.csr -signkey master.key -out web-client.crt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This certificate is given to the client application and every request originating from the client will include the contents of that certificate in a custom header.&lt;/p&gt;
&lt;p&gt;Meanwhile, in the service… when a request is received we&amp;#8217;ll need to assert that the certificate provided is authentic. We do this by verifying that the certificate is signed by the master key. The best part is that the service doesn&amp;#8217;t even need the private, master key to do that verification. It can use a public key derived from the master key instead.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;openssl rsa -in master.key -pubout &amp;gt; master.pub
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every service gets this same public, master key and uses it to verify all incoming requests.&lt;/p&gt;
&lt;h2&gt;Finally, Ruby&lt;/h2&gt;
&lt;p&gt;Assuming your service is written in Rails, here&amp;#8217;s how that verification might be implemented:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;class ApplicationController &amp;lt; ActionController::Base
  before_filter :require_authentication

  private

  def require_authentication
    unless current_certificate.verify(public_key)
      head :forbidden
    end
  end

  def public_key
    @public_key ||= OpenSSL::PKey::RSA.new(ENV['AUTH_PUBLIC_KEY'])
  end

  def current_certificate
    @current_certificate ||= OpenSSL::X509::Certificate.new(request.headers['X-SSL-Auth'])
  end

  def current_client
    current_certificate.issuer.to_a.assoc('OU')[1]
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The public key used for verification is kept in the server&amp;#8217;s environment at &lt;code&gt;ENV['AUTH_PUBLIC_KEY']&lt;/code&gt; and the current request&amp;#8217;s certificate is pulled from the headers.&lt;/p&gt;
&lt;p&gt;The certificate responds to the &lt;code&gt;verify&lt;/code&gt; method which accepts the public key and simply returns &lt;code&gt;true&lt;/code&gt; if the certificate is authentic or &lt;code&gt;false&lt;/code&gt; otherwise.&lt;/p&gt;
&lt;p&gt;You&amp;#8217;ll notice the &lt;code&gt;current_client&lt;/code&gt; method as well, which extracts the organizational unit from the certificate that we set during the &lt;span class="caps"&gt;CSR&lt;/span&gt; generation. This identifies the client application making the request. With that information, the service can implement any sort of access controls that may (or may not) be required.&lt;/p&gt;
&lt;h2&gt;In the Wild&lt;/h2&gt;
&lt;p&gt;At Collective Idea, we think &lt;span class="caps"&gt;SSL&lt;/span&gt; certificate authentication is a cool idea and in some cases, a great fit. It&amp;#8217;s also comforting to know that &lt;a href="https://squareup.com"&gt;Square&lt;/a&gt; uses this same approach for authentication between its own services.&lt;/p&gt;
&lt;p&gt;In fact, this post was inspired by a &lt;a href="http://www.confreaks.com/videos/1273-rubyconf2012-service-oriented-architecture-at-square"&gt;talk&lt;/a&gt; by Square&amp;#8217;s Chris Hunt at RubyConf 2012. His talk omits some of the details of certificate generation and verification but it&amp;#8217;s a fantastic resource for good service-oriented architecture.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m personally new to &lt;span class="caps"&gt;SSL&lt;/span&gt;-based security so please leave a comment for how this might be improved or expanded upon. Thank you!&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/collectiveidea/~4/XowQnVRFW80" height="1" width="1"/&gt;</content>
      <author>
        <name>Steve Richert</name>
      </author>
    <feedburner:origLink>http://collectiveidea.com/blog/archives/2012/12/17/inter-service-authentication-with-ssl/</feedburner:origLink></entry>
  
    <entry>
      <title>So We Bought a Printer</title>
      <link href="http://feedproxy.google.com/~r/collectiveidea/~3/NDY32sHtDqw/" />
      <id>50c8012ddabe9d4fc000282d</id>
      <updated>2012-12-12T11:44:54-05:00</updated>
      <published>2012-12-11T23:00:00-05:00</published>
      <content type="html">&lt;p&gt;We needed a printer for our new office.&lt;/p&gt;
&lt;p&gt;Well, we didn&amp;#8217;t really &lt;strong&gt;need&lt;/strong&gt; one. We don&amp;#8217;t print all that much these days, not even boarding passes anymore. Really, I wanted a scanner so we could &lt;strong&gt;reduce&lt;/strong&gt; our paper floating around. I wanted one with a document feeder so I could scan multiple pages at once. If it came attached to a printer for those few times we wanted it, even better.&lt;/p&gt;
&lt;h2&gt;Shopping for printers is hard.&lt;/h2&gt;
&lt;p&gt;Really hard. Especially since I don&amp;#8217;t care about &lt;abbr title="Dots Per Inch"&gt;&lt;span class="caps"&gt;DPI&lt;/span&gt;&lt;/abbr&gt;, &lt;abbr title="Pages Per Minute"&gt;&lt;span class="caps"&gt;PPM&lt;/span&gt;&lt;/abbr&gt;, or any other marketing &lt;abbr title="Bullshit"&gt;BS&lt;/abbr&gt;. Also, I don&amp;#8217;t care about the software. I want it to work with my Mac. Work, not make toast, just work.&lt;/p&gt;
&lt;p&gt;Maybe I&amp;#8217;m weird, but I haven&amp;#8217;t cared at all about printers in years. At our last office, we shared a Dell laser that worked well enough, and I didn&amp;#8217;t have to worry about it.&lt;/p&gt;
&lt;h2&gt;Research&lt;/h2&gt;
&lt;p&gt;Dear god there are a lot of printers on the market. Ok, I limited myself to wireless, AirPrint-compatible models with a document feeder. Still way too many. Ok. Pick a few at random and start reading reviews&lt;sup class="footnote" id="fnr1"&gt;&lt;a href="#fn1"&gt;1&lt;/a&gt;&lt;/sup&gt;. Not very helpful. Anything under $150 seems to be flimsy, and no good idea about how they really work on WiFi with Macs.&lt;/p&gt;
&lt;h2&gt;Software&lt;/h2&gt;
&lt;p&gt;Finally deep in the HP excuse for a website, I found what I was looking for: No need to install the software on a Mac, just connect and Apple&amp;#8217;s Software Update will do the rest. As I said, my big thing was scanning. No idea how this &lt;strong&gt;actually&lt;/strong&gt; works. I&amp;#8217;ve had bad experiences in the past&lt;sup class="footnote" id="fnr2"&gt;&lt;a href="#fn2"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;I was happy to discover that Preview.app can scan direct! As long as you&amp;#8217;re on the network, you get a &amp;#8220;Import from&amp;#8230;&amp;#8221; menu item. Ok time to go for it.&lt;/p&gt;
&lt;figure&gt;
&lt;img src="/assets/50c8b050dabe9d2b9900047d/Screen_Shot_2012_12_12_at_11_26_30_AM.png" alt="Preview's file menu"&gt;
  &lt;figcaption&gt;Preview&amp;#8217;s file menu&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;a href="/assets/50c8b3efdabe9d2b1b00878e/Screen_Shot_2012_12_12_at_11_41_40_AM.png"&gt;&lt;img src="/assets/50c8b3efdabe9d2b1b00878e/fit_in_blog_post/Screen_Shot_2012_12_12_at_11_41_40_AM.png" alt="Preview ready to scan my multipage document"&gt;&lt;/a&gt;
  &lt;figcaption&gt;Preview ready to scan my multipage document&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2&gt;Buying&lt;/h2&gt;
&lt;p&gt;I was sick of research, so time for impulse buy. HP had the best feature + review combos, so I was all geared up to get a &lt;a href="http://www.staples.com/HP-Photosmart-7520-e-All-in-One-Printer/product_951136"&gt;HP Photosmart 7520&lt;/a&gt; but the guys convinced my that laser would work better with our low print volume. Time to drive to the store so I could get it done.&lt;/p&gt;
&lt;p&gt;After hemming and hawing over the price and size, I gave up and grabbed the &lt;a href="http://www.staples.com/HP-Laserjet-Pro-200-M276nw-Color-All-in-One-Printer/product_949797"&gt;HP Laserjet Pro 200 M276nw&lt;/a&gt;. Color laser, wireless, scanner tray, all the goodies. Also the thing is freaking huge. The wireless module must be the size of a cinder block, but whatever.&lt;/p&gt;
&lt;p&gt;Setup was easy, and it felt great to toss the 3 CDs straight in the trash&lt;sup class="footnote" id="fnr3"&gt;&lt;a href="#fn3"&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h2&gt;Does it work?&lt;/h2&gt;
&lt;p&gt;&lt;img src="/assets/50c8afd4dabe9d21dd0027cc/fit_in_blog_post/photo_1_1.jpg" alt="our test page"&gt;&lt;/p&gt;
&lt;p&gt;Like a champ. &lt;a href="https://twitter.com/slack"&gt;Slack&lt;/a&gt; printed up a &amp;#8220;Test Page&amp;#8221; as soon as I got the wireless setup, and then I tried scanning a 3 page document. Added the printer in System Preferences, no issues, and then went to Preview and it Just Worked. No hassels, it got all the pages and I was happy.&lt;/p&gt;
&lt;p&gt;So now we have a printer. Hope this helps someone out there decide. Either way, I&amp;#8217;ll be able to print up some passive aggressive signs for the new office and scan the few papers I still have around.&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/50c8af75dabe9d1d2a007cc8/fit_in_blog_post/photo_2.JPG" alt="a giant printer"&gt;&lt;/p&gt;
&lt;p class="footnote" id="fn1"&gt;&lt;a href="#fnr1"&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt; This is how I found out that the HP 7520 dropped the Facebook support that the 7510 had. What&amp;#8217;s that, you didn&amp;#8217;t know printers had Facebook? Me either.&lt;/p&gt;
&lt;p class="footnote" id="fn2"&gt;&lt;a href="#fnr2"&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; Seriously, in 2012 scanning is still hard?&lt;/p&gt;
&lt;p class="footnote" id="fn3"&gt;&lt;a href="#fnr3"&gt;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt; Well, except for the waste &amp;amp; environmental impact…&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/collectiveidea/~4/NDY32sHtDqw" height="1" width="1"/&gt;</content>
      <author>
        <name>Daniel Morrison</name>
      </author>
    <feedburner:origLink>http://collectiveidea.com/blog/archives/2012/12/11/so-we-bought-a-printer/</feedburner:origLink></entry>
  
    <entry>
      <title>Desks 2.0</title>
      <link href="http://feedproxy.google.com/~r/collectiveidea/~3/GP9FSgtoE6k/" />
      <id>50c74053dabe9d245d0017c9</id>
      <updated>2012-12-11T11:47:01-05:00</updated>
      <published>2012-12-11T11:00:00-05:00</published>
      <content type="html">&lt;p&gt;At Collective Idea, we&amp;#8217;ve been hard at work moving into a bigger, new and improved office space. We have big plans for the space, including refinished hardwood floors, &lt;a href="http://www.flor.com/"&gt;Flor&lt;/a&gt; carpet, &lt;a href="http://www.nest.com/"&gt;Nest&lt;/a&gt; thermostats and more. But before all the bells and whistles, we need to be able to… work.&lt;/p&gt;
&lt;p&gt;We don&amp;#8217;t need much: a solid internet connection, a handful of &lt;a href="http://www.apple.com/displays/"&gt;cinema displays&lt;/a&gt; and some good &lt;a href="http://www.medicalbillingandcoding.org/sitting-kills/"&gt;stand-up&lt;/a&gt; desks. That&amp;#8217;s where I come in.&lt;/p&gt;
&lt;p&gt;When I was 12 years old, I built a wood shop in the back of my parents&amp;#8217; garage. I made and sold &lt;a href="https://www.google.com/search?q=Adirondack+chairs&amp;amp;tbm=isch"&gt;Adirondack chairs&lt;/a&gt; throughout high school, taking orders from all around the U.S. and even Norway. In fact, building the website for my carpentry business was my first foray into web development.&lt;/p&gt;
&lt;p&gt;So when &lt;a href="http://twitter.com/danielmorrison"&gt;Daniel&lt;/a&gt; realized the lack of stand-up desk options out there and started talking about the possibility of building our own desks for the new office, I jumped at the chance.&lt;/p&gt;
&lt;h2&gt;The Goal&lt;/h2&gt;
&lt;p&gt;The stand-up desks that &lt;em&gt;are&lt;/em&gt; available for purchase are generally pretty &lt;a href="http://www.geekdesk.com/"&gt;expensive&lt;/a&gt;. Plus, the options are limited so there&amp;#8217;s always that nagging feeling of having the same thing as all the other standers out there.&lt;/p&gt;
&lt;p&gt;So the goal was simple: build an inexpensive, one-of-a-kind desk that fits our space and style.&lt;/p&gt;
&lt;h2&gt;The Top&lt;/h2&gt;
&lt;p&gt;The first picture that came to mind is of the monolithic wooden  tables in Apple retail stores.&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c740e8dabe9d0bdd00560f/original/AppleStoreSanlitun.jpg"&gt;&lt;img src="/assets/50c740e8dabe9d0bdd00560f/fit_in_blog_post/AppleStoreSanlitun.jpg" alt="Apple Retail Store" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So I started looking at butcher block tabletops. The problem was that the 4×6&amp;#8217; size we wanted was either unavailable or terribly expensive. That&amp;#8217;s when &lt;a href="http://twitter.com/bryckbost"&gt;Brian&lt;/a&gt; saved the day with &lt;a href="http://fixedesign.com/INAM_01.php"&gt;this suggestion&lt;/a&gt; to use two inexpensive &lt;a href="http://www.ikea.com"&gt;&lt;span class="caps"&gt;IKEA&lt;/span&gt;&lt;/a&gt; countertops:&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c740f5dabe9d16870065d1/original/INAM_01.jpg"&gt;&lt;img src="/assets/50c740f5dabe9d16870065d1/fit_in_blog_post/INAM_01.jpg" alt="Fixed Design INAM Conference Table" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We all loved the idea and decided on the &lt;a href="http://www.ikea.com/us/en/catalog/products/20057854/#/20057854"&gt;NUMERÄR&lt;/a&gt; countertops in beech because they would pretty closely match our maple hardwood floors.&lt;/p&gt;
&lt;h2&gt;The Power&lt;/h2&gt;
&lt;p&gt;The difference between the desks we wanted to build and the conference table above is that we need power; to our cinema displays as well as whatever gadgets we&amp;#8217;ll be charging during the day.&lt;/p&gt;
&lt;p&gt;The easiest option was to do nothing at all. We could simply drape the power cords off the back of the desk. But we quickly ruled that out because, well… it&amp;#8217;s ugly. Plus there&amp;#8217;s no &amp;#8220;back&amp;#8221; of the desk since we intend on setting up stations all around the desk&amp;#8217;s perimeter.&lt;/p&gt;
&lt;p&gt;The next idea was a trough. By separating the two countertops a few inches, we could create a channel in which to mount outlets and stow power bricks. The downside is that we lose a little of the monolithic look we were going for by dividing our tabletop in two. Plus, it wouldn&amp;#8217;t allow us to easily set up stations on either end of the desk.&lt;/p&gt;
&lt;p&gt;The solution we landed on was a mix of the two; to cut out a slot in the middle of the tabletop. Eventually, we&amp;#8217;ll install a &amp;#8220;floor&amp;#8221; to the power management slot where we can install outlets, throw our power bricks, etc. but for the first iteration we&amp;#8217;ll simply drop our power cords through it.&lt;/p&gt;
&lt;h2&gt;The Base&lt;/h2&gt;
&lt;p&gt;We tried to contact the builder of the &lt;span class="caps"&gt;IKEA&lt;/span&gt; conference table above to ask how he built the base of the table, but we got no response. So we had to go it alone.&lt;/p&gt;
&lt;p&gt;We briefly considered wooden legs to match the tabletop but it would have required a lot of supporting structure and/or fancy joints that we didn&amp;#8217;t really have the time (or patience) for.&lt;/p&gt;
&lt;p&gt;Our next stop was stainless steel hairpin legs. We found &lt;a href="http://hairpinlegs.com/products.php?id=7"&gt;some&lt;/a&gt; that looked great and could be made custom, hopefully to accommodate our stand-up height. They also have levelers for uneven floors (which we have).&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/50c740f1dabe9d16870065be/IMG_0327.JPG" alt="HairpinLegs.com Original" /&gt;&lt;/p&gt;
&lt;p&gt;Unfortunately, we couldn&amp;#8217;t find anyone who would make us legs that were quite as tall as we were looking for, at about 41&amp;quot;. Most topped out at around 36&amp;quot;.&lt;/p&gt;
&lt;p&gt;Next strategy: frantic Googling. And we came up with simple 3&amp;quot; &lt;a href="http://www.tablelegsonline.com/shop/4034-tall-3-diameter-set-p-2051.html?cPath=3"&gt;tubular legs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c74270dabe9d1687006ae3/original/BAR41_app5.jpg"&gt;&lt;img src="/assets/50c74270dabe9d1687006ae3/fit_in_blog_post/BAR41_app5.jpg" alt="TableLegsOnline.com 41x3" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;These legs can be custom cut but already come in a 40¾&amp;quot; height, standard. Each leg can support a 300 lb. load which is plenty, even for our heavy 200 lb. tabletop.&lt;/p&gt;
&lt;p&gt;If we use a half-inch spacer between the leg and the tabletop, the surface of our desk will be 42¾&amp;quot; high, which is the height we&amp;#8217;ve all become comfortable with.&lt;/p&gt;
&lt;p&gt;So now that the legs are chosen, it&amp;#8217;s time to start building!&lt;/p&gt;
&lt;h2&gt;The Build&lt;/h2&gt;
&lt;p&gt;I recently moved into a new house and I don&amp;#8217;t have a wood shop set up yet so the plan was to build one of the six desks from start to finish and use it as a workbench for the remaining five. It would also give me some practice so that hopefully, the rest would come together more smoothly.&lt;/p&gt;
&lt;h3&gt;Routing the Tabletop&lt;/h3&gt;
&lt;p&gt;In order to make the slot for power management, we need to cut a groove in the long side of each countertop that would make up our tabletop. A good &lt;a href="http://en.wikipedia.org/wiki/Router_%28woodworking%29"&gt;router&lt;/a&gt; is the perfect tool for the job, along with a &lt;a href="http://www.amazon.com/Freud-50-118-Bearing-Diameter-Carbide/dp/B002IPHGAW"&gt;pattern bit&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Using a straightedge and a soup can, I drew the rounded shape of the groove on a scrap of &lt;span class="caps"&gt;MDF&lt;/span&gt; (Medium-Density Fiberboard). I cut it out with my &lt;a href="http://en.wikipedia.org/wikiiki/Jigsaw_%28power_tool%29"&gt;jigsaw&lt;/a&gt; and sanded to remove any bumps or imperfections. This one cut would be the template for all twelve finished grooves so it&amp;#8217;s important that it&amp;#8217;s done carefully.&lt;/p&gt;
&lt;p&gt;After hoisting the countertop onto my workbench, I clamped the &lt;span class="caps"&gt;MDF&lt;/span&gt; template onto the edge and traced the inside of the curve with a pencil. Beech is a hard wood so I&amp;#8217;d have to remove most of the material with my jigsaw before I could use the router to finish it off with a clean edge.&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c74353dabe9d0bdd007e4f/original/IMG_0002.jpg"&gt;&lt;img src="/assets/50c74353dabe9d0bdd007e4f/fit_in_blog_post/IMG_0002.jpg" alt="Template" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c7435adabe9d0bda00ccb5/original/IMG_0004.jpg"&gt;&lt;img src="/assets/50c7435adabe9d0bda00ccb5/fit_in_blog_post/IMG_0004.jpg" alt="Template detail" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The curves of the groove have a tight 1½&amp;quot; radius which is difficult to cut with a jigsaw, especially when the stock is 1½&amp;quot; thick. To make it easier, I cut into the curve at several places along the radius. This makes cutting the entire curve easier by simply connecting the dots.&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c74364dabe9d0bda00ccfe/original/IMG_0006.jpg"&gt;&lt;img src="/assets/50c74364dabe9d0bda00ccfe/fit_in_blog_post/IMG_0006.jpg" alt="Radial cuts" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c7436bdabe9d2460003345/original/IMG_0007.jpg"&gt;&lt;img src="/assets/50c7436bdabe9d2460003345/fit_in_blog_post/IMG_0007.jpg" alt="Perimeter cuts" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;With the curves cut, I finished the groove by cutting lengthwise down the countertop. This gives me my rough cut that&amp;#8217;s ready for the router.&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c74373dabe9d0bda00cda9/original/IMG_0008.jpg"&gt;&lt;img src="/assets/50c74373dabe9d0bda00cda9/fit_in_blog_post/IMG_0008.jpg" alt="Lengthwise cut" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c7437adabe9d2467003969/original/IMG_0009.jpg"&gt;&lt;img src="/assets/50c7437adabe9d2467003969/fit_in_blog_post/IMG_0009.jpg" alt="Ready for router" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The pattern bit I&amp;#8217;m using in the router has a ball bearing at the top that rides along the &lt;span class="caps"&gt;MDF&lt;/span&gt; template and cutters below that remove the material. Slowly working from left to right, the router has no trouble eating through all of the excess material that the jigsaw left behind. We&amp;#8217;re left with a clean edge.&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c74381dabe9d2460003566/original/IMG_0013.jpg"&gt;&lt;img src="/assets/50c74381dabe9d2460003566/fit_in_blog_post/IMG_0013.jpg" alt="Routing" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c7437adabe9d2467003969/original/IMG_0009.jpg"&gt;&lt;img src="/assets/50c7437adabe9d2467003969/fit_in_blog_post/IMG_0009.jpg" alt="Routed edge" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c74397dabe9d050200ebae/original/IMG_0025.jpg"&gt;&lt;img src="/assets/50c74397dabe9d050200ebae/fit_in_blog_post/IMG_0025.jpg" alt="Groove" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A quick sanding removes any ridges left by the router and knocks down the hard edges and corners.&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c7438edabe9d24670039a1/original/IMG_0024.jpg"&gt;&lt;img src="/assets/50c7438edabe9d24670039a1/fit_in_blog_post/IMG_0024.jpg" alt="Hard edge" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c7439ddabe9d16870075ac/original/IMG_0026.jpg"&gt;&lt;img src="/assets/50c7439ddabe9d16870075ac/fit_in_blog_post/IMG_0026.jpg" alt="Sanding" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c743a4dabe9d2afa001e7c/original/IMG_0028.jpg"&gt;&lt;img src="/assets/50c743a4dabe9d2afa001e7c/fit_in_blog_post/IMG_0028.jpg" alt="Soft edge" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;That&amp;#8217;s it for this one countertop. One more just like it and we&amp;#8217;re ready to start assembling the tabletop.&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c743addabe9d1687007773/original/IMG_0037.jpg"&gt;&lt;img src="/assets/50c743addabe9d1687007773/fit_in_blog_post/IMG_0037.jpg" alt="Countertop pair" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Attaching the Stringers&lt;/h3&gt;
&lt;p&gt;The first thing to do was flip the countertops over so that we&amp;#8217;re working with the underside. Sounds obvious, but important! Each countertop has one better side intended to be the top surface. &lt;span class="caps"&gt;IKEA&lt;/span&gt; stamps the underside.&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c743b4dabe9d2b16002142/original/IMG_0038.jpg"&gt;&lt;img src="/assets/50c743b4dabe9d2b16002142/fit_in_blog_post/IMG_0038.jpg" alt="Underside" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c743bcdabe9d2467003c4e/original/IMG_0039.jpg"&gt;&lt;img src="/assets/50c743bcdabe9d2467003c4e/fit_in_blog_post/IMG_0039.jpg" alt="Stamp" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I cut the stringers from the same &lt;span class="caps"&gt;MDF&lt;/span&gt; I used for the groove template. These will attach the two countertops to each other and provide locations to attach the legs. The &lt;span class="caps"&gt;MDF&lt;/span&gt; is ½&amp;quot; thick, giving us our desired height once the legs are attached. Each stringer has four holes pre-drilled at each end to accept leg brackets. The holes should be big enough for the screws to pass through easily. We don&amp;#8217;t care about the screw threads gripping in the &lt;span class="caps"&gt;MDF&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c743c4dabe9d1687007abb/original/IMG_0040.jpg"&gt;&lt;img src="/assets/50c743c4dabe9d1687007abb/fit_in_blog_post/IMG_0040.jpg" alt="Drilling stringers" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The stringers are positioned so that the center of each leg is 8½&amp;quot; from the edges of the table. This will put the surface of the 3&amp;quot; legs 7&amp;quot; from the edges of the table to allow enough room between them for stools but also help avoid accidental kicking. Once the stringer is in position, we drill through one of the holes in the stringer and attach the bracket and stringer to the countertop. Then we make sure the other end is still in position and repeat. When one screw is attached in each countertop, we can pre-drill the remaining holes.&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c743d3dabe9d1687007be5/original/IMG_0043.jpg"&gt;&lt;img src="/assets/50c743d3dabe9d1687007be5/fit_in_blog_post/IMG_0043.jpg" alt="Pre-drilling brackets" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Starting with the opposite corner of each bracket, I secure the remaining screws. I&amp;#8217;m using #12 1½&amp;quot; self-tapping &lt;a href="http://www.homedepot.com/h_d1/N-25ecodZ5yc1v/R-100162381/h_d2/ProductDisplay"&gt;screws&lt;/a&gt;. They have great grip and are easily driven in using a power nut driver.&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c743ccdabe9d2467003d7d/original/IMG_0041.jpg"&gt;&lt;img src="/assets/50c743ccdabe9d2467003d7d/fit_in_blog_post/IMG_0041.jpg" alt="Driving brackets" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;MDF&lt;/span&gt; is fairly flexible so in order to minimize the length of the stringer that is subject to bending forces, we&amp;#8217;ll attach the length of the stringer to the countertops using standard #10 1½&amp;quot; &lt;a href="http://www.homedepot.com/h_d1/N-5yc1v/R-202041010/h_d2/ProductDisplay"&gt;wood screws&lt;/a&gt;. Each screw hole is marked, pre-drilled and &lt;a href="http://en.wikipedia.org/wiki/Countersink"&gt;countersunk&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c743dbdabe9d2467003e58/original/IMG_0044.jpg"&gt;&lt;img src="/assets/50c743dbdabe9d2467003e58/fit_in_blog_post/IMG_0044.jpg" alt="Marking" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c743e2dabe9d2460003b36/original/IMG_0045.jpg"&gt;&lt;img src="/assets/50c743e2dabe9d2460003b36/fit_in_blog_post/IMG_0045.jpg" alt="Countersinking" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You can gauge how far to countersink by drilling until the head of the screw fits in the opening.&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c743e8dabe9d1687007f97/original/IMG_0046.jpg"&gt;&lt;img src="/assets/50c743e8dabe9d1687007f97/fit_in_blog_post/IMG_0046.jpg" alt="Countersinking tip" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Then all screws are driven.&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c743efdabe9d2460003d62/original/IMG_0048.jpg"&gt;&lt;img src="/assets/50c743efdabe9d2460003d62/fit_in_blog_post/IMG_0048.jpg" alt="Driven screws" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c743f5dabe9d0bda00d488/original/IMG_0049.jpg"&gt;&lt;img src="/assets/50c743f5dabe9d0bda00d488/fit_in_blog_post/IMG_0049.jpg" alt="Driven screws detail" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;What Not to Do&lt;/h3&gt;
&lt;p&gt;I learned the hard way that if your pre-drilled holes aren&amp;#8217;t big or deep enough, the beech countertops are so hard that you can spin the heads right off of the screws. As usual, trial and error wins the day!&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c743fddabe9d2460003ed8/original/IMG_0051.jpg"&gt;&lt;img src="/assets/50c743fddabe9d2460003ed8/fit_in_blog_post/IMG_0051.jpg" alt="Oops" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Adding the Supports&lt;/h3&gt;
&lt;p&gt;Butcher block is resistant to flexing lengthwise but our tabletops have a structural weakness. The load of cinema displays near the center of the table will subject each countertop to twisting forces. To distribute those forces across both countertops, we&amp;#8217;re adding angle iron supports near the center. Angle iron is extremely resistant to flexing.&lt;/p&gt;
&lt;p&gt;Each support is attached to the countertops using four #14 1¼&amp;quot; self-tapping &lt;a href="http://www.homedepot.com/h_d1/N-5yc1v/R-100196327/h_d2/ProductDisplay"&gt;screws&lt;/a&gt;; two at the far ends and two near where the countertops meet.&lt;/p&gt;
&lt;p&gt;Be careful when pre-drilling these holes. Without the addition of the ½&amp;quot; stringer, it&amp;#8217;s easy to pre-drill too deep and drill through the top surface of the tabletop. To prevent that, wrap the drill bit in tape at the proper depth so you know when to stop drilling.&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c74418dabe9d246700405c/original/IMG_0059.jpg"&gt;&lt;img src="/assets/50c74418dabe9d246700405c/fit_in_blog_post/IMG_0059.jpg" alt="Pre-drilling tip" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Once pre-drilled, we tighten the supports to the tabletop with screws and washers.&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c74404dabe9d2460003f65/original/IMG_0053.jpg"&gt;&lt;img src="/assets/50c74404dabe9d2460003f65/fit_in_blog_post/IMG_0053.jpg" alt="Supports detail" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c7440adabe9d0bda00d596/original/IMG_0054.jpg"&gt;&lt;img src="/assets/50c7440adabe9d0bda00d596/fit_in_blog_post/IMG_0054.jpg" alt="Supports" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Wrapping Up&lt;/h3&gt;
&lt;p&gt;At this point, this tabletop is done! Flip it over and admire.&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c74411dabe9d16870081d6/original/IMG_0058.jpg"&gt;&lt;img src="/assets/50c74411dabe9d16870081d6/fit_in_blog_post/IMG_0058.jpg" alt="Finished top" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Placing the table in position is a two-person job (at least). First we put the tabletop upside-down on the floor where we want it to be. Two people are needed because each tabletop weighs around 200 lb. and they&amp;#8217;re big! The stainless steel legs are then hand-twisted into the brackets.&lt;/p&gt;
&lt;p&gt;Finally, we flip the table over into its upright position. It&amp;#8217;s important to keep an eye on the legs while flipping the table. If a leg gets caught underneath the table while flipping, the lateral force can damage or break the leg and bracket. These legs are built for vertical forces.&lt;/p&gt;
&lt;h2&gt;The Result&lt;/h2&gt;
&lt;p&gt;We&amp;#8217;ve been working from our new office for a couple months now and we love it. After applying a few coats of a &lt;a href="http://www.homedepot.com/h_d1/N-5yc1v/R-202061403/h_d2/ProductDisplay?catalogId=10053"&gt;clear satin finish&lt;/a&gt;, the tables are working and looking great. All told, we built two sit-down height and four stand-up height desks.&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c757c2dabe9d62700002d7/original/IMG_0243.JPG"&gt;&lt;img src="/assets/50c757c2dabe9d62700002d7/fit_in_blog_post/IMG_0243.JPG" alt="Sit-down height" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c757ccdabe9d6270000323/original/IMG_0244.JPG"&gt;&lt;img src="/assets/50c757ccdabe9d6270000323/fit_in_blog_post/IMG_0244.JPG" alt="Stand-up height" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We also finished installing power management in each desk and made them chainable to clean up cord clutter underneath.&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c759c5dabe9d53180067e8/original/IMG_0246.JPG"&gt;&lt;img src="/assets/50c759c5dabe9d53180067e8/fit_in_blog_post/IMG_0246.JPG" alt="Power management above" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="/assets/50c7593edabe9d531800612d/original/IMG_0245.JPG"&gt;&lt;img src="/assets/50c7593edabe9d531800612d/fit_in_blog_post/IMG_0245.JPG" alt="Power management below" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So far, we&amp;#8217;ve been loving them. Come on by and see for yourself. We&amp;#8217;ll save a spot for you!&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/collectiveidea/~4/GP9FSgtoE6k" height="1" width="1"/&gt;</content>
      <author>
        <name>Steve Richert</name>
      </author>
    <feedburner:origLink>http://collectiveidea.com/blog/archives/2012/12/11/desks-20/</feedburner:origLink></entry>
  
    <entry>
      <title>Building Office 2.0 </title>
      <link href="http://feedproxy.google.com/~r/collectiveidea/~3/6csaNgmxi6c/" />
      <id>50c4f538dabe9d5aa4000443</id>
      <updated>2012-12-10T16:50:34-05:00</updated>
      <published>2012-12-10T16:45:00-05:00</published>
      <content type="html">&lt;p&gt;I had long had my eye on our new office space. The two previous tenants are friends and I loved the location.&lt;/p&gt;
&lt;p&gt;We first looked at it over the summer. It was just an empty space but it had the advantage of being fully built-out and well-kept.&lt;/p&gt;
&lt;figure&gt;
&lt;p&gt;&lt;a href="/assets/50c4f6badabe9d5aa40012e2/photo.JPG"&gt;&lt;img src="/assets/50c4f6badabe9d5aa40012e2/fit_in_blog_post/photo.JPG"&gt;&lt;/a&gt;&lt;br /&gt;
&lt;figcaption&gt;blurry photo from our first visit&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;I knew we had to make a plan or we&amp;#8217;d be sitting on the floor, so we brought in our secret weapon: Interior Designer Jenn Ryckbost. Jenn quickly had an accurate mock-up of the entire space in &lt;a href="http://www.sketchup.com"&gt;SketchUp&lt;/a&gt; and we started designing what the space should look like.&lt;/p&gt;
&lt;figure&gt;
&lt;p&gt;&lt;a href="/assets/50c4f5a0dabe9d4c64006642/Riverview_4th_floor_views_08_02_12.jpg"&gt;&lt;img src="/assets/50c4f5a0dabe9d4c64006642/fit_in_blog_post/Riverview_4th_floor_views_08_02_12.jpg"&gt;&lt;/a&gt;&lt;br /&gt;
&lt;figcaption&gt;early renderings&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;We put the SketchUp file in Dropbox and the whole team started to give input. We tried to make the entire process transparent. We had a Basecamp project for discussions and tried to involve everyone in any part they were interested in.&lt;/p&gt;
&lt;p&gt;Like building software, spending time in the design process got us a better result. We iterated daily, changing anything we didn&amp;#8217;t like, and only placing orders when we were satisfied.&lt;/p&gt;
&lt;figure&gt;
&lt;p&gt;&lt;a href="/assets/50c6502edabe9d70c00193db/Riverview_410_update.png"&gt;&lt;img src="/assets/50c6502edabe9d70c00193db/fit_in_blog_post/Riverview_410_update.png"&gt;&lt;/a&gt;&lt;br /&gt;
&lt;figcaption&gt;most of the furniture is in&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;It was also great to have help with picking colors. If left to me, I probably would have left everything off-white. Jenn built us a great palette, and now we have great splashes of color, with a chalkboard wall too!&lt;/p&gt;
&lt;figure&gt;
&lt;p&gt;&lt;a href="/assets/50c64d50dabe9d113d00e15d/ColorSpring615.jpg"&gt;&lt;img src="/assets/50c64d50dabe9d113d00e15d/fit_in_blog_post/ColorSpring615.jpg"&gt;&lt;/a&gt;&lt;br /&gt;
&lt;figcaption&gt;an early color palette&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="/about/#steve"&gt;Steve&lt;/a&gt; got to work building most of our tables and we ordered everything else we needed. The day we got wifi setup, we didn&amp;#8217;t have much more than our stand-up tables but we started working out of the new space. Ordering furniture can be slow (finally got our last batch a week ago) but it was great to know what we had in store.&lt;/p&gt;
&lt;p&gt;The final result looks amazingly (but unsurprisingly) like our designs. We&amp;#8217;ll post more photos soon or you can come to our &lt;a href="/office2/"&gt;Open House&lt;/a&gt; to see for yourself!&lt;/p&gt;
&lt;figure&gt;
&lt;p&gt;&lt;a href="/assets/50c64d58dabe9d2f49004193/Riverview_410_update_2012_11_09_17480600000.jpg"&gt;&lt;img src="/assets/50c64d58dabe9d2f49004193/fit_in_blog_post/Riverview_410_update_2012_11_09_17480600000.jpg"&gt;&lt;/a&gt;&lt;br /&gt;
&lt;figcaption&gt;Final rendering&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/collectiveidea/~4/6csaNgmxi6c" height="1" width="1"/&gt;</content>
      <author>
        <name>Daniel Morrison</name>
      </author>
    <feedburner:origLink>http://collectiveidea.com/blog/archives/2012/12/10/building-office-20/</feedburner:origLink></entry>
  
    <entry>
      <title>Office 2.0</title>
      <link href="http://feedproxy.google.com/~r/collectiveidea/~3/kFAvN0_TIe0/" />
      <id>50377d63dabe9d3ee6005497</id>
      <updated>2012-12-12T11:44:36-05:00</updated>
      <published>2012-12-10T16:40:00-05:00</published>
      <content type="html">&lt;p&gt;We&amp;#8217;re now working out of our Office 2.0 (and having an &lt;a href="/office2/"&gt;Open House&lt;/a&gt; Dec. 13&lt;sup&gt;th&lt;/sup&gt;). We found a beautiful spot in the heart  of downtown Holland with a bit of space to grow.&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/50b93723dabe9d3046003be6/fit_in_blog_post/background_office2.jpg" alt="The team in our Mad Men pose"&gt;&lt;/p&gt;
&lt;p&gt;We plan to share a lot about what we did, decisions we made, and details about how we work. We&amp;#8217;ll use this post to collect those future posts too.&lt;/p&gt;
&lt;p&gt;Posts in this series:&lt;/p&gt;
&lt;ol&gt;
	&lt;li&gt;&lt;a href="/blog/archives/2012/12/10/building-office-20/"&gt;Building Office 2.0&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="/blog/archives/2012/12/11/desks-20/"&gt;Desks 2.0&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="/blog/archives/2012/12/11/so-we-bought-a-printer/"&gt;So We Bought a Printer&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Posts about our first office, Office 1.0 include:&lt;/p&gt;
&lt;ol&gt;
	&lt;li&gt;&lt;a href="http://collectiveidea.com/blog/archives/2010/06/10/on-an-agile-space/"&gt;On an Agile Space&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="http://collectiveidea.com/blog/archives/2011/05/11/on-stand-up-desks/"&gt;On Stand-Up Desks&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;img src="http://feeds.feedburner.com/~r/collectiveidea/~4/kFAvN0_TIe0" height="1" width="1"/&gt;</content>
      <author>
        <name>Daniel Morrison</name>
      </author>
    <feedburner:origLink>http://collectiveidea.com/blog/archives/2012/12/10/office-two-point-zero/</feedburner:origLink></entry>
  
    <entry>
      <title>Playing with Go: Embarrassingly Parallel Scripts</title>
      <link href="http://feedproxy.google.com/~r/collectiveidea/~3/FRYVjjpTNgc/" />
      <id>50b7b0cbdabe9d161b00a83d</id>
      <updated>2012-12-03T10:03:27-05:00</updated>
      <published>2012-12-03T09:00:00-05:00</published>
      <content type="html">&lt;p&gt;I recently needed to take a list of domain and find which ones point to a specific IP address. For a small list, say less than 10, manually running &lt;code&gt;dig&lt;/code&gt; in the console would work great, but this list had almost 800 domains so I needed a script. As domain lookup is a network request and thus very slow, setting up the domain requests in parallel made sense. I could easily just do this in Ruby, my language du-jour, but I&amp;#8217;ve done this type of thread work before and frankly it can be tedious to set up, fragile, and still won&amp;#8217;t have access to all of my system&amp;#8217;s resources due to the &lt;span class="caps"&gt;GVL&lt;/span&gt;&lt;sup class="footnote" id="fnr1"&gt;&lt;a href="#fn1"&gt;1&lt;/a&gt;&lt;/sup&gt;. I&amp;#8217;ve been keeping an eye on &lt;a href="http://golang.org"&gt;Google&amp;#8217;s Go&lt;/a&gt; for some time now and decided to see how it handled this problem.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve been intrigued by Go since it was originally announced about three years ago. Here was a compiled, fast, light-weight, low level language with many of the features we take for granted these days, such as garbage collection, while also adding on a very sophisticated concurrency model similar to what&amp;#8217;s found in Erlang: very lightweight internal processes managed by the runtime. Sounds like a perfect fit for my requirements.&lt;/p&gt;
&lt;p&gt;The code I ended up with is here: &lt;a href="https://gist.github.com/4170926"&gt;https://gist.github.com/4170926&lt;/a&gt;. For the sake of comparisons I built a sequential version of the script as well as the parallel version and added timings for running both scripts against the full list of domains.&lt;/p&gt;
&lt;p&gt;Running these scripts for yourself is a one-liner: &lt;code&gt;go run [script.go]&lt;/code&gt;. The input file &lt;code&gt;domains.txt&lt;/code&gt; needs to be a newline-delimited list of domains. I&amp;#8217;ll go over the more confusing parts of the two scripts to help with understanding what&amp;#8217;s really going on here.&lt;/p&gt;
&lt;h4&gt;Objects?&lt;/h4&gt;
&lt;p&gt;Go&amp;#8217;s object model is very close to C&amp;#8217;s: structs with data and methods that operate on said structs. Both scripts only use a small, two-element struct, &lt;code&gt;DomainMap&lt;/code&gt;, to keep track of the IP address found for a given domain. I use the short-form to initialization new instances of the &lt;code&gt;DomainMap&lt;/code&gt; structure. The order of values maps directly to the order of the defined fields at the top of the scripts.&lt;/p&gt;
&lt;pre&gt;
type DomainMap struct {
  Domain string
  IpMapping string
}

object := DomainMap{domain, ipAddress}

object.Domain == domain
object.IpMapping == ipAddress
&lt;/pre&gt;
&lt;h4&gt;Error handling&lt;/h4&gt;
&lt;p&gt;Go does error handling by returning multiple values from a function, where the second return value is expected to be a value of type &lt;code&gt;error&lt;/code&gt;. You can ignore this with the &lt;code&gt;_&lt;/code&gt; variable.&lt;/p&gt;
&lt;pre&gt;
rawIpAddresses, _ := net.LookupIP(domain)
&lt;/pre&gt;
&lt;h4&gt;Parallelism&lt;/h4&gt;
&lt;p&gt;The parallel version of the script has some new concepts that need explaining, particularly goroutines, channels, and channel communication.&lt;/p&gt;
&lt;p&gt;A goroutine is a very lightweight process, sort of like a Ruby Fiber. Creating one is simple:&lt;/p&gt;
&lt;pre&gt;go domainLookup(responseChannel, domain)&lt;/pre&gt;
&lt;p&gt;Go will grab the function call after the &lt;code&gt;go&lt;/code&gt; keyword and execute it in parallel. However, given that we&amp;#8217;re no longer in the main process, we can&amp;#8217;t just return values from the function. We now need a different way to get the return value. This is where channels come in.&lt;/p&gt;
&lt;pre&gt;responseChannel := make(chan DomainMap)&lt;/pre&gt;
&lt;p&gt;As Go is a statically typed language, we need to define the type of channel being created. Channels can only accept data of the same type as the channel. Communication through channels is done with the reverse-stabby operator &lt;code&gt;&amp;lt;-&lt;/code&gt;, which should be read as &amp;#8220;the data on the right side is flowing to the left side&amp;#8221;:&lt;/p&gt;
&lt;pre&gt;
// Write into a channel
returnChannel &amp;lt;- DomainMap{domain, ipAddress}
&lt;/pre&gt;

&lt;pre&gt;
// Read from the channel
domainMap := &amp;lt;- responseChannel
&lt;/pre&gt;
&lt;p&gt;And that&amp;#8217;s all the special syntax. The only real difference between the parallel and sequential scripts is the map-reduce-esque setup to wait for all the goroutines to finish. I didn&amp;#8217;t need to worry about thread pooling, system capabilities, or thread safety. Go makes it so easy to write truly parallel code that there&amp;#8217;s no excuse not to anymore. I was able to run almost 800 goroutines (one per domain) all throwing out &lt;span class="caps"&gt;DNS&lt;/span&gt; queries and coming back in less than 10 seconds, in a script that doesn&amp;#8217;t even look like it&amp;#8217;s running in parallel.&lt;/p&gt;
&lt;p&gt;Now that Go 1.0 stable is out, it&amp;#8217;s a great time to get familiar with this language. I highly recommend checking out the &lt;a href="http://tour.golang.org/"&gt;Tour of Go&lt;/a&gt; for basic introductions into every major feature of the language, and there&amp;#8217;s a ton of documentation on the main website &lt;a href="http://golang.org"&gt;golang.org&lt;/a&gt;. For the little bit of time I&amp;#8217;ve played with Go now, I see a very bright future for this language.&lt;/p&gt;
&lt;p&gt;&lt;sup class="footnote" id="fnr1"&gt;&lt;a href="#fn1"&gt;1&lt;/a&gt;&lt;/sup&gt; Global VM Lock, more about Ruby&amp;#8217;s concurrency here: &lt;a href="http://www.engineyard.com/blog/2011/ruby-concurrency-and-you/"&gt;http://www.engineyard.com/blog/2011/ruby-concurrency-and-you/&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/collectiveidea/~4/FRYVjjpTNgc" height="1" width="1"/&gt;</content>
      <author>
        <name>Jason Roelofs</name>
      </author>
    <feedburner:origLink>http://collectiveidea.com/blog/archives/2012/12/03/playing-with-go-embarrassingly-parallel-scripts/</feedburner:origLink></entry>
  
</feed>
