<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">
 
 <title>Digressions of a Drive-by Hacker</title>
 
 <link href="http://mojodna.net/" />
 <updated>2009-11-01T11:26:31-08:00</updated>
 <id>http://mojodna.net/</id>
 <author>
   <name>Seth Fitzsimmons</name>
   <email>seth@mojodna.net</email>
 </author>
 
 
 <link rel="self" href="http://feeds.feedburner.com/mojodnadotnet" type="application/atom+xml" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com" /><entry>
   <title>CSSpring Cleaning</title>
   <link href="http://feedproxy.google.com/~r/mojodnadotnet/~3/8PTR_F1drrA/csspring-cleaning.html" />
   <updated>2009-09-08T00:00:00-07:00</updated>
   <id>http://mojodna.net/2009/09/08/csspring-cleaning</id>
   <content type="html">&lt;h2 id='csspring_cleaning'&gt;CSSpring Cleaning&lt;/h2&gt;

&lt;p&gt;I was talking to some front-end devs a couple weeks ago about maintaining CSS on an evolving website. In particular, how to figure out which rules are no longer being applied (remnants from experiments / old designs / whatever) and remove them.&lt;/p&gt;

&lt;p&gt;I remembered seeing something a few months ago that claimed to do this. After digging around on &lt;a href='http://github.com/'&gt;GitHub&lt;/a&gt; for a bit, I re-found &lt;a href='http://github.com/aanand/deadweight'&gt;deadweight&lt;/a&gt;. Unfortunately, after looking at it briefly, I realized that it made some pretty strong assumptions, mainly about being run in the context of a Rails app.&lt;/p&gt;

&lt;p&gt;Time for some hacking.&lt;/p&gt;

&lt;p&gt;&lt;a href='http://github.com/mojodna/deadweight'&gt;My fork&lt;/a&gt; can be installed with RubyGems (&lt;code&gt;0.0.5&lt;/code&gt; is required for &lt;em&gt;deflate&lt;/em&gt; and &lt;em&gt;gzip&lt;/em&gt; content-encodings):&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;sudo gem install mojodna-deadweight -s http://gems.github.com/
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The first step was to make it accessible as a command-line utility (ideally in the spirit of the &lt;a href='http://en.wikipedia.org/wiki/Unix_philosophy'&gt;UNIX Philosophy&lt;/a&gt;).&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;deadweight -s styles.css -s ie.css index.html about.html
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;(This will check &lt;code&gt;styles.css&lt;/code&gt; and &lt;code&gt;ie.css&lt;/code&gt; against &lt;code&gt;index.html&lt;/code&gt; and &lt;code&gt;about.html&lt;/code&gt;, outputting unused rules.)&lt;/p&gt;

&lt;p&gt;It also supports input from pipes, meaning that you can chain it together and filter orphaned rules without writing them to a file (you&amp;#8217;ll have to configure logging in order to limit output).&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;cat styles.css | deadweight index.html
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Deadweight now contains an experimental &lt;code&gt;-L&lt;/code&gt; argument that causes it to use &lt;a href='http://github.com/defunkt/lyndon'&gt;Lyndon&lt;/a&gt; if the &lt;code&gt;lyndon&lt;/code&gt; executable is in your &lt;code&gt;$PATH&lt;/code&gt; (only possible on OS X with MacRuby installed). It was a bit flaky in my testing, but I may be using an older version of MacRuby. Great idea though. &lt;a href='http://ozmm.org/posts/lyndon.html'&gt;Check this post for more info on what it does / how it works.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next step was to expose it as an HTTP proxy. It uses &lt;code&gt;8002&lt;/code&gt; by default for no particular reason. (&lt;a href='/2009/08/21/exploring-oauth-protected-apis.html'&gt;oauth-proxy&lt;/a&gt; uses &lt;code&gt;8001&lt;/code&gt;.)&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;deadweight -l deadweight.log -s styles.css -w http://github.com/ -P
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;(This dumps log output to &lt;code&gt;deadweight.log&lt;/code&gt; in &lt;code&gt;$PWD&lt;/code&gt; and matches &lt;code&gt;http://github.com/*&lt;/code&gt; against &lt;code&gt;styles.css&lt;/code&gt;.)&lt;/p&gt;

&lt;p&gt;It&amp;#8217;s probably most useful as an HTTP proxy; you can start it with a list of target stylesheets, configure your browser to load pages through it (sorry, it gets &lt;strong&gt;really&lt;/strong&gt; slow when requesting &lt;em&gt;gzip&lt;/em&gt;- or &lt;em&gt;deflate&lt;/em&gt;-encoded pages, which is a lot of the time), and watch the output to see how many rules it&amp;#8217;s hit. When you&amp;#8217;re done, hit &lt;code&gt;^C&lt;/code&gt; to stop it and (optionally) output the remaining CSS rules into a file for you to examine (&lt;code&gt;-o orphans.css&lt;/code&gt; will do this for you when it shuts down).&lt;/p&gt;

&lt;p&gt;If you&amp;#8217;re lucky, the file containing orphaned CSS will be empty. If not, re-start the proxy with the orphaned CSS as the target and continue browsing until you&amp;#8217;re ready to start looking for matches by hand (this is necessary, as it won&amp;#8217;t catch classes applied by JavaScript).&lt;/p&gt;

&lt;h3 id='things_to_improve'&gt;Things to Improve&lt;/h3&gt;

&lt;p&gt;&lt;a href='http://en.wikipedia.org/wiki/WEBrick'&gt;WEBrick&lt;/a&gt;&amp;#8217;s HTTP proxy implementation leaves a lot to be desired (it&amp;#8217;s nowhere on the same level as &lt;a href='http://twistedmatrix.com/trac/'&gt;Twisted&lt;/a&gt;&amp;#8217;s), but it&amp;#8217;s the only game in town. &lt;a href='http://www.igvita.com/'&gt;Ilya Grigorik&lt;/a&gt; has done some excellent work with &lt;a href='http://github.com/igrigorik/em-proxy'&gt;EventMachine and proxies&lt;/a&gt;, but it doesn&amp;#8217;t appear that anyone has built a general-purpose HTTP proxy that easily supports header modification. It&amp;#8217;s not a particularly hard problem and being able to move deadweight&amp;#8217;s proxy implementation to such a base would provide the ability to turn off compression and thus speed things up considerably.&lt;/p&gt;

&lt;p&gt;One could also imagine a world in which the proxy server also contains a web interface that displays unmatched CSS rules and updates on the fly. EventMachine would do the trick here, too.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/mojodnadotnet/~4/8PTR_F1drrA" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://mojodna.net/2009/09/08/csspring-cleaning.html</feedburner:origLink></entry>
 
 <entry>
   <title>Exploring OAuth-Protected APIs</title>
   <link href="http://feedproxy.google.com/~r/mojodnadotnet/~3/CrbDF0aP3MM/exploring-oauth-protected-apis.html" />
   <updated>2009-08-21T00:00:00-07:00</updated>
   <id>http://mojodna.net/2009/08/21/exploring-oauth-protected-apis</id>
   <content type="html">&lt;h2 id='exploring_oauthprotected_apis'&gt;Exploring OAuth-Protected APIs&lt;/h2&gt;

&lt;p&gt;From time to time I need to debug OAuth-protected APIs, checking response headers and examining XML and JSON payloads. &lt;code&gt;curl&lt;/code&gt; generally rocks for this sort of thing, but when the APIs in question are protected with OAuth, things break down. Likewise for benchmarking (&lt;code&gt;ab&lt;/code&gt;, &lt;code&gt;httperf&lt;/code&gt;, etc.) and exploration&amp;#8211;isn&amp;#8217;t it nice to browse APIs that return XML in Firefox?&lt;/p&gt;

&lt;p&gt;This needn&amp;#8217;t to be the case.&lt;/p&gt;

&lt;h3 id='enter_'&gt;Enter &lt;code&gt;oauth-proxy&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;This is why I wrote &lt;a href='http://github.com/mojodna/oauth-proxy'&gt;&lt;code&gt;oauth-proxy&lt;/code&gt;&lt;/a&gt;. It does what it says on the tin: it acts a proxy server that transparently adds OAuth headers to requests.&lt;/p&gt;

&lt;p&gt;There are 3 steps to using it. First, clone the repository:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;git clone git://github.com/mojodna/oauth-proxy.git
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Then, start it:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;&lt;span class='nb'&gt;cd &lt;/span&gt;oauth-proxy
&lt;span class='nv'&gt;$ &lt;/span&gt;twistd -n oauth_proxy &lt;span class='se'&gt;\&lt;/span&gt;
    --consumer-key &amp;lt;consumer key&amp;gt; &lt;span class='se'&gt;\&lt;/span&gt;
    --consumer-secret &amp;lt;consumer secret&amp;gt; &lt;span class='se'&gt;\&lt;/span&gt;
    &lt;span class='o'&gt;[&lt;/span&gt;--token &amp;lt;token&amp;gt;&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='se'&gt;\&lt;/span&gt;
    &lt;span class='o'&gt;[&lt;/span&gt;--token-secret &amp;lt;token secret&amp;gt;&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='se'&gt;\&lt;/span&gt;
    &lt;span class='o'&gt;[&lt;/span&gt;-p &amp;lt;proxy port&amp;gt;&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='se'&gt;\&lt;/span&gt;
    &lt;span class='o'&gt;[&lt;/span&gt;--ssl&lt;span class='o'&gt;]&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you&amp;#8217;re accessing a resource that only requires 2-legged OAuth, you can omit &lt;code&gt;--token&lt;/code&gt; and &lt;code&gt;--token-secret&lt;/code&gt;. The proxy port defaults to &lt;code&gt;8001&lt;/code&gt;, and &lt;code&gt;--ssl&lt;/code&gt; is used if you&amp;#8217;re proxying connections to a provider that requires SSL (Fire Eagle, for example).&lt;/p&gt;

&lt;p&gt;In order to run the proxy, you&amp;#8217;ll need Python and a modern version of &lt;a href='http://twistedmatrix.com/'&gt;Twisted&lt;/a&gt; (I use 8.2.0). OS X Leopard comes with Python 2.5.1 (which is sufficient) and an old version of Twisted (2.5.0), so you&amp;#8217;ll need to update that:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;easy_install twisted
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Once it&amp;#8217;s been started, use &lt;code&gt;curl&lt;/code&gt; to make requests through it:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;curl -x localhost:8001 http://host.name/path
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You can also benchmark your APIs through it using ApacheBench (&lt;code&gt;ab&lt;/code&gt;, as it includes support for HTTP proxies). Note that you are introducing additional overhead by proxying the request, so your numbers may be a bit off.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;ab -X localhost:8001 http://host.name/path
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Firefox (and browsers in general) supports HTTP proxies, so you can add a &amp;#8220;Manual Proxy Configuration&amp;#8221; and pass requests through &lt;code&gt;oauth-proxy&lt;/code&gt; to explore APIs in the comfort of your favorite browser. By default, all requests that Firefox makes will go through the proxy and be signed, which may confuse other websites you visit (and will inadvertently reveal your consumer key and access token but not the corresponding secrets).&lt;/p&gt;

&lt;h3 id='access_tokens_sold_separately'&gt;Access Tokens Sold Separately&lt;/h3&gt;

&lt;p&gt;If you&amp;#8217;re accessing a resource that requires 3-legged OAuth, you&amp;#8217;ll need a token. You may already have one, but if you don&amp;#8217;t, you can use the &lt;a href='http://github.com/mojodna/oauth'&gt;OAuth library for Ruby&lt;/a&gt; to obtain one.&lt;/p&gt;

&lt;p&gt;First, install the gem (0.3.5 is current as of this writing):&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;sudo gem install oauth
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Then, trigger the authorization process from the command-line:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;oauth &lt;span class='se'&gt;\&lt;/span&gt;
    --consumer-key &amp;lt;consumer key&amp;gt; &lt;span class='se'&gt;\&lt;/span&gt;
    --consumer-secret &amp;lt;consumer secret&amp;gt; &lt;span class='se'&gt;\&lt;/span&gt;
    --access-token-url http://host.name/path/to/access_token &lt;span class='se'&gt;\&lt;/span&gt;
    --authorize-url http://host.name/path/to/authorize &lt;span class='se'&gt;\&lt;/span&gt;
    --request-token-url http://host.name/path/to/request_token &lt;span class='se'&gt;\&lt;/span&gt;
    authorize
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Follow the prompts, and voilà, an access token and secret that you can use with &lt;code&gt;oauth-proxy&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id='a_concrete_example'&gt;A Concrete Example&lt;/h3&gt;

&lt;p&gt;Twitter&amp;#8217;s popular, right? Let&amp;#8217;s use that.&lt;/p&gt;

&lt;p&gt;First, &lt;a href='http://twitter.com/apps/new'&gt;register an application&lt;/a&gt; to get a consumer key and secret. I registered as a &amp;#8220;client&amp;#8221; application, since the command-line still doesn&amp;#8217;t have a callback url.&lt;/p&gt;

&lt;p&gt;Once that&amp;#8217;s done, you&amp;#8217;ll get a set of credentials that can be used to initiate the authorization process.&lt;/p&gt;

&lt;p&gt;Let&amp;#8217;s authorize.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;oauth &lt;span class='se'&gt;\&lt;/span&gt;
  --consumer-key &amp;lt;consumer key&amp;gt; &lt;span class='se'&gt;\&lt;/span&gt;
  --consumer-secret &amp;lt;consumer secret&amp;gt; &lt;span class='se'&gt;\&lt;/span&gt;
  --access-token-url http://twitter.com/oauth/access_token &lt;span class='se'&gt;\&lt;/span&gt;
  --authorize-url http://twitter.com/oauth/authorize &lt;span class='se'&gt;\&lt;/span&gt;
  --request-token-url http://twitter.com/oauth/request_token &lt;span class='se'&gt;\&lt;/span&gt;
  authorize
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;After following the prompts, you&amp;#8217;ll get something back that looks like this:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='l-Scalar-Plain'&gt;oauth_token_secret&lt;/span&gt;&lt;span class='p-Indicator'&gt;:&lt;/span&gt; &lt;span class='p-Indicator'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;redacted&lt;/span&gt;&lt;span class='p-Indicator'&gt;]&lt;/span&gt;
&lt;span class='l-Scalar-Plain'&gt;oauth_token&lt;/span&gt;&lt;span class='p-Indicator'&gt;:&lt;/span&gt; &lt;span class='p-Indicator'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;redacted&lt;/span&gt;&lt;span class='p-Indicator'&gt;]&lt;/span&gt;
&lt;span class='l-Scalar-Plain'&gt;user_id&lt;/span&gt;&lt;span class='p-Indicator'&gt;:&lt;/span&gt; &lt;span class='p-Indicator'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;redacted&lt;/span&gt;&lt;span class='p-Indicator'&gt;]&lt;/span&gt;
&lt;span class='l-Scalar-Plain'&gt;screen_name&lt;/span&gt;&lt;span class='p-Indicator'&gt;:&lt;/span&gt; &lt;span class='p-Indicator'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;redacted&lt;/span&gt;&lt;span class='p-Indicator'&gt;]&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You can then use those values to start the OAuth proxy:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;twistd -n oauth_proxy &lt;span class='se'&gt;\&lt;/span&gt;
    --consumer-key &amp;lt;consumer key&amp;gt; &lt;span class='se'&gt;\&lt;/span&gt;
    --consumer-secret &amp;lt;consumer secret&amp;gt; &lt;span class='se'&gt;\&lt;/span&gt;
    --token &amp;lt;access token&amp;gt; &lt;span class='se'&gt;\&lt;/span&gt;
    --token-secret &amp;lt;token secret&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now we&amp;#8217;re set. Let&amp;#8217;s go exploring:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;curl -sx http://localhost:8001 &lt;span class='se'&gt;\&lt;/span&gt;
    http://twitter.com/statuses/friends_timeline.json | &lt;span class='se'&gt;\&lt;/span&gt;
    jsonpretty | pygmentize -l js
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You&amp;#8217;ll get exactly what you&amp;#8217;re expecting &lt;strong&gt;and&lt;/strong&gt; you&amp;#8217;ll be using OAuth (this is a partially contrived example since Twitter still supports HTTP Base Auth).&lt;/p&gt;

&lt;p&gt;(&lt;a href='http://github.com/nicksieger/jsonpretty'&gt;&lt;code&gt;jsonpretty&lt;/code&gt;&lt;/a&gt; rocks. &lt;code&gt;pygmentize&lt;/code&gt; (&lt;code&gt;easy install pygments&lt;/code&gt;) makes it easier to make sense of the chaos.)&lt;/p&gt;

&lt;p&gt;That&amp;#8217;s all. I use this stuff all the time and can&amp;#8217;t imagine debugging APIs without it.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/mojodnadotnet/~4/CrbDF0aP3MM" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://mojodna.net/2009/08/21/exploring-oauth-protected-apis.html</feedburner:origLink></entry>
 
 <entry>
   <title>Subscribing to Wordpress.com with Switchboard</title>
   <link href="http://feedproxy.google.com/~r/mojodnadotnet/~3/IpgMj5GlUmc/subscribing-to-wordpress-com-with-switchboard.html" />
   <updated>2009-07-21T00:00:00-07:00</updated>
   <id>http://mojodna.net/2009/07/21/subscribing-to-wordpress-com-with-switchboard</id>
   <content type="html">&lt;h2 id='subscribing_to_wordpresscom_with_switchboard'&gt;Subscribing to Wordpress.com with Switchboard&lt;/h2&gt;

&lt;p&gt;Last week, Andy Skelton announced &lt;a href='http://andy.wordpress.com/2009/07/16/real-time-wordpress-com-subscription/'&gt;Real-time Wordpress.com subscriptions&lt;/a&gt; via XMPP. It does IM, but more interestingly for me, it supports PubSub. Had I realized this when he posted that, I would have included it as an example in &lt;a href='http://mojodna.net/2009/07/16/switchboard-curl-for-xmpp.html'&gt;switchboard : XMPP :: curl : HTTP&lt;/a&gt; alongside Fire Eagle and Superfeedr. What&amp;#8217;s done is done, however, so here&amp;#8217;s a quick rundown of how to interact with Wordpress.com using Switchboard.&lt;/p&gt;

&lt;p&gt;First, let&amp;#8217;s get a basic idea of the identities and features that Wordpress.com advertises:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard disco --target pubsub.im.wordpress.com &lt;span class='nv'&gt;info&lt;/span&gt;
&lt;span class='o'&gt;=&lt;/span&gt;&amp;gt; Switchboard started.
Discovery Info &lt;span class='k'&gt;for &lt;/span&gt;pubsub.im.wordpress.com
Identities:
  pubsub/service: Publish-Subscribe

Features:
  http://jabber.org/protocol/disco#info
  http://jabber.org/protocol/disco#items
  http://jabber.org/protocol/pubsub
  vcard-temp
  http://jabber.org/protocol/pubsub#access-authorize
  http://jabber.org/protocol/pubsub#access-open
  http://jabber.org/protocol/pubsub#access-presence
  http://jabber.org/protocol/pubsub#access-whitelist
  http://jabber.org/protocol/pubsub#auto-create
  http://jabber.org/protocol/pubsub#collections
  http://jabber.org/protocol/pubsub#config-node
  http://jabber.org/protocol/pubsub#create-and-configure
  http://jabber.org/protocol/pubsub#create-nodes
  http://jabber.org/protocol/pubsub#delete-items
  http://jabber.org/protocol/pubsub#delete-nodes
  http://jabber.org/protocol/pubsub#instant-nodes
  http://jabber.org/protocol/pubsub#item-ids
  http://jabber.org/protocol/pubsub#last-published
  http://jabber.org/protocol/pubsub#manage-subscriptions
  http://jabber.org/protocol/pubsub#member-affiliation
  http://jabber.org/protocol/pubsub#modify-affiliations
  http://jabber.org/protocol/pubsub#multi-subscribe
  http://jabber.org/protocol/pubsub#outcast-affiliation
  http://jabber.org/protocol/pubsub#persistent-items
  http://jabber.org/protocol/pubsub#presence-notifications
  http://jabber.org/protocol/pubsub#presence-subscribe
  http://jabber.org/protocol/pubsub#publish
  http://jabber.org/protocol/pubsub#publisher-affiliation
  http://jabber.org/protocol/pubsub#purge-nodes
  http://jabber.org/protocol/pubsub#retract-items
  http://jabber.org/protocol/pubsub#retrieve-affiliations
  http://jabber.org/protocol/pubsub#retrieve-default
  http://jabber.org/protocol/pubsub#retrieve-items
  http://jabber.org/protocol/pubsub#retrieve-subscriptions
  http://jabber.org/protocol/pubsub#subscribe
  http://jabber.org/protocol/pubsub#subscription-notifications
  http://jabber.org/protocol/pubsub#subscription-options
Shutdown initiated.
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Lo, it supports PubSub! (But it doesn&amp;#8217;t support node discovery, so we can&amp;#8217;t get a list of available nodes.) Fortunately, Andy&amp;#8217;s post includes a couple. Let&amp;#8217;s subscribe:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard pubsub --server pubsub.im.wordpress.com &lt;span class='se'&gt;\&lt;/span&gt;
    --node /blogs/andy.wordpress.com &lt;span class='nv'&gt;subscribe&lt;/span&gt;
&lt;span class='o'&gt;=&lt;/span&gt;&amp;gt; Switchboard started.
Subscribe successful.
Shutdown initiated.

&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard pubsub --server pubsub.im.wordpress.com &lt;span class='se'&gt;\&lt;/span&gt;
    --node /blogs/andy.wordpress.com/comments &lt;span class='nv'&gt;subscribe&lt;/span&gt;
&lt;span class='o'&gt;=&lt;/span&gt;&amp;gt; Switchboard started.
Subscribe successful.
Shutdown initiated.

&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard pubsub --server pubsub.im.wordpress.com --node &lt;span class='se'&gt;\&lt;/span&gt;
     /blogs/andy.wordpress.com/2009/07/16/real-time-wordpress-com-subscription/ &lt;span class='se'&gt;\&lt;/span&gt;
    &lt;span class='nv'&gt;subscribe&lt;/span&gt;
&lt;span class='o'&gt;=&lt;/span&gt;&amp;gt; Switchboard started.
Subscribe successful.
Shutdown initiated.
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Switchboard claimed that that worked, but let&amp;#8217;s double-check and list our subscriptions:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard pubsub --server pubsub.im.wordpress.com &lt;span class='nv'&gt;subscriptions&lt;/span&gt;
&lt;span class='o'&gt;=&lt;/span&gt;&amp;gt; Switchboard started.
Subscriptions:
4E039BC19E028: me@jabber.org &lt;span class='o'&gt;=&lt;/span&gt;&amp;gt; /blogs/andy.wordpress.com &lt;span class='o'&gt;(&lt;/span&gt;subscribed&lt;span class='o'&gt;)&lt;/span&gt;
4E039D2A2B691: me@jabber.org &lt;span class='o'&gt;=&lt;/span&gt;&amp;gt; /blogs/andy.wordpress.com/2009/07/16/real-time-wordpress-com-subscription &lt;span class='o'&gt;(&lt;/span&gt;subscribed&lt;span class='o'&gt;)&lt;/span&gt;
4E039BC7E8A69: me@jabber.org &lt;span class='o'&gt;=&lt;/span&gt;&amp;gt; /blogs/andy.wordpress.com/comments &lt;span class='o'&gt;(&lt;/span&gt;subscribed&lt;span class='o'&gt;)&lt;/span&gt;
Shutdown initiated.
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Actually, there was no need to subscribe to comments for &amp;#8220;Real-time Wordpress.com subscription&amp;#8221; because we&amp;#8217;re also subscribed to the comments feed. Let&amp;#8217;s unsubscribe and check our subscriptions again:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard pubsub --server pubsub.im.wordpress.com --node &lt;span class='se'&gt;\&lt;/span&gt;
     /blogs/andy.wordpress.com/2009/07/16/real-time-wordpress-com-subscription &lt;span class='se'&gt;\&lt;/span&gt;
    &lt;span class='nv'&gt;unsubscribe&lt;/span&gt;
&lt;span class='o'&gt;=&lt;/span&gt;&amp;gt; Switchboard started.
Unsubscribe was successful.
Shutdown initiated.

&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard pubsub --server pubsub.im.wordpress.com &lt;span class='nv'&gt;subscriptions&lt;/span&gt;
&lt;span class='o'&gt;=&lt;/span&gt;&amp;gt; Switchboard started.
Subscriptions:
4E039BC19E028: me@jabber.org &lt;span class='o'&gt;=&lt;/span&gt;&amp;gt; /blogs/andy.wordpress.com &lt;span class='o'&gt;(&lt;/span&gt;subscribed&lt;span class='o'&gt;)&lt;/span&gt;
4E039BC7E8A69: me@jabber.org &lt;span class='o'&gt;=&lt;/span&gt;&amp;gt; /blogs/andy.wordpress.com/comments &lt;span class='o'&gt;(&lt;/span&gt;subscribed&lt;span class='o'&gt;)&lt;/span&gt;
Shutdown initiated.
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Ok, all good. Now let&amp;#8217;s hope Andy posts something or gets a comment on something so we can listen for it:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard pubsub --server pubsub.im.wordpress.com listen
&lt;span class='o'&gt;(&lt;/span&gt;sample post and comment &amp;lt;event /&amp;gt; stanzas to come&lt;span class='o'&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;For bonus points, let&amp;#8217;s implement a &lt;code&gt;WordpressJack&lt;/code&gt;:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='k'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;WordpressJack&lt;/span&gt;
  &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nc'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nf'&gt;connect&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;switchboard&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;settings&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='n'&gt;switchboard&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;plug!&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;AutoAcceptJack&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='no'&gt;NotifyJack&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='no'&gt;PubSubJack&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='n'&gt;switchboard&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;hook&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='ss'&gt;:new_post&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:new_comment&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;

    &lt;span class='n'&gt;switchboard&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;on_pubsub_event&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;event&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;
      &lt;span class='n'&gt;event&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;payload&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;each&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;payload&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;
        &lt;span class='n'&gt;payload&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;elements&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;each&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;item&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;
          &lt;span class='c1'&gt;# grab the feed out of the payload&lt;/span&gt;
          &lt;span class='n'&gt;feed&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;item&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;first_element&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;feed&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
          
          &lt;span class='c1'&gt;# TODO determine whether it&amp;#39;s a comment or a post&lt;/span&gt;
          &lt;span class='c1'&gt;# for now, we&amp;#39;ll assume it&amp;#39;s a post&lt;/span&gt;
          &lt;span class='c1'&gt;# invoke the :new_post hook with the feed subtree&lt;/span&gt;
          &lt;span class='c1'&gt;# (later: parse it with FeedTools or something)&lt;/span&gt;
          &lt;span class='n'&gt;on&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='ss'&gt;:new_post&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;feed&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
        &lt;span class='k'&gt;end&lt;/span&gt;
      &lt;span class='k'&gt;end&lt;/span&gt;
    &lt;span class='k'&gt;end&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;And a quick-and-dirty consumer:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c1'&gt;#!/usr/bin/env ruby -rubygems&lt;/span&gt;

&lt;span class='nb'&gt;require&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;switchboard&amp;#39;&lt;/span&gt;
&lt;span class='nb'&gt;require&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;wordpress_jack&amp;#39;&lt;/span&gt;

&lt;span class='n'&gt;switchboard&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Switchboard&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;Client&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;
&lt;span class='n'&gt;switchboard&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;plug!&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;WordpressJack&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;

&lt;span class='n'&gt;switchboard&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;on_new_post&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;post&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;
  &lt;span class='nb'&gt;puts&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;A new post was received:&amp;quot;&lt;/span&gt;
  &lt;span class='nb'&gt;puts&lt;/span&gt; &lt;span class='n'&gt;post&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;

&lt;span class='n'&gt;switchboard&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;on_new_comment&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;comment&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;
  &lt;span class='nb'&gt;puts&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;A new comment was received:&amp;quot;&lt;/span&gt;
  &lt;span class='nb'&gt;puts&lt;/span&gt; &lt;span class='n'&gt;comment&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;

&lt;span class='n'&gt;switchboard&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;run!&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;There are a number of improvements that can be made here (this is the &amp;#8220;making blind assumptions about responses&amp;#8221; version). Go wild.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/mojodnadotnet/~4/IpgMj5GlUmc" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://mojodna.net/2009/07/21/subscribing-to-wordpress-com-with-switchboard.html</feedburner:origLink></entry>
 
 <entry>
   <title>Switchboard as a Framework</title>
   <link href="http://feedproxy.google.com/~r/mojodnadotnet/~3/D81DnOug2Gg/switchboard-as-a-framework.html" />
   <updated>2009-07-19T00:00:00-07:00</updated>
   <id>http://mojodna.net/2009/07/19/switchboard-as-a-framework</id>
   <content type="html">&lt;h2 id='switchboard_as_a_framework'&gt;Switchboard as a Framework&lt;/h2&gt;

&lt;p&gt;While Switchboard is a &lt;a href='http://mojodna.net/2009/07/16/switchboard-curl-for-xmpp.html'&gt;useful tool for debugging and probing XMPP services&lt;/a&gt;, it&amp;#8217;s also a convenient and full-featured framework for building clients and components.&lt;/p&gt;

&lt;h3 id='a_simple_client'&gt;A Simple Client&lt;/h3&gt;

&lt;p&gt;This is the simplest client (bot) I could think of. It listens for input and replies with whatever was sent in the first place.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c1'&gt;#!/usr/bin/env ruby -rubygems&lt;/span&gt;

&lt;span class='nb'&gt;require&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;switchboard&amp;#39;&lt;/span&gt;

&lt;span class='n'&gt;switchboard&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Switchboard&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;Client&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;
&lt;span class='n'&gt;switchboard&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;plug!&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;AutoAcceptJack&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='no'&gt;EchoJack&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='no'&gt;NotifyJack&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='n'&gt;switchboard&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;run!&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Let&amp;#8217;s break it down.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c1'&gt;#!/usr/bin/env ruby -rubygems&lt;/span&gt;

&lt;span class='nb'&gt;require&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;switchboard&amp;#39;&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Um, I hope this is pretty straightforward.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='n'&gt;switchboard&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Switchboard&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;Client&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This instantiates &lt;code&gt;Switchboard::Client&lt;/code&gt; with some default options, mainly &lt;code&gt;spin = true&lt;/code&gt; (this is the 2nd argument). &lt;code&gt;spin&lt;/code&gt; means that &lt;code&gt;#run!&lt;/code&gt; will cause the process to run in a loop and not return immediately. &lt;code&gt;^C&lt;/code&gt; will interrupt the process and shut it down cleanly. (&lt;code&gt;^C&lt;/code&gt; a second time if it hangs.)&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='n'&gt;switchboard&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;plug!&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;AutoAcceptJack&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='no'&gt;EchoJack&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='no'&gt;NotifyJack&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This is the meat of it, even though it doesn&amp;#8217;t look like it at first glance. &lt;em&gt;Jack&lt;/em&gt;s are the basic units of shared functionality. More later, but the basic rundown is that &lt;code&gt;AutoAcceptJack&lt;/code&gt; auto-accepts (and reciprocates) roster additions (&amp;#8220;friend requests&amp;#8221;), &lt;code&gt;EchoJack&lt;/code&gt; does the heavy lifting of echoing input back, and &lt;code&gt;NotifyJack&lt;/code&gt; sends presence (i.e. &amp;#8221;I&amp;#8217;m online&amp;#8221;) notifications to everyone (and everything) in your roster.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;EchoJack&lt;/code&gt; (&lt;code&gt;lib/switchboard/jacks/echo.rb&lt;/code&gt;) looks like this:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='k'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;EchoJack&lt;/span&gt;
  &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nc'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nf'&gt;connect&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;switchboard&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;settings&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='n'&gt;switchboard&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;on_message&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;message&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;
      &lt;span class='n'&gt;stream&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;send&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;message&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;answer&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='k'&gt;end&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;I&amp;#8217;ll get to the details of &lt;code&gt;self.connect&lt;/code&gt; shortly, but the gist of it is that the &lt;code&gt;EchoJack&lt;/code&gt; registers an &lt;code&gt;on_message&lt;/code&gt; callback and uses &lt;code&gt;Jabber::Message&lt;/code&gt;&amp;#8217;s &lt;code&gt;#answer&lt;/code&gt; method to create a response (inverting the sender and receiver) before sending it back on the stream (client or component connection, as the case may be).&lt;/p&gt;

&lt;p&gt;Implementing the &lt;code&gt;EchoBot&lt;/code&gt; as a jack is perhaps overkill, but the goal was to demonstrate how short and modular Switchboard apps can be. The alternate implementation looks like this:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='n'&gt;switchboard&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;plug!&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;AutoAcceptJack&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='no'&gt;NotifyJack&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='n'&gt;switchboard&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;on_message&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;message&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;
  &lt;span class='n'&gt;stream&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;send&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;message&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;answer&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Callbacks are executed in the context of the &lt;code&gt;switchboard&lt;/code&gt; object; this is important to remember, as variables defined in a different scope will be unavailable.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='n'&gt;switchboard&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;run!&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This kicks off the process of connecting to the server. As XMPP is an asynchronous protocol, the different hooks will be called in response to varying inputs.&lt;/p&gt;

&lt;h3 id='jacks_and_hooks'&gt;Jacks and Hooks&lt;/h3&gt;

&lt;p&gt;Jacks are extractions of standard functionality that are executed in the context of the Switchboard core. Thus, references to &lt;code&gt;stream&lt;/code&gt;, etc. call methods implemented by &lt;code&gt;Switchboard::Core&lt;/code&gt; and its subclasses rather than the jack itself. If you need convenience methods, they should be defined on the &lt;code&gt;switchboard&lt;/code&gt; object provided as the 1st argument to &lt;code&gt;connect&lt;/code&gt;:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nc'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nf'&gt;connect&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;switchboard&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;settings&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nc'&gt;switchboard&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nf'&gt;helper_method&lt;/span&gt;
    &lt;span class='c1'&gt;# do something&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
  
  &lt;span class='n'&gt;switchboard&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;on_message&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;message&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;
    &lt;span class='n'&gt;helper_method&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;connect&lt;/code&gt; is the entry point for all jacks. When the jack is plugged into switchboard (using &lt;code&gt;Switchboard::Core#plug!&lt;/code&gt;), &lt;code&gt;connect&lt;/code&gt; is called with the active Switchboard instance and its corresponding &lt;code&gt;Switchboard::Settings&lt;/code&gt;. You can modify the settings in the body of &lt;code&gt;connect&lt;/code&gt;, but what you&amp;#8217;ll most often be doing is adding additional functionality (via method definitions or &lt;em&gt;Module&lt;/em&gt;s) to the Switchboard instance.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;PubSubJack&lt;/code&gt; (&lt;code&gt;lib/switchboard/jacks/pubsub.rb&lt;/code&gt;) modifies the &lt;code&gt;switchboard&lt;/code&gt; object by extending a helper &lt;em&gt;Module&lt;/em&gt;:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nc'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nf'&gt;connect&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;switchoard&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;settings&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='n'&gt;switchboard&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;extend&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;Switchboard&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;Helpers&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;PubSubHelper&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  
  &lt;span class='n'&gt;switchboard&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;on_startup&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt;
    &lt;span class='c1'&gt;# ...&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Like everything else, jacks have access to all hooks (lifecycle callbacks). These are called using &lt;code&gt;on_&amp;lt;hook name&amp;gt;&lt;/code&gt;. In general, these map to callbacks defined by &lt;code&gt;xmpp4r&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;startup&lt;/code&gt;&lt;/li&gt;

&lt;li&gt;&lt;code&gt;shutdown&lt;/code&gt;&lt;/li&gt;

&lt;li&gt;&lt;code&gt;stream_connected&lt;/code&gt;&lt;/li&gt;

&lt;li&gt;&lt;code&gt;exception&lt;/code&gt;&lt;/li&gt;

&lt;li&gt;&lt;code&gt;stanza&lt;/code&gt;&lt;/li&gt;

&lt;li&gt;&lt;code&gt;message&lt;/code&gt;&lt;/li&gt;

&lt;li&gt;&lt;code&gt;presence&lt;/code&gt;&lt;/li&gt;

&lt;li&gt;&lt;code&gt;iq&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Clients provide the following additional hooks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;roster_presence&lt;/code&gt;&lt;/li&gt;

&lt;li&gt;&lt;code&gt;roster_query&lt;/code&gt;&lt;/li&gt;

&lt;li&gt;&lt;code&gt;roster_subscription&lt;/code&gt;&lt;/li&gt;

&lt;li&gt;&lt;code&gt;roster_subscription_request&lt;/code&gt;&lt;/li&gt;

&lt;li&gt;&lt;code&gt;roster_loaded&lt;/code&gt;&lt;/li&gt;

&lt;li&gt;&lt;code&gt;roster_update&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Strictly speaking, these hooks should probably be implemented by a &lt;code&gt;RosterJack&lt;/code&gt; and plugged in to &lt;code&gt;Switchboard::Client&lt;/code&gt; by default.&lt;/p&gt;

&lt;p&gt;Some jacks also introduce additional hooks (using &lt;code&gt;hook(:name)&lt;/code&gt;), such as &lt;code&gt;PubSubJack&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pubsub_event&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This hook is registed in &lt;code&gt;Switchboard::Helpers::PubSubHelper&lt;/code&gt; (&lt;code&gt;lib/switchboard/helpers/pubsub.rb&lt;/code&gt;) with &lt;code&gt;Switchboard::Core.hook(:pubsub_event)&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id='standard_jacks'&gt;Standard Jacks&lt;/h3&gt;

&lt;p&gt;The following jacks are available from the standard distribution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AutoAcceptJack&lt;/code&gt; - Auto-accepts and reciprocates roster requests.&lt;/li&gt;

&lt;li&gt;&lt;code&gt;DebugJack&lt;/code&gt; - Displays color-coded &lt;code&gt;&amp;lt;message /&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;presence /&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;iq /&amp;gt;&lt;/code&gt; stanzas.&lt;/li&gt;

&lt;li&gt;&lt;code&gt;EchoJack&lt;/code&gt; - Example jack that echos inputs back to the sender.&lt;/li&gt;

&lt;li&gt;&lt;code&gt;NotifyJack&lt;/code&gt; - Sends online/offline presence to all roster items.&lt;/li&gt;

&lt;li&gt;&lt;code&gt;PubSubJack&lt;/code&gt; - Adds a &lt;code&gt;pubsub_event&lt;/code&gt; hook and some PubSub convenience methods.&lt;/li&gt;

&lt;li&gt;&lt;code&gt;OAuthPubSubJack&lt;/code&gt; - Adds a &lt;code&gt;pubsub_event&lt;/code&gt; hook and some OAuth-enabled PubSub convenience methods. The &lt;code&gt;oauth&lt;/code&gt; gem must be installed for this to work.&lt;/li&gt;

&lt;li&gt;&lt;code&gt;RosterDebugJack&lt;/code&gt; - Like the &lt;code&gt;DebugJack&lt;/code&gt;, but for roster events and colorless.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href='http://github.com/mojodna/fire-hydrant/tree'&gt;Fire Hydrant&lt;/a&gt; includes a &lt;code&gt;FireEagleJack&lt;/code&gt; that introduces a &lt;code&gt;location_update&lt;/code&gt; hook that yields a &lt;code&gt;FireEagle::User&lt;/code&gt; object, demonstrating that Switchboard apps can be blissfully unaware of XMPP semantics when using the right jacks.&lt;/p&gt;

&lt;h3 id='a_more_complex_example_with_pandas'&gt;A More Complex Example, with Pandas&lt;/h3&gt;

&lt;p&gt;&lt;a href='http://github.com/mojodna/fire-hydrant/tree'&gt;Bamboo Shooter&lt;/a&gt; is a pseudo-realtime interface to the &lt;a href='http://flickr.com/'&gt;Flickr&lt;/a&gt; &lt;a href='http://code.flickr.com/blog/2009/03/03/panda-tuesday-the-history-of-the-panda-new-apis-explore-and-you/'&gt;Panda APIs&lt;/a&gt;. It polls the APIs once per minute and dribbles the responses out over XMPP over the course of the subsequent minute.&lt;/p&gt;

&lt;p&gt;This represents a more complex example for several reasons. Firstly, it coexists with &lt;a href='http://rubyeventmachine.com/'&gt;EventMachine&lt;/a&gt; as a set of additional threads (EventMachine is evented and thus single-threaded):&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='no'&gt;EM&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;run&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt;
  &lt;span class='no'&gt;Thread&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt;
    &lt;span class='c1'&gt;# Bamboo::Shooter subclasses Switchboard::Component&lt;/span&gt;
    &lt;span class='vi'&gt;@shooter&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Bamboo&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;Shooter&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;SETTINGS&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='vi'&gt;@shooter&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;run!&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
  
  &lt;span class='c1'&gt;# ...&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Secondly, it implements a &lt;a href='http://xmpp.org/extensions/xep-0060.html'&gt;PubSub&lt;/a&gt; service as a &lt;a href='http://xmpp.org/extensions/xep-0225.html'&gt;component&lt;/a&gt;, so it subclasses &lt;code&gt;Switchboard::Component&lt;/code&gt; rather than &lt;code&gt;Switchboard::Client&lt;/code&gt;. A side-effect of implementing a service as a component is that the logic for everything that the server would ordinarily do has to be managed by your code instead. Presence handling (implemented by &lt;code&gt;Bamboo::Shooter#presence_handler&lt;/code&gt;) is usually the first example of this.&lt;/p&gt;

&lt;p&gt;PubSub requests and subscription handling are application-specific concerns, but much of the implementation is essentially boilerplate. &lt;a href='http://github.com/mojodna/dovetail/tree'&gt;Dovetail&lt;/a&gt; represents a start at abstracting this, but it&amp;#8217;s incomplete at the moment. &lt;code&gt;&amp;lt;subscribe /&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;unsubscribe /&amp;gt;&lt;/code&gt; requests are the only operations currently supported; the fallback is to return a &lt;code&gt;&amp;lt;feature-not-implemented /&amp;gt;&lt;/code&gt; response:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;iq_handler&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;iq&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='n'&gt;iq&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;pubsub&lt;/span&gt;
    &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='n'&gt;subscribe&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;iq&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;pubsub&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;first_element&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;subscribe&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
      &lt;span class='n'&gt;node&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;subscribe&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;attributes&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;node&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;

      &lt;span class='nb'&gt;puts&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;Subscription to &lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;node&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt; requested by &lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;iq&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;from&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;&lt;/span&gt;
      &lt;span class='n'&gt;subscribers&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='n'&gt;node&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='o'&gt;||=&lt;/span&gt; &lt;span class='o'&gt;[]&lt;/span&gt;
      &lt;span class='k'&gt;unless&lt;/span&gt; &lt;span class='n'&gt;subscribers&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='n'&gt;node&lt;/span&gt;&lt;span class='o'&gt;].&lt;/span&gt;&lt;span class='n'&gt;include?&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;iq&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;from&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;strip&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
        &lt;span class='n'&gt;subscribers&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='n'&gt;node&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='o'&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class='n'&gt;iq&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;from&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;strip&lt;/span&gt; 
      &lt;span class='k'&gt;end&lt;/span&gt;

      &lt;span class='n'&gt;resp&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Jabber&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;Iq&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='ss'&gt;:result&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;iq&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;from&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
      &lt;span class='n'&gt;resp&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;from&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;iq&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to&lt;/span&gt; &lt;span class='c1'&gt;# TODO component.domain (elsewhere, too)&lt;/span&gt;
      &lt;span class='n'&gt;resp&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;id&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;iq&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;id&lt;/span&gt;
      &lt;span class='n'&gt;pubsub&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Jabber&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;PubSub&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;IqPubSub&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;
      &lt;span class='n'&gt;subscription&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Jabber&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;PubSub&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;Subscription&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;iq&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;from&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;strip&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;node&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
      &lt;span class='n'&gt;subscription&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;state&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;subscribed&amp;quot;&lt;/span&gt;
      &lt;span class='n'&gt;pubsub&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;add&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;subscription&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
      &lt;span class='n'&gt;resp&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;add&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;pubsub&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;

      &lt;span class='n'&gt;deliver&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;resp&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='k'&gt;elsif&lt;/span&gt; &lt;span class='n'&gt;unsubscribe&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;iq&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;pubsub&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;first_element&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;unsubscribe&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
      &lt;span class='n'&gt;node&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;unsubscribe&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;attributes&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;node&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;

      &lt;span class='nb'&gt;puts&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;Unsubscription from &lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;node&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt; requested by &lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;iq&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;from&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;&lt;/span&gt;
      &lt;span class='n'&gt;subscribers&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='n'&gt;node&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='o'&gt;||=&lt;/span&gt; &lt;span class='o'&gt;[]&lt;/span&gt;
      &lt;span class='n'&gt;subscribers&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='n'&gt;node&lt;/span&gt;&lt;span class='o'&gt;].&lt;/span&gt;&lt;span class='n'&gt;delete&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;iq&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;from&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;strip&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;

      &lt;span class='n'&gt;resp&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Jabber&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;Iq&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='ss'&gt;:result&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;iq&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;from&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
      &lt;span class='n'&gt;resp&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;from&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;iq&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to&lt;/span&gt; &lt;span class='c1'&gt;# TODO component.domain (elsewhere, too)&lt;/span&gt;
      &lt;span class='n'&gt;resp&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;id&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;iq&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;id&lt;/span&gt;
      &lt;span class='n'&gt;deliver&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;resp&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='k'&gt;else&lt;/span&gt;
      &lt;span class='nb'&gt;puts&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;Received a pubsub message&amp;quot;&lt;/span&gt;
      &lt;span class='nb'&gt;puts&lt;/span&gt; &lt;span class='n'&gt;iq&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_s&lt;/span&gt;
      &lt;span class='c1'&gt;# TODO not-supported&lt;/span&gt;
      &lt;span class='n'&gt;not_implemented&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;iq&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='k'&gt;end&lt;/span&gt;
  &lt;span class='k'&gt;else&lt;/span&gt;
    &lt;span class='c1'&gt;# unrecognized iq&lt;/span&gt;
    &lt;span class='n'&gt;not_implemented&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;iq&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This is tightly tied to &lt;code&gt;xmpp4r&lt;/code&gt; and REXML, but as the rest of Switchboard is too, it&amp;#8217;s not a big deal for the time being.&lt;/p&gt;

&lt;p&gt;Presence tracking is a somewhat tricky problem (particularly for large numbers of consumers), so you&amp;#8217;ll notice that I completed punted and left it unimplemented. This means that subscriptions remain active (and will attempt to deliver photo payloads) even if a client has gone offline. For this reason, the consumers of this service are written to subscribe on startup and unsubscribe on shutdown. If the unsubscribe doesn&amp;#8217;t happen, there&amp;#8217;s a fair chance that the &lt;code&gt;bamboo-shooter&lt;/code&gt; process&amp;#8217;s memory usage will balloon. As a result, there is currently no public instance of this service running.&lt;/p&gt;

&lt;p&gt;Polling of the Panda APIs is done using &lt;code&gt;EventMachine:HttpRequest&lt;/code&gt;, which is a non-blocking, evented HTTP client. The result of each request (scheduled to run once per minute per panda) is parsed using REXML (because Switchboard is already using it via &lt;code&gt;xmpp4r&lt;/code&gt;), split up, and scheduled for publishing using &lt;code&gt;EventMachine.add_timer&lt;/code&gt;:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='no'&gt;EM&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;run&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt;
  &lt;span class='c1'&gt;# ...&lt;/span&gt;

  &lt;span class='n'&gt;check_pandas&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nb'&gt;lambda&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt;
    &lt;span class='n'&gt;params&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
      &lt;span class='s1'&gt;&amp;#39;api_key&amp;#39;&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='no'&gt;SETTINGS&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;flickr.key&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
      &lt;span class='s1'&gt;&amp;#39;method&amp;#39;&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;flickr.panda.getPhotos&amp;#39;&lt;/span&gt;
    &lt;span class='p'&gt;}&lt;/span&gt;

    &lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;ling ling&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;hsing hsing&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;wang wang&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;].&lt;/span&gt;&lt;span class='n'&gt;each&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;panda&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;
      &lt;span class='n'&gt;req&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;EventMachine&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;HttpRequest&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;http://api.flickr.com/services/rest/&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
      &lt;span class='n'&gt;http&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;req&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;get&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='ss'&gt;:query&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;params&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;merge&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;panda_name&amp;#39;&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;panda&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;

      &lt;span class='n'&gt;http&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;callback&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt;
        &lt;span class='k'&gt;begin&lt;/span&gt;
          &lt;span class='n'&gt;doc&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;REXML&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;Document&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;http&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;response&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
          &lt;span class='n'&gt;doc&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;root&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;each_element&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;rsp&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;
            &lt;span class='n'&gt;total&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;rsp&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;attributes&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;total&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;].&lt;/span&gt;&lt;span class='n'&gt;to_s&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_f&lt;/span&gt;
            &lt;span class='n'&gt;panda&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;rsp&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;attributes&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;panda&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;].&lt;/span&gt;&lt;span class='n'&gt;to_s&lt;/span&gt;
            &lt;span class='n'&gt;interval&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;rsp&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;attributes&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;interval&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;].&lt;/span&gt;&lt;span class='n'&gt;to_s&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_f&lt;/span&gt;
            &lt;span class='n'&gt;interval&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;interval&lt;/span&gt; &lt;span class='o'&gt;/&lt;/span&gt; &lt;span class='n'&gt;total&lt;/span&gt;
            &lt;span class='n'&gt;delay&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;

            &lt;span class='nb'&gt;puts&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;&lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;panda&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt; found &lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;total&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt; items with a &lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;interval&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;s delay.&amp;quot;&lt;/span&gt;

            &lt;span class='n'&gt;rsp&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;each_element&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;node&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;
              &lt;span class='no'&gt;EventMachine&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='n'&gt;add_timer&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;delay&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt;
                &lt;span class='vi'&gt;@shooter&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;publish&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;/flickr/pandas/&lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='no'&gt;CGI&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;escape&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;panda&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;node&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
              &lt;span class='k'&gt;end&lt;/span&gt;
              &lt;span class='n'&gt;delay&lt;/span&gt; &lt;span class='o'&gt;+=&lt;/span&gt; &lt;span class='n'&gt;interval&lt;/span&gt;
            &lt;span class='k'&gt;end&lt;/span&gt;
          &lt;span class='k'&gt;end&lt;/span&gt;
        &lt;span class='k'&gt;rescue&lt;/span&gt; &lt;span class='no'&gt;REXML&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;ParseException&lt;/span&gt;
        &lt;span class='k'&gt;end&lt;/span&gt;
      &lt;span class='k'&gt;end&lt;/span&gt;
    &lt;span class='k'&gt;end&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;

  &lt;span class='no'&gt;EventMachine&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='n'&gt;add_periodic_timer&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;61&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='o'&gt;&amp;amp;&lt;/span&gt;&lt;span class='n'&gt;check_pandas&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='n'&gt;check_pandas&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;call&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Consuming the Bamboo Shooter feed can either be done by running &lt;code&gt;switchboard
pubsub --server &amp;lt;server&amp;gt; listen&lt;/code&gt; or with code like this (&lt;code&gt;earth.rb&lt;/code&gt;, which will zoom Google Earth to the location where the photo was taken). Remember, Wang Wang is the Panda who likes maps.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c1'&gt;#!/usr/bin/env ruby -rubygems&lt;/span&gt;

&lt;span class='k'&gt;begin&lt;/span&gt;
  &lt;span class='nb'&gt;require&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;appscript&amp;#39;&lt;/span&gt;
&lt;span class='k'&gt;rescue&lt;/span&gt; &lt;span class='no'&gt;LoadError&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;e&lt;/span&gt;
  &lt;span class='n'&gt;gem&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;e&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;message&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;split&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;--&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;last&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;strip&lt;/span&gt;
  &lt;span class='nb'&gt;puts&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;The &lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;gem&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt; gem is required.&amp;quot;&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;

&lt;span class='nb'&gt;require&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;cgi&amp;#39;&lt;/span&gt;
&lt;span class='nb'&gt;require&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;switchboard&amp;#39;&lt;/span&gt;

&lt;span class='n'&gt;node&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;/flickr/pandas/&lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='no'&gt;CGI&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;escape&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;ARGV&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;&lt;/span&gt;

&lt;span class='n'&gt;earth&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Appscript&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;app&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;Google Earth&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;

&lt;span class='no'&gt;DEFAULTS&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
  &lt;span class='s2'&gt;&amp;quot;resource&amp;quot;&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;earth&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
&lt;span class='p'&gt;}&lt;/span&gt;

&lt;span class='n'&gt;settings&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;YAML&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;load&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;File&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;read&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;bamboo_shooter.yml&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
&lt;span class='n'&gt;switchboard&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Switchboard&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;Client&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;settings&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;merge&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;DEFAULTS&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;
&lt;span class='n'&gt;switchboard&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;plug!&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;AutoAcceptJack&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='no'&gt;NotifyJack&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='no'&gt;PubSubJack&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;

&lt;span class='n'&gt;switchboard&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;on_startup&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt;
  &lt;span class='n'&gt;defer&lt;/span&gt; &lt;span class='ss'&gt;:subscribed&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt;
    &lt;span class='nb'&gt;puts&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;Subscribing to &lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;node&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;&lt;/span&gt;
    &lt;span class='n'&gt;subscribe_to&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;node&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;

&lt;span class='n'&gt;switchboard&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;on_shutdown&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt;
  &lt;span class='nb'&gt;puts&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;Unsubscribing from &lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;node&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;&lt;/span&gt;
  &lt;span class='n'&gt;unsubscribe_from&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;node&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;

&lt;span class='n'&gt;switchboard&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;on_pubsub_event&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;event&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;
  &lt;span class='n'&gt;event&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;payload&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;each&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;payload&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;
    &lt;span class='n'&gt;payload&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;elements&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;each&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;item&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;
      &lt;span class='n'&gt;photo&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;item&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;first_element&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;photo&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
      &lt;span class='n'&gt;lat&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;photo&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;attributes&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;latitude&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;].&lt;/span&gt;&lt;span class='n'&gt;to_f&lt;/span&gt;
      &lt;span class='n'&gt;lon&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;photo&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;attributes&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;longitude&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;].&lt;/span&gt;&lt;span class='n'&gt;to_f&lt;/span&gt;
      &lt;span class='n'&gt;earth&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;SetViewInfo&lt;/span&gt;&lt;span class='p'&gt;({&lt;/span&gt;&lt;span class='ss'&gt;:latitude&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;lat&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
                         &lt;span class='ss'&gt;:longitude&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;lon&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
                         &lt;span class='ss'&gt;:distance&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;rand&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt; &lt;span class='mi'&gt;25000&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='mi'&gt;5000&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
                         &lt;span class='ss'&gt;:azimuth&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='nb'&gt;rand&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt; &lt;span class='mi'&gt;360&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
                         &lt;span class='ss'&gt;:tilt&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;rand&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt; &lt;span class='mi'&gt;75&lt;/span&gt;&lt;span class='p'&gt;)},&lt;/span&gt;
                        &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='ss'&gt;:speed&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;})&lt;/span&gt;
    &lt;span class='k'&gt;end&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;

&lt;span class='n'&gt;switchboard&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;run!&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Publishing is managed by the Switchboard component:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;publish&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;node&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;xml_node&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='n'&gt;event&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Jabber&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;PubSub&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;Event&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;
  &lt;span class='n'&gt;items&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Jabber&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;PubSub&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;EventItems&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;
  &lt;span class='n'&gt;items&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;node&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;node&lt;/span&gt;
  &lt;span class='n'&gt;item&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Jabber&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;PubSub&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;EventItem&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;

  &lt;span class='n'&gt;item&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;add&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;xml_node&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='n'&gt;items&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;add&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;item&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='n'&gt;event&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;add&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;items&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;

  &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;subscribers&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='n'&gt;node&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='o'&gt;||&lt;/span&gt; &lt;span class='o'&gt;[]&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;each&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;subscriber&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;
    &lt;span class='n'&gt;message&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;subscriber&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;event&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Getting Bamboo Shooter running is a little tricky, as it requires an XMPP that supports the component protocol (I use &lt;a href='http://ejabberd.im/'&gt;ejabberd&lt;/a&gt; with the following configuration). More complicatedly, this server either needs to be public (with DNS SRV records configured to support federation) or on a private network with a second XMPP server running. When developing locally, I use an Ubuntu VM with ejabberd running in component mode (using &lt;code&gt;ejabberd.cfg&lt;/code&gt; below) and a second ejabberd instance with the default configuration running in OS X. &lt;code&gt;bamboo-shooter.rb&lt;/code&gt; connects to Ubuntu, the client connects to OS X, and the ejabberd instances figure out how to connect to one another with ZeroConf (&lt;code&gt;hostname.local&lt;/code&gt;; &lt;code&gt;avahi-daemon&lt;/code&gt; on Ubuntu makes this possible).&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;loglevel&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;4&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;hosts&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;localhost&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;listen&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='o'&gt;[&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='mi'&gt;5222&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;ejabberd_c2s&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='o'&gt;[&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;access&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;c2s&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;shaper&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;c2s_shaper&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;max_stanza_size&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;65536&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt; &lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='mi'&gt;5269&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;ejabberd_s2s_in&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='o'&gt;[&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;shaper&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;s2s_shaper&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;max_stanza_size&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;131072&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt; &lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='mi'&gt;5288&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;ejabberd_service&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;host&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;&amp;lt;hostname&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;password&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;&amp;lt;password&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt;
 &lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;auth_method&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;internal&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;shaper&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;normal&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;maxrate&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;1000&lt;/span&gt;&lt;span class='p'&gt;}}&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;shaper&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;fast&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;maxrate&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;50000&lt;/span&gt;&lt;span class='p'&gt;}}&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;acl&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;local&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;user_regexp&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;}}&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;language&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;en&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;modules&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='o'&gt;[&lt;/span&gt;
 &lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id='thats_it_for_now'&gt;That&amp;#8217;s it for now&lt;/h3&gt;

&lt;p&gt;That&amp;#8217;s all I&amp;#8217;ve got for now. It should be enough to get you started building clients and components, but if you have any questions, post a comment below or write to the &lt;a href='http://groups.google.com/group/switchboard'&gt;Switchboard Google Group&lt;/a&gt;.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/mojodnadotnet/~4/D81DnOug2Gg" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://mojodna.net/2009/07/19/switchboard-as-a-framework.html</feedburner:origLink></entry>
 
 <entry>
   <title>switchboard : XMPP :: curl : HTTP</title>
   <link href="http://feedproxy.google.com/~r/mojodnadotnet/~3/R1gZ4dKGvCM/switchboard-curl-for-xmpp.html" />
   <updated>2009-07-16T00:00:00-07:00</updated>
   <id>http://mojodna.net/2009/07/16/switchboard-curl-for-xmpp</id>
   <content type="html">&lt;h2 id='switchboard__xmpp__curl__http'&gt;switchboard : XMPP :: curl : HTTP&lt;/h2&gt;

&lt;h3 id='quick_start'&gt;Quick Start&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Switchboard on GitHub: &lt;a href='http://github.com/mojodna/switchboard/tree'&gt;http://github.com/mojodna/switchboard/tree&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;Switchboard Google Group: &lt;a href='http://groups.google.com/group/switchboard'&gt;http://groups.google.com/group/switchboard&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here&amp;#8217;s how to install and use Switchboard for a few basic use-cases:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;&lt;span class='c'&gt;# install Switchboard&lt;/span&gt;
&lt;span class='nv'&gt;$ &lt;/span&gt;sudo gem install switchboard

&lt;span class='nv'&gt;$ &lt;/span&gt;&lt;span class='c'&gt;# list everyone on your roster (buddy list)&lt;/span&gt;
&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard --jid client@example.com --password pa55word &lt;span class='se'&gt;\&lt;/span&gt;
    roster list

&lt;span class='nv'&gt;$ &lt;/span&gt;&lt;span class='c'&gt;# add a friend to your roster&lt;/span&gt;
&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard --jid client@example.com --password pa55word &lt;span class='se'&gt;\&lt;/span&gt;
    roster add friend@example.com

&lt;span class='nv'&gt;$ &lt;/span&gt;&lt;span class='c'&gt;# listen for PubSub events&lt;/span&gt;
&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard --jid subscriber@example.com --password pa55word &lt;span class='se'&gt;\&lt;/span&gt;
    pubsub --server &amp;lt;pubsub server&amp;gt; listen
&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id='curl_for_xmpp'&gt;&amp;#8220;curl for XMPP?&amp;#8221;&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;curl&lt;/code&gt; is the ultimate Swiss Army Knife for HTTP. Switchboard aims to be the same for XMPP.&lt;/p&gt;

&lt;p&gt;HTTP is (relatively) easy. It&amp;#8217;s stateless and has fairly limited semantics. However, if you&amp;#8217;re trying to debug a web service and want to determine what certain &lt;code&gt;ETag&lt;/code&gt;s, additional headers, or specific content types respond with, &lt;code&gt;curl&lt;/code&gt; is your go-to.&lt;/p&gt;

&lt;p&gt;XMPP is a bit more complicated; it&amp;#8217;s stateful, it (usually) requires login credentials, it&amp;#8217;s asynchronous, and it has many extensions to the core protocol that are in varying levels of use. Traditionally, in order to explore an XMPP service, you&amp;#8217;d have to delve into the advanced features of a client like &lt;a href='http://psi-im.org/'&gt;Psi&lt;/a&gt; or &lt;a href='http://synapse.im/'&gt;Synapse&lt;/a&gt;, often dropping to the level of entering raw XML to see what happens in response.&lt;/p&gt;

&lt;p&gt;Switchboard (and its command-line equivalent, &lt;code&gt;switchboard&lt;/code&gt;) simplifies the process of repeatedly making common requests (e.g. roster manipulation, probing, and PubSub node operations) and is easily extended to support new behaviors.&lt;/p&gt;

&lt;h3 id='switchboard_background'&gt;Switchboard background&lt;/h3&gt;

&lt;p&gt;Copying and pasting XML isn&amp;#8217;t the least error-prone process I can think of. In addition, I found myself repeatedly testing the same types of stanzas (e.g. &lt;a href='http://xmpp.org/extensions/xep-0060.html'&gt;PubSub&lt;/a&gt; requests) and attempting to implement extensions for which no implementations existed (i.e. &lt;a href='http://xmpp.org/extensions/xep-0235.html'&gt;OAuth over XMPP&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Diving into &lt;a href='http://home.gna.org/xmpp4r/'&gt;&lt;code&gt;xmpp4r&lt;/code&gt;&lt;/a&gt;&amp;#8217;s functionality led me to expose additional features to the command-line tool such as &lt;a href='http://xmpp.org/extensions/xep-0163.html'&gt;PEP&lt;/a&gt; and &lt;a href='http://xmpp.org/extensions/xep-0012.html'&gt;Last Activity&lt;/a&gt; to see how difficult it would be.&lt;/p&gt;

&lt;p&gt;Since all of its dependencies have been released and are installable via RubyGems now, people who aren&amp;#8217;t me can finally use Switchboard without much hassle. In honor of this milestone, Switchboard has been updated to 0.1.0. Real mature, I know, but it is still quite useful.&lt;/p&gt;

&lt;p&gt;People who &lt;em&gt;are&lt;/em&gt; me have been using this tool since last November (in fact, this is the project that inspired &lt;a href='/2009/03/09/my-public-git-workflow.html'&gt;&amp;#8220;My (public) Git Workflow&amp;#8221;&lt;/a&gt;). (Some people have been using it to consume &lt;a href='http://fireeagle.yahoo.net/'&gt;Fire Eagle&lt;/a&gt;&amp;#8217;s &lt;a href='http://feblog.yahoo.net/2009/02/19/fire-eagle-location-streams/'&gt;Location Streams&lt;/a&gt;, but it hasn&amp;#8217;t been for the faint of heart.)&lt;/p&gt;

&lt;p&gt;Switchboard&amp;#8217;s primary strength is certainly the command-line interface, but it turns out that it&amp;#8217;s a pretty good abstraction over &lt;code&gt;xmpp4r&lt;/code&gt; for building clients and servers (as components).&lt;/p&gt;

&lt;p&gt;Here are a few projects I&amp;#8217;ve worked on that use Switchboard:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href='http://github.com/mojodna/bamboo-shooter/tree'&gt;bamboo-shooter&lt;/a&gt; - PubSub for Pandas: an pseudo-realtime XMPP interface to the Flickr Panda APIs&lt;/li&gt;

&lt;li&gt;&lt;a href='http://github.com/mojodna/fire-hydrant/tree'&gt;fire-hydrant&lt;/a&gt; - A simple library (as a Switchboard &amp;#8220;jack&amp;#8221;) for consuming &lt;a href='http://fireeagle.yahoo.net/'&gt;Fire Eagle&lt;/a&gt;&amp;#8217;s &lt;a href='http://feblog.yahoo.net/2009/02/19/fire-eagle-location-streams/'&gt;Location Streams&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='http://github.com/mojodna/mars/tree'&gt;mars&lt;/a&gt; and &lt;a href='http://github.com/mojodna/jupiter/tree'&gt;jupiter&lt;/a&gt; - A matched pair of experiments mapping REST to XMPP&lt;/li&gt;

&lt;li&gt;&lt;a href='http://github.com/mojodna/dovetail/tree'&gt;dovetail&lt;/a&gt; - A start at a framework for building PubSub servers (again, as components)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id='what_to_do'&gt;What to do?&lt;/h3&gt;

&lt;h4 id='configuration'&gt;Configuration&lt;/h4&gt;

&lt;p&gt;The first thing you&amp;#8217;ll probably want to do with Switchboard is to provide some basic configuration so you won&amp;#8217;t have to constantly enter your login credentials:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard config jid jid@example.com
&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard config password pa55word
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;To get the value of a setting, don&amp;#8217;t include a value:&lt;/p&gt;

&lt;p&gt;&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard config jid &lt;span class='c'&gt;# =&amp;gt; jid@example.com&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;&lt;/p&gt;

&lt;p&gt;Some additional useful settings to set defaults for are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;debug&lt;/code&gt; - Turn on verbose mode: good for debugging. (&lt;code&gt;true&lt;/code&gt; / &lt;code&gt;false&lt;/code&gt;)&lt;/li&gt;

&lt;li&gt;&lt;code&gt;resource&lt;/code&gt; - Override the default resource of &lt;code&gt;/switchboard&lt;/code&gt; (don&amp;#8217;t include the &lt;code&gt;/&lt;/code&gt;). This is useful if you want to run multiple instances with the same &lt;em&gt;JID&lt;/em&gt;. This is equivalent to &lt;code&gt;switchboard --resource &amp;lt;resource&amp;gt;&lt;/code&gt;. (&lt;em&gt;String&lt;/em&gt;)&lt;/li&gt;

&lt;li&gt;&lt;code&gt;pubsub.server&lt;/code&gt; - Specify a default server to make PubSub requests against. This is equivalent to &lt;code&gt;switchboard pubsub --server &amp;lt;server&amp;gt;&lt;/code&gt;. (&lt;em&gt;String&lt;/em&gt;)&lt;/li&gt;

&lt;li&gt;&lt;code&gt;oauth&lt;/code&gt; - Use &lt;a href='http://oauth.net/'&gt;OAuth&lt;/a&gt; when making PubSub requests. This is equivalent to &lt;code&gt;switchboard pubsub --oauth&lt;/code&gt; and requires that the &lt;a href='http://oauth.rubyforge.org/'&gt;&lt;code&gt;oauth&lt;/code&gt; gem&lt;/a&gt; be installed. (&lt;code&gt;true&lt;/code&gt; / &lt;code&gt;false&lt;/code&gt;)&lt;/li&gt;

&lt;li&gt;&lt;code&gt;oauth.consumer_key&lt;/code&gt; - Specify a default OAuth consumer key. This is equivalent to &lt;code&gt;switchboard pubsub --oauth-consumer-key &amp;lt;key&amp;gt;&lt;/code&gt;. (&lt;em&gt;String&lt;/em&gt;)&lt;/li&gt;

&lt;li&gt;&lt;code&gt;oauth.consumer_secret&lt;/code&gt; - Specify a default OAuth consumer secret. This is equivalent to &lt;code&gt;switchboard pubsub --oauth-consumer-secret &amp;lt;secret&amp;gt;&lt;/code&gt;. (&lt;em&gt;String&lt;/em&gt;)&lt;/li&gt;

&lt;li&gt;&lt;code&gt;oauth.token&lt;/code&gt; - Specify a default OAuth token. This is equivalent to &lt;code&gt;switchboard pubsub --oauth-token &amp;lt;token&amp;gt;&lt;/code&gt;. (&lt;em&gt;String&lt;/em&gt;)&lt;/li&gt;

&lt;li&gt;&lt;code&gt;oauth.token_secret&lt;/code&gt; - Specify a default OAuth token secret. This is equivalent to &lt;code&gt;switchboard pubsub --oauth-token-secret &amp;lt;secret&amp;gt;&lt;/code&gt;. (&lt;em&gt;String&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In general, anything that is referred to in the library in the form &lt;code&gt;OPTIONS[&amp;quot;pubsub.node&amp;quot;]&lt;/code&gt; can be set as a Switchboard setting.&lt;/p&gt;

&lt;h4 id='roster_manipulation'&gt;Roster Manipulation&lt;/h4&gt;

&lt;p&gt;Roster manipulation is something that can be easily handled by a desktop XMPP client, but sometimes it&amp;#8217;s more convenient to be able to do it from the command-line.&lt;/p&gt;

&lt;p&gt;Rosters can be listed, added to, or removed from. I&amp;#8217;ll assume you&amp;#8217;ve configured Switchboard with some login credentials.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard roster list
&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard roster online
&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard roster add friend1@example.org friend2@example.org
&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard roster remove friend2@example.org enemy@example.org
&lt;/pre&gt;
&lt;/div&gt;
&lt;h4 id='probing_and_discovery'&gt;Probing and Discovery&lt;/h4&gt;

&lt;p&gt;XMPP provides some pretty heady functionality when it comes to determining what a server is capable of. A &lt;code&gt;disco#info&lt;/code&gt; query is the first step to use when determining capabilities:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard disco --target jabber.org info
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The response to this query includes &lt;code&gt;http://jabber.org/protocol/disco#items&lt;/code&gt;, which means that &lt;code&gt;jabber.org&lt;/code&gt; supports &lt;code&gt;disco#items&lt;/code&gt; queries, which allow you to determine what top-level items (services) are available:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard disco --target jabber.org items
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This response includes &lt;code&gt;conference.jabber.org&lt;/code&gt;. Let&amp;#8217;s list items available there:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard disco --target conference.jabber.org items
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Whoa. A list of &lt;em&gt;MUC&lt;/em&gt;s (multi-user chats) hosted on &lt;code&gt;conference.jabber.org&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can do the same thing to list available PubSub nodes if you&amp;#8217;re running a local copy of &lt;a href='http://ejabberd.im/'&gt;ejabberd&lt;/a&gt; or another XMPP server that supports it. (Note that you may need to prefix your hostname with &lt;code&gt;pubsub&lt;/code&gt; in order to see the nodes.)&lt;/p&gt;

&lt;h4 id='pubsub'&gt;PubSub&lt;/h4&gt;

&lt;p&gt;Switchboard supports more of &lt;a href='http://xmpp.org/extensions/xep-0060.html'&gt;PubSub&lt;/a&gt; than any other extension, mainly because that&amp;#8217;s been my primary focus of XMPP experimentation. To get a full list of available PubSub commands:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard pubsub
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;A basic sequence of events is to subscribe:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard pubsub --server &amp;lt;server&amp;gt; subscribe --node &amp;lt;node&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;List subscriptions:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard pubsub --server &amp;lt;server&amp;gt; subscriptions
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Listen for notifications:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard pubsub --server &amp;lt;server&amp;gt; listen
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Unsubscribe:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard pubsub --server &amp;lt;server&amp;gt; unsubscribe --node &amp;lt;node&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Let&amp;#8217;s walk through a couple examples.&lt;/p&gt;

&lt;p&gt;First, &lt;a href='http://superfeedr.com/'&gt;Superfeedr&lt;/a&gt;, which bills itself as &amp;#8220;real-time feed parsing in the cloud&amp;#8221;. To begin, you&amp;#8217;ll need to register and activate your account. Once you&amp;#8217;ve done that, set up a subscription a &lt;a href='http://twitter.com/'&gt;Twitter&lt;/a&gt; search for &amp;#8220;xmpp&amp;#8221;:&lt;/p&gt;

&lt;p&gt;&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard --jid &amp;lt;username&amp;gt;@superfeedr.com --password &amp;lt;password&amp;gt; &lt;span class='se'&gt;\&lt;/span&gt;
    pubsub --server firehoser.superfeedr.com &lt;span class='se'&gt;\&lt;/span&gt;
    subscribe --node &lt;span class='s2'&gt;&amp;quot;http://search.twitter.com/search.atom?q=xmpp&amp;quot;&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;&lt;/p&gt;

&lt;p&gt;We would next list subscriptions, but Superfeedr uses a non-standard mechanism to do so. Instead, let&amp;#8217;s listen for new results:&lt;/p&gt;

&lt;p&gt;&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard --jid &amp;lt;username&amp;gt;@superfeedr.com --password &amp;lt;password&amp;gt; &lt;span class='se'&gt;\&lt;/span&gt;
    pubsub --server firehoser.superfeedr.com listen
&lt;/pre&gt;
&lt;/div&gt;&lt;/p&gt;

&lt;p&gt;If you&amp;#8217;re lucky, you&amp;#8217;ll get an Atom payload or two. Here&amp;#8217;s one:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nt'&gt;&amp;lt;event&lt;/span&gt; &lt;span class='na'&gt;xmlns=&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;http://jabber.org/protocol/pubsub#event&amp;#39;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&lt;/span&gt;
  &lt;span class='nt'&gt;&amp;lt;status&lt;/span&gt; &lt;span class='na'&gt;feed=&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;http://search.twitter.com/search.atom?q=xmpp&amp;#39;&lt;/span&gt; &lt;span class='na'&gt;xmlns=&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;http://superfeedr.com/xmpp-pubsub-ext&amp;#39;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;http&lt;/span&gt; &lt;span class='na'&gt;code=&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;200&amp;#39;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&lt;/span&gt;16933 bytes fetched in 0.600034s&lt;span class='nt'&gt;&amp;lt;/http&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;next_fetch&amp;gt;&lt;/span&gt;2009-07-22T17:26:22Z&lt;span class='nt'&gt;&amp;lt;/next_fetch&amp;gt;&lt;/span&gt;
  &lt;span class='nt'&gt;&amp;lt;/status&amp;gt;&lt;/span&gt;
  &lt;span class='nt'&gt;&amp;lt;items&lt;/span&gt; &lt;span class='na'&gt;node=&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;http://search.twitter.com/search.atom?q=xmpp&amp;#39;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;item&lt;/span&gt; &lt;span class='na'&gt;chunk=&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;1&amp;#39;&lt;/span&gt; &lt;span class='na'&gt;chunks=&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;1&amp;#39;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&lt;/span&gt;
      &lt;span class='nt'&gt;&amp;lt;entry&lt;/span&gt; &lt;span class='na'&gt;xmlns=&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;http://www.w3.org/2005/Atom&amp;#39;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&lt;/span&gt;
        &lt;span class='nt'&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Great... trillian update has killed my ability to view my xmpp rosters&lt;span class='nt'&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
        &lt;span class='nt'&gt;&amp;lt;summary&amp;gt;&lt;/span&gt;Great... trillian update has killed my ability to view my &lt;span class='ni'&gt;&amp;amp;lt;&lt;/span&gt;b&lt;span class='ni'&gt;&amp;amp;gt;&lt;/span&gt;xmpp&lt;span class='ni'&gt;&amp;amp;lt;&lt;/span&gt;/b&lt;span class='ni'&gt;&amp;amp;gt;&lt;/span&gt; rosters&lt;span class='nt'&gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;
        &lt;span class='nt'&gt;&amp;lt;link&lt;/span&gt; &lt;span class='na'&gt;href=&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;http://superfeedr.com/entries/tr5gfgstf8oqlcgr5opaeotxk39ovtos0oiat7h12mqfuoxdmgbjz1rnjtzswqvja2dqh8cgg31&amp;#39;&lt;/span&gt; &lt;span class='na'&gt;rel=&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;alternate&amp;#39;&lt;/span&gt; &lt;span class='na'&gt;type=&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;text/html&amp;#39;&lt;/span&gt;&lt;span class='nt'&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class='nt'&gt;&amp;lt;published&amp;gt;&lt;/span&gt;2009-07-22T17:10:52Z&lt;span class='nt'&gt;&amp;lt;/published&amp;gt;&lt;/span&gt;
        &lt;span class='nt'&gt;&amp;lt;id&amp;gt;&lt;/span&gt;tag:search.twitter.com,2005:2781212809&lt;span class='nt'&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
      &lt;span class='nt'&gt;&amp;lt;/entry&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;item&lt;/span&gt; &lt;span class='na'&gt;chunk=&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;1&amp;#39;&lt;/span&gt; &lt;span class='na'&gt;chunks=&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;1&amp;#39;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&lt;/span&gt;
      &lt;span class='nt'&gt;&amp;lt;entry&lt;/span&gt; &lt;span class='na'&gt;xmlns=&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;http://www.w3.org/2005/Atom&amp;#39;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&lt;/span&gt;
        &lt;span class='nt'&gt;&amp;lt;title&amp;gt;&lt;/span&gt;usando Tkabber: TKabber is Tcl/Tk Jabber-client with great functionality. It supports MUC, XMPP-statuses a.. http://bit.ly/AsFPg&lt;span class='nt'&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
        &lt;span class='nt'&gt;&amp;lt;summary&amp;gt;&lt;/span&gt;usando Tkabber: TKabber is Tcl/Tk Jabber-client with great functionality. It supports MUC, &lt;span class='ni'&gt;&amp;amp;lt;&lt;/span&gt;b&lt;span class='ni'&gt;&amp;amp;gt;&lt;/span&gt;XMPP&lt;span class='ni'&gt;&amp;amp;lt;&lt;/span&gt;/b&lt;span class='ni'&gt;&amp;amp;gt;&lt;/span&gt;-statuses a.. &lt;span class='ni'&gt;&amp;amp;lt;&lt;/span&gt;a href=&lt;span class='ni'&gt;&amp;amp;quot;&lt;/span&gt;http://bit.ly/AsFPg&lt;span class='ni'&gt;&amp;amp;quot;&amp;amp;gt;&lt;/span&gt;http://bit.ly/AsFPg&lt;span class='ni'&gt;&amp;amp;lt;&lt;/span&gt;/a&lt;span class='ni'&gt;&amp;amp;gt;&lt;/span&gt;&lt;span class='nt'&gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;
        &lt;span class='nt'&gt;&amp;lt;link&lt;/span&gt; &lt;span class='na'&gt;href=&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;http://superfeedr.com/entries/w6cezbjniqqgga3bd79ruklxhu7f4a6qqpro76hgz50gzccuekgmehz39yb1zi1cclgo83s&amp;#39;&lt;/span&gt; &lt;span class='na'&gt;rel=&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;alternate&amp;#39;&lt;/span&gt; &lt;span class='na'&gt;type=&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;text/html&amp;#39;&lt;/span&gt;&lt;span class='nt'&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class='nt'&gt;&amp;lt;published&amp;gt;&lt;/span&gt;2009-07-22T17:07:54Z&lt;span class='nt'&gt;&amp;lt;/published&amp;gt;&lt;/span&gt;
        &lt;span class='nt'&gt;&amp;lt;id&amp;gt;&lt;/span&gt;tag:search.twitter.com,2005:2781162048&lt;span class='nt'&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
      &lt;span class='nt'&gt;&amp;lt;/entry&amp;gt;&lt;/span&gt;
    &lt;span class='nt'&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
  &lt;span class='nt'&gt;&amp;lt;/items&amp;gt;&lt;/span&gt;
&lt;span class='nt'&gt;&amp;lt;/event&amp;gt;&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;It includes a Superfeedr-specific &lt;code&gt;&amp;lt;status/&amp;gt;&lt;/code&gt; element with information on the most recent fetch as well as standard Atom feeds contained within standard &lt;code&gt;&amp;lt;item/&amp;gt;&lt;/code&gt; elements. This means that you can extract the Atom elements from the PubSub payload and hand it to a feedparser of some variety to work its magic.&lt;/p&gt;

&lt;p&gt;&lt;a href='http://fireeagle.yahoo.net/'&gt;Fire Eagle&lt;/a&gt;&amp;#8217;s &lt;a href='http://feblog.yahoo.net/2009/02/19/fire-eagle-location-streams/'&gt;Location Streams&lt;/a&gt; also use PubSub, but subscription management requires that requests are signed using OAuth. This allows 3rd party applications to make requests on behalf of specific users without having to obtain their actual credentials (in short, it&amp;#8217;s exactly the same use-case for OAuth over HTTP).&lt;/p&gt;

&lt;p&gt;Let&amp;#8217;s start with a subscriptions list request, since we have the credentials (&amp;#8220;General Purpose Access Token&amp;#8221;) immediately after registering a &amp;#8220;web&amp;#8221; application with &lt;a href='http://fireeagle.yahoo.net/'&gt;Fire Eagle&lt;/a&gt;.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard pubsub --oauth &lt;span class='se'&gt;\&lt;/span&gt;
    --oauth-consumer-key &amp;lt;consumer key&amp;gt; &lt;span class='se'&gt;\&lt;/span&gt;
    --oauth-consumer-secret &amp;lt;consumer secret&amp;gt; &lt;span class='se'&gt;\&lt;/span&gt;
    --oauth-token &amp;lt;general token&amp;gt; &lt;span class='se'&gt;\&lt;/span&gt;
    --oauth-token-secret &amp;lt;general token secret&amp;gt; &lt;span class='se'&gt;\&lt;/span&gt;
    --server fireeagle.com &lt;span class='se'&gt;\&lt;/span&gt;
    subscriptions
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Odds are, you&amp;#8217;ll have nothing there. Let&amp;#8217;s change that. Send yourself through the authorization process in order to get a valid OAuth token and secret:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;oauth --consumer-key &amp;lt;consumer key&amp;gt; &lt;span class='se'&gt;\&lt;/span&gt;
    --consumer-secret &amp;lt;consumer secret&amp;gt; &lt;span class='se'&gt;\&lt;/span&gt;
    --access-token-url https://fireeagle.yahooapis.com/oauth/access_token
    --authorize-url https://fireeagle.yahoo.net/oauth/authorize
    --request-token-url https://fireeagle.yahooapis.com/oauth/request_token
    authorize
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;With that token and secret, subscribe to your Location Stream:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard pubsub --oauth &lt;span class='se'&gt;\&lt;/span&gt;
    --oauth-consumer-key &amp;lt;consumer key&amp;gt; &lt;span class='se'&gt;\&lt;/span&gt;
    --oauth-consumer-secret &amp;lt;consumer secret&amp;gt; &lt;span class='se'&gt;\&lt;/span&gt;
    --oauth-token &amp;lt;token&amp;gt; &lt;span class='se'&gt;\&lt;/span&gt;
    --oauth-token-secret &amp;lt;token secret&amp;gt; &lt;span class='se'&gt;\&lt;/span&gt;
    --server fireeagle.com &lt;span class='se'&gt;\&lt;/span&gt;
    subscribe --node &lt;span class='s2'&gt;&amp;quot;/api/0.1/user/&amp;lt;token&amp;gt;&amp;quot;&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now you&amp;#8217;ll have a subscription to query for:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard pubsub --oauth &lt;span class='se'&gt;\&lt;/span&gt;
    --oauth-consumer-key &amp;lt;consumer key&amp;gt; &lt;span class='se'&gt;\&lt;/span&gt;
    --oauth-consumer-secret &amp;lt;consumer secret&amp;gt; &lt;span class='se'&gt;\&lt;/span&gt;
    --oauth-token &amp;lt;general token&amp;gt; &lt;span class='se'&gt;\&lt;/span&gt;
    --oauth-token-secret &amp;lt;general token secret&amp;gt; &lt;span class='se'&gt;\&lt;/span&gt;
    --server fireeagle.com &lt;span class='se'&gt;\&lt;/span&gt;
    subscriptions
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Listen for location updates:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard pubsub --server fireeagle.com listen
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href='http://fireeagle.yahoo.net/my/location'&gt;Update your current location&lt;/a&gt; and watch as the update rolls in. If you&amp;#8217;d like to visualize updates with &lt;a href='http://earth.google.com/'&gt;Google Earth&lt;/a&gt;, check out &lt;a href='http://github.com/mojodna/fire-hydrant/tree'&gt;fire-hydrant on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We&amp;#8217;re done, so we may as well clean up and unsubscribe:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard pubsub --oauth &lt;span class='se'&gt;\&lt;/span&gt;
    --oauth-consumer-key &amp;lt;consumer key&amp;gt; &lt;span class='se'&gt;\&lt;/span&gt;
    --oauth-consumer-secret &amp;lt;consumer secret&amp;gt; &lt;span class='se'&gt;\&lt;/span&gt;
    --oauth-token &amp;lt;token&amp;gt; &lt;span class='se'&gt;\&lt;/span&gt;
    --oauth-token-secret &amp;lt;token secret&amp;gt; &lt;span class='se'&gt;\&lt;/span&gt;
    --server fireeagle.com &lt;span class='se'&gt;\&lt;/span&gt;
    unsubscribe --node &lt;span class='s2'&gt;&amp;quot;/api/0.1/user/&amp;lt;token&amp;gt;&amp;quot;&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;h4 id='pep_personal_eventing_protocol'&gt;PEP (Personal Eventing Protocol)&lt;/h4&gt;

&lt;p&gt;&lt;a href='http://xmpp.org/extensions/xep-0163.html'&gt;PEP&lt;/a&gt; is a specialized version of PubSub, intended to allow individuals to associate data with their JIDs. Switchboard supports publishing of User Tune and User Location.&lt;/p&gt;

&lt;p&gt;Due to the ability of XMPP to allow multiple instances of the same account online (identified with different resources), Switchboard can serve up User Tune and User Location for the same account you&amp;#8217;re already online with. Not every client supports displaying them, but they&amp;#8217;re fun to play with regardless.&lt;/p&gt;

&lt;p&gt;To publish User Tune, you need to be on a Mac, running iTunes, and have the &lt;code&gt;rb-appscript&lt;/code&gt; gem installed (&lt;code&gt;sudo gem install rb-appscript&lt;/code&gt;). Once that&amp;#8217;s done:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard --resource switchtunes pep tune
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;To publish User Location, you need to be updating &lt;a href='http://fireeagle.yahoo.net/'&gt;Fire Eagle&lt;/a&gt; (&lt;a href='http://tomtaylor.co.uk/projects/clarke/'&gt;Clarke&lt;/a&gt; is an excellent background updater for OS X) and have the &lt;code&gt;fire-hydrant&lt;/code&gt; gem installed from GitHub (&lt;code&gt;sudo
gem install mojodna-fire-hydrant -s http://gems.github.com&lt;/code&gt;). Once that&amp;#8217;s square:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard --resource switchfire pep location
&lt;/pre&gt;
&lt;/div&gt;
&lt;h4 id='more'&gt;More&lt;/h4&gt;

&lt;p&gt;Switchboard supports more functionality than I&amp;#8217;ve described above. To get a list of general &lt;code&gt;switchboard&lt;/code&gt; commands (some of which may have sub-commands):&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard
&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id='getting_help'&gt;Getting Help&lt;/h3&gt;

&lt;p&gt;In theory, if you want more information about a specific command, you can use &lt;code&gt;switchboard help &amp;lt;command&amp;gt;&lt;/code&gt;. For example:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;switchboard &lt;span class='nb'&gt;help &lt;/span&gt;pubsub
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;For now, you&amp;#8217;ll notice that it&amp;#8217;s not particularly useful. If you&amp;#8217;d like to help rectify this, you can implement various &lt;code&gt;help&lt;/code&gt; methods that are lying around, such as &lt;code&gt;Switchboard::Commands::PubSub.help&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id='extending'&gt;Extending&lt;/h3&gt;

&lt;p&gt;Writing new Switchboard commands is really easy, assuming that the primary application logic that you&amp;#8217;re depending exists elsewhere (i.e. in &lt;code&gt;xmpp4r&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;I was on a panel with &lt;a href='http://stpeter.im/'&gt;Peter St. Andre&lt;/a&gt; and &lt;a href='http://metajack.im/'&gt;Jack Moffitt&lt;/a&gt; at the Glue Conference in Denver this Spring and we got to talking about tools like Switchboard. Jack wondered how hard it would be to implement something like &lt;code&gt;grep&lt;/code&gt; for XMPP.&lt;/p&gt;

&lt;p&gt;(Jack is one of the authors of a Python project similar to Switchboard named &lt;a href='https://launchpad.net/poetry'&gt;&lt;code&gt;poetry&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;I took a whack at it and it came out like this:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='k'&gt;module&lt;/span&gt; &lt;span class='nn'&gt;Switchboard&lt;/span&gt;
  &lt;span class='k'&gt;module&lt;/span&gt; &lt;span class='nn'&gt;Commands&lt;/span&gt;
    &lt;span class='k'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;Grep&lt;/span&gt; &lt;span class='o'&gt;&amp;lt;&lt;/span&gt; &lt;span class='no'&gt;Switchboard&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;Command&lt;/span&gt;
      &lt;span class='n'&gt;description&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;Search for an XPath expression&amp;quot;&lt;/span&gt;

      &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nc'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nf'&gt;run!&lt;/span&gt;
        &lt;span class='n'&gt;expr&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;ARGV&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;pop&lt;/span&gt;

        &lt;span class='n'&gt;switchboard&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Switchboard&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;Client&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;
        &lt;span class='n'&gt;switchboard&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;plug!&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;AutoAcceptJack&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='no'&gt;NotifyJack&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;

        &lt;span class='n'&gt;switchboard&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;on_stanza&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;stanza&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;
          &lt;span class='c1'&gt;# TODO doesn&amp;#39;t handle default namespaces properly&lt;/span&gt;
          &lt;span class='no'&gt;REXML&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;XPath&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;each&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;stanza&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;expr&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;el&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;
            &lt;span class='nb'&gt;puts&lt;/span&gt; &lt;span class='n'&gt;el&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_s&lt;/span&gt;
          &lt;span class='k'&gt;end&lt;/span&gt;
        &lt;span class='k'&gt;end&lt;/span&gt;

        &lt;span class='n'&gt;switchboard&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;run!&lt;/span&gt;
      &lt;span class='k'&gt;end&lt;/span&gt;
    &lt;span class='k'&gt;end&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Jack&lt;/em&gt;s (not Moffitt) deserve their own discussion, but the thrust of this piece of code is the &lt;code&gt;#on_stanza&lt;/code&gt; callback (which yields a &lt;code&gt;REXML::Node&lt;/code&gt; object) and the XPath expression. Note that for whatever reason, you can&amp;#8217;t search for nodes that have a default namespace (e.g. &lt;code&gt;&amp;lt;presence /&amp;gt;&lt;/code&gt;); I&amp;#8217;m going to assume that this is a REXML quirk.&lt;/p&gt;

&lt;p&gt;If you want to take a shot at implementing a Switchboard command, &lt;a href='http://xmpp.org/extensions/xep-0199.html'&gt;Ping&lt;/a&gt; should be pretty simple. Alternately, Switchboard doesn&amp;#8217;t support sending or receiving basic &lt;code&gt;&amp;lt;message
/&amp;gt;&lt;/code&gt; stanzas from the command-line. Supporting those would make it simple to interact with a service like &lt;a href='http://identi.ca/'&gt;identi.ca&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id='how_to_help'&gt;How to Help&lt;/h3&gt;

&lt;p&gt;There are a few simple ways to help out with Switchboard&amp;#8217;s development:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;explore the API&lt;/li&gt;

&lt;li&gt;build out the docs&lt;/li&gt;

&lt;li&gt;implement IM functionality&lt;/li&gt;

&lt;li&gt;write up how to use it for a particular service / use-case&lt;/li&gt;

&lt;li&gt;use it!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What&amp;#8217;s your favorite use for Switchboard?&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/mojodnadotnet/~4/R1gZ4dKGvCM" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://mojodna.net/2009/07/16/switchboard-curl-for-xmpp.html</feedburner:origLink></entry>
 
 <entry>
   <title>Updating Ruby Consumers and Providers to OAuth 1.0a</title>
   <link href="http://feedproxy.google.com/~r/mojodnadotnet/~3/6t2W0mmnwwM/updating-ruby-consumers-and-providers-to-oauth-10a.html" />
   <updated>2009-05-20T00:00:00-07:00</updated>
   <id>http://mojodna.net/2009/05/20/updating-ruby-consumers-and-providers-to-oauth-10a</id>
   <content type="html">&lt;h2 id='updating_ruby_consumers_and_providers_to_oauth_10a'&gt;Updating Ruby Consumers and Providers to OAuth 1.0a&lt;/h2&gt;

&lt;p&gt;In a previous post, I did a &lt;a href='/2009/05/20/updating-ruby-consumers-and-providers-to-oauth-10a.html'&gt;quick run-through of the changes that were introduced in OAuth 1.0a&lt;/a&gt;. As promised, here&amp;#8217;s a rough guide to updating Ruby Consumers and Providers to 1.0a. Don&amp;#8217;t mind the pseudo code.&lt;/p&gt;

&lt;h3 id='updating_ruby_oauth_consumers_to_10a'&gt;Updating Ruby OAuth Consumers to 1.0a&lt;/h3&gt;

&lt;p&gt;In order for things to work properly, you&amp;#8217;ll need to use a version of the OAuth gem that&amp;#8217;s at least &lt;em&gt;0.3.4.1&lt;/em&gt; (0.3.5 was released on 6/3/09). To install it and check the version number:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;sudo gem install oauth
&lt;span class='nv'&gt;$ &lt;/span&gt;oauth --version
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Authorization code that once looked like this:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='n'&gt;request_token&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;consumer&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;get_request_token&lt;/span&gt;
&lt;span class='nb'&gt;puts&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;Please visit the following URL to authorize this application:&amp;quot;&lt;/span&gt;
&lt;span class='nb'&gt;puts&lt;/span&gt; &lt;span class='n'&gt;request_token&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;authorize_url&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='ss'&gt;:oauth_callback&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;callback_url&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='c1'&gt;# wait for input&lt;/span&gt;
&lt;span class='nb'&gt;gets&lt;/span&gt;
&lt;span class='n'&gt;access_token&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;request_token&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;get_access_token&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Should now look like this:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='n'&gt;request_token&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;consumer&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;get_request_token&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='ss'&gt;:oauth_callback&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;callback_url&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='nb'&gt;puts&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;Please visit the following URL to authorize this application:&amp;quot;&lt;/span&gt;
&lt;span class='nb'&gt;puts&lt;/span&gt; &lt;span class='n'&gt;request_token&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;authorize_url&lt;/span&gt;
&lt;span class='c1'&gt;# wait for input&lt;/span&gt;
&lt;span class='nb'&gt;puts&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;What&amp;#39;s the value of `oauth_verifier`?&amp;quot;&lt;/span&gt;
&lt;span class='n'&gt;oauth_verifier&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nb'&gt;gets&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;chomp&lt;/span&gt;
&lt;span class='c1'&gt;# `oauth_verifier` is extracted from the expanded callback URL or was displayed to the user&lt;/span&gt;
&lt;span class='n'&gt;access_token&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;request_token&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;get_access_token&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='ss'&gt;:oauth_verifier&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;oauth_verifier&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You can detect whether a Service Provider supports 1.0a:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='n'&gt;request_token&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;consumer&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;get_request_token&lt;/span&gt;
&lt;span class='nb'&gt;puts&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;OAuth 1.0a detected&amp;quot;&lt;/span&gt; &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='n'&gt;request_token&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;callback_confirmed?&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id='updating_ruby_oauth_providers_to_10a'&gt;Updating Ruby OAuth Providers to 1.0a&lt;/h3&gt;

&lt;p&gt;As with the updates to consumer applications, you&amp;#8217;ll need to be running at least &lt;em&gt;0.3.4.1&lt;/em&gt;. For the sake of simplicity (and additional laziness on my part), I&amp;#8217;ll assume OAuth verification is baked into your Rails app rather than as Rack middleware (where it probably belongs).&lt;/p&gt;

&lt;p&gt;You&amp;#8217;ll need to add a pair of columns to your &lt;code&gt;oauth_request_tokens&lt;/code&gt; table (or whatever it&amp;#8217;s called): &lt;code&gt;callback&lt;/code&gt; and &lt;code&gt;verifier&lt;/code&gt;.&lt;/p&gt;

&lt;h4 id='accepting__during_the_request_token_phase'&gt;Accepting &lt;code&gt;oauth_callback&lt;/code&gt; During the Request Token Phase&lt;/h4&gt;

&lt;p&gt;The first step to supporting OAuth 1.0a is to accept &lt;code&gt;oauth_token&lt;/code&gt; parameters when issuing Request Tokens. To do this, you&amp;#8217;ll need to make the &lt;code&gt;OAuth::RequestProxy::ActionControllerRequest&lt;/code&gt; available to methods that run later in a request&amp;#8217;s lifecycle:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;verify_oauth_signature&lt;/span&gt;
  &lt;span class='n'&gt;valid&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;OAuth&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;Signature&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;verify&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;request&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;request_proxy&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;
    &lt;span class='c1'&gt;# make the request proxy available outside this block&lt;/span&gt;
    &lt;span class='vi'&gt;@_request_proxy&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;request_proxy&lt;/span&gt;
    
    &lt;span class='c1'&gt;# proceed normally...&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;

&lt;span class='c1'&gt;# accessor for the request proxy&lt;/span&gt;
&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;oauth_request_proxy&lt;/span&gt;
  &lt;span class='vi'&gt;@_request_proxy&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Once this is set up, you&amp;#8217;ll need to modify your &lt;code&gt;request_token&lt;/code&gt; method to associate the &lt;code&gt;oauth_callback&lt;/code&gt; parameter with your Request token and set &lt;code&gt;oauth_callback_confirmed&lt;/code&gt; to &lt;em&gt;true&lt;/em&gt;:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;request_token&lt;/span&gt;
  &lt;span class='n'&gt;request_token&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;new_request_token&lt;/span&gt;
   &lt;span class='c1'&gt;# request_proxy provides unified interface to params + headers&lt;/span&gt;
  &lt;span class='n'&gt;request_token&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;callback&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;request_proxy&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;oauth_callback&lt;/span&gt;
  &lt;span class='n'&gt;request_token&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;save&lt;/span&gt;
  
  &lt;span class='n'&gt;render&lt;/span&gt; &lt;span class='ss'&gt;:text&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;oauth_token=&lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;request_token&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;token&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;&amp;amp;&amp;quot;&lt;/span&gt; &lt;span class='p'&gt;\&lt;/span&gt;
                  &lt;span class='s2'&gt;&amp;quot;oauth_token_secret=&lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;request_token&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;secret&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;&amp;amp;&amp;quot;&lt;/span&gt; &lt;span class='p'&gt;\&lt;/span&gt;
                  &lt;span class='s2'&gt;&amp;quot;oauth_callback_confirmed=true&amp;quot;&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;h4 id='generating_an__during_the_authorization_phase'&gt;Generating An &lt;code&gt;oauth_verifier&lt;/code&gt; During the Authorization Phase&lt;/h4&gt;

&lt;p&gt;In addition to validating the Request Token during the authorization phase, you&amp;#8217;ll want to generate an &lt;code&gt;oauth_verifier&lt;/code&gt; value and return it to the application via the pre-registered callback url (or display it to the user and instruct them to enter it into their application).&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;authorize&lt;/span&gt;
  &lt;span class='c1'&gt;# display the authorization page&lt;/span&gt;
  
  &lt;span class='n'&gt;render&lt;/span&gt; &lt;span class='ow'&gt;and&lt;/span&gt; &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='k'&gt;unless&lt;/span&gt; &lt;span class='n'&gt;request&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;post?&lt;/span&gt;

  &lt;span class='c1'&gt;# validate the token&lt;/span&gt;

  &lt;span class='n'&gt;request_token&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;find_request_token&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;params&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='ss'&gt;:oauth_token&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='n'&gt;request_token&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;validated&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='kp'&gt;true&lt;/span&gt;
  &lt;span class='c1'&gt;# generate a verification code&lt;/span&gt;
  &lt;span class='n'&gt;request_token&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;verifier&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;generate_verifier&lt;/span&gt;
  &lt;span class='n'&gt;request_token&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;save&lt;/span&gt;
  
  &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='n'&gt;request_token&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;callback?&lt;/span&gt;
    &lt;span class='c1'&gt;# this was previously params[:oauth_callback]&lt;/span&gt;
    &lt;span class='n'&gt;redirect_to&lt;/span&gt; &lt;span class='n'&gt;request_token&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;callback&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;?oauth_verifier=&lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;request_token&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;verifier&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;&lt;/span&gt;
  &lt;span class='k'&gt;else&lt;/span&gt;
    &lt;span class='vi'&gt;@verifier&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;request_token&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;verifier&lt;/span&gt;
    &lt;span class='n'&gt;render&lt;/span&gt; &lt;span class='c1'&gt;# display the verification code to the user&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;h4 id='verifying_access_token_exchanges'&gt;Verifying Access Token Exchanges&lt;/h4&gt;

&lt;p&gt;When exchanging a Request Token for an Access Token, you need to confirm that the verification code provided by the consumer matches the one on file.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;access_token&lt;/span&gt;
  &lt;span class='c1'&gt;# this is a correctly signed request: oauth_token has already been loaded&lt;/span&gt;

  &lt;span class='c1'&gt;# compare the verifier from the request proxy to the one we generated&lt;/span&gt;
  &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='n'&gt;oauth_token&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;verifier&lt;/span&gt; &lt;span class='o'&gt;==&lt;/span&gt; &lt;span class='n'&gt;request_proxy&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;oauth_verifier&lt;/span&gt;
    &lt;span class='c1'&gt;# exchange the request token for an access token&lt;/span&gt;
    &lt;span class='n'&gt;access_token&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;oauth_token&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;exchange&lt;/span&gt;
    &lt;span class='n'&gt;render&lt;/span&gt; &lt;span class='ss'&gt;:text&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;oauth_token=&lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;access_token&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;token&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;&amp;amp;&amp;quot;&lt;/span&gt; &lt;span class='p'&gt;\&lt;/span&gt;
                    &lt;span class='s2'&gt;&amp;quot;oauth_token_secret=&lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;access_token&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;secret&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;&lt;/span&gt;
  &lt;span class='k'&gt;else&lt;/span&gt;
    &lt;span class='k'&gt;raise&lt;/span&gt; &lt;span class='no'&gt;OAuth&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;InvalidVerifier&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id='summary'&gt;Summary&lt;/h3&gt;

&lt;p&gt;Obviously, there are cleaner ways to do this, but they&amp;#8217;re presumably very specific to individual codebases. If you have questions, check out the &lt;a href='http://groups.google.com/group/oauth-ruby'&gt;oauth-ruby&lt;/a&gt; mailing list. Otherwise, patches can be submitted against &lt;a href='http://github.com/mojodna/oauth/tree/mergeable'&gt;http://github.com/mojodna/oauth/tree/mergeable&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Good luck!&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/mojodnadotnet/~4/6t2W0mmnwwM" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://mojodna.net/2009/05/20/updating-ruby-consumers-and-providers-to-oauth-10a.html</feedburner:origLink></entry>
 
 <entry>
   <title>An Idiot's Guide to OAuth 1.0a</title>
   <link href="http://feedproxy.google.com/~r/mojodnadotnet/~3/s9L6t-J_buw/an-idiots-guide-to-oauth-10a.html" />
   <updated>2009-05-20T00:00:00-07:00</updated>
   <id>http://mojodna.net/2009/05/20/an-idiots-guide-to-oauth-10a</id>
   <content type="html">&lt;h2 id='an_idiots_guide_to_oauth_10a'&gt;An Idiot&amp;#8217;s Guide to OAuth 1.0a&lt;/h2&gt;

&lt;p&gt;I&amp;#8217;m lazy. I don&amp;#8217;t usually enjoy re-reading things I&amp;#8217;ve already read before (Matt Ruff and Neil Stephenson books are an exception). So, as a service to all 3 of my readers, I&amp;#8217;ll summarize the changes to the &lt;a href='http://oauth.net/'&gt;OAuth&lt;/a&gt; specification for &lt;a href='http://oauth.googlecode.com/svn/spec/core/1.0a/drafts/3/oauth-core-1_0a.html'&gt;1.0a (draft 3)&lt;/a&gt; as I understand them. Finding changes in a large document is a pain&amp;#8211;you have better things to do with your time. Embrace your inner ignorance and leave responsibility to the wind.&lt;/p&gt;

&lt;p&gt;As an added bonus, I&amp;#8217;ll demonstrate how to &lt;a href='/2009/05/20/updating-ruby-consumers-and-providers-to-oauth-10a.html'&gt;update consumer and provider code using the Ruby library&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id='what_happened'&gt;What Happened?&lt;/h3&gt;

&lt;p&gt;&lt;a href='http://www.readwriteweb.com/archives/how_the_oauth_security_battle_was_won_open_web_sty.php'&gt;Much&lt;/a&gt; has been written about this &lt;a href='http://www.hueniverse.com/hueniverse/2009/04/explaining-the-oauth-session-fixation-attack.html'&gt;elsewhere&lt;/a&gt;, so I&amp;#8217;ll be brief. A &lt;a href='http://oauth.net/advisories/2009-1'&gt;&lt;em&gt;session fixation&lt;/em&gt; attack was discovered&lt;/a&gt; a little over a month ago. Essentially, it affixes &lt;strong&gt;your&lt;/strong&gt; sessions to someone else&amp;#8217;s. Imagine burrs or inverted balls of duct tape with little teeny messages (on grains of rice) attached to them.&lt;/p&gt;

&lt;p&gt;Anyway. It was bad. It was one of those things that once you&amp;#8217;ve seen, it&amp;#8217;s impossible to un-see. There was no straightforward fix that could be deployed, so you started seeing lots of BIG ANGRY warning notices explaining that your car might be repossessed if you were bold enough to continue.&lt;/p&gt;

&lt;p&gt;The longer-term solution was to &lt;a href='http://oauth.googlecode.com/svn/spec/core/1.0a/drafts/3/oauth-core-1_0a.html'&gt;change the specification&lt;/a&gt;, get providers on-board to change their code, and finally to reach out to developers to update their client applications. All so the world doesn&amp;#8217;t implode next time you turn right. DON&amp;#8217;T TURN RIGHT. Ok?&lt;/p&gt;

&lt;h3 id='so_what_changed'&gt;So, What Changed?&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Callbacks (to your site or application) have been supported in various forms (with varying restrictions), but they have always been parameters passed to the Service Provider&amp;#8217;s authorization page. Instead, the &lt;code&gt;oauth_callback&lt;/code&gt; parameter is now part of the Request Token phase. If your client cannot accept callbacks, the value &lt;strong&gt;MUST&lt;/strong&gt; be &lt;code&gt;oob&lt;/code&gt;. SPs use this to determine whether a client supports 1.0a.&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;&lt;code&gt;oauth_callback_confirmed&lt;/code&gt; will be present (indeed, &lt;strong&gt;MUST&lt;/strong&gt; according to the spec) when Service Providers supporting 1.0a issue a Request Token. (e.g. &lt;code&gt;oauth_token=asdf&amp;amp;oauth_token_secret=qwerty&amp;amp;oauth_callback_confirmed=true&lt;/code&gt;) Clients can use this to determine whether a server supports 1.0a.&lt;/p&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;p&gt;An &lt;code&gt;oauth_verifier&lt;/code&gt; parameter is provided to the client either in the pre-configured callback URL or through the fingers of your users (the aforementioned &lt;code&gt;oob&lt;/code&gt; (&amp;#8220;out of band&amp;#8221;) mechanism). This value &lt;strong&gt;MUST&lt;/strong&gt; be included when exchanging Request Tokens for Access Tokens.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The changes to the spec are limited to the Request Token ➡ Access Token exchange, so once you have an Access Token, everything should behave as it did before. For this reason, clients that only implement 2-legged OAuth are unaffected.&lt;/p&gt;

&lt;p&gt;&lt;a href='http://oauth.googlecode.com/svn/spec/core/1.0a/drafts/3/oauth-core-1_0a.html#anchor38'&gt;Section 11.14. Cross-Site Request Forgery (CSRF)&lt;/a&gt; is also worth reading (and understanding) in order to further secure the use of callback URLs.&lt;/p&gt;

&lt;h3 id='services_supporting_oauth_10a'&gt;Services Supporting OAuth 1.0a&lt;/h3&gt;

&lt;p&gt;At the time of this writing, the only service provider I&amp;#8217;m aware of that supports 1.0a is &lt;a href='http://developer.yahoo.net/blog/archives/2009/05/oauth_update_3.html'&gt;Yahoo!&lt;/a&gt;. This doesn&amp;#8217;t include Fire Eagle (unless you include my laptop), although we intend for this to change in the next couple weeks. Google&amp;#8217;s OAuth endpoint should support 1.0a soon, but I&amp;#8217;ve yet to see any confirmation.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/mojodnadotnet/~4/s9L6t-J_buw" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://mojodna.net/2009/05/20/an-idiots-guide-to-oauth-10a.html</feedburner:origLink></entry>
 
 <entry>
   <title>My (public) Git Workflow</title>
   <link href="http://feedproxy.google.com/~r/mojodnadotnet/~3/Ycn1RsF7LHQ/my-public-git-workflow.html" />
   <updated>2009-03-09T00:00:00-07:00</updated>
   <id>http://mojodna.net/2009/03/09/my-public-git-workflow</id>
   <content type="html">&lt;h2 id='my_public_git_workflow'&gt;My (public) Git Workflow&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem&lt;/strong&gt;: You want to be an effective contributor to a public project that&amp;#8217;s using Git.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The solution&lt;/strong&gt;: Follow conventions and keep clean branches to make them easier to merge.&lt;/p&gt;

&lt;h3 id='lets_go'&gt;Let&amp;#8217;s Go&lt;/h3&gt;

&lt;p&gt;For your own projects, it doesn&amp;#8217;t really matter what you do to publish and maintain your code, at least until other people start forking and attempting to hack on your stuff. In any case, you should keep things simple. Use topic branches for particular features or refactoring if you want, but no big deal.&lt;/p&gt;

&lt;h4 id='determining_active_and_authoritative_forks'&gt;Determining &amp;#8220;Active&amp;#8221; and &amp;#8220;Authoritative&amp;#8221; Forks&lt;/h4&gt;

&lt;p&gt;&lt;a href='http://github.com/blog/309-new-and-improved-search'&gt;GitHub&amp;#8217;s search&lt;/a&gt; attempts to surface active projects by displaying the number of forks, number of watchers, and recent activity, but it doesn&amp;#8217;t tell the full story. The &amp;#8220;root&amp;#8221; of the fork tree (which isn&amp;#8217;t really a tree, but is &lt;em&gt;kinda&lt;/em&gt; useful to think of that way) isn&amp;#8217;t necessarily the current focus of activity. Someone may have published a project and lost interest, but a community may have formed around one of the forks.&lt;/p&gt;

&lt;p&gt;Alternately, a project may have been imported from Subversion by a wannabe contributor prior to the original author joining GitHub and applying community-created patches to his or her own fork (which, at this point, isn&amp;#8217;t the root of the fork tree).&lt;/p&gt;

&lt;p&gt;If there are a relatively low number of forks (say, one or two), you can (mostly) assume that the fork with the most activity is authoritative. Other contributors, with forks that come and go or that are generally out of date, can be considered &amp;#8220;outsiders.&amp;#8221;&lt;/p&gt;

&lt;p&gt;For projects with a larger number of forks, there may be multiple authorities (and potentially even multiple development directions), with many outsiders periodically contributing patches.&lt;/p&gt;

&lt;p&gt;All of this means that you, as a potential contributor, need to do some additional research to determine which fork (or forks) is &amp;#8220;active&amp;#8221; and/or &amp;#8220;authoritative&amp;#8221;. They may not be one and the same; the &amp;#8220;authoritative&amp;#8221; fork may focus on stability; you may need bleeding edge features developed by an &amp;#8220;authoritative&amp;#8221; member of the community instead.&lt;/p&gt;

&lt;p&gt;GitHub&amp;#8217;s &lt;a href='http://github.com/blog/39-say-hello-to-the-network-graph-visualizer'&gt;Network Graph&lt;/a&gt; is a great tool for this purpose. From the interface, you can see which forks have been the most active (hint: more &amp;#8226;&amp;#8217;s), most recently active (&amp;#8226;&amp;#8217;s further right), or have had the most merges (more ⤴&amp;#8217;s). Those don&amp;#8217;t tell the full story, so you&amp;#8217;ll want to mouse-over individual &amp;#8226;&amp;#8217;s to see who wrote the patch (and who committed it). That will help distinguish between the forks where the patches originated and where they&amp;#8217;ve merely been pulled in. You&amp;#8217;ll also get a sense of the number and names of people working on a project.&lt;/p&gt;

&lt;h4 id='some_rules_of_thumb'&gt;Some Rules of Thumb&lt;/h4&gt;

&lt;p&gt;When hacking on other peoples&amp;#8217; stuff, your eventual goal is (probably) to get those changes applied. Here are a few tips to make life easier for everyone involved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First rule of thumb&lt;/strong&gt;: merge from authoritative contributors, cherry pick from outsiders.&lt;/p&gt;

&lt;p&gt;By merging (e.g. &lt;code&gt;git merge user/master&lt;/code&gt;) from authoritative contributors, it becomes easier for them to apply your changes if and when they deem them fit. Merging from outsiders may force the authorities into accepting patches (or series) that they may not be ready for (as those branches may include changes unrelated to what you were working on).&lt;/p&gt;

&lt;p&gt;GitHub&amp;#8217;s Fork Queue cherry picks (e.g. &lt;code&gt;git cherry-pick 469df8&lt;/code&gt;) commits by default, so that&amp;#8217;s a good way to get your tree in order before adding your own changes. If there are changes to your fork&amp;#8217;s parent, you can &lt;a href='http://github.com/blog/266-fast-forward-your-fork'&gt;fast forward your fork&lt;/a&gt; before starting work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second rule of thumb&lt;/strong&gt;: keep your changes easily apply-able to the authoritative repository.&lt;/p&gt;

&lt;p&gt;For a single line of development, this is easy: rebase against the authoritative &lt;em&gt;master&lt;/em&gt; branch whenever possible. Feel free to rewrite history (i.e. &lt;code&gt;git push -f&lt;/code&gt;) as long as you&amp;#8217;re confident no one is treating you as authoritative.&lt;/p&gt;

&lt;p&gt;If others &lt;em&gt;are&lt;/em&gt; treating you as an authoritative source, create a separate branch that gets periodically rebased and make it clear that it&amp;#8217;s not suitable for tracking. Others&amp;#8217; changes will eventually need to be rebased against those changes, but probably not until your changes have been applied by a real authoritative contributor.&lt;/p&gt;

&lt;p&gt;For changes that do many things, this means keeping multiple branches active. I usually use a branch per feature, plus a separate branch for general bugfixes and documentation. For minor changes, all branches can diverge from a common commit (usually the tip of the authoritative repository&amp;#8217;s &lt;em&gt;master&lt;/em&gt; branch).&lt;/p&gt;

&lt;p&gt;For more drastic changes, you&amp;#8217;ll need to decide which is either the most important feature or the least controversial change. Use that as the first patch series and rebase it against the authoritative repository&amp;#8217;s &lt;em&gt;HEAD&lt;/em&gt;. Rebase subsequent patch series against the next most important (or least controversial).&lt;/p&gt;

&lt;p&gt;Write down the order in which your branches should be merged. You can use your project wiki or put it at the top of the &lt;code&gt;README&lt;/code&gt; in your &lt;em&gt;master&lt;/em&gt; branch. (&lt;a href='http://github.com/mojodna/xmpp4r/blob/06ec50260555ba242880dc2c69229ea5f8ff811e/README.rdoc'&gt;See my fork of &lt;em&gt;xmpp4r&lt;/em&gt; to see how I&amp;#8217;ve done it.&lt;/a&gt;) At this point, your &lt;em&gt;master&lt;/em&gt; branch is just the default view of your fork and shouldn&amp;#8217;t be considerably different from the branch of the authoritative copy you&amp;#8217;re tracking (see the exception below).&lt;/p&gt;

&lt;p&gt;In some cases, you&amp;#8217;ll have branches that aren&amp;#8217;t quite ready for prime-time. Treat them the same as branches that are, but don&amp;#8217;t make apply-able branches dependent on them (i.e. they should always be the last changes to be applied). Include them in the summary in your &lt;code&gt;README&lt;/code&gt;, but make it clear that they&amp;#8217;re not ready yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third rule of thumb&lt;/strong&gt;: reset your history to match an authoritative repository&amp;#8217;s history if their content is the same (or essentially the same).&lt;/p&gt;

&lt;p&gt;If your history frequently diverges from an authoritative repository&amp;#8217;s history, you&amp;#8217;re going to have more trouble applying others&amp;#8217; changes and synchronizing with authoritative repositories.&lt;/p&gt;

&lt;p&gt;The simplest way is to delete your repository and re-fork. If you have minor changes, generate a diff to apply once you&amp;#8217;ve re-forked.&lt;/p&gt;

&lt;h4 id='an_exception'&gt;An Exception&lt;/h4&gt;

&lt;p&gt;Because GitHub will only build gems for changes on the &lt;em&gt;master&lt;/em&gt; branch, if you have changes that you want to publish (prepared neatly in topic branches, as above), you&amp;#8217;ll need to break the rules somewhat.&lt;/p&gt;

&lt;p&gt;To handle this scenario, start with your &lt;em&gt;master&lt;/em&gt; branch matching the authoritative branch you&amp;#8217;re tracking, then merge branches like the owner of the authoritative branch would (this has the helpful side-effect of discovering whether your changes will apply cleanly).&lt;/p&gt;

&lt;p&gt;The tip of your &lt;em&gt;master&lt;/em&gt; should always contain the most up-to-date gemspec (in order to trigger GitHub&amp;#8217;s building and publishing of your fork) as well as a &lt;code&gt;README&lt;/code&gt; that contains information on the state of your repository (including branches, as above, but also information about how you&amp;#8217;re treating the &lt;em&gt;master&lt;/em&gt; branch).&lt;/p&gt;

&lt;h3 id='maintaining_a_project'&gt;Maintaining a Project&lt;/h3&gt;

&lt;p&gt;Over the last couple months, I&amp;#8217;ve become one of the authoritative forks for the &lt;a href='http://github.com/mojodna/oauth'&gt;OAuth gem&lt;/a&gt;, indirectly resulting in this document. I&amp;#8217;ve attempted to be consistent in the management of my fork, including publishing periodic releases for others to test functionality, but it became clear that my methods weren&amp;#8217;t as transparent as they ought to be.&lt;/p&gt;

&lt;p&gt;As a maintainer (or authoritative fork), you frequently find the need to merge in work from other developers. GitHub&amp;#8217;s &lt;a href='http://github.com/blog/270-the-fork-queue'&gt;Fork Queue&lt;/a&gt; is excellent for this, but it&amp;#8217;s not the last word.&lt;/p&gt;

&lt;p&gt;The Fork Queue is perfect for merging individual commits, but when someone submits a patch series, it&amp;#8217;s time to make a decision: you can &lt;em&gt;cherry-pick&lt;/em&gt; (e.g. &lt;code&gt;git cherry-pick 469df8&lt;/code&gt;) all of their commits (which is what the Fork Queue does behind the scenes)&amp;#8211;retaining authorship but setting the &lt;em&gt;committer&lt;/em&gt; field to yourself&amp;#8211;or you can pull their branch down and perform a &lt;em&gt;merge&lt;/em&gt; (e.g. &lt;code&gt;git merge user/master&lt;/code&gt;), retaining both &lt;em&gt;author&lt;/em&gt; and &lt;em&gt;committer&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Cherry picking commits is easier&amp;#8211;unless you want/need to make adjustments to them (in which case the SHAs would change anyway)&amp;#8211;and results in linear history (no merges) with the downside of having repetitive commits (with different &lt;em&gt;committer&lt;/em&gt;s) across the project network, thus confusing GitHub&amp;#8217;s Network Graph (or anything that displays the history of multiple branches) and making it more difficult to find un-applied patches (as the same patch may exist with multiple SHAs).&lt;/p&gt;

&lt;p&gt;Merging changes locally is more difficult, but the &lt;a href='http://github.com/defunkt/github-gem'&gt;github gem&lt;/a&gt; makes it easier by pulling in the entire patch universe with &lt;code&gt;gh network fetch&lt;/code&gt;. Once that&amp;#8217;s been done, the combination of &lt;code&gt;gh network commits&lt;/code&gt;, &lt;a href='http://gitx.frim.nl/'&gt;GitX&lt;/a&gt;, and the Network Graph make it relatively easy to identify commits ripe for picking.&lt;/p&gt;

&lt;p&gt;An advantage of cherry picking is that you have the freedom to amend (&lt;code&gt;git
commit --amend&lt;/code&gt;) commits before pushing them out as part of your tree. This is a great opportunity to clean things up to match local coding standards (that should already be the case, but YMMV) and to add additional context to the commit message.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; You can achieve the same effect as amending commits by using &lt;code&gt;git
format-patch -1 469df8&lt;/code&gt; to create a patch, applying it with &lt;code&gt;git apply&lt;/code&gt;, pausing to clean it up, and committing it with &lt;code&gt;git commit -c 469df8&lt;/code&gt;. &lt;a href='http://gitready.com/'&gt;git ready&lt;/a&gt; has a good article on &lt;a href='http://gitready.com/intermediate/2009/03/04/pick-out-individual-commits.html'&gt;picking out individual commits&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The main problem with cherry picking becomes more acute when there are multiple authoritative forks (which should maintain a consistent public state) constantly passing work back and forth. If the participants periodically merge others&amp;#8217; work, you end up with a number of merge commits, but a relatively coherent history. If one or more participants choose to cherry pick commits instead, merging their branches back will include repeated commits with different SHAs and &lt;em&gt;committer&lt;/em&gt; fields. The resultant state of the tree is fine, but the history gets somewhat polluted and becomes more difficult to follow.&lt;/p&gt;

&lt;p&gt;This can be mitigated by sending patches via email (or any mechanism by which public repositories don&amp;#8217;t change) and repeatedly munging history locally, but that&amp;#8217;s often not an option, given the level of coordination required.&lt;/p&gt;

&lt;h3 id='aside_complexity_and__or_maturity'&gt;Aside: Complexity and / or Maturity&lt;/h3&gt;

&lt;p&gt;I used to see complicated Network Graphs (with lots of merging and branching) as a sign of project activity, but now I consider it more as a measure of contributors&amp;#8217; comfort with &lt;code&gt;git&lt;/code&gt; (as individual contributors may be responsible for multiple branches). I believe that the main reason that projects end up with complicated history is due to (relatively) limited coordination between collaborator and maintainer; if I had to guess, I would expect closed projects using Git to have much more linear histories.&lt;/p&gt;

&lt;p&gt;Ultimately, I think complicated histories are a side-effect of Git&amp;#8217;s merging prowess promoting and supporting the drive-by contributor model. However, as powerful as Git is, coordination between collaborators still lessens the burden on the person doing the merging, thus the above manifesto.&lt;/p&gt;

&lt;h2 id='now_what'&gt;Now What?&lt;/h2&gt;

&lt;p&gt;Get yourself out there, coding whatever and whenever, and contribute back. You&amp;#8217;ll feel good and you will have made the world a little better, one patch at a time.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/mojodnadotnet/~4/Ycn1RsF7LHQ" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://mojodna.net/2009/03/09/my-public-git-workflow.html</feedburner:origLink></entry>
 
 <entry>
   <title>My (work) Git Workflow</title>
   <link href="http://feedproxy.google.com/~r/mojodnadotnet/~3/7Q1GdAFUuKg/my-work-git-workflow.html" />
   <updated>2009-02-24T00:00:00-08:00</updated>
   <id>http://mojodna.net/2009/02/24/my-work-git-workflow</id>
   <content type="html">&lt;h2 id='my_work_git_workflow'&gt;My (work) Git Workflow&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem&lt;/strong&gt;: You want to track multiple patchsets against an upstream Subversion repository easily.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The solution&lt;/strong&gt;: Use &lt;code&gt;git-svn&lt;/code&gt; to track it and create topic branches for local changes.&lt;/p&gt;

&lt;p&gt;&lt;a href='#the_sizzle'&gt;Jump here for my actual workflow&lt;/a&gt;&lt;/p&gt;

&lt;h3 id='a_notsobrief_introduction_with_code'&gt;A Not-So-Brief Introduction (with code!)&lt;/h3&gt;

&lt;p&gt;I started using &lt;a href='http://git-scm.com/'&gt;&lt;code&gt;git&lt;/code&gt;&lt;/a&gt; and &lt;a href='http://www.kernel.org/pub/software/scm/git/docs/git-svn.html'&gt;&lt;code&gt;git-svn&lt;/code&gt;&lt;/a&gt; a little over a year ago. I was an Amtrak regular at the time, so the ability to make offline commits seemed helpful. Plus, it was shiny, and I&amp;#8217;d seen a demo of &lt;a href='http://github.com/'&gt;GitHub&lt;/a&gt;, so I had a hunch that it was going to be big.&lt;/p&gt;

&lt;p&gt;One day, I sat down over lunch and watched most of &lt;a href='http://www.youtube.com/watch?v=4XpnKHJAok8'&gt;Linus Torvalds&amp;#8217; git talk at Google&lt;/a&gt;. Until then, I hadn&amp;#8217;t really considered DVCSes, staying in my comfortable little Subversion world, branching out to &lt;code&gt;cvs&lt;/code&gt; and &lt;code&gt;bzr&lt;/code&gt; as necessary. I don&amp;#8217;t really remember what I got out of the talk, but somehow it inspired me to start reading up on how &lt;code&gt;git&lt;/code&gt; could be used with &lt;code&gt;svn&lt;/code&gt;, much like &lt;code&gt;svk&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git-svn&lt;/code&gt; made its debut performance on our team&amp;#8217;s IRC channel about a month later; I had been doing some major refactoring on a local git branch with lots of commits (squashing commits was mystery to be discovered later) and the time had come to merge it into &lt;em&gt;trunk&lt;/em&gt;. The merge went fine (lucky, I guess), so I went for a &lt;code&gt;git svn dcommit&lt;/code&gt;. And spammed the IRC channel for the next 30 seconds or so.&lt;/p&gt;

&lt;p&gt;Flash forward to the following winter and new (remote) contributors to our project. It was time to start doing legitimate code reviews, in part to get the new folks up to speed with the code-base.&lt;/p&gt;

&lt;p&gt;In the process of setting up &lt;a href='http://www.review-board.org/'&gt;Review Board&lt;/a&gt;, I discovered that it doesn&amp;#8217;t play well with diffs generated with &lt;code&gt;git&lt;/code&gt;. Bummer. They&amp;#8217;re nice. I spent some time poking around RB&amp;#8217;s source tree to see why that was the case. Long story short, no dice. However, on one of the message boards where the problem had been raised (no resolution, natch), I saw a snippet to transform &lt;code&gt;git&lt;/code&gt; diffs into &lt;code&gt;svn&lt;/code&gt; diffs, revision numbers and all. Bingo. After fiddling with it for a while, I ended up with the following, which has been working well.&lt;/p&gt;
&lt;script src='http://gist.github.com/44537.js'&gt;
// nothing here
&lt;/script&gt;
&lt;p&gt;Add the following to your &lt;code&gt;~/.gitconfig&lt;/code&gt; to enable &lt;code&gt;git svn-diff&lt;/code&gt;. Awesome. It even looks like it belongs there. (&lt;code&gt;git-svn-diff&lt;/code&gt; must be in your &lt;code&gt;PATH&lt;/code&gt;.)&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c'&gt;# ~/.gitconfig&lt;/span&gt;
&lt;span class='k'&gt;[alias]&lt;/span&gt;
  &lt;span class='na'&gt;svn-diff&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;!git-svn-diff&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;&lt;div class='caption'&gt;add &lt;code&gt;git svn-diff&lt;/code&gt; as an alias for
&lt;code&gt;git-svn-diff&lt;/code&gt;&lt;/div&gt;
&lt;h4 id='bonus_points'&gt;Bonus Points&lt;/h4&gt;

&lt;p&gt;Here are the relevant pieces of my &lt;code&gt;~/.bashrc&lt;/code&gt; to get tab completion and a colorized prompt that includes &lt;code&gt;git branch&lt;/code&gt; information in it:&lt;/p&gt;
&lt;script src='http://gist.github.com/69960.js'&gt;
// nothing here
&lt;/script&gt;
&lt;h3 id='the_sizzle'&gt;The Sizzle&lt;/h3&gt;

&lt;p&gt;This is essentially the &lt;code&gt;git-svn&lt;/code&gt; variation of &lt;a href='http://blog.hasmanythrough.com/2008/12/18/agile-git-and-the-story-branch-pattern'&gt;Josh Susser&amp;#8217;s pure &lt;code&gt;git&lt;/code&gt; workflow&lt;/a&gt;.&lt;/p&gt;

&lt;h4 id='the_setup'&gt;The Setup&lt;/h4&gt;

&lt;p&gt;Clone the target Subversion repository:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;git svn clone svn+ssh://svn.host/path/to/repo -s
&lt;/pre&gt;
&lt;/div&gt;&lt;div class='caption'&gt;assuming a standard Subversion layout&lt;/div&gt;
&lt;p&gt;Alternately, if you&amp;#8217;re planning on sharing topic branches with fellow developers, have one person do the clone and pass it out (by copying it, to retain Subversion metadata).&lt;/p&gt;

&lt;h4 id='fixing_a_bug__implementing_a_feature'&gt;Fixing a Bug / Implementing a Feature&lt;/h4&gt;

&lt;p&gt;Make sure you&amp;#8217;re up-to-date:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='o'&gt;[&lt;/span&gt;master&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git svn rebase
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Create a topic branch (I include a title to recognize it more easily) and check it out:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='o'&gt;[&lt;/span&gt;master&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git checkout -b bug-42-title
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Attempt a bug fix (write a test, make it pass):&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='o'&gt;[&lt;/span&gt;bug-42-title&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;...
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Check it in:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='o'&gt;[&lt;/span&gt;bug-42-title&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git commit -a
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Generate a patch for review:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='o'&gt;[&lt;/span&gt;bug-42-title&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git svn-diff &amp;gt; bug-42-title.patch
&lt;/pre&gt;
&lt;/div&gt;&lt;div class='caption'&gt;this will diff against the checked out trunk
revision&lt;/div&gt;
&lt;p&gt;Post it for review.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Time passes&amp;#8230; Your diff hasn&amp;#8217;t been reviewed, development continues&amp;#8230;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Update the tracking branch:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='o'&gt;[&lt;/span&gt;master&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git svn rebase
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Rebase your topic branch against the current trunk:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='o'&gt;[&lt;/span&gt;master&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git checkout bug-42-title
&lt;span class='o'&gt;[&lt;/span&gt;bug-42-title&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git rebase master
&lt;span class='o'&gt;[&lt;/span&gt;bug-42-title&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;&lt;span class='c'&gt;# resolve conflicts; `git mergetool` is handy&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Regenerate the patch:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='o'&gt;[&lt;/span&gt;bug-42-title&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git svn-diff &amp;gt; bug-42-title-2.patch
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Post it for review.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Time passes&amp;#8230; Your diff has been reviewed and been found wanting&amp;#8230;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Make changes to your topic branch:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='o'&gt;[&lt;/span&gt;bug-42-title&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;...
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Check them in:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='o'&gt;[&lt;/span&gt;bug-42-title&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git commit -a
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Regenerate the patch:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='o'&gt;[&lt;/span&gt;bug-42-title&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git svn-diff &amp;gt; bug-42-title-3.patch
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Post it for review.&lt;/p&gt;

&lt;h4 id='landing_a_reviewed_patch'&gt;Landing a Reviewed Patch&lt;/h4&gt;

&lt;p&gt;You have a few options here.&lt;/p&gt;

&lt;p&gt;You can apply the patch directly to the tracking branch:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='o'&gt;[&lt;/span&gt;master&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git apply bug-42-title-3.patch
&lt;span class='o'&gt;[&lt;/span&gt;master&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git commit -a
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you want to preserve history (i.e. multiple commits that tell a story), update the tracking branch and rebase your topic branch against it before merging:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='o'&gt;[&lt;/span&gt;master&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git svn rebase
&lt;span class='o'&gt;[&lt;/span&gt;master&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git checkout bug-42-title
&lt;span class='o'&gt;[&lt;/span&gt;bug-42-title&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git rebase master
&lt;span class='o'&gt;[&lt;/span&gt;bug-42-title&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;&lt;span class='c'&gt;# resolve conflicts&lt;/span&gt;
&lt;span class='o'&gt;[&lt;/span&gt;bug-42-title&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git checkout master
&lt;span class='o'&gt;[&lt;/span&gt;master&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git merge bug-42-title
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you want to get fancy (and remove your frustrated profanity), do an interactive rebase on the topic branch before merging:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='o'&gt;[&lt;/span&gt;bug-42-title&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git rebase -i
&lt;span class='o'&gt;[&lt;/span&gt;bug-42-title&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git checkout master
&lt;span class='o'&gt;[&lt;/span&gt;master&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git merge bug-42-title
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Whew. Almost done. You&amp;#8217;ll want to update the upstream Subversion repository, lest you risk your hard work being wasted:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='o'&gt;[&lt;/span&gt;master&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git svn dcommit
&lt;/pre&gt;
&lt;/div&gt;&lt;div class='caption'&gt;this will &lt;code&gt;git svn rebase&lt;/code&gt; if necessary&lt;/div&gt;
&lt;p&gt;If you don&amp;#8217;t have write access to the upstream repository, submit the patch by mail instead.&lt;/p&gt;

&lt;p&gt;Finally, once the patch has been merged, you can clean up your local repository by removing the topic branch:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='o'&gt;[&lt;/span&gt;master&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git branch -d bug-42-title
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Topic branches needn&amp;#8217;t to be limited to your own bug-fixes. &lt;code&gt;git apply&lt;/code&gt; is quite liberal in what it understands, so you can grab diffs (from ReviewBoard, for example) and apply them to new branches to try out uncommitted functionality (or as an aid to the code reviewing process).&lt;/p&gt;

&lt;p&gt;That&amp;#8217;s it! You can keep as many topic branches active as you want (or need to), rebasing them against your tracking branch as necessary. I use this approach for bugs, features, and exploratory refactoring that may never see the light of day.&lt;/p&gt;

&lt;h4 id='working_with_subversion_branches'&gt;Working with Subversion Branches&lt;/h4&gt;

&lt;p&gt;Working with Subversion branches is remarkably straightforward, but it&amp;#8217;s necessary to understand a few things about the repository layout and the branches that &lt;code&gt;git-svn&lt;/code&gt; sets up when it does the initial clone.&lt;/p&gt;

&lt;p&gt;First, if you did a &lt;code&gt;git svn clone -s&lt;/code&gt; against a Subversion repository with a typical layout (&lt;code&gt;trunk&lt;/code&gt;, &lt;code&gt;branches/&amp;lt;branch&amp;gt;&lt;/code&gt;, &lt;code&gt;tags/&amp;lt;tag&amp;gt;&lt;/code&gt;), a number of remote Git branches will have been created: one for &lt;code&gt;trunk&lt;/code&gt;, one for each branch, and one for each tag (odd, but sensible given that Subversion doesn&amp;#8217;t strictly &lt;strong&gt;do&lt;/strong&gt; tags).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git branch&lt;/code&gt; will list local branches:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='o'&gt;[&lt;/span&gt;master&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git branch
  bug-42-title
* master
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;git branch -r&lt;/code&gt; will list Subversion branches (and tags):&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='o'&gt;[&lt;/span&gt;master&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git branch -r
  stable-1.0
  tags/REL_1.0
  trunk
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;By default, your local &lt;code&gt;master&lt;/code&gt; branch will be set up to track the remote &lt;code&gt;trunk&lt;/code&gt; branch. Instead of using &lt;code&gt;git pull&lt;/code&gt; (in a pure Git workflow) to update &lt;code&gt;trunk&lt;/code&gt; to the current remote revision, use &lt;code&gt;git svn rebase&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you want to ensure that you have up-to-date versions of all Subversion branches, use &lt;code&gt;git svn fetch&lt;/code&gt; to load upstream commits. This has the side-effect of causing &lt;code&gt;master&lt;/code&gt; and &lt;code&gt;trunk&lt;/code&gt; to be out-of-sync, so you&amp;#8217;ll have to follow that with &lt;code&gt;git svn rebase&lt;/code&gt; from the master branch. &lt;a href='http://gitx.frim.nl/'&gt;GitX&lt;/a&gt; will show you the current state of both local and remote branches, and thus is immensely useful when attempting to determine your current repository state.&lt;/p&gt;

&lt;p&gt;In case you didn&amp;#8217;t fully grok that last paragraph, where you would have used &lt;code&gt;git svn rebase&lt;/code&gt; without branches, you should do the following (rebasing still works, but your branches won&amp;#8217;t be up-to-date):&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='o'&gt;[&lt;/span&gt;master&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git svn fetch
&lt;span class='o'&gt;[&lt;/span&gt;master&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git svn rebase
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;git svn rebase&lt;/code&gt; in this case is the equivalent to &lt;code&gt;git rebase trunk&lt;/code&gt;, as &lt;code&gt;trunk&lt;/code&gt; is the &amp;#8220;SVN parent of the current HEAD&amp;#8221; (from &lt;code&gt;git help svn&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git svn dcommit&lt;/code&gt; will use &lt;code&gt;svn&lt;/code&gt; to commit outstanding changes upstream (again, to the &amp;#8220;SVN parent of the current HEAD&amp;#8221;). The upstream branch in question depends on the Subversion branch that your current branch is tracking, usually using &lt;code&gt;git branch --track &amp;lt;local&amp;gt; &amp;lt;remote&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Subversion branches and &amp;#8220;tags&amp;#8221; can also be created with &lt;code&gt;git-svn&lt;/code&gt;. For example, to tag and branch a 1.1 release:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='o'&gt;[&lt;/span&gt;master&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git svn branch -m &lt;span class='s2'&gt;&amp;quot;branching for 1.1&amp;quot;&lt;/span&gt; stable-1.1
&lt;span class='o'&gt;[&lt;/span&gt;master&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git svn branch -m &lt;span class='s2'&gt;&amp;quot;1.1 release&amp;quot;&lt;/span&gt; -t REL_1.1
&lt;span class='o'&gt;[&lt;/span&gt;master&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git branch -r
  stable-1.0
  stable-1.1
  tags/REL_1.0
  tags/REL_1.1
  trunk
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Commit messages are necessary, as branching in Subversion is the equivalent to creating a changeset where a directory is copied.&lt;/p&gt;

&lt;p&gt;Now that the branch has been created upstream, create a local tracking branch such that &lt;code&gt;git svn dcommit&lt;/code&gt; will commit to the correct upstream branch.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='o'&gt;[&lt;/span&gt;master&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git branch --track stable-1.1 stable-1.1
&lt;span class='o'&gt;[&lt;/span&gt;master&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='nv'&gt;$ &lt;/span&gt;git checkout stable-1.1
&lt;span class='o'&gt;[&lt;/span&gt;stable-1.1&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='err'&gt;$&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;h4 id='extra_credit'&gt;Extra Credit&lt;/h4&gt;

&lt;p&gt;Convert Subversion &amp;#8220;tags&amp;#8221; into proper Git tags:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c'&gt;#!/bin/sh&lt;/span&gt;
&lt;span class='c'&gt;#&lt;/span&gt;
&lt;span class='c'&gt;# git-svn-convert-tags&lt;/span&gt;
&lt;span class='c'&gt;# Convert Subversion &amp;quot;tags&amp;quot; into Git tags&lt;/span&gt;
&lt;span class='k'&gt;for &lt;/span&gt;tag in &lt;span class='sb'&gt;`&lt;/span&gt;git branch -r | grep &lt;span class='s2'&gt;&amp;quot;  tags/&amp;quot;&lt;/span&gt; | sed &lt;span class='s1'&gt;&amp;#39;s/  tags\///&amp;#39;&lt;/span&gt;&lt;span class='sb'&gt;`&lt;/span&gt;; &lt;span class='k'&gt;do&lt;/span&gt;
&lt;span class='k'&gt;  &lt;/span&gt;git tag &lt;span class='nv'&gt;$tag&lt;/span&gt; refs/remotes/tags/&lt;span class='nv'&gt;$tag&lt;/span&gt;
&lt;span class='k'&gt;done&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Add the following to your &lt;code&gt;~/.gitconfig&lt;/code&gt; to enable &lt;code&gt;git svn-convert-tags&lt;/code&gt;. (&lt;code&gt;git-svn-convert-tags&lt;/code&gt; must be in your &lt;code&gt;PATH&lt;/code&gt;.)&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c'&gt;# ~/.gitconfig&lt;/span&gt;
&lt;span class='k'&gt;[alias]&lt;/span&gt;
  &lt;span class='na'&gt;svn-convert-tags&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;!git-svn-convert-tags&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/mojodnadotnet/~4/7Q1GdAFUuKg" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://mojodna.net/2009/02/24/my-work-git-workflow.html</feedburner:origLink></entry>
 
 <entry>
   <title>Extending ActiveRecord Attributes</title>
   <link href="http://feedproxy.google.com/~r/mojodnadotnet/~3/KFN40om43KQ/extending-activerecord-attributes.html" />
   <updated>2007-06-14T00:00:00-07:00</updated>
   <id>http://mojodna.net/2007/06/14/extending-activerecord-attributes</id>
   <content type="html">&lt;h2 id='extending_activerecord_attributes'&gt;Extending ActiveRecord Attributes&lt;/h2&gt;

&lt;p&gt;David Black came to speak at the &lt;a href='http://boston.rubygroup.org/'&gt;Boston Ruby Group&lt;/a&gt; on Tuesday (along with Zed Shaw, making it the most star-studded event since I started going). It was in one of the lecture halls at the Harvard Science Center (which, if you follow the pattern of every single other building on campus, is named after the &amp;#8220;Science Center&amp;#8221; family; seems like it should be Merrill, or Morrill, or something instead&amp;#8211;here&amp;#8217;s your chance). I was expecting stand-up comedy, which turned out to be an unreasonable expectation because it was in lecture hall A, not C (which is where HSUCS (a group of stand-up comedians at Harvard) has had events in the past). But I digress.&lt;/p&gt;

&lt;p&gt;David&amp;#8217;s talk was about &amp;#8220;Per Object Behavior in Ruby&amp;#8221; and about treating it as a prototype language (a logical follow-up to Dave Thomas&amp;#8217; RailsConf keynote; he didn&amp;#8217;t disappoint and provided classless code (except for the tests; that would have been a nice touch)). I won&amp;#8217;t go much into the substance of the talk, except to say that he managed to clarify the meaning and use of an object&amp;#8217;s &amp;#8220;singleton class&amp;#8221; / &amp;#8220;eigenclass&amp;#8221; as well as further emphasizing that class != type, but that a class is merely a template from which new objects spring. He encouraged people to take advantage of Ruby&amp;#8217;s ability to extend individual instances of objects with methods (generally via modules), as that&amp;#8217;s a considerable part of what makes Ruby Ruby.&lt;/p&gt;

&lt;p&gt;I was reminded that I had written some ActiveRecord code this spring that allows &amp;#8220;primitive&amp;#8221; (&lt;code&gt;String&lt;/code&gt;, &lt;code&gt;Fixnum&lt;/code&gt;) attributes of AR objects to be embellished by extending the attributes&amp;#8217; objects before returning them from the containing object.&lt;/p&gt;

&lt;p&gt;In our case, we had a notion of &lt;code&gt;FormattedText&lt;/code&gt; objects, which were essentially &lt;code&gt;String&lt;/code&gt;s that could contain HTML. Unfiltered HTML, as it turned out. Rather than filtering during the input stage, we decided that we would store what the user provided and leave it to the output stage to handle the filtering. Instead of using a helper method in each and every case (with the downside of having to remember to use it), I decided that the filtering functionality would fit better within the object (so it would be used appropriately in the majority case without any extra thought). In this case, by overriding &lt;code&gt;String#to_s&lt;/code&gt;.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='k'&gt;module&lt;/span&gt; &lt;span class='nn'&gt;FormattedText&lt;/span&gt;
  &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;to_s&lt;/span&gt;
    &lt;span class='c1'&gt;# filter and output&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The next step was making sure that only attributes known to contain formatted text were extended by &lt;code&gt;FormattedText&lt;/code&gt; (and that the internal representation remained untouched, so that the filtered version wouldn&amp;#8217;t be saved back to the database). A side-effect of this is that changing the value that was returned and saving the object will not save the new value back to the database; to get around this, you&amp;#8217;ll need to set the value on the AR object itself.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c1'&gt;# Quick and dirty monkey-patch (should work with 1.1.x+)&lt;/span&gt;
&lt;span class='k'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;ActiveRecord&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;Base&lt;/span&gt;
  &lt;span class='c1'&gt;# Attribute extension&lt;/span&gt;
  &lt;span class='n'&gt;cattr_accessor&lt;/span&gt; &lt;span class='ss'&gt;:extended_attributes&lt;/span&gt;
  &lt;span class='vc'&gt;@@extended_attributes&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;{}&lt;/span&gt;

  &lt;span class='k'&gt;class&lt;/span&gt; &lt;span class='o'&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class='nb'&gt;self&lt;/span&gt;
    &lt;span class='c1'&gt;# create the &amp;quot;extends&amp;quot; declaration&lt;/span&gt;
    &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;extends&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;attr_name&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;options&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
      &lt;span class='n'&gt;extended_attributes&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='n'&gt;attr_name&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_sym&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;options&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='ss'&gt;:with&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;
    &lt;span class='k'&gt;end&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;

  &lt;span class='n'&gt;alias_method&lt;/span&gt; &lt;span class='ss'&gt;:define_read_method_without_extends&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:define_read_method&lt;/span&gt;
  &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;define_read_method_with_extends&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;symbol&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;attr_name&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;column&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='k'&gt;unless&lt;/span&gt; &lt;span class='nb'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;class&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;read_methods&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;include?&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;attr_name&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;||&lt;/span&gt;
           &lt;span class='nb'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;class&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;extended_attributes&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;keys&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;include?&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;attr_name&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_sym&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
      &lt;span class='n'&gt;define_read_method_without_enhances&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;symbol&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;attr_name&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;column&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='k'&gt;else&lt;/span&gt;
      &lt;span class='n'&gt;old_method_name&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;&lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;symbol&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;_without_extends&amp;quot;&lt;/span&gt;
      &lt;span class='n'&gt;define_read_method_without_extends&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;old_method_name&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_sym&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;attr_name&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;column&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
      &lt;span class='n'&gt;evaluate_read_method&lt;/span&gt; &lt;span class='n'&gt;attr_name&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;def &lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;symbol&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;; enhance(&lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;old_method_name&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;, &amp;#39;&lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;attr_name&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;&amp;#39;); end&amp;quot;&lt;/span&gt;
    &lt;span class='k'&gt;end&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
  &lt;span class='n'&gt;alias_method&lt;/span&gt; &lt;span class='ss'&gt;:define_read_method&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:define_read_method_with_extends&lt;/span&gt;

  &lt;span class='c1'&gt;# Override read_attribute for first call (pre-edge)&lt;/span&gt;
  &lt;span class='c1'&gt;# or when AR::Base.generate_read_methods = false&lt;/span&gt;
  &lt;span class='n'&gt;alias_method&lt;/span&gt; &lt;span class='ss'&gt;:read_attribute_without_extends&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:read_attribute&lt;/span&gt;
  &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;read_attribute_with_extends&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;attr_name&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='n'&gt;enhance&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;read_attribute_without_extends&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;attr_name&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt; &lt;span class='n'&gt;attr_name&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
  &lt;span class='n'&gt;alias_method&lt;/span&gt; &lt;span class='ss'&gt;:read_attribute&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:read_attribute_with_extends&lt;/span&gt;

  &lt;span class='kp'&gt;private&lt;/span&gt;

  &lt;span class='c1'&gt;# Enhance the attribute as appropriate&lt;/span&gt;
  &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;enhance&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;value&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;attr_name&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='n'&gt;mod&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nb'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;class&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;extended_attributes&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='n'&gt;attr_name&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_sym&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;
    &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='n'&gt;mod&lt;/span&gt; &lt;span class='o'&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class='o'&gt;!&lt;/span&gt;&lt;span class='n'&gt;value&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;nil?&lt;/span&gt;
      &lt;span class='n'&gt;value&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;dup&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;extend&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;mod&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='k'&gt;else&lt;/span&gt;
      &lt;span class='n'&gt;value&lt;/span&gt;
    &lt;span class='k'&gt;end&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;An alternate way to implement this pattern would have been to use &lt;code&gt;composed_of&lt;/code&gt; and to create a &lt;code&gt;FormattedText&lt;/code&gt; class that extend &lt;code&gt;String&lt;/code&gt;. That may be better for performance reasons (as it seems that it would simplify method lookup), but strikes me as being slightly less Rubylike.&lt;/p&gt;

&lt;p&gt;What do you think?&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/mojodnadotnet/~4/KFN40om43KQ" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://mojodna.net/2007/06/14/extending-activerecord-attributes.html</feedburner:origLink></entry>
 
 <entry>
   <title>Teach Capistrano to Deploy From a Tag or Branch</title>
   <link href="http://feedproxy.google.com/~r/mojodnadotnet/~3/4bJyoAXaHjM/teach-capistrano-to-deploy-from-a-tag-or-branch.html" />
   <updated>2007-03-08T00:00:00-08:00</updated>
   <id>http://mojodna.net/2007/03/08/teach-capistrano-to-deploy-from-a-tag-or-branch</id>
   <content type="html">&lt;h2 id='teach_capistrano_to_deploy_from_a_tag_or_branch'&gt;Teach Capistrano to Deploy From a Tag or Branch&lt;/h2&gt;

&lt;p&gt;There comes a time in the life of many an application where it becomes more or less stable. And once it becomes stable, it also becomes boring because the changes that a developer really wants to make are bound to make it unstable, and thus unsuitable for deployment.&lt;/p&gt;

&lt;p&gt;An oft-used approach to this situation is to create a stable maintenance branch (i.e. &lt;code&gt;application/branches/1.0&lt;/code&gt;) and create tags for point releases (i.e. &lt;code&gt;application/tags/1.0.6&lt;/code&gt;), continuing on the trunk with potentially destabilizing work.&lt;/p&gt;

&lt;p&gt;Out of the box, Capistrano only supports deploying from the tip of the trunk. The snippet below demonstrates how to deploy from a branch (to a QA or staging environment) or tag (releases to production should &lt;strong&gt;always&lt;/strong&gt; have tags). Stick it in your &lt;code&gt;config/deploy.rb&lt;/code&gt;.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='n'&gt;set&lt;/span&gt; &lt;span class='ss'&gt;:base_repository&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;http://svn.mojodna.net/repository/&lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;application&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;&lt;/span&gt;
&lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='n'&gt;variables&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='ss'&gt;:tag&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;
  &lt;span class='n'&gt;set&lt;/span&gt; &lt;span class='ss'&gt;:repository&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;&lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;base_repository&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;/tags/&lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;variables&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='ss'&gt;:tag&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;&lt;/span&gt;
&lt;span class='k'&gt;elsif&lt;/span&gt; &lt;span class='n'&gt;variables&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='ss'&gt;:branch&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;
  &lt;span class='n'&gt;set&lt;/span&gt; &lt;span class='ss'&gt;:repository&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;&lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;base_repository&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;/branches/&lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;variables&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='ss'&gt;:branch&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;&lt;/span&gt;
&lt;span class='k'&gt;else&lt;/span&gt;
  &lt;span class='n'&gt;set&lt;/span&gt; &lt;span class='ss'&gt;:repository&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;&lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;base_repository&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;/trunk&amp;quot;&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;With this modification in place, you can now deploy from a branch:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ RAILS_ENV&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;qa cap deploy -Sbranch&lt;span class='o'&gt;=&lt;/span&gt;1.0
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Or from a tag:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ RAILS_ENV&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;production cap deploy -Stag&lt;span class='o'&gt;=&lt;/span&gt;1.0.6
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Go forth and revel in your instability!&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/mojodnadotnet/~4/4bJyoAXaHjM" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://mojodna.net/2007/03/08/teach-capistrano-to-deploy-from-a-tag-or-branch.html</feedburner:origLink></entry>
 
 <entry>
   <title>Classloading in Rails</title>
   <link href="http://feedproxy.google.com/~r/mojodnadotnet/~3/HPTIolDOwf4/classloading-in-rails.html" />
   <updated>2007-02-12T00:00:00-08:00</updated>
   <id>http://mojodna.net/2007/02/12/classloading-in-rails</id>
   <content type="html">&lt;h2 id='classloading_in_rails'&gt;Classloading in Rails&lt;/h2&gt;

&lt;p&gt;Occasionally, I see code similar to the following used as a shortcut to load all model objects. It&amp;#8217;s usually in a Rake task or somewhere that follows a &amp;#8220;global require&amp;#8221; by looping through all available objects and doing something with them. It&amp;#8217;s simple, straightforward, and a logical follow up to shorthand like &lt;code&gt;[&amp;quot;post&amp;quot;, &amp;quot;user&amp;quot;].each { |m| require m }&lt;/code&gt;. However, it often sits out there like a dormant volcano, with a wide variety of unpredictable behavior waiting to erupt.&lt;/p&gt;

&lt;p&gt;In our case, we&amp;#8217;d been using said snippet to help test out &lt;a href='http://www.danga.com/memcached/'&gt;memcached&lt;/a&gt;; we would occasionally be handed serialized objects of unrecognizable types; the source of the problem turned out to be that the object&amp;#8217;s class definition had not yet been loaded. We also discovered that finders in intermediate subclasses (we use &lt;abbr title='Single
Table Inheritance'&gt;STI&lt;/abbr&gt; extensively) would occasionally fail to find instances of subclasses, again because the subclasses had not been loaded (and the intermediate subclass was unable to provide a complete list of its subclasses).&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c1'&gt;# load all models explicitly&lt;/span&gt;
&lt;span class='no'&gt;Dir&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;glob&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;File&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;join&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;RAILS_ROOT&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;app&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;models&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;*.rb&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;each&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;rbfile&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;
  &lt;span class='nb'&gt;require&lt;/span&gt; &lt;span class='n'&gt;rbfile&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;There are a couple of problems with this approach. It doesn&amp;#8217;t guarantee that a model will only be loaded once&amp;#8211;classes may have already been loaded through some external mechanism. Alternately, dependencies may be loaded while loading a specific class. In both cases, the above code may cause classes to be loaded multiple times (which can cause bizarre behavior). It also doesn&amp;#8217;t handle namespaced models correctly.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c1'&gt;# force loading (but not reloading) of all models by saying their name&lt;/span&gt;
&lt;span class='c1'&gt;# Adapted from PragDave&amp;#39;s Annotate Models plugin.&lt;/span&gt;
&lt;span class='n'&gt;model_path&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;File&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;join&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;RAILS_ROOT&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;app&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;models&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='no'&gt;Dir&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;glob&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;File&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;join&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;model_path&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;**&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;*.rb&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;each&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;m&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;
  &lt;span class='n'&gt;class_name&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;m&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;sub&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;model_path&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='no'&gt;File&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;SEPARATOR&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;sub&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='sr'&gt;/\\.rb$/&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;camelize&lt;/span&gt;
  &lt;span class='n'&gt;klass&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;class_name&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;split&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;::&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;inject&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;Object&lt;/span&gt;&lt;span class='p'&gt;){&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;klass&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='n'&gt;part&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt; &lt;span class='n'&gt;klass&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;const_get&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;part&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;}&lt;/span&gt; &lt;span class='k'&gt;rescue&lt;/span&gt; &lt;span class='kp'&gt;nil&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This approach is considerably better; it supports namespaced models and defers the actual classloading to Rails (note the lack of an explicit &lt;code&gt;require&lt;/code&gt;), which prevents classes from being loaded multiple times.&lt;/p&gt;

&lt;p&gt;Anything to avoid time-eating debugging sessions, like the one described in &lt;a href='http://blog.duncandavidson.com/2006/11/rails_sometimes.html'&gt;Rails Sometimes Eats Class Load Errors&lt;/a&gt;. In our case, it had to do with &lt;a href='http://code.google.com/p/activemessaging/'&gt;ActiveMessaging&lt;/a&gt; and classes silently failing to load outside the development environment. The fix wound up being requiring &lt;code&gt;config/messaging.rb&lt;/code&gt; before the block above. The lesson learned is to make sure that any dependent resources for models are loaded &lt;strong&gt;first&lt;/strong&gt; and to look for them if things are acting strangely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; The above primarily relevant to Rails 1.1. 1.2 exhibits similar behavior, but it gets even weirder, as the class loading mechanism was rewritten. There, you&amp;#8217;ll find that STI works properly the first time (because all classes have been explicitly loaded), but if &lt;code&gt;cache_classes&lt;/code&gt; is on (the default in development mode), they&amp;#8217;ll be unloaded after the first request and thus will fail to work on subsequent requests.&lt;/p&gt;

&lt;p&gt;Our rather hackish solution is to trigger a &lt;code&gt;before_filter&lt;/code&gt; in &lt;code&gt;ApplicationController&lt;/code&gt; when &lt;code&gt;cache_classes&lt;/code&gt; is determined to be on (when &lt;code&gt;Dependencies.mechanism == :load&lt;/code&gt;):&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='n'&gt;before_filter&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt;
  &lt;span class='n'&gt;model_path&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;File&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;join&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;RAILS_ROOT&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;app&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;models&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='no'&gt;Dir&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;glob&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;File&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;join&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;model_path&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;**&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;*.rb&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;each&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;m&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;
    &lt;span class='n'&gt;class_name&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;m&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;sub&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;model_path&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='no'&gt;File&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;SEPARATOR&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;sub&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='sr'&gt;/\\.rb$/&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;camelize&lt;/span&gt;
    &lt;span class='n'&gt;klass&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;class_name&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;split&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;::&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;inject&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;Object&lt;/span&gt;&lt;span class='p'&gt;){&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;klass&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='n'&gt;part&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt; &lt;span class='n'&gt;klass&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;const_get&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;part&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;}&lt;/span&gt; &lt;span class='k'&gt;rescue&lt;/span&gt; &lt;span class='kp'&gt;nil&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt; &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='no'&gt;Dependencies&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;mechanism&lt;/span&gt; &lt;span class='o'&gt;==&lt;/span&gt; &lt;span class='ss'&gt;:load&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/mojodnadotnet/~4/HPTIolDOwf4" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://mojodna.net/2007/02/12/classloading-in-rails.html</feedburner:origLink></entry>
 
 <entry>
   <title>Searchable: Annotation-Driven Indexing and Searching with Lucene</title>
   <link href="http://feedproxy.google.com/~r/mojodnadotnet/~3/-aZTNDIlyIQ/searchable-annotation-driven-indexing-and-searching-with-lucene.html" />
   <updated>2006-10-02T00:00:00-07:00</updated>
   <id>http://mojodna.net/2006/10/02/searchable-annotation-driven-indexing-and-searching-with-lucene</id>
   <content type="html">&lt;h2 id='searchable_annotationdriven_indexing_and_searching_with_lucene'&gt;Searchable: Annotation-Driven Indexing and Searching with Lucene&lt;/h2&gt;

&lt;h3 id='overview'&gt;Overview&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;&lt;a href='http://github.com/mojodna/searchable'&gt;Searchable&lt;/a&gt; is a toolkit for Lucene that harnesses the power of annotations to specify what properties to index and how to treat them.&lt;/em&gt;&lt;/p&gt;

&lt;h3 id='basics'&gt;Basics&lt;/h3&gt;

&lt;p&gt;&lt;a href='http://github.com/mojodna/searchable'&gt;Searchable&lt;/a&gt; requires JDK 1.5 and Lucene 1.9+.&lt;/p&gt;

&lt;p&gt;At its core, Searchable provides a set of annotations that can be used to instruct its indexer how to deal with properties present in an annotated bean. It can also be used without the annotations (by writing a custom &lt;strong&gt;Indexer&lt;/strong&gt;/&lt;strong&gt;Searcher&lt;/strong&gt; combination). However, Searchable really shines when custom &lt;strong&gt;Indexers&lt;/strong&gt; and &lt;strong&gt;Searchers&lt;/strong&gt; are combined with the annotations.&lt;/p&gt;

&lt;p&gt;Searchable 0.8-SNAPSHOT can use &lt;a href='http://lucene.apache.org/solr/'&gt;Apache Solr&lt;/a&gt; for indexing and searching via &lt;strong&gt;SolrIndexer&lt;/strong&gt; and &lt;strong&gt;SolrSearcher&lt;/strong&gt; (&lt;strong&gt;SchemaGenerator&lt;/strong&gt; will create an appropriate Solr schema for you; some tweaks may be necessary).&lt;/p&gt;

&lt;p&gt;Searchable is available on GitHub here: &lt;a href='http://github.com/mojodna/searchable'&gt;http://github.com/mojodna/searchable&lt;/a&gt; Do with it what you will.&lt;/p&gt;

&lt;p&gt;Please see the examples below for pointers on usage.&lt;/p&gt;

&lt;h3 id='annotations'&gt;Annotations&lt;/h3&gt;

&lt;p&gt;In order for the annotations to have any effect, they must annotate a class that extends &lt;strong&gt;Searchable&lt;/strong&gt;. With the exception of @DefaultFields, they must all be placed on the reader method of a property (&lt;code&gt;getXXX()&lt;/code&gt;). Unlike traditional Annotation behavior, they may be placed on an interface method or a method that is overridden.&lt;/p&gt;

&lt;h4 id='id'&gt;@ID&lt;/h4&gt;

&lt;p&gt;This allows a property other than &lt;em&gt;id&lt;/em&gt; to be specified as the id field. There should only be one per class hierarchy.&lt;/p&gt;

&lt;p&gt;e.g.:&lt;/p&gt;

&lt;p&gt;&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nd'&gt;@ID&lt;/span&gt;
&lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='n'&gt;Integer&lt;/span&gt; &lt;span class='nf'&gt;getKey&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt; &lt;span class='o'&gt;...&lt;/span&gt; &lt;span class='o'&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;&lt;/p&gt;

&lt;h4 id='indexed'&gt;@Indexed&lt;/h4&gt;

&lt;p&gt;This specifies that a field should be indexed. The following attributes are available:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;aliases - Array of aliases to also use as field names. Default: none.&lt;/li&gt;

&lt;li&gt;boost - Boost factor (as a float) for this field. Default: 1.0.&lt;/li&gt;

&lt;li&gt;name - Field name to use for this property. Default: property name.&lt;/li&gt;

&lt;li&gt;nested - Whether to index this field in a nested context (i.e. a &lt;em&gt;Searchable&lt;/em&gt; as a property of another &lt;em&gt;Searchable&lt;/em&gt;). Default: false.&lt;/li&gt;

&lt;li&gt;stored - Store this property in the index. Default: false.&lt;/li&gt;

&lt;li&gt;storeTermVector - Store term vectors for this field. Default: false.&lt;/li&gt;

&lt;li&gt;tokenized - Tokenize the value of this property before adding it to the index. Default: true.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;e.g.:&lt;/p&gt;

&lt;p&gt;&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nd'&gt;@Indexed&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;boost&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='mf'&gt;2.0&lt;/span&gt;&lt;span class='n'&gt;F&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt;
&lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='n'&gt;String&lt;/span&gt; &lt;span class='nf'&gt;getName&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt; &lt;span class='o'&gt;...&lt;/span&gt; &lt;span class='o'&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;&lt;/p&gt;

&lt;h4 id='stored'&gt;@Stored&lt;/h4&gt;

&lt;p&gt;This specifies that a field should be stored in the index. In typical usage, this should not be necessary, although you may find that it comes in hand from time to time. The following attributes are available:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;aliases - Array of aliases to also use as field names. Default: none.&lt;/li&gt;

&lt;li&gt;name - Field name to use for this property. Default: property name.&lt;/li&gt;

&lt;li&gt;nested - Whether to store this field in a nested context (i.e. a &lt;em&gt;Searchable&lt;/em&gt; as a property of another &lt;em&gt;Searchable&lt;/em&gt;). Default: false.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;e.g.:&lt;/p&gt;

&lt;p&gt;&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nd'&gt;@Stored&lt;/span&gt;
&lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='n'&gt;String&lt;/span&gt; &lt;span class='nf'&gt;getDescription&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt; &lt;span class='o'&gt;...&lt;/span&gt; &lt;span class='o'&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;&lt;/p&gt;

&lt;h4 id='sortable'&gt;@Sortable&lt;/h4&gt;

&lt;p&gt;This specifies that a custom Keyword field should be created to use for sorting results in Lucene. The following attribute is available:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;nested - Whether to create a sorted field for this property in a nested context (i.e. a &lt;em&gt;Searchable&lt;/em&gt; as a property of another &lt;em&gt;Searchable&lt;/em&gt;). Default: false.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;e.g.:&lt;/p&gt;

&lt;p&gt;&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nd'&gt;@Indexed&lt;/span&gt;
&lt;span class='nd'&gt;@Sortable&lt;/span&gt;
&lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='n'&gt;String&lt;/span&gt; &lt;span class='nf'&gt;getName&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt; &lt;span class='o'&gt;...&lt;/span&gt; &lt;span class='o'&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;&lt;/p&gt;

&lt;h4 id='defaultfields'&gt;@DefaultFields&lt;/h4&gt;

&lt;p&gt;This contains an array of field names that should be used as the default list. If this annotation is not present and no fields are specified when searching, the default is to use all fields present in the index.&lt;/p&gt;

&lt;p&gt;Used during the search process.&lt;/p&gt;

&lt;p&gt;e.g.:&lt;/p&gt;

&lt;p&gt;&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nd'&gt;@DefaultFields&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;address.city&amp;quot;&lt;/span&gt; &lt;span class='o'&gt;}&lt;/span&gt; &lt;span class='o'&gt;)&lt;/span&gt;
&lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='kd'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;Person&lt;/span&gt; &lt;span class='kd'&gt;implements&lt;/span&gt; &lt;span class='n'&gt;Searchable&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt; &lt;span class='o'&gt;...&lt;/span&gt; &lt;span class='o'&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;&lt;/p&gt;

&lt;h4 id='excerptable'&gt;@Excerptable&lt;/h4&gt;

&lt;p&gt;This specifies that the annotated property should be used when creating a search extract. &lt;strong&gt;NOTE&lt;/strong&gt;: The resultant object must be hydrated &lt;strong&gt;in your code&lt;/strong&gt; before it can be excerpted. In many cases, this involves reloading the object from Hibernate Session by using &lt;em&gt;session.load()&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;e.g.:&lt;/p&gt;

&lt;p&gt;&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nd'&gt;@Indexed&lt;/span&gt;
&lt;span class='nd'&gt;@Excerptable&lt;/span&gt;
&lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='n'&gt;String&lt;/span&gt; &lt;span class='nf'&gt;getDescription&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt; &lt;span class='o'&gt;...&lt;/span&gt; &lt;span class='o'&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;&lt;/p&gt;

&lt;h3 id='extension_points'&gt;Extension Points&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;AbstractSearcher&lt;/em&gt;, &lt;em&gt;AbstractMultiSearcher&lt;/em&gt;, &lt;em&gt;AbstractIndexer&lt;/em&gt;, and &lt;em&gt;AbstractBeanIndexer&lt;/em&gt; are provided as abstract base classes with the majority of necessary functionality provided as protected methods. &lt;em&gt;AbstractSearcher&lt;/em&gt; exposes multiple signatures for certain methods that allow alternate implementations of Lucene &lt;em&gt;IndexReader&lt;/em&gt;s and &lt;em&gt;Searcher&lt;/em&gt;s. &lt;em&gt;AbstractMultiSearcher&lt;/em&gt; makes use of these to implement cross-index searching (an example is provided below), but a similar approach could be used to implement remote searching.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;setAnalyzer(Analyzer)&lt;/code&gt; and &lt;code&gt;setIndexPath(String)&lt;/code&gt; should be used to override the default behavior of Searchable. Both are best called in your constructor.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;getIndexReader()&lt;/code&gt;, &lt;code&gt;getIndexModifier()&lt;/code&gt;, and &lt;code&gt;getIndexSearcher()&lt;/code&gt; provide shared access to &lt;em&gt;IndexReader&lt;/em&gt;s, &lt;em&gt;IndexModifier&lt;/em&gt;s, and &lt;em&gt;IndexSearcher&lt;/em&gt;s over the index specified using &lt;code&gt;setIndexPath(String)&lt;/code&gt;. In certain circumstances, you may wish to override these methods to provide alternate implementations (a &lt;em&gt;MultiSearcher&lt;/em&gt; for example; if you wish to provide a &lt;em&gt;RemoteSearchable&lt;/em&gt;, you must define an additional method, as &lt;code&gt;getIndexSearcher()&lt;/code&gt; returns a &lt;em&gt;Searcher&lt;/em&gt;, not a &lt;em&gt;Searchable&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Searcher&lt;/em&gt; and &lt;em&gt;Indexer&lt;/em&gt; are provided as interfaces that may be extended to expose additional functionality to your application in a generic fashion.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Result&lt;/em&gt; and &lt;em&gt;ResultImpl&lt;/em&gt; have been split into an interface and an implementation in order to hide the &lt;code&gt;add(Result)&lt;/code&gt; and &lt;code&gt;replace(Result,
Result)&lt;/code&gt; methods as well as to provide additional flexibility for alternate implementations. One such alternate implementation would also implement &lt;a href='http://displaytag.sf.net/'&gt;DisplayTag&lt;/a&gt;&amp;#8217;s &lt;em&gt;PaginatedList&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AbstractResult&lt;/em&gt; is made available as a base class suitable for extension by objects that implement &lt;em&gt;Result&lt;/em&gt; and are not required to extend anything else.&lt;/p&gt;

&lt;h3 id='batch_indexing'&gt;Batch Indexing&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;BatchIndexer&lt;/em&gt; extends the &lt;em&gt;Indexer&lt;/em&gt; interface by introducing three methods: &lt;code&gt;flush()&lt;/code&gt; (to be implemented by the indexer), &lt;code&gt;setBatchMode(boolean)&lt;/code&gt;, and &lt;code&gt;isBatchMode()&lt;/code&gt;, of which the latter two are provided by the indexing infrastructure. &lt;code&gt;flush()&lt;/code&gt; is executed during &lt;code&gt;close()&lt;/code&gt; immediately before the index is optimized. A typical implementation calls &lt;code&gt;flushDeletes()&lt;/code&gt;. which flushes any document deletions that had previously been queued (rather than flushing them immediately, as in a non-batch indexer). The hybrid example below demonstrates a &lt;em&gt;BatchIndexer&lt;/em&gt; in action.&lt;/p&gt;

&lt;h3 id='limitations'&gt;Limitations&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;AbstractSearcher&lt;/em&gt; does not yet support default field arrays as arguments to the various &lt;code&gt;doSearch()&lt;/code&gt; methods.&lt;/p&gt;

&lt;p&gt;The @DefaultFields and @Excerptable annotations are only available on objects that implement &lt;em&gt;Searchable&lt;/em&gt;. Ideally, they would be available for any @Result, as there&amp;#8217;s nothing that necessarily limits their use to &lt;em&gt;Searchable&lt;/em&gt;s.&lt;/p&gt;

&lt;p&gt;Fields that are stored in the index are returned in a storedFields Map (specified in the &lt;em&gt;Result&lt;/em&gt; interface) regardless of whether or not the names correspond to properties on the object being returned. In the future, Lucene could be used as a persistence tool of sorts by reconstituting the object as much as possible. Thus, if all properties of an object were indexed (and stored), the object could be fully reconstituted and no external persistence mechanism would be necessary. The trade-off is index size, so this would not be feasible for large datasets.&lt;/p&gt;

&lt;h3 id='examples'&gt;Examples&lt;/h3&gt;

&lt;h4 id='simple_example'&gt;Simple Example&lt;/h4&gt;

&lt;p&gt;This example demonstrates how to use Searchable without using the annotations.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AddressIndexer.java:&lt;/em&gt;&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c'&gt;/**&lt;/span&gt;
&lt;span class='c'&gt; * Indexes Addresses.&lt;/span&gt;
&lt;span class='c'&gt; */&lt;/span&gt;
&lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='kd'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;AddressIndexer&lt;/span&gt; &lt;span class='kd'&gt;extends&lt;/span&gt; &lt;span class='n'&gt;AbstractIndexer&lt;/span&gt; &lt;span class='kd'&gt;implements&lt;/span&gt; &lt;span class='n'&gt;Indexer&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
    &lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='nf'&gt;AddressIndexer&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
        &lt;span class='c'&gt;// use /tmp/addresses as the index path&lt;/span&gt;
        &lt;span class='n'&gt;setIndexPath&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;/tmp/addresses&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;);&lt;/span&gt;
    &lt;span class='o'&gt;}&lt;/span&gt;

    &lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='kt'&gt;void&lt;/span&gt; &lt;span class='nf'&gt;add&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;Address&lt;/span&gt; &lt;span class='n'&gt;address&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='kd'&gt;throws&lt;/span&gt; &lt;span class='n'&gt;IndexingException&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
        &lt;span class='c'&gt;// create a document with &amp;quot;address&amp;quot; as the type&lt;/span&gt;
        &lt;span class='n'&gt;Document&lt;/span&gt; &lt;span class='n'&gt;doc&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;createDocument&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;address&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='n'&gt;address&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;getId&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;

        &lt;span class='c'&gt;// add fields for each property of Address&lt;/span&gt;
        &lt;span class='c'&gt;// implementation of Address is left to your imagination&lt;/span&gt;
        &lt;span class='n'&gt;doc&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;add&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;Field&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;UnStored&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;street&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='n'&gt;address&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;getStreet&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
        &lt;span class='n'&gt;doc&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;add&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;Field&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;UnStored&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;city&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='n'&gt;address&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;getCity&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
        &lt;span class='n'&gt;doc&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;add&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;Field&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;UnStored&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;state&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='n'&gt;address&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;getState&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
        &lt;span class='n'&gt;doc&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;add&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;Field&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;UnStored&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;zip&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='n'&gt;address&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;getZip&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;

        &lt;span class='c'&gt;// save the document to the index&lt;/span&gt;
        &lt;span class='n'&gt;save&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;doc&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
    &lt;span class='o'&gt;}&lt;/span&gt;

    &lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='kt'&gt;void&lt;/span&gt; &lt;span class='nf'&gt;delete&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;Address&lt;/span&gt; &lt;span class='n'&gt;address&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='kd'&gt;throws&lt;/span&gt; &lt;span class='n'&gt;IndexingException&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
        &lt;span class='n'&gt;delete&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;address&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='n'&gt;address&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;getId&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
    &lt;span class='o'&gt;}&lt;/span&gt;
&lt;span class='o'&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;AddressSearcher.java:&lt;/em&gt;&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c'&gt;/**&lt;/span&gt;
&lt;span class='c'&gt; * Searches Addresses.&lt;/span&gt;
&lt;span class='c'&gt; */&lt;/span&gt;
&lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='kd'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;AddressSearcher&lt;/span&gt; &lt;span class='kd'&gt;extends&lt;/span&gt; &lt;span class='n'&gt;AbstractSearcher&lt;/span&gt; &lt;span class='kd'&gt;implements&lt;/span&gt; &lt;span class='n'&gt;Searcher&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
    &lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='nf'&gt;AddressSearcher&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
        &lt;span class='c'&gt;// use /tmp/addresses as the index path&lt;/span&gt;
        &lt;span class='n'&gt;setIndexPath&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;/tmp/addresses&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;);&lt;/span&gt;
    &lt;span class='o'&gt;}&lt;/span&gt;

    &lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='n'&gt;ResultSet&lt;/span&gt; &lt;span class='nf'&gt;search&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;String&lt;/span&gt; &lt;span class='n'&gt;query&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='kd'&gt;throws&lt;/span&gt; &lt;span class='n'&gt;SearchException&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
        &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='nf'&gt;doSearch&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;query&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
    &lt;span class='o'&gt;}&lt;/span&gt;
&lt;span class='o'&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;AddressIndexTest.java:&lt;/em&gt;&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c'&gt;// ...&lt;/span&gt;
&lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='kt'&gt;void&lt;/span&gt; &lt;span class='nf'&gt;test&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='kd'&gt;throws&lt;/span&gt; &lt;span class='n'&gt;Exception&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
    &lt;span class='n'&gt;AddressIndexer&lt;/span&gt; &lt;span class='n'&gt;indexer&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='k'&gt;new&lt;/span&gt; &lt;span class='n'&gt;AddressIndexer&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt;
    &lt;span class='n'&gt;indexer&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;add&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;makeAddress&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
    &lt;span class='n'&gt;indexer&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;close&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt;

    &lt;span class='n'&gt;AddressSearcher&lt;/span&gt; &lt;span class='n'&gt;searcher&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='k'&gt;new&lt;/span&gt; &lt;span class='n'&gt;AddressSearcher&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt;
    &lt;span class='n'&gt;ResultSet&lt;/span&gt; &lt;span class='n'&gt;rs&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;searcher&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;search&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;city:Cambridge&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;);&lt;/span&gt;
    &lt;span class='n'&gt;Result&lt;/span&gt; &lt;span class='n'&gt;result&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;rs&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;iterator&lt;/span&gt;&lt;span class='o'&gt;().&lt;/span&gt;&lt;span class='na'&gt;next&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt;
    &lt;span class='c'&gt;// Address does not implement Result, nor was it indexed with its&lt;/span&gt;
    &lt;span class='c'&gt;// fully qualified class name, so the result is a GenericResult&lt;/span&gt;
    &lt;span class='n'&gt;assertTrue&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;result&lt;/span&gt; &lt;span class='k'&gt;instanceof&lt;/span&gt; &lt;span class='n'&gt;GenericResult&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
    &lt;span class='n'&gt;GenericResult&lt;/span&gt; &lt;span class='n'&gt;gr&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;GenericResult&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='n'&gt;result&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
    &lt;span class='n'&gt;assertEquals&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;address&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='n'&gt;gr&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;getType&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
&lt;span class='o'&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;h4 id='simple_annotationdriven_example'&gt;Simple Annotation-Driven Example&lt;/h4&gt;

&lt;p&gt;This example demonstrates the basics of annotation-driven indexing.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;SearchableBean.java:&lt;/em&gt;&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c'&gt;/**&lt;/span&gt;
&lt;span class='c'&gt; * Implementation of Searchable to be indexed.&lt;/span&gt;
&lt;span class='c'&gt; *&lt;/span&gt;
&lt;span class='c'&gt; * Extends AbstractResult to avoid needing to implement Result methods&lt;/span&gt;
&lt;span class='c'&gt; * (inherited from Searchable).&lt;/span&gt;
&lt;span class='c'&gt; */&lt;/span&gt;
&lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='kd'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;SearchableBean&lt;/span&gt; &lt;span class='kd'&gt;extends&lt;/span&gt; &lt;span class='n'&gt;AbstractResult&lt;/span&gt; &lt;span class='kd'&gt;implements&lt;/span&gt; &lt;span class='n'&gt;Searchable&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
    &lt;span class='kd'&gt;private&lt;/span&gt; &lt;span class='n'&gt;Integer&lt;/span&gt; &lt;span class='n'&gt;id&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
    &lt;span class='kd'&gt;private&lt;/span&gt; &lt;span class='n'&gt;String&lt;/span&gt; &lt;span class='n'&gt;name&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;

    &lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='nf'&gt;SearchableBean&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;Integer&lt;/span&gt; &lt;span class='n'&gt;id&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='n'&gt;String&lt;/span&gt; &lt;span class='n'&gt;name&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
        &lt;span class='k'&gt;this&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;id&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;id&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
        &lt;span class='k'&gt;this&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;name&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;name&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
    &lt;span class='o'&gt;}&lt;/span&gt;

    &lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='n'&gt;Integer&lt;/span&gt; &lt;span class='nf'&gt;getId&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
        &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='n'&gt;id&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
    &lt;span class='o'&gt;}&lt;/span&gt;

    &lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='kt'&gt;void&lt;/span&gt; &lt;span class='nf'&gt;setId&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;Integer&lt;/span&gt; &lt;span class='n'&gt;id&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
        &lt;span class='k'&gt;this&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;id&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;id&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
    &lt;span class='o'&gt;}&lt;/span&gt;

    &lt;span class='nd'&gt;@Indexed&lt;/span&gt;
    &lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='n'&gt;String&lt;/span&gt; &lt;span class='nf'&gt;getName&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
        &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='n'&gt;name&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
    &lt;span class='o'&gt;}&lt;/span&gt;

    &lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='kt'&gt;void&lt;/span&gt; &lt;span class='nf'&gt;setName&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;String&lt;/span&gt; &lt;span class='n'&gt;name&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
        &lt;span class='k'&gt;this&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;name&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;name&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
    &lt;span class='o'&gt;}&lt;/span&gt;
&lt;span class='o'&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;IndexManager.java:&lt;/em&gt;&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c'&gt;// ...&lt;/span&gt;
&lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='kt'&gt;void&lt;/span&gt; &lt;span class='nf'&gt;index&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;SearchableBean&lt;/span&gt; &lt;span class='n'&gt;bean&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='kd'&gt;throws&lt;/span&gt; &lt;span class='n'&gt;IndexingException&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
    &lt;span class='n'&gt;BeanIndexer&lt;/span&gt; &lt;span class='n'&gt;bi&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='k'&gt;new&lt;/span&gt; &lt;span class='n'&gt;BeanIndexer&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt;
    &lt;span class='n'&gt;bi&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;add&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;bean&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
    &lt;span class='n'&gt;bi&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;close&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt;
&lt;span class='o'&gt;}&lt;/span&gt;

&lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='n'&gt;ResultSet&lt;/span&gt; &lt;span class='nf'&gt;search&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;String&lt;/span&gt; &lt;span class='n'&gt;query&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='kd'&gt;throws&lt;/span&gt; &lt;span class='n'&gt;SearchException&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
    &lt;span class='n'&gt;BeanSearcher&lt;/span&gt; &lt;span class='n'&gt;bs&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='k'&gt;new&lt;/span&gt; &lt;span class='n'&gt;BeanSearcher&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt;
    &lt;span class='c'&gt;// searching will attempt to reconstitute objects and set their ids&lt;/span&gt;
    &lt;span class='c'&gt;// this allows the calling layer to know a) the type and b) the id&lt;/span&gt;
    &lt;span class='c'&gt;// in order to refresh it properly&lt;/span&gt;
    &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='n'&gt;bs&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;search&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;query&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
&lt;span class='o'&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;IndexManagerTest.java:&lt;/em&gt;&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c'&gt;// ...&lt;/span&gt;
&lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='kt'&gt;void&lt;/span&gt; &lt;span class='nf'&gt;testSearch&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='kd'&gt;throws&lt;/span&gt; &lt;span class='n'&gt;Exception&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
    &lt;span class='n'&gt;IndexManager&lt;/span&gt; &lt;span class='n'&gt;im&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='k'&gt;new&lt;/span&gt; &lt;span class='n'&gt;IndexManager&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt;
    &lt;span class='n'&gt;im&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;index&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='k'&gt;new&lt;/span&gt; &lt;span class='n'&gt;SearchableBean&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
    &lt;span class='n'&gt;ResultSet&lt;/span&gt; &lt;span class='n'&gt;rs&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;im&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;search&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;name:seth&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;);&lt;/span&gt;
    &lt;span class='n'&gt;assertEquals&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='n'&gt;rs&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;size&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
    &lt;span class='n'&gt;Result&lt;/span&gt; &lt;span class='n'&gt;result&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;rs&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;iterator&lt;/span&gt;&lt;span class='o'&gt;().&lt;/span&gt;&lt;span class='na'&gt;next&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt;
    &lt;span class='n'&gt;assertTrue&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;result&lt;/span&gt; &lt;span class='k'&gt;instanceof&lt;/span&gt; &lt;span class='n'&gt;SearchableBean&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
    &lt;span class='n'&gt;assertEquals&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='n'&gt;result&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;getId&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
    &lt;span class='c'&gt;// name has not been set&lt;/span&gt;
    &lt;span class='n'&gt;assertNull&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;result&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;getName&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
&lt;span class='o'&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;h4 id='hybrid_example'&gt;Hybrid Example&lt;/h4&gt;

&lt;p&gt;This example demonstrates how to use Searchable in a hybrid mode. &lt;em&gt;TeapotIndexer&lt;/em&gt; can operate in batch mode.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Teapot.java:&lt;/em&gt;&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c'&gt;/**&lt;/span&gt;
&lt;span class='c'&gt; * A representation of a teapot.  When searching the Teapot index, only the&lt;/span&gt;
&lt;span class='c'&gt; * &amp;quot;name&amp;quot; and &amp;quot;description&amp;quot; fields will be searched by default (if&lt;/span&gt;
&lt;span class='c'&gt; * @DefaultFields were not specified, all indexed fields would be searched).&lt;/span&gt;
&lt;span class='c'&gt; */&lt;/span&gt;
&lt;span class='nd'&gt;@DefaultFields&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;description&amp;quot;&lt;/span&gt; &lt;span class='o'&gt;}&lt;/span&gt; &lt;span class='o'&gt;)&lt;/span&gt;
&lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='kd'&gt;interface&lt;/span&gt; &lt;span class='nc'&gt;Teapot&lt;/span&gt; &lt;span class='kd'&gt;extends&lt;/span&gt; &lt;span class='n'&gt;Searchable&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
    &lt;span class='c'&gt;/**&lt;/span&gt;
&lt;span class='c'&gt;     * Name of the Teapot.  This serves as the id, is indexed, and can be&lt;/span&gt;
&lt;span class='c'&gt;     * sorted by.  This is the only field that is indexed when a Teapot is&lt;/span&gt;
&lt;span class='c'&gt;     * nested in another object (a TeaSet, for example).&lt;/span&gt;
&lt;span class='c'&gt;     */&lt;/span&gt;
    &lt;span class='nd'&gt;@ID&lt;/span&gt;
    &lt;span class='nd'&gt;@Indexed&lt;/span&gt;
    &lt;span class='nd'&gt;@Sortable&lt;/span&gt;
    &lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='n'&gt;String&lt;/span&gt; &lt;span class='nf'&gt;getName&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt;

    &lt;span class='c'&gt;/**&lt;/span&gt;
&lt;span class='c'&gt;     * Color of the Teapot.  Aliased to &amp;quot;colour&amp;quot; for Brits.&lt;/span&gt;
&lt;span class='c'&gt;     */&lt;/span&gt;
    &lt;span class='nd'&gt;@Indexed&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;aliases&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;colour&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='n'&gt;nested&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='kc'&gt;false&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt;
    &lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='n'&gt;String&lt;/span&gt; &lt;span class='nf'&gt;getColor&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt;

    &lt;span class='c'&gt;/**&lt;/span&gt;
&lt;span class='c'&gt;     * Material the Teapot is made of.  Indexed and stored.&lt;/span&gt;
&lt;span class='c'&gt;    @Index(stored=true, nested=false)&lt;/span&gt;
&lt;span class='c'&gt;    public String getMaterial();&lt;/span&gt;

&lt;span class='c'&gt;    /**&lt;/span&gt;
&lt;span class='c'&gt;     * The type of tea this Teapot contains.  Not searchable, but stored.&lt;/span&gt;
&lt;span class='c'&gt;     */&lt;/span&gt;
    &lt;span class='nd'&gt;@Stored&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;nested&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='kc'&gt;false&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt;
    &lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='n'&gt;String&lt;/span&gt; &lt;span class='nf'&gt;getTeaType&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt;

    &lt;span class='c'&gt;/**&lt;/span&gt;
&lt;span class='c'&gt;     * Description.  Indexed and used when creating an excerpt.&lt;/span&gt;
&lt;span class='c'&gt;     */&lt;/span&gt;
    &lt;span class='nd'&gt;@Indexed&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;nested&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='kc'&gt;false&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt;
    &lt;span class='nd'&gt;@Excerptable&lt;/span&gt;
    &lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='n'&gt;String&lt;/span&gt; &lt;span class='nf'&gt;getDescription&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt;
&lt;span class='o'&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;TeapotIndexer.java:&lt;/em&gt;&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c'&gt;/**&lt;/span&gt;
&lt;span class='c'&gt; * Indexes teapots.&lt;/span&gt;
&lt;span class='c'&gt; */&lt;/span&gt;
&lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='kd'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;TeapotIndexer&lt;/span&gt; &lt;span class='kd'&gt;extends&lt;/span&gt; &lt;span class='n'&gt;AbstractBeanIndexer&lt;/span&gt; &lt;span class='kd'&gt;implements&lt;/span&gt; &lt;span class='n'&gt;BatchIndexer&lt;/span&gt;&lt;span class='o'&gt;&amp;lt;&lt;/span&gt;&lt;span class='n'&gt;Teapot&lt;/span&gt;&lt;span class='o'&gt;&amp;gt;&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
    &lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='nf'&gt;TeapotIndexer&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
        &lt;span class='c'&gt;// use /tmp/teapots as the index path&lt;/span&gt;
        &lt;span class='n'&gt;setIndexPath&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;/tmp/teapots&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;);&lt;/span&gt;
    &lt;span class='o'&gt;}&lt;/span&gt;

    &lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='kt'&gt;void&lt;/span&gt; &lt;span class='nf'&gt;add&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;Teapot&lt;/span&gt; &lt;span class='n'&gt;tp&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='kd'&gt;throws&lt;/span&gt; &lt;span class='n'&gt;IndexingException&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
        &lt;span class='n'&gt;Document&lt;/span&gt; &lt;span class='n'&gt;doc&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;doCreate&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;tp&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;

        &lt;span class='n'&gt;processBean&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;tp&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
        &lt;span class='n'&gt;postProcessTeapot&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;tp&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;

        &lt;span class='c'&gt;// save the document to the index&lt;/span&gt;
        &lt;span class='n'&gt;save&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;doc&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
    &lt;span class='o'&gt;}&lt;/span&gt;

    &lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='kt'&gt;void&lt;/span&gt; &lt;span class='nf'&gt;delete&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;Teapot&lt;/span&gt; &lt;span class='n'&gt;tp&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='kd'&gt;throws&lt;/span&gt; &lt;span class='n'&gt;IndexingException&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
        &lt;span class='n'&gt;doDelete&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;tp&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
    &lt;span class='o'&gt;}&lt;/span&gt;

    &lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='kt'&gt;void&lt;/span&gt; &lt;span class='nf'&gt;flush&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='kd'&gt;throws&lt;/span&gt; &lt;span class='n'&gt;IndexException&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
        &lt;span class='c'&gt;// flush any pending deletes&lt;/span&gt;
        &lt;span class='n'&gt;flushDeletes&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt;
    &lt;span class='o'&gt;}&lt;/span&gt;

    &lt;span class='kd'&gt;protected&lt;/span&gt; &lt;span class='n'&gt;Document&lt;/span&gt; &lt;span class='nf'&gt;postProcessTeapot&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;Teapot&lt;/span&gt; &lt;span class='n'&gt;tp&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
        &lt;span class='c'&gt;// add an &amp;quot;owner&amp;quot; field not specified by the teapot&lt;/span&gt;
        &lt;span class='n'&gt;doc&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;add&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;Field&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;UnStored&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;owner&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;Nathan&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
    &lt;span class='o'&gt;}&lt;/span&gt;
&lt;span class='o'&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;TeapotSearcher.java:&lt;/em&gt;&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c'&gt;/**&lt;/span&gt;
&lt;span class='c'&gt; * Searches teapots.&lt;/span&gt;
&lt;span class='c'&gt; */&lt;/span&gt;
&lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='kd'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;TeapotSearcher&lt;/span&gt; &lt;span class='kd'&gt;extends&lt;/span&gt; &lt;span class='n'&gt;AbstractSearcher&lt;/span&gt; &lt;span class='kd'&gt;implements&lt;/span&gt; &lt;span class='n'&gt;Searcher&lt;/span&gt;&lt;span class='o'&gt;&amp;lt;&lt;/span&gt;&lt;span class='n'&gt;Teapot&lt;/span&gt;&lt;span class='o'&gt;&amp;gt;&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
    &lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='nf'&gt;TeapotSearcher&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
        &lt;span class='c'&gt;// use /tmp/teapots as the index path&lt;/span&gt;
        &lt;span class='n'&gt;setIndexPath&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;/tmp/teapots&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;);&lt;/span&gt;
    &lt;span class='o'&gt;}&lt;/span&gt;

    &lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='n'&gt;ResultSet&lt;/span&gt;&lt;span class='o'&gt;&amp;lt;&lt;/span&gt;&lt;span class='n'&gt;Teapot&lt;/span&gt;&lt;span class='o'&gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;search&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;String&lt;/span&gt; &lt;span class='n'&gt;query&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='kd'&gt;throws&lt;/span&gt; &lt;span class='n'&gt;SearchException&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
        &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='nf'&gt;excerpt&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;refresh&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;doSearch&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;query&lt;/span&gt; &lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
    &lt;span class='o'&gt;}&lt;/span&gt;

    &lt;span class='kd'&gt;protected&lt;/span&gt; &lt;span class='n'&gt;ResultSet&lt;/span&gt;&lt;span class='o'&gt;&amp;lt;&lt;/span&gt;&lt;span class='n'&gt;Teapot&lt;/span&gt;&lt;span class='o'&gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;load&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;ResultSet&lt;/span&gt;&lt;span class='o'&gt;&amp;lt;&lt;/span&gt;&lt;span class='n'&gt;Teapot&lt;/span&gt;&lt;span class='o'&gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;results&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
        &lt;span class='n'&gt;ResultSetImpl&lt;/span&gt; &lt;span class='n'&gt;rs&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;ResultSetImpl&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='n'&gt;results&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
        &lt;span class='k'&gt;for&lt;/span&gt; &lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;Teapot&lt;/span&gt; &lt;span class='n'&gt;tp&lt;/span&gt; &lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='n'&gt;results&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
            &lt;span class='c'&gt;// session is a HibernateSession&lt;/span&gt;
            &lt;span class='c'&gt;// your actual implementation may involve a DAO&lt;/span&gt;
            &lt;span class='c'&gt;// replaces stub Teapot with a fully loaded one&lt;/span&gt;
            &lt;span class='c'&gt;// ResultSetImpl handles transferring of Result properties&lt;/span&gt;
            &lt;span class='n'&gt;rs&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;replace&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;tp&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;Teapot&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='n'&gt;session&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;load&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;tp&lt;/span&gt; &lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
        &lt;span class='o'&gt;}&lt;/span&gt;
        &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='n'&gt;results&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
    &lt;span class='o'&gt;}&lt;/span&gt;
&lt;span class='o'&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;TeapotTest.java:&lt;/em&gt;&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c'&gt;// ...&lt;/span&gt;
&lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='kt'&gt;void&lt;/span&gt; &lt;span class='nf'&gt;test&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='kd'&gt;throws&lt;/span&gt; &lt;span class='n'&gt;Exception&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
    &lt;span class='n'&gt;BatchIndexer&lt;/span&gt;&lt;span class='o'&gt;&amp;lt;&lt;/span&gt;&lt;span class='n'&gt;Teapot&lt;/span&gt;&lt;span class='o'&gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;indexer&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='k'&gt;new&lt;/span&gt; &lt;span class='n'&gt;TeapotIndexer&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt;
    &lt;span class='c'&gt;// run TeapotIndexer in batch mode&lt;/span&gt;
    &lt;span class='n'&gt;indexer&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;setBatchMode&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='kc'&gt;true&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
    &lt;span class='n'&gt;indexer&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;add&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;makeTeapot&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
    &lt;span class='n'&gt;indexer&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;close&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt;

    &lt;span class='n'&gt;Searcher&lt;/span&gt;&lt;span class='o'&gt;&amp;lt;&lt;/span&gt;&lt;span class='n'&gt;Teapot&lt;/span&gt;&lt;span class='o'&gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;searcher&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='k'&gt;new&lt;/span&gt; &lt;span class='n'&gt;TeapotSearcher&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt;
    &lt;span class='n'&gt;ResultSet&lt;/span&gt;&lt;span class='o'&gt;&amp;lt;&lt;/span&gt;&lt;span class='n'&gt;Teapot&lt;/span&gt;&lt;span class='o'&gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;rs&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;searcher&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;search&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;material:china OR wireframe&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;);&lt;/span&gt;
    &lt;span class='n'&gt;Iterator&lt;/span&gt;&lt;span class='o'&gt;&amp;lt;&lt;/span&gt;&lt;span class='n'&gt;Teapot&lt;/span&gt;&lt;span class='o'&gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;i&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;rs&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;iterator&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt;
    &lt;span class='n'&gt;Teapot&lt;/span&gt; &lt;span class='n'&gt;firstResult&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;i&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;next&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt;
    &lt;span class='n'&gt;Teapot&lt;/span&gt; &lt;span class='n'&gt;secondResult&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;i&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;next&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt;
    &lt;span class='n'&gt;assertEquals&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;china&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='n'&gt;firstResult&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;getMaterial&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
    &lt;span class='n'&gt;assertTrue&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;secondResult&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;getDescription&lt;/span&gt;&lt;span class='o'&gt;().&lt;/span&gt;&lt;span class='na'&gt;contains&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;wireframe&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
    &lt;span class='n'&gt;assertTrue&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;secondResult&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;getSearchExtract&lt;/span&gt;&lt;span class='o'&gt;().&lt;/span&gt;&lt;span class='na'&gt;contains&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;wireframe&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
&lt;span class='o'&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;h4 id='searching_multiple_indexes_example'&gt;Searching Multiple Indexes Example&lt;/h4&gt;

&lt;p&gt;This example demonstrates how to use Searchable to search multiple indexes for different types of objects. This assumes a 1 class/index breakdown, but that&amp;#8217;s not strictly necessary.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AddressAndTeapotSearcher.java:&lt;/em&gt;&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c'&gt;/**&lt;/span&gt;
&lt;span class='c'&gt; * Searches Addresses and Teapots.&lt;/span&gt;
&lt;span class='c'&gt; */&lt;/span&gt;
&lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='kd'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;AddressAndTeapotSearcher&lt;/span&gt; &lt;span class='kd'&gt;extends&lt;/span&gt; &lt;span class='n'&gt;AbstractMultiSearcher&lt;/span&gt; &lt;span class='kd'&gt;implements&lt;/span&gt; &lt;span class='n'&gt;Searcher&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
    &lt;span class='c'&gt;/**&lt;/span&gt;
&lt;span class='c'&gt;     * Constructs this as a MultiSearcher for Addresses and Teapots.  The order and length&lt;/span&gt;
&lt;span class='c'&gt;     * of both arrays must be equivalent.&lt;/span&gt;
&lt;span class='c'&gt;     */&lt;/span&gt;
    &lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='nf'&gt;AddressAndTeapotSearcher&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
        &lt;span class='kd'&gt;super&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='k'&gt;new&lt;/span&gt; &lt;span class='n'&gt;String&lt;/span&gt;&lt;span class='o'&gt;[]&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;/tmp/addresses&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;/tmp/teapots&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;},&lt;/span&gt;
               &lt;span class='k'&gt;new&lt;/span&gt; &lt;span class='n'&gt;Class&lt;/span&gt;&lt;span class='o'&gt;[]&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt; &lt;span class='n'&gt;Address&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;class&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='n'&gt;Teapot&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;class&lt;/span&gt; &lt;span class='o'&gt;}&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
    &lt;span class='o'&gt;}&lt;/span&gt;

    &lt;span class='c'&gt;/**&lt;/span&gt;
&lt;span class='c'&gt;     * Searches the teapot index using default fields determined by @DefaultFields&lt;/span&gt;
&lt;span class='c'&gt;     * annotation on the Teapot.  Also searches the address index using all available&lt;/span&gt;
&lt;span class='c'&gt;     * fields as defaults.  If Address implemented Searchable and contained a&lt;/span&gt;
&lt;span class='c'&gt;     * @DefaultField annotation, those fields would be used instead.&lt;/span&gt;
&lt;span class='c'&gt;     */&lt;/span&gt;
    &lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='n'&gt;ResultSet&lt;/span&gt; &lt;span class='nf'&gt;search&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;String&lt;/span&gt; &lt;span class='n'&gt;query&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='kd'&gt;throws&lt;/span&gt; &lt;span class='n'&gt;SearchException&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
        &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='nf'&gt;doSearch&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;query&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
    &lt;span class='o'&gt;}&lt;/span&gt;
&lt;span class='o'&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/mojodnadotnet/~4/-aZTNDIlyIQ" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://mojodna.net/2006/10/02/searchable-annotation-driven-indexing-and-searching-with-lucene.html</feedburner:origLink></entry>
 
 <entry>
   <title>Sprout: Annotation-Powered Simplicity for Struts</title>
   <link href="http://feedproxy.google.com/~r/mojodnadotnet/~3/mHmFU-MqNtA/sprout-annotation-powered-simplicity-for-struts.html" />
   <updated>2005-11-30T00:00:00-08:00</updated>
   <id>http://mojodna.net/2005/11/30/sprout-annotation-powered-simplicity-for-struts</id>
   <content type="html">&lt;h2 id='sprout_annotationpowered_simplicity_for_struts'&gt;Sprout: Annotation-Powered Simplicity for Struts&lt;/h2&gt;

&lt;h3 id='overview'&gt;Overview&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Sprout aims to significantly simplify development with Struts by reducing the amount of configuration required through the use of annotations and sensible defaults.&lt;/em&gt;&lt;/p&gt;

&lt;h3 id='basics'&gt;Basics&lt;/h3&gt;

&lt;p&gt;Sprout requires JDK 1.5. Without that, you&amp;#8217;re out of luck.&lt;/p&gt;

&lt;p&gt;Sprout is an extension of a Struts &lt;code&gt;MappingDispatchAction&lt;/code&gt;, which allows for multiple actions to be defined within the same &lt;em&gt;Action&lt;/em&gt; class. In this case, the name of the method is mapped to the URL (after converting method names; &lt;em&gt;methodName&lt;/em&gt; is exposed as &lt;em&gt;/method_name.do&lt;/em&gt;). Paths are determined based on the package name of the action. For example, &lt;em&gt;net.mojodna.sprout.action.HomeAction&lt;/em&gt; corresponds to &lt;em&gt;/&lt;/em&gt;, while &lt;em&gt;net.mojodna.sprout.action.example.ExampleAction&lt;/em&gt; corresponds to &lt;em&gt;/example/&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Sprout also uses a custom &lt;em&gt;RequestProcessor&lt;/em&gt;&amp;#8211;&lt;code&gt;SproutRequestProcessor&lt;/code&gt;, which extends Spring&amp;#8217;s &lt;em&gt;DelegatingRequestProcessor&lt;/em&gt;. This means that you can specify dependencies within your actions using setter-injection.&lt;/p&gt;

&lt;p&gt;Sprout is also &lt;em&gt;completely&lt;/em&gt; backward-compatible with legacy Struts applications.. It was built for use in a legacy Struts application; many of the older Actions are untouched&amp;#8211;new development is done using Sprout (I often find myself &lt;em&gt;removing&lt;/em&gt; action-mappings while adding new functionality).&lt;/p&gt;

&lt;p&gt;Sprout is available on GitHub here: &lt;a href='http://github.com/mojodna/sprout'&gt;http://github.com/mojodna/sprout&lt;/a&gt; Do with it what you will.&lt;/p&gt;

&lt;h3 id='annotations'&gt;Annotations&lt;/h3&gt;

&lt;p&gt;All annotations are optional.&lt;/p&gt;

&lt;h4 id='formname'&gt;@FormName&lt;/h4&gt;

&lt;p&gt;Allows the developer to override the name of the form-bean (defined in &lt;em&gt;struts-config.xml&lt;/em&gt;) used for this method. This is equivalent to setting the &lt;em&gt;name&lt;/em&gt; attribute within an &lt;em&gt;action&lt;/em&gt; mapping.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Defaults to ${action-name}Form; e.g. for &lt;strong&gt;AdminAction&lt;/strong&gt; the default ActionForm name would be &lt;strong&gt;AdminActionForm&lt;/strong&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h4 id='forward'&gt;@Forward&lt;/h4&gt;

&lt;p&gt;Specifies additional forwards. Multiple forwards may be specified by providing arrays as arguments to &lt;em&gt;name&lt;/em&gt;, &lt;em&gt;path&lt;/em&gt;, and &lt;em&gt;redirect&lt;/em&gt;. &lt;em&gt;redirect&lt;/em&gt; defaults to &lt;em&gt;false&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A default redirect is provided; the key is &lt;code&gt;Sprout.FWD_SUCCESS&lt;/code&gt; and the path is the converted path + .jsp. E.g., &lt;em&gt;AdminAction.methodName()&lt;/em&gt; corresponds to &lt;em&gt;method_name.jsp&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;e.g. @Forward(name=&amp;#8221;failure&amp;#8221;, path=&amp;#8221;/failure.jsp&amp;#8221; redirect=&amp;#8221;true&amp;#8221;)&lt;/em&gt;&lt;/p&gt;

&lt;h4 id='input'&gt;@Input&lt;/h4&gt;

&lt;p&gt;This annotation is required if this action is validating the output from a different action. In that case, the argument to &lt;em&gt;@Input&lt;/em&gt; should be the path to the JSP containing the form whose input is being validated.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;e.g @Input(&amp;#8220;login.jsp&amp;#8221;) if this is &lt;strong&gt;not&lt;/strong&gt; login() and the action that initiated this request &lt;strong&gt;is&lt;/strong&gt; login().&lt;/em&gt;&lt;/p&gt;

&lt;h4 id='scope'&gt;@Scope&lt;/h4&gt;

&lt;p&gt;Specifies the &lt;em&gt;scope&lt;/em&gt; attribute for the generated action mapping. This exists primarily for completeness; it is likely that you may never use this annotation.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;As with &lt;strong&gt;struts-config.xml&lt;/strong&gt;, the default is &lt;strong&gt;request&lt;/strong&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h4 id='validate'&gt;@Validate&lt;/h4&gt;

&lt;p&gt;Specifies the &lt;em&gt;validate&lt;/em&gt; attribute for the generated action mapping. Set this to &lt;em&gt;true&lt;/em&gt; if you desire the output of this action to be validated. For this to have any effect, you must have specified rules in &lt;em&gt;validator-rules.xml&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sprout does not contain anything to ease the actual validation process at this time.&lt;/em&gt;&lt;/p&gt;

&lt;h3 id='example'&gt;Example&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;src/java/net/mojodna/sprout/action/example/ExampleAction.java&lt;/em&gt;:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='c'&gt;// URL should be /example/*&lt;/span&gt;
&lt;span class='kn'&gt;package&lt;/span&gt; &lt;span class='n'&gt;net&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;mojodna&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;sprout&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;action&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;example&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;

&lt;span class='c'&gt;// ...&lt;/span&gt;

&lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='kd'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;ExampleAction&lt;/span&gt; &lt;span class='kd'&gt;extends&lt;/span&gt; &lt;span class='n'&gt;Sprout&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
  &lt;span class='c'&gt;// overrides Sprout.index()&lt;/span&gt;
  &lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='n'&gt;ActionForward&lt;/span&gt; &lt;span class='nf'&gt;index&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='o'&gt;...&lt;/span&gt; &lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
    &lt;span class='c'&gt;// do something&lt;/span&gt;

    &lt;span class='c'&gt;// redirect to index.jsp&lt;/span&gt;
    &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='n'&gt;mapping&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;findForward&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;FWD_SUCCESS&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
  &lt;span class='o'&gt;}&lt;/span&gt;
&lt;span class='o'&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;src/java/applicationContext.xml&lt;/em&gt;:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;...
&lt;span class='nt'&gt;&amp;lt;bean&lt;/span&gt; &lt;span class='na'&gt;name=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;ExampleAction&amp;quot;&lt;/span&gt; &lt;span class='na'&gt;class=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;net.mojodna.sprout.action.example.ExampleAction&amp;quot;&lt;/span&gt; &lt;span class='na'&gt;singleton=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;true&amp;quot;&lt;/span&gt; &lt;span class='nt'&gt;/&amp;gt;&lt;/span&gt;
...
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;src/web/WEB-INF/struts-config.xml&lt;/em&gt;:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;...
&lt;span class='nt'&gt;&amp;lt;form-bean&lt;/span&gt; &lt;span class='na'&gt;name=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;ExampleActionForm&amp;quot;&lt;/span&gt; &lt;span class='na'&gt;type=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;org.apache.struts.validator.DynaValidatorForm&amp;quot;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&lt;/span&gt;
  &lt;span class='nt'&gt;&amp;lt;form-property&lt;/span&gt; &lt;span class='na'&gt;name=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;...&amp;quot;&lt;/span&gt; &lt;span class='na'&gt;type=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;...&amp;quot;&lt;/span&gt; &lt;span class='nt'&gt;/&amp;gt;&lt;/span&gt;
  ...
&lt;span class='nt'&gt;&amp;lt;/form-bean&amp;gt;&lt;/span&gt;

&lt;span class='c'&gt;&amp;lt;!-- No action-mappings!!! --&amp;gt;&lt;/span&gt;
&lt;span class='nt'&gt;&amp;lt;action-mappings&lt;/span&gt; &lt;span class='nt'&gt;/&amp;gt;&lt;/span&gt;

&lt;span class='c'&gt;&amp;lt;!-- Define an alternate RequestProcessor --&amp;gt;&lt;/span&gt;
&lt;span class='nt'&gt;&amp;lt;controller&lt;/span&gt; &lt;span class='na'&gt;processorClass=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;net.mojodna.sprout.SproutRequestProcessor&amp;quot;&lt;/span&gt; &lt;span class='nt'&gt;/&amp;gt;&lt;/span&gt;

&lt;span class='c'&gt;&amp;lt;!-- Sprout plug-in --&amp;gt;&lt;/span&gt;
&lt;span class='nt'&gt;&amp;lt;plug-in&lt;/span&gt; &lt;span class='na'&gt;className=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;net.mojodna.sprout.SproutAutoLoaderPlugIn&amp;quot;&lt;/span&gt; &lt;span class='nt'&gt;/&amp;gt;&lt;/span&gt;
...
&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id='shorthand'&gt;Shorthand&lt;/h3&gt;

&lt;h4 id='index_actions'&gt;Index Actions&lt;/h4&gt;

&lt;p&gt;Sprout contains an &lt;em&gt;index()&lt;/em&gt; method to speed up the process of getting something working. To use this, subclass Sprout (no methods necessary), register the action-bean in &lt;em&gt;applicationContext.xml&lt;/em&gt; and create a corresponding &lt;em&gt;index.jsp&lt;/em&gt;. When you need to add logic to the action, override &lt;em&gt;index()&lt;/em&gt; in your Sprout sub-class and add it there.&lt;/p&gt;

&lt;h4 id='dynaactionforms'&gt;DynaActionForms&lt;/h4&gt;

&lt;p&gt;Helper methods have been added to ease development using &lt;em&gt;DynaActionForms&lt;/em&gt;.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='n'&gt;String&lt;/span&gt; &lt;span class='n'&gt;key&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;foo&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
&lt;span class='n'&gt;String&lt;/span&gt; &lt;span class='n'&gt;value&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;bar&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
&lt;span class='c'&gt;// returns a String&lt;/span&gt;
&lt;span class='n'&gt;f&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;key&lt;/span&gt; &lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='o'&gt;==&lt;/span&gt; &lt;span class='o'&gt;((&lt;/span&gt;&lt;span class='n'&gt;DynaActionForm&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='n'&gt;form&lt;/span&gt;&lt;span class='o'&gt;).&lt;/span&gt;&lt;span class='na'&gt;getString&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;key&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;

&lt;span class='c'&gt;// returns an Object&lt;/span&gt;
&lt;span class='n'&gt;F&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;key&lt;/span&gt; &lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='o'&gt;==&lt;/span&gt; &lt;span class='o'&gt;((&lt;/span&gt;&lt;span class='n'&gt;DynaActionForm&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='n'&gt;form&lt;/span&gt;&lt;span class='o'&gt;).&lt;/span&gt;&lt;span class='na'&gt;get&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;key&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;

&lt;span class='c'&gt;// sets a value&lt;/span&gt;
&lt;span class='n'&gt;s&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;key&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='n'&gt;value&lt;/span&gt; &lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='o'&gt;==&lt;/span&gt; &lt;span class='o'&gt;((&lt;/span&gt;&lt;span class='n'&gt;DynaActionForm&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='n'&gt;form&lt;/span&gt;&lt;span class='o'&gt;).&lt;/span&gt;&lt;span class='na'&gt;set&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;key&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='n'&gt;value&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id='actionmessage_handling'&gt;ActionMessage handling&lt;/h3&gt;

&lt;p&gt;Sprout contains adaptations to the traditional way Struts handles &lt;em&gt;ActionMessages&lt;/em&gt;. &lt;code&gt;getMessages()&lt;/code&gt; and &lt;code&gt;getErrors()&lt;/code&gt; have been modified to store and retrieve messages from the session rather than the request. This means that messages and errors will be displayed (and subsequently cleared) on the next invocation of &lt;code&gt;&amp;lt;html:messages /&amp;gt;&lt;/code&gt; or a variant (such as &lt;code&gt;&amp;lt;ui:notifications /&amp;gt;&lt;/code&gt;), regardless of whether either of the &lt;em&gt;get&lt;/em&gt; methods have been called or if the invocation occurs during a separate request.&lt;/p&gt;

&lt;p&gt;Sample message / error handling code (within an Action):&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='n'&gt;ActionMessages&lt;/span&gt; &lt;span class='n'&gt;msgs&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;getMessages&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;request&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
&lt;span class='n'&gt;ActionMessages&lt;/span&gt; &lt;span class='n'&gt;errors&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;getErrors&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;request&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;

&lt;span class='c'&gt;// Add a message&lt;/span&gt;
&lt;span class='n'&gt;msgs&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;add&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='o'&gt;...&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;

&lt;span class='c'&gt;// Add an error&lt;/span&gt;
&lt;span class='n'&gt;errors&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;add&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='o'&gt;...&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;

&lt;span class='c'&gt;// Save messages and errors&lt;/span&gt;
&lt;span class='n'&gt;saveMessages&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;request&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='n'&gt;msgs&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
&lt;span class='n'&gt;saveErrors&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt; &lt;span class='n'&gt;request&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='n'&gt;errors&lt;/span&gt; &lt;span class='o'&gt;);&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;ui:notifications /&amp;gt;&lt;/code&gt; (&lt;em&gt;src/web/WEB-INF/tags/ui/notifications.tag&lt;/em&gt;) is an alternative tag file that can be modified for your use. The primary difference between &lt;code&gt;&amp;lt;html:messages /&amp;gt;&lt;/code&gt; is that it will display both messages and errors (and will discriminate between them, allowing you to style them differently depending on your application&amp;#8217;s needs).&lt;/p&gt;

&lt;h3 id='servlets_and_taglibs'&gt;Servlets and Taglibs&lt;/h3&gt;

&lt;p&gt;Spring&amp;#8217;s Struts integration lacks the ability to autowire servlets and taglibs. Sprout contains classes (&lt;code&gt;Sproutlet&lt;/code&gt; and &lt;code&gt;SproutTag&lt;/code&gt;) that can be subclassed to provide auto-wiring capability. They will be wired during their initialization process.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;These support classes only support the &lt;strong&gt;byName&lt;/strong&gt; auto-wiring mechanism.&lt;/em&gt;&lt;/p&gt;

&lt;h3 id='q__a'&gt;Q + A&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Q: Why the Spring dependencies?&lt;/li&gt;

&lt;li&gt;A: I&amp;#8217;m already using Spring. There&amp;#8217;s a good chance that you are as well. Removing the dependency makes reflection upon the registered actions (or finding them in the first place) significantly more difficult.&lt;/li&gt;
&lt;/ul&gt;&lt;img src="http://feeds.feedburner.com/~r/mojodnadotnet/~4/mHmFU-MqNtA" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://mojodna.net/2005/11/30/sprout-annotation-powered-simplicity-for-struts.html</feedburner:origLink></entry>
 
 <entry>
   <title>Introducing Sprout - Pure annotation goodness</title>
   <link href="http://feedproxy.google.com/~r/mojodnadotnet/~3/LnMkSFH-2mU/struts-spring-java-5-sprout.html" />
   <updated>2005-10-31T00:00:00-08:00</updated>
   <id>http://mojodna.net/2005/10/31/struts-spring-java-5-sprout</id>
   <content type="html">&lt;h2 id='introducing_sprout__pure_annotation_goodness'&gt;Introducing Sprout - Pure annotation goodness&lt;/h2&gt;

&lt;p&gt;Summary: &lt;a href='http://github.com/mojodna/sprout'&gt;Sprout&lt;/a&gt;. Check it out.&lt;/p&gt;

&lt;p&gt;I have a &lt;a href='http://struts.apache.org/'&gt;Struts&lt;/a&gt; application that I work on every day. It has been a Struts application for about 3 years. It will likely continue to be a Struts application for the foreseeable future (it&amp;#8217;s now 2009 and it&amp;#8217;s a Rails app). I imagine some of you may be in the same position.&lt;/p&gt;

&lt;p&gt;Ever since I started working on web applications in Java, I&amp;#8217;ve been poking around the other web frameworks that are out there. &lt;a href='http://www.opensymphony.com/webwork/'&gt;WebWork&lt;/a&gt; is interesting (especially now that it&amp;#8217;s to merge with Struts). &lt;a href='http://springframework.org/'&gt;Spring MVC&lt;/a&gt; is promising, if only because I like having my services wired together and to my actions. &lt;a href='http://jakarta.apache.org/tapestry/'&gt;Tapestry&lt;/a&gt; seemed too complex, and &lt;a href='http://www.opensymphony.com/sitemesh/'&gt;SiteMesh&lt;/a&gt;, while not a framework, gave me everything that Tapestry was promising (granted, I haven&amp;#8217;t looked at Tapestry a whole lot and should probably have another look). &lt;a href='http://wicket.sf.net/'&gt;Wicket&lt;/a&gt; and especially &lt;a href='http://java.sun.com/j2ee/javaserverfaces/'&gt;JSF&lt;/a&gt;, ick. The web is not a client application and does not lend itself well to the Swing approach to components and widgets (yet). JSF, in particular, strikes me as great for both sides of the Toolbuilder / Application developer divide, but the overhead involved in creating custom components (a requirement for me, as I still like reinventing my HTML constructs) is simply too great. JSF in its optimal form also takes an event-based view of the web, which just doesn&amp;#8217;t work so well yet (POSTs everywhere!). This isn&amp;#8217;t about Java frameworks; this is about Struts.&lt;/p&gt;

&lt;p&gt;Enter &lt;a href='http://rubyonrails.org/'&gt;Ruby on Rails&lt;/a&gt;. Nifty. It fits the small development team model much better (I am a development team of 1; Struts and its ilk assume and thrive in an environment where tasks can and are divided up, such as the one my app was originally written in). It was not created as a framework, but more extracted from an application because it worked and made its creator&amp;#8217;s life simpler. Convention over configuration (rather than conventions AND configuration). Embedded opinions that just happen to jive with my own view of the world. Looking at Rails and gaining an understanding of how it fits together changed the way I think about web application development. But this isn&amp;#8217;t about Rails.&lt;/p&gt;

&lt;p&gt;I have a Struts application that I work on every day. I have a minor Rails application that I rarely work on. I like Ruby. But I also use Java.&lt;/p&gt;

&lt;p&gt;I recently spent some time focused on making Struts work for me rather than accepting the tedium of its status quo. Out grew a &lt;a href='http://github.com/mojodna/sprout'&gt;Sprout&lt;/a&gt;. The extract of something that makes my life easier. It might do the same for yours. Let me know.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: Richard Harms took this work a couple years after I abandoned it and maintained it for a year or so as a &lt;a href='http://code.google.com/p/struts-sprout/'&gt;Google Code project&lt;/a&gt;. It lives on GitHub for posterity (and portfolio) and includes the work that Richard contributed.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/mojodnadotnet/~4/LnMkSFH-2mU" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://mojodna.net/2005/10/31/struts-spring-java-5-sprout.html</feedburner:origLink></entry>
 
 <entry>
   <title>Ongoing MBTA Work</title>
   <link href="http://feedproxy.google.com/~r/mojodnadotnet/~3/a0EUZeai3Hk/ongoing-mbta-work.html" />
   <updated>2005-04-21T00:00:00-07:00</updated>
   <id>http://mojodna.net/2005/04/21/ongoing-mbta-work</id>
   <content type="html">&lt;h2 id='ongoing_mbta_work'&gt;Ongoing MBTA Work&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; I haven&amp;#8217;t worked on this in many moons and it has ceased to work. &lt;a href='http://www.thrall.net/maps/mbta.html'&gt;Here is a currently working MBTA map.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The coverage area has increased to cover Boston, Charlestown, South Boston, and Brookline (the 4 downtown maps). Rather than creating the slices for tiles by hand in Photoshop, I whipped up a PHP script to do it for me - &lt;a href='http://maps.mojodna.net/mbta/slicer.phps'&gt;slicer.php&lt;/a&gt; (requires GD w/ GIF support, an increased memory_limit in php.ini, and a GIF version of full.psd (same dimensions)).&lt;/p&gt;

&lt;p&gt;I added zoom level 1 by importing the PDFs as 3500px wide images and providing alternate coordinates to the slicer. Hint: it&amp;#8217;s much faster to work with them after converting them to Indexed Color (via RGB, as they get imported as CMYK) (and obviates the need to Export to Web).&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/mojodnadotnet/~4/a0EUZeai3Hk" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://mojodna.net/2005/04/21/ongoing-mbta-work.html</feedburner:origLink></entry>
 
 <entry>
   <title>Continuing to map the MBTA</title>
   <link href="http://feedproxy.google.com/~r/mojodnadotnet/~3/s7R7oZgukO0/continuing-to-map-the-mbta.html" />
   <updated>2005-04-20T00:00:00-07:00</updated>
   <id>http://mojodna.net/2005/04/20/continuing-to-map-the-mbta</id>
   <content type="html">&lt;h2 id='continuing_to_map_the_mbta'&gt;Continuing to map the MBTA&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; I haven&amp;#8217;t worked on this in many moons and it has ceased to work. &lt;a href='http://www.thrall.net/maps/mbta.html'&gt;Here is a currently working MBTA map.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ok, so &lt;a href='/2005/04/19/mbta-maps/'&gt;it&lt;/a&gt;&amp;#8217;s not that useful right now, since it only covers a small part of Cambridge at one zoom level. And since I don&amp;#8217;t really have the time to keep working on it right now, I&amp;#8217;ll post what I&amp;#8217;ve got so anyone else can continue.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href='http://www.mbta.com/traveling_t/schedules_pdfmaps_system.asp'&gt;Maps in PDF&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='http://maps.mojodna.net/mbta/mbta-images.tgz'&gt;Tiles so far (x=5378, y=-740 (top left) to x=5386, y=-733 (bottom right))&lt;/a&gt; (these are 128x128 each)&lt;/li&gt;

&lt;li&gt;&lt;a href='http://maps.mojodna.net/mbta/tiles.phps'&gt;Tile server&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='http://maps.mojodna.net/mbta/mbta_google_maps.user.js'&gt;Greasemonkey script&lt;/a&gt; (change mbta.baseURL to your own copy of the tiles)&lt;/li&gt;

&lt;li&gt;&lt;a href='http://maps.mojodna.net/mbta/full.psd.gz'&gt;Cambridge/Brookline/Charlestown/Boston, rasterized and stitched together (12MB)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The 4 downtown maps work best when rasterized to a 1710px wide canvas (before cropping) to create the tiles for zoom=2. I experimented with different sizes and even figured out the pixel/mile ratio (more below), but that didn&amp;#8217;t help.&lt;/p&gt;

&lt;p&gt;The PDFs are nice enough that it should be possible to create tiles for multiple zoom levels.&lt;/p&gt;

&lt;p&gt;The MBTA also has a nice &lt;a href='http://trip.mbta.com/cgi-bin/itin_page.pl'&gt;Trip Planner&lt;/a&gt; that could potentially be scraped to provide transfers on the sidebar (where driving directions would usually be placed). It appears to use lat/lon pairs internally, which is helpful.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/mojodnadotnet/~4/s7R7oZgukO0" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://mojodna.net/2005/04/20/continuing-to-map-the-mbta.html</feedburner:origLink></entry>
 
 <entry>
   <title>MBTA Maps</title>
   <link href="http://feedproxy.google.com/~r/mojodnadotnet/~3/4tfC3XhgZvk/mbta-maps.html" />
   <updated>2005-04-19T00:00:00-07:00</updated>
   <id>http://mojodna.net/2005/04/19/mbta-maps</id>
   <content type="html">&lt;h2 id='mbta_maps'&gt;MBTA Maps&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; I haven&amp;#8217;t worked on this in many moons and it has ceased to work. &lt;a href='http://www.thrall.net/maps/mbta.html'&gt;Here is a currently working MBTA map&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Following on the heels of &lt;a href='http://www.holovaty.com/'&gt;Adrian Holovaty&lt;/a&gt;&amp;#8217;s work with &lt;a href='http://www.holovaty.com/blog/archive/2005/04/19/0216'&gt;Chicago Transit Authority&lt;/a&gt; maps, I dug around on the &lt;a href='http://www.mbta.com/'&gt;MBTA&lt;/a&gt;&amp;#8217;s website (Boston&amp;#8217;s T) for some decent &lt;a href='http://www.mbta.com/traveling_t/schedules_pdfmaps_system.asp'&gt;maps&lt;/a&gt;. These are very clean and to-scale, so they were ripe for a Boston version.&lt;/p&gt;

&lt;p&gt;After some fiddling around, I discovered that the Cambridge / Somerville inset appears to correspond with Google&amp;#8217;s tiles when the PDF is imported into Photoshop with a width of 1710px. After some slicing and dicing, I give you:&lt;/p&gt;
&lt;a href='http://maps.mojodna.net/mbta/mbta.png'&gt;&lt;img src='http://maps.mojodna.net/mbta/mbta-thumb.png' border='0' alt='MBTA on Google Maps' /&gt;&lt;/a&gt;
&lt;p&gt;This is a screenshot of &lt;a href='http://maps.mojodna.net/mbta/mbta_google_maps.user.js'&gt;the Greasemonkey version&lt;/a&gt; (with &lt;strong&gt;minor&lt;/strong&gt; alterations to Adrian&amp;#8217;s).&lt;/p&gt;

&lt;p&gt;Building on top of some experimentation I&amp;#8217;d done last week with &lt;a href='http://stuff.rancidbacon.com/gmaps-standalone/'&gt;Google Maps stand-alone&lt;/a&gt;, I made a non-Greasemonkey version by adding in a hook that effectively runs the Greasemonkey script. The default zoom-level is wrong, and my quick and dirty tile-serving code uses redirects (slowing things down and not handling missing tiles gracefully), so I&amp;#8217;ll post that later once I get a chance to clean it up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; I don&amp;#8217;t have enough time to keep going on my own, so I&amp;#8217;ve &lt;a href='http://mojodna.net/2005/04/20/continuing-to-map-the-mbta/'&gt;posted my work and a basic knowledge dump&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; Ok, so I found a bit more time and added &lt;code&gt;zoom=1&lt;/code&gt; and &lt;code&gt;zoom=3&lt;/code&gt; (the latter with the bigger map, thus covering the suburbs, though the North Shore is a bit off). Adding additional zoom levels (&lt;code&gt;zoom=4&lt;/code&gt;, &lt;code&gt;zoom=5&lt;/code&gt;?) is probably worthwhile to get more of an overview. Beyond that, what&amp;#8217;s next is probably merging maps together to increase the coverage area and using the insets for additional zoom levels.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/mojodnadotnet/~4/4tfC3XhgZvk" height="1" width="1"/&gt;</content>
 <feedburner:origLink>http://mojodna.net/2005/04/19/mbta-maps.html</feedburner:origLink></entry>
 
 
</feed>
