<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" xml:lang="en">

    <title type="text">Viget Extend : The Development Lab</title>

    <subtitle type="text">Development Blog: Viget Labs:</subtitle>
    <link rel="alternate" type="text/html" href="http://www.viget.com/extend/" />
    
    <updated>2009-07-01T21:19:24Z</updated>
    <rights>Copyright (c) 2009, Clinton R. Nixon</rights>
    <generator uri="http://expressionengine.com/" version="1.6.2">ExpressionEngine</generator>
    <id>tag:viget.com,2009:07:02</id>


    <link rel="self" href="http://feeds.feedburner.com/VigetExtend" type="application/atom+xml" /><entry>
      <title>Rails, Internationalization, and Tú</title>
      <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/VigetExtend/~3/f1b6WWd1x_U/" />
      <id>tag:viget.com,2009:extend/4.1616</id>
      <published>2009-07-02T07:00:00Z</published>
      <updated>2009-07-01T21:19:24Z</updated>
      <author>
                        <name>Clinton R. Nixon, Development Director</name>
                        <email>clinton.nixon@viget.com</email>
            <uri>http://www.viget.com/about/team/cnixon</uri>      </author>

      <category term="Ruby on Rails" scheme="http://www.viget.com/inspire/category/ruby_on_rails/" label="Ruby on Rails" />
      <category term="Tips and Tricks" scheme="http://www.viget.com/inspire/category/tips_and_tricks/" label="Tips and Tricks" />
      <content type="html">


                 &lt;p&gt;Internationalization (or I18n if you&amp;rsquo;re hep) is a bear of a problem to deal with in software development. I&amp;rsquo;ve had to work with a multi-lingual site in PHP before. It wasn&amp;rsquo;t painful, but it was constantly annoying. I just got a chance to work with internationalization in Rails for the first time, and I was pretty excited to see how it&amp;rsquo;s been handled.&lt;/p&gt;
&lt;p&gt;The good news that I found is that Rails&amp;rsquo; I18n support is pretty great. The bad news is that it is overly integrated with the rest of Rails, making changing it in isolation difficult.&lt;/p&gt;
                 &lt;h3 id="the_basics_of_i18n_in_rails"&gt;The Basics of I18n in Rails&lt;/h3&gt;
&lt;p&gt;There&amp;rsquo;s &lt;a href="http://guides.rubyonrails.org/i18n.html"&gt;a great Rails Guide on internationalization&lt;/a&gt; that&amp;rsquo;s required reading for anyone trying this at home. My brief nutshell of an explanation is that you create a Ruby or YAML file under &lt;code&gt;config/locales&lt;/code&gt; that has a set of hashes of hashes pointing to a specific key. An example would be &lt;code&gt;{ 'en' =&amp;gt; { 'activerecord' =&amp;gt; { 'errors' =&amp;gt; {'blank' =&amp;gt; 'is required' } } }&lt;/code&gt;. That sets the English translation for the key &lt;code&gt;activerecord.errors.blank&lt;/code&gt; to &amp;ldquo;is required.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;You can set your Rails application&amp;rsquo;s default locale like so:&lt;/p&gt;
&lt;pre name="code" class="ruby:nogutter"&gt;I18n.default_locale = :es&lt;/pre&gt;
&lt;p&gt;That will set it to Spanish. There&amp;rsquo;s not a definitive list of locales, so you could use &lt;code&gt;:lolcatz&lt;/code&gt; or &lt;code&gt;:morlock&lt;/code&gt; and it will work fine. You set the locale in a specific request like this:&lt;/p&gt;
&lt;pre name="code" class="ruby:nogutter"&gt;I18n.locale = :es&lt;/pre&gt;
&lt;p&gt;When you do that, all keys called via &lt;code&gt;I18n.translate&lt;/code&gt; (accessible as &lt;code&gt;t&lt;/code&gt; in views because of helpers) will return the Spanish translation.&lt;/p&gt;
&lt;p&gt;Overall, this is simple and easy. We found this to be helpful not only for Spanish translations, but setting English text as well. You can set the human-readable names of your ActiveRecord models and attributes through this facility, which is very nice for creating forms, especially with &lt;a href="http://github.com/justinfrench/formtastic/tree/master"&gt;Formtastic&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Extending I18n&lt;/h3&gt;
&lt;p&gt;Reading these YAML and Ruby files and holding the translations in memory is the job of the class &lt;code&gt;I18n::Backend::Simple&lt;/code&gt;, which is what&amp;rsquo;s written on the box: a simple backend for the I18n service in Rails. If you want translations to be editable by non-programmers, though, YAML and Ruby files won&amp;rsquo;t cut it.&lt;/p&gt;
&lt;p&gt;It is easy to extend &lt;code&gt;Simple&lt;/code&gt; or write a new backend. Once you&amp;rsquo;ve written one, you can tell `I18n` to use it:&lt;/p&gt;
&lt;pre name="code" class="ruby:nogutter"&gt;I18n.backend = I18n::Backend::SnippetBackend.new&lt;/pre&gt;
&lt;p&gt;I wanted to store my translations in the database and knew I&amp;rsquo;d only have English and Spanish translations forever. If you&amp;rsquo;re supporting multiple languages, you&amp;rsquo;ll obviously want to add another model to the following code. Here&amp;rsquo;s the backend I wrote:&lt;/p&gt;
&lt;pre name="code" class="ruby:nogutter"&gt;module I18n
  module Backend
    class SnippetBackend &amp;lt; Simple
      protected

      def init_translations
        load_translations(*I18n.load_path.flatten)
        load_translations_from_database
        @initialized = true
      end

      def load_translations_from_database
        data = { :en =&amp;gt; {}, :es =&amp;gt; {} }

        Snippet.all.each do |snippet|
          path = snippet.name.split(".")
          key = path.pop

          en = data[:en]
          es = data[:es]

          path.each do |group|
            en[group] ||= {}
            en = en[group]

            es[group] ||= {}
            es = es[group]
          end

          en[key] = snippet.english
          es[key] = snippet.spanish
        end

        data.each do |locale, d| 
          merge_translations(locale, d)
        end
      end
    end
  end
end
&lt;/pre&gt;
&lt;p id="where_my_problems_began"&gt;This code pulls all the translations I have stored as Snippet models out of the database and merges them into the translations already loaded from the YAML and Ruby files. The only piece of magic here is splitting the key's name. The period syntax to indicate nesting is used in the keys normally, so I continued it in my snippets. To make this match the data from the YAML and Ruby files, I have to transform it into nested hashes.&lt;/p&gt;
&lt;h3&gt;Where My Problems Began&lt;/h3&gt;
&lt;p&gt;I also used a plugin called &lt;a href="http://intraducibles.com/projects/typus"&gt;Typus&lt;/a&gt; for an administrative interface in this application. It&amp;rsquo;s a great plugin, and I can recommend it if you want a Django-style interface for editing content without a lot of fuss. &lt;/p&gt;
&lt;p&gt;When displaying a list of your editable models, it pluralizes those models&amp;rsquo; human names; for example, &lt;code&gt;EmergencyContact&lt;/code&gt; becomes &amp;ldquo;Emergency contact&amp;rdquo; and is then pluralized to &amp;ldquo;Emergency contacts.&amp;rdquo; This works perfectly with English. However, when using Spanish, I ended up with a slight problem:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt; EmergencyContact.typus_human_name
=&amp;gt; "Emergency contact"
&amp;gt;&amp;gt; EmergencyContact.typus_human_name.pluralize
=&amp;gt; "Emergency contacts"
&amp;gt;&amp;gt; I18n.locale = :es
=&amp;gt; :es
&amp;gt;&amp;gt; EmergencyContact.typus_human_name
=&amp;gt; "contacto de emergencia"
&amp;gt;&amp;gt; EmergencyContact.typus_human_name.pluralize
=&amp;gt; "contacto de emergencias"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;ldquo;Contacto de emergencias&amp;rdquo; means something like, &amp;ldquo;a contact for lots of emergencies,&amp;rdquo; which wasn&amp;rsquo;t my intention. What I need is &amp;ldquo;contactos de emergencia,&amp;rdquo; which would require Spanish pluralization rules. The Rails pluralization rules are based on English. That made sense to me, so I could go add pluralization rules for Spanish, just like you can add translations for Spanish, right?&lt;/p&gt;
&lt;p&gt;Unfortunately, that&amp;rsquo;s not true. Inflection rules in Rails don&amp;rsquo;t take multiple languages into account, so you can&amp;rsquo;t define new ones for a particular locale.&lt;/p&gt;
&lt;p&gt;Quixotically, I decided to fix this. Here&amp;rsquo;s the code I ended up with:&lt;/p&gt;
&lt;pre name="code" class="ruby:nogutter"&gt;module Inflector
   # Yields a singleton instance of Inflector::Inflections so you can specify additional
   # inflector rules.
   #
   # Example:
   #   ActiveSupport::Inflector.inflections do |inflect|
   #     inflect.uncountable "rails"
   #   end

    # Original method
   def inflections
     if block_given?
       yield Inflections.instance
     else
       Inflections.instance
     end
   end      

    # My crazy method
   def inflections(locale = nil)
     locale ||= I18n.locale
     locale_class = \
       if locale.to_s == I18n.default_locale.to_s
         ActiveSupport::Inflector::Inflections
       else
         ActiveSupport::Inflector.const_get("Inflections_#{locale}") rescue nil
       end

     if locale_class.nil?
       ActiveSupport::Inflector.module_eval %{
         class ActiveSupport::Inflector::Inflections_#{locale} &amp;lt; ActiveSupport::Inflector::Inflections
         end
       }
       locale_class = \
         ActiveSupport::Inflector.const_get("Inflections_#{locale}")
     end

     if block_given?
       yield locale_class.instance
     else
       locale_class.instance
     end
   end

   # Returns the plural form of the word in the string.
   #
   # Examples:
   #   "post".pluralize             # =&amp;gt; "posts"
   #   "octopus".pluralize          # =&amp;gt; "octopi"

    # Original method
    def pluralize(word)
     result = word.to_s.dup

     if word.empty? || inflections.uncountables.include?(result.downcase)
       result
     else
       inflections.plurals.each do |(rule, replacement)| 
         break if result.gsub!(rule, replacement)
       end
       result
     end
   end

    # My method
   def pluralize(word, locale = nil)
     locale ||= I18n.locale
     result = word.to_s.dup

     if word.empty? || inflections(locale).uncountables.include?(result.downcase)
       result
     else
       inflections(locale).plurals.each do |(rule, replacement)|
         if replacement.respond_to?(:call)
           break if result.gsub!(rule, &amp;amp;replacement)
         else
           break if result.gsub!(rule, replacement)
         end
       end
       result
     end
   end
end
&lt;/pre&gt;
&lt;p&gt;This code is pretty complex, but there's only three major changes to the original methods. First, I added the ability to pass in a locale to use on each method, and made the current locale the default one. Second, I changed the behavior of the &lt;code&gt;Inflections&lt;/code&gt; class. Instead of having one singleton class, I have a lot of singleton classes, each associated with a locale. These classes are created dynamically through a &lt;code&gt;module_eval&lt;/code&gt;. Lastly, I added the ability for inflection transformations to be lambdas instead of just strings.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re shaking your head and asking, &amp;ldquo;did he just create a new class for each different locale?&amp;rdquo;, I don&amp;rsquo;t blame you. It&amp;rsquo;s a little crazy. Believe it or not, this works magically, though! Here it is at work:&lt;/p&gt;
&lt;pre name="code" class="ruby:nogutter"&gt;ActiveSupport::Inflector.inflections(:es) do |inflect|
  inflect.plural /$/, 's'
  inflect.plural /([^aeiou&amp;eacute;])$/, '\1es'
  inflect.plural /([aeiou]s)$/, '\1'
  inflect.plural /z$/, 'ces'
  inflect.plural /&amp;aacute;([sn])$/, 'a\1es'
  inflect.plural /&amp;iacute;([sn])$/, 'i\1es'
  inflect.plural /&amp;oacute;([sn])$/, 'o\1es'
  inflect.plural /&amp;uacute;([sn])$/, 'u\1es'
  inflect.plural(/^(\w+)\s(.+)$/, lambda { |match|
                   head, tail = match.split(/\s+/, 2)
                   "#{head.pluralize} #{tail}"
                 })
end
&lt;/pre&gt;
&lt;p&gt;You'll notice that lambda in there: I added that ability so that I could transform the first word in a string instead of the last.&lt;/p&gt;
&lt;h3 id="my_problems_compound"&gt;My Problems Compound&lt;/h3&gt;
&lt;p&gt;The above works a little too magically, however. The &lt;code&gt;pluralize&lt;/code&gt; method is used everywhere, particularly in changing model class names to table names. As you can imagine, I started seeing errors like &amp;ldquo;No table found with name &lt;code&gt;emergency_contactes&lt;/code&gt;.&amp;rdquo; I spent a good day running into similar issues and fixing it with code like:&lt;/p&gt;
&lt;pre name="code" class="ruby:nogutter"&gt;# Create the name of a table like Rails does for models to table names. This method
# uses the +pluralize+ method on the last word in the string.
#
# Examples
#   "RawScaledScorer".tableize # =&amp;gt; "raw_scaled_scorers"
def tableize(class_name)
  pluralize(underscore(class_name), I18n.default_locale)
end
&lt;/pre&gt;
&lt;p&gt;Note that I called &lt;code&gt;pluralize&lt;/code&gt; explicitly specifying that I want to use the default locale, which would be English. This is a simple fix, but I ended up having to repeat it multiple places in &lt;code&gt;ActiveRecord&lt;/code&gt;, &lt;code&gt;ActiveSupport&lt;/code&gt;, and &lt;code&gt;ActionController&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="lessons_learned"&gt;Lessons Learned&lt;/h3&gt;
&lt;p&gt;First, even though this post&amp;rsquo;s talked about issues I had, the I18n support in Rails rocks, and I&amp;rsquo;ve found it to be a bit of a universal solvent for form issues, which I hope to talk about in another post. The ability to add new backends is nice, and the combination of my backend and Typus is hot.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve learned that Rails&amp;rsquo; inflections are used throughout the framework and trying to reuse them for automatic pluralization and singularization of non-English languages is hard. I think the results might be worth the effort, though. The translation system does have the ability to define singular and plural names for things, but they don&amp;rsquo;t work through &lt;code&gt;pluralize&lt;/code&gt;, so you&amp;rsquo;d have to re-write how you generate plural names for model objects. That&amp;rsquo;d be more local than my sweeping monkey-patches, however. Looking back over my code, I&amp;rsquo;ve decided that it is full of red flags, but not altogether misguided.&lt;/p&gt;
&lt;p&gt;How are you using Rails&amp;rsquo; I18n? Have you had problems? Are the above fixes terrible hacks or awesome hacks? Let&amp;rsquo;s talk in the comments.&lt;/p&gt;
&lt;p&gt;(You can see all the code from this post at &lt;a href="http://gist.github.com/138956"&gt;http://gist.github.com/138956&lt;/a&gt;.)&lt;/p&gt; 


      &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=f1b6WWd1x_U:mn5Hor4IqXw:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=f1b6WWd1x_U:mn5Hor4IqXw:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=f1b6WWd1x_U:mn5Hor4IqXw:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=f1b6WWd1x_U:mn5Hor4IqXw:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=f1b6WWd1x_U:mn5Hor4IqXw:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=f1b6WWd1x_U:mn5Hor4IqXw:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/VigetExtend/~4/f1b6WWd1x_U" height="1" width="1"/&gt;</content>
    <feedburner:origLink>http://www.viget.com/extend/rails-internationalization-and-tu/</feedburner:origLink></entry>

    <entry>
      <title>How (&amp; Why) to Run Autotest on your Mac</title>
      <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/VigetExtend/~3/gkY5zQQHAX0/" />
      <id>tag:viget.com,2009:extend/4.1602</id>
      <published>2009-06-19T12:09:00Z</published>
      <updated>2009-06-19T13:51:31Z</updated>
      <author>
                        <name>David Eisinger, Web Developer</name>
                        <email>david.eisinger@viget.com</email>
            <uri>http://www.viget.com/about/team/deisinger</uri>      </author>

      <category term="Tools of the Trade" scheme="http://www.viget.com/inspire/category/tools-of-the-trade/" label="Tools of the Trade" />
      <content type="html">


                 &lt;p&gt;If you aren&amp;rsquo;t using Autotest to develop your Ruby application, you&amp;rsquo;re missing out on effortless continuous testing. If you&amp;rsquo;d &lt;em&gt;like&lt;/em&gt; to be using Autotest, but can&amp;rsquo;t get it running properly, I&amp;rsquo;ll show you how to set it up.&lt;/p&gt;
&lt;p&gt;Autotest is a fantastic way to do &lt;abbr title="test-driven development"&gt;TDD&lt;/abbr&gt;/&lt;abbr title="behavior-driven development"&gt;BDD&lt;/abbr&gt;. Here&amp;rsquo;s a rundown of the benefits from the &lt;a href="http://www.zenspider.com/ZSS/Products/ZenTest/"&gt;project homepage&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Improves feedback by running tests continuously.&lt;/li&gt;
&lt;li&gt;Continually runs tests based on files you&amp;rsquo;ve changed.&lt;/li&gt;
&lt;li&gt;Get feedback as soon as you save. Keeps you in your editor
allowing you to get stuff done faster.&lt;/li&gt;
&lt;li&gt;Focuses on running previous failures until you&amp;rsquo;ve fixed them.&lt;/li&gt;
&lt;/ul&gt;
                 &lt;p&gt;Like any responsible Ruby citizen, Autotest changes radically every month or so. A few weeks ago, some enterprising developers released autotest-mac (now &lt;a href="http://www.bitcetera.com/en/techblog/2009/05/27/mac-friendly-autotest/"&gt;autotest-fsevent&lt;/a&gt;), which monitors code changes via native OS X system events rather than by polling the hard drive, increasing battery and disk life and improving performance. Here&amp;rsquo;s how get Autotest running on your Mac, current as of this morning:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install autotest:&lt;/p&gt;
&lt;pre name="code"&gt;gem install ZenTest
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Or, if you&amp;rsquo;ve already got an older version installed:&lt;/p&gt;
&lt;pre name="code"&gt;gem update ZenTest
gem cleanup ZenTest
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install autotest-rails:&lt;/p&gt;
&lt;pre name="code"&gt;gem install autotest-rails
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install autotest-fsevent:&lt;/p&gt;
&lt;pre name="code"&gt;gem install autotest-fsevent
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install autotest-growl:&lt;/p&gt;
&lt;pre name="code"&gt;gem install autotest-growl
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Make a &lt;code&gt;~/.autotest&lt;/code&gt; file, with the following:&lt;/p&gt;
&lt;pre name="code"&gt;require "autotest/growl"
require "autotest/fsevent"
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run &lt;code&gt;autotest&lt;/code&gt; in your app root.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Autotest is a fundamental part of my development workflow, and well worth the occasional setup headache; give it a shot and I think you&amp;rsquo;ll agree. These instructions should be enough to get you up and running, unless you&amp;rsquo;re reading this more than three weeks after it was published, in which case all. bets. are. off.&lt;/p&gt; 


      &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=gkY5zQQHAX0:oPttZfKij8U:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=gkY5zQQHAX0:oPttZfKij8U:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=gkY5zQQHAX0:oPttZfKij8U:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=gkY5zQQHAX0:oPttZfKij8U:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=gkY5zQQHAX0:oPttZfKij8U:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=gkY5zQQHAX0:oPttZfKij8U:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/VigetExtend/~4/gkY5zQQHAX0" height="1" width="1"/&gt;</content>
    <feedburner:origLink>http://www.viget.com/extend/how-why-to-run-autotest-on-your-mac/</feedburner:origLink></entry>

    <entry>
      <title>Unfuddle User Feedback</title>
      <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/VigetExtend/~3/ucVSMOQv4Ik/" />
      <id>tag:viget.com,2009:extend/4.1589</id>
      <published>2009-06-02T18:32:00Z</published>
      <updated>2009-06-03T13:35:04Z</updated>
      <author>
                        <name>David Eisinger, Web Developer</name>
                        <email>david.eisinger@viget.com</email>
            <uri>http://www.viget.com/about/team/deisinger</uri>      </author>

      <category term="Tips and Tricks" scheme="http://www.viget.com/inspire/category/tips_and_tricks/" label="Tips and Tricks" />
      <content type="html">


                 &lt;p&gt;Recently, we wanted a better system for managing feedback from &lt;a href="http://speakerrate.com/"&gt;SpeakerRate&lt;/a&gt; users. While we do receive some general site suggestions, most of the feedback we get involves discrete corrections to data (a speaker who has been entered into the system twice, for example). We started to create a simple admin interface for managing these requests, when we realized that the ticket tracking system we use internally, &lt;a href="http://unfuddle.com/"&gt;Unfuddle&lt;/a&gt;, already has all the features we need.&lt;/p&gt;
                 &lt;p&gt;Fortunately, Unfuddle has a full-featured &lt;a href="http://unfuddle.com/docs/api"&gt;API&lt;/a&gt;, so programatically creating tickets is simply a matter of adding &lt;a href="http://railstips.org/2008/7/29/it-s-an-httparty-and-everyone-is-invited"&gt;HTTParty&lt;/a&gt; to our &lt;code&gt;Feedback&lt;/code&gt; model:&lt;/p&gt;

&lt;pre name="code" class="ruby"&gt;class Feedback &amp;lt; ActiveRecord::Base
  include HTTParty
  base_uri "viget.unfuddle.com/projects/#{UNFUDDLE[:project]}

  validates_presence_of :description

  after_create :post_to_unfuddle, :if =&amp;gt; proc { Rails.env == "production" }

  def post_to_unfuddle
    self.class.post("/tickets.xml",
      :basic_auth =&amp;gt; UNFUDDLE[:auth],
      :query =&amp;gt; { :ticket =&amp;gt; ticket })
  end

  private

  def ticket
    returning(Hash.new) do |ticket|
      ticket[:summary]      = "#{self.topic}"
      ticket[:description]  = "#{self.name} (#{self.email}) - #{self.created_at}:\n\n#{self.description}"
      ticket[:milestone_id] = UNFUDDLE[:milestone]
      ticket[:priority]     = 3
    end
  end
end
&lt;/pre&gt;

&lt;p&gt;We store our Unfuddle configuration in &lt;code&gt;config/initializers/unfuddle.rb&lt;/code&gt;:&lt;/p&gt;

&lt;pre name="code" class="ruby"&gt;UNFUDDLE = {
  :project    =&amp;gt; 12345,
  :milestone  =&amp;gt; 12345,  # the 'feedback' milestone

  :auth =&amp;gt; {
    :username =&amp;gt; "username",
    :password =&amp;gt; "password"
  }
}
&lt;/pre&gt;

&lt;p&gt;Put your user feedback into Unfuddle, and you get all of its features: email notification, bulk ticket updates, commenting, file attachments, etc. This technique isn&amp;rsquo;t meant to replace customer-service oriented software like &lt;a href="http://getsatisfaction.com/"&gt;Get Satisfaction&lt;/a&gt; (we&amp;rsquo;re using both on SpeakerRate), and if you&amp;rsquo;re not already using a ticketing system to manage your project, this is probably overkill; something like &lt;a href="http://lighthouseapp.com/"&gt;Lighthouse&lt;/a&gt; or &lt;a href="http://github.com/blog/411-github-issue-tracker"&gt;GitHub Issues&lt;/a&gt; would better suit your needs, and both have APIs if you want to set up a similar system.&amp;nbsp;But for us here at Viget, who manage all aspects of our projects through a ticketing system, seeing actionable user feedback in the same place as the rest of our tasks has been extremely convenient.&lt;/p&gt; 


      &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=ucVSMOQv4Ik:zuA6bMvVLP4:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=ucVSMOQv4Ik:zuA6bMvVLP4:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=ucVSMOQv4Ik:zuA6bMvVLP4:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=ucVSMOQv4Ik:zuA6bMvVLP4:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=ucVSMOQv4Ik:zuA6bMvVLP4:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=ucVSMOQv4Ik:zuA6bMvVLP4:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/VigetExtend/~4/ucVSMOQv4Ik" height="1" width="1"/&gt;</content>
    <feedburner:origLink>http://www.viget.com/extend/unfuddle-user-feedback/</feedburner:origLink></entry>

    <entry>
      <title>Developer Day DC, Done!</title>
      <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/VigetExtend/~3/9oxiqcMghwg/" />
      <id>tag:viget.com,2009:extend/4.1586</id>
      <published>2009-06-01T14:00:00Z</published>
      <updated>2009-06-01T14:21:42Z</updated>
      <author>
                        <name>Ben Scofield, Technology Director</name>
                        <email>ben.scofield@viget.com</email>
            <uri>http://www.viget.com/about/team/bscofield</uri>      </author>

      <category term="Events" scheme="http://www.viget.com/inspire/category/events/" label="Events" />
      <content type="html">


                 &lt;p&gt;&lt;a href="http://developer-day.com/events/2009-dc.html"&gt;Developer Day DC&lt;/a&gt; took place this weekend, and by all accounts everyone had a great time. Bookended by &lt;a href="http://www.speakerrate.com/talks/1125-opening-keynote"&gt;Jay Virdy&amp;rsquo;s story about the rise of Summize&lt;/a&gt; (and its sale to Twitter) and &lt;a href="http://www.speakerrate.com/talks/1132-programming-in-interesting-times"&gt;Russ Olsen&amp;rsquo;s survey of emerging languages&lt;/a&gt;, we had great presentations on a wide variety of topics. Keeping to our technology-agnosticism, we had &lt;a href="http://www.speakerrate.com/talks/1126-give-your-sites-a-push-with-comet"&gt;JavaScript&lt;/a&gt; (&lt;a href="http://www.speakerrate.com/talks/1130-javascript-testing-in-rails-fast-headless-in-browser-pick-any-three"&gt;twice!&lt;/a&gt;), &lt;a href="http://www.speakerrate.com/talks/1128-from-paralysis-to-static-analysis-a-ruby-1-9-case-study-on-upgrading-rcov"&gt;Ruby&lt;/a&gt;, and &lt;a href="http://www.speakerrate.com/talks/1131-import-antigravity-making-life-more-enjoyable-with-python"&gt;Python&lt;/a&gt; on the schedule, as well as a &lt;a href="http://www.speakerrate.com/talks/1127-make-everyone-a-tester-natural-language-acceptance-testing"&gt;more general session on testing&lt;/a&gt; and a &lt;a href="http://www.speakerrate.com/talks/1129-beyond-thunderdome-browser-based-visual-programming-with-lily-and-the-monome"&gt;mind-expanding demonstration of the Monome and other fringe technologies&lt;/a&gt;. (If you couldn&amp;rsquo;t make it, &lt;a href="http://push.cx"&gt;Peter Harkins&lt;/a&gt; posted his notes &lt;a href="http://push.cx/2009/developer-day-notes"&gt;here&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;After conquering &lt;a href="http://developer-day.com/events/2009-durham.html"&gt;Durham&lt;/a&gt; and DC, we&amp;rsquo;ve decided to widen our range a bit, so if you&amp;rsquo;re eager for a fun, local, cheap conference of your very own, keep watching the &lt;a href="http://developer-day.com/"&gt;Developer Day site&lt;/a&gt;. Next up: Boston!&lt;/p&gt;
                  


      &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=9oxiqcMghwg:-65h0MKByWs:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=9oxiqcMghwg:-65h0MKByWs:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=9oxiqcMghwg:-65h0MKByWs:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=9oxiqcMghwg:-65h0MKByWs:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=9oxiqcMghwg:-65h0MKByWs:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=9oxiqcMghwg:-65h0MKByWs:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/VigetExtend/~4/9oxiqcMghwg" height="1" width="1"/&gt;</content>
    <feedburner:origLink>http://www.viget.com/extend/developer-day-dc-done/</feedburner:origLink></entry>

    <entry>
      <title>Reusing Contexts in Shoulda with Context Macros</title>
      <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/VigetExtend/~3/67CnO2UDoiQ/" />
      <id>tag:viget.com,2009:extend/4.1583</id>
      <published>2009-05-26T18:08:00Z</published>
      <updated>2009-05-27T19:44:48Z</updated>
      <author>
                        <name>Justin Marney, Web Developer</name>
                        <email>justin.marney@viget.com</email>
            <uri>http://www.viget.com/about/team/jmarney</uri>      </author>

      <category term="Tips and Tricks" scheme="http://www.viget.com/inspire/category/tips_and_tricks/" label="Tips and Tricks" />
      <category term="Tools of the Trade" scheme="http://www.viget.com/inspire/category/tools-of-the-trade/" label="Tools of the Trade" />
      <content type="html">


                 &lt;p&gt;Last month &lt;a href="http://www.viget.com/about/team/deisinger"&gt;David&lt;/a&gt; wrote up a good explanation of how to create &lt;a href="http://www.viget.com/extend/shoulda-macros-with-blocks/"&gt;shoulda macros with blocks&lt;/a&gt;.  Recently, I needed to reuse context behavior across a few different tests as well.  Out of curiosity, I went in search of a more idiomatic solution and was able to find &lt;a href="https://thoughtbot.lighthouseapp.com/projects/5807/tickets/122-patch-shared-behaviours"&gt;this ticket&lt;/a&gt; and its associated conversation.  From the discussion surrounding that ticket, I learned that you can use the merge_block method to create nestable context macros.&lt;/p&gt;

&lt;pre name="code" class="ruby:nogutter"&gt;class Test::Unit::TestCase
  def self.context_with_an_object(&amp;amp;block)
    context "With an object" do
      setup do
        @object = {:rock =&amp;gt; 'on'}
      end

      should "do something fantastic" do
        assert @object[:rock], 'on'
      end

      merge_block(&amp;amp;block) if block_given?
    end
  end
end
&lt;/pre&gt;&lt;p&gt;

You can use the context macro in one of your tests and it will accept a block as well as respect context nesting.

&lt;/p&gt;&lt;pre name="code" class="ruby:nogutter"&gt;class ModelTest &amp;lt; Test::Unit::TestCase
  context "A great and wonderous test" do
    setup do
      @thing = {:creature =&amp;gt; 'lagoon', :rock =&amp;gt; 'on'}
    end

    context_with_an_object do
      should "do something specific" do
        assert_equal @thing[:rock], @object[:rock]
      end

      context "And a friend" do
        setup do
          @friend = {:rock =&amp;gt; 'on'}
        end

        should "respect some nested context insanity" do
          assert_equal @friend[:rock], @thing[:rock]
        end
      end
    end
  end
end
&lt;/pre&gt;&lt;p&gt;

You can also create your context macros such that they accept arguments.

&lt;/p&gt;&lt;pre name="code" class="ruby:nogutter"&gt;class Test::Unit::TestCase
  def self.context_with_a_modified_object(modifier, &amp;amp;block)
    context "with a modified object" do
      setup do
        @object = {:mod =&amp;gt; modifier}
      end

      merge_block(&amp;amp;block) if block_given?
    end
  end
end
&lt;/pre&gt;&lt;p&gt;

Modifying the context behavior via an argument allows you to test a handful of edge cases without having to duplicate context code.

&lt;/p&gt;&lt;pre name="code" class="ruby:nogutter"&gt;class ModelTest &amp;lt; Test::Unit::TestCase
  context_with_a_modified_object("dance!") do
    should "be modified" do
      assert_equal @object[:mod], "dance!"
    end
  end
end
&lt;/pre&gt;&lt;p&gt;

I recommend using this technique sparingly and only to remove unnecessary duplication among your tests.  It is possible to hide too much context behavior behind these macros and end up with tests that are difficult to understand and maintain.  For contexts that aren't reused outside of a single file consider defining them at the top of the test file.&lt;/p&gt;
                  


      &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=67CnO2UDoiQ:dqbM4DhUVmY:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=67CnO2UDoiQ:dqbM4DhUVmY:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=67CnO2UDoiQ:dqbM4DhUVmY:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=67CnO2UDoiQ:dqbM4DhUVmY:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=67CnO2UDoiQ:dqbM4DhUVmY:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=67CnO2UDoiQ:dqbM4DhUVmY:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/VigetExtend/~4/67CnO2UDoiQ" height="1" width="1"/&gt;</content>
    <feedburner:origLink>http://www.viget.com/extend/reusing-contexts-in-shoulda-with-context-macros/</feedburner:origLink></entry>

    <entry>
      <title>Rack Support in Rails: Why It Matters</title>
      <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/VigetExtend/~3/mixwUtrPUUE/" />
      <id>tag:viget.com,2009:extend/4.1578</id>
      <published>2009-05-19T12:00:00Z</published>
      <updated>2009-05-18T18:52:15Z</updated>
      <author>
                        <name>Ben Scofield, Technology Director</name>
                        <email>ben.scofield@viget.com</email>
            <uri>http://www.viget.com/about/team/bscofield</uri>      </author>

      <category term="Ruby on Rails" scheme="http://www.viget.com/inspire/category/ruby_on_rails/" label="Ruby on Rails" />
      <content type="html">


                 &lt;p&gt;As I&amp;rsquo;ve mentioned &lt;a href="http://www.viget.com/extend/railsconf-2009-wrapup/"&gt;elsewhere&lt;/a&gt;, my &lt;a href="http://en.oreilly.com/rails2009/public/schedule/detail/7717"&gt;RailsConf session on Rack support in Rails&lt;/a&gt; went very well. I&amp;rsquo;m not planning on giving this talk again, however, so I thought I&amp;rsquo;d write a quick summary of it for people who are interested in a little more detail than the &lt;a href="http://www.slideshare.net/bscofield/and-the-greatest-of-these-is-rack-support"&gt;slides&lt;/a&gt; can provide.&lt;/p&gt; &lt;p&gt;My main motivation in giving this talk was to call attention to what I feel has been the most important change in Rails over the past year &amp;ndash; its move to become Rack-compatible. The initial work on this was done by &lt;a href="http://github.com/rails/rails/commit/06cb20708be13fbf736447aa0e5e6dd7d64c8b5d"&gt;Ezra&lt;/a&gt; almost a year ago, but it only made it into a stable release with 2.3. This delay meant that it stayed a bit under the radar, and even the addition of &lt;a href="http://weblog.rubyonrails.org/2008/12/17/introducing-rails-metal"&gt;Rails Metal&lt;/a&gt; didn&amp;rsquo;t really pull it into the spotlight. Then, in December of 2008, the announcement of the Merb-Rails merger pretty well decided the story of the year for Rails.&lt;/p&gt; &lt;h3&gt;So What?&lt;/h3&gt;&lt;p&gt;So, why is Rack support so important? Why should it be recognized as more vital to the success of Rails than the merger of the two most popular Ruby web frameworks? Rack is &lt;strong&gt;just&lt;/strong&gt; a common &lt;span class="caps"&gt;API&lt;/span&gt; for Ruby web frameworks, after all, and as such isn&amp;rsquo;t as flashy as, say, Merb&amp;rsquo;s router.&lt;/p&gt;&lt;p&gt;The answer is just that Rack &lt;strong&gt;is&lt;/strong&gt; a common &lt;span class="caps"&gt;API&lt;/span&gt; for Ruby web frameworks. It&amp;rsquo;s essentially the Ruby version of Python&amp;rsquo;s &lt;a href="http://en.wikipedia.org/wiki/Web_Server_Gateway_Interface"&gt;&lt;span class="caps"&gt;WSGI&lt;/span&gt;&lt;/a&gt;, specifiying a simple interface for handling &lt;span class="caps"&gt;HTTP&lt;/span&gt; requests in Ruby. The simplest Rack-compatible web application would look something like this:&lt;/p&gt;

&lt;pre name="code" class="ruby:nogutter"&gt;class SimpleRackApp
  def call(env)
    [       
      status,  # 200
      headers, # {"Content-Type" =&amp;gt; "text/html"}  
      body     # ["..."] 
    ] 
  end
end
&lt;/pre&gt;

&lt;p&gt;To be Rack-compatible, an application need only have a &lt;code&gt;call&lt;/code&gt; method that accepts a hash of environment data (including, for instance, the &lt;span class="caps"&gt;HTTP&lt;/span&gt; headers included in the request) and returns an array of an &lt;span class="caps"&gt;HTTP&lt;/span&gt; status, a hash of headers, and a body that responds to &lt;code&gt;.each&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;Standardizing the interface for web applications means that you can link them together in interesting and useful ways &amp;ndash; leading us to Rack middleware. Middlewares are just Rack-compatible applications that intercept and manipulate a request or response; a middleware could, for example, rescue any unhandled exceptions from your application and &lt;a href="http://rack.rubyforge.org/doc/classes/Rack/ShowExceptions.html"&gt;display a friendly error page&lt;/a&gt;.&lt;/p&gt; &lt;h3&gt;Rack in Rails&lt;/h3&gt;&lt;p&gt;As one of the results of the integration of Rack into Rails, many of the functions of ActionController have been refactored into middlewares, which means they can be required into non-Rails applications and used. As of today, these middlewares live in the ActionDispatch module, and include classes like ActionDispatch::Failsafe, ActionDispatch::ShowExceptions, and ActionDispatch::ParamsParser (among others). Normally, these will be automatically required when you run your Rails application through script/server or Passenger, but you can control their inclusion or replace them entirely with environment.rb; see the &lt;a href="http://guides.rubyonrails.org/rails_on_rack.html"&gt;Rack guide&lt;/a&gt; for more details.&lt;/p&gt;&lt;p&gt;Rails Metal is another of the results of the move to Rack. Metal provides a way to bypass some of the normal overhead of the Rails stack, allowing a developer to specify certain routes and code to execute when those routes are hit. Metal actions avoid the entirety of ActionController, and so are somewhat faster than standard Rails actions (though this performance benefit &lt;a href="http://weblog.rubyonrails.org/2008/12/20/performance-of-rails-metal"&gt;may not always be significant&lt;/a&gt;).&lt;/p&gt; &lt;h3&gt;Tools and Techniques&lt;/h3&gt;&lt;p&gt;All of this is just background, though; the real argument in favor of the importance of Rack is found when you look at what you can now do. In the session at RailsConf, I presented two such examples: &lt;a href="http://github.com/brynary/rack-bug/tree/master"&gt;Rack::Bug&lt;/a&gt; and &lt;a href="http://www.culann.com/2009/04/progressive-caching"&gt;progressive caching&lt;/a&gt;. Rack::Bug is a middleware, and &lt;a href="http://www.brynary.com/2009/4/22/rack-bug-debugging-toolbar-in-four-minutes"&gt;some great things&lt;/a&gt; have been written on it recently. Progressive caching is a technique that uses Rails Metal to respond to &lt;span class="caps"&gt;AJAX&lt;/span&gt; requests, making page caching a more generally useful strategy (it is also one of the other talks I give, and I&amp;rsquo;ve written on it in a &lt;a href="http://www.culann.com/2009/04/progressive-caching"&gt;couple&lt;/a&gt; of &lt;a href="http://www.viget.com/extend/progressive-caching-in-depth/"&gt;places&lt;/a&gt;).&lt;/p&gt;&lt;p&gt;During the final part of the section, I showed how to write a couple of middlewares that might be generally useful: Rack::Embiggener and Rack::Hoptoad. The latter is obviously a port of the hoptoad_notifier plugin to a Rack middleware (and indeed, this is a direction that the thoughtbot team is considering &amp;ndash; something I&amp;rsquo;m really looking forward to!). The former, however, may be a more interesting example. Rack::Embiggener is a middleware that intercepts the response from a Rack application, detects any TinyURLs embedded in it, and replaces them with the expanded &lt;span class="caps"&gt;URL&lt;/span&gt;. An obvious use-case for this is Twitter, where tweets could be stored with TinyURLs in place (remaining under the 140 character limit), while they are displayed with the full &lt;span class="caps"&gt;URL&lt;/span&gt; visible regardless of the length&lt;sup&gt;&lt;a href="#fn1"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt; 

&lt;pre name="code" class="ruby:nogutter"&gt;module Rack
  class Embiggener
    def initialize(app)
      @app = app
    end
    
    def call(env)
      status, headers, body = @app.call(env)
      headers.delete('Content-Length')
    
      response = Rack::Response.new(
        Rack::Embiggener.embiggen_urls(body),
        status,
        headers
      )
    
      response.finish
      return response.to_a
    end
        
    def self.embiggen_urls(original_body)
      new_body = []
      
      original_body.each { |line|
        tinys = line.scan(/(http:\/\/(?:www\.)?tinyurl\.com\/(.{6}))/)
        new_body &amp;lt;&amp;lt; tinys.uniq.inject(line) do |body, tiny|
          original_tiny = tiny[0]
          preview_url = "http://preview.tinyurl.com/#{tiny[1]}"
          response = Net::HTTP.get_response(URI.parse(preview_url))
          embiggened_url = response.body.match(/redirecturl" href="(.*)"&amp;gt;/)[1]
          body.gsub(original_tiny, embiggened_url)
        end
      }
      
      new_body
    end
  end 
end
&lt;/pre&gt;

&lt;p&gt;Walking through the code:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;code&gt;#initialize&lt;/code&gt; stores the Rack application object in a variable for later use.&lt;/li&gt; &lt;li&gt;&lt;code&gt;#call&lt;/code&gt; passes the request to the application stored in &lt;code&gt;@app&lt;/code&gt;, and records its responses. It then deletes the Content-Length header (since we&amp;rsquo;ll be enlarging some URLs), and creates a new Rack response with the same information, passing the original body through the &lt;code&gt;embiggen_urls&lt;/code&gt; method. The &lt;code&gt;response.finish&lt;/code&gt; call ensures that the appropriate headers (including a new Content-Length) are set.&lt;/li&gt; &lt;li&gt;&lt;code&gt;embiggen_urls&lt;/code&gt; accepts a body (that, per the Rack specification, responds to &lt;code&gt;.each&lt;/code&gt;), and (inefficiently) scans over it to find and replace the TinyURLs&lt;sup&gt;&lt;a href="#fn2"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;With this middleware, then, any Rack-compatible application &amp;ndash; be it Twitter, some other Rails 2.3 application, a Sinatra microapp, or even something written in Camping &amp;ndash; can replace TinyURLs with their expansions.&lt;/p&gt; &lt;p id="fn1"&gt;&lt;sup&gt;1&lt;/sup&gt; More interestingly, Twitter or another service might be able to replace the full &amp;lsquo;http:/tinyurl.com/[foo]&amp;rsquo; with just &amp;lsquo;[foo]&amp;rsquo; and a single-character flag of some sort, providing even more storage savings.&lt;/p&gt; &lt;p id="fn2"&gt;&lt;sup&gt;2&lt;/sup&gt; This code was just meant to inspire people by showing something that could be done, and isn&amp;rsquo;t meant to be particularly efficient or good. &lt;a href="http://gist.github.com/113533"&gt;Feel free to fork and improve it&lt;/a&gt;!&lt;/p&gt; &lt;h3&gt;Wrapup&lt;/h3&gt; &lt;p&gt;One of my favorite sayings comes from Socrates: &amp;ldquo;A list is not a definition.&amp;rdquo; My presentation (and this post) argue for the importance of Rack in Rails, but all they really do is give a list of examples of useful tools and techniques. My final point, then, is this: Rack compliance makes Rails a first-class member of the community of Ruby web frameworks (contrast that with &lt;a href="http://www.djangoproject.com/"&gt;Django&lt;/a&gt;, which &lt;a href="http://code.djangoproject.com/ticket/8928"&gt;implements a non-standard version of &lt;span class="caps"&gt;WSGI&lt;/span&gt;&lt;/a&gt;). This means that work done for Rails, such as the refactoring of parts of ActionController into ActionDispatch&amp;rsquo;s middlewares, benefits both Rails developers and users of other frameworks, and vice versa. Rack unites the fragmented Ruby web framework ecosystem, which means that no matter the community in which we work, our efforts improve the whole rather than just a single piece. As we&amp;rsquo;ve seen in open source time and again, the more people working on a set of problems, the better.&lt;/p&gt;
                  


      &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=mixwUtrPUUE:pkVpazoWfyE:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=mixwUtrPUUE:pkVpazoWfyE:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=mixwUtrPUUE:pkVpazoWfyE:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=mixwUtrPUUE:pkVpazoWfyE:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=mixwUtrPUUE:pkVpazoWfyE:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=mixwUtrPUUE:pkVpazoWfyE:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/VigetExtend/~4/mixwUtrPUUE" height="1" width="1"/&gt;</content>
    <feedburner:origLink>http://www.viget.com/extend/rack-support-in-rails-why-it-matters/</feedburner:origLink></entry>

    <entry>
      <title>Provisional and Repo Man: Automated Bootstrapping</title>
      <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/VigetExtend/~3/g3O3HGyF45w/" />
      <id>tag:viget.com,2009:extend/4.1575</id>
      <published>2009-05-13T15:10:00Z</published>
      <updated>2009-05-13T15:35:15Z</updated>
      <author>
                        <name>Mark Cornick, Web Developer</name>
                        <email>mark.cornick@viget.com</email>
            <uri>http://www.viget.com/about/team/mcornick</uri>      </author>

      <category term="General" scheme="http://www.viget.com/inspire/category/general/" label="General" />
      <content type="html">


                 &lt;p&gt;If &lt;a href="http://crnixon.org/articles/2008/09/03/relentless-automation.html"&gt;we&amp;rsquo;ve said it once&lt;/a&gt;, &lt;a href="http://www.viget.com/extend/scratching-an-itch-the-rails-model-generator-with-factory-girl-support/"&gt;we&amp;rsquo;ve said it many times&lt;/a&gt;: Don&amp;rsquo;t Repeat Yourself.&lt;/p&gt;
                 &lt;p&gt;Ahem. As the kids say, &amp;ldquo;do-over!&amp;rdquo;&lt;/p&gt; &lt;p&gt;Really, what we&amp;rsquo;ve been repeating is &amp;ldquo;Don&amp;rsquo;t repeat yourself without automating the repetitive task.&amp;rdquo; We try to automate everything we do more than once, and as a busy agency, one of the things we do more than once is bootstrap new Rails applications. And we do pretty much the same things every time: we create a new Rails application, we install a few favorite gems and plugins, and we check the lot into a freshly-created Git or Subversion repository. It&amp;rsquo;s ripe for automation, not just because we repeatedly do it, but because we &lt;em&gt;want&lt;/em&gt; to repeat it. We want the same setup every time, so that we know we&amp;rsquo;re starting from the same base on every project.&lt;/p&gt; &lt;p&gt;&lt;a href="http://www.ryandaigle.com/articles/2008/12/11/what-s-new-in-edge-rails-application-generators"&gt;Rails application templates&lt;/a&gt; arrived as part of Rails 2.3, and they go a long way towards automating the new-Rails-app process. Spend a little time crafting your ideal template, and you can use it to stamp out identical Rails skeletons, ready for development. If you write more than one or two Rails applications a year, and you haven&amp;rsquo;t written a template yet, you really should.&lt;/p&gt; &lt;p&gt;But what about the other step we mentioned above - setting up our new SCM repository? Surely we could automate that process as well. Several of the popular code-hosting sites have APIs to manipulate repositories. Why not programatically set up our SCM and check in the fruits of our templating labor?&lt;/p&gt; &lt;p&gt;That&amp;rsquo;s why we wrote &lt;a href="http://github.com/vigetlabs/provisional/tree/master"&gt;Provisional&lt;/a&gt;. It picks up where the Rails application generator leaves off, setting up a Git or Subversion repository and putting your code in. It&amp;rsquo;s one less thing to do by hand, and that&amp;rsquo;s always a good thing.&lt;/p&gt; &lt;p&gt;Let&amp;rsquo;s take a peek. Go ahead and &lt;code&gt;gem install provisional&lt;/code&gt;, then run &lt;code&gt;provisional --help&lt;/code&gt; to check out the options:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;$ provisional --help
Options:
      --name, -n &amp;lt;s&amp;gt;:   Name of the project
       --scm, -s &amp;lt;s&amp;gt;:   SCM to use
  --template, -t &amp;lt;s&amp;gt;:   Rails template to use
    --domain, -d &amp;lt;s&amp;gt;:   Domain (for some SCMs, see documentation)
  --username, -u &amp;lt;s&amp;gt;:   Username (for some SCMs, see documentation)
  --password, -p &amp;lt;s&amp;gt;:   Password (for some SCMs, see documentation)
        --id, -i &amp;lt;s&amp;gt;:   Id (for some SCMs, see documentation)
    --config, -c &amp;lt;s&amp;gt;:   Config file (optional)
          --help, -h:   Show this message
&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;The three most important options are at the top: a name for your application; an SCM system to use; and the Rails template to apply. (The other options are used for identifying, and authenticating to, code hosting services. See the README for more information on how to use these.)&lt;/p&gt; &lt;p&gt;By default, Provisional will create a Git repository on your local system, ready to push out to wherever you&amp;rsquo;d like. If you&amp;rsquo;re hosting on &lt;a href="http://github.com/"&gt;GitHub&lt;/a&gt;, you can specify that, and Provisional will create a new repository on GitHub, set it as origin, and push out to master. Likewise for &lt;a href="http://unfuddle.com/"&gt;Unfuddle&lt;/a&gt;, a project-management site that can host both Git and SVN repositories; Provisional supports them both. (Have you got your own Git or SVN server inside your firewall? I&amp;rsquo;ll come back to you later.)&lt;/p&gt; &lt;p&gt;You can specify any Rails application template you like. As with running the generator directly, you can use a local template file, or a URL to a remote file. If you don&amp;rsquo;t choose, you&amp;rsquo;ll get the default template we use at Viget, which is a busy template indeed; it:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;freezes Rails from your currently installed gems&lt;/li&gt; &lt;li&gt;installs one of our favorite testing stacks: &lt;a href="http://www.thoughtbot.com/projects/shoulda"&gt;Shoulda&lt;/a&gt;, &lt;a href="http://www.thoughtbot.com/projects/factory_girl"&gt;Factory Girl&lt;/a&gt; and &lt;a href="http://mocha.rubyforge.org/"&gt;Mocha&lt;/a&gt;&lt;/li&gt; &lt;li&gt;installs the &lt;a href="http://hoptoadapp.com/"&gt;Hoptoad&lt;/a&gt; notifier, so we can keep a handle on exceptions&lt;/li&gt; &lt;li&gt;installs &lt;a href="http://ennerchi.com/projects/jrails"&gt;jRails&lt;/a&gt;, since we roll with jQuery these days&lt;/li&gt; &lt;li&gt;installs our &lt;a href="http://www.viget.com/extend/scratching-an-itch-the-rails-model-generator-with-factory-girl-support/"&gt;model generator with Factory Girl support&lt;/a&gt;&lt;/li&gt; &lt;li&gt;installs our &lt;a href="http://github.com/vigetlabs/viget_deployment"&gt;standard Capistrano setup&lt;/a&gt;&lt;/li&gt; &lt;li&gt;installs our &lt;a href="http://github.com/vigetlabs/vl_cruise_control"&gt;coverage enforcement plugin&lt;/a&gt;&lt;/li&gt; &lt;li&gt;cleans up various things we don&amp;rsquo;t use, like the default &lt;code&gt;public/index.html&lt;/code&gt; and prototype/script.aculo.us&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;(You&amp;rsquo;ll find it under &lt;code&gt;lib/provisional/templates/viget.rb&lt;/code&gt; in the gem, if you&amp;rsquo;d like to fork it and write your own.)&lt;/p&gt; &lt;p&gt;And that&amp;rsquo;s pretty much it. There are some other things it could do, but currently doesn&amp;rsquo;t. It won&amp;rsquo;t set up your production server for you; &lt;a href="http://github.com/crafterm/sprinkle/tree/master"&gt;Sprinkle&lt;/a&gt;&amp;rsquo;s got that market covered. It&amp;rsquo;d be nice if it set up continuous integration, but we haven&amp;rsquo;t settled on our one true CI server yet. Maybe that&amp;rsquo;ll be a future add-on. For now, that&amp;rsquo;s it for Provisional; go forth and bootstrap!&lt;/p&gt; &lt;p&gt;&amp;ldquo;Hey, wait!&amp;rdquo; you say. &amp;ldquo;I&amp;rsquo;m hosting my own Git or SVN server inside my firewall. You said you&amp;rsquo;d come back to me later.&amp;rdquo; We haven&amp;rsquo;t forgotten you. Actually, at Viget, we&amp;rsquo;ve had Git and SVN inside our firewall for years. Since we use Provisional for our own projects, we need to handle this kind of setup. Before Provisional, we had a couple of shell scripts on our Git/SVN server that we used to create new, empty repositories with access for everyone in the company. They were handy, and worked well, but weren&amp;rsquo;t that easy to automate.&lt;/p&gt; &lt;p&gt;So we wrote &lt;a href="http://github.com/vigetlabs/repo_man"&gt;Repo Man&lt;/a&gt;, a very simple (perhaps too simple) Rails application to create repositories. Originally, we had written Repo Man to let our design staff create repositories for non-code purposes. All we had to do is add some controller logic to respond to Active Resource calls, and it became API-capable just like the code hosting sites. And with that, the &lt;a href="http://github.com/vigetlabs/provisional-repoman"&gt;Provisional-Repo Man bridge&lt;/a&gt; was a snap to create.&lt;/p&gt; &lt;p&gt;Repo Man can work with your internal SCM setup if it runs on a Linux or UNIX-like system. You&amp;rsquo;ll just need to create some scripts like the ones we wrote in the beginning (which are provided with Repo Man as examples.) And you&amp;rsquo;ll probably want to add some support for your local authentication scheme. But Repo Man will get you pretty close to what you need to work with Provisional.&lt;/p&gt; &lt;p&gt;And there you have it - a complete system for automating those crucial early bootstrapping tests. It&amp;rsquo;s just one more step in our ongoing quest to automate everything we do more than once. (Except writing these blog posts, of course. That still requires a human touch&amp;hellip;)&lt;/p&gt; 


      &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=g3O3HGyF45w:jXT2pc-H9fI:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=g3O3HGyF45w:jXT2pc-H9fI:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=g3O3HGyF45w:jXT2pc-H9fI:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=g3O3HGyF45w:jXT2pc-H9fI:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=g3O3HGyF45w:jXT2pc-H9fI:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=g3O3HGyF45w:jXT2pc-H9fI:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/VigetExtend/~4/g3O3HGyF45w" height="1" width="1"/&gt;</content>
    <feedburner:origLink>http://www.viget.com/extend/provisional-and-repo-man-automated-bootstrapping/</feedburner:origLink></entry>

    <entry>
      <title>JSConf 2009 Recap: Javascript at the Edge</title>
      <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/VigetExtend/~3/FPhe1gdqBRo/" />
      <id>tag:viget.com,2009:extend/4.1568</id>
      <published>2009-05-12T13:00:00Z</published>
      <updated>2009-05-12T13:47:13Z</updated>
      <author>
                        <name>Brian Landau, Web Developer</name>
                        <email>brian.landau@viget.com</email>
                  </author>

      <category term="Events" scheme="http://www.viget.com/inspire/category/events/" label="Events" />
      <content type="html">


                 &lt;p&gt;Two weeks ago, I spent an amazingly full two days at &lt;a href="http://jsconf2009.com/"&gt;JSConf 2009&lt;/a&gt; getting my mind blown by some JavaScript developers who are pushing the boundaries of the language&lt;a href="http://jsconf2009.com/"&gt;&lt;/a&gt;. Thanks to having so many great people there, we all had a great time discussing ideas and learning from each other. Having a smaller number of attendees really enhanced this aspect of the conference and allowed people to discuss the ideas more deeply.&lt;/p&gt; &lt;p&gt;Beside the discussions of newer Javascript Frameworks (like Cappuccino/Objective-J and SproutCore), the main theme that I think threaded its way through a lot of the conference was using javascript outside the browser. Also, everyone I met was fired up about Javascript and exploring what new things we can do with it.&lt;/p&gt;
                 &lt;h3 id="couchdb_to_the_edge"&gt;CouchDB to the Edge&lt;/h3&gt; &lt;p&gt;The technology that probably had the biggest spotlight on it was &lt;a href="http://couchdb.apache.org/"&gt;CouchDB&lt;/a&gt;. &lt;a href="http://jchrisa.net/"&gt;Chris Anderson&lt;/a&gt; &amp;amp; &lt;a href="http://jan.prima.de/plok/"&gt;Jan Lehnardt&lt;/a&gt; gave a stellar talk on how CouchDB works and what makes it special. As they expressed, CouchDB has 3 big selling points: it's Simple, Scalable, and Distributed. The first point is mainly a result of using JSON, REST/HTTP, and Javascript as the basis for interacting with the DB and building apps on top of it. The last two are mostly due to it being built in Erlang, and using Map-Reduce in the &amp;ldquo;views.&amp;rdquo; They also talked heavily about the ease of creating &lt;a href="http://github.com/jchris/couchapp/tree/master"&gt;CouchApps&lt;/a&gt;, which are web apps that are stored and run on CouchDB. The fact that CouchApps have filesystem mapping and that CouchDB is RESTful allows for very simple cloning of CouchApps. CouchApps appear to be extremely scalable and simple to create, making them the item from the conference I&amp;rsquo;m most interested in exploring more deeply.&lt;/p&gt; &lt;h3 id="the_real_time_web_with_xmpp"&gt;The Real Time Web with XMPP&lt;/h3&gt; &lt;p&gt;The other technology I was excited about was &lt;a href="http://code.stanziq.com/strophe/"&gt;Strophe&lt;/a&gt;, a Javascript library that allows you to do XMPP calls in the browser. Strophe provides a very simple API for doing &lt;a href="http://en.wikipedia.org/wiki/BOSH"&gt;XMPP over BOSH&lt;/a&gt; communications in the browser. &lt;a href="http://metajack.im/"&gt;Jack Moffitt&lt;/a&gt; explained the basics of how the library implements this and then went on to give some examples of just how easy using the framework is.&lt;br /&gt; The most obvious use of this is adding chat capabilities to any page. This can, of course, be specific to the page, specific to the site, or somewhere in between. The less traditional use for it is as a real-time communication channel between the web page and the server. Using this in conjunction with &lt;a href="http://nutrun.com/weblog/distributed-programming-with-jabber-and-eventmachine/" title="nutrun &amp;raquo; Distributed programming with Jabber and EventMachine"&gt;some of the methodologies discussed by George Malamidis&lt;/a&gt;, you could easily have this type of client-server interaction.&lt;/p&gt; &lt;h3 id="interoperable_javascript_based_client_server_web_applications"&gt;Interoperable Javascript-Based Client/Server Web Applications&lt;/h3&gt; &lt;p&gt;Kris Zyp of Sitepen presented on &lt;a href="http://www.persvr.org/"&gt;Persevere&lt;/a&gt;, an object storage server based REST/HTTP and JSON. It allows you to take advantage of a number of cutting edge JSON technologies including &lt;a href="http://www.json.com/2007/10/19/json-referencing-proposal-and-library/"&gt;JSON referencing&lt;/a&gt;, &lt;a href="http://www.sitepen.com/blog/2008/07/16/jsonquery-data-querying-beyond-jsonpath/"&gt;JSONQuery&lt;/a&gt;/&lt;a href="http://goessner.net/articles/JsonPath/"&gt;JSONPath&lt;/a&gt;, and &lt;a href="http://groups.google.com/group/json-schema"&gt;JSON Schema&lt;/a&gt;. They&amp;rsquo;ve worked heavily to offer tight integration with &lt;a href="http://dojotoolkit.org/book/dojo-book-0-9/part-3-programmatic-dijit-and-dojo/data-retrieval-dojo-data-0" title="Using dojo.data | The Dojo Toolkit"&gt;Dojo&amp;rsquo;s Data&lt;/a&gt; &lt;a href="http://docs.dojocampus.org/dojox/data" title="dojox/data - DojoCampus - Docs"&gt;JsonRestStore functionality&lt;/a&gt;, although it can also work with other frameworks (like &lt;a href="http://thoughtbot.com/projects/jester"&gt;Jester&lt;/a&gt;). It also opens up the possibility to work with other data stores on the server side, including MySQL and CouchDB, opening up all sorts of interesting possibilities. What I found most interesting here was the use of JSON referencing to handle relationships between objects. This is a traditional problem with JSON document based storage in my opinion, but this offers a solution to that.&lt;/p&gt; &lt;h3 id="jazzrecord"&gt;JazzRecord&lt;/h3&gt; &lt;p&gt;&lt;a href="http://www.thynctank.com/"&gt;Nick Carter&lt;/a&gt; gave an excellent hands-on demonstration of the &lt;a href="http://www.jazzrecord.org/" title="JazzRecord is ActiveRecord for JavaScript (ORM)"&gt;Javascript ORM JazzRecord&lt;/a&gt;. It mirrors ActiveRecord&amp;rsquo;s API and syntax as closely as it can, offering up the ability to store data client side via Gears and other client-side data persistence options. It&amp;rsquo;s a really great start to offering a single way of interacting with data with-in a Rails app, be it on the server or on the client. &lt;a href="http://github.com/thynctank/jazzrecord/tree/master"&gt;The code is up on github&lt;/a&gt;, and he&amp;rsquo;s looking to offer support HTML 5 databases so if you&amp;rsquo;ve got a chance check it out and help out by forking it.&lt;/p&gt; &lt;h3 id="yql_execute"&gt;YQL Execute&lt;/h3&gt; &lt;p&gt;&lt;a href="http://kid666.com/"&gt;Tom Hughes-Croucher&lt;/a&gt; gave a demo of what you can do with &lt;a href="http://developer.yahoo.com/yql/" title="Yahoo Query Language"&gt;YQL&lt;/a&gt;, a service that offers up SQL like syntax to access data from various web services. He gave us a preview of the &lt;a href="http://developer.yahoo.net/blog/archives/2009/04/yql_execute.html" title="With YQL Execute, the Internet becomes your database (Yahoo! Developer Network Blog)"&gt;now-announced execute functionality&lt;/a&gt;. With execute in your YQL table definition, you can take the data coming into the query and modify it with Javascript before sending out the final response. The main advantage YQL offers is offloading the fetching, processing, sorting, filtering, and joining of data to an external service that&amp;rsquo;s much faster. To quote Tom, &amp;ldquo;Our [Yahoo&amp;rsquo;s] pipes are fatter than yours.&amp;rdquo;&lt;/p&gt; &lt;h3 id="phonegap"&gt;PhoneGap&lt;/h3&gt; &lt;p&gt;Every conference needs some comic relief, and that could be found in &lt;a href="https://twitter.com/brianleroux"&gt;Brian LeRoux&lt;/a&gt;&amp;rsquo;s hilarious Day 2 morning session on PhoneGap. &lt;a href="http://phonegap.com/"&gt;PhoneGap&lt;/a&gt; is a framework and toolset to create apps for the iPhone, Android, and Blackberry using just HTML, CSS, and Javascript. It even offers up an API to get at the devices native technologies like geo location, vibration, accelerometer, and sound. The slides where great and it got everyone excited about building an app with PhoneGap.&lt;/p&gt; &lt;h3 id="and_much_much_more"&gt;And much, much more.&lt;/h3&gt; &lt;p&gt;Really, that&amp;rsquo;s just skimming the surface. &lt;a href="http://ejohn.org/"&gt;John Resig&lt;/a&gt; gave a really phenomenal talk on better ways to test Javascript performance and compatibility, and introduced a &lt;a href="http://deepleap.org/" title="DeepLeap: The Fast-Paced Time-Wasting Word Game!"&gt;killer Javascript game&lt;/a&gt;. There was also an interesting session on &lt;a href="http://titaniumapp.com/"&gt;Appcelerator&amp;rsquo;s Titanium&lt;/a&gt;, a way WebKit based platform to create desktop apps with HTML, CSS, and Javascript, Ruby or Python.&lt;br /&gt; Also, &lt;a href="https://developer.mozilla.org/en/e4x"&gt;E4X&lt;/a&gt; was mentioned a number of times. E4X is an extension to Javascript for Rhino that makes XML a native primitive. It allows you to write, access, and manipulate XML in Javascript easier and quicker then with traditional DOM methods.&lt;/p&gt; &lt;p&gt;Overall, it got me very excited to try out all these new up and coming tools. I&amp;rsquo;d be very excited to attend a JSConf 2010. Javascript as a real programming language is very much a growing topic and I think based on what I saw this year there&amp;rsquo;ll be even more amazing sessions in the future.&lt;/p&gt; 


      &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=FPhe1gdqBRo:ql84lcm1rEs:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=FPhe1gdqBRo:ql84lcm1rEs:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=FPhe1gdqBRo:ql84lcm1rEs:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=FPhe1gdqBRo:ql84lcm1rEs:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=FPhe1gdqBRo:ql84lcm1rEs:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=FPhe1gdqBRo:ql84lcm1rEs:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/VigetExtend/~4/FPhe1gdqBRo" height="1" width="1"/&gt;</content>
    <feedburner:origLink>http://www.viget.com/extend/jsconf-2009-recap/</feedburner:origLink></entry>

    <entry>
      <title>RailsConf 2009 Wrapup</title>
      <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/VigetExtend/~3/VnfI9v9H7VI/" />
      <id>tag:viget.com,2009:extend/4.1567</id>
      <published>2009-05-11T13:00:00Z</published>
      <updated>2009-05-11T13:52:05Z</updated>
      <author>
                        <name>Ben Scofield, Technology Director</name>
                        <email>ben.scofield@viget.com</email>
            <uri>http://www.viget.com/about/team/bscofield</uri>      </author>

      <category term="Events" scheme="http://www.viget.com/inspire/category/events/" label="Events" />
      <content type="html">


                 &lt;p&gt;I've been back from RailsConf for a few days now, and I've had a chance to reflect on the things I learned while in Las Vegas for what turned out to be my favorite RailsConf yet. Elsewhere, I wrote about my overall impressions, but for this post I want to focus on several of the sessions I attended and participated in.&lt;/p&gt;&lt;h3&gt;&lt;a href="http://en.oreilly.com/rails2009/public/schedule/detail/9221"&gt;Teaching Rails BoF&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;RailsConf for me really started Monday night, with the Teaching Rails Birds of a Feather session that I moderated. We had a great turnout (somewhere in the neighborhood of 30 people), and the attendees ranged across a number of different disciplines &amp;mdash; some people were teaching in a university setting, some were writing books, and others were interested primarily in building the community through educating new developers. Regardless of their backgrounds, though, everyone had good ideas and experiences to share, and I learned about a number of exciting new efforts (including the various components of the &lt;a href="http://railsbridge.org/"&gt;RailsBridge&lt;/a&gt; effort and the upcoming Rails Tutorials project).&lt;/p&gt;&lt;h3&gt;&lt;a href="http://en.oreilly.com/rails2009/public/schedule/detail/8497"&gt;The Even-Darker Art of Rails Engines&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Tuesday, I attended &lt;a href="http://interblah.net/"&gt;James Adam&lt;/a&gt;'s session on Rails Engines. I heard great things about his presentation on plugins from the 2008 RailsConf, so I had high hopes about this one. As it turned out, the presentation itself was decent, but the content was a little too basic for me &amp;mdash; we've looked fairly carefully at the engines support in Rails as the next step for our resourceful plugins, so most of what James talked about I'd already seen. I do have to say that the DHH-headed cherub slide was a real winner, though.&lt;/p&gt;
                 &lt;h3 style="font-size: 1.17em;"&gt;&lt;a href="http://en.oreilly.com/rails2009/public/schedule/detail/8772"&gt;The Women in Rails Panel&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Given the recent flap in the community, I was eager to see how the Women in Rails panel would go. Attendance was lower than most everyone expected, though the major players were all present. &lt;a href="http://www.desimcadam.com/"&gt;Desi McAdam&lt;/a&gt;, &lt;a href="http://www.sarahmei.com/blog/"&gt;Sarah Mei&lt;/a&gt;, and &lt;a href="http://blog.dragonsharp.com/"&gt;Lori Olson&lt;/a&gt; avoided (for the most part) the brouhaha, and ended up having a nice discussion of the various issues surrounding women in technology in general and in the Rails community in particular. As I mentioned to various others at the conference, I think it's good that we're having these sorts of discussions now, and that people are willing to talk about diversity and its value.&amp;nbsp;&lt;/p&gt;&lt;h3 style="font-size: 1.17em;"&gt;&lt;a href="http://en.oreilly.com/rails2009/public/schedule/detail/7367"&gt;Smacking Git Around &amp;mdash; Advanced Git Tricks&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;This was another session that I was looking forward to primarily based on the presenter. &lt;a href="http://jointheconversation.org/"&gt;Scott Chacon&lt;/a&gt; did the session before mine last year, and famously ran through several hundred slides; this year, he had somewhere around 400. Amazingly, his slides aren't even of the Takahashi school &amp;mdash; he puts some fairly complex diagrams and animations on there, and does an excellent job of presenting compex issues. I knew of many of the advanced tricks in this presentation beforehand, but a lot of it was new, and I had a great time at the session.&lt;/p&gt;&lt;h3 style="font-size: 1.17em;"&gt;&lt;a href="http://en.oreilly.com/rails2009/public/schedule/detail/7721"&gt;Blood, Sweat, and Rails&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;I wasn't entirely sure what to expect of &lt;a href="http://obiefernandez.com/"&gt;Obie Fernandez&lt;/a&gt;'s presentation; it ended up being a primer on the various issues he's dealt with as the founder of &lt;a href="http://www.hashrocket.com/"&gt;Hashrocket&lt;/a&gt;. Viget's similarities to Hashrocket meant that I was familiar with many of these, but Obie's an engaging presenter, and I think he communicated the challenges very well.&lt;/p&gt;&lt;h3 style="font-size: 1.17em;"&gt;&lt;a href="http://en.oreilly.com/rails2009/public/schedule/detail/9032"&gt;Keynote: Chris Wanstrath&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;For the most part, I'd like to avoid talking about the keynotes. I do, however, want to call specific attention to &lt;a href="http://errtheblog.com/"&gt;Chris Wanstrath&lt;/a&gt;'s. Unlike many presenters, Chris writes essays for his talks, and he reads them live. Unlike some speakers who do this, though, Chris is really good at it. It also helps that his content is excellent. For this talk, Chris told people how to become 1) a famous Rails developer, and 2) a good Rails developer. Unsurprisingly, the two are not synonymous, and his exploration of the processes behind each was inspirational.&lt;/p&gt;&lt;h3 style="font-size: 1.17em;"&gt;&lt;a href="http://en.oreilly.com/rails2009/public/schedule/detail/8276"&gt;Rails Metal, Rack, and Sinatra&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;I attended this session mainly to make sure that my session on Thursday wouldn't duplicate too much of &lt;a href="http://adam.blog.heroku.com/"&gt;Adam Wiggins&lt;/a&gt;' content. Luckily, Adam provided a much more basic introduction to the issues than I'd planned on, so there was minimal overlap. The most interesting thing I came out of this session with was an understanding of just how pervasive Rack was at this RailsConf &amp;mdash; everyone was talking about it to some extent. I almost think there could've been a complete Rack track across all three days...&lt;/p&gt;&lt;h3 style="font-size: 1.17em;"&gt;&lt;a href="http://en.oreilly.com/rails2009/public/schedule/detail/7765"&gt;Starting Up Fast: Lessons from the Rails Rumble&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Unfortunately, the Rumble panel didn't go as well as I'd hoped. The more events I attend, the more convinced I am that panels are one of the hardest sessions to do well &amp;mdash; and while I really enjoyed the discussions all of us had leading up to the session, the actual on-panel conversations just didn't seem to flow as smoothly as the less formal ones did.&lt;/p&gt;&lt;p&gt;I was very excited to meet the other panelists, though &amp;mdash; they've all worked on some remarkable things, and they're great guys. I'm looking forward to seeing what they all do in this year's &lt;a href="http://www.railsrumble.com/"&gt;Rails Rumble&lt;/a&gt; (August 22nd-23rd, by the way - mark your calendars!)&lt;/p&gt;&lt;h3 style="font-size: 1.17em;"&gt;&lt;a href="http://en.oreilly.com/rails2009/public/schedule/detail/7717"&gt;And the Greatest of These Is ... Rack Support&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;My solo session was (as it always seems to be) on the last day of the conference. Luckily, however, this time it was in the morning, so people were still fairly alert, and were only beginning to suffer from learning-overload. I'm very happy with how the overall presentation went, particularly as I was trying out a new technique for taking questions (which I originally heard about from &lt;a href="http://gilesbowkett.blogspot.com/2009/04/how-to-take-questions-at-tech.html"&gt;Giles Bowkett&lt;/a&gt;).&lt;/p&gt;&lt;p&gt;At the beginning of the talk, I showed the hashtag &lt;a href="http://search.twitter.com/search?q=%23%3Fforben"&gt;#?forben&lt;/a&gt;, and asked people to tweet their questions as the session progressed. This has the dual benefit of letting people ask questions when they arise, and forcing those questions to a reasonable (140 character) length. When I was ready for Q&amp;amp;A, I fired up my trusty Twitter client and saw what people had asked. The main problem with the technique was the layout of the room, which meant I had to jump down off the stage and run over to the side to see the actual questions (after a few iterations of this, the audience finally just shouted the question out to me and I answered it from the stage).&lt;/p&gt;&lt;p&gt;One other interesting thing came out of this session; while I was talking about all the great changes in Rails over the past year, I put up a slide with the most awesome logo I know of (the one &lt;a href="http://www.viget.com/about/team/davery"&gt;Doug Avery&lt;/a&gt; created for &lt;a href="http://www.bowlingthunder.org/"&gt;Bowling Thunder&lt;/a&gt; last year). The logo and website got around, and finally ended up on &lt;a href="http://www.reddit.com/comments/8iyx7/easily_the_awesomest_logo_you_will_ever_see_ak47/"&gt;Reddit&lt;/a&gt;, where it apparently generated a small flood of traffic to the website. Let's hear it for Doug Avery, everybody!&lt;/p&gt; &lt;div id="__ss_1402259" style="width: 425px; text-align: left;"&gt;&lt;a href="http://www.slideshare.net/bscofield/and-the-greatest-of-these-is-rack-support?type=powerpoint" style="font:14px Helvetica,Arial,Sans-serif;display:block;margin:12px 0 3px 0;text-decoration:underline;" title="And the Greatest of These Is ... Rack Support"&gt;And the Greatest of These Is ... Rack Support&lt;/a&gt;&lt;object width="425" height="355"&gt;&lt;param name="allowFullScreen" value="true" /&gt;&lt;param name="allowScriptAccess" value="always" /&gt;&lt;param name="src" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=railsconf-090507153118-phpapp01&amp;amp;stripped_title=and-the-greatest-of-these-is-rack-support" /&gt;&lt;embed src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=railsconf-090507153118-phpapp01&amp;amp;stripped_title=and-the-greatest-of-these-is-rack-support" width="425" height="355" allowScriptAccess="always" type="application/x-shockwave-flash"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div style="font-size: 11px; font-family: tahoma,arial; height: 26px; padding-top: 2px;"&gt;View more &lt;a href="http://www.slideshare.net/" style="text-decoration:underline;"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/bscofield" style="text-decoration:underline;"&gt;Ben Scofield&lt;/a&gt;.&lt;/div&gt;&lt;/div&gt; &lt;h3 style="font-size: 1.17em;"&gt;&lt;a href="http://en.oreilly.com/rails2009/public/schedule/detail/7785"&gt;The Russian Doll Pattern: Mountable Apps in Rails 3&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Going in to RailsConf, this was the session I was most excited about. The problems with integrating disparate Rails applications have always been one of my prime interests, so I'm greatly looking forward to it being much easier in Rails 3. Unfortunately, however, &lt;a href="http://yehudakatz.com/"&gt;Yehuda&lt;/a&gt;'s and &lt;a href="http://splendificent.com/"&gt;Carl&lt;/a&gt;'s talk left me unsatisfied and a little less hopeful. They admitted upfront that there was no code for mountable applications yet, and that they'd be showing us unicorns &amp;mdash; the way they hope it'll work &amp;mdash; which is perfectly fine. The vision they presented, though, appeared to me to be very incomplete, and to have some fundamental issues that don't appear to have been addressed. At this point, however, it's pretty close to useless to object, since the vision itself is so far from being implemented. I'll be keeping an eye out, however, and will be very interested to see how this progresses in the future.&lt;/p&gt;&lt;p&gt;Incidentally, this talk was also the one that most inspired me to begin working on &lt;a href="http://github.com/bscofield/athena/tree/master"&gt;Athena&lt;/a&gt; again, since one of the features I'd originally planned for it was a painless way to bundle multiple applications together.&lt;/p&gt;&lt;p&gt;So, that's a quick review of some of the sessions I attended. All in all, I think this RailsConf was a huge success &amp;mdash; I met a ton of new people, and learned some interesting things along the way, which is about all you can hope for out of a conference, isn't it? Let's hope that next year is as good!&lt;/p&gt; 


      &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=VnfI9v9H7VI:jrBpPfHZCwE:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=VnfI9v9H7VI:jrBpPfHZCwE:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=VnfI9v9H7VI:jrBpPfHZCwE:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=VnfI9v9H7VI:jrBpPfHZCwE:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=VnfI9v9H7VI:jrBpPfHZCwE:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=VnfI9v9H7VI:jrBpPfHZCwE:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/VigetExtend/~4/VnfI9v9H7VI" height="1" width="1"/&gt;</content>
    <feedburner:origLink>http://www.viget.com/extend/railsconf-2009-wrapup/</feedburner:origLink></entry>

    <entry>
      <title>Backup your Database in Git</title>
      <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/VigetExtend/~3/IIOEK0JTg6E/" />
      <id>tag:viget.com,2009:extend/4.1566</id>
      <published>2009-05-08T14:15:00Z</published>
      <updated>2009-05-08T17:44:18Z</updated>
      <author>
                        <name>David Eisinger, Web Developer</name>
                        <email>david.eisinger@viget.com</email>
            <uri>http://www.viget.com/about/team/deisinger</uri>      </author>

      <category term="Tips and Tricks" scheme="http://www.viget.com/inspire/category/tips_and_tricks/" label="Tips and Tricks" />
      <content type="html">


                 &lt;p&gt;&lt;strong&gt;Short version&lt;/strong&gt;: dump your production database into a git repository for an instant backup solution.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Long version&lt;/strong&gt;: keeping backups of production data is fundamental for a well-run web application, but it&amp;rsquo;s tricky to maintain history while keeping disk usage at a reasonable level. You could continually overwrite the backup with the latest data, but you risk automatically replacing good data with bad. You could save each version in a separate, timestamped file, but since most of the data is static, you would end up wasting a lot of disk space.&lt;/p&gt;
                 &lt;p&gt;When you think about it, a database dump is just SQL code, so why not manage it the same way you manage the rest of your code &amp;mdash; in a source code manager? Setting such a scheme up is dead simple. On your production server, with git installed:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;mkdir -p /path/to/backup
cd /path/to/backup
mysqldump -u [user] -p[pass] --skip-extended-insert [database] &amp;gt; [database].sql
git init
git add [database].sql
git commit -m "Initial commit"
&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;The &lt;code&gt;--skip-extended-insert&lt;/code&gt; option tells mysqldump to give each table row its own &lt;code&gt;insert&lt;/code&gt; statement. This creates a larger initial commit than the default bulk insert, but makes future commits much easier to read and (I suspect) keeps the overall repository size smaller, since each patch only includes the individual records added/updated/deleted.&lt;/p&gt; &lt;p&gt;From here, all we have to do is set up a cronjob to update the backup:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;0 * * * * cd /path/to/backup &amp;amp;&amp;amp; \
mysqldump -u [user] -p[pass] --skip-extended-insert [database] &amp;gt; [database].sql &amp;amp;&amp;amp; \
git commit -am "Updating DB backup"
&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;You may want to add another entry to run &lt;a href="http://www.kernel.org/pub/software/scm/git/docs/git-gc.html"&gt;&lt;code&gt;git gc&lt;/code&gt;&lt;/a&gt; every day or so in order to keep disk space down and performance up.&lt;/p&gt; &lt;p&gt;Now that you have all of your data in a git repo, you&amp;rsquo;ve got a lot of options. Easily view activity on your site with &lt;code&gt;git whatchanged -p&lt;/code&gt;. Update your staging server to the latest data with &lt;code&gt;git clone ssh://[hostname]/path/to/backup&lt;/code&gt;. Add a remote on &lt;a href="http://github.com/"&gt;Github&lt;/a&gt; and get offsite backups with a simple &lt;code&gt;git push&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;This technique might fall down if your app approaches &lt;a href="http://craigslist.org/"&gt;Craigslist&lt;/a&gt;-level traffic, but it&amp;rsquo;s working flawlessly for us on &lt;a href="http://speakerrate.com"&gt;SpeakerRate&lt;/a&gt;, and should work well for your small- to medium-sized web application.&lt;/p&gt; 


      &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=IIOEK0JTg6E:YvR81XXQJuQ:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=IIOEK0JTg6E:YvR81XXQJuQ:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=IIOEK0JTg6E:YvR81XXQJuQ:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=IIOEK0JTg6E:YvR81XXQJuQ:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=IIOEK0JTg6E:YvR81XXQJuQ:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=IIOEK0JTg6E:YvR81XXQJuQ:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/VigetExtend/~4/IIOEK0JTg6E" height="1" width="1"/&gt;</content>
    <feedburner:origLink>http://www.viget.com/extend/backup-your-database-in-git/</feedburner:origLink></entry>

    <entry>
      <title>SimplestAuth: Gem-ified, with DataMapper Support</title>
      <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/VigetExtend/~3/YDaYeldf4hc/" />
      <id>tag:viget.com,2009:extend/4.1561</id>
      <published>2009-05-07T19:00:00Z</published>
      <updated>2009-05-08T03:55:12Z</updated>
      <author>
                        <name>Tony Pitale, Web Developer</name>
                        <email>tony.pitale@viget.com</email>
            <uri>http://t.pitale.com</uri>      </author>

      <category term="General" scheme="http://www.viget.com/inspire/category/general/" label="General" />
      <content type="html">


                 &lt;p&gt;A few months ago we released &lt;a href="http://github.com/vigetlabs/simplest_auth/tree/master"&gt;simplest_auth&lt;/a&gt; as a stripped-down alternative to authentication plugins such as restful auth. After using it in a few projects we came to the realization that there was no particular reason for it to be a plugin and even less of a reason for it to work only with ActiveRecord.&lt;/p&gt; &lt;p&gt;As a result, the latest version on Github is now built as a gem and can be used in a Rails project that is using either ActiveRecord or DataMapper as its ORM. The same example usage from the README applies to ActiveRecord, but I'd like to give an example which uses DataMapper for the model.&lt;/p&gt; &lt;pre name="code" class="ruby:nogutter"&gt;  class User
    include DataMapper::Resource
    include SimplestAuth::Model

    property :id, Serial
    property :email, String, :nullable =&amp;gt; false
    property :crypted_password, String, :length =&amp;gt; 60

    # validate your model as you see fit
    # these are some sane defaults for password
    validates_present :password, :if =&amp;gt; :password_required?
    validates_is_confirmed :password, :if =&amp;gt; :password_required?
  end
&lt;/pre&gt; &lt;p&gt;Or, using a username instead of an email address just change these lines:&lt;/p&gt; &lt;pre name="code" class="ruby:nogutter"&gt;  property :username, String, :nullable =&amp;gt; false

  # add this line when not using the default of email
  authenticate_by :username
&lt;/pre&gt; &lt;p&gt;Everything else should remain the same. Install the latest gem using:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;
sudo gem install simplest_auth
&lt;/code&gt;&lt;/pre&gt;
                  


      &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=YDaYeldf4hc:tN15PCrW_7c:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=YDaYeldf4hc:tN15PCrW_7c:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=YDaYeldf4hc:tN15PCrW_7c:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=YDaYeldf4hc:tN15PCrW_7c:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=YDaYeldf4hc:tN15PCrW_7c:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=YDaYeldf4hc:tN15PCrW_7c:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/VigetExtend/~4/YDaYeldf4hc" height="1" width="1"/&gt;</content>
    <feedburner:origLink>http://www.viget.com/extend/simplestauth-gem-ified-with-datamapper-support/</feedburner:origLink></entry>

    <entry>
      <title>Updated Garb: Even Easier Access to the Google Analytics API</title>
      <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/VigetExtend/~3/4zD2o4Hcgd0/" />
      <id>tag:viget.com,2009:extend/4.1553</id>
      <published>2009-05-04T17:00:00Z</published>
      <updated>2009-05-04T18:46:36Z</updated>
      <author>
                        <name>Tony Pitale, Web Developer</name>
                        <email>tony.pitale@viget.com</email>
            <uri>http://t.pitale.com</uri>      </author>

      <category term="Introducing" scheme="http://www.viget.com/inspire/category/introducing/" label="Introducing" />
      <category term="Tools of the Trade" scheme="http://www.viget.com/inspire/category/tools-of-the-trade/" label="Tools of the Trade" />
      <category term="Trends" scheme="http://www.viget.com/inspire/category/trends/" label="Trends" />
      <content type="html">


                 &lt;p&gt;In my &lt;a href="http://www.viget.com/extend/introducing-garb-access-the-google-analytics-data-export-api-with-ruby/"&gt;introductory post&lt;/a&gt;, I explained what Garb was, and how it could be useful to those wishing to get access to their Google Analytics data. In this post, I would like to introduce the latest version of Garb (0.2.1 as of this writing) and explain the many changes and new ways to use Garb to get at your data.&lt;/p&gt; &lt;p&gt;Getting sessions and profiles is still exactly the same.&lt;/p&gt; &lt;pre name="code" class="ruby:nogutter"&gt;  Garb::Session.login('username', 'password')
  profile = Garb::Profile.all.first
&lt;/pre&gt; &lt;p&gt;What's new in 0.2.1 is the way in which reports are built and results are retrieved. Check it out:&lt;/p&gt; &lt;h2&gt;As a Report Class&lt;/h2&gt; &lt;pre name="code" class="ruby:nogutter"&gt;  class Exits
    include Garb::Resource

    metrics :exits, :exit_rate
    dimensions :request_uri
  end
&lt;/pre&gt; &lt;h3&gt;Getting the Results with a Class&lt;/h3&gt; &lt;pre name="code" class="ruby:nogutter"&gt;  Exits.results(profile, :limit =&amp;gt; 10,
                         :offset =&amp;gt; 20,
                         :start_date =&amp;gt; (Date.today - 30),
                         :end_date =&amp;gt; Date.today)

  # With Filtering and Sorting

  Exits.results(profile) do
    filter :request_uri.contains =&amp;gt; 'fun', :exits.gte =&amp;gt; 1000
    sort :exit_rate
  end
&lt;/pre&gt; &lt;h2&gt;One-off Report&lt;/h2&gt; &lt;pre name="code" class="ruby:nogutter"&gt;  report = Garb::Report.new(profile)
  report.metrics :exits, :exit_rate
  report.dimensions :request_uri

  # With Filtering and Sort

  report.filter :request_uri.contains =&amp;gt; 'fun'
  report.filter :exits.gte =&amp;gt; 1000
  report.sort :exit_rate.desc

  # Getting Results

  report.results(:limit =&amp;gt; 10,
                 :offset =&amp;gt; 20,
                 :start_date =&amp;gt; (Date.today - 30),
                 :end_date =&amp;gt; Date.today)
&lt;/pre&gt; &lt;p&gt;The results returned from Garb will be OpenStructs with methods for each of the metrics and dimensions in an array.&lt;/p&gt; &lt;pre name="code" class="ruby:nogutter"&gt;  results.exits       #=&amp;gt; 1234
  results.exit_rate   #=&amp;gt; 0.20423810234
  results.request_uri #=&amp;gt; '/some/fun/url/to/a/page'
&lt;/pre&gt; &lt;p&gt;Overall, we feel that the improvements are solid and make more sense. Be warned: if you've used a previous version of Garb, then updating to the latest version will very likely break most of what was done previously. I hope everyone can find a use for this, and I encourage all to check out the &lt;a href="http://github.com/vigetlabs/garb/tree/master"&gt;project on Github&lt;/a&gt; and to read the &lt;a href="http://wiki.github.com/vigetlabs/garb"&gt;documentation in the Wiki&lt;/a&gt;.&lt;/p&gt;
                  


      &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=4zD2o4Hcgd0:18rqiR5SeWc:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=4zD2o4Hcgd0:18rqiR5SeWc:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=4zD2o4Hcgd0:18rqiR5SeWc:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=4zD2o4Hcgd0:18rqiR5SeWc:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=4zD2o4Hcgd0:18rqiR5SeWc:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=4zD2o4Hcgd0:18rqiR5SeWc:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/VigetExtend/~4/4zD2o4Hcgd0" height="1" width="1"/&gt;</content>
    <feedburner:origLink>http://www.viget.com/extend/updated-garb-even-easier-access-to-the-google-analytics-api/</feedburner:origLink></entry>

    <entry>
      <title>Shoulda Macros with Blocks</title>
      <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/VigetExtend/~3/FLcEwfbqzkg/" />
      <id>tag:viget.com,2009:extend/4.1548</id>
      <published>2009-04-29T12:00:00Z</published>
      <updated>2009-05-04T18:14:40Z</updated>
      <author>
                        <name>David Eisinger, Web Developer</name>
                        <email>david.eisinger@viget.com</email>
            <uri>http://www.viget.com/about/team/deisinger</uri>      </author>

      <category term="Tips and Tricks" scheme="http://www.viget.com/inspire/category/tips_and_tricks/" label="Tips and Tricks" />
      <content type="html">


                 &lt;p&gt;When I&amp;rsquo;m not working on client projects, I keep myself busy with&amp;nbsp;&lt;a href="http://speakerrate.com"&gt;SpeakerRate&lt;/a&gt;, a site that lets conference goers rate the talks they&amp;rsquo;ve attended. After a number of similar suggestions from users, we decided to display the total number of ratings alongside the averages. Although only talks can be rated, speakers, events and series also have ratings through their associated talks. As you can imagine, calculating the total ratings for each of these required a lot of somewhat repetitive code in the models, and &lt;em&gt;very&lt;/em&gt; repetitive code in the associated tests.&lt;/p&gt; &lt;p&gt;Fortunately, since we&amp;rsquo;re using &lt;a href="http://thoughtbot.com/projects/shoulda/"&gt;Shoulda&lt;/a&gt;, we were able to DRY things up considerably with a macro:&lt;/p&gt;
                 &lt;pre name="code" class="ruby"&gt;class Test::Unit::TestCase
  def self.should_sum_total_ratings
    klass = model_class

    context "finding total ratings" do
      setup do
        @ratable = Factory(klass.to_s.downcase)
      end

      should "have zero total ratings if no rated talks" do
        assert_equal 0, @ratable.total_ratings
      end

      should "have one total rating if one delivery &amp;amp; content rating" do
        talk = block_given? ? yield(@ratable) : @ratable
        Factory(:content_rating,  :talk =&amp;gt; talk)
        Factory(:delivery_rating, :talk =&amp;gt; talk)

        assert_equal 1, @ratable.reload.total_ratings
      end
    end
  end
end
&lt;/pre&gt; &lt;p&gt;This way, if we&amp;rsquo;re testing a talk, we can just say:&lt;/p&gt; &lt;pre name="code" class="ruby"&gt;class TalkTest &amp;lt; Test::Unit::TestCase
  context "A Talk" do
    should_sum_total_ratings
  end
end
&lt;/pre&gt; &lt;p&gt;But if we&amp;rsquo;re testing something that has a relationship with multiple talks, our macro accepts a block that serves as a factory to create a talk with the appropriate relationship. For events, we can do something like:&lt;/p&gt; &lt;pre name="code" class="ruby"&gt;class EventTest &amp;lt; Test::Unit::TestCase
  context "An Event" do
    should_sum_total_ratings do |event|
      Factory(:talk, :event =&amp;gt; event)
    end
  end
end
&lt;/pre&gt; &lt;p&gt;I'm pretty happy with this solution, but having to type &amp;ldquo;event&amp;rdquo; three times still seems a little verbose. If you've got any suggestions for refactoring, let us know in the comments.&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt; 


      &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=FLcEwfbqzkg:NiC1CUxCc8g:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=FLcEwfbqzkg:NiC1CUxCc8g:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=FLcEwfbqzkg:NiC1CUxCc8g:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=FLcEwfbqzkg:NiC1CUxCc8g:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=FLcEwfbqzkg:NiC1CUxCc8g:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=FLcEwfbqzkg:NiC1CUxCc8g:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/VigetExtend/~4/FLcEwfbqzkg" height="1" width="1"/&gt;</content>
    <feedburner:origLink>http://www.viget.com/extend/shoulda-macros-with-blocks/</feedburner:origLink></entry>

    <entry>
      <title>Introducing Garb: Access the Google Analytics Data Export API with Ruby</title>
      <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/VigetExtend/~3/0wEAWFflLAY/" />
      <id>tag:viget.com,2009:extend/4.1416</id>
      <published>2009-04-24T13:00:00Z</published>
      <updated>2009-04-29T21:52:20Z</updated>
      <author>
                        <name>Tony Pitale, Web Developer</name>
                        <email>tony.pitale@viget.com</email>
            <uri>http://t.pitale.com</uri>      </author>

      <category term="Introducing" scheme="http://www.viget.com/inspire/category/introducing/" label="Introducing" />
      <category term="Opinions/Reviews" scheme="http://www.viget.com/inspire/category/opinions_reviews/" label="Opinions/Reviews" />
      <category term="Trends" scheme="http://www.viget.com/inspire/category/trends/" label="Trends" />
      <content type="html">


                 &lt;p&gt;Since the moment Google announced that they would be releasing an API for their de-facto analytics tools,   I was very excited to dive into the wealth of useful information within. Shortly thereafter, Josh and Jen   from our Marketing Lab came to us to develop some tools that would help in collecting the data for our reporting.&lt;/p&gt; &lt;p&gt;To accomplish these goals, I first read through all of the documentation that Google provided with   access to the analytics data export beta. After, with some brainstorming with Pat, we decided to create a new   gem to handle much of the interaction with the new API. Garb is what we've come up with.&lt;/p&gt; &lt;p&gt;Thankfully, the waiting period is over.   &lt;a href="http://analytics.blogspot.com/2009/04/attention-developers-google-analytics.html"&gt; Google has announced the release of the API to the public&lt;/a&gt;, and Josh has   &lt;a href="http://www.viget.com/engage/google-analytics-api-now-open-for-business/"&gt;written his   take on the announcement&lt;/a&gt;.&lt;/p&gt; &lt;h3&gt;The API and Design of Garb&lt;/h3&gt; &lt;h4&gt;Documentation&lt;/h4&gt; &lt;p&gt;The documentation for the analytics API is pretty solid, though complex. Sections describe the authentication   process, retrieving accounts and profiles, the metrics and dimensions available and how they could be combined,   and details for sorting and filtering the data. It is very well written, and very concise. I suppose I should   have expected nothing less from Google.&lt;/p&gt; &lt;h4&gt;A Word on Dimensions and Metrics&lt;/h4&gt; &lt;p&gt;The API provides access to all of the same dimensions and metrics (and more) that are familiar to anyone who has   ever used Google Analytics and especially the custom reporting tools within. Through the API there is, however,   a substantial set of rules surrounding the selection and combination of dimensions and metrics.&lt;/p&gt; &lt;p&gt;Basically, two dimensions and up to 50 metrics can be selected in any one request. Easy enough? Unfortunately, we hit   snags trying to make (what seem like) commonplace combinations. For example, while we were developing   Garb, you could not get the "visits" metric scoped to the "requested page URI" dimension. It took a good bit of studying to   truly understand the boundaries of these combinations.&lt;/p&gt; &lt;h3&gt;So, On To Using Garb in Your Project&lt;/h3&gt; &lt;p&gt;There are three parts to using Garb. The first is getting a session, which requires your Google Analytics username and password . Second is the profile (or many profiles) that your authenticated account has access to.   Last, given the first two parts, you can create reports and retrieve the data from the API.&lt;/p&gt; &lt;h4&gt;Session&lt;/h4&gt; &lt;p&gt;Sessions are simple enough to create and are currently stored globally. This is simpler, but limits us to one session at a time.&lt;/p&gt; &lt;pre&gt;&lt;code&gt;
Garb::Session.login('username', 'password')
&lt;/code&gt;
&lt;/pre&gt; &lt;h4&gt;Profile&lt;/h4&gt; &lt;pre&gt;&lt;code&gt;
profile = Garb::Profile.all.first # or select from the list
&lt;/code&gt;
&lt;/pre&gt; &lt;h4&gt;Reports&lt;/h4&gt; &lt;p&gt;You can create a simple report.&lt;/p&gt; &lt;pre&gt;&lt;code&gt;
report = Garb::Report.new(profile, {:metrics =&amp;gt; [:visits]})
report.all
&lt;/code&gt;
&lt;/pre&gt; &lt;p&gt;Or, an advanced one.&lt;/p&gt; &lt;pre&gt;&lt;code&gt;
class ExitsReport &amp;lt; Garb::Report
  def initialize(profile)
    super(profile) do |config|
      config.start_date = Time.now.at_beginning_of_month
      config.end_date = Time.now.at_end_of_month
      config.metrics &amp;lt;&amp;lt; [:exits, :pageviews, :exit_rate]
      config.dimensions &amp;lt;&amp;lt; :request_uri
      config.sort &amp;lt;&amp;lt; :exits.desc
      config.max_results = 10
    end
  end
end

ExitsReport.new(profile).all
&lt;/code&gt;
&lt;/pre&gt; &lt;p&gt;Note: the date/time related methods are from ActiveSupport.&lt;/p&gt; &lt;p&gt;The Google Analytics Data Export API has come along very quickly. It's already powerful enough to provide   the data to generate reports that we've never before been able to create. Soon, we'll only be limited by our   imaginations, which is exactly how Google wants it. There have always been many compelling reasons to use   Google Analytics. Now, I struggle to find a reason not to.&lt;/p&gt; &lt;p&gt;I encourage everyone interested in using this library to read the documentation, check the code out on Github at   &lt;a href="http://github.com/vigetlabs/garb/tree/master"&gt;http://github.com/vigetlabs/garb/tree/master&lt;/a&gt; and, of   course, comment, question, and enjoy. If you'd like to go straight to installing it be sure to vigetlabs-garb sourced from Github until I can get it up on Rubyforge!&lt;/p&gt;
                  


      &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=0wEAWFflLAY:zVT0-hEaNTw:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=0wEAWFflLAY:zVT0-hEaNTw:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=0wEAWFflLAY:zVT0-hEaNTw:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=0wEAWFflLAY:zVT0-hEaNTw:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=0wEAWFflLAY:zVT0-hEaNTw:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=0wEAWFflLAY:zVT0-hEaNTw:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/VigetExtend/~4/0wEAWFflLAY" height="1" width="1"/&gt;</content>
    <feedburner:origLink>http://www.viget.com/extend/introducing-garb-access-the-google-analytics-data-export-api-with-ruby/</feedburner:origLink></entry>

    <entry>
      <title>Progressive Caching In-Depth</title>
      <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/VigetExtend/~3/oSoEPtOBAX8/" />
      <id>tag:viget.com,2009:extend/4.1543</id>
      <published>2009-04-23T09:01:00Z</published>
      <updated>2009-04-23T10:24:30Z</updated>
      <author>
                        <name>Ben Scofield, Technology Director</name>
                        <email>ben.scofield@viget.com</email>
            <uri>http://www.viget.com/about/team/bscofield</uri>      </author>

      <category term="Introducing" scheme="http://www.viget.com/inspire/category/introducing/" label="Introducing" />
      <content type="html">


                 &lt;p&gt;Recently, I've been presenting on a technique that takes advantage of Rack support in Rails to revitalize page caching; I've taken to calling it "progressive caching." &amp;nbsp;There are a couple of places around the web where you can find an introduction to the topic, but this post will go into significantly more depth.&lt;/p&gt;&lt;p&gt;Note: This technique isn't particularly new (I've seen at least &lt;a href="http://withoutscope.com/2007/8/12/for-the-love-of-god-use-the-page-cache"&gt;one blog post from 2007&lt;/a&gt; about it), though some of the advances in Rails make it more effective. As far as I can tell, however, it hasn't really been explored in depth, but I welcome disagreement on that point!&lt;/p&gt;
                 &lt;h3&gt;Background&lt;/h3&gt;&lt;p&gt;When you implement page caching in Rails, an action is only processed once; after that, the generated markup is saved to a static file in /public. Subsequent requests to the same URL are then processed by the web server (Apache, nginx, etc.), which makes them orders of magnitude faster than if they were processed dynamically.&lt;/p&gt;&lt;p&gt;This performance boost is wonderful in some cases, but comes with two major challenges: cached pages are both public and static. If you present a page only to authenticated users, you can't use page caching &amp;mdash; after the first valid presentation, the content would be cached and anyone could see it, because the web server doesn't respect your application's authentication and authorization code. Also, cached pages are (by definition) static, which means that this is unsuitable for pages that change frequently, or change based on who is viewing them.&lt;/p&gt;&lt;p&gt;Progressive caching is intended to overcome both of these challenges by combining page caching with highly-efficient AJAX requests and client-side JavaScript.&lt;/p&gt;&lt;h3&gt;Example&lt;/h3&gt;&lt;p&gt;To see how this works, let's start with a sample page:&lt;/p&gt;&lt;p&gt;&lt;img alt="Ask a Ninja on Odeo" height="591" src="http://www.viget.com/uploads/image/odeo.png" width="430" /&gt;&lt;/p&gt;&lt;p&gt;This is a channel page from &lt;a href="http://www.odeo.com/"&gt;odeo.com&lt;/a&gt;; it shows, among other things, the most recent episodes for the "Ask a Ninja" series. Notice that each episode has a small, pink plus icon. When you add an episode to your quicklist on Odeo, the pink changes to gray and the icon is disabled &amp;mdash; and this state persists across all pages where the episode appears.&lt;/p&gt;&lt;p&gt;This practice means that every time a page on Odeo is rendered, the system has to check whether the episodes on the page are in the current user's quicklist, and it has to show the correct icon for each. Let's show some (entirely faked) code for that:&lt;/p&gt; &lt;pre name="code" class="ruby"&gt;class ChannelsController
  def show
    @quicklist = current_user.quicklist
    @channel = Channel.find(params[:id])
    @episodes = @channel.episodes.recent
    # ...
  end
end
&lt;/pre&gt; &lt;pre name="code" class="ruby"&gt;&amp;lt;% @episodes.each do |episode| %&amp;gt;
&lt;div&gt;    &amp;lt;%= image_tag episode.thumbnail_url, :alt =&amp;gt; h(episode.title) %&amp;gt;&amp;nbsp;&amp;nbsp;
    &amp;lt;%= link_to image_tag('quicklist_icon.png',
                        :alt =&amp;gt; "Add this episode to your quicklist"),&amp;nbsp;&amp;nbsp;
                        foo_url,
                        :class =&amp;gt; (@quicklist.includes?(episode) ? 'listed' : '')  %&amp;gt;&amp;nbsp;&amp;nbsp;
&lt;/div&gt;&amp;lt;% end %&amp;gt;
&lt;/pre&gt; &lt;p&gt;In the view, we're changing a class on the link based on whether the episode has been added to the quicklist or not. Through CSS, this changes the icon displayed to the user.&lt;/p&gt;&lt;p&gt;Changing this fairly costly action to use progressive caching is easy. First, we remove the conditional from the view, which leaves us with content suitable for page caching (since it is no longer dependent on the logged-in user):&lt;/p&gt;&lt;pre name="code" class="ruby"&gt;&amp;lt;% @episodes.each do |episode| %&amp;gt;
&lt;div&gt;    &amp;lt;%= image_tag episode.thumbnail_url, :alt =&amp;gt; h(episode.title) %&amp;gt;
    &amp;lt;%= link_to image_tag('quicklist_icon.png',
                        :alt =&amp;gt; "Add this episode to your quicklist"),
                        foo_url,
                        :id =&amp;gt; "quicklist_#{episode.id}" %&amp;gt;
&lt;/div&gt;&amp;lt;% end %&amp;gt;
&lt;/pre&gt;&lt;p&gt;Next, we cache the page and remove unnecessary code (note: at this point you'd also have to add the appropriate cache-expiration code &amp;mdash; say, when new episodes are uploaded):&lt;/p&gt;&lt;pre name="code" class="ruby"&gt;class ChannelsController
  caches_page :show

  def show
    @channel = Channel.find(params[:id])
    @episodes = @channel.episodes.recent
    # ...
  end
end
&lt;/pre&gt;&lt;p&gt;At this point, the bulk of the original page's content is being processed through the web server, but we're obviously missing the bits that depend on the logged-in user. To add those, we'll add some Metal:&lt;/p&gt;&lt;pre name="code5" class="bash"&gt;./script/generate metal Personalizer
&lt;/pre&gt; &lt;pre name="code" class="ruby"&gt;# Allow the metal piece to run in isolation
require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails)

class Personalizer
  def self.call(env)
    if env["PATH_INFO"] =~ /^\/personalize/
      [
        200, 
        {"Content-Type" =&amp;gt; "application/javascript"}, 
        [User.find(env['rack.session'][:user]).quicklist.episode_ids.to_json]
      ]
    else
      [404, {"Content-Type" =&amp;gt; "text/html"}, ["Not Found"]]
    end
  end
end
&lt;/pre&gt;&lt;p&gt;And finally, a bit of jQuery that runs when the page loads:&lt;/p&gt;&lt;pre name="code" class="ruby"&gt;$(document).ready(function() {
  $.getJSON('/personalize', function(data) {
    $.each(data, function() {
      $('#quicklist_'+this).addClass('listed');
    });
  });
});
&lt;/pre&gt;&lt;p&gt;After that, we're pretty much done. When someone visits the page, they see all the episodes as if none were in the quicklist. After a brief delay (during which the AJAX call is sent back to the Metal action), the page updates and the episodes that are in the quicklist change. If users are interrupted or confused by the delay, the original state might instead have activity indicators (e.g., spinners) in place of the quicklist icons, but that all comes down to a UX decision.&lt;/p&gt;&lt;p&gt;By now (if not well before), you may have asked yourself about the payoff. Well, on the sample app I describe in my presentation, using progressive caching took a locally-hosted page from 617ms (including database and rendering time) to 135ms (39ms for the cached, static content + 96ms for the AJAX call), which is a decrease of nearly 80%. A second version (which avoided ActiveRecord in the Metal action) took a total of 66ms (43ms for the page, 23ms for the AJAX). These might seem like small numbers in the absolute sense, but when added up over a day, week, or month of an application, they can become extremely significant.&lt;/p&gt;&lt;h3&gt;Problems&lt;br /&gt;&lt;/h3&gt;&lt;p&gt;Of course, there are no perfect techniques, and progressive caching is no exception. We've already mentioned one issue (the delay before the page updates), but there are also problems with accessibility and testing.&lt;/p&gt;&lt;p&gt;Clearly, this approach requires JavaScript on the client. Some people browse without JavaScript (for a variety of reasons), and there are known accessibility issues with dynamically-updating pages. Progressive caching runs into these headlong, so if your application is one that must degrade gracefully, it may not be the correct approach for you.&lt;/p&gt;&lt;p&gt;Also, the state of testing for Rails Metal is ... primitive, to say the least. As far as I know, the best approach is somewhere between an integration test and a homebrewed unit test. That's far from ideal, which means that using Metal can tempt you into testing less. If you do decide to use this strategy, remain vigilant and &lt;strong&gt;keep testing&lt;/strong&gt;!&lt;/p&gt;&lt;p&gt;So, that's it &amp;mdash; progressive caching in one form or another has been around for a while, but with the recent changes in Rails (Rack, Metal, and the like), it's poised to come into its own. If you're working on applications that present mostly-similar content to everyone, with pages that vary only in discrete pieces, it may be a viable option for boosting performance by a noticeable amount.&lt;/p&gt; 


      &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=oSoEPtOBAX8:MuTq3lmjk7c:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=oSoEPtOBAX8:MuTq3lmjk7c:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=oSoEPtOBAX8:MuTq3lmjk7c:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=oSoEPtOBAX8:MuTq3lmjk7c:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=oSoEPtOBAX8:MuTq3lmjk7c:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=oSoEPtOBAX8:MuTq3lmjk7c:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/VigetExtend/~4/oSoEPtOBAX8" height="1" width="1"/&gt;</content>
    <feedburner:origLink>http://www.viget.com/extend/progressive-caching-in-depth/</feedburner:origLink></entry>

    <entry>
      <title>Look Out! It’s Developer Day in DC!</title>
      <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/VigetExtend/~3/Rfk4GRB2pzU/" />
      <id>tag:viget.com,2009:extend/4.1539</id>
      <published>2009-04-15T10:05:00Z</published>
      <updated>2009-04-15T14:48:33Z</updated>
      <author>
                        <name>Ben Scofield, Technology Director</name>
                        <email>ben.scofield@viget.com</email>
            <uri>http://www.viget.com/about/team/bscofield</uri>      </author>

      <category term="Events" scheme="http://www.viget.com/inspire/category/events/" label="Events" />
      <content type="html">


                 &lt;p&gt;That's right, folks, we're opening up &lt;a href="http://developerdaydc.eventbrite.com/"&gt;registration&lt;/a&gt; for &lt;a href="http://developer-day.com/"&gt;Developer Day&lt;/a&gt; in DC. If you're interested in new and exciting developments in technology, you should mark May 30th on your calendar &amp;mdash; we've got talks planned on hot-off-the-code-press Ruby 1.9, &lt;a href="http://cukes.info/"&gt;Cucumber&lt;/a&gt; for acceptance testing, and &lt;a href="http://www.lilyapp.org/"&gt;Lily&lt;/a&gt; and &lt;a href="http://monome.org/"&gt;the Monome&lt;/a&gt;, and we're adding more even as you read this.&lt;br /&gt;&lt;br /&gt;The inaugural &lt;a href="http://developer-day.com/events/2009-durham.html"&gt;Developer Day in Durham&lt;/a&gt; was a hit, and we expect this one to be even better, with more diverse technologies represented and more people to connect with. So join us on May 30th in Falls Church for a day full of engaging talks and good food, all for the low, low price of $50!&lt;br /&gt;&lt;br /&gt;Note: If DC is a bit too far for a day trip, don't despair! We're planning on continuing to expand Developer Day to other cities. Email me at ben.scofield@viget.com if you're interested in more details.&lt;/p&gt;
                  


      &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=Rfk4GRB2pzU:hUoNwMo7qv0:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=Rfk4GRB2pzU:hUoNwMo7qv0:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=Rfk4GRB2pzU:hUoNwMo7qv0:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=Rfk4GRB2pzU:hUoNwMo7qv0:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=Rfk4GRB2pzU:hUoNwMo7qv0:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=Rfk4GRB2pzU:hUoNwMo7qv0:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/VigetExtend/~4/Rfk4GRB2pzU" height="1" width="1"/&gt;</content>
    <feedburner:origLink>http://www.viget.com/extend/look-out-its-developer-day-in-dc/</feedburner:origLink></entry>

    <entry>
      <title>Out, Damned Tabs</title>
      <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/VigetExtend/~3/cjw0MR842Bk/" />
      <id>tag:viget.com,2009:extend/4.1534</id>
      <published>2009-04-09T14:06:00Z</published>
      <updated>2009-04-09T16:22:38Z</updated>
      <author>
                        <name>David Eisinger, Web Developer</name>
                        <email>david.eisinger@viget.com</email>
            <uri>http://www.viget.com/about/team/deisinger</uri>      </author>

      <category term="Tips and Tricks" scheme="http://www.viget.com/inspire/category/tips_and_tricks/" label="Tips and Tricks" />
      <content type="html">


                 &lt;p&gt;Like many developers I know, I&amp;rsquo;m a little bit OCD about code formatting. While there are about as many ideas of properly formatted code as there are coders, I think we can all agree that code with tabs and trailing whitespace is not it. Git has the &lt;code&gt;whitespace = fix&lt;/code&gt; option, which does a fine job removing trailing spaces before commits, but leaves the spaces in the working copy, and doesn&amp;rsquo;t manage tabs at all.&lt;/p&gt; &lt;p&gt;I figured there had to be a better way to automate this type of code formatting, and with help from &lt;a href="http://conceptsahead.com/off-axis/proper-trimming-on-save-with-textmate"&gt;Kevin McFadden&amp;rsquo;s post&lt;/a&gt;, I think I&amp;rsquo;ve found one, by configuring &lt;a href="http://macromates.com/"&gt;TextMate&lt;/a&gt; to strip off trailing whitespace and replace tabs with spaces whenever a file is saved. Here&amp;rsquo;s how to set it up:&lt;/p&gt; &lt;ol&gt; &lt;li&gt;Open the Bundle Editor (Bundles &amp;gt; Bundle Editor &amp;gt; Show Bundle Editor).&lt;/li&gt; &lt;li&gt;Create a new bundle using the &amp;ldquo;+&amp;rdquo; menu at the bottom of the page. Call it something like &amp;ldquo;Whitespace.&amp;rdquo;&lt;/li&gt; &lt;li&gt;&lt;p&gt;With your new bundle selected, create a new command called &amp;ldquo;Save Current File,&amp;rdquo; and give it the following settings:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Save: Current File&lt;/li&gt; &lt;li&gt;Command(s): blank&lt;/li&gt; &lt;li&gt;Input: None&lt;/li&gt; &lt;li&gt;Output: Discard&lt;/li&gt; &lt;/ul&gt;&lt;/li&gt; &lt;li&gt;Start recording a new macro (Bundles &amp;gt; Macros &amp;gt; Start Recording).&lt;/li&gt; &lt;li&gt;Strip out trailing whitespace (Bundles &amp;gt; Text &amp;gt; Converting/Stripping &amp;gt; Remove Trailing Spaces in Document).&lt;/li&gt; &lt;li&gt;Replace tabs with spaces (Text &amp;gt; Convert &amp;gt; Tabs to Spaces).&lt;/li&gt; &lt;li&gt;Save the current document (Bundles &amp;gt; Formatting &amp;gt; Save Current Document).&lt;/li&gt; &lt;li&gt;Stop recording the macro (Bundles &amp;gt; Macros &amp;gt; Stop Recording).&lt;/li&gt; &lt;li&gt;Save the macro (Bundles &amp;gt; Macros &amp;gt; Save Last Recording). Call it something like &amp;ldquo;Strip Whitespace.&amp;rdquo;&lt;/li&gt; &lt;li&gt;Click in the Activation (Key Equivalent) text field and hit Command+S.&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;Alternatively, we&amp;rsquo;ve packaged the bundle up and put it up on &lt;a href="http://github.com/vigetlabs/whitespace-tmbundle/tree/master"&gt;GitHub&lt;/a&gt;. Instructions for setting it up are on the page, and patches are encouraged.&lt;/p&gt; &lt;h3 id="how_about_you"&gt;How About You?&lt;/h3&gt; &lt;p&gt;This approach is working well for me; I&amp;rsquo;m curious if other people are doing anything like this. If you&amp;rsquo;ve got an alternative way to deal with extraneous whitespace in your code, please tell us how in the comments.&lt;/p&gt;
                  


      &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=cjw0MR842Bk:jMJXBuKDMoY:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=cjw0MR842Bk:jMJXBuKDMoY:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=cjw0MR842Bk:jMJXBuKDMoY:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=cjw0MR842Bk:jMJXBuKDMoY:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=cjw0MR842Bk:jMJXBuKDMoY:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=cjw0MR842Bk:jMJXBuKDMoY:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/VigetExtend/~4/cjw0MR842Bk" height="1" width="1"/&gt;</content>
    <feedburner:origLink>http://www.viget.com/extend/out-damned-tabs/</feedburner:origLink></entry>

    <entry>
      <title>Pain-Free Pretty URLs in Rails</title>
      <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/VigetExtend/~3/dD5_QqacdUg/" />
      <id>tag:viget.com,2009:extend/4.1529</id>
      <published>2009-04-07T20:18:00Z</published>
      <updated>2009-04-08T16:01:50Z</updated>
      <author>
                        <name>Brian Landau, Web Developer</name>
                        <email>brian.landau@viget.com</email>
                  </author>

      <category term="Ruby on Rails" scheme="http://www.viget.com/inspire/category/ruby_on_rails/" label="Ruby on Rails" />
      <category term="Tips and Tricks" scheme="http://www.viget.com/inspire/category/tips_and_tricks/" label="Tips and Tricks" />
      <content type="html">


                 &lt;p&gt;When working recently on a client project in a later stage of development, it became clear that the client wanted &amp;ldquo;pretty&amp;rdquo; URLs for specific models in the application. In particular, they wanted some URLs without an id, meaning the normal Rails &lt;code&gt;to_param&lt;/code&gt; trick was out:&lt;/p&gt; &lt;pre name="code" class="ruby"&gt;def to_param
  "#{self.id}-#{self.name.parameterize}"
end
&lt;/pre&gt; &lt;p&gt;We needed this type of URL on the &lt;code&gt;User&lt;/code&gt; model. We already had a field that was both unique and &amp;ldquo;URL safe,&amp;rdquo; the login field, so for this model we could just do&lt;/p&gt; &lt;pre name="code" class="ruby"&gt;def to_param
  self.login
end
&lt;/pre&gt; &lt;p&gt;On another model, I decided to create a &lt;code&gt;slug&lt;/code&gt; field just for the purpose of pretty URLs. There was already a &lt;code&gt;name&lt;/code&gt; field that had to be unique, so I just created a &lt;code&gt;before_validation&lt;/code&gt; method to handle the creation and updating of the slug:&lt;/p&gt; &lt;pre name="code" class="ruby"&gt;before_validation :update_or_create_slug

def to_param
  slug
end

private
def update_or_create_slug
  if self.new_record? || self.name_changed? || self.slug.blank?
    self.slug = self.name.try(:slugize)
  end
end
&lt;/pre&gt; &lt;p&gt;I used &lt;a href="http://www.websideattractions.com/2008/11/06/squish-the-slug-the-rails-way/" title="Squish the slug the Rails way &amp;raquo; Web Side Attractions by Brian Landau"&gt;my own &amp;ldquo;&lt;code&gt;slugize&lt;/code&gt;&amp;rdquo; method&lt;/a&gt;, as it&amp;rsquo;s a little different from the Rails built-in &lt;code&gt;parameterize&lt;/code&gt;, and I believe it offers a few minor advantages. I also check for three conditions where we need to assign a new slug:&lt;/p&gt; &lt;ol&gt; &lt;li&gt;If it's on a new record.&lt;/li&gt; &lt;li&gt;If the name has changed.&lt;/li&gt; &lt;li&gt;If the slug currently happens to be blank.&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;You&amp;rsquo;ll also notice I use the &amp;ldquo;&lt;code&gt;.try(:slugize)&lt;/code&gt;&amp;rdquo; in case the model is invalid and has &lt;code&gt;nil&lt;/code&gt; for the name.&lt;/p&gt; &lt;p&gt;With all this in place, you&amp;rsquo;d think we&amp;rsquo;d be all set -- or at least I did.&lt;/p&gt; &lt;p&gt;There was one problem left, though. Let&amp;rsquo;s say you have a user who is editing an existing model object in the system, and they try to update it with a blank name. This will result in a user with a blank &lt;code&gt;slug&lt;/code&gt; field. This wouldn&amp;rsquo;t seem too bad, but it results in the edit page not being able to render because &lt;code&gt;to_param&lt;/code&gt; returns a blank string to the named route URL helper on the form, which raises an &lt;code&gt;ActionController::RoutingError&lt;/code&gt; exception.&lt;/p&gt; &lt;p&gt;The solution is to return the old slug value for &lt;code&gt;to_param&lt;/code&gt;:&lt;/p&gt; &lt;pre name="code" class="ruby"&gt;def to_param
  slug_changed? ? slug_was : slug
end
&lt;/pre&gt; &lt;p&gt;With that solved, we&amp;rsquo;re all set to go with pretty, id-free URLs.&lt;/p&gt;&lt;p&gt;If you&amp;rsquo;re in a situation where you don&amp;rsquo;t have a required unique field like we did on this project, you might want to look at &lt;a href="http://www.viget.com/extend/conditionally-customize-your-urls-in-rails" title="Conditionally Customize Your URLs in Rails"&gt;Patrick&amp;rsquo;s solution&lt;/a&gt; from &lt;a href="http://feedstitch.com/"&gt;FeedStitch&lt;/a&gt;.&lt;/p&gt;
                  


      &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=dD5_QqacdUg:txsI_ahP2a4:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=dD5_QqacdUg:txsI_ahP2a4:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=dD5_QqacdUg:txsI_ahP2a4:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=dD5_QqacdUg:txsI_ahP2a4:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=dD5_QqacdUg:txsI_ahP2a4:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=dD5_QqacdUg:txsI_ahP2a4:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/VigetExtend/~4/dD5_QqacdUg" height="1" width="1"/&gt;</content>
    <feedburner:origLink>http://www.viget.com/extend/pain-free-pretty-urls-in-rails/</feedburner:origLink></entry>

    <entry>
      <title>Stop Pissing Off Your Designers</title>
      <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/VigetExtend/~3/EQnUZlhg8gQ/" />
      <id>tag:viget.com,2009:extend/4.1523</id>
      <published>2009-04-01T15:00:00Z</published>
      <updated>2009-04-01T16:02:12Z</updated>
      <author>
                        <name>David Eisinger, Web Developer</name>
                        <email>david.eisinger@viget.com</email>
            <uri>http://www.viget.com/about/team/deisinger</uri>      </author>

      <category term="Events" scheme="http://www.viget.com/inspire/category/events/" label="Events" />
      <content type="html">


                 &lt;p&gt;A few weeks ago, our local &lt;a href="http://refreshthetriangle.org"&gt;Refresh&lt;/a&gt; group pitted me (representing web developers) against Viget designer &lt;a href="http://www.viget.com/about/team/mwagner"&gt;Mindy&lt;/a&gt; in a battle for the ages. Our talk, &amp;ldquo;Ten Things Designers Do That Piss Developers Off (and Vice Versa),&amp;rdquo; offered a back-and-forth look at some of the issues that crop up between web professionals. Despite the overwhelming strength of my arguments, I won&amp;rsquo;t deny that she got some good shots in. Here are some of the key lessons I took away.&lt;/p&gt; &lt;h3&gt;Stay off the bandwagon&lt;/h3&gt; &lt;p&gt;One of Mindy&amp;rsquo;s best points was the tendency of developers, when selecting technologies to use on a project, to go with what&amp;rsquo;s new and hip rather than what&amp;rsquo;s the best fit or what will yield the best final result. I think we can all relate to learning a new technology or technique and then wanting to immediately apply it to whatever we&amp;rsquo;re working on.&lt;/p&gt; &lt;p&gt;Technology bandwagon-jumping goes hand-in-hand with another common problem: over-engineering. In my experience, when a chosen technology is a bad fit for a project, it&amp;rsquo;s typically because it&amp;rsquo;s too powerful. An over-engineered solution is a nightmare for the next developer &amp;mdash; in a past life, I maintained a Spring-powered, Lucene-searchable monstrosity running on dedicated hardware that would have been better served with a WordPress install on Dreamhost.&lt;/p&gt; &lt;p&gt;When selecting technologies, stick with the best fit, whether that&amp;rsquo;s what you know best or what will lead to the best final product. If you&amp;rsquo;re just dying to try out some new technology, do what I do: redo your personal site (in lieu of actually posting any new content to it).&lt;/p&gt; &lt;h3&gt;Avoid the knee-jerk &amp;ldquo;No&amp;rdquo;&lt;/h3&gt; &lt;p&gt;Picture this: you&amp;rsquo;re sitting at your desk one morning, happily reading Hacker News, when an IM window pops up on your screen. It&amp;rsquo;s your PM, and she&amp;rsquo;s got a new feature request from the client. It&amp;rsquo;s not a major change, but it will involve a substantial overhaul of the messaging system you built. What&amp;rsquo;s your response? Be honest &amp;mdash; you give her seventeen reasons why the requested change is a bad idea.&lt;/p&gt; &lt;p&gt;When discussing feature requests, keep in mind that the ultimate goal is to create the best product possible. Requirements change, and though it sucks to complicate elegant solutions, sometimes change is necessary. As an added benefit, if you avoid staying &amp;ldquo;no&amp;rdquo; instinctively, when a &lt;em&gt;truly&lt;/em&gt; bad idea lands on your plate, your objections will carry a lot more weight.&lt;/p&gt; &lt;h3&gt;Remember: you are not the user&lt;/h3&gt; &lt;p&gt;Mindy noted a trait common to many developers: a lack of empathy for the user, or rather, the mistaken idea that we ourselves are the typical user. In other words, developers are prone to creating features that they would want to use, regardless of how well they might serve the actual audience of the site.&lt;/p&gt; &lt;p&gt;When deciding on geeky features, it&amp;rsquo;s important to keep your audience in mind. If you&amp;rsquo;re designing a site about web productivity, by all means, go nuts &amp;mdash; bookmarklets, keyboard shortcuts, customizable RSS feeds, the whole nine yards. But if your site&amp;rsquo;s intended audience is, say, gardening enthusiasts, your time would probably be better spent elsewhere.&lt;/p&gt; &lt;h3&gt;But in the end&lt;/h3&gt; &lt;p&gt;We all want to create the best web sites possible. Disagreements arise about definitions of &amp;ldquo;best&amp;rdquo;; while a designer wants a site that&amp;rsquo;s attractive and intuitive, the developer wants one that is stable and maintainable. In the end, these qualities aren&amp;rsquo;t mutually exclusive &amp;mdash; the highest-quality websites have them all.&lt;/p&gt; &lt;p&gt;Mindy has posted &lt;a href="http://www.viget.com/inspire/stop-driving-your-developers-crazy"&gt;her thoughts&lt;/a&gt; on the talk, and our slides are available on &lt;a href="http://www.slideshare.net/mindywagner/10-things-designers-do-that-piss-developers-off-and-vice-versa"&gt;SlideShare&lt;/a&gt;. And if you&amp;rsquo;re in Durham (or lesser nearby cities), come on out to the next &lt;a href="http://refreshthetriangle.org"&gt;Refresh&lt;/a&gt; meeting.&lt;/p&gt;
                  


      &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=EQnUZlhg8gQ:BOhr_ipx1HU:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=EQnUZlhg8gQ:BOhr_ipx1HU:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=EQnUZlhg8gQ:BOhr_ipx1HU:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=EQnUZlhg8gQ:BOhr_ipx1HU:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=EQnUZlhg8gQ:BOhr_ipx1HU:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=EQnUZlhg8gQ:BOhr_ipx1HU:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/VigetExtend/~4/EQnUZlhg8gQ" height="1" width="1"/&gt;</content>
    <feedburner:origLink>http://www.viget.com/extend/stop-pissing-off-your-designers/</feedburner:origLink></entry>

    <entry>
      <title>Conditionally Customize Your URLs in Rails</title>
      <link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/VigetExtend/~3/qCtc0TU5IXU/" />
      <id>tag:viget.com,2009:extend/4.1520</id>
      <published>2009-03-31T17:49:00Z</published>
      <updated>2009-04-09T16:48:35Z</updated>
      <author>
                        <name>Patrick Reagan, Development Director</name>
                        <email>patrick.reagan@viget.com</email>
            <uri>http://www.viget.com/about/team/preagan</uri>      </author>

      <category term="Ruby on Rails" scheme="http://www.viget.com/inspire/category/ruby_on_rails/" label="Ruby on Rails" />
      <category term="Tips and Tricks" scheme="http://www.viget.com/inspire/category/tips_and_tricks/" label="Tips and Tricks" />
      <content type="html">


                 &lt;p&gt;Our primary goal when building &lt;a href="http://feedstitch.com"&gt;FeedStitch&lt;/a&gt; was to encourage users to share their feeds by making the process both simple and easily customizable.  In the interest of simplicity, we went with OpenID (using &lt;a href="http://rpxnow.com/"&gt;RPX&lt;/a&gt; through the &lt;a href="http://github.com/grosser/rpx_now/tree/master"&gt;rpx_now plugin&lt;/a&gt;) to combine the registration and login processes so that users could quickly start creating feeds.&lt;/p&gt; &lt;p&gt;As part of encouraging sharing, we wanted to allow users to customize the URL that they could give out to others.  The challenge here was keeping this functionality from interrupting the sign-in process while understanding that users may opt out of the customization step altogether.&lt;/p&gt; &lt;p&gt;We implemented this using a quick &lt;tt&gt;to_param&lt;/tt&gt; trick - here's how you can do the same in your Rails application.&lt;/p&gt;
                 &lt;h3&gt;The User Model&lt;/h3&gt; &lt;p&gt;Start generating:&lt;/p&gt; &lt;pre name="code" class="bash"&gt;$ ./script/generate model user username:string
$ rake db:migrate
&lt;/pre&gt; &lt;p&gt;Our user model is simple - we only ask for an optional username.  There are only a few modifications needed to make it useful:&lt;/p&gt; &lt;pre name="code" class="ruby"&gt;class User &amp;lt; ActiveRecord::Base
  validates_presence_of :username, :on =&amp;gt; :update
  validates_format_of :username, :with =&amp;gt; /^[a-z]$/i, :on =&amp;gt; :update

  def self.find_by_identifier(identifier)
    find(:first, :conditions =&amp;gt; ['id = ? OR username = ?', identifier, identifier])
  end

  def to_param
    username || id
  end
end
&lt;/pre&gt; &lt;p&gt;This will allow us to find a user by either ID or username - the &lt;tt&gt;to_param&lt;/tt&gt; magic will be required later.&lt;/p&gt; &lt;h3&gt;The Users Controller&lt;/h3&gt; &lt;p&gt;We'll need a list of users and a way to view an individual user's profile:&lt;/p&gt; &lt;pre name="code" class="bash"&gt;$ ./script/generate controller users index show
&lt;/pre&gt; &lt;pre name="code" class="ruby"&gt;# config/routes.rb
ActionController::Routing::Routes.draw do |map|
  map.user ':identifier', :controller =&amp;gt; 'users', :action =&amp;gt; 'show'
  map.root :controller =&amp;gt; 'users', :action =&amp;gt; 'index'
end
&lt;/pre&gt; &lt;p&gt;Our controller is pretty standard, the only change is using our custom finder in the &lt;tt&gt;show&lt;/tt&gt; action:&lt;/p&gt; &lt;pre name="code" class="ruby"&gt;class UsersController &amp;lt; ApplicationController
  def index
    @users = User.all
  end

  def show
    @user = User.find_by_identifier(params[:identifier])
  end
end
&lt;/pre&gt; &lt;h3&gt;Views&lt;/h3&gt; &lt;p&gt;The view logic is simple, we can use the &lt;tt&gt;user_path&lt;/tt&gt; helper as usual:&lt;/p&gt; &lt;pre name="code" class="ruby"&gt;# views/users/index.html.erb
&amp;lt;% @users.each do |user| %&amp;gt;
  &amp;lt;p&amp;gt;&amp;lt;%= link_to (user.username || 'N/A'), user_path(user) %&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;% end %&amp;gt;
&lt;/pre&gt; &lt;pre name="code" class="ruby"&gt;# views/users/show.html.erb
&amp;lt;p&amp;gt;User Id: &amp;lt;%= @user.id %&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;Username: &amp;lt;%= @user.username || 'N/A' %&amp;gt;&amp;lt;/p&amp;gt;
&lt;/pre&gt; &lt;h3&gt;Testing it Out&lt;/h3&gt; &lt;p&gt;Now that all the pieces are in place, create some users to see how this all comes together:&lt;/p&gt; &lt;pre name="code"&gt;$ ./script/console
&amp;gt;&amp;gt; User.create!
&amp;gt;&amp;gt; User.create!(:username =&amp;gt; 'reagent')
&lt;/pre&gt; &lt;p&gt;Hit the homepage and click on the links for the individual user pages.  You'll see that the URL switches between the ID and username as appropriate.&lt;/p&gt; &lt;h3&gt;How Does It Work?&lt;/h3&gt; &lt;p&gt;The secret to this is how Rails generates the URL identifier when ActiveRecord instances are passed to a URL helper.  The &lt;tt&gt;to_param&lt;/tt&gt; method will return a username first and then fall back to returning the ID.  This data is then exposed in the URL and gets picked up by the custom finder that we defined on the User model.&lt;/p&gt; &lt;p&gt;An interesting side effect of this process is that all the user profiles that have usernames are still available using their unique IDs.  This can be prevented with a little more work, but the benefits of such an approach are minimal.&lt;/p&gt; &lt;p&gt;&lt;em&gt;&lt;strong&gt;Update:&lt;/strong&gt;&lt;/em&gt; If you have a field that's required for URL customization, check out &lt;a href="http://www.viget.com/about/team/blandau"&gt;Brian's&lt;/a&gt; &lt;a href="http://www.viget.com/extend/pain-free-pretty-urls-in-rails/"&gt;solution to the problem&lt;/a&gt;.&lt;/p&gt; 


      &lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=qCtc0TU5IXU:e78cJgP3HTY:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=qCtc0TU5IXU:e78cJgP3HTY:TzevzKxY174"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?d=TzevzKxY174" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=qCtc0TU5IXU:e78cJgP3HTY:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=qCtc0TU5IXU:e78cJgP3HTY:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/VigetExtend?a=qCtc0TU5IXU:e78cJgP3HTY:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/VigetExtend?i=qCtc0TU5IXU:e78cJgP3HTY:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/VigetExtend/~4/qCtc0TU5IXU" height="1" width="1"/&gt;</content>
    <feedburner:origLink>http://www.viget.com/extend/conditionally-customize-your-urls-in-rails/</feedburner:origLink></entry>


</feed>
