<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><rss xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" version="2.0"><channel><description>A crazy ride on Rails.</description><title>Locomotivation</title><generator>Tumblr (3.0; @locomotivation)</generator><link>http://locomotivation.squeejee.com/</link><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" href="http://feeds.feedburner.com/locomotivation" type="application/rss+xml" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com" /><item><title>We've Moved!</title><description>&lt;p&gt;Hopefully, you’ve already figured out that we’ve moved all our blogging to the main &lt;a href="http://squeejee.com/blog"&gt;Squeejee blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We decided to make this change in an effort to unify our efforts around the Squeejee brand, rather than splitting the tech stuff out into something else.&lt;/p&gt;

&lt;p&gt;So head on over to &lt;a href="http://squeejee.com/blog"&gt;http://squeejee.com/blog&lt;/a&gt; for all the latest goodness!&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=WHLUmDZd7Kk:CmbIFcdFsfA:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=WHLUmDZd7Kk:CmbIFcdFsfA:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=WHLUmDZd7Kk:CmbIFcdFsfA:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=WHLUmDZd7Kk:CmbIFcdFsfA:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=WHLUmDZd7Kk:CmbIFcdFsfA:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</description><link>http://feedproxy.google.com/~r/locomotivation/~3/WHLUmDZd7Kk/241848543</link><guid isPermaLink="false">http://locomotivation.squeejee.com/post/241848543</guid><pubDate>Thu, 12 Nov 2009 17:06:28 -0500</pubDate><feedburner:origLink>http://locomotivation.squeejee.com/post/241848543</feedburner:origLink></item><item><title>TweetSaver.com Launched on MongoDB, MongoMapper</title><description>&lt;p&gt;As you all probably well know by now, we’re big fans of &lt;a href="http://www.mongodb.org"&gt;MongoDB&lt;/a&gt; here at &lt;a href="http://squeejee.com"&gt;Squeejee&lt;/a&gt;. We’ve &lt;a href="http://locomotivation.squeejee.com/post/148492725/twitter-apps-sing-on-mongodb"&gt;written about it&lt;/a&gt;, spoken about it, and ported mysql based &lt;a href="http://floxee.com"&gt;apps&lt;/a&gt; over to it.&lt;/p&gt;

&lt;p&gt;We’ve recently launched our first application built from the ground up on MongoDB. &lt;a href="http://tweetsaver.com"&gt;TweetSaver&lt;/a&gt; also happens to be the first production app that we know of utilizing &lt;a href="http://railstips.org"&gt;John Nunemaker’s&lt;/a&gt; &lt;a href="http://github.com/jnunemaker/mongomapper"&gt;MongoMapper&lt;/a&gt; ORM.&lt;/p&gt;

&lt;h3&gt;How MongoDB Made Things Easy&lt;/h3&gt;

&lt;h4&gt;Look, Ma, no schema!&lt;/h4&gt;

&lt;p&gt;Mongo’s schema-less nature made it easy to store all sorts of data from a number of APIs. Grabbing rich, deep data structures was easy because we didn’t have to relationally model the relationships between documents and subdocuments. We could simply ‘stash the hash.’&lt;/p&gt;

&lt;h4&gt;Upserts!&lt;/h4&gt;

&lt;p&gt;One of the coolest features of MongoDB is the ‘upsert.’ In ActiveRecord we do this with find_or_create_by_xxx. This performs two calls — a select and insert — for new records. MongoDB allows you to peform fire-and-forget saves by &lt;em&gt;upserting&lt;/em&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;myColl.update( { name: "Joe" }, { name: "Joe", age: 20 }, { upsert: true } );
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;where the name ‘Joe’ is your unique field.&lt;/p&gt;

&lt;h3&gt;How MongoDB Made Things Hard&lt;/h3&gt;

&lt;h4&gt;Embedded object, foreign key, or DBRef?&lt;/h4&gt;

&lt;p&gt;One of the toughest things about going NO-SQL is having to think about the best way to model data for your application. Although MongoDB shines in storing deep, nested document structures, it also fully supports more relational database design. This choice means that you need to think through the tradeoffs up front. It’s a good idea to go through the scenarios in which you’ll consume pieces of data and determine how to store it.&lt;/p&gt;

&lt;h4&gt;Plugins / Gems built on ActiveRecord&lt;/h4&gt;

&lt;p&gt;Since MongoMapper is not ActiveRecord, all of those Rails gems and plugins that have ActiveRecord dependencies will not work.  Your choices here are:&lt;/p&gt;

&lt;p&gt;(a) Go with a MySQL hybrid approach to store a subset of data in MySQL.  For example, we store our Users table in MySQL to take advantage of the number of Authentication plugins out there. (RestfulAuth / AuthLogic / TwitterAuth/ etc). However, even this is pretty simple to implement with MongoDB.  Here is a Gist from John Nunemaker to implement authentication with his MongoMapper ORM:  &lt;a href="http://gist.github.com/147427"&gt;http://gist.github.com/147427&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(b) Port over a solution to work with MongoDB.  The nice part about this route is that there are no migrations to futz with.  Just define your needed fields directly in your app, plugin, or gem.&lt;/p&gt;

&lt;h3&gt;Major Gotcha’s&lt;/h3&gt;

&lt;p&gt;It’s easy to play around with Mongo, see its speed and think that the laws of computing have been suspended. Alas, collections with hundreds of thousands of records still require indexing.&lt;/p&gt;

&lt;p&gt;So, before you start your next project take a minute to see if &lt;a href="http://www.mongodb.org"&gt;MongoDB&lt;/a&gt; might be a good fit… it’s been a great addition to the &lt;a href="http://squeejee.com"&gt;Squeejee&lt;/a&gt; toolbox.&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=VxDoewNW2tw:685a0qzxWtY:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=VxDoewNW2tw:685a0qzxWtY:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=VxDoewNW2tw:685a0qzxWtY:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=VxDoewNW2tw:685a0qzxWtY:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=VxDoewNW2tw:685a0qzxWtY:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</description><link>http://feedproxy.google.com/~r/locomotivation/~3/VxDoewNW2tw/200163417</link><guid isPermaLink="false">http://locomotivation.squeejee.com/post/200163417</guid><pubDate>Tue, 29 Sep 2009 11:41:13 -0400</pubDate><category>mongodb</category><category>tweetsaver</category><category>launch</category><feedburner:origLink>http://locomotivation.squeejee.com/post/200163417</feedburner:origLink></item><item><title>Ready, FIGHT! TwitterLand now includes ThumbFight support</title><description>&lt;p&gt;&lt;img src="http://thumbfight.com/images/fight.gif" alt="ThumbFight logo"/&gt; Thanks to &lt;a href="http://deadprogrammersociety.com/"&gt;Ron Evans&lt;/a&gt; for contributing &lt;a href="http://thumbfight.com"&gt;ThumbFight&lt;/a&gt; support for our &lt;a href="http://github.com/squeejee/twitterland"&gt;TwitterLand gem&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://thumbfight.com"&gt;ThumbFight&lt;/a&gt; measures how people liked or disliked search terms based on the last one hundred tweets for the term. The results can be shown in a head-to-head matchup &lt;a href="http://thumbfight.com/fight?challenger1=Ruby&amp;x=0&amp;y=0&amp;challenger2=Python"&gt;like this one for “Ruby vs. Python”&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To use the ThumbFight API, just upgrade to TwitterLand version 0.3.0: &lt;code&gt;sudo gem install twitterland&lt;/code&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=ColCzMiDhhI:rpJZe4c42jI:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=ColCzMiDhhI:rpJZe4c42jI:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=ColCzMiDhhI:rpJZe4c42jI:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=ColCzMiDhhI:rpJZe4c42jI:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=ColCzMiDhhI:rpJZe4c42jI:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</description><link>http://feedproxy.google.com/~r/locomotivation/~3/ColCzMiDhhI/181298776</link><guid isPermaLink="false">http://locomotivation.squeejee.com/post/181298776</guid><pubDate>Sun, 06 Sep 2009 14:03:09 -0400</pubDate><category>twitterland</category><category>Twitter</category><feedburner:origLink>http://locomotivation.squeejee.com/post/181298776</feedburner:origLink></item><item><title>My favorite of all the new Google Reader features released this...</title><description>&lt;img src="http://14.media.tumblr.com/tumblr_kodlr7UYBq1qzdpp2o1_500.jpg"/&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt;My favorite of all &lt;a href="http://googlereader.blogspot.com/2009/08/flurry-of-features-for-feed-readers.html"&gt;the new Google Reader features&lt;/a&gt; released this week. Now catching up on older posts is not quite all-or-nothing.&lt;/p&gt;

&lt;p&gt;— &lt;a href="http://twitter.com/pengwynn"&gt;@pengwynn&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=x0Qnm8TWuDE:KMn2o761bII:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=x0Qnm8TWuDE:KMn2o761bII:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=x0Qnm8TWuDE:KMn2o761bII:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=x0Qnm8TWuDE:KMn2o761bII:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=x0Qnm8TWuDE:KMn2o761bII:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</description><link>http://feedproxy.google.com/~r/locomotivation/~3/x0Qnm8TWuDE/162910489</link><guid isPermaLink="false">http://locomotivation.squeejee.com/post/162910489</guid><pubDate>Fri, 14 Aug 2009 13:00:00 -0400</pubDate><category>google reader</category><category>tools</category><category>tips</category><feedburner:origLink>http://locomotivation.squeejee.com/post/162910489</feedburner:origLink></item><item><title>Do the Monster Mash!</title><description>&lt;p&gt;Working on &lt;a href="http://locomotivation.squeejee.com/post/158183764/twitterland-all-the-best-twitter-related-apis-in-one-gem"&gt;several&lt;/a&gt; API &lt;a href="http://locomotivation.squeejee.com/post/156527806/remixr-ruby-gem-for-the-bestbuy-remix-api"&gt;wrappers&lt;/a&gt; lately, one of my favorite gems is &lt;a href="http://www.intridea.com/2008/4/12/mash-mocking-hash-for-total-poser-objects?blog=company"&gt;Mash&lt;/a&gt; from &lt;a href="http://twitter.com/mbleigh"&gt;Michael Bleigh&lt;/a&gt;. Much like &lt;a href="http://www.ruby-doc.org/stdlib/libdoc/ostruct/rdoc/classes/OpenStruct.html"&gt;OpenStruct&lt;/a&gt;, Mash is basically a Hash with dot notation.&lt;/p&gt;

&lt;p&gt;But unlike OpenStruct, you can pass a Hash to Mash and it will recursively do a deep dive and Mashify it. Now you get pretty accessor methods for all those parsed hashes from your JSON API calls:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# Return tweet referencing a URL
results = Twitterland::BackTweets.search('http://squeejee.com', 'OU812')
results.tweets.size
=&gt; 25
results.tweets.first.from_user
=&gt; "euromarianne"
results.items_per_page
=&gt; 25
results.total_results
=&gt; 3301
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;i_am_ron_burgandy?&lt;/h3&gt;

&lt;p&gt;Mash includes a couple of helpful methods for traversing those deep hashes when you’re not certain if a key has been set:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;mash = Mash.new
mash.first_name?
=&gt; false
mash.first_name = 'Wynn'
mash.first_name?
=&gt; true
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The &lt;code&gt;!&lt;/code&gt; method provides multi-level assignment, kind of like &lt;code&gt;mkdir -p&lt;/code&gt; for hashes:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;mash = Mash.new
mash.these_droids!.will_do_nicely = true
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Install the gem and mash it up&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;sudo gem install mash
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;— posted by Wynn Netherland // &lt;a href="http://twitter.com/pengwynn"&gt;@pengwynn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;style type="text/css"&gt;
pre {
  overflow: auto;
  padding: 1em;
  background: #000;
  color: #efefef;
}
&lt;/style&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=KpObz-yqwIY:M4CtHhfYEso:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=KpObz-yqwIY:M4CtHhfYEso:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=KpObz-yqwIY:M4CtHhfYEso:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=KpObz-yqwIY:M4CtHhfYEso:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=KpObz-yqwIY:M4CtHhfYEso:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</description><link>http://feedproxy.google.com/~r/locomotivation/~3/KpObz-yqwIY/162787420</link><guid isPermaLink="false">http://locomotivation.squeejee.com/post/162787420</guid><pubDate>Fri, 14 Aug 2009 09:00:00 -0400</pubDate><category>gems</category><category>ruby</category><feedburner:origLink>http://locomotivation.squeejee.com/post/162787420</feedburner:origLink></item><item><title>Google Ad(makes no)Sense</title><description>&lt;p&gt;Today we received an email from Google AdSense letting us know they had disabled our AdSense account.  The only explanation we got was that we were a “Significant risk to their AdWords advertisers”.  Check out the full email.&lt;/p&gt;



&lt;p&gt;&lt;i&gt;Hello,&lt;br/&gt;&lt;br/&gt; While going through our records recently, we found that your AdSense&lt;br/&gt; account has posed a significant risk to our AdWords advertisers. Since&lt;br/&gt; keeping your account in our publisher network may financially damage our&lt;br/&gt; advertisers in the future, we’ve decided to disable your account.&lt;br/&gt;&lt;br/&gt; Please understand that we consider this a necessary step to protect the&lt;br/&gt; interests of both our advertisers and our other AdSense publishers. We&lt;br/&gt; realize the inconvenience this may cause you, and we thank you in advance&lt;br/&gt; for your understanding and cooperation.&lt;br/&gt;&lt;br/&gt; If you have any questions about your account or the actions we’ve taken,&lt;br/&gt; please do not reply to this email. You can find more information by&lt;br/&gt; visiting&lt;br/&gt;&lt;a href="https://www.google.com/adsense/support/bin/answer.py?answer=57153" target="_blank"&gt;&lt;a href="https://www.google.com/adsense/support/bin/answer.py?answer=57153"&gt;https://www.google.com/adsense/support/bin/answer.py?answer=57153&lt;/a&gt;&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt; Sincerely,&lt;br/&gt;&lt;br/&gt; The Google AdSense Team&lt;/i&gt;&lt;/p&gt;



&lt;p&gt;Google’s email is cryptic to say the least.  It would have been nice to at least know the specific violations we commited that put their advertisers at risk.  Not only did they cancel our account but they also confiscated our AdSense earnings.  I just don’t understand the draconian nature of their actions.  If we did something that violated the TOS then tell us.  If we knew what we did wrong I promise we would make it right.  I did file an appeal and will keep you updated on our communication with Google.&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=Mvxy_m_zbu8:NvYpq0K3ik8:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=Mvxy_m_zbu8:NvYpq0K3ik8:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=Mvxy_m_zbu8:NvYpq0K3ik8:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=Mvxy_m_zbu8:NvYpq0K3ik8:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=Mvxy_m_zbu8:NvYpq0K3ik8:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</description><link>http://feedproxy.google.com/~r/locomotivation/~3/Mvxy_m_zbu8/161342648</link><guid isPermaLink="false">http://locomotivation.squeejee.com/post/161342648</guid><pubDate>Wed, 12 Aug 2009 11:57:00 -0400</pubDate><feedburner:origLink>http://locomotivation.squeejee.com/post/161342648</feedburner:origLink></item><item><title>TwitterLand - All the best Twitter-related APIs in one gem!</title><description>&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Update&lt;/em&gt; - Added TweetBlocker API support&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Update&lt;/em&gt; - Added BackTweets API support&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here at Squeejee we do a lot of data mashups, and more and more of those are mashups of Twitter and 3rd-party services related to Twitter.&lt;/p&gt;

&lt;p&gt;There are a bunch of Twitter-related sites out there that have great APIs to pull in their data. Since we’ve been using so many of them repeatedly in our projects we finally decided to pull them into a single gem. TwitterLand will give all you other Twitter app developers out there easy access to these great APIs in your ruby projects.&lt;/p&gt;

&lt;p&gt;So far we’ve included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://followcost.com"&gt;Follow Cost&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://twitter.grader.com"&gt;Twitter Grader&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://mrtweet.com"&gt;Mr. Tweet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://twinfluence.com"&gt;Twinfluence&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="http://twittercounter.com"&gt;Twitter Counter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://tweetblocker.com"&gt;TweetBlocker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://backtweets.com"&gt;BackTweets&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Install&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt; sudo gem install twitterland
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Follow Cost Usage&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;# Get follow cost for specified user
Twitterland::FollowCost.show('bradleyjoyce')

=&gt; &lt;Mash at_reply_index=24.0 average_tweets_per_day=6.87254901960784 average_tweets_per_day_recently=19.6396220282001 golden_index=3.0 milliscobles_all_time=324.02 milliscobles_recently=925.96 political_index=1.0 profile_image_url="http://s3.amazonaws.com/twitter_production/profile_images/179927752/bradley_normal.png" statuses_count=3505 twitter_created_at="2008/03/14 18:26:52 -0700" username="bradleyjoyce"&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Twitter Grader Usage&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;# request your api key at [http://twitter.grader.com/accessrequestform](&lt;a href="http://twitter.grader.com/accessrequestform"&gt;http://twitter.grader.com/accessrequestform&lt;/a&gt;)
# get twitter grade for user
api_key = "OU812"
Twitterland::TwitterGrader.grade('bradleyjoyce', api_key)

=&gt; 98.4183
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Mr.Tweet Usage&lt;/h3&gt;

&lt;h5&gt;get your api key at &lt;a href="http://api.mrtweet.com/newapi"&gt;&lt;a href="http://api.mrtweet.com/newapi"&gt;http://api.mrtweet.com/newapi&lt;/a&gt;&lt;/a&gt;
&lt;/h5&gt;

&lt;h4&gt;Initialize Mrtweet&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;api_key = 'OU812'
mt = Twitterland::Mrtweet.new(api_key,'bradleyjoyce')
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;Is user&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;# Check whether the given user is a MrTweet user.
mt.is_user

=&gt; true
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;Profile&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;# Returns MrTweet statistics of the given user
mt.profile

=&gt; &lt;Mash conversation=0.225 conversation_percentile=53 frequency=8.98621 frequency_percentile=90 links=0.53 links_percentile=87 recommendations=1&gt;

mt.profile.links

=&gt; 0.53
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;Recommendations&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;# Returns the latest recommendations the given user received on MrTweet
mt.recommendations

=&gt; [&lt;Mash date=Thu Aug 06 01:02:54 -0500 2009 name="billtrammel" text="he is an entrepreneur, and one of the developers of TweetCongress.org, award-winning site promoting government transparency. Plus, he's a good friend."&gt;]

mt.recommendations.first.name

=&gt; "billtrammel"
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;Most attention towards&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;# Returns the twitter_id's of 3 users that for the given user pays the most attention to
mt.most_attenion_towards

=&gt; [15049040, 17993906, 22286046]
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;Recommend&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;# Creates a recommendation from the given user, to another user (aka "friend_name")
reason = "Wynn is an awesome entrepreneur, rubyist, designer and friend! Follow him for his useful and entertaining tweets!"

friend_name = "pengwynn"

mt.recommend(reason,friend_name)

=&gt; true
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Twinfluence&lt;/h3&gt;

&lt;h4&gt;Initialize Twinfluence&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;username = 'bradleyjoyce'
password = 'mypassword'
t = Twitterland::Twinfluence.new(username,password)

=&gt; #&lt;Twitterland::Twinfluence:0x317ecf8 @username="bradleyjoyce", @password="mypassword"&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;User&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;# gets twinfluence data for user
t.user('bradleyjoyce')

t.user('bradleyjoyce')

=&gt; &lt;Mash user=&lt;Mash adrider=&lt;Mash script=[&lt;Mash type="text/javascript"&gt;, &lt;Mash src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type="text/javascript"&gt;]&gt; centralization="24.224443556856" centralization_grade="0.0 Average - Resilient" description="Entrepreneur and Web Developer -- Floxee.com, TweetCongress.org, Sherflock.com, Squeejee.com" followers_count="1315" friends_count="1278" id="14688076" location=nil name="Bradley Joyce" screen_name="bradleyjoyce" second_order="7936664" second_order_grade="Rank: &lt;b&gt;#10,185&lt;/b&gt; (93%)" social_capital="6035.4859315589" social_capital_grade="+1.4 High" statuses_count="3511" unix_timestamp="1249683954" url="http://bradleyjoyce.me" velocity="15535.453112211" velocity_grade="+0.1 Fast Average"&gt;&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Twitter Counter&lt;/h3&gt;

&lt;h4&gt;Show&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;tc = Twitterland::TwitterCounter.show('bradleyjoyce')

tc.rank

=&gt; 37194

# available methods
tomorrow_2w 
followers_2w_ago 
followers_yesterday 
followers_current 
friends_current 
next_month 
growth_since_2w  
started_followers 
rank 
user_id 
growth_since 
follow_days 
tomorrow 
next_month_2w 
average_growth 
average_growth_2w
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;TweetBlocker&lt;/h3&gt;

&lt;h4&gt;User&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;# get grade for user
result = Twitterland::TweetBlocker.user('bradleyjoyce')

=&gt; &lt;Mash grade="a" score=100 url="http://twitter.com/bradleyjoyce" username="Bradley Joyce"&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;Spam&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;# report a user as spammer
Twitterland::TweetBlocker.report_spam('spamtest')
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;Rate Limit Status&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;# check your rate limit status
Twitterland::TweetBlocker.rate_limit
=&gt; &lt;Mash hourly_limit=100 remaining_hits=100 reset_time="2009-08-11 23:12:41 UTC" reset_time_in_seconds=2746&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;BackTweets&lt;/h3&gt;

&lt;h4&gt;Search&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;# Return tweet referencing a URL
results = Twitterland::BackTweets.search('http://squeejee.com', 'OU812')
results.tweets.size
=&gt; 25
results.tweets.first.from_user
=&gt; "euromarianne"
results.items_per_page
=&gt; 25
results.total_results
=&gt; 3301
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Source&lt;/h3&gt;

&lt;p&gt;&lt;a href="http://github.com/squeejee/twitterland/"&gt;&lt;a href="http://github.com/squeejee/twitterland/"&gt;http://github.com/squeejee/twitterland/&lt;/a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Documentation&lt;/h3&gt;

&lt;p&gt;&lt;a href="http://rdoc.info/projects/squeejee/twitterland"&gt;&lt;a href="http://rdoc.info/projects/squeejee/twitterland"&gt;http://rdoc.info/projects/squeejee/twitterland&lt;/a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Copyright&lt;/h3&gt;

&lt;p&gt;Copyright (c) 2009 Squeejee. See LICENSE for details.&lt;/p&gt;

&lt;h3&gt;posted by &lt;a href="http://twitter.squeejee.com/people/bradley-joyce"&gt;@bradleyjoyce&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;style type="text/css"&gt;
pre {
  overflow: auto;
  padding: 1em;
  background: #000;
  color: #efefef;
}
&lt;/style&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=fsY3r3MVngM:UMJ6wtXwCdg:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=fsY3r3MVngM:UMJ6wtXwCdg:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=fsY3r3MVngM:UMJ6wtXwCdg:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=fsY3r3MVngM:UMJ6wtXwCdg:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=fsY3r3MVngM:UMJ6wtXwCdg:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</description><link>http://feedproxy.google.com/~r/locomotivation/~3/fsY3r3MVngM/158183764</link><guid isPermaLink="false">http://locomotivation.squeejee.com/post/158183764</guid><pubDate>Fri, 07 Aug 2009 18:48:00 -0400</pubDate><category>gems</category><category>open-source</category><category>ruby</category><category>twitter</category><category>apis</category><feedburner:origLink>http://locomotivation.squeejee.com/post/158183764</feedburner:origLink></item><item><title>Remixr - Ruby gem for the BestBuy Remix API</title><description>&lt;p&gt;The Ruby BestBuy &lt;a href="http://remix.bestbuy.com/docs"&gt;Remix API&lt;/a&gt; gem. Remix is an API that gives you access to BestBuy.com’s product catalog data and more. &lt;a href="http://squeejee.github.com/remixr"&gt;Remixr&lt;/a&gt; is a Ruby wrapper for the Remix API.&lt;/p&gt;

&lt;h3&gt;Install&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;sudo gem install remixr
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Usage&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;Remixr.api_key = 'OU812' # get yours from &lt;a href="http://remix.bestbuy.com/apps/register"&gt;http://remix.bestbuy.com/apps/register&lt;/a&gt;
client = Remixr::Client.new
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;Find stores&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;# find stores within 50 miles of ZIP 76227
stores = client.stores({:area =&gt; ['76227', 50]}).fetch.stores

stores.first

=&gt; {"city"=&gt;"Denton", "longName"=&gt;"Best Buy - Denton", "name"=&gt;"Denton", "region"=&gt;"TX", "address"=&gt;"1800 S Loop 288, Ste 102 Bldg 1 ", "country"=&gt;"US", "lng"=&gt;-97.10067, "postalCode"=&gt;"76205", "phone"=&gt;"940-384-9581", "hours"=&gt;"Mon: 10-9; Tue: 10-9; Wed: 10-9; Thurs: 10-9; Fri: 10-10; Sat: 10-10; Sun: 11-8", "storeId"=&gt;827, "fullPostalCode"=&gt;"76205", "lat"=&gt;33.192524, "distance"=&gt;9.79}
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;Find products&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;# fetch first page of products on sale below 20 bucks
products = client.products({:salePrice =&gt; {'$lt' =&gt; 20.00}}).fetch.products

# fetch only SKU and salePrice 
products = client.products({:salePrice =&gt; {'$lt' =&gt; 20.00}}).fetch.products
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;Chaining&lt;/h4&gt;

&lt;p&gt;You can also chain &lt;code&gt;stores&lt;/code&gt; and &lt;code&gt;products&lt;/code&gt; to return stores and nested product info or vice versa&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# find stores within 50 miles of ZIP 76227 and products over three G's

stores = client.stores({:area =&gt; ['76227', 50]}).products({:salePrice =&gt; {'$gt' =&gt; 3000}}).fetch.stores

stores.first.products.first.shortDescription

#=&gt; "ENERGY STAR Qualified 4 HDMI inputs; gray Touch of Color bezel; 16:9 aspect ratio"
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;Fetching&lt;/h4&gt;

&lt;p&gt;All calls terminate in a call to &lt;code&gt;fetch&lt;/code&gt; which takes the following options&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;:page - positive integer for page number
:show - comma delimited string or array of field names to show
:sort - hash or string of sort info {'fieldname' =&gt; 'asc|desc'}
&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;Conditional operators&lt;/h4&gt;

&lt;p&gt;We took a page out of &lt;a href="http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-ConditionalOperators%3A%3C%2C%3C%3D%2C%3E%2C%3E%3D"&gt;MongoDB’s&lt;/a&gt; playbook and mapped conditional operators to text equivalents to avoid having these be keys to hashes:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;{:salePrice =&gt; {'$gte' =&gt; 300.00}}
=&gt; salePrice &gt; 300.00

$gte - greater than or equal to : field &gt; value
$lte - less than or equal to : field &gt; value
$gt - greater than : field &gt; value
$lt - less than : field &gt; value
$ne - not equal to : field != value
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;More in the examples folder:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://github.com/squeejee/remixr/tree/master/examples"&gt;&lt;a href="http://github.com/squeejee/remixr/tree/master/examples"&gt;http://github.com/squeejee/remixr/tree/master/examples&lt;/a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;Documentation&lt;/h4&gt;

&lt;p&gt;&lt;a href="http://rdoc.info/projects/squeejee/remixr"&gt;&lt;a href="http://rdoc.info/projects/squeejee/remixr"&gt;http://rdoc.info/projects/squeejee/remixr&lt;/a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;Copyright&lt;/h4&gt;

&lt;p&gt;Copyright (c) 2009 Squeejee. See LICENSE for details.&lt;/p&gt;

&lt;p&gt;&lt;style type="text/css"&gt;
pre {
  overflow: auto;
  padding: 1em;
  background: #000;
  color: #efefef;
}
&lt;/style&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=sHSirqL-1fo:DknPtL-LU0w:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=sHSirqL-1fo:DknPtL-LU0w:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=sHSirqL-1fo:DknPtL-LU0w:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=sHSirqL-1fo:DknPtL-LU0w:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=sHSirqL-1fo:DknPtL-LU0w:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</description><link>http://feedproxy.google.com/~r/locomotivation/~3/sHSirqL-1fo/156527806</link><guid isPermaLink="false">http://locomotivation.squeejee.com/post/156527806</guid><pubDate>Wed, 05 Aug 2009 13:38:07 -0400</pubDate><category>ruby</category><category>api</category><category>remix</category><category>code</category><feedburner:origLink>http://locomotivation.squeejee.com/post/156527806</feedburner:origLink></item><item><title>Twitter apps sing on MongoDB</title><description>&lt;p&gt;As we’ve &lt;a href="http://locomotivation.squeejee.com/post/117119353/mongodb-ruby-friendly-document-storage-that-doesnt-rhyme"&gt;previously mentioned&lt;/a&gt;, at &lt;a href="http://squeejee.com"&gt;Squeejee&lt;/a&gt;, we’re big fans of &lt;a href="http://mongodb.org"&gt;MongoDB&lt;/a&gt;. We’re excited that some &lt;a href="http://railstips.org"&gt;very&lt;/a&gt; &lt;a href="http://twitter.com/tobi/status/2773737825"&gt;smart&lt;/a&gt; people are starting to take notice of Mongo, too.&lt;/p&gt;

&lt;p&gt;In this first post of what we hope will be a helpful introductory series, we’ll cover the use case of using a document store like MongoDB.&lt;/p&gt;

&lt;h3&gt;Mashups are all the rage&lt;/h3&gt;

&lt;p&gt;It’s rare that we are presented with a new project from a prospective client these days that doesn’t pull data from some third-party API. &lt;a href="http://twitter.com"&gt;Twitter&lt;/a&gt; seems to be the most popular choice of late, perhaps because it adds value both as a content provider and a transportation medium. One of the challenges of integrating with Twitter in any large-scale way is creating your own local cache of Twitter data so you can ration those precious API calls and avoid their &lt;a href="http://apiwiki.twitter.com/Rate-limiting"&gt;API rate limiting&lt;/a&gt;. When we built our own mashup, &lt;a href="http://tweetcongress.org"&gt;Tweet Congress&lt;/a&gt; we wanted to integrate with a number of third-party Twitter services that each pulled data from Twitter, analyzed it, and passed that value on via their own APIs. The common thread was that each of these services exposed data as &lt;a href="http://www.json.org/"&gt;JSON (JavaScript Object Notation)&lt;/a&gt;. Taking these deep, rich data structures and breaking them out into relational database tables seemed silly (not to mention tedious). With the relational approach, if a service exposed a new value that we wanted to consume without fighting our framework, we’d have to extend the schema on our end to capture it.&lt;/p&gt;

&lt;h3&gt;Schema-less is more&lt;/h3&gt;

&lt;p&gt;MongoDB is a high-performance, open source, &lt;a href="http://mongodb.org"&gt;schema-free document-oriented  database&lt;/a&gt;.  This means we stash those deep data structures  we get back from those nifty web services more easily. MongoDB uses &lt;a href="http://www.mongodb.org/display/DOCS/BSON"&gt;BSON&lt;/a&gt; to store your documents (or objects). This BSON smells less like &lt;a href="http://en.wikipedia.org/wiki/Bison"&gt;a buffalo&lt;/a&gt; and more like JSON, which you already know. The main difference is that BSON can serialize binary data (images, and even Word documents, oh the irony!) and dates. This schema-less storage means your app can store all that new data &lt;a href="http://twittercounter.com"&gt;TwitterCounter&lt;/a&gt; returns you tomorrow without needing to change your database.&lt;/p&gt;

&lt;h3&gt;Hey, ever heard of CouchDB?&lt;/h3&gt;

&lt;p&gt;Certainly &lt;a href="http://couchdb.apache.org"&gt;CouchDB&lt;/a&gt; (and many other tools that predate it), does document storage and does it well. We are still big CouchDB fans, however we found that querying CouchDB dynamically was a lot like getting a question answered at the DMV. CouchDB shines when you know how you’re going to query the data up-front, in static views of schema-less data.&lt;/p&gt;

&lt;p&gt;MongoDB provides all that schema-less goodness without giving up any of the rich dynamic querying you get with a relational database. More on this in our next post.&lt;/p&gt;

&lt;h3&gt;Back to our Twitter mashup&lt;/h3&gt;

&lt;p&gt;Let’s talk specifically about Twitter for a moment. The Twitter API has methods to retrieve the social graph for a particular user, returning an array of integer user IDs for the user’s friends or followers. With a relational database, you’d normally insert a row for each of those values. So a user with a thousand friends would get a thousand database rows, each with something like &lt;code&gt;user_id&lt;/code&gt; and &lt;code&gt;friend_id&lt;/code&gt; (and &lt;code&gt;id&lt;/code&gt; if you’re using some Rails magic). With MongoDB, we can just stash that array with the object itself — and query it: &lt;code&gt;:conditions =&gt; {:friend_ids =&gt; current_user.id}&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;Fields on-the-fly&lt;/h3&gt;

&lt;p&gt;Another benefit of all this schema-less goodness is your application can define fields at run-time. On our &lt;a href="http://floxee.com"&gt;Floxee&lt;/a&gt; platform, we support different custom fields on a per-site basis. Due to the magic of MongoDB, we can have custom fields for &lt;code&gt;party&lt;/code&gt;, &lt;code&gt;state&lt;/code&gt;, and &lt;code&gt;district&lt;/code&gt; for all the congressfolk in &lt;a href="http://tweetcongress.org"&gt;Tweet Congress&lt;/a&gt; and fields like &lt;code&gt;skills&lt;/code&gt; and &lt;code&gt;soda_preference&lt;/code&gt; on our own &lt;a href="http://twitter.squeejee.com"&gt;Squeejee twitter directory&lt;/a&gt;. Just upload your CSV with some custom columns and we’ll create those fields on-the-fly (after sanitizing them of course).&lt;/p&gt;

&lt;h3&gt;More to come&lt;/h3&gt;

&lt;p&gt;In our upcoming posts we’ll cover Ruby bindings, performance, deployment, and more. Stay tuned!&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=FzUT9loqV88:8OnCSCFxSzk:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=FzUT9loqV88:8OnCSCFxSzk:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=FzUT9loqV88:8OnCSCFxSzk:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=FzUT9loqV88:8OnCSCFxSzk:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=FzUT9loqV88:8OnCSCFxSzk:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</description><link>http://feedproxy.google.com/~r/locomotivation/~3/FzUT9loqV88/148492725</link><guid isPermaLink="false">http://locomotivation.squeejee.com/post/148492725</guid><pubDate>Fri, 24 Jul 2009 17:42:00 -0400</pubDate><category>business,</category><category>mongodb</category><category>databases</category><category>mashups</category><feedburner:origLink>http://locomotivation.squeejee.com/post/148492725</feedburner:origLink></item><item><title>TextMate Pro Tip: Use F2 for bookmarks</title><description>&lt;p&gt;One of the things I love about &lt;a href="http://macromates.com/"&gt;TextMate&lt;/a&gt; is that it’s not an IDE, and it feels lightning fast.  That said, sometimes I forget to use some of the powerful features in TM.  Here’s one I decided to start using:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cmd-F2 to toggle a bookmark&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;F2 to navigate to the next bookmark&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shift-F2 to navigate to the previous bookmark&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;How many times have you scrolled back and forth between two (or more) chunks of code using the mouse wheel, Cmd-F, Cmd-G, page up, page down, or the dreaded scrollbar drag which makes your “mouse mileage” go up?&lt;/p&gt;

&lt;p&gt;Just use F2 and you can quickly flip between your bookmarks.  &lt;strong&gt;Note&lt;/strong&gt;: You can also use the mouse to toggle the “*” that appears on your bookmarked lines of code.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Pro Tip: On a Mac laptop, consider changing this setting:&lt;/em&gt;
&lt;em&gt;System Preferences -&gt; Keyboard &amp; Mouse -&gt; Use all F1, F2, etc as standard function&lt;/em&gt;
&lt;em&gt;keys to “checked”.  That way you won’t have to hit the “fn” key to use the magical F2!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Carpal tunnel syndrome be damned!!&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=2Lpm0tXLOBY:JouUCCvV3pM:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=2Lpm0tXLOBY:JouUCCvV3pM:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=2Lpm0tXLOBY:JouUCCvV3pM:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=2Lpm0tXLOBY:JouUCCvV3pM:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=2Lpm0tXLOBY:JouUCCvV3pM:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</description><link>http://feedproxy.google.com/~r/locomotivation/~3/2Lpm0tXLOBY/146040197</link><guid isPermaLink="false">http://locomotivation.squeejee.com/post/146040197</guid><pubDate>Tue, 21 Jul 2009 08:54:00 -0400</pubDate><category>textmate</category><category>protips</category><feedburner:origLink>http://locomotivation.squeejee.com/post/146040197</feedburner:origLink></item><item><title>Global Settings With Mongo and Rails in 15 Lines of Code</title><description>&lt;p&gt;There is a nice ActiveRecord Rails plugin called &lt;a href="http://github.com/Squeegy/rails-settings/tree/master"&gt;rails-settings&lt;/a&gt; that does the following:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Settings is a plugin that makes managing a table of global key, value pairs easy. Think of 
  it like a global Hash stored in you database, that uses simple ActiveRecord like methods
  for manipulation. Keep track of any global setting that you dont want to hard code into 
  your rails app. You can store any kind of object. Strings, numbers, arrays, or any object.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We have used this plugin in the past for global key / value pairs and it works great.  However, for our Floxee app, we are using MongoDB / MongoRecord so this plugin would not work.&lt;/p&gt;

&lt;p&gt;Lucky for us, MongoDB is already a document store so I was able to recreate similar code in about 15 lines of code inside of a “method_missing” method.&lt;/p&gt;

&lt;script src="http://gist.github.com/138612.js"&gt;&lt;/script&gt;&lt;p&gt;With this, we are now able to do things like:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&gt;&gt; AppSetting.jim = "is cool"&lt;/code&gt;&lt;br/&gt;&lt;code&gt;=&gt; "is cool"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&gt;&gt; AppSetting.arr = ["jim", "was", "here"]&lt;/code&gt;&lt;br/&gt;&lt;code&gt;=&gt; ["jim", "was", "here"]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&gt;&gt; AppSetting.arr.first&lt;/code&gt;&lt;br/&gt;&lt;code&gt;=&gt; "jim"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&gt;&gt; AppSetting.hsh = {"hash_1"=&gt;1, "hash_2"=&gt;"demo"}&lt;/code&gt;&lt;br/&gt;&lt;code&gt;=&gt; {"hash_1"=&gt;1, "hash_2"=&gt;"demo"}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&gt;&gt; AppSetting.hsh["hash_2"]&lt;/code&gt;&lt;br/&gt;&lt;code&gt;=&gt; "demo"&lt;/code&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=XJKN5b10Zt8:1WOQzNAnSSA:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=XJKN5b10Zt8:1WOQzNAnSSA:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=XJKN5b10Zt8:1WOQzNAnSSA:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=XJKN5b10Zt8:1WOQzNAnSSA:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=XJKN5b10Zt8:1WOQzNAnSSA:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</description><link>http://feedproxy.google.com/~r/locomotivation/~3/XJKN5b10Zt8/133377121</link><guid isPermaLink="false">http://locomotivation.squeejee.com/post/133377121</guid><pubDate>Wed, 01 Jul 2009 01:52:00 -0400</pubDate><feedburner:origLink>http://locomotivation.squeejee.com/post/133377121</feedburner:origLink></item><item><title>MongoDB - Ruby friendly document storage that doesn't rhyme with ouch</title><description>&lt;p&gt;I had the opportunity to share &lt;a href="http://mongodb.org"&gt;MongoDB&lt;/a&gt; with the &lt;a href="http://dallasrb.org"&gt;Dallas.rb&lt;/a&gt; group this evening. Thanks &lt;a href="http://twitter.com/mully"&gt;@mully&lt;/a&gt; and &lt;a href="http://twitter.com/jnunemaker"&gt;@jnunemaker&lt;/a&gt; for contributing slides!&lt;/p&gt;

&lt;div style="width:425px;text-align:left" id="__ss_1524935"&gt;
&lt;a style="font:14px Helvetica,Arial,Sans-serif;display:block;margin:12px 0 3px 0;text-decoration:underline;" href="http://www.slideshare.net/pengwynn/mongodb-ruby-document-store-that-doesnt-rhyme-with-ouch?type=presentation" title="MongoDB - Ruby document store that doesn't rhyme with ouch"&gt;MongoDB - Ruby document store that doesn’t rhyme with ouch&lt;/a&gt;&lt;object style="margin:0px" width="425" height="355"&gt;&lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=mongodallasrb-090602212347-phpapp02&amp;stripped_title=mongodb-ruby-document-store-that-doesnt-rhyme-with-ouch"&gt;
&lt;param name="allowFullScreen" value="true"&gt;
&lt;param name="allowScriptAccess" value="always"&gt;
&lt;embed src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=mongodallasrb-090602212347-phpapp02&amp;stripped_title=mongodb-ruby-document-store-that-doesnt-rhyme-with-ouch" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div style="font-size:11px;font-family:tahoma,arial;height:26px;padding-top:2px;"&gt;View more &lt;a style="text-decoration:underline;" href="http://www.slideshare.net/"&gt;PDF documents&lt;/a&gt; from &lt;a style="text-decoration:underline;" href="http://www.slideshare.net/pengwynn"&gt;Wynn Netherland&lt;/a&gt;.&lt;/div&gt;
&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=2EYRyTmqzGg:Wdc2lKkagFQ:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=2EYRyTmqzGg:Wdc2lKkagFQ:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=2EYRyTmqzGg:Wdc2lKkagFQ:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=2EYRyTmqzGg:Wdc2lKkagFQ:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=2EYRyTmqzGg:Wdc2lKkagFQ:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</description><link>http://feedproxy.google.com/~r/locomotivation/~3/2EYRyTmqzGg/117119353</link><guid isPermaLink="false">http://locomotivation.squeejee.com/post/117119353</guid><pubDate>Tue, 02 Jun 2009 22:30:27 -0400</pubDate><category>mongodb</category><category>dallasrb</category><category>ruby</category><category>database</category><feedburner:origLink>http://locomotivation.squeejee.com/post/117119353</feedburner:origLink></item><item><title>Same train, new station</title><description>&lt;p&gt;At RailsConf recently we met a lot of folks who gave us kudos on this blog. Many were surprised to learn that Locomotivation is brought to you by the &lt;a href="http://squeejee.com"&gt;Squeejee &lt;/a&gt; team.&lt;/p&gt;

&lt;p&gt;So, we’ve decided to integrate our code blog a bit more with our &lt;a href="http://squeejee.com"&gt;web site&lt;/a&gt;. Sorry if the move caused your RSS reader to panic. Hopefully, once the dust settles we’ll have more regular content from our crazy ride on Rails.&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=WBXqA_Xquj8:ra0TXi8d6Y8:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=WBXqA_Xquj8:ra0TXi8d6Y8:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=WBXqA_Xquj8:ra0TXi8d6Y8:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=WBXqA_Xquj8:ra0TXi8d6Y8:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=WBXqA_Xquj8:ra0TXi8d6Y8:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</description><link>http://feedproxy.google.com/~r/locomotivation/~3/WBXqA_Xquj8/116258144</link><guid isPermaLink="false">http://locomotivation.squeejee.com/post/116258144</guid><pubDate>Mon, 01 Jun 2009 10:18:22 -0400</pubDate><feedburner:origLink>http://locomotivation.squeejee.com/post/116258144</feedburner:origLink></item><item><title>Installing CouchDB From Source On Mac OS X</title><description>&lt;p&gt;Wynn and I started dabbling with &lt;a href="http://couchdb.apache.org/"&gt;CouchDB&lt;/a&gt; this week.  As with any technology, step one when dabbling is installing the software.&lt;/p&gt;

&lt;p&gt;For Mac OS X Leopard Intel users, there is a very nice CouchDB packaged zip file that can be downloaded from the &lt;a href="http://code.google.com/p/couchprojects/downloads/list"&gt;Couch Projects Google Code site&lt;/a&gt;.  The huge positive of going this route is that it is drop dead simple to install.  Simply download the .zip file, extract the code, and double-click the “CouchDBX” file.  You then simply click the “Play” icon and you are off and running with CouchDB.&lt;/p&gt;

&lt;p&gt;The negative going this route is that, as the time of this blog post, this package runs CouchDB version 0.8.1 which is about 7 months old.&lt;/p&gt;

&lt;p&gt;In order to run the new 0.9 version of CouchDB, you will need to pull the latest source code from the SVN trunk, install some prereqs, and compile from source.  Here are the exact steps I took to get up and running.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;NOTE:  For details on each of these steps, you can look at the README file that is downloaded with the couchdb source code.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;From the command line:&lt;/p&gt;

&lt;script src="http://gist.github.com/113174.js"&gt;&lt;/script&gt;&lt;p&gt;Browse to the following URL in a browser to view your CouchDB Futon admin tool:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://127.0.0.1:5984/_utils/"&gt;&lt;a href="http://127.0.0.1:5984/_utils/"&gt;http://127.0.0.1:5984/_utils/&lt;/a&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=farBxPx18zk:fcSN_Y0ULbg:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=farBxPx18zk:fcSN_Y0ULbg:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=farBxPx18zk:fcSN_Y0ULbg:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=farBxPx18zk:fcSN_Y0ULbg:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=farBxPx18zk:fcSN_Y0ULbg:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</description><link>http://feedproxy.google.com/~r/locomotivation/~3/farBxPx18zk/109180933</link><guid isPermaLink="false">http://locomotivation.squeejee.com/post/109180933</guid><pubDate>Thu, 12 Mar 2009 00:00:00 -0400</pubDate><category>couchdb</category><feedburner:origLink>http://locomotivation.squeejee.com/post/109180933</feedburner:origLink></item><item><title>Ruby on Rails Integration Testing with Integrity</title><description>&lt;p&gt;I did some research on what was new in the Continuous Integration testing field in regards to Ruby this week.  We have used &lt;a href="http://cruisecontrolrb.thoughtworks.com/"&gt;CruiseControl.rb&lt;/a&gt; in the past which worked fine, but I remember the setup being a bigger pain than I thought it had to be.  I was hoping for something simpler.&lt;/p&gt;

&lt;p&gt;With this in mind, I decided to give &lt;a href="http://integrityapp.com/"&gt;Integrity&lt;/a&gt; a shot.  Their title of “The Easy and Fun Automated Continuous Integration Server” was exactly what I was looking for.  After installing, configuring, and playing around with it for the past day or so, I have not been disappointed.&lt;/p&gt;

&lt;p&gt;With that being said, I did have to jump around to a few different sources to get everything running correctly on a server with &lt;a href="http://www.modrails.com/"&gt;Phusion Passenger&lt;/a&gt;, so I thought I would document the steps I had to take here.&lt;/p&gt;

&lt;h3&gt;Installation:&lt;/h3&gt;

&lt;script src="http://gist.github.com/113276.js"&gt;&lt;/script&gt;&lt;p&gt;This will create an Integrity application directory with a couple files on your server, mainly config.yml, config.ru, and a “public” directory we will point our Apache virtual host to.&lt;/p&gt;

&lt;p&gt;Edit config.yml to suit your needs.  This file is very will commented and your edits should be self-explanatory.  I kept the default Sqlite database configuration to keep things simple.  However, due to this, I had to install Sqlite on our Ubuntu server via:&lt;/p&gt;

&lt;script src="http://gist.github.com/113277.js"&gt;&lt;/script&gt;&lt;p&gt;Setup a DNS entry to point to this server and create an Apache virtual host as you would any other Rails or Sinatra web application:&lt;/p&gt;

&lt;script src="http://gist.github.com/113278.js"&gt;&lt;/script&gt;&lt;p&gt;Once this is all complete, simply restart apache to access your new Integrity instance.&lt;/p&gt;

&lt;h3&gt;Project Creation:&lt;/h3&gt;

&lt;h4&gt;Create a Test Database:&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;(You can skip this test if you can use Sqlite databases for testing.  Our code inevitably has MySql specific SQL syntax in it which requires us to use MySql for our tests.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Create a MySql user that will be used for Integrity tests:&lt;/p&gt;

&lt;script src="http://gist.github.com/113279.js"&gt;&lt;/script&gt;&lt;p&gt;Note that “IDENTIFIED BY PASSWORD” is optional.  If it is not included, your user can log in without a password.&lt;/p&gt;

&lt;h4&gt;Project Creation:&lt;/h4&gt;

&lt;p&gt;Assuming the above was successful, you should now be able to browse to the Integrity URL you created. From here, click the “Add new project” link.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Name:&lt;/em&gt;  Unique name for the project you will be tracking&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Git Repo:&lt;/em&gt;  .git URL of the project you will be tracking.  For example, for an internal project on GitHub, the url would be something like:  git@github.com:acme/my_cool_application.git&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Branch:&lt;/em&gt;  The git branch Integrity should be tracking&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Build Script:&lt;/em&gt;  The rake command to use for testing after the code is pulled into your CI (Continuous Integration) server.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rake spec (for rspec)&lt;/li&gt;
&lt;li&gt;rake test (for test unit)&lt;/li&gt;
&lt;li&gt;rake features (for Cucumber)&lt;/li&gt;
&lt;li&gt;etc&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Customizing Your Build Script&lt;/h4&gt;

&lt;p&gt;The build script can be a tad bit tricky.  It is our practice not to keep database.yml in Git for password security purposes.  Because of this, Integrity will not know the test database configuration settings needed to run the tests.  To get around this issue, we created a database.yml.test file with the db credentials Integrity would need to run a test similar to this:&lt;/p&gt;

&lt;p&gt;Using MySql:&lt;/p&gt;

&lt;script src="http://gist.github.com/113280.js"&gt;&lt;/script&gt;&lt;p&gt;Using Sqlite:&lt;/p&gt;

&lt;script src="http://gist.github.com/113281.js"&gt;&lt;/script&gt;&lt;p&gt;In my Rails project, I then created the below custom Rake task in “lib/tasks/my_cool_application.rake”:&lt;/p&gt;

&lt;script src="http://gist.github.com/113282.js"&gt;&lt;/script&gt;&lt;p&gt;Once this is done and the files are added/committed/pushed to git, go back to your edit project page in Integrity and change your “Build Script” to be &lt;em&gt;&lt;em&gt;“rake test:move_test_db_config features”&lt;/em&gt;&lt;/em&gt; for Cucumber tests, for example.&lt;/p&gt;

&lt;h4&gt;Manual Build&lt;/h4&gt;

&lt;p&gt;After all of the above is complete, you should be able to click the “manual build” link for your new project to pull your code and run your tests!&lt;/p&gt;

&lt;h3&gt;Automated Tests Via Git Service Hooks&lt;/h3&gt;

&lt;p&gt;Integrity tests are automated with Git Service Hooks.  Adding a service hook is drop-dead simple with GitHub.  To do so:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Browse to your project in GitHub&lt;/li&gt;
&lt;li&gt;Click on the “Admin” tab&lt;/li&gt;
&lt;li&gt;Click on “Service Hooks” in the header&lt;/li&gt;
&lt;li&gt;In the “Post-Receive URLs” box, add a URL similar to this:

&lt;ul&gt;
&lt;li&gt;http://username:password@integrity.example.com/my_cool_application/push&lt;/li&gt;
&lt;li&gt;where “username” and “password” are the HTTP authentication credentials setup in the integrity/config.yml file&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once this is setup, your CI Integrity test will be run everytime you code is pushed to the appropriate branch in GitHub.&lt;/p&gt;

&lt;h3&gt;Notifiers&lt;/h3&gt;

&lt;p&gt;Integrity also provides a &lt;a href="http://integrityapp.com/notifiers"&gt;notification system&lt;/a&gt; to inform you when tests are run.&lt;/p&gt;

&lt;p&gt;Current notifiers include email, Jabber, Campfire, IRC, Twitter, and Basecamp.  These are installed independently of Integrity via Gems.  Once a gem is installed on your CI server, just log back into your project within your Integrity web site and setup your notifier credentials.  You are also free to write your own custom notifiers for applications that are not supported yet.&lt;/p&gt;

&lt;h3&gt;Resources:&lt;/h3&gt;

&lt;p&gt;Thanks to the following sites for assisting me with my original setup and this blog post:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://integrityapp.com/"&gt;Integrity web site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://morethanseven.net/2008/12/28/local-continuous-integration-integrity/"&gt;Local Continuous Integration from MoreThanSeven.net&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://github.com/foca/integrity/tree/master"&gt;GitHub Project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://groups.google.com/group/integrityapp"&gt;Google Group&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=MCCD-dX5f4w:nVvZSb3qB0o:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=MCCD-dX5f4w:nVvZSb3qB0o:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=MCCD-dX5f4w:nVvZSb3qB0o:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=MCCD-dX5f4w:nVvZSb3qB0o:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=MCCD-dX5f4w:nVvZSb3qB0o:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</description><link>http://feedproxy.google.com/~r/locomotivation/~3/MCCD-dX5f4w/109227509</link><guid isPermaLink="false">http://locomotivation.squeejee.com/post/109227509</guid><pubDate>Wed, 25 Feb 2009 00:00:00 -0500</pubDate><category>integrity</category><category>ruby</category><category>continuous integration</category><category>testing</category><feedburner:origLink>http://locomotivation.squeejee.com/post/109227509</feedburner:origLink></item><item><title>Workstreaming with Present.ly</title><description>&lt;p&gt;Having fled the cubicle jungle of Corporate America, we have a strong distaste for status reports. That’s not to say we don’t believe in letting others on the team know what we’re doing. We just prefer to &lt;a href="http://webworkerdaily.com/2007/03/03/workstreaming-the-new-face-time/"&gt;workstream&lt;/a&gt; our activities and let our work announce itself to the rest of the team.&lt;/p&gt;

&lt;h3&gt;
&lt;a href="http://present.ly"&gt;Present.ly&lt;/a&gt; - our workstreaming tool&lt;/h3&gt;

&lt;p&gt;I hate to put &lt;a href="http://present.ly"&gt;Present.ly&lt;/a&gt; in a box by calling it “Twitter for Business” (because it is so much more than that), but that’s the easiest way to describe it.&lt;/p&gt;

&lt;h4&gt;Familiar Twitter feel&lt;/h4&gt;

&lt;p&gt;If you’re comfortable using Twitter, you’ll feel at home with Present.ly because it supports @replies, direct messages, and hashtags.&lt;/p&gt;

&lt;h4&gt;Questions and urgent messages&lt;/h4&gt;

&lt;p&gt;Present.ly adds some smarts to the display of messages or “tweets.” Messages that end in a question mark are displayed as questions to the group: &lt;img src="http://assets.squeejee.com/locomotivation/images/question.png" alt="question mark"/&gt;&lt;/p&gt;

&lt;p&gt;&lt;br style="clear: both"/&gt;&lt;/p&gt;

&lt;p&gt;Need to mark a message as urgent? Simply add three exclamation marks: &lt;img src="http://assets.squeejee.com/locomotivation/images/urgent.png" alt="urgent"/&gt;&lt;/p&gt;

&lt;p&gt;&lt;br style="clear: both"/&gt;&lt;/p&gt;

&lt;h4&gt;Private and secure&lt;/h4&gt;

&lt;p&gt;Unlike Twitter, only your users see your messages.&lt;/p&gt;

&lt;h4&gt;Groups!&lt;/h4&gt;

&lt;p&gt;I really hope Twitter builds functionality for groups one day and when they do, I hope they rip off Present.ly’s implementation. Admins control who belongs to what groups. Perhaps the coolest feature of Present.ly groups is the broadcast. Broadcasts are like direct messages to a group except they are displayed as if sent from the group itself, which gets its own avatar. The following broadcast message&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;b system Elvis has left the building
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;would display as if it came from the System itself: &lt;img src="http://assets.squeejee.com/locomotivation/images/broadcast.png" alt="broadcast"/&gt;&lt;/p&gt;

&lt;p&gt;&lt;br style="clear: both"/&gt;&lt;/p&gt;

&lt;h3&gt;How we use it&lt;/h3&gt;

&lt;p&gt;Much of our Present.ly use is still manual. We tweet each other questions and comments just like we do on Twitter. In fact, Present.ly let’s you cross-post to twitter by using the t! command:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;t! This message would go to Twitter, too!
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But, increasingly, we’ve automated much of our workstreaming by sending broadcast messages at certain points in our development cycle.&lt;/p&gt;

&lt;h4&gt;Source control checkins&lt;/h4&gt;

&lt;p&gt;One of the most important things we do as a development team is add code to source control. We use “GitHub”:http://github.com for our code repositories. GitHub has a nifty feature called Service Hooks, which allows you to send a message to a number of third party services for code commits to your repo. Setting up Present.ly really easy:&lt;/p&gt;

&lt;p&gt;&lt;img src="http://assets.squeejee.com/locomotivation/images/posts/github-presently-1.png" alt="github presently"/&gt;&lt;br style="clear: both"/&gt;&lt;img src="http://assets.squeejee.com/locomotivation/images/posts/github-presently-2.png" alt="github presently"/&gt;&lt;br style="clear: both"/&gt;&lt;img src="http://assets.squeejee.com/locomotivation/images/posts/github-presently-3.png" alt="github presently"/&gt;&lt;br style="clear: both"/&gt;&lt;/p&gt;

&lt;p&gt;Once set up, GitHub will send a new broadcast message from the group you specify with each new code push. The message includes the commit message and a link to the commit back on GitHub:&lt;/p&gt;

&lt;p&gt;&lt;img src="http://assets.squeejee.com/locomotivation/images/posts/github-presently-4.png" alt="github presently"/&gt;&lt;br style="clear: both"/&gt;&lt;/p&gt;

&lt;h5&gt;Deploy notices&lt;/h5&gt;

&lt;p&gt;Before Present.ly we had been sending our deployment notices to &lt;a href="http://www.campfirenow.com"&gt;Campfire&lt;/a&gt; using &lt;a href="http://www.locomotivation.com/blog/2007/09/29/heat-up-your-campfire-with-tinder.html"&gt;Tinder&lt;/a&gt;. While this works well, we really preferred to have our workstream in one spot and leav eour Campfires for discussion and chat. Since Present.ly implements the Twitter API, wiring up the deploy notices from &lt;a href="http://www.capify.org/"&gt;Capistrano&lt;/a&gt; was a piece of cake.&lt;/p&gt;

&lt;script src="http://gist.github.com/113292.js"&gt;&lt;/script&gt;&lt;p&gt;Now everybody knows when the build is done:&lt;/p&gt;

&lt;p&gt;&lt;img src="http://assets.squeejee.com/locomotivation/images/posts/deploy-presently.png" alt="deploy presntly"/&gt;&lt;/p&gt;

&lt;p&gt;&lt;br style="clear: both"/&gt;&lt;/p&gt;

&lt;h4&gt;How are you using it?&lt;/h4&gt;

&lt;p&gt;We’d love to hear how you’re using Present.ly in your workflow? Any cool uses we’re missing?&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=kuTF4RjPdlM:8uvSqh9ULxA:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=kuTF4RjPdlM:8uvSqh9ULxA:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=kuTF4RjPdlM:8uvSqh9ULxA:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=kuTF4RjPdlM:8uvSqh9ULxA:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=kuTF4RjPdlM:8uvSqh9ULxA:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</description><link>http://feedproxy.google.com/~r/locomotivation/~3/kuTF4RjPdlM/109235014</link><guid isPermaLink="false">http://locomotivation.squeejee.com/post/109235014</guid><pubDate>Sat, 20 Dec 2008 00:00:00 -0500</pubDate><category>workstreaming</category><category>present.ly</category><category>github</category><feedburner:origLink>http://locomotivation.squeejee.com/post/109235014</feedburner:origLink></item><item><title>People, sites, and software I am thankful for this year</title><description>&lt;p&gt;I’ve been building web applications for a dozen years now and I’m convinced &lt;em&gt;there has never been a better time to be a web developer!&lt;/em&gt; I can’t imagine doing my job if it weren’t for the impact of some great people, sites, and software. This post isn’t a &lt;a href="http://nettuts.com/articles/web-roundups/10-biggest-milestones-in-web-development/"&gt;Ten Biggest&lt;/a&gt; style article. I won’t be thanking &lt;a href="http://en.wikipedia.org/wiki/Tim_Berners-Lee"&gt;Tim Berners-Lee&lt;/a&gt;, although we all should.&lt;/p&gt;

&lt;p&gt;Rather, this list is simply the &lt;strong&gt;extra&lt;/strong&gt;ordinary people and things that have made my job a joy over the past year.&lt;/p&gt;

&lt;h3&gt;People I am thankful for&lt;/h3&gt;

&lt;h4&gt;&lt;a href="http://ejohn.org/"&gt;John Resig&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;Creator of &lt;a href="http://jquery.com/"&gt;jQuery&lt;/a&gt;, JavaScript ninja, John casts a large shadow on the web development landscape. Looking at John’s code has taught me more about JavaScript than every book I’ve ever bought on the subject.&lt;/p&gt;

&lt;h4&gt;&lt;a href="http://ozmm.org/"&gt;Chris Wanstrath aka defunkt&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;Half of the &lt;a href="http://errtheblog.com/"&gt;err the blog&lt;/a&gt; duo, a third of the &lt;a href="http://github.com"&gt;GitHub posse&lt;/a&gt;, Chris is the most energetic Rubyist I’ve seen. Just watching the amount of software his company puts out will either inspire you or send you into a shame spiral.&lt;/p&gt;

&lt;h4&gt;&lt;a href="http://b.lesseverything.com/"&gt;The Less Everything guys, Steve and Allan&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;Makers of &lt;a href="http://www.lessaccounting.com/"&gt;cool&lt;/a&gt;, &lt;a href="http://lovdbyless.com/"&gt;free&lt;/a&gt;, and &lt;a href="http://weallhatequickbooks.com/"&gt;downright funny&lt;/a&gt; software, these guys write more &lt;em&gt;less&lt;/em&gt; code than anybody around. I’m thankful that guys like this are around to keep us from taking ourselves too seriously.&lt;/p&gt;

&lt;h4&gt;&lt;a href="http://www.redmine.org"&gt;Jean-Philippe Lang&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;I’ve never seen anyone single-handedly will a project into existence like JP. While there is no doubt that Redmine has a large and growing community behind it, I’m sure everyone would agree that Redmine exists because of the sheer will of Jean-Phillipe.&lt;/p&gt;

&lt;h3&gt;Sites I am thankful for&lt;/h3&gt;

&lt;h4&gt;&lt;a href="http://github.com"&gt;GitHub&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;No other technology has impacted me more this year than &lt;a href="http://git.or.cz/"&gt;Git&lt;/a&gt;, and I don’t think it would have happened without GitHub.  We first signed up for GitHub because we wanted a turnkey central repository for our projects. But GitHub is so much more. GitHub brings social networking to source control. This means you can see what other folks are working on and discover new projects and tools all the time. It also means it’s never been as easy to contribute to open source. Want to improve an open source project? With GitHub, you just fork it and send a pull request. If the original owner goes off the grid, no worries, you’ve got your own repo and keep on chugging.&lt;/p&gt;

&lt;h4&gt;&lt;a href="http://twitter.com"&gt;Twitter&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;I’ll have to admit at first I didn’t get Twitter. I mean, take a look at the &lt;a href="http://twitter.com/public_timeline"&gt;public timeline&lt;/a&gt;. Who cares when random folk are &lt;a href="http://twitter.com/sambam87/status/1026603120"&gt;bathing&lt;/a&gt;? I’ve since learned that there is no better tool for finding those other folks that are just as weird as you. You know, those long lost members of your tribe(s). Folks who are interested in those things you bug everyone else about.&lt;/p&gt;

&lt;h4&gt;&lt;a href="http://presentlyapp.com"&gt;Presently&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;Soon after I climbed on the Twitter train, I longed for something as easy as Twitter to workstream all those source control checkin notices, build statuses, and “what I am doing” updates. The folks at &lt;a href="http://www.intridea.com/"&gt;Intridea&lt;/a&gt; have hit a homerun with Present.ly. The groups and hashtag functionality is awesome. Try it for your team!&lt;/p&gt;

&lt;h3&gt;Software I am thankful for&lt;/h3&gt;

&lt;h4&gt;&lt;a href="http://redmine.org"&gt;Redmine&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;We’ve used a lot of issue trackers. Some &lt;a href="http://unfuddle.com"&gt;great ones&lt;/a&gt;. Some &lt;a href="http://trac.edgewall.org/"&gt;workhorses&lt;/a&gt;. But we’ve never used anything as extensible as Redmine. Plugins, themes, and custom fields, oh my!&lt;/p&gt;

&lt;h4&gt;&lt;a href="http://webby.rubyforge.org"&gt;Webby&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;I want to thank &lt;a href="http://github.com/TwP/webby/tree/master"&gt;Tim Pease&lt;/a&gt; for building such a cool little tool. I’m using Webby to write this post!&lt;/p&gt;

&lt;h4&gt;&lt;a href="http://www.blueprintcss.org/"&gt;Blueprint CSS&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;Blueprint has been a part of my standard toolbox since it came out. Blueprint brings sanity to my layout and typography while make it so easy developers can do CSS!&lt;/p&gt;

&lt;p&gt;This year I’ll be looking to &lt;a href="http://github.com/chriseppstein/compass/tree/master"&gt;Compass&lt;/a&gt; to semantify my Blueprint.&lt;/p&gt;

&lt;h4&gt;&lt;a href="http://jquery.com"&gt;jQuery&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;I wasn’t in the market for a new JavaScript framework when I discovered jQuery, but I quickly fell in love with this one. jQuery lets me do more powerful things with less code than any other framework I’ve used. With comprehensive CSS3 (and bonus) selectors, chaining, and plugin architecture it’s a joy to use.&lt;/p&gt;

&lt;h3&gt;How about you?&lt;/h3&gt;

&lt;p&gt;What are the people, sites, and projects you’re thankful for this year?&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=zd6DhK-g7fw:ylTv-reRyaY:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=zd6DhK-g7fw:ylTv-reRyaY:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=zd6DhK-g7fw:ylTv-reRyaY:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=zd6DhK-g7fw:ylTv-reRyaY:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=zd6DhK-g7fw:ylTv-reRyaY:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</description><link>http://feedproxy.google.com/~r/locomotivation/~3/zd6DhK-g7fw/109247775</link><guid isPermaLink="false">http://locomotivation.squeejee.com/post/109247775</guid><pubDate>Thu, 27 Nov 2008 00:00:00 -0500</pubDate><category>thanksgiving</category><feedburner:origLink>http://locomotivation.squeejee.com/post/109247775</feedburner:origLink></item><item><title>Javascript and Ruby Frameworks at Lone Star Ruby Conf 2008</title><description>&lt;p&gt;&lt;a href="http://www.flickr.com/photos/27640669@N02/2831498316/" style="float: right; border: none; background: #fff"&gt;&lt;img class="frame" src="http://farm4.static.flickr.com/3071/2831498316_4b8822eeeb_m.jpg" alt="Wynn giving a talk at Lone Star Ruby Conf 2008"/&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I had the opportunity to give a talk at Lone Star Ruby Conf 2008. There are a lot of hardcore Rubyists here writing great web applications in Rails, Merb, and Waves. It was fun to knock around ideas with so many folks about how Javascript fits into the mix.&lt;/p&gt;

&lt;div id="__ss_585749" style="width: 425px; text-align: left;"&gt;
&lt;object style="margin:0px" width="425" height="355"&gt;&lt;param name="movie" value="http://static.slideshare.net/swf/ssplayer2.swf?doc=javascriptandrubyframworks-1220732647167244-8&amp;stripped_title=javascript-and-ruby-frameworks-presentation"&gt;
&lt;param name="allowFullScreen" value="true"&gt;
&lt;param name="allowScriptAccess" value="always"&gt;
&lt;embed src="http://static.slideshare.net/swf/ssplayer2.swf?doc=javascriptandrubyframworks-1220732647167244-8&amp;stripped_title=javascript-and-ruby-frameworks-presentation" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div style="font-size: 11px; font-family: tahoma,arial; height: 26px; padding-top: 2px;"&gt;View SlideShare &lt;a href="http://www.slideshare.net/pengwynn/javascript-and-ruby-frameworks-presentation?src=embed" title="View Javascript And Ruby Frameworks on SlideShare" style="text-decoration: underline;"&gt;presentation&lt;/a&gt; or &lt;a href="http://www.slideshare.net/upload?src=embed" style="text-decoration: underline;"&gt;Upload&lt;/a&gt; your own. (tags: &lt;a href="http://slideshare.net/tag/ruby" style="text-decoration: underline;"&gt;ruby&lt;/a&gt; &lt;a href="http://slideshare.net/tag/javascript" style="text-decoration: underline;"&gt;javascript&lt;/a&gt;)&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Until the slides are posted on the &lt;a href="http://www.lonestarrubyconf.com/"&gt;official &lt;span class="caps"&gt;LSRC&lt;/span&gt; site&lt;/a&gt; you can grab them from &lt;a href="http://www.slideshare.net/pengwynn/javascript-and-ruby-frameworks-presentation"&gt;SlideShare&lt;/a&gt;.&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=SQgHhYkXRlM:IFONzEJuYL0:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=SQgHhYkXRlM:IFONzEJuYL0:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=SQgHhYkXRlM:IFONzEJuYL0:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=SQgHhYkXRlM:IFONzEJuYL0:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=SQgHhYkXRlM:IFONzEJuYL0:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</description><link>http://feedproxy.google.com/~r/locomotivation/~3/SQgHhYkXRlM/109260735</link><guid isPermaLink="false">http://locomotivation.squeejee.com/post/109260735</guid><pubDate>Sun, 07 Sep 2008 00:00:00 -0400</pubDate><category>lsrc</category><category>presentations</category><feedburner:origLink>http://locomotivation.squeejee.com/post/109260735</feedburner:origLink></item><item><title>Simplify ActiveRecord Aggregates and Other Goodies via named_scope</title><description>&lt;p&gt;We’ve been huge fans of named_scope since its introduction in Rails 2.1.  If you are new to the named_scope concept, please read &lt;a href="http://ryandaigle.com/articles/2008/3/24/what-s-new-in-edge-rails-has-finder-functionality"&gt;Ryan Daigle’s intro to named_scope&lt;/a&gt; and watch Ryan Bates’ &lt;a href="http://railscasts.com/episodes/108"&gt;named_scope RailsCast&lt;/a&gt;.  Ryan Daigle also had a good follow up article &lt;a href="http://ryandaigle.com/articles/2008/8/20/named-scope-it-s-not-just-for-conditions-ya-know"&gt;explaining his utility scope gem&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With a recent project, we really started utilizing extensions with named_scope which is very powerful and cleaned up our code considerably.&lt;/p&gt;

&lt;p&gt;For example, let’s say you have an issue tracking system that has an Entry model that belongs_to both a User model and a Project model.  Using named_scope, we can easily access Entries for a date range filtered to both the Project and User level using named_scope lambdas:&lt;/p&gt;

&lt;script src="http://gist.github.com/113283.js"&gt;&lt;/script&gt;&lt;p&gt;To find the Entries for the last week for the first user, we would simply do:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;u = User.first
u.entries.by_date(1.week.ago, Time.now)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To find the Entries for the last week for the first project, we would simply do:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;p = Project.first
p.entries.by_date(1.week.ago, Time.now)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;What happens if we want to see the sum of hours worked for a date range?  This is where extensions come in very handy.  Let’s add a “total_duration” extension to our named_scope:&lt;/p&gt;

&lt;script src="http://gist.github.com/113284.js"&gt;&lt;/script&gt;&lt;p&gt;Now to get the duration for the past week for the first user we simply do:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;u = User.first
u.entries.by_date(1.week.ago, Time.now).total_duration
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;with similar logic at the project level.&lt;/p&gt;

&lt;p&gt;To group all of the entries by project, we would do:&lt;/p&gt;

&lt;script src="http://gist.github.com/113285.js"&gt;&lt;/script&gt;&lt;p&gt;If you need the lambda arguments inside your named_scope extension, you will need to use “self.proxy_options”.  For example, let’s say you need the from and to dates in order to calculate the average duration per weekday.  That code would look something like this:&lt;/p&gt;

&lt;script src="http://gist.github.com/113287.js"&gt;&lt;/script&gt;&lt;p&gt;In the above example, “weekday_count” is a custom method we created to calculate the number of weekdays between 2 given dates.&lt;/p&gt;

&lt;p&gt;The opportunities to simplify code using named_scope is great.  As I was writing this post, one of our team members, Jason Derrett, created a named_scope to wrap a Xapian full text search into a Lesson model:&lt;/p&gt;

&lt;script src="http://gist.github.com/113288.js"&gt;&lt;/script&gt;&lt;p&gt;In this example, using Xapian to search for a keyword becomes as simple as:&lt;/p&gt;

&lt;pre&gt;&lt;code class="ruby"&gt;Lesson.find_with_xapian "fishing"&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;How cool is that?!?&lt;/p&gt;

&lt;p&gt;Are there any other cool named_scope tricks out there that any of you are using?&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=yzfov3og-hQ:fAwvRa0ZOj0:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=yzfov3og-hQ:fAwvRa0ZOj0:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=yzfov3og-hQ:fAwvRa0ZOj0:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=yzfov3og-hQ:fAwvRa0ZOj0:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=yzfov3og-hQ:fAwvRa0ZOj0:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</description><link>http://feedproxy.google.com/~r/locomotivation/~3/yzfov3og-hQ/109282292</link><guid isPermaLink="false">http://locomotivation.squeejee.com/post/109282292</guid><pubDate>Mon, 25 Aug 2008 00:00:00 -0400</pubDate><category>rails</category><category>active record</category><category>database</category><feedburner:origLink>http://locomotivation.squeejee.com/post/109282292</feedburner:origLink></item><item><title>Simple Ruby on Rails Full Text Search Using Xapian</title><description>&lt;p&gt;In my &lt;a href="http://locomotivation.com/2008/07/15/mulling-over-our-ruby-on-rails-full-text-search-options"&gt;previous blog post&lt;/a&gt;, I wrote about the several full-text search engines that are available to us as Rails programmers and how we ultimately decided to go with &lt;a href="http://www.xapian.com/"&gt;Xapian&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;Background&lt;/h3&gt;

&lt;p&gt;I am by no means a Xapian expert.  I first heard about it less than a week ago.  However, we have been very impressed with its combination of ease of installation and robust features.&lt;/p&gt;

&lt;p&gt;Xapian is written in C++ and has been around in its current form since 2001.  Its code base is derived from a previous search tool &lt;a href="http://www.xapian.org/history.php"&gt;that went closed source.&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Written in C++&lt;/li&gt;
  &lt;li&gt;
&lt;a href="http://www.xapian.org/features.php"&gt;Feature rich&lt;/a&gt; including relevance feedback, boolean search operators, stemming, wildcard searching, and similar results&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Its &lt;a href="http://www.xapian.org/users.php"&gt;user list&lt;/a&gt; includes &lt;a href="http://localhost:3000/del.icio.us"&gt;del.icio.us&lt;/a&gt; and &lt;a href="http://localhost:3000/www.gmane.org"&gt;GMane&lt;/a&gt; both of which are indexing tens of millions of records, so scalability should not be an issue. Here is a &lt;a href="http://www.xapian.org/docs/scalability.html"&gt;quote about scalability from the site&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;People often want to know how Xapian will scale. The short answer is “very well” - a previous version of the software
  powered BrightStation’s Webtop search engine, which offered a search over around 500 million web pages (around 1.5 terabytes of database files).&lt;/p&gt;
  
  &lt;p&gt;Searches took less than a second.&lt;/p&gt;
  
  &lt;p&gt;The largest recent installation we’re aware of is probably gmane, which currently indexes over 50 million mail messages.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On the other hand, indexing time could take a while if you have a huge set of documents.  According to &lt;a href="http://users.softlab.ece.ntua.gr/~ttsiod/buildWikipediaOfflhttp://xapian.org/users.php#commwebine.html"&gt;this article&lt;/a&gt;, it took about 5 hours to index the entire &lt;a href="http://www.wikipedia.org/"&gt;Wikipedia&lt;/a&gt; database using Xapian for offline Wikipedia access and searching.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://github.com/frabcus/acts_as_xapian/tree/master"&gt;Acts_As_Xapian&lt;/a&gt; is the Rails plugin for the Xapian.  It was written in May 2008 by Francis Irving after he experienced some frustrations with &lt;a href="http://lucene.apache.org/solr/"&gt;Solr&lt;/a&gt;.  Acts_As_Xapian has a google group at &lt;a href="http://groups.google.com/group/acts_as_xapian."&gt;http://groups.google.com/group/acts_as_xapian.&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Xapian Installation:&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;For Ubuntu Users:&lt;/em&gt;&lt;/p&gt;

&lt;pre&gt;sudo apt-get install libxapian15 libxapian-ruby1.8&lt;/pre&gt;

&lt;p&gt;&lt;em&gt;For Mac / Other: (taken from the &lt;a href="http://www.xapian.org/docs/install.html"&gt;xapian.org install docs&lt;/a&gt;&lt;/em&gt;)&lt;/p&gt;

&lt;p&gt;(version = 1.0.6 at the time of this writing)&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;wget &lt;a href="http://oligarchy.co.uk/xapian/1.0.6/xapian-core-1.0.6.tar.gz"&gt;http://oligarchy.co.uk/xapian/1.0.6/xapian-core-1.0.6.tar.gz&lt;/a&gt;
wget &lt;a href="http://oligarchy.co.uk/xapian/1.0.6/xapian-bindings-1.0.6.tar.gz"&gt;http://oligarchy.co.uk/xapian/1.0.6/xapian-bindings-1.0.6.tar.gz&lt;/a&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Alternately if you haven’t installed wget on the mac:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;curl -O &lt;a href="http://oligarchy.co.uk/xapian/1.0.6/xapian-core-1.0.6.tar.gz"&gt;http://oligarchy.co.uk/xapian/1.0.6/xapian-core-1.0.6.tar.gz&lt;/a&gt;
curl -O &lt;a href="http://oligarchy.co.uk/xapian/1.0.6/xapian-bindings-1.0.6.tar.gz"&gt;http://oligarchy.co.uk/xapian/1.0.6/xapian-bindings-1.0.6.tar.gz&lt;/a&gt;

tar zxvf xapian-core-versionnumber.tar.gz
tar zxvf xapian-bindings-versionnumber.tar.gz

cd xapian-core-versionnumber
./configure --prefix=/opt
make
sudo make install
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you don’t have root access to install Xapian, you can specify a prefix in your home directory, for example:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;./configure—prefix=/home/jenny/xapian-install


cd xapian-bindings-&lt;version&gt;
./configure XAPIAN_CONFIG=/opt/bin/xapian-config
make
sudo make install
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Acts_As_Xapian Installation:&lt;/h3&gt;

&lt;p&gt;Install the plugin:
    script/plugin install git://github.com/frabcus/acts_as_xapian.git&lt;/p&gt;

&lt;p&gt;Generate and execute the acts_as_xapian migration:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;script/generate acts_as_xapian
rake db:migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Basic Model Code&lt;/h3&gt;

&lt;p&gt;To include fields to your index, you will need to add an “acts_as_xapian” call to the model you want to index.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;class Lesson &lt; ActiveRecord::Base
  acts_as_xapian :texts =&gt; [:name, :description]
end
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In this case, only the data found in the “name” and “description” attributes of our Lesson model will be searched.&lt;/p&gt;

&lt;h3&gt;A Quick Test Of Your Index&lt;/h3&gt;

&lt;p&gt;At this point, you can quickly test that your Xapian install, plugin, and model code are all working together via the following rake commands:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;To build the index:&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;rake xapian:rebuild_index models="Lesson" RAILS_ENV=development&lt;/pre&gt;

&lt;p&gt;&lt;em&gt;(You can add more models with a space delimeter.  “Lesson User Tag”)&lt;/em&gt;
&lt;strong&gt;To update index:&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;rake xapian:update_index RAILS_ENV=development&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;To test index:&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;rake xapian:query models="Lesson" query="golf" RAILS_ENV=development&lt;/pre&gt;

&lt;h3&gt;The Basic Search&lt;/h3&gt;

&lt;p&gt;Adding the this line of code
    @search = ActsAsXapian::Search.new([Lesson], @lesson_search_params)&lt;/p&gt;

&lt;p&gt;will return a ActsAsXapian::Search object with the following (From the acts_as_xapian &lt;a href="http://github.com/frabcus/acts_as_xapian/wikis"&gt;&lt;span class="caps"&gt;README&lt;/span&gt;&lt;/a&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;description – a techy one, to check how the query has been parsed&lt;/li&gt;
  &lt;li&gt;matches_estimated – a guesstimate at the total number of hits&lt;/li&gt;
  &lt;li&gt;spelling_correction – the corrected query string if there is a correction, otherwise nil&lt;/li&gt;
  &lt;li&gt;words_to_highlight – list of words for you to highlight, perhaps with TextHelper::highlight&lt;/li&gt;
  &lt;li&gt;results – an array of hashes each containing:
&lt;ul&gt;
&lt;li&gt;:model – your Rails model, this is what you most want!&lt;/li&gt;
  &lt;li&gt;:weight – relevancy measure&lt;/li&gt;
  &lt;li&gt;:percent – the weight as a %, 0 meaning the item did not match the query at all&lt;/li&gt;
  &lt;li&gt;:collapse_count – number of results with the same prefix, if you specified collapse_by_prefix&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, if we want to return an ActiveRecord model object we could do something like this:
    @lessons = @search.results.collect {|r| r[:model]}&lt;/p&gt;

&lt;p&gt;To get the recommended spelling for a misspelled word(s) in a search, we would do:
    @corrections = @search.spelling_correction&lt;/p&gt;

&lt;h3&gt;Similar Results&lt;/h3&gt;

&lt;p&gt;If you have a requirement to show similar or “You may also like” results based on a search result set or a single result, this can be done with a single line of code:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;@similar_lessons = ActsAsXapian::Similar.new([Lesson], @lessons).results.collect {|r| r[:model]}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;where @lessons in this case would be the list of lessons returned in the original search.&lt;/p&gt;

&lt;h3&gt;Updating The Index&lt;/h3&gt;

&lt;p&gt;Unfortunately, one drawback to Acts_As_Xapian is that the indexers are not updated automatically when data in an indexed model is added, modified, or deleted.  When model data is changed, Xapian will put a record in the table ‘acts_as_xapian_jobs’ in your database to notify the update task to update the index.  A cron job or something of the like will be needed to call the following rake task periodically to keep the index up-to-date:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;rake xapian:update_index
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Francis, the acts_as_xapian creator, commented on this on my original &lt;a href="http://locomotivation.com/2008/07/15/mulling-over-our-ruby-on-rails-full-text-search-options"&gt;Mulling Over Our Ruby On Rails Full Text Search Options&lt;/a&gt; blog post.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;with Xapian you can update and search simultaneously, and updates are immediate. However, only one thread can update a Xapian database at the same time. Since I wanted offline indexing anyway (as my index operation is risky, complex and slow, involving parsing Word documents, PDFs etc.), I didnâ€™t try to find a solution that causes a second thread in the web application to, say, wait for the database lock. So acts_as_xapian currently only supports offline indexing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;This is a very basic tutorial meant to highlight the steps needed to get up and running with simple Xapian searches quickly.  There are quite a few topics that I did not touch on such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sorting and grouping with the :values option in the model&lt;/li&gt;
  &lt;li&gt;Advanced searching with the :terms option in the model&lt;/li&gt;
  &lt;li&gt;Adding extended attributes from other models into a searched index.  (Such as adding lesson comments to the Lesson index)&lt;/li&gt;
  &lt;li&gt;Filtering searched data (in our case, we had to filter our results so only “active” lessons were returned)&lt;/li&gt;
  &lt;li&gt;Highlighting keywords in the search results&lt;/li&gt;
&lt;/ul&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=dOIQfZS8-hY:Vup6SKX7HeY:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=dOIQfZS8-hY:Vup6SKX7HeY:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=dOIQfZS8-hY:Vup6SKX7HeY:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/locomotivation?a=dOIQfZS8-hY:Vup6SKX7HeY:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/locomotivation?i=dOIQfZS8-hY:Vup6SKX7HeY:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</description><link>http://feedproxy.google.com/~r/locomotivation/~3/dOIQfZS8-hY/109279130</link><guid isPermaLink="false">http://locomotivation.squeejee.com/post/109279130</guid><pubDate>Wed, 23 Jul 2008 00:00:00 -0400</pubDate><category>xapian</category><category>rails</category><category>search</category><feedburner:origLink>http://locomotivation.squeejee.com/post/109279130</feedburner:origLink></item></channel></rss>
