<?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 version="2.0">
  <channel>
    <title>Perspectives: Writings from The Killswitch Collective</title>
    <link>http://www.killswitchcollective.com/blog/index.xml</link>
    <description>Perspectives is the blog of The Killswitch Collective, a Chicago-based web development, design and communication firm.</description>
    <language>en-us</language>
    <atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" href="http://feeds.feedburner.com/killswitchcollective/perspectives" type="application/rss+xml" /><item>
      <title>Monetize This!</title>
      <category />
      <description>&lt;p&gt;It seems that business pundits are of the mind that many of the successful social networks that exist today are doomed to fail because of a crippling inability to monetize themselves. I've thought about this and I'd like to take a moment to mount my digital soapbox. After much reflection, I think that this is not only a myopic point of view, but that it is exactly this line of thinking that will in fact bring down social networking as we know it.&lt;p&gt;

&lt;p&gt;I'll be honest with you; in college I didn't exactly excel in my
Finance, Accounting and Econ courses (and that, as my mother will tell you, is putting it lightly). However, I have been around long enough to recognize good business practice when I see it. I mean think about it, we all are experts in our own right, simply by performing our role as consumers.&lt;p&gt;

&lt;p&gt;As consumers, we reward those that provide useful, reliable, friendly services with our continued patronage. If they provide us with the contrary, we take our business elsewhere. We may not be schooled in the science of supply and demand, but we are smart enough to know when we are getting the short end of the stick.&lt;p&gt;

&lt;p&gt;But I digress… Back to the topic of monetizing social networks. For the jury, I submit Exhibit A: Facebook, the Grand Poobah of the social realm. Facebook seems to be perpetually seeking ways to monetize itself and every time it does, it shoots itself in its very corporate foot.&lt;p&gt;

&lt;p&gt;In November 2007, Facebook launched Beacon, a system where third-party websites can include a script by Facebook on their sites, and use it to send information about the actions of Facebook users on their site to Facebook. Information such as purchases made and games played are published in the user's news feed. While still active, a class action lawsuit was filed in 2008 against Facebook, Blockbuster Inc., Overstock.com, Fandango, Hotwire.com, GameFly, Zappos.com, and any additional "John Doe" corporations that participate in the Beacon service.&lt;p&gt;

&lt;p&gt;In 2008, we loyal users learned of a clause in the network's terms of service that reserved Facebook the right to sell users' data to private companies, stating "We may share your information with third parties, including responsible companies with which we have a relationship." The backlash from this revelation was so great that Facebook eventually removed the clause from their privacy policy when it was updated on November 26, 2008.&lt;p&gt;

&lt;p&gt;More recently, there are reports of rampant "click fraud" occurring within the network's cost-per-click advertising, a service mainly used by small self serve advertisers. In some cases, advertisers have reported gross misrepresentation of click-throughs as high as 10:1 when gauged against third-party metric tracking sites. The site instructed those who have issue with the service to log the discrepancies and submit them to Facebook. After taking the time to collate these logs, advertisers have been rewarded with a reassuring, pre-scripted response letter.&lt;p&gt;

&lt;p&gt;As I list these controversies, I know that they have resulted in little or no change in how the Facebook runs its network and that they have had no effect whatsoever on the exponential increase in FB users (What? Are we supposed to switch to MySpace? Please, that is so 2004).&lt;p&gt;

&lt;p&gt;However, the alternative network &lt;a href="http://www.twitter.com/kscollective" target="_blank"&gt; twitter &lt;/a&gt;, which most social mavens now use almost exclusively, has Facebook worried about its future (for evidence, just look at all of the new, twitter-like features recently added to Facebook). It isn't out of the realm of possibility that if Facebook continues to lose credibility, users will up and take their profiles elsewhere.&lt;p&gt;

&lt;p&gt;WARNING: If you are a devout capitalist, or if your name is Rupert Murdoch, the following paragraph may make your head explode.&lt;p&gt;

&lt;p&gt;What I propose as a solution is simple: don't monetize them, at least not in the traditional sense.  There exists in media a perfect example of how these sites can persevere and ensure longevity, but I haven't heard a single pundit offer this idea yet. I suggest that social networks adopt the same model as PBS and NPR. Revenue will come through "donations" that are made by corporations and businesses that wish to participate in this medium. If they are smart, these businesses will realize that they have a vested interest in ensuring the survival of these networks as a communication tool. This will require that that they accept that there is no way to moderate or direct the conversation; the value lays in the ability to facilitate and participate in it.&lt;p&gt;

&lt;p&gt;Not only will this provide a constant stream of revenue, but this model will ensure transparency throughout and allow these networks to maintain the trust and continued participation of its users.&lt;p&gt;

&lt;p&gt;I am not naive enough to think that the CEO's, CFO's, MBA's, etc. that run these sites will ever consider such a drastic course. I understand the need for ever-increasing revenue, especially in the digital world where the necessity for continued improvement requires substantial reinvestment. Only time will tell if Facebook and twitter and their ilk can survive or if the imminent death of MySpace is an aberration and not a paragon. I simply encourage those in charge of running our social networks be wary of how they seek to monetize them in the future, because their track record so far has me worried.&lt;p&gt; 
</description>
      <pubDate>Thu, 25 Jun 2009 09:00:35 -0700</pubDate>
      <link>http://www.killswitchcollective.com/articles/64</link>
      <guid>http://www.killswitchcollective.com/articles/64</guid>
    </item>
    <item>
      <title>Twitter Backgrounds, Courtesy of Killswitch</title>
      <category />
      <description>&lt;p&gt;Your friends swear it is the best thing ever. Time Magazine is running a cover story on it. You've heard stories of people getting jobs through it.&lt;/p&gt; 

&lt;p&gt;And finally, you got yourself a twitter account.&lt;/p&gt; 

&lt;p&gt;Hopefully, you've quickly acclimated yourself with this awesome service and are on your way to becoming a full-blown "Tweep" (person on twitter). And as you've perused through Ashton Kutcher's, Brittney Spears' and hopefully some more informative pages, you may notice that twitter allows its users to post custom background.&lt;/p&gt;

&lt;p&gt;Want a unique twitter background? Killswitch is here to help you make your twitter page all the rage with some backgrounds of our very own design.&lt;/p&gt;

&lt;p&gt;Just visit the ftp sites and download the images to you desktop.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www2.killswitchcollective.com/twitters/ksc_twitter_v1.jpg" target="_blank"&gt;Sports 1 &lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www2.killswitchcollective.com/twitters/ksc_twitter_v2.jpg" target="_blank"&gt;Sports 2 &lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www2.killswitchcollective.com/twitters/ksc_twitter_v3.jpg" target="_blank"&gt;Super Powers &lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www2.killswitchcollective.com/twitters/ksc_twitter_v5.jpg" target="_blank"&gt;Floral Corner &lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www2.killswitchcollective.com/twitters/ksc_twitter_v6.jpg" target="_blank"&gt;Paint Spill &lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www2.killswitchcollective.com/twitters/ksc_twitter_v7.jpg" target="_blank"&gt;Floral Full 1 &lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www2.killswitchcollective.com/twitters/ksc_twitter_v8.jpg" target="_blank"&gt;Floral Full 2 &lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www2.killswitchcollective.com/twitters/ksc_twitter_v9.jpg" target="_blank"&gt;Floral Side &lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Changing your twitter background is easy:&lt;/p&gt;
   
   &lt;p&gt;1. Log in&lt;/p&gt;
   &lt;p&gt;2. In top navigation bar, click Settings (or go to http://twitter.com/account/settings)&lt;/p&gt;
   &lt;p&gt;3. Click the last settings tab, Design&lt;/p&gt; 
  &lt;p&gt;4. Change your text and background colors and  upload your background     
   image&lt;/p&gt;
  &lt;p&gt;5. Save your changes&lt;/p&gt; 

&lt;p&gt;We've also included a our &lt;a href="http://www2.killswitchcollective.com/twitters/ksc_twitter_v4.gif" target="_blank"&gt;Grey Things &lt;/a&gt; image as a transparent GIF. This way, if you want to add your own touch to this background (our feelings won't be hurt, go ahead!), you can adjust your background color to whatever you please. However, this is a little tricky...&lt;/p&gt;
   
&lt;p&gt;1. From the Design tab, choose "Change background image"&lt;/p&gt;
   &lt;p&gt;2. Then, click "no image"&lt;/p&gt;
   &lt;p&gt;3. Adjust your background color to your liking&lt;/p&gt;
   &lt;p&gt;4. Upload the "Grey Things" GIF&lt;/p&gt;
   &lt;p&gt;5. Save changes&lt;/p&gt;

&lt;p&gt;And the image should show up on top of the background you selected.&lt;/p&gt;

&lt;p&gt;Don't forget to follow us on &lt;a href="http://www.twitter.com/kscollective" target="_blank"&gt;Twitter &lt;/a&gt; for more artwork to come!
</description>
      <pubDate>Mon, 08 Jun 2009 15:03:52 -0700</pubDate>
      <link>http://www.killswitchcollective.com/articles/63</link>
      <guid>http://www.killswitchcollective.com/articles/63</guid>
    </item>
    <item>
      <title>Digital Strategy In A Recession</title>
      <category>Perspectives</category>
      <description>&lt;p&gt;When times are tough, companies tighten the belt buckle (thank you, Captain Obvious). This generally entails companies cutting or severely limiting their expenditures on non-essential projects to free up money for important things…like executive bonuses (ZING!).  While businesses are cutting down expenditures across the board, many proactive companies are making concentrated efforts to grow and enhance their digital presence.&lt;p&gt;

&lt;p&gt;Before I write any further, let me address my somewhat biased opinion on the matter. Yes, I am the New Business &amp; Marketing Manager of a firm that sells clients digital services and that would serve to reduce my impartiality.  With that said, I am merely pointing out what others have chosen to do with their limited budgets. I just happen to think that they are right.&lt;p&gt;

&lt;p&gt;A great first example is the recent launch by The New York Times of its &lt;a href="https://timesreader.nytimes.com" target="_blank"&gt; TimesReader 2.0 &lt;/a&gt; . Here is an industry that is, by all accounts, dying. It seems that every month there is news of yet another prominent newspaper going bankrupt, and while the Times will no doubt survive this print holocaust, it has not been immune to the pitfalls that plague its industry contemporaries (see Reuters' article &lt;a href="http://www.reuters.com/article/ousiv/idUSTRE54K5US20090521" target="_blank"&gt; "S&amp;P cuts New York Times rating deeper into junk status" &lt;/a&gt;).  Realizing that in order to survive and remain an industry-leader, the Times has chosen to invest in developing the first truly "digital newspaper".&lt;p&gt; 

&lt;p&gt;Competitive advantages are all the more important in today's tough marketplace. One of the best advantages a business can leverage in a recession is customer service which, when executed correctly, builds consumer loyalty and can distinguish a business from its competitors. The web provides businesses the opportunity to interact and engage customers in an expedited and convenient manner, provided that they embrace emerging technologies and digital trends (note to cellular companies, your online chats are even more infuriating than your 800 lines).  Domino's Pizza recently committed to improving dialogue with consumers by hiring a consulting agency to assist them in strengthening their relationships with consumers through online communities. As stated in their press release, Domino's seeks to "continue to incorporate social media into their overall communications plan, expand consumer trust, and build excitement in new services and products."&lt;p&gt; 

&lt;p&gt;But investing in digital doesn't have to mean investing large amounts of capital. For example, take what some small businesses are doing with the free service&lt;a href="http://www.twitter.com/kscollective" target="_blank"&gt; Twitter &lt;/a&gt; . New Orleans based Naked Pizza, recently launched a Twitter-specific marketing campaign that proved to be rather successful.  In a test run April 23, an exclusive-to-Twitter promotion brought in 15% of the day's business. And while Twitter has yet to offer a comprehensive analytics tool, the simple daily tracking of how many users you are attracting shows at a macro-level how your outreach via the service is fairing.&lt;p&gt;

&lt;p&gt;Does bolstering your digital presence make your company "recession-proof"…probably not.  It isn't a cure-all and it can certainly require some investment when funding is tight.  However, going digital (as it pertains to your specific industry and audience) arms your company with a proven means of communication in a time when traditional methods are proving ineffective.&lt;p&gt;

&lt;p&gt;Or you can always think of it like this: In this economy, it couldn't hurt to try.&lt;p&gt;</description>
      <pubDate>Wed, 27 May 2009 12:00:28 -0700</pubDate>
      <link>http://www.killswitchcollective.com/articles/59</link>
      <guid>http://www.killswitchcollective.com/articles/59</guid>
    </item>
    <item>
      <title>Super Easy Number Tracking with FatNum.com</title>
      <category>Development</category>
      <description>&lt;h3&gt;What's Your FatNum?&lt;/h3&gt;

&lt;p&gt;Numbers are a big deal. Whether you're talking about business, family, fun, personal goals, a lot can be simply boiled down to one big, fat number...&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;How many people registered for my app?&lt;/li&gt;
  &lt;li&gt;How many batches are left in my database import?&lt;/li&gt;
  &lt;li&gt;How many days left until my birthday?&lt;/li&gt;
  &lt;li&gt;How many pounds have I lost on my diet?&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;And so on...&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So what's the big, fat number that you need to keep track of? Whatever it is, &lt;a href="http://www.fatnum.com" target="_blank"&gt;FatNum.com&lt;/a&gt; can help.&lt;/p&gt;

&lt;h3&gt;What is FatNum.com?&lt;/h3&gt;

&lt;p&gt;FatNum.com lets you signup and start tracking your FatNums right now! Right away you are given a simple Web interface for creating your FatNums and keeping them up to date. Each FatNum is assigned a five-character code, so you can share your FatNum with others discreetly without having to log in.&lt;/p&gt;

&lt;p&gt;For all you programmers who want their Ruby apps to share data with FatNum.com, I've built &lt;a href="http://github.com/chrisjpowers/fat_num/tree/master" target="_blank"&gt;this RubyGem&lt;/a&gt; for easily accessing and updating your FatNums. From the documentation:&lt;/p&gt;

&lt;pre&gt;
&lt;span class="ident"&gt;require&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;rubygems&lt;/span&gt;&lt;span class="punct"&gt;'&lt;/span&gt;
&lt;span class="ident"&gt;require&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;fat_num&lt;/span&gt;&lt;span class="punct"&gt;'&lt;/span&gt;

&lt;span class="ident"&gt;f&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span style="color:#DA4939"&gt;FatNum&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;new&lt;/span&gt;&lt;span class="punct"&gt;('&lt;/span&gt;&lt;span style="color:#A5C261"&gt;your@email.com&lt;/span&gt;&lt;span class="punct"&gt;',&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;password&lt;/span&gt;&lt;span class="punct"&gt;')&lt;/span&gt;

&lt;span style="color:#BC9458"&gt;# get your statistic&lt;/span&gt;
&lt;span class="ident"&gt;response&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="ident"&gt;f&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;get&lt;/span&gt;&lt;span class="punct"&gt;('&lt;/span&gt;&lt;span style="color:#A5C261"&gt;e37gh&lt;/span&gt;&lt;span class="punct"&gt;')&lt;/span&gt;
&lt;span class="ident"&gt;response&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;digit&lt;/span&gt; &lt;span style="color:#BC9458"&gt;#=&amp;gt; 120&lt;/span&gt;
&lt;span class="ident"&gt;response&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;description&lt;/span&gt; &lt;span style="color:#BC9458"&gt;#=&amp;gt; 'batches left'&lt;/span&gt;

&lt;span style="color:#BC9458"&gt;# update your statistic&lt;/span&gt;
&lt;span class="ident"&gt;f&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;update&lt;/span&gt;&lt;span class="punct"&gt;('&lt;/span&gt;&lt;span style="color:#A5C261"&gt;e37gh&lt;/span&gt;&lt;span class="punct"&gt;',&lt;/span&gt; &lt;span class="number"&gt;119&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;

&lt;span style="color:#BC9458"&gt;# sure enough, our update was successful&lt;/span&gt;
&lt;span class="ident"&gt;f&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;get&lt;/span&gt;&lt;span class="punct"&gt;('&lt;/span&gt;&lt;span style="color:#A5C261"&gt;e37gh&lt;/span&gt;&lt;span class="punct"&gt;').&lt;/span&gt;&lt;span class="ident"&gt;digit&lt;/span&gt; &lt;span style="color:#BC9458"&gt;#=&amp;gt; 119&lt;/span&gt;
&lt;/pre&gt;

&lt;p&gt;There's also some love for all you Mac users out there -- download the &lt;a href="http://www.fatnum.com/FatNum.zip"&gt;FatNum Dashboard Widget&lt;/a&gt; and easily track your FatNums from the comfort of your OSX Dashboard!&lt;/p&gt;

&lt;h3&gt;Any More Examples?&lt;/h3&gt;

&lt;p&gt;Here are a couple FatNums that FatNum.com is using for itself:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://www.fatnum.com/count" target="_blank"&gt;Number of FatNums in the System&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="http://www.fatnum.com/usrct" target="_blank"&gt;Number of Users on FatNum.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;FatNum.com is super simple, almost to a ridiculous degree, but I hope you can find some imaginitive uses for it!&lt;/p&gt;</description>
      <pubDate>Mon, 16 Mar 2009 09:43:28 -0700</pubDate>
      <link>http://www.killswitchcollective.com/articles/57</link>
      <guid>http://www.killswitchcollective.com/articles/57</guid>
    </item>
    <item>
      <title>Best Practices for Using and Hacking Gems</title>
      <category>Development</category>
      <description>&lt;p&gt;If you have built a Rails application, there is a good chance the power of Ruby gems were utilized to quickly add new functionality. In this article, we're going to go over some best practices to follow when using gems within a Rails application. I will demonstrate how needed gems can be specified in your environment, unpack a copy into your application and then we will do a little gem hacking without breaking them.&lt;/p&gt;

&lt;p&gt;The application we are about to build won't really be an application, we won't be creating any views, controller or even touch the database. We will be using the &lt;a href="http://github.com/topfunky/gruff/tree/master"&gt;Gruff gem&lt;/a&gt; by Geoffrey Grosenbach to generate some simple graphs and then we will enhance Gruff with some new options.&lt;/p&gt;

&lt;h3&gt;Installing and Freezing&lt;/h3&gt;

&lt;p&gt;Let's get started by creating a new Rails application and installing the Gruff gem.&lt;/p&gt;

&lt;pre&gt;
rails gem_hacks
sudo gem install gruff
&lt;/pre&gt;

&lt;p&gt;Add the following in the &lt;code&gt;Rails::Initializer.run&lt;/code&gt; block in your &lt;code&gt;config/environment.rb&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;
&lt;span class="ident"&gt;config&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;gem&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;gruff&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;
&lt;/pre&gt;

&lt;p&gt;By adding &lt;code&gt;config.gem "gruff"&lt;/code&gt; in our environment the application will look for the gruff gem on start up. If it doesn't exist it will throw an exception. This is much better than having the application running while the portions that depend on the gem error out.&lt;/p&gt;

&lt;p&gt;Lets make sure Gruff appears in the application's gem list and then copy it to the &lt;code&gt;vendor/gems&lt;/code&gt; directory of the application with the &lt;code&gt;unpack&lt;/code&gt; task.&lt;/p&gt;

&lt;pre&gt;
rake gems
rake gems:unpack:dependencies gruff
&lt;/pre&gt;

&lt;p&gt;By including Gruff within the application, we won't have to install the gem on every box the application finds itself on. Also, every developer working on the project will not have to worry about having the same gem version installed.&lt;/p&gt;


&lt;h3&gt;Making It Our Own&lt;/h3&gt;

&lt;p&gt;Next let's create a &lt;code&gt;BarGraph&lt;/code&gt; class that will require 'gruff'. The &lt;code&gt;BarGraph&lt;/code&gt; class will serve as a wrapper method for generating common bar graphs found on the site.&lt;/p&gt;
 
&lt;pre&gt;
&lt;span class="ident"&gt;require&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;gruff&lt;/span&gt;&lt;span class="punct"&gt;'&lt;/span&gt;
&lt;span style="color:#CC7833"&gt;class &lt;/span&gt;&lt;span class="class"&gt;BarGraph&lt;/span&gt;
  
&lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;/pre&gt;

&lt;p&gt;Every ruby class needs an &lt;code&gt;initialize&lt;/code&gt; method in order to create new instances. Add an &lt;code&gt;initialize&lt;/code&gt; method inside the &lt;code&gt;BarGraph&lt;/code&gt; class filled with Gruff code that will generate a static bar graph for now. Then create the &lt;code&gt;bar_graphs&lt;/code&gt; directory inside of &lt;code&gt;public/images&lt;/code&gt;.&lt;/p&gt;


&lt;pre&gt;
&lt;span style="color:#CC7833"&gt;def &lt;/span&gt;&lt;span class="method"&gt;initialize&lt;/span&gt;
  &lt;span class="ident"&gt;g&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span style="color:#DA4939"&gt;Gruff&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span style="color:#DA4939"&gt;Bar&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;new&lt;/span&gt;&lt;span class="punct"&gt;(&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;400x300&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;)&lt;/span&gt;
  &lt;span class="ident"&gt;g&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;data&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:years&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="punct"&gt;[&lt;/span&gt;&lt;span class="number"&gt;185&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="number"&gt;155&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="number"&gt;110&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="number"&gt;90&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="number"&gt;135&lt;/span&gt;&lt;span class="punct"&gt;]&lt;/span&gt;
  &lt;span class="ident"&gt;g&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;labels&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="punct"&gt;{&lt;/span&gt; &lt;span class="number"&gt;0&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;2004&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;,&lt;/span&gt; &lt;span class="number"&gt;1&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;2005&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;,&lt;/span&gt; &lt;span class="number"&gt;2&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;2006&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;,&lt;/span&gt; &lt;span class="number"&gt;3&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;2007&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;,&lt;/span&gt; &lt;span class="number"&gt;4&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;2008&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;&lt;/span&gt; &lt;span class="punct"&gt;}&lt;/span&gt;
  &lt;span class="ident"&gt;g&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;write&lt;/span&gt;&lt;span class="punct"&gt;('&lt;/span&gt;&lt;span style="color:#A5C261"&gt;public/images/bar_graphs/test.jpg&lt;/span&gt;&lt;span class="punct"&gt;')&lt;/span&gt;
&lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;/pre&gt;
 
 

&lt;p&gt;Next fire up your console and try &lt;code&gt;BarGraph.new&lt;/code&gt;, then go into your &lt;code&gt;public/images/bar_graphs&lt;/code&gt; directory and open up &lt;code&gt;test.jpg&lt;/code&gt;, it should look like the following:&lt;/p&gt;

&lt;p&gt;
&lt;img src="http://www2.killswitchcollective.com/articles/gruff/gem_hacks_graph1.jpg"&gt;
&lt;/p&gt;


&lt;h3&gt;Making Improvements&lt;/h3&gt; 
 
&lt;p&gt;Notice how the bar of the lowest value (90) is barely visible? By default, the minimum value will be the lowest value and the maximum will be the highest value. We can customize this by setting more attribute values of the instance. Add the following before the &lt;code&gt;g.write&lt;/code&gt; line.&lt;/p&gt;
 
&lt;pre&gt;
&lt;span class="ident"&gt;g&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;minimum_value&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="number"&gt;0&lt;/span&gt;
&lt;span class="ident"&gt;g&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;maximum_value&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="number"&gt;200&lt;/span&gt;
&lt;/pre&gt;
 
 
 
&lt;p&gt;Let's once again regenerate our graph through the console; now the bar with the value of 110 should be visible.&lt;/p&gt;

&lt;p&gt;
&lt;img src="http://www2.killswitchcollective.com/articles/gruff/gem_hacks_graph2.jpg"&gt;
&lt;/p&gt;
 
 
&lt;p&gt;Great, our graph starts at 0 and we've added some potential by specifying a higher maximum value. Lets make our &lt;code&gt;BarGraph&lt;/code&gt; class more dynamic by adding some arguments that can be passed through the &lt;code&gt;initialize&lt;/code&gt; method and refactor the code within.&lt;/p&gt;
 
&lt;pre&gt;
&lt;span style="color:#CC7833"&gt;def &lt;/span&gt;&lt;span class="method"&gt;initialize&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;values&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;labels&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;save_path&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;options&lt;/span&gt;&lt;span class="punct"&gt;={})&lt;/span&gt;
  &lt;span class="ident"&gt;g&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span style="color:#DA4939"&gt;Gruff&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span style="color:#DA4939"&gt;Bar&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;new&lt;/span&gt;&lt;span class="punct"&gt;(&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;400x300&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;)&lt;/span&gt;
  &lt;span class="ident"&gt;g&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;data&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:years&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;values&lt;/span&gt;
  &lt;span class="ident"&gt;g&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;labels&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="ident"&gt;labels&lt;/span&gt;
  &lt;span class="ident"&gt;g&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;minimum_value&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="number"&gt;0&lt;/span&gt;
  &lt;span class="ident"&gt;g&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;maximum_value&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="number"&gt;150&lt;/span&gt;
  &lt;span class="ident"&gt;g&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;write&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;save_path&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
&lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;/pre&gt;
 
 
 
&lt;p&gt;Now whenever we call &lt;code&gt;BarGraph.new&lt;/code&gt;, arguments for &lt;code&gt;values&lt;/code&gt;, &lt;code&gt;labels&lt;/code&gt; and &lt;CODE&gt;save_path&lt;/CODE&gt; will be needed. Reload your console and give the following a try:&lt;/p&gt;
 
&lt;span style="color:#DA4939"&gt;BarGraph&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;new&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt; &lt;span class="punct"&gt;[&lt;/span&gt;&lt;span class="number"&gt;185&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="number"&gt;155&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="number"&gt;110&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="number"&gt;90&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="number"&gt;135&lt;/span&gt;&lt;span class="punct"&gt;],&lt;/span&gt; 
              &lt;span class="punct"&gt;{&lt;/span&gt; &lt;span class="number"&gt;0&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;2004&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;,&lt;/span&gt; &lt;span class="number"&gt;1&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;2005&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;,&lt;/span&gt; &lt;span class="number"&gt;2&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;2006&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;,&lt;/span&gt; &lt;span class="number"&gt;3&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;2007&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;,&lt;/span&gt; &lt;span class="number"&gt;4&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;2008&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;&lt;/span&gt; &lt;span class="punct"&gt;},&lt;/span&gt; 
              &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;public/images/bar_graphs/test_with_arguments.jpg&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;&lt;/span&gt; &lt;span class="punct"&gt;)&lt;/span&gt;
&lt;/pre&gt;
 
 
 
&lt;p&gt;This will generate the same graph as before since we are passing in the same data. If we passed in an array with values higher then 200, however, it would get cut off due to the static maximum value in our &lt;code&gt;initialize&lt;/code&gt; method. We can remove the maximum value portion from our &lt;code&gt;initialize&lt;/code&gt; and Gruff will default to the highest value in our array. Another option is to add code that will take that maximum value and round it up.&lt;/p&gt;

&lt;h3&gt;Hacking the Gruff Gem&lt;/h3&gt;

&lt;p&gt;The auto-rounding of the maximum value would be a good feature to have in other Gruff graphs as well. Since we have the gem source in the &lt;code&gt;vendor/gems&lt;/code&gt; of our projects, adding it there might sound like a good idea at first. Later in the project lifecycle, however, the gem might be updated and those custom additions would be lost.&lt;/p&gt;
 
&lt;p&gt;The ideal solution would be to create a new module that would contain our hacks which will be included wherever needed. Whenever the gem is updated and our hacks break, we can easily fix them since they are all located in one place. With that being said let's create our &lt;code&gt;GruffHacks&lt;/code&gt; module and save it as &lt;code&gt;gruff_hacks.rb&lt;/code&gt; in &lt;code&gt;lib&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;
&lt;span style="color:#CC7833"&gt;module &lt;/span&gt;&lt;span class="module"&gt;GruffHacks&lt;/span&gt;
  &lt;span style="color:#CC7833"&gt;class &lt;/span&gt;&lt;span class="class"&gt;Gruff::Base&lt;/span&gt;
  
  &lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;/pre&gt;
 
 
&lt;p&gt;Our &lt;code&gt;GruffHacks&lt;/code&gt; module contains the &lt;code&gt;Gruff::Base&lt;/code&gt; class which is found in &lt;code&gt;vendor/gems/gruff/lib/gruff/base.rb&lt;/code&gt;. Take a few minutes to browse through the &lt;code&gt;Base&lt;/code&gt; class as we will be overriding methods from it next. Near the top all the attribute accessors for Gruff are being set. These attributes are shared between all graphs since they are part of base. Lets add a attribute accessor for rounding maximum values called round_maximum_value into &lt;code&gt;Gruff::Base&lt;/code&gt; of our &lt;code&gt;GruffHacks module&lt;/code&gt;.&lt;/p&gt;
 
&lt;p&gt;Remember we are not editing any code from the gem source, we are only going to use it for reference and copy methods that we will be overriding from it. Next lets copy the entire &lt;code&gt;initialize_ivars&lt;/code&gt; method from the gem source into &lt;code&gt;Gruff::Base&lt;/code&gt; of our &lt;code&gt;GruffHacks&lt;/code&gt; module. At the bottom, after &lt;code&gt;@norm_data = nil&lt;/code&gt;, let's add &lt;code&gt;@round_maximum_value = false&lt;/code&gt;. The &lt;code&gt;initialize_ivars&lt;/code&gt; method is responsible for setting default values for Gruff attributes, here we are setting &lt;code&gt;@round_max_value&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt; to cancel out the rounding functionality we will add soon. The goal with this module is to enhance Gruff, but to have it function normally if our attribute hacks are not being set.&lt;/p&gt;
 
&lt;p&gt;With the &lt;code&gt;round_maximum_value&lt;/code&gt; attribute accessor along with its default value added, now it is time to set the new maximum value when &lt;code&gt;round_maximum_value&lt;/code&gt; is true. Add the following method into &lt;code&gt;Gruff::Base&lt;/code&gt; of our &lt;code&gt;GruffHacks module&lt;/code&gt;&lt;/p&gt;
 
&lt;pre&gt;
&lt;span style="color:#CC7833"&gt;def &lt;/span&gt;&lt;span class="method"&gt;round_maximum_value=&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;value&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
  &lt;span style="color:#CC7833"&gt;if&lt;/span&gt; &lt;span class="ident"&gt;value&lt;/span&gt; &lt;span class="punct"&gt;==&lt;/span&gt; &lt;span style="color:#DA4939"&gt;true&lt;/span&gt;
    &lt;span class="ident"&gt;rounded_maximum_values&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="punct"&gt;[]&lt;/span&gt;
    
    &lt;span style="color:#BC9458"&gt;# loop through each bar group, grab the highest value, round it up and add&lt;/span&gt;
                &lt;span style="color:#BC9458"&gt;# to rounded_maximum_values array&lt;/span&gt;
    &lt;span class="ident"&gt;highest_val&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span style="color:#D0D0FF"&gt;@data&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;each&lt;/span&gt; &lt;span style="color:#CC7833"&gt;do&lt;/span&gt; &lt;span class="punct"&gt;|&lt;/span&gt;&lt;span class="ident"&gt;row&lt;/span&gt;&lt;span class="punct"&gt;|&lt;/span&gt;
      &lt;span class="ident"&gt;row_highest&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="ident"&gt;row&lt;/span&gt;&lt;span class="punct"&gt;[&lt;/span&gt;&lt;span class="number"&gt;1&lt;/span&gt;&lt;span class="punct"&gt;].&lt;/span&gt;&lt;span class="ident"&gt;sort&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;last&lt;/span&gt;
      &lt;span class="ident"&gt;round_to&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="number"&gt;10&lt;/span&gt; &lt;span class="punct"&gt;**&lt;/span&gt; &lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;row_highest&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;to_s&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;length&lt;/span&gt; &lt;span class="punct"&gt;-&lt;/span&gt; &lt;span class="number"&gt;1&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
      &lt;span class="ident"&gt;rounded_maximum_values&lt;/span&gt; &lt;span class="punct"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="ident"&gt;row_highest&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;roundup&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;round_to&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
    &lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
    &lt;span style="color:#BC9458"&gt;# Set maximum value based on highest rounded value&lt;/span&gt;
    &lt;span style="color:#D0D0FF"&gt;@maximum_value&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="ident"&gt;rounded_maximum_values&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;sort&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;last&lt;/span&gt;
  &lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;/pre&gt;
 
 
&lt;p&gt;Add this after the very last method of &lt;code&gt;GruffHacks&lt;/code&gt;, these two helpful methods are created by &lt;a href="http://pullmonkey.com/2008/1/31/rounding-to-the-nearest-number-in-ruby"&gt;Charlie from PullMonkey&lt;/a&gt; for rounding numbers up or down. Our &lt;code&gt;round_maximum_value&lt;/code&gt; method uses the roundup method.&lt;/p&gt;
 
&lt;pre&gt;
&lt;span style="color:#CC7833"&gt;class &lt;/span&gt;&lt;span class="class"&gt;Numeric&lt;/span&gt;
  &lt;span style="color:#CC7833"&gt;def &lt;/span&gt;&lt;span class="method"&gt;roundup&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;nearest&lt;/span&gt;&lt;span class="punct"&gt;=&lt;/span&gt;&lt;span class="number"&gt;10&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
    &lt;span style="color:#DA4939"&gt;self&lt;/span&gt; &lt;span class="punct"&gt;%&lt;/span&gt; &lt;span class="ident"&gt;nearest&lt;/span&gt; &lt;span class="punct"&gt;==&lt;/span&gt; &lt;span class="number"&gt;0&lt;/span&gt; &lt;span class="punct"&gt;?&lt;/span&gt; &lt;span style="color:#DA4939"&gt;self&lt;/span&gt; &lt;span class="punct"&gt;:&lt;/span&gt; &lt;span style="color:#DA4939"&gt;self&lt;/span&gt; &lt;span class="punct"&gt;+&lt;/span&gt; &lt;span class="ident"&gt;nearest&lt;/span&gt; &lt;span class="punct"&gt;-&lt;/span&gt; &lt;span class="punct"&gt;(&lt;/span&gt;&lt;span style="color:#DA4939"&gt;self&lt;/span&gt; &lt;span class="punct"&gt;%&lt;/span&gt; &lt;span class="ident"&gt;nearest&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
  &lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
  &lt;span style="color:#CC7833"&gt;def &lt;/span&gt;&lt;span class="method"&gt;rounddown&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;nearest&lt;/span&gt;&lt;span class="punct"&gt;=&lt;/span&gt;&lt;span class="number"&gt;10&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
    &lt;span style="color:#DA4939"&gt;self&lt;/span&gt; &lt;span class="punct"&gt;%&lt;/span&gt; &lt;span class="ident"&gt;nearest&lt;/span&gt; &lt;span class="punct"&gt;==&lt;/span&gt; &lt;span class="number"&gt;0&lt;/span&gt; &lt;span class="punct"&gt;?&lt;/span&gt; &lt;span style="color:#DA4939"&gt;self&lt;/span&gt; &lt;span class="punct"&gt;:&lt;/span&gt; &lt;span style="color:#DA4939"&gt;self&lt;/span&gt; &lt;span class="punct"&gt;-&lt;/span&gt; &lt;span class="punct"&gt;(&lt;/span&gt;&lt;span style="color:#DA4939"&gt;self&lt;/span&gt; &lt;span class="punct"&gt;%&lt;/span&gt; &lt;span class="ident"&gt;nearest&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
  &lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;/pre&gt;
 
 
&lt;p&gt;Before testing our new hack, lets include the &lt;code&gt;GruffHacks&lt;/code&gt; module into our &lt;code&gt;BarGraph&lt;/code&gt; class by adding &lt;code&gt;include GruffHacks&lt;/code&gt; after the Gruff &lt;code&gt;require&lt;/code&gt; statement. In our &lt;code&gt;initialize&lt;/code&gt; method remove the &lt;code&gt;g.maximum_value&lt;/code&gt; line and add in &lt;code&gt;g.round_maximum_value = true&lt;/code&gt;. If you try regenerating the same graph as before, the maximum value will now be 200 since 185 was rounded up.&lt;/p&gt;

&lt;p&gt;
&lt;img src="http://www2.killswitchcollective.com/articles/gruff/gem_hacks_graph3.jpg"&gt;
&lt;/p&gt;

&lt;h3&gt;Worth the Effort&lt;/h3&gt;

&lt;p&gt;By overriding the &lt;code&gt;initialize_ivars&lt;/code&gt; and adding the &lt;code&gt;round_maximum_value&lt;/code&gt; setter into &lt;code&gt;Gruff::Base&lt;/code&gt; we are able to automatically add some space between the highest value and the top of the graph by simply setting a single attribute to true. If there is a need to change this functionality later on, we won't have to dig through the gem source and hunt for modified lines. Instead we open up our &lt;code&gt;GruffHacks&lt;/code&gt; module and easily find our overridden methods. It is also much easier to add new methods, maybe more style options are needed for the bars or the labels need to be positioned in a different way. Whatever it is, by keeping these enhancements in a separate module makes sharing and updating hacks between applications easier.&lt;/p&gt;

&lt;p&gt;As projects grow, it becomes harder to keep up with all the details. Even if you are the only developer on the project, there is a good chance the inner workings will be forgotten months later. By breaking simple hacks into new modules we are minimizing the potential of possible bugs, and any bugs that do exist will be easier to find. Spending a few extra minutes to better organize your code and using the object oriented nature of a language such as Ruby will save time for you and your fellow colleagues.&lt;/p&gt;</description>
      <pubDate>Mon, 09 Mar 2009 09:26:05 -0700</pubDate>
      <link>http://www.killswitchcollective.com/articles/56</link>
      <guid>http://www.killswitchcollective.com/articles/56</guid>
    </item>
    <item>
      <title>DataMapper::AbstractAdapter 101</title>
      <category>Development</category>
      <description>&lt;p&gt;&lt;a href="http://datamapper.org"&gt;DataMapper&lt;/a&gt; is an &lt;a href="http://en.wikipedia.org/wiki/Object-relational_mapping"&gt;object relational mapper&lt;/a&gt; for &lt;a href="http://ruby-lang.org"&gt;ruby&lt;/a&gt; with an interface somewhat similar to &lt;a href="http://ar.rubyonrails.org"&gt;ActiveRecord&lt;/a&gt;'s. More than sprinkles on top of a generic SQL adapter, DataMapper is a &lt;a href="http://www.agiledata.org/essays/mappingObjects.html"&gt;design pattern&lt;/a&gt; for defining repositories and the models that love them. DataMapper differs from ActiveRecord and &lt;a href="http://api.rubyonrails.com/classes/ActiveResource/Base.html"&gt;ActiveResource&lt;/a&gt; in that models are encapsulated from repositories while queries and collections communicate between them. One of the most significant advantages to this approach lies in the ability to develop models separately from their repository.&lt;/p&gt;

&lt;p&gt;Over the next several articles we will use the &lt;a href="http://apiwiki.twitter.com/"&gt;Twitter API&lt;/a&gt; to explore how DataMapper expects custom adapters to work, executes CRUD requests, handles associations and works with multiple repositories. For now we will concentrate on the basics of DataMapper and querying users from the Twitter API service. Those comfortable with ruby or ActiveRecord should be able to follow along, however I strongly recommend spending time with DataMapper's fantastic &lt;a href="http://datamapper.org/doku.php?id=docs"&gt;documentation&lt;/a&gt; if you have not already.&lt;/p&gt;



&lt;h3&gt;Modeling Our Models&lt;/h3&gt;
&lt;p&gt;First things first, we should install DataMapper and define a model to represent a user account from the Twitter API. To do that we need to define a module using the &lt;code&gt;DataMapper::Resource&lt;/code&gt; module and describe the properties Twitter provides.&lt;/p&gt;

&lt;pre&gt;
gem install datamapper
&lt;/pre&gt;

&lt;pre&gt;
&lt;span class="ident"&gt;require&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;dm-core&lt;/span&gt;&lt;span class="punct"&gt;'&lt;/span&gt;

&lt;span style="color:#CC7833"&gt;class &lt;/span&gt;&lt;span class="class"&gt;User&lt;/span&gt;
  &lt;span class="ident"&gt;include&lt;/span&gt; &lt;span style="color:#DA4939"&gt;DataMapper&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span style="color:#DA4939"&gt;Resource&lt;/span&gt;

  &lt;span class="ident"&gt;property&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:id&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span style="color:#DA4939"&gt;Integer&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:field&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;user_id&lt;/span&gt;&lt;span class="punct"&gt;'&lt;/span&gt;
  &lt;span class="ident"&gt;property&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:name&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span style="color:#DA4939"&gt;String&lt;/span&gt;
  &lt;span class="ident"&gt;property&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:screen_name&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span style="color:#DA4939"&gt;String&lt;/span&gt;
  &lt;span class="ident"&gt;property&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:email&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span style="color:#DA4939"&gt;String&lt;/span&gt;
  &lt;span class="ident"&gt;property&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:location&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span style="color:#DA4939"&gt;String&lt;/span&gt;
  &lt;span class="ident"&gt;property&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:description&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span style="color:#DA4939"&gt;Text&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:lazy&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#DA4939"&gt;false&lt;/span&gt;
  &lt;span class="ident"&gt;property&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:profile_image_url&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span style="color:#DA4939"&gt;String&lt;/span&gt;
  &lt;span class="ident"&gt;property&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:url&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span style="color:#DA4939"&gt;String&lt;/span&gt;
  &lt;span class="ident"&gt;property&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:protected&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span style="color:#DA4939"&gt;Boolean&lt;/span&gt;
  &lt;span class="ident"&gt;property&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:followers_count&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span style="color:#DA4939"&gt;Integer&lt;/span&gt;
&lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;/pre&gt;

&lt;p&gt;DataMapper differs from the ActiveRecord family in that fields are defined in your model rather than being created from the repository's schema. This allows models to be built without an adapter making a connection and avoids the headaches of ActiveRecord-style migrations. Additionally you may specify the &lt;code&gt;:field&lt;/code&gt; name to be used for each property, allowing antiquated or confusing field names to be user friendly. Defining a model's properties also allows DataMapper to intelligently calculate by type which fields should be lazily loaded, which may also be customized by passing the &lt;code&gt;:lazy&lt;/code&gt; option a true or false value. In our User model we have set the lazy option to false as Twitter provides a user's description by default, and there is no use in wasting an API hit if we do not need to (Twitter limits API calls by the hour). A more in-depth description of DataMapper's &lt;a href="http://datamapper.org/doku.php?id=spotlight:laziness"&gt;lazy fields&lt;/a&gt; can be found in the documentation.&lt;/p&gt;

&lt;h3&gt;The Base Adapter&lt;/h3&gt;
&lt;p&gt;For the foundation of our Twitter adapter we need to catch any authentication options passed to the initialization as well as write a method for communicating with Twitter.&lt;/p&gt;

&lt;pre&gt;
&lt;span class="ident"&gt;require&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;cgi&lt;/span&gt;&lt;span class="punct"&gt;'&lt;/span&gt;
&lt;span class="ident"&gt;require&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;open-uri&lt;/span&gt;&lt;span class="punct"&gt;'&lt;/span&gt;
&lt;span class="ident"&gt;require&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;rubygems&lt;/span&gt;&lt;span class="punct"&gt;'&lt;/span&gt;
&lt;span class="ident"&gt;require&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;dm-core&lt;/span&gt;&lt;span class="punct"&gt;'&lt;/span&gt;
&lt;span class="ident"&gt;require&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;xmlsimple&lt;/span&gt;&lt;span class="punct"&gt;'&lt;/span&gt;

&lt;span style="color:#CC7833"&gt;module &lt;/span&gt;&lt;span class="module"&gt;DataMapper&lt;/span&gt;
  &lt;span style="color:#CC7833"&gt;module &lt;/span&gt;&lt;span class="module"&gt;Adapters&lt;/span&gt;
    &lt;span style="color:#CC7833"&gt;class &lt;/span&gt;&lt;span class="class"&gt;TwitterAdapter&lt;/span&gt;

      &lt;span style="color:#BC9458"&gt;# Clients can provide DataMapper with a URI string or hash of options when&lt;/span&gt;
      &lt;span style="color:#BC9458"&gt;# initializing an adapter. We can store these values and use them for each&lt;/span&gt;
      &lt;span style="color:#BC9458"&gt;# request to the Twitter service if the client provides them. Depending on&lt;/span&gt;
      &lt;span style="color:#BC9458"&gt;# your repository you may wish to verify authentication here rather than &lt;/span&gt;
      &lt;span style="color:#BC9458"&gt;# waiting for the initial request.&lt;/span&gt;
      &lt;span style="color:#BC9458"&gt;#&lt;/span&gt;
      &lt;span style="color:#BC9458"&gt;# name:: Name of the adapter&lt;/span&gt;
      &lt;span style="color:#BC9458"&gt;# uri_or_options:: A uri string, or hash of options used to initialize the adapter&lt;/span&gt;
      &lt;span style="color:#BC9458"&gt;#&lt;/span&gt;
      &lt;span style="color:#CC7833"&gt;def &lt;/span&gt;&lt;span class="method"&gt;initialize&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;name&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;uri_or_options&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
        &lt;span style="color:#BC9458"&gt;# don't forget to phone home!&lt;/span&gt;
        &lt;span style="color:#CC7833"&gt;super&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;name&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;uri_or_options&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;

        &lt;span style="color:#CC7833"&gt;case&lt;/span&gt; &lt;span class="ident"&gt;uri_or_options&lt;/span&gt;
        &lt;span style="color:#CC7833"&gt;when&lt;/span&gt; &lt;span style="color:#DA4939"&gt;Hash&lt;/span&gt;
          &lt;span class="ident"&gt;user&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="ident"&gt;uri_or_options&lt;/span&gt;&lt;span class="punct"&gt;[&lt;/span&gt;&lt;span style="color:#6E9CBE"&gt;:user&lt;/span&gt;&lt;span class="punct"&gt;]&lt;/span&gt; &lt;span class="punct"&gt;||&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;&lt;/span&gt;&lt;span class="punct"&gt;'&lt;/span&gt;
          &lt;span class="ident"&gt;pass&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="ident"&gt;uri_or_options&lt;/span&gt;&lt;span class="punct"&gt;[&lt;/span&gt;&lt;span style="color:#6E9CBE"&gt;:pass&lt;/span&gt;&lt;span class="punct"&gt;]&lt;/span&gt; &lt;span class="punct"&gt;||&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;&lt;/span&gt;&lt;span class="punct"&gt;'&lt;/span&gt;
          &lt;span style="color:#D0D0FF"&gt;@auth&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="ident"&gt;user&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;blank?&lt;/span&gt; &lt;span class="punct"&gt;||&lt;/span&gt; &lt;span class="ident"&gt;pass&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;blank?&lt;/span&gt; &lt;span class="punct"&gt;?&lt;/span&gt; &lt;span style="color:#DA4939"&gt;nil&lt;/span&gt; &lt;span class="punct"&gt;:&lt;/span&gt; &lt;span class="punct"&gt;[&lt;/span&gt;&lt;span class="ident"&gt;user&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;path&lt;/span&gt;&lt;span class="punct"&gt;]&lt;/span&gt;
        &lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
      &lt;span style="color:#CC7833"&gt;end&lt;/span&gt;

      &lt;span class="ident"&gt;private&lt;/span&gt;

      &lt;span style="color:#BC9458"&gt;# Requests a resource from the Twitter API. If the adapter was initialized&lt;/span&gt;
      &lt;span style="color:#BC9458"&gt;# with the :user and :pass options, they will be used to authenticate the request.&lt;/span&gt;
      &lt;span style="color:#BC9458"&gt;#&lt;/span&gt;
      &lt;span style="color:#BC9458"&gt;# method:: Path to follow the base URI 'http://twitter.com'&lt;/span&gt;
      &lt;span style="color:#BC9458"&gt;# params:: Hash of key/value pairs to be used as the query string&lt;/span&gt;
      &lt;span style="color:#BC9458"&gt;# returns:: XmlSimple representation of the response from Twitter&lt;/span&gt;
      &lt;span style="color:#BC9458"&gt;#&lt;/span&gt;
      &lt;span style="color:#CC7833"&gt;def &lt;/span&gt;&lt;span class="method"&gt;request&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;method&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;params&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="punct"&gt;{})&lt;/span&gt;
        &lt;span class="ident"&gt;uri&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;http://twitter.com/&lt;span class="expr"&gt;#{method}&lt;/span&gt;&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;
        &lt;span class="ident"&gt;options&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="punct"&gt;{&lt;/span&gt;&lt;span style="color:#6E9CBE"&gt;:http_basic_authentication&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#D0D0FF"&gt;@auth&lt;/span&gt;&lt;span class="punct"&gt;}&lt;/span&gt;

        &lt;span style="color:#CC7833"&gt;unless&lt;/span&gt; &lt;span class="ident"&gt;params&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;blank?&lt;/span&gt;
          &lt;span class="ident"&gt;query&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="ident"&gt;params&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;map&lt;/span&gt; &lt;span class="punct"&gt;{&lt;/span&gt; &lt;span class="punct"&gt;|&lt;/span&gt;&lt;span class="ident"&gt;k&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt;&lt;span class="ident"&gt;v&lt;/span&gt;&lt;span class="punct"&gt;|&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;%s=%s&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;&lt;/span&gt; &lt;span class="punct"&gt;%&lt;/span&gt; &lt;span class="punct"&gt;[&lt;/span&gt;&lt;span style="color:#DA4939"&gt;CGI&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;escape&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;k&lt;/span&gt;&lt;span class="punct"&gt;),&lt;/span&gt; &lt;span style="color:#DA4939"&gt;CGI&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;escape&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;v&lt;/span&gt;&lt;span class="punct"&gt;)]&lt;/span&gt; &lt;span class="punct"&gt;}&lt;/span&gt;
          &lt;span class="ident"&gt;uri&lt;/span&gt; &lt;span class="punct"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;?&lt;span class="expr"&gt;#{query.join('&amp;amp;')}&lt;/span&gt;&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;
        &lt;span style="color:#CC7833"&gt;end&lt;/span&gt;

        &lt;span class="ident"&gt;result&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="ident"&gt;open&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;uri&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;options&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
        &lt;span style="color:#CC7833"&gt;return&lt;/span&gt; &lt;span style="color:#DA4939"&gt;XmlSimple&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;xml_in&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;result&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;read&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="punct"&gt;{'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;ForceArray&lt;/span&gt;&lt;span class="punct"&gt;'&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#DA4939"&gt;false&lt;/span&gt;&lt;span class="punct"&gt;})&lt;/span&gt;
      &lt;span style="color:#CC7833"&gt;end&lt;/span&gt;

    &lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
  &lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;/pre&gt;

&lt;p&gt;Now we can go ahead and see if we are on the right path, we should get a &lt;code&gt;NotImplementedError&lt;/code&gt; when we try to perform any action with our repository.&lt;/p&gt;

&lt;pre&gt;
&lt;span style="color:#DA4939"&gt;DataMapper&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;setup&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span style="color:#6E9CBE"&gt;:default&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="punct"&gt;{&lt;/span&gt;
  &lt;span style="color:#6E9CBE"&gt;:adapter&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;twitter&lt;/span&gt;&lt;span class="punct"&gt;',&lt;/span&gt;
  &lt;span style="color:#6E9CBE"&gt;:user&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;kscollective&lt;/span&gt;&lt;span class="punct"&gt;',&lt;/span&gt;
  &lt;span style="color:#6E9CBE"&gt;:pass&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;snark snark&lt;/span&gt;&lt;span class="punct"&gt;'&lt;/span&gt;
&lt;span class="punct"&gt;})&lt;/span&gt;

&lt;span style="color:#DA4939"&gt;User&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;first&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span style="color:#6E9CBE"&gt;:screen_name&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;kscollective&lt;/span&gt;&lt;span class="punct"&gt;')&lt;/span&gt; &lt;span style="color:#BC9458"&gt;# =&amp;gt; NotImplementedError&lt;/span&gt;
&lt;/pre&gt;

&lt;h3&gt;Fetching Heffalumps And Woozles&lt;/h3&gt;
&lt;p&gt;In order to continue building our adapter we have to be able to understand the queries and collections DataMapper uses to mediate between the models and adapters. &lt;code&gt;DataMapper::AbstractAdapter&lt;/code&gt; defines &lt;code&gt;#read_one&lt;/code&gt; and &lt;code&gt;#read_many&lt;/code&gt;, both of which accept the query as the single parameter. The query object allows us to determine which model and fields to query along with any possible conditions to limit our results by. Queries also tell our adapter about any possible offsets, limits or ordering, but we will come back to that another day.&lt;/p&gt;

&lt;h4&gt;&lt;strong&gt;Query#model&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Each query belongs to a model which we can use to load and return instances fetched from our repository, and may also be used to customize repository requests by type. We can even use the model to DRY up our adapter and work with a single &lt;code&gt;#read&lt;/code&gt; method.&lt;/p&gt;

&lt;pre&gt;
&lt;span style="color:#CC7833"&gt;class &lt;/span&gt;&lt;span class="punct"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style="color:#DA4939"&gt;TwitterAdapter&lt;/span&gt;

  &lt;span style="color:#CC7833"&gt;def &lt;/span&gt;&lt;span class="method"&gt;read_one&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;query&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
    &lt;span class="ident"&gt;read&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;query&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;query&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;model&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span style="color:#DA4939"&gt;false&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
  &lt;span style="color:#CC7833"&gt;end&lt;/span&gt;

  &lt;span style="color:#CC7833"&gt;def &lt;/span&gt;&lt;span class="method"&gt;read_many&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;query&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
    &lt;span style="color:#DA4939"&gt;Collection&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;new&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;query&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt; &lt;span style="color:#CC7833"&gt;do&lt;/span&gt; &lt;span class="punct"&gt;|&lt;/span&gt;&lt;span class="ident"&gt;set&lt;/span&gt;&lt;span class="punct"&gt;|&lt;/span&gt;
                  &lt;span class="ident"&gt;read&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;query&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;set&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span style="color:#DA4939"&gt;true&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
    &lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
  &lt;span style="color:#CC7833"&gt;end&lt;/span&gt;

  &lt;span class="ident"&gt;private&lt;/span&gt;

  &lt;span style="color:#BC9458"&gt;# Each read has a query and returns a set, #read_one and #read_many should provide&lt;/span&gt;
  &lt;span style="color:#BC9458"&gt;# the set to load the results into. When called by #read_many nothing needs to be returned&lt;/span&gt;
  &lt;span style="color:#BC9458"&gt;# as the collection is filling itself, however we must return what ever object #read_one &lt;/span&gt;
  &lt;span style="color:#BC9458"&gt;# should return back to the client code.&lt;/span&gt;
  &lt;span style="color:#BC9458"&gt;#&lt;/span&gt;
  &lt;span style="color:#CC7833"&gt;def &lt;/span&gt;&lt;span class="method"&gt;read&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;query&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;set&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;many&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span style="color:#DA4939"&gt;true&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
    &lt;span style="color:#CC7833"&gt;raise&lt;/span&gt; &lt;span style="color:#DA4939"&gt;NotImplementedError&lt;/span&gt; &lt;span style="color:#BC9458"&gt;# to be filled in later&lt;/span&gt;
  &lt;span style="color:#CC7833"&gt;end&lt;/span&gt;

&lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;/pre&gt;

&lt;h4&gt;&lt;strong&gt;Query#fields&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Each query contains a subset of the model's properties specifying which fields to query as well as the order of our values when instantiating each result. Twitter does not provide a means to filter out fields so we will use &lt;code&gt;#fields&lt;/code&gt; only to order the values we pass to the model builders.&lt;/p&gt;

&lt;pre&gt;
&lt;span style="color:#CC7833"&gt;class &lt;/span&gt;&lt;span class="punct"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style="color:#DA4939"&gt;TwitterAdapter&lt;/span&gt;
  
  &lt;span style="color:#BC9458"&gt;# Map the values from item into an array of the same size and order as Query#fields&lt;/span&gt;
  &lt;span style="color:#BC9458"&gt;# [id, name, something_else, title] =&amp;gt; [1, 'Hello World', nil, 'Cats!']&lt;/span&gt;
  &lt;span style="color:#BC9458"&gt;#&lt;/span&gt;
  &lt;span style="color:#CC7833"&gt;def &lt;/span&gt;&lt;span class="method"&gt;parse_user_values&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;query&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;item&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
    &lt;span style="color:#CC7833"&gt;return&lt;/span&gt; &lt;span class="ident"&gt;query&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;fields&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;map&lt;/span&gt; &lt;span class="punct"&gt;{&lt;/span&gt; &lt;span class="punct"&gt;|&lt;/span&gt;&lt;span class="ident"&gt;f&lt;/span&gt;&lt;span class="punct"&gt;|&lt;/span&gt; &lt;span class="ident"&gt;item&lt;/span&gt;&lt;span class="punct"&gt;[&lt;/span&gt;&lt;span class="ident"&gt;f&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;field&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;to_s&lt;/span&gt;&lt;span class="punct"&gt;]&lt;/span&gt; &lt;span class="punct"&gt;}&lt;/span&gt;
  &lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;/pre&gt;

&lt;h4&gt;&lt;strong&gt;Query#conditions&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;The meat of most queries, conditions is an array of tuples containing the operator, property and value to be considered when executing the request. Each operator may be any of the standard 'SQL' operators (&lt;code&gt;:eql&lt;/code&gt;, &lt;code&gt;:in&lt;/code&gt;, &lt;code&gt;:gt[e]&lt;/code&gt;, &lt;code&gt;:lt[e]&lt;/code&gt;) and should be used to match the property with one or more values. Because Twitter only provides a method to query individual users by id, email or screen name we can write a method to create an array of key/value pair queries.&lt;/p&gt;

&lt;pre&gt;
&lt;span style="color:#CC7833"&gt;class &lt;/span&gt;&lt;span class="punct"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style="color:#DA4939"&gt;TwitterAdapter&lt;/span&gt;

  &lt;span style="color:#CC7833"&gt;def &lt;/span&gt;&lt;span class="method"&gt;generate_users_query&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;query&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
    &lt;span class="ident"&gt;result&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span style="color:#DA4939"&gt;Array&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;new&lt;/span&gt;
    &lt;span class="ident"&gt;fields&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="punct"&gt;['&lt;/span&gt;&lt;span style="color:#A5C261"&gt;user_id&lt;/span&gt;&lt;span class="punct"&gt;',&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;email&lt;/span&gt;&lt;span class="punct"&gt;',&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;screen_name&lt;/span&gt;&lt;span class="punct"&gt;']&lt;/span&gt;
    
    &lt;span class="ident"&gt;conditions&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="ident"&gt;query&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;conditions&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;select&lt;/span&gt; &lt;span style="color:#CC7833"&gt;do&lt;/span&gt; &lt;span class="punct"&gt;|&lt;/span&gt;&lt;span class="ident"&gt;condition&lt;/span&gt;&lt;span class="punct"&gt;|&lt;/span&gt;
      &lt;span class="ident"&gt;condition&lt;/span&gt;&lt;span class="punct"&gt;[&lt;/span&gt;&lt;span class="number"&gt;0&lt;/span&gt;&lt;span class="punct"&gt;]&lt;/span&gt; &lt;span class="punct"&gt;==&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:eql&lt;/span&gt; &lt;span style="color:#CC7833"&gt;and&lt;/span&gt; &lt;span class="ident"&gt;fields&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;include?&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;condition&lt;/span&gt;&lt;span class="punct"&gt;[&lt;/span&gt;&lt;span class="number"&gt;1&lt;/span&gt;&lt;span class="punct"&gt;].&lt;/span&gt;&lt;span class="ident"&gt;field&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
    &lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
    
    &lt;span style="color:#BC9458"&gt;# each item in conditions is a [operator, property, value] tuple&lt;/span&gt;
    &lt;span style="color:#CC7833"&gt;for&lt;/span&gt; &lt;span class="ident"&gt;operator&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;property&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;value&lt;/span&gt; &lt;span style="color:#CC7833"&gt;in&lt;/span&gt; &lt;span class="ident"&gt;conditions&lt;/span&gt;
      &lt;span style="color:#BC9458"&gt;# if an array, each value must be queried individually&lt;/span&gt;
      &lt;span class="punct"&gt;[&lt;/span&gt;&lt;span class="ident"&gt;value&lt;/span&gt;&lt;span class="punct"&gt;].&lt;/span&gt;&lt;span class="ident"&gt;flatten&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;each&lt;/span&gt; &lt;span class="punct"&gt;{&lt;/span&gt; &lt;span class="punct"&gt;|&lt;/span&gt;&lt;span class="ident"&gt;v&lt;/span&gt;&lt;span class="punct"&gt;|&lt;/span&gt; &lt;span class="ident"&gt;result&lt;/span&gt; &lt;span class="punct"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="punct"&gt;[&lt;/span&gt;&lt;span class="ident"&gt;property&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;field&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;v&lt;/span&gt;&lt;span class="punct"&gt;]&lt;/span&gt; &lt;span class="punct"&gt;}&lt;/span&gt;
    &lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
    
    &lt;span style="color:#CC7833"&gt;return&lt;/span&gt; &lt;span class="ident"&gt;result&lt;/span&gt;
  &lt;span style="color:#CC7833"&gt;end&lt;/span&gt;

&lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;/pre&gt;

&lt;h3&gt;Mind The Gap!&lt;/h3&gt;
&lt;p&gt;What used to be the hardest part in using third party resources has now become a matter of building the request, parsing the request and loading the model.&lt;/p&gt;

&lt;pre&gt;
&lt;span style="color:#CC7833"&gt;def &lt;/span&gt;&lt;span class="method"&gt;read&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;query&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;set&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;many&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span style="color:#DA4939"&gt;true&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
  &lt;span class="ident"&gt;queries&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="ident"&gt;generate_user_queries&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;query&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
  
  &lt;span style="color:#CC7833"&gt;for&lt;/span&gt; &lt;span class="ident"&gt;key&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;value&lt;/span&gt; &lt;span style="color:#CC7833"&gt;in&lt;/span&gt; &lt;span class="ident"&gt;queries&lt;/span&gt;
    &lt;span class="ident"&gt;twitter_user&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="ident"&gt;request&lt;/span&gt;&lt;span class="punct"&gt;(&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;users/show.xml&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;,&lt;/span&gt; &lt;span class="punct"&gt;{&lt;/span&gt;&lt;span class="ident"&gt;key&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="ident"&gt;value&lt;/span&gt;&lt;span class="punct"&gt;})&lt;/span&gt;
    &lt;span style="color:#CC7833"&gt;next&lt;/span&gt; &lt;span style="color:#CC7833"&gt;if&lt;/span&gt; &lt;span class="ident"&gt;twitter_user&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;blank?&lt;/span&gt; &lt;span style="color:#CC7833"&gt;or&lt;/span&gt; &lt;span class="ident"&gt;twitter_user&lt;/span&gt;&lt;span class="punct"&gt;['&lt;/span&gt;&lt;span style="color:#A5C261"&gt;screen_name&lt;/span&gt;&lt;span class="punct"&gt;'].&lt;/span&gt;&lt;span class="ident"&gt;blank?&lt;/span&gt;
    &lt;span class="ident"&gt;user_values&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="ident"&gt;parse_user_values&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;query&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;item&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
    &lt;span class="ident"&gt;many&lt;/span&gt; &lt;span class="punct"&gt;?&lt;/span&gt; &lt;span class="ident"&gt;set&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;load&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;user_values&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt; &lt;span class="punct"&gt;:&lt;/span&gt; &lt;span class="punct"&gt;(&lt;/span&gt;&lt;span style="color:#CC7833"&gt;break&lt;/span&gt; &lt;span class="ident"&gt;set&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;load&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;user_values&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;query&lt;/span&gt;&lt;span class="punct"&gt;))&lt;/span&gt;
  &lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
  
  &lt;span style="color:#CC7833"&gt;return&lt;/span&gt;
&lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;/pre&gt;

&lt;h3&gt;Recess!&lt;/h3&gt;
&lt;p&gt;At this point you should be able to query users from Twitter without parsing a single query string or XML response.&lt;/p&gt;

&lt;pre&gt;
&lt;span style="color:#DA4939"&gt;DataMapper&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;setup&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span style="color:#6E9CBE"&gt;:default&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="punct"&gt;{&lt;/span&gt;&lt;span style="color:#6E9CBE"&gt;:adapter&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;Twitter&lt;/span&gt;&lt;span class="punct"&gt;'})&lt;/span&gt;

&lt;span class="ident"&gt;user&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span style="color:#DA4939"&gt;User&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;first&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span style="color:#6E9CBE"&gt;:screen_name&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span style="color:#A5C261"&gt;KSCollective&lt;/span&gt;&lt;span class="punct"&gt;')&lt;/span&gt;
&lt;span class="ident"&gt;puts&lt;/span&gt; &lt;span class="ident"&gt;user&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;url&lt;/span&gt; &lt;span style="color:#BC9458"&gt;# =&amp;gt; http://www.killswitchcollective.com&lt;/span&gt;
&lt;/pre&gt;

&lt;p&gt;As you can see DataMapper can make working with third party repositories as automagical as ActiveRecord. DataMapper itself comes with adapters for the major databases with additional adapters available for CouchDB, Google Video and many others. In a few weeks we will continue building our Twitter adapter, leveraging the power of associations. Until then I highly recommend reviewing DataMappers documentation, the adapters included with the &lt;a href="http://github.com/datamapper/dm-core"&gt;dm-core gem&lt;/a&gt; and the base adapter &lt;a href="http://github.com/jkestr/dm-twitter/tree/Episode1"&gt;we have just built&lt;/a&gt;.&lt;/p&gt;
</description>
      <pubDate>Fri, 27 Feb 2009 12:20:20 -0800</pubDate>
      <link>http://www.killswitchcollective.com/articles/55</link>
      <guid>http://www.killswitchcollective.com/articles/55</guid>
    </item>
  </channel>
</rss>
