<?xml version="1.0" encoding="UTF-8"?>
<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>
    <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>
    <item>
      <title>Introducing FlexibleCSV</title>
      <category>Development</category>
      <description>&lt;blockquote style="padding-left: 0; text-align: center;"&gt;
  &lt;a href="http://github.com/chrisjpowers/flexible_csv/tree/master" target="_blank"&gt;Get FlexibleCSV from GitHub&lt;/a&gt;
&lt;/blockquote&gt;

&lt;h3&gt;A Challenge in Flexibility&lt;/h3&gt;

&lt;p&gt;As part of a contact management system we are building for a client, I encountered a unique challenge with allowing users to upload and import their contacts from CSV files. Usually this would not be a problem, except that in this case there was no standardization to what the header names would be or what order the columns were in. Because the FasterCSV gem relies on using the header names as access keys, this process was suddenly quite complicated.&lt;/p&gt;

&lt;p&gt;One solution would be to create a user interface that would display our database fields, their CSV columns and allow them to pair them up. For example, my database column is 'email' but their CSV column is 'Email Address', so they could mark those as equivalent. What would I do, however, for the users who have a "Full Name" column when I use 'first_name' and 'last_name' database columns? Suddenly the user interface could get very complicated and confusing.&lt;/p&gt;

&lt;h3&gt;Introducing FlexibleCSV&lt;/h3&gt;

&lt;p&gt;Instead, I developed FlexibleCSV, a gem that allows you to parse through a CSV file without knowing exactly what the headers are named. By providing a list of possible header names, you can access all the CSV columns with a uniform interface.&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;flexible_csv&lt;/span&gt;&lt;span class="punct"&gt;'&lt;/span&gt;

&lt;span style="color:#BC9458"&gt;# Arbitrary CSV data&lt;/span&gt;
&lt;span class="ident"&gt;csv_data1&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="punct"&gt;%Q{&lt;/span&gt;&lt;span style="color:#A5C261"&gt;Full Name, Email Address&lt;span class="escape"&gt;\n&lt;/span&gt;John Doe, john@doe.com&lt;/span&gt;&lt;span class="punct"&gt;}&lt;/span&gt;
&lt;span class="ident"&gt;csv_data2&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="punct"&gt;%Q{&lt;/span&gt;&lt;span style="color:#A5C261"&gt;Email, Name&lt;span class="escape"&gt;\n&lt;/span&gt;john@doe.com, John Doe&lt;/span&gt;&lt;span class="punct"&gt;}&lt;/span&gt;

&lt;span class="ident"&gt;parser&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span style="color:#DA4939"&gt;FlexibleCsv&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;new&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;csv&lt;/span&gt;&lt;span class="punct"&gt;|&lt;/span&gt;
  &lt;span class="ident"&gt;csv&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;column&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:full_name&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;Name&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;,&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;Full Name&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;,&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;Client Name&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;
  &lt;span class="ident"&gt;csv&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;column&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:email&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;Email&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;,&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;Email Address&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;parser&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;parse&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;csv_data1&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;puts&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;full_name&lt;/span&gt; &lt;span style="color:#BC9458"&gt;#=&amp;gt; 'John Doe'&lt;/span&gt;
  &lt;span class="ident"&gt;puts&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;email&lt;/span&gt;     &lt;span style="color:#BC9458"&gt;#=&amp;gt; 'john@doe.com'&lt;/span&gt;
&lt;span style="color:#CC7833"&gt;end&lt;/span&gt;

&lt;span class="ident"&gt;parser&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;parse&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;csv_data2&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;puts&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;full_name&lt;/span&gt; &lt;span style="color:#BC9458"&gt;#=&amp;gt; 'John Doe'&lt;/span&gt;
  &lt;span class="ident"&gt;puts&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;email&lt;/span&gt;     &lt;span style="color:#BC9458"&gt;#=&amp;gt; 'john@doe.com'&lt;/span&gt;
&lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;/pre&gt;

&lt;p&gt;Both data sets can now be accessed using the uniform &lt;code&gt;#full_name&lt;/code&gt; and &lt;code&gt;#email&lt;/code&gt; accessors.&lt;/p&gt;

&lt;h3&gt;Handling Complexity with Adapters&lt;/h3&gt;

&lt;p&gt;Going back to my original example, how would we handle CSV files that separated first and last names when my database uses the full name? Or vis versa? Though I considered adding this kind of functionality to the FlexibleCSV gem, ultimately I thought it best to keep that kind of logic in a separate adapter class. For example:&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;flexible_csv&lt;/span&gt;&lt;span class="punct"&gt;'&lt;/span&gt;

&lt;span style="color:#BC9458"&gt;# Arbitrary CSV data&lt;/span&gt;
&lt;span class="ident"&gt;csv_data1&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="punct"&gt;%Q{&lt;/span&gt;&lt;span style="color:#A5C261"&gt;Full Name&lt;span class="escape"&gt;\n&lt;/span&gt;John Doe&lt;/span&gt;&lt;span class="punct"&gt;}&lt;/span&gt;
&lt;span class="ident"&gt;csv_data2&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="punct"&gt;%Q{&lt;/span&gt;&lt;span style="color:#A5C261"&gt;First Name, Last Name&lt;span class="escape"&gt;\n&lt;/span&gt;John,Doe&lt;/span&gt;&lt;span class="punct"&gt;}&lt;/span&gt;

&lt;span class="ident"&gt;parser&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span style="color:#DA4939"&gt;FlexibleCsv&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;new&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;csv&lt;/span&gt;&lt;span class="punct"&gt;|&lt;/span&gt;
  &lt;span class="ident"&gt;csv&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;column&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:full_name&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;Name&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;,&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;Full Name&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;,&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;Client Name&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;
  &lt;span class="ident"&gt;csv&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;column&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:first_name&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;First Name&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;,&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;First&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;
  &lt;span class="ident"&gt;csv&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;column&lt;/span&gt; &lt;span style="color:#6E9CBE"&gt;:last_name&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;Last Name&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;,&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;Last&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;,&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color:#A5C261"&gt;Surname&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 style="color:#CC7833"&gt;class &lt;/span&gt;&lt;span class="class"&gt;CsvAdapter&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;row&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
    &lt;span style="color:#D0D0FF"&gt;@row&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="ident"&gt;row&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;full_name&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;full_name&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;&lt;span class="expr"&gt;#{row.first_name}&lt;/span&gt; &lt;span class="expr"&gt;#{row.last_name}&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 style="color:#CC7833"&gt;def &lt;/span&gt;&lt;span class="method"&gt;last_name&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;last_name&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;full_name&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;split&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;last&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;first_name&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;first_name&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;full_name&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;split&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;first&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;method_missing&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;method_name&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="punct"&gt;*&lt;/span&gt;&lt;span class="ident"&gt;args&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;send&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;method_name&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="punct"&gt;*&lt;/span&gt;&lt;span class="ident"&gt;args&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;parser&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;parse&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;csv_data1&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;ad_row&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span style="color:#DA4939"&gt;CsvAdapter&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;row&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;ad_row&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;full_name&lt;/span&gt;  &lt;span style="color:#BC9458"&gt;#=&amp;gt; 'John Doe'&lt;/span&gt;
  &lt;span class="ident"&gt;puts&lt;/span&gt; &lt;span class="ident"&gt;ad_row&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;first_name&lt;/span&gt; &lt;span style="color:#BC9458"&gt;#=&amp;gt; 'John'&lt;/span&gt;
  &lt;span class="ident"&gt;puts&lt;/span&gt; &lt;span class="ident"&gt;ad_row&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;last_name&lt;/span&gt;  &lt;span style="color:#BC9458"&gt;#=&amp;gt; 'Doe'&lt;/span&gt;
&lt;span style="color:#CC7833"&gt;end&lt;/span&gt;

&lt;span class="ident"&gt;parser&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;parse&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;csv_data2&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;ad_row&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span style="color:#DA4939"&gt;CsvAdapter&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;row&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;ad_row&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;full_name&lt;/span&gt;  &lt;span style="color:#BC9458"&gt;#=&amp;gt; 'John Doe'&lt;/span&gt;
  &lt;span class="ident"&gt;puts&lt;/span&gt; &lt;span class="ident"&gt;ad_row&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;first_name&lt;/span&gt; &lt;span style="color:#BC9458"&gt;#=&amp;gt; 'John'&lt;/span&gt;
  &lt;span class="ident"&gt;puts&lt;/span&gt; &lt;span class="ident"&gt;ad_row&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;last_name&lt;/span&gt;  &lt;span style="color:#BC9458"&gt;#=&amp;gt; 'Doe'&lt;/span&gt;
&lt;span style="color:#CC7833"&gt;end&lt;/span&gt;
&lt;/pre&gt;

&lt;p&gt;Using the adapter class, we can once again access each row of data from any CSV file with a uniform interface.&lt;/p&gt;

&lt;h3&gt;Go Get It!&lt;/h3&gt;

&lt;p&gt;To use the FlexibleCSV gem, you can follow or fork the project &lt;a href="http://github.com/chrisjpowers/flexible_csv/tree/master" target="_blank"&gt;on GitHub&lt;/a&gt; or just install the gem:&lt;/p&gt;

&lt;pre&gt;
sudo gem install chrisjpowers-flexible_csv
&lt;/pre&gt;</description>
      <pubDate>Thu, 19 Feb 2009 12:46:53 -0800</pubDate>
      <link>http://www.killswitchcollective.com/articles/54</link>
      <guid>http://www.killswitchcollective.com/articles/54</guid>
    </item>
    <item>
      <title>Transparency in the Social Realm</title>
      <category>Perspectives</category>
      <description>&lt;p&gt;There is a universal rule that people, companies and organizations must adhere to when participating in the Social Media Realm: Remain Transparent!&lt;/p&gt;

&lt;p&gt;All too often, those trying to maximize the all-too-appealing benefits of conducting social campaigns forget the transparency tenet. Truthfully, I understand the temptation, especially for marketers, advertisers and PR professionals.  People tend to respond more favorably to communications that they believe are altruistic or sincere, communications that do not possess ulterior motives.&lt;/p&gt;

&lt;p&gt;But transparency goes both ways and applies, if not even more so, to those who maintain and run these sites.  Again, I understand the temptation. These sites are cash cows (Facebook's total implied value is around $15 billion). While just about every industry and company is struggling in these financial times, the social media companies that run sites like Facebook and Hulu continue to add more users each month, which in turn means larger advertising premiums. However, the decision makers of these sites must be wary that the monetization of the communities they've created doesn't mitigate the user's trust.&lt;/p&gt;

&lt;p&gt;Case in point, let's look at what Hulu recently did to assuage user rage over the removal of a popular TV show on the site. Hulu, at the request of the content provider, FX Network, removed "It's Always Sunny In Philadelphia" from the site's downloads, and it did so without any form of notification to its users.  "Sunny" was one of the most popular shows on Hulu and its many fans began emailing, posting and tweeting away in protest.  Below is the letter posted by Jason Kilar, CEO of Hulu, to the site's users.&lt;/p&gt;

&lt;blockquote&gt;

&lt;p&gt;On January 9, we removed nearly 3 seasons of full episodes of "It's Always Sunny in Philadelphia." We did this at the request of the content owner. Despite Hulu's opinion and position on such content removals (which we share liberally with all of our content partners), these things do happen and will continue to happen on the Hulu service with regards to some television series. As power users of Hulu have seen, we've added a large amount of content to the library each month, and every once in a while we are required to remove some content as well.&lt;/p&gt;

&lt;p&gt;This note, however, is not about the fact that episodes of "It's Always Sunny in Philadelphia" were taken down. Rather, this note is to communicate to our users that we screwed up royally with regards to how we handled this specific content removal and to apologize for our lack of strong execution. We gave effectively no notice to our users that these "Sunny" episodes would be coming off the service. We handled this in precisely the opposite way that we should have. We believe that our users deserve the decency of a reasonable warning before content is taken down from the Hulu service. Please accept our apologies.&lt;/p&gt;

&lt;p&gt;Given the very reasonable user feedback that we have received on this topic (we read every twitter, email and post), we have just re-posted all of the episodes that we had previously removed. I'd like to point out to our users that the content owner in this case - FX Networks - was very quick to say yes to our request to give users reasonable advance notice here, despite the fact that it was the Hulu team that dropped the ball. We have re-posted all of the episodes in the interest of giving people advance notice before the episodes will be taken down two weeks from today. The episodes will be taken down on January 25, 2009. Unfortunately we do not have the permission to keep the specific episodes up on Hulu beyond that. We hope that the additional two weeks of availability will help to address some of the frustration that was felt over the past few days.&lt;/p&gt;

&lt;p&gt;The team at Hulu is doing our best to make lemonade out of lemons on this one, but it's not easy given how poorly we executed here. Please know that we will do our best to learn from this mistake such that the Hulu user experience benefits in other ways down the road.&lt;/p&gt;

&lt;p&gt;Sincerely,&lt;/p&gt;

&lt;p&gt;Jason Kilar, CEO, Hulu&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a textbook example of how to diffuse a social uprising. Kilar directly addressed Hulu's users, accepted blame for the way the situation was handled, offered a remedy and then promised to learn from this mistake (I may just disseminate this letter to politicians).&lt;/p&gt;

&lt;p&gt;Think about this example when your company or organization decides to go Social.  Remember that the user is wary and easily slighted.  Remain transparent and reap the rewards!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thanks to Muhammad Saleem (&lt;code&gt;@msaleem&lt;/code&gt; on Twitter) for writing a &lt;a href="http://mashable.com/2009/01/28/social-media-revolt/"&gt;great article&lt;/a&gt; on this subject.&lt;/em&gt;&lt;/p&gt;</description>
      <pubDate>Tue, 10 Feb 2009 06:52:12 -0800</pubDate>
      <link>http://www.killswitchcollective.com/articles/53</link>
      <guid>http://www.killswitchcollective.com/articles/53</guid>
    </item>
  </channel>
</rss>
