<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-294680397865315179</id><updated>2012-01-17T12:34:25.503-06:00</updated><category term='Plugins'/><category term='Static Typing'/><category term='3D modeling'/><category term='web'/><category term='UI'/><category term='hosting'/><category term='Windows'/><category term='iteration'/><category term='ASP.NET'/><category term='Environment'/><category term='cool stuff'/><category term='Customer Service'/><category term='ergonomics'/><category term='code coverage'/><category term='git'/><category term='Mac'/><category term='performance'/><category term='training'/><category term='coding discipline'/><category term='Dynamic Typing'/><category term='startups'/><category term='scripting'/><category term='Enterprise Development'/><category term='IE7'/><category term='Time Management'/><category term='SMS'/><category term='business'/><category term='CSS'/><category term='java'/><category term='refactoring'/><category term='Images'/><category term='Rails'/><category term='Arc'/><category term='rants'/><category term='college'/><category term='philosophy'/><category term='Best Practices'/><category term='employment'/><category term='twelve-hour'/><category term='Development Practices'/><category term='tutorials'/><category term='software'/><category term='Cron'/><category term='HTML'/><category term='voice recognition'/><category term='NHibernate'/><category term='RCov'/><category term='fun'/><category term='testing'/><category term='RTP'/><category term='gotcha'/><category term='svn'/><category term='recursion'/><category term='.NET'/><category term='users'/><category term='Analytics'/><category term='education'/><category term='Twitter'/><category term='Microsoft'/><category term='javascript'/><category term='Rails 2.2'/><category term='iTunesU'/><category term='Error Notification'/><category term='Security'/><category term='Lisp'/><category term='Sales'/><category term='source version control'/><category term='Blackberry'/><category term='consulting'/><category term='Centurytel'/><category term='Zetassociates'/><category term='code'/><category term='Rake'/><category term='debug'/><category term='other'/><category term='personal'/><category term='REST'/><category term='Office'/><category term='BackgroundJob'/><category term='IMAP'/><category term='Rails Source'/><category term='DB4O'/><category term='monitoring'/><category term='Rails 2.1'/><category term='service review'/><category term='Generics'/><category term='web services'/><category term='Algorithms'/><category term='hackers'/><category term='Google'/><category term='code reading'/><category term='Blogging'/><category term='life'/><category term='C#'/><category term='named_scope'/><category term='Open-source'/><category term='infrastructure'/><category term='mobile development'/><category term='Ruby'/><category term='Database'/><category term='administration'/><category term='languages'/><category term='investment'/><category term='Tools'/><category term='Hardware'/><category term='career'/><category term='Hoptoad'/><category term='Books'/><title type='text'>Ethan's Software Blog</title><subtitle type='html'>#{catchy_title.generate!}</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default?start-index=101&amp;max-results=100'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>192</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-4452248565077449992</id><published>2011-03-22T21:46:00.000-05:00</published><updated>2011-03-22T21:46:31.742-05:00</updated><title type='text'>Inspired</title><content type='html'>I've been reading way more than writing lately, and my current favorite blogger has got to be &lt;a href="http://www.skorks.com/about/"&gt;Alan Skorkin&lt;/a&gt;.  In particular, I'm a fan of two of his articles, one &lt;a href="http://www.skorks.com/2010/04/on-the-value-of-fundamentals-in-software-development/"&gt;on the value of fundamentals&lt;/a&gt;, and one &lt;a href="http://www.skorks.com/2011/02/the-greatest-developer-fallacy-or-the-wisest-words-youll-ever-hear/"&gt;on the "I'll learn it when I need it" attitude&lt;/a&gt;.  I love his writing, but more importantly, I'm convicted by his arguments.&lt;br /&gt;&lt;br /&gt;With respect to these points, I'm making it a point to encourage my growth at least a little each day, starting right now by taking Alan's advice from the former article and exploring the API of the Array class in Ruby (a class probably used more often than any other in my day to day work).  See my next post for anything I might learn...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-4452248565077449992?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/4452248565077449992/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=4452248565077449992' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/4452248565077449992'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/4452248565077449992'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2011/03/inspired.html' title='Inspired'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-1388863739522017622</id><published>2010-11-22T00:15:00.000-06:00</published><updated>2010-11-22T00:15:36.532-06:00</updated><title type='text'>Tag-it 0.3.1 is released!</title><content type='html'>If you've been a consistent reader, you know that last month I released a new gem called &lt;a href="http://github.com/evizitei/tag-it"&gt;"tag-it"&lt;/a&gt; to interface with RFID receivers and the tags that come into range with them.  Today, there's a new version with a new feature.&lt;br /&gt;&lt;br /&gt;In version 0.2.x, there was 1 primary class for continually monitoring a port for tag events.  This doesn't do everything I wanted though, because in some cases I really just needed to know what tags were in range at one precise instant.  The long-running class really wasn't a good fit, so there's a new class available called TagIt::TagSnapshot which has a "shoot!" method that returns an array of the tag-names currently found:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;  require "tag_it"&lt;br /&gt;  port = SerialPort.new("/dev/tty.yourport",{:baud=&gt;9600})&lt;br /&gt;  snapshot = TagIt::TagSnapshot.new(port)&lt;br /&gt;  tags = snapshot.shoot!&lt;br /&gt;  # =&gt; ["1nri","1okD","1nrP"]&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Get the latest:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;  gem install tag-it&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-1388863739522017622?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/1388863739522017622/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=1388863739522017622' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/1388863739522017622'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/1388863739522017622'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/11/tag-it-031-is-released.html' title='Tag-it 0.3.1 is released!'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-3640399158329115024</id><published>2010-11-16T08:57:00.001-06:00</published><updated>2010-11-16T09:53:50.732-06:00</updated><title type='text'>json (_pure) ruins my morning</title><content type='html'>&lt;pre&gt;wrong argument type JSON::Pure::Generator::State (expected Data)&lt;/pre&gt;&lt;br /&gt;This is what I saw staring back at me from my console this morning when I checked out a project and ran my specs.  Specs that had been passing just fine yesterday.  The only thing I could think of was that I had added a couple new gems to my gem file, but since I hadn't used them yet surely they couldn't be breaking my test suite?&lt;br /&gt;&lt;br /&gt;Well, if you've been a coder for any length of time, by now you should know that the likelihood of a given event contributing to your code failure is inversely proportional to your initial index of suspicion of it's relation.&lt;br /&gt;&lt;br /&gt;Fortunately, I didn't have to search long.  Googling for "wrong argument type JSON::Pure::Generator::State" quickly turned up &lt;a href="http://prettystatemachine.blogspot.com/2010/09/typeerrors-in-tojson-make-me-briefly.html"&gt;this blog post&lt;/a&gt; on prettystatemachine that explains the problem.  Those gems I'd added?  One had "json_pure" as a dependency, so I'll include my own brief version for reference:&lt;br /&gt;&lt;br /&gt;ActiveSupport and json_pure both hook into the "to_json" method. ActiveSupport defines to_json for many simple types, but expects fixnum's "to_json" to be handled by Object.  json_pure is intercepting that call and not getting the data it wants.  Defining "to_json" on fixnum seems to solve the problem.&lt;br /&gt;&lt;br /&gt;Hence, I've created an initializer in my rails app called "./initializers/json_patch.rb" and put the following code in it:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;class Fixnum&lt;br /&gt;  def to_json(options = nil)&lt;br /&gt;    to_s&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Specs pass.  I would thank "prettystatemachine" by name, but I can't find any reference to the author of the blog.  Whoever you are, thanks man.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-3640399158329115024?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/3640399158329115024/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=3640399158329115024' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/3640399158329115024'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/3640399158329115024'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/11/json-pure-ruins-my-morning.html' title='json (_pure) ruins my morning'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-3971071151900991533</id><published>2010-11-13T23:58:00.001-06:00</published><updated>2010-11-13T23:59:13.234-06:00</updated><title type='text'>Hello node</title><content type='html'>I've been playing a bit with node.js recently, but have had trouble getting my friends to try it out.  They hate javascript, can't see how this framework is any different, won't even give it a look, and further more evented programming sucks.  &lt;br /&gt;&lt;br /&gt;Well, everyone is entitled to their opinion, but you can't really have a valid opinion without the information.  Node.js and other event driven models (EventMachine in ruby, etc) are going to continue to be a huge deal as more applications require more realtime information to be pushed between client and server, let's at least know a little about the tools that are probably carrying the future on their actively-under-development shoulders.&lt;br /&gt;&lt;br /&gt;In this spirit, I've put together a very simple "hello world" application in Express (web framework for node.js), available on my github profile here: &lt;a href="https://github.com/evizitei/Hello-Node"&gt;Hello Node&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;You see how nice I am?  You don't even have to write your own hello world, I just did it for you, all you have to do is check out the code and read through all 4 files therein to get a feeling for why this new tool really isn't all that unfamiliar compared to whatever web framework you're working with now.  No fan-boy over-hyped blog post about it's infinite scalability, no drooling and sobbing over how wonderful it is, just a few blocks of harmless code that wants to be your friend.  Still hate everything about it?  Fine, but at least you'll have some ground to stand on from legitimately having taken a look.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-3971071151900991533?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/3971071151900991533/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=3971071151900991533' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/3971071151900991533'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/3971071151900991533'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/11/hello-node.html' title='Hello node'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-653404397357031008</id><published>2010-11-13T15:27:00.002-06:00</published><updated>2010-11-13T15:37:34.135-06:00</updated><title type='text'>Testing Heroku's SMS addon with Cucumber</title><content type='html'>(&lt;strong&gt;UPDATE:&lt;/strong&gt; All code on this page is also available as a gist here: &lt;a href="https://gist.github.com/675659"&gt;GIST&lt;/a&gt;)&lt;br /&gt;&lt;br /&gt;I love Heroku's add-ons.&lt;br /&gt;&lt;br /&gt;I love Cucumber testing.&lt;br /&gt;&lt;br /&gt;But I sometimes hate putting the two together. &amp;nbsp;How do you test the infrastructure you don't know much about? &amp;nbsp;I've been confronted with this recently with the "moonshado-sms" addon, which is a super-simple way to hook your application up to a cheap sms gateway. &amp;nbsp;My integration tests were difficult, though. &amp;nbsp;How do I check that everything got sent appropriately? &amp;nbsp;That the SMS went to the right person, with the right number, and the right message?&lt;br /&gt;&lt;br /&gt;I stumbled a bit, but came up with something that I think is pretty clever, and I'm going to share it here in case you have the same insatiable desire to see a series of green dots before deploying that I do. &amp;nbsp;In my env.rb file (for cucumber), at the bottom, I include some monkey patching of Moonshado's API:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;module Moonshado&lt;br /&gt;&amp;nbsp;&amp;nbsp;class Sms&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;cattr_accessor :sent_messages&lt;br /&gt;&amp;nbsp;&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;def deliver_sms&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;raise MoonshadoSMSException.new("Invalid message") unless is_message_valid?(@message)&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;data = {:sms =&amp;gt; {:device_address =&amp;gt; format_number(@number), :message =&amp;gt; @message.to_s}}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;self.class.sent_messages ||= []&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;self.class.sent_messages &amp;lt;&amp;lt; data&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;response = RestClient::Response.create('{"stat":"ok","id":"sms_id_mock"}', "", {})&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;parse(response.to_s)&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;rescue MoonshadoSMSException =&amp;gt; exception&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;raise exception&lt;br /&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;end&lt;br /&gt;&amp;nbsp;&amp;nbsp;end&lt;br /&gt;end&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Now mix in some clever step defenitions with "sms_steps.rb" in your cucumber "step_definitions" folder:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;When /^the sms message "([^"]*)" is sent to "([^"]*)"$/ do |number, message|&lt;br /&gt;  Moonshado::Sms.new(message,number).deliver_sms&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;Then /^there should be an SMS sent to "([^"]*)" saying "([^"]*)"$/ do |number, sms_text|&lt;br /&gt;  messages = Moonshado::Sms.sent_messages&lt;br /&gt;  messages = Moonshado::Sms.sent_messages.select{|data| &lt;br /&gt;    data[:sms][:device_address] == number and data[:sms][:message] == sms_text&lt;br /&gt;  }&lt;br /&gt;  messages.size.should == 1&lt;br /&gt;  messages.each{|msg| Moonshado::Sms.sent_messages.delete(msg)}&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;Then /^there should be no SMS messages sent$/ do &lt;br /&gt;  if Moonshado::Sms.sent_messages&lt;br /&gt;    Moonshado::Sms.sent_messages.size.should == 0&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Take and edit for your own purposes, and may your code always be well tested.&lt;br /&gt;&lt;br /&gt;(PS: All code on this page is also available as a gist here: &lt;a href="https://gist.github.com/675659"&gt;GIST&lt;/a&gt;)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-653404397357031008?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/653404397357031008/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=653404397357031008' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/653404397357031008'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/653404397357031008'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/11/testing-herokus-sms-addon-with-cucumber.html' title='Testing Heroku&apos;s SMS addon with Cucumber'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-7761758224522126020</id><published>2010-11-13T11:19:00.001-06:00</published><updated>2010-11-13T11:26:29.803-06:00</updated><title type='text'>Changing Gears</title><content type='html'>Sometimes opportunities come into your life that seem too good to pass up.  That happened to me this week when a conversation with the "higher-ups" at &lt;a href="http://www.12spokes.com/"&gt;12spokes Web Development&lt;/a&gt; turned into an offer.  &lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://www.12spokes.com/images/12-spokes-logo.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://www.12spokes.com/images/12-spokes-logo.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Circumstances are such that this is a good time for me to make a move like that, so I was happy to accept, and for the foreseeable future that's where I'll be contributing the bulk of my Ruby on Rails skills.  I've got a good vibe from this company, and am really looking forward to branching out onto bigger projects, so keep watching for any knowledge gems I pick up as I come up to speed on the current projects being worked on at this shop.&lt;br /&gt;&lt;br /&gt;And by the way, if you're looking for a web development shop to build your next application....well, I know a guy.  :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-7761758224522126020?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/7761758224522126020/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=7761758224522126020' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/7761758224522126020'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/7761758224522126020'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/11/changing-gears.html' title='Changing Gears'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-2131079920990476319</id><published>2010-11-08T10:36:00.004-06:00</published><updated>2010-11-08T10:39:07.741-06:00</updated><title type='text'>Vim, Day 2</title><content type='html'>As I mentioned in a &lt;a href="http://blog.ethanvizitei.com/2010/11/vim-at-last.html"&gt;previous post&lt;/a&gt;, I recently started trying to use Vim instead of textmate as my default editor, with mixed results.  &lt;br /&gt;&lt;br /&gt;Are some of the command shortcuts awesome?  Yes.  I love "dd" for deleting a full line, and "o" or "O" for inserting a new line above or below where I am.  But that doesn't totally make up for a loss of direction when it comes to moving between files;  that's what I DIDN'T love.&lt;br /&gt;&lt;br /&gt;Fortunately, one of my friends forwarded me &lt;a href="http://yehudakatz.com/2010/07/29/everyone-who-tried-to-convince-me-to-use-vim-was-wrong/"&gt;this blog post by Yehuda Katz&lt;/a&gt;, where he gives a great summary of his experience in switching into Vim incrementally, but which also (most importantly to me) mentioned the &lt;a href="http://www.vim.org/scripts/script.php?script_id=1658"&gt;"NERDTree" plugin&lt;/a&gt;. &lt;br /&gt;&lt;br /&gt;If you don't know about it (I didn't) it gives you a "tree" interface to the directory you specify (I'm  using my project home directory) and lets you use a mouse interface or a series of handy shortcuts to navigate.  There's nothing else holding me back.&lt;br /&gt;&lt;br /&gt;Now I'm sold.  Powerful text editing, workable project drawer, I'm a Vim advocate from today forward.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-2131079920990476319?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/2131079920990476319/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=2131079920990476319' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/2131079920990476319'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/2131079920990476319'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/11/vim-day-2.html' title='Vim, Day 2'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-840800566115105310</id><published>2010-11-07T17:24:00.000-06:00</published><updated>2010-11-07T17:24:53.487-06:00</updated><title type='text'>R you programming?</title><content type='html'>My wife is in school again getting her masters, and one of the classes she's having to take is statistics.  An interesting side effect of this is that she's having to do something that she never thought she would:  Programming!  Yes, her class is using SPSS, but at home she's starting to work with the "R" programming language, and I'm frankly fascinated.&lt;br /&gt;&lt;br /&gt;I'm so excited about sharing something technical with my spouse for the first time, that I'm downloading R onto my laptop myself to learn along with her, and I've bought "R in a Nutshell" from the O'Reilly book series to help me along the way.   In this post, I'll be cataloguing some of my first impressions as I play with this new (to me) language.&lt;br /&gt;&lt;br /&gt;1) R is interpreted, has a REPL, and so reminds me of when I first started learning Ruby or LISP.&lt;br /&gt;&lt;br /&gt;2) R uses vectors a lot, which is expected for a language designed for statistical analysis.  Although initially I was thinking of them like an Array, in truth they're different and in some cases a little more powerful than arrays in other programming languages.  Vectors provide advanced indexing (integers, ranges, mapped selecting functions) and vector arithmetic both in a concise format.&lt;br /&gt;&lt;br /&gt;3) functions in R are just objects.  This provides for an interesting source reading technique:&lt;br /&gt;&lt;pre&gt;&amp;gt; double_func = function(x,y,z){ c(x*2,y*2,z*2)}&lt;br /&gt;&amp;gt; double_func(2,4,6)&lt;br /&gt;[1] 4 8 12&lt;br /&gt;&amp;gt; double_func&lt;br /&gt;function(x,y,z){ c(x*2,y*2,z*2)}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;4) R also has a class for arrays (which is defined as multidimensional vectors), and although I've dealt with multidimensional arrays before, they feel totally different in this syntax:&lt;br /&gt;&lt;pre&gt;&amp;gt; a = array(data=c(1:100),dim=c(2,10,3,2))&lt;br /&gt;&amp;gt; a&lt;br /&gt;, , 1, 1&lt;br /&gt;&lt;br /&gt;     [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]&lt;br /&gt;[1,]    1    3    5    7    9   11   13   15   17    19&lt;br /&gt;[2,]    2    4    6    8   10   12   14   16   18    20&lt;br /&gt;&lt;br /&gt;, , 2, 1&lt;br /&gt;&lt;br /&gt;     [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]&lt;br /&gt;[1,]   21   23   25   27   29   31   33   35   37    39&lt;br /&gt;[2,]   22   24   26   28   30   32   34   36   38    40&lt;br /&gt;&lt;br /&gt;, , 3, 1&lt;br /&gt;&lt;br /&gt;     [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]&lt;br /&gt;[1,]   41   43   45   47   49   51   53   55   57    59&lt;br /&gt;[2,]   42   44   46   48   50   52   54   56   58    60&lt;br /&gt;&lt;br /&gt;, , 1, 2&lt;br /&gt;&lt;br /&gt;     [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]&lt;br /&gt;[1,]   61   63   65   67   69   71   73   75   77    79&lt;br /&gt;[2,]   62   64   66   68   70   72   74   76   78    80&lt;br /&gt;&lt;br /&gt;, , 2, 2&lt;br /&gt;&lt;br /&gt;     [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]&lt;br /&gt;[1,]   81   83   85   87   89   91   93   95   97    99&lt;br /&gt;[2,]   82   84   86   88   90   92   94   96   98   100&lt;br /&gt;&lt;br /&gt;, , 3, 2&lt;br /&gt;&lt;br /&gt;     [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]&lt;br /&gt;[1,]    1    3    5    7    9   11   13   15   17    19&lt;br /&gt;[2,]    2    4    6    8   10   12   14   16   18    20&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;5) "lists" in R are a little different in that they can have named components and can hold different object types.  Building a list in R almost feels like building a struct in C++ or Ruby:&lt;br /&gt;&lt;pre&gt;&amp;gt; car = list(make="Honda",model="CRV",mileage=1258)&lt;br /&gt;&amp;gt; car&lt;br /&gt;$make&lt;br /&gt;[1] "Honda"&lt;br /&gt;&lt;br /&gt;$model&lt;br /&gt;[1] "CRV"&lt;br /&gt;&lt;br /&gt;$mileage&lt;br /&gt;[1] 1258&lt;br /&gt;&lt;br /&gt;&amp;gt; car$make&lt;br /&gt;[1] "Honda"&lt;br /&gt;&amp;gt; car$model&lt;br /&gt;[1] "CRV"&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;6) Everything in R is an object, and a "class" is basically a certain type of function definition.  In this way, the language feels reminiscent of javascript, where there isn't really an alternate sytax for defining global functions and object classes.&lt;br /&gt;&lt;br /&gt;That's all for one day, don't want to cram in too much at one time.  But as an overall impression, I'm getting a feeling for why the language is so valued for statistics. Data is very easy to build and manipulate en masse, and I'm sure that although I haven't gotten there yet, there are probably many built in functions for analysis to help along the way.  Subscribe to my RSS feed if you want to get further updates as I dig deeper into the R language in weeks to come.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-840800566115105310?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/840800566115105310/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=840800566115105310' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/840800566115105310'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/840800566115105310'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/11/r-you-programming.html' title='R you programming?'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-1345409400411767741</id><published>2010-11-05T15:31:00.000-05:00</published><updated>2010-11-05T15:31:20.211-05:00</updated><title type='text'>Embedding Cramp in a Rails App, Part II</title><content type='html'>After my last post on getting "Hello World" running on Cramp alongside a full rails application, I thought I'd continue to catalogue my progress on actually getting a websockets action running and doing a real data push back to the client.&lt;br /&gt;&lt;br /&gt;First things first, the blog post by Pratik that introduces the concept is invaluable: http://m.onkey.org/2010/1/15/websockets-made-easy-with-cramp.&lt;br /&gt;&lt;br /&gt;Following the directions there, I created an initializer (config/initializers/cramp_server.rb for cramp and included the following:&lt;br /&gt;&lt;pre&gt;Cramp::Websocket.backend = :thin&lt;/pre&gt;&lt;br /&gt;Notice that this isn't quite the same as the blog post, because I'm using edge cramp, and it's been refactored to remove the "Controller" module.&lt;br /&gt;&lt;br /&gt;Anyway, rebooting the server everything still worked, so the next step was to rebuild my controller as a websockets aware rack application.&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;class ChatAction &lt; Cramp::Websocket&lt;br /&gt;  on_data :received_data&lt;br /&gt;&lt;br /&gt;  def received_data(data)&lt;br /&gt;    render "Got your #{data}"&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Which did indeed startup and respond to data requests from the client with an echo. Nice!&lt;br /&gt;&lt;br /&gt;However, in order to support IE users, it would be necessary to use flash sockets, and although it's not that hard to do, I'm  already getting uncomfortable with the number of things that are new to me in this app.  So, as a responsible design decision, I'm planning on just using Cramp for long-polling for the time being until better websockets support is available across all the major browsers.  &lt;br /&gt;&lt;br /&gt;Thanks Pratik for such a cool framework!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-1345409400411767741?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/1345409400411767741/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=1345409400411767741' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/1345409400411767741'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/1345409400411767741'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/11/embedding-cramp-in-rails-app-part-ii.html' title='Embedding Cramp in a Rails App, Part II'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-9145795545316848730</id><published>2010-11-05T12:33:00.001-05:00</published><updated>2010-11-05T12:37:07.677-05:00</updated><title type='text'>Embedding Cramp in a Rails App</title><content type='html'>So I have an application that would work great with &lt;a href="http://websockets.org/"&gt;websockets&lt;/a&gt; because I don't want to be doing client polling.  Rails doesn't deal with this right now, but &lt;a href="https://github.com/lifo/cramp"&gt;Cramp&lt;/a&gt; does (cramp is a young web framework built on top of EventMachine).  I know, I know, &lt;a href="http://nodejs.org/"&gt;Node.js&lt;/a&gt; right?  But I've already deployed on &lt;a href="http://www.heroku.com"&gt;Heroku&lt;/a&gt; and they aren't taking new participants for their Node.js beta, so I'm going to have to make it work.&lt;br /&gt;&lt;br /&gt;Now, Cramp is indeed a Rack-based framework (mostly, sometimes not compliant), so theoretically I should be able to do this, and if it works out you'll have this post to help you along the way (using Rails 3.0, BTW).&lt;br /&gt;&lt;br /&gt;First, I added Cramp to my gemfile (gem "cramp") and tried to bundle install it.  Failure.  The current cramp gem depends on arel (= 0.3.3), and Rails 3 of course need arel 1.1.0.&lt;br /&gt;&lt;br /&gt;To fix that, I tried loading edge cramp into my app by using the git repo in my gemfile:&lt;br /&gt;&lt;pre&gt;gem "cramp",:git=&gt;'git://github.com/lifo/cramp.git'&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;So far so good, at least the bundle installed correctly.&lt;br /&gt;&lt;br /&gt;Before going any farther, I also added "&lt;a href="http://code.macournoyer.com/thin/"&gt;thin&lt;/a&gt;" to my gemfile to use as my webserver in development, because in the Cramp documentation they point out that Cramp only works with Thin or Rainbows!.&lt;br /&gt;&lt;br /&gt;Now, to get a cramp application running.   I saw a great &lt;a href="http://railscasts.com/episodes/222-rack-in-rails-3"&gt;Screencast by Ryan Bates&lt;/a&gt; about Routing with Rack, so that's the approach I'm going to take using the "Hello World" example from &lt;a herf="http://m.onkey.org/2010/1/7/introducing-cramp"&gt;this blogpost&lt;/a&gt; (&lt;b&gt;IMPORTANT:&lt;/b&gt; Edge cramp does not use "Cramp::Controller::Action" anymore like in that blog post, Action is directly under Cramp now).  To clarify, I'm dropping the following class into the lib directory:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;pre&gt;class ChatAction &lt; Cramp::Action&lt;br /&gt;  on_start :send_hello_world&lt;br /&gt;&lt;br /&gt;  def send_hello_world&lt;br /&gt;    render "Hello World"&lt;br /&gt;    finish&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;And then tried to route a path to it (routes.rb):&lt;code&gt;&lt;pre&gt;MyApp::Application.routes.draw do&lt;br /&gt;  match "/chat"=&gt;ChatAction&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;Starting up the server, I got a failure due to an uninitialized constant (It doesn't know about my ChatAction yet).  I had to think about the right place to load it, because I haven't really traced the rails startup sequence before.  Looking in "application.rb", Bundler loads all it's gems after loading up rails, so I decided to drop in my requirement right after that (load all bundler gems, than load my Cramp application, then start with the rails app config):&lt;code&gt;&lt;pre&gt;require File.expand_path('../boot', __FILE__)&lt;br /&gt;&lt;br /&gt;require 'rails/all'&lt;br /&gt;&lt;br /&gt;Bundler.require(:default, Rails.env) if defined?(Bundler)&lt;br /&gt;require "lib/chat_action"&lt;br /&gt;&lt;br /&gt;module MyApp&lt;br /&gt;  class Application &lt; Rails::Application&lt;br /&gt;    config.autoload_paths += %W( #{config.root}/extras )&lt;br /&gt;    &lt;br /&gt;    config.encoding = "utf-8"&lt;br /&gt;&lt;br /&gt;    config.filter_parameters += [:password]&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;That seemed to work; at least everything booted without any more errors when I did this from the application home directory:&lt;pre&gt;rails server thin&lt;/pre&gt;&lt;br /&gt;and when I traveled to "localhost:3000/chat", I saw my "Hello World" render as expected.&lt;br /&gt;&lt;br /&gt;So there you have it, how to get a cramp application running in your Rails 3 application (in the patented "Stumble your way through" methodology).  Now to actually make it do something useful!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-9145795545316848730?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/9145795545316848730/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=9145795545316848730' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/9145795545316848730'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/9145795545316848730'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/11/embedding-cramp-in-rails-app.html' title='Embedding Cramp in a Rails App'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-2326657980366142536</id><published>2010-11-04T12:20:00.001-05:00</published><updated>2010-11-04T12:31:17.376-05:00</updated><title type='text'>Vim at last</title><content type='html'>I've been using TextMate for a long time, and I love it.  Starting my career in Visual Studio, the monster of all IDEs, I've gradually simplified over the years one small step at a time; First by moving to Eclipse, Than XCode, to netbeans, finally to TextMate, which I've been using for 3 years now.  However, over that time period I've watched friends of mine blaze away in their code with their mystical mouse-less ways on editors like EMACS and VIM.  Today I decided to dive in and try it out myself.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Setup&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Downloading the &lt;a href="http://code.google.com/p/macvim/"&gt;Mac OS X binary for MacVim&lt;/a&gt; from the google group is not really in the spirit of the game (if I was feeling hardcore today I'd compile it from the source on github repo), but it sure is fast for setup.  Uncompress, move the .app file into the Applications directory, and away you go (you can also move the "mvim" script that comes along with the download into a folder that's on your Path so you can open it from the command line any time; I did).  Of course, that's also when I realized that the console version of Vim comes standard on my Mac.  Oh well, use which ever you prefer.  &lt;br /&gt;&lt;br /&gt;&lt;b&gt;Learning&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;The first thing I notice is that nothing makes sense.  I can't seem to click anything. Than I remember that you aren't really supposed to be trying to click on things if VIM is your editor of choice, because it's keyboard focused.  Realizing I'm not going to figure this out myself, I opt for running "vimtutor" at the console which gives you a concise walkthrough on how to operate inside this strange program.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Results&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;...Time passes...&lt;br /&gt;&lt;br /&gt;Ok, so now I can move text around in VIM pretty comfortably, and when editing a single file, I'm pretty solid.  I'm happy with how short the commands are to do things that require a lot of highlighting and click-dragging in modern editors (delete a full line, jump a few words down the line, etc).  What I'm NOT comfortable with yet is jumping around to different files in a project, and the syntax highlighting is still unfamiliar to me.  However, the only way to GET used to it is to use it, so although it's going to hurt for the first couple days I'm sure, I think I have enough solid ground under my feet to make a go of it.(&lt;b&gt;EDIT:&lt;/b&gt; &lt;a href="http://rails.vim.tpope.net/"&gt;rails.vim&lt;/a&gt; makes a big difference for me)&lt;br /&gt;&lt;br /&gt;Stay tuned for updates on whether I find out that this really improves my coding or not.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-2326657980366142536?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/2326657980366142536/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=2326657980366142536' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/2326657980366142536'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/2326657980366142536'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/11/vim-at-last.html' title='Vim at last'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-1725024336631640560</id><published>2010-11-03T20:46:00.000-05:00</published><updated>2010-11-03T20:46:38.000-05:00</updated><title type='text'>The options Hash, a classic ruby pattern</title><content type='html'>If you use rails, or any of a thousand other ruby gems, you're familiar with using methods like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;phone = SweetPhone.new&lt;br /&gt;if caller.is_my_friend?&lt;br /&gt;  phone.ring!(:volume=&gt;6,:ringtone=&gt;:smooth_jazz)&lt;br /&gt;elsif caller.is_my_boss?&lt;br /&gt;  phone.ring!(:volume=&gt;11,:ringtone=&gt;:totally_obnoxious)&lt;br /&gt;else&lt;br /&gt;  phone.ring!&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;yes, the conditional could be constructed to just build the hash and only call the method in one place, I know; that's not today's point.  What I'm emphasizing is the fact that this "ring" method can just be called by itself, or with any of a series of options.  It's nice, because you have intelligent defaults, and easy configuration if necessary.&lt;br /&gt;&lt;br /&gt;The problem I see, is that even though this is the simplest of the basic ruby patterns that you see every day, and even though we're all more than happy to use these APIs in third-party libraries, I still run across code every now and then in home-brewed applications that looks like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;class LamePhone&lt;br /&gt;  def ring!(volume,ringtone,vibrate,max_rings,caller_id)&lt;br /&gt;    #...&lt;br /&gt;    #implementation&lt;br /&gt;    #...&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;and 80% of the code that calls these long-paramed functions uses the SAME SET OF PARAMETERS.  &lt;br /&gt;&lt;br /&gt;If you've been programming for a while, this isn't news to you, so disregard this post, but if you're new to ruby or software in general, take note: repeating yourself is a bad sign.  If you're sending the same message from 50 places in your code, and 40 of them use the same function list, it may be time to set some intelligent defaults.  To fix this anti-pattern, the options hash works beautifully:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;class SweetPhone&lt;br /&gt;  def ring!(options = {})&lt;br /&gt;    adjust_volume(options[:volume] || 6)&lt;br /&gt;    ringtone = pick_ringtone_from_category(options[:ringtone] || :all)&lt;br /&gt;    sound_the_ringer!(ringtone)&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-1725024336631640560?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/1725024336631640560/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=1725024336631640560' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/1725024336631640560'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/1725024336631640560'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/11/options-hash-classic-ruby-pattern.html' title='The options Hash, a classic ruby pattern'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-3551933518709538242</id><published>2010-10-29T14:26:00.000-05:00</published><updated>2010-10-29T14:26:26.434-05:00</updated><title type='text'>the whenever gem and EngineYard AppCloud</title><content type='html'>If you have any sort of jobs you need run on a regular basis for your rails app, I can highly recommend the &lt;a href="http://github.com/javan/whenever"&gt;whenever&lt;/a&gt; gem.  Make a schedule.rb file with all your cron jobs in a pleasent ruby DSL, it's lovely!  Every time you deploy, you can setup a hook in whatever deploy process you're using (chef, capistrano) to run the "Whenever" executable script and update your crontab. Sweet!&lt;br /&gt;&lt;br /&gt;But we've had a problem recently when we deployed our app to &lt;a href="http://cloud.engineyard.com"&gt;Engineyard AppCloud&lt;/a&gt;.  We put in a deploy hook to run the whenever script, so far so good.  The only problem was deployment wasn't the only time that we needed our crontab updated.  You see, as with most cloud setups, we could rebuild our cluster at anytime to make configuration changes or what have you, and it should pick up where it left off.  That would bring our code back, but our crontab would be squeaky clean (we found that out the hard way when we rebuilt our cluster and suddenly our billing jobs weren't running). &lt;br /&gt;&lt;br /&gt;Fortunately, engineyard uses Chef for their cluster configuration, and they provide you a way to apply &lt;a href="http://docs.engineyard.com/appcloud/howtos/customizations/custom-chef-recipes"&gt;Custom Chef Recipes&lt;/a&gt; to your environments that will do things during your cluster rebuild.  &lt;br /&gt;&lt;br /&gt;Taking advantage of this, I built a small custom recipe for "whenever" that simply executes the whenever script in your current app directory (only if it's a utility instance or a solo instance, so your app servers aren't running your big jobs).  Check it out at &lt;a href="http://gist.github.com/654216"&gt;this gist&lt;/a&gt;!&lt;br /&gt;&lt;br /&gt;Also, if you'd like to see it in it's native environment as part of your ey-cloud-recipes project, you can check out our public git repo &lt;a href="http://github.com/evizitei/ey-cloud-recipes"&gt;Here&lt;/a&gt; (recipe is in /cookbooks/whenever/recipes/default.rb).&lt;br /&gt;&lt;br /&gt;Cheers!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-3551933518709538242?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/3551933518709538242/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=3551933518709538242' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/3551933518709538242'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/3551933518709538242'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/10/whenever-gem-and-engineyard-appcloud.html' title='the whenever gem and EngineYard AppCloud'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-697728716077999566</id><published>2010-10-23T20:50:00.001-05:00</published><updated>2010-10-23T20:54:18.167-05:00</updated><title type='text'>tag-it 0.2.2 is released!</title><content type='html'>If you read this blog regularly, you know that last week I released a new gem called &lt;a href="http://github.com/evizitei/tag-it"&gt;"tag-it"&lt;/a&gt; to interface with RFID receivers and the tags that come into range with them.  Well, there's a hot new version.&lt;br /&gt;&lt;br /&gt;Before, there were only 2 events that were thrown out by the tag tracker; one when a tag arrived (came into range) and another whenever a tag departed.  The problem was, unless people were actually moving into and out of range of the receiver, there was nothing going on.  That's not really an inherently bad thing, but from my web-app's view it WAS very similar to what would happen if the client were totally non-functional (powered down, error'd out, etc).  In order to allow applications to be sure that nothing's happened, tag-it now also sends out a "pulse" event every 180 seconds (including an array of currently in range tags).  Get the latest with &lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;  gem install tag-it&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-697728716077999566?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/697728716077999566/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=697728716077999566' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/697728716077999566'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/697728716077999566'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/10/tag-it-022-is-released.html' title='tag-it 0.2.2 is released!'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-6690255439400032944</id><published>2010-10-19T10:09:00.000-05:00</published><updated>2010-10-19T10:09:35.459-05:00</updated><title type='text'>Arrival!</title><content type='html'>One of my most attainable dreams has finally come true, as my most recent gem was mentioned today on &lt;a href="http://ruby5.envylabs.com/episodes/123-episode-121-october-19-2010"&gt;Ruby 5&lt;/a&gt;.  I've been listening to the show since it's creation, and to hear my own name called out (albeit pronounced incorrectly) was a true joy.  :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-6690255439400032944?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/6690255439400032944/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=6690255439400032944' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/6690255439400032944'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/6690255439400032944'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/10/arrival.html' title='Arrival!'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-2383357280837170309</id><published>2010-10-18T14:55:00.001-05:00</published><updated>2010-10-18T15:02:47.810-05:00</updated><title type='text'>How One Person Unknowingly Destroyed our delayed_job queue</title><content type='html'>this post is one for the "aren't you glad you found this here on my blog due to some furious googling rather than trying to dig into it yourself" category.&lt;br /&gt;&lt;br /&gt;We use delayed_job for our background queue, and it's great.  You can do this really cool thing where you call "send_later" on any object, with the method you want eventually called, and it will queue it up for later execution.  Very lightweight.&lt;br /&gt;&lt;br /&gt;This is what we do for our reporting features.  A user selects a date range and which report they want to run, and it gets offloaded to the background queue, sent to them later as an email link to an S3 file.  &lt;br /&gt;&lt;br /&gt;Today, suddenly things aren't working.  Reports aren't coming through, in fact the whole queue has frozen, it's backed up to 85 jobs, and when we look at "top" on one of our EC2 instances, we can see the ruby processed dying as quickly as they spool up.  Shit.&lt;br /&gt;&lt;br /&gt;Frantically we search the logs.  Nothing.  Configuration?  Still the same, and looks good. We try to manually run a few jobs, no issues.  Finally I try spooling up a worker in process and telling it to work off the queue, and I see this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;ArgumentError: argument out of range&lt;br /&gt; from /usr/lib/ruby/1.8/yaml.rb:133:in `utc'&lt;br /&gt; from /usr/lib/ruby/1.8/yaml.rb:133:in `node_import'&lt;br /&gt; from /usr/lib/ruby/1.8/yaml.rb:133:in `load'&lt;br /&gt; from /usr/lib/ruby/1.8/yaml.rb:133:in `load'&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;From YAML?  Why?!&lt;br /&gt;&lt;br /&gt;Fortunately, we got some help from our friends at &lt;a href="http://www.engineyard.com/"&gt;EngineYard&lt;/a&gt; (we've always had top notch support from them) who pointed out &lt;a href="http://redmine.ruby-lang.org/issues/show/1391"&gt;this little beauty on the ruby bug list&lt;/a&gt;.  A bad date could cause YAML to fail hard, and this would be during the "deserialization" of our jobs (when the process loads them out of the database to decide which one to lock and run), so it would effectively hit any worker that came across that job, killing the process, effectively destroying our job queue by halting it in it's path. Upon investigation, we found that someone was indeed trying to run a report from 08/01/2010 to 10/01/12010.  Dammit!&lt;br /&gt;&lt;br /&gt;Deleting that job allowed our queue to get back up and start processing again (and since we use &lt;a href="http://www.engineyard.com/products/appcloud"&gt;AppCloud&lt;/a&gt;, we were able to spool up another utility instance to work off the now VERY long backlog of jobs).  Nevertheless, this now means we need to sanitize all of our report inputs, because it's not enough to be a VALID date (which technically that is, although far in the future), it also has to be within a reasonable distance from now (at least until we're using Ruby 1.9.2, in which this is fixed).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-2383357280837170309?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/2383357280837170309/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=2383357280837170309' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/2383357280837170309'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/2383357280837170309'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/10/how-one-person-unknowingly-destroyed.html' title='How One Person Unknowingly Destroyed our delayed_job queue'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-5170262052050041921</id><published>2010-10-18T08:24:00.000-05:00</published><updated>2010-10-18T08:24:39.526-05:00</updated><title type='text'>RFID in Ruby</title><content type='html'>Anyone who's been reading my blog recently know that I've been playing with some RFID pieces for a local project. The project itself isn't important, though I may go into that in another post.  For now, what I want to talk about is the gem I've written to help you get a project up and running with active RFID technology.  &lt;br /&gt;&lt;br /&gt;It was actually kind of fun to slog through the process of pulling off the input stream from this devices serial port, character by character, and try to put together something useful;  However, I wouldn't want to do it again.  Enter &lt;a href="http://github.com/evizitei/tag-it"&gt;tag-it&lt;/a&gt;, my newest micro-gem &lt;a href="http://rubygems.org/gems/tag-it"&gt;hosted on rubygems.org&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The problem was this:  I bought &lt;a href="http://cliste.sailwhatcom.com/active-rfid-rssi-receiver-serial"&gt;this RFID receiver&lt;/a&gt;, which works great.  It's got a built in serial-to-usb adapter, so as long as you have the right driver installed, you can just plug it into a USB port and it will report tag names and their Relative Signal Strength as they come into range (see my &lt;a href="http://blog.ethanvizitei.com/2010/10/real-world-integration.html"&gt;last post&lt;/a&gt; for details).  Then they just continue repeating all the in range tags every 2.5 seconds until they go out of range again.&lt;br /&gt;&lt;br /&gt;That's cool, but it needs to be filtered.  If you have a webservice tracking which tags are near which receivers, it's not performance friendly to just keep pinging the server over and over and over again with the same information (this tag is still here, this tag is still here, etc).  In my case, I really only care about a tag "arriving" (coming into range) and "departing" (moving out of range).  Thus, I built tag-it as a gem that sets up a class monitoring your serial port, and dispatching events as it decides they occur.  &lt;br /&gt;&lt;br /&gt;Example:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;require "tag_it"&lt;br /&gt;port = SerialPort.new("/dev/tty.yourport",{:baud=&gt;9600})&lt;br /&gt;tracker = TagIt::TagTracker.new(port)&lt;br /&gt;watcher = YourCustomWatcher.new&lt;br /&gt;tracher.add_observer(watcher)&lt;br /&gt;tracker.start!&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;tag-it uses the standard &lt;a href="http://ruby-doc.org/stdlib/libdoc/observer/rdoc/index.html"&gt;observer&lt;/a&gt; class to dispatch events through an "update" method defined on the watcher, and sends three parameters (tag_name, RSSI, and event).&lt;br /&gt;&lt;br /&gt;So, your watcher should be built like this:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;class YourCustomWatcher&lt;br /&gt;  def update(tag,strength,event)&lt;br /&gt;    if event == :tag_arrived&lt;br /&gt;      # do something because a tag has come into range&lt;br /&gt;    elsif event == :tag_departed&lt;br /&gt;      # do something because a tag has gone out of range&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Thus far, that's it. Couldn't be simpler (well, maybe it could, but the gem is opensourced on github, so if you COULD make it simpler, send me a patch!).&lt;br /&gt;&lt;br /&gt;So far this thing only interacts with this receiver (the one linked to above), but as I procure other sources of hardware, I hope to make this a pretty general library able to interact with whatever other input formats are available out there from other manufacturers devices.  &lt;br /&gt;&lt;br /&gt;Happy tagging!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-5170262052050041921?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/5170262052050041921/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=5170262052050041921' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/5170262052050041921'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/5170262052050041921'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/10/rfid-in-ruby.html' title='RFID in Ruby'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-771705643814840134</id><published>2010-10-17T16:18:00.001-05:00</published><updated>2010-10-17T16:19:13.378-05:00</updated><title type='text'>Real World Integration</title><content type='html'>As part of a new project, I'm playing with some RFID technology, and I decided to document my setup process here so I could duplicate it later if I needed to do it again on another computer. The idea?  use active RFID tags to track people as they come and go from a building.  Here are the steps for getting the tag processing up and running on my macbook pro: &lt;br /&gt;&lt;br /&gt;&lt;b&gt;0)&lt;/b&gt; buy RFID receiver and tags from http://cliste.sailwhatcom.com/&lt;br /&gt;&lt;br /&gt;&lt;b&gt;1)&lt;/b&gt; Download Mac OS X driver for the serial to usb adapter built into the receiver&lt;br /&gt;http://www.prolific.com.tw/eng/downloads.asp?ID=31. run the downloaded pkg file from the unzipped DMG. (will restart your computer)&lt;br /&gt;&lt;br /&gt;&lt;b&gt;2)&lt;/b&gt; make sure the receiver is sending data.  Plug it into a USB port, and you can use (on Mac OS X) "screen /dev/tty.usbserial 9600" to watch the port's data come across your screen.  (BTW, I was only able to receive telemetry from my RFID receiver from the USB port on the left side of my Macbook Pro's body.  When I hooked into the USB port to the right of the keyboard, I got nothing.  No idea why, just something to play with if you have trouble here).&lt;br /&gt;&lt;br /&gt;&lt;b&gt;3)&lt;/b&gt; Make sure you are using ruby 1.8.7, 1.9.x does not appear to work for this next gem &lt;br /&gt;&lt;br /&gt;&lt;b&gt;4)&lt;/b&gt; install the ruby-serialport gem with "gem install ruby-serialport"&lt;br /&gt;&lt;br /&gt;&lt;b&gt;5)&lt;/b&gt; write some code to read from the port, something like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;require 'rubygems'&lt;br /&gt;require 'bundler/setup'&lt;br /&gt;require 'serialport'&lt;br /&gt;&lt;br /&gt;port = SerialPort.new("/dev/tty.usbserial",:baud=&gt;9600,:data_bits=&gt;8,:stop_bits=&gt;1)&lt;br /&gt;&lt;br /&gt;count = 0&lt;br /&gt;while count &lt; 1000&lt;br /&gt;  printf("%c",port.getc)&lt;br /&gt;  count += 1&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;port.close&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;There's a simple program that just reads the tag names as they come in range, along with their relative signal strengths, and prints them to the console one character at a time.  Output when I have 2 Active RFID 20 meter transmitters sitting on the table next to the receiver looks something like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;1nri85 1nwP79 1nri85 1nwP79 1nri85 1nwP79 1nri85 1nwP79 1nri86 1nwP79 1nri85 1nwP79 1nri85 1nwP79 1nri85 1nwP79 1nri85 1nwP79 1nri85 1nwP78 1nri85 1nwP79 1nri85 1nwP79 1nri85 1nwP79 1nri85 1nwP78 1nri85 1nwP78 1nri85 1nwP79 1nri84 1nwP78 1nri84 1nwP79 1nri84 1nwP79 1nri85 1nwP79 1nri86 1nwP79 1nri85 1nwP79 1nri85 1nwP79 1nri86 1nwP79 1nri86 1nwP79 1nri85 1nwP80 1nri86 1nwP80 1nri87 1nwP80 1nri86 1nwP80 1nri85 1nwP79 1nri85 1nwP79 1nri85 1nwP81 1nri85 1nwP79 1nri85 1nwP79 1nri85 1nwP79 1nri86 1nwP79 1nri86 1nwP79 1nri85 1nwP79 1nri85 1nwP79 1nri85 1nwP80 1nri87 1nwP79 1nri88 1nwP79 1nri85 1nwP80 1nri85 1nwP80 1nri85 1nwP79 1nri86 1nwP80 1nri86 1nwP80 1nri86 1nwP78 1nri86 1nwP79 1nri85 1nwP79 1nri85 1nwP81 1nri85 1nwP79 1nri85 1nwP79 1nri86 1nwP78 1nri85 1nwP78 1nri85 1nwP78 1nri86 1nwP79 1nri85 1nwP78 1nri85 1nwP78 1nri85 1nwP78 1nri85 1nwP78 1nri85 1nwP78 1nri85 1nwP78 1nri86 1nwP79 1nri85 &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Note:  BE SURE TO CLOSE YOUR PORT!  On a couple occasions I've terminated the program without closing the port object, it seems to leave it open and any future attempts to connect with that port get bounced with a "resource busy" message.  Only a restart has solved that problem for me.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-771705643814840134?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/771705643814840134/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=771705643814840134' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/771705643814840134'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/771705643814840134'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/10/real-world-integration.html' title='Real World Integration'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-1666445689381139917</id><published>2010-10-15T15:51:00.000-05:00</published><updated>2010-10-15T15:51:51.047-05:00</updated><title type='text'>you will_paginate in FBML!</title><content type='html'>I hate writing facebook applications.  I don't know anyone who enjoys it.&lt;br /&gt;&lt;br /&gt;Nevertheless, it's a necessity in todays contracting marketplace, and sometimes we just have to live with the fact that we're going to write facebook apps.&lt;br /&gt;&lt;br /&gt;One of the big problems is that all these super useful gems and plugins we use to speed up our development when using ruby on rails (including elements of the rails stack itself) don't really work inside the facebook application paradigm (especially if you're using &lt;a href="http://developers.facebook.com/docs/reference/fbml/"&gt;FBML&lt;/a&gt;, which I don't want to, but one of the requirements for this app is a profile tab, and right now there are no other options).&lt;br /&gt;&lt;br /&gt;Today, I was saddened when my favorite pagination tool, &lt;a href="http://github.com/mislav/will_paginate/wiki"&gt;will_paginate&lt;/a&gt;, was rendered useless by the fact that when dealing with FBML, any links have to work according to the facebook routing scheme rather than rails's defaults.  It makes sense why, but by default will_paginate was generating URLs for my pagination links that pointed to my routes on the server, which are not the same as my routes within the facebook application (maybe I should have done some better configuration up front.  Doh!).  &lt;br /&gt;&lt;br /&gt;Normally I would start googling like crazy for a solution, but I read a very persuasive blog post yesterday by &lt;a href="http://twitter.com/jnunemaker"&gt;John Nunemaker&lt;/a&gt; on just &lt;a href="http://railstips.org/blog/archives/2010/10/14/stop-googling/"&gt;reading the code on gems you want to learn about rather than trying to google up their interface&lt;/a&gt;.  So (determined to at least try following the advice of an industry icon such as John), I installed gemedit, and opened up the will_paginate source for the 3.0 prerelease, and found that I could actually very easily customize how will_paginate's links get generated.  &lt;br /&gt;&lt;br /&gt;In will_paginate, you can specify when you call your link-generating-view-helper just what class you want to render the links.  The default is WillPaginate::ViewHelpers::LinkRenderer, which does a whole lot of nifty work with boxing up your pagination situation into a nice set of links (and is very well written for readability, in my opinion).  In my case, I just wanted to change how the urls were built, so I created a new class, subclassed the LinkRenderer, overrode just one method (the one that builds a link, cleverly named "link"), and reimplemented it to do the routing the way I needed it.   Then, in my view, I specified my custom renderer as the class to use for generating links, and presto, my favorite pagination gem is running again even under the arduous unpleasentries of working from within facebook.  See the code below:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;class FacebookLinkRenderer &lt; &lt;br /&gt;  WillPaginate::ViewHelpers::LinkRenderer&lt;br /&gt;&lt;br /&gt;  def link(text, target, attributes = {})&lt;br /&gt;    if target.is_a? Fixnum&lt;br /&gt;      attributes[:rel] = rel_value(target)&lt;br /&gt;      target = url(target)&lt;br /&gt;    end&lt;br /&gt;    target = target.gsub "/what_is_wrong","/what_is_right"&lt;br /&gt;    attributes[:href] = target&lt;br /&gt;    tag(:a, text, attributes)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;And in the view:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;#...haml haml haml....&lt;br /&gt;&lt;br /&gt;=will_paginate(@items,:renderer=&gt;"FacebookLinkRenderer")&lt;br /&gt;&lt;br /&gt;#...haml haml haml....&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Nice!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-1666445689381139917?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/1666445689381139917/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=1666445689381139917' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/1666445689381139917'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/1666445689381139917'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/10/you-willpaginate-in-fbml.html' title='you will_paginate in FBML!'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-279997288102984509</id><published>2010-10-13T19:07:00.001-05:00</published><updated>2010-10-13T19:08:47.028-05:00</updated><title type='text'>Classic ASP on a Mac</title><content type='html'>Although I am exclusively working on my own business most of the time, I do occasionally do some side contract work if it's to help out a friend or local organization.  Usually this involves doing a small web-application or some sort of data processing, but this week I got a unique request.  &lt;br /&gt;&lt;br /&gt;My local fire department is interested in redoing their web presence entirely.  I'm all game to help with that, but they also need some emergency help because their current application is flat out broken.  This means actually diving into their current code, and stabilizing it before we even talk about moving to a more current platform (cough...Ruby on Rails...).  &lt;br /&gt;&lt;br /&gt;Having been part of these rescue missions before, I know that the problems are usually deeper than the description: "there's just a few things that need tweaking and fixing, no big deal really".  In anticipation of this, I've already taken the code I copied off of their production server (yes, some organizations still do this), put it into a git repo, and checked it out to 2 separate computers.&lt;br /&gt;&lt;br /&gt;The further complication?  The entire app is in classic ASP.  And all three of my computers are Macs.  &lt;br /&gt;&lt;br /&gt;But I'm not the last person who will ever have this problem, so we're going to follow along as I get this dinosaur up and running here on my OS X 10.6 Macbook Pro.  &lt;br /&gt;&lt;br /&gt;&lt;strong&gt;1) Download and Install the Mono Framework&lt;/strong&gt;  &lt;br /&gt;&lt;br /&gt;Mono is basically an open source implementation of the .NET Framework.  If you want to know more than that, checkout &lt;a href="http://mono-project.com/"&gt;mono-project.com&lt;/a&gt;.  For now, just know that you need this if you want to run an ASP application on your Mac, and you can download the latest version &lt;a href="http://www.go-mono.com/mono-downloads/download.html"&gt;here&lt;/a&gt;.  The dmg file has a package installer inside of it, and it should run just fine with the default configuration.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;2) Download and Install the Mono CSDK&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;This is available from the same page, and is actually the link just to the RIGHT of the link for downloading the framework itself.  I'd be lying if I said I knew what this was, but many people have said in the forums that it's necessary.  :)&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;3) Download MonoDevelop and copy .app file to the Applications folder&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;MonoDevelop is a development environment for the .NET framework that you can run on a mac.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;4) Forget everything about steps 1-3...&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;...when you realize that Mono isn't going to be much help running Classic ASP, which is not on the .NET framework.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;5) find Apache::ASP&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;Sweet!  Execute your classic ASP in Perl with &lt;a href="http://www.apache-asp.org/"&gt;http://www.apache-asp.org/&lt;/a&gt;&lt;br /&gt;&lt;strong&gt;6) Make sure Apache is installed with mod_perl&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;If you're on a Mac, it's already there.  Just add this to your httpd.conf:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;LoadModule perl_module libexec/apache2/mod_perl.so&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;and add a perl.conf file to apache's "other" directory to allow the execution of perl scripts:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;&amp;lt;ifmodule perl_module=""&amp;gt;&lt;br /&gt;AddHandler cgi-script .pl&lt;br /&gt;&amp;lt;ifmodule dir_module=""&amp;gt;&lt;br /&gt;DirectoryIndex index.pl index.html&lt;br /&gt;&amp;lt;/ifmodule&amp;gt;&lt;br /&gt;&amp;lt;/ifmodule&amp;gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;b&gt;7) download and install the latest Apache::ASP&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;tar file can be found &lt;a href="http://www.cpan.org/modules/by-module/Apache/"&gt;here&lt;/a&gt;. I used 2.6.1.&lt;br /&gt;&lt;br /&gt;First, edit your httpd.conf file again:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;&amp;lt;directory $document_root="" asp="" eg=""&amp;gt;&lt;br /&gt;  Options FollowSymLinks &lt;br /&gt;  AllowOverride All&lt;br /&gt;&amp;lt;/directory&amp;gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Then, in the root directory of the Apache::ASP download, run the following:&lt;br /&gt;&lt;pre&gt;perl Makefile.PL&lt;/pre&gt;Which will tell you if you're missing any modules.  I had to install MLDBM::Sync.&lt;br /&gt;From there you can do your make process like any other:&lt;br /&gt;&lt;pre&gt;make&lt;br /&gt;make test&lt;br /&gt;make install&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;b&gt;8) test to make sure you can run an ASP script&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;With these steps in place, you should now at least be able to debug your inherited Classic ASP application on your Mac.  Now, hurry up and rewrite it on a better platform!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-279997288102984509?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/279997288102984509/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=279997288102984509' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/279997288102984509'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/279997288102984509'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/10/classic-asp-on-mac.html' title='Classic ASP on a Mac'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-5184760327694105823</id><published>2010-10-13T12:54:00.000-05:00</published><updated>2010-10-13T12:54:55.805-05:00</updated><title type='text'>Refactoring in Ruby with Blocks</title><content type='html'>The block can be a mysterious construct for the aspiring rubyist.  Sure, they're probably comfortable using it to iterate over a collection, that's common.  But when would I ever write my own?  Why would this confusing jump in control flow be useful?&lt;br /&gt;&lt;br /&gt;Fortunately for you, if you're one who would ask that question, I solved a refactoring problem just today by using a block to de-duplicate some code, and I think it makes a good example for showing what kind of situations blocks are good for helping solve.  Enjoy!&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;The problem&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;I have a class that merges some active record models together when it turns out we have duplicate information in the database.  At first glance, this isn't particularly complex.  Merge the authoritative attributes over the secondary attributes, combine collections of child records, and they're merged.  The problem is, some of the child record collections need to be reduced.  For example, if I have two user models that represent the same person, and they both have collections of child records for the last 12 months indicating that they're eligible for something every month (doesn't matter what), then when merged I'll have one user, with 2 eligibility records for every month back to a year.  That's not what I really want, I want to reduce those child collections based on criteria unique to each child collection so that we only have one record representing each discrete period of time.  So, to do this for two seperate chile collections, I started out by writing (after the tests) some code like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt; removed_scripts = []&lt;br /&gt; user.prescriptions.each do |script|&lt;br /&gt;  if removed_scripts.index(script.id).nil?&lt;br /&gt;   other_scripts = user.prescriptions.find(:all,&lt;br /&gt;              :conditions=&gt;["id != #{script.id} and &lt;br /&gt;                     start_date = ? and &lt;br /&gt;                     expiration_date = ?",&lt;br /&gt;                 script.start_date,script.expiration_date])&lt;br /&gt;   other_scripts.each do |os|&lt;br /&gt;    removed_scripts &lt;&lt; os.id&lt;br /&gt;    os.destroy&lt;br /&gt;   end&lt;br /&gt;  end&lt;br /&gt; end&lt;br /&gt;    &lt;br /&gt; removed_eligibility = []&lt;br /&gt; user.eligibility_records.each do |rec|&lt;br /&gt;  if removed_eligibility.index(rec.id).nil?&lt;br /&gt;   other_elig = user.eligibility_records.find(:all,&lt;br /&gt;             :conditions=&gt;["id != #{rec.id} and &lt;br /&gt;               year = ? and month = ?",&lt;br /&gt;              ser.year,ser.month])&lt;br /&gt;   other_elig.each do |oe|&lt;br /&gt;    removed_eligibility &lt;&lt; oe.id&lt;br /&gt;    oe.destroy&lt;br /&gt;   end&lt;br /&gt;  end&lt;br /&gt; end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;This was functional, but obviously I have two blocks of code that are more similar than different.  How can I combine them into one function that can handle both cases?  &lt;br /&gt;&lt;br /&gt;&lt;b&gt;The Solution&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;What makes this situation tough is that one of the things that is different between the two chunks of code above is within an iteration over a set of child records, and is different as it's applied to each record (that is, the conditions array for finding other records to destroy).  You can't pass in static conditions, because the conditions are dependent on the particular child record being used at that time for data binding.  What you need to do is pass a function that can take that child record, and give you your conditions.  Hence, my refactor:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;def reduce_recs(child_class,conditions)&lt;br /&gt; removed_recs = []&lt;br /&gt; child_class.find(:all,:conditions=&gt;conditions).each do |cld|&lt;br /&gt;  if removed_recs.index(cld.id).nil?&lt;br /&gt;   other_recs = child_class.find(:all,:conditions=&gt;yield(cld))&lt;br /&gt;   other_recs.each do |oth_rec|&lt;br /&gt;    removed_recs &lt;&lt; oth_rec.id&lt;br /&gt;    oth_rec.destroy&lt;br /&gt;   end&lt;br /&gt;  end&lt;br /&gt; end&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;By putting in the "yield" right where the main difference in implementation was, I can submit a different function for each child collection to do the necessary matching.  Thus, my new reduce_recs function can be used like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;reduce_recs(Prescription,"user_id = #{user.id}") do |child|&lt;br /&gt; ["id != #{child.id} and start_date = ? and &lt;br /&gt;   expiration_date = ?",&lt;br /&gt;   child.start_date,child.expiration_date]&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;reduce_recs(EligibilityRecord,"user_id = #{user.id}") do |child|&lt;br /&gt; ["id != #{child.id} and year = ? and month = ?",&lt;br /&gt;             child.year,child.month]&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Now, are there some other things that could be done to make this code more expressive?  Absolutely, and maybe I'll look at those in another post, but the lesson you can take away today is:  when you have a duplicated code chunk that only really differs at some point within nested iteration, you might consider using a block to abstract the functionality into a single function that yields out right at that critical point, to allow the calling code to specify what needs to be done for that small difference.&lt;br /&gt;&lt;br /&gt;Or, to refactor my last sentence: Blocks are good for making a lightweight &lt;a href="http://en.wikipedia.org/wiki/Template_method_pattern"&gt;template pattern&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Cheers,&lt;br /&gt;&lt;br /&gt;~Ethan&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-5184760327694105823?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/5184760327694105823/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=5184760327694105823' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/5184760327694105823'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/5184760327694105823'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/10/refactoring-in-ruby-with-blocks.html' title='Refactoring in Ruby with Blocks'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-1610011403615640172</id><published>2010-10-11T16:11:00.003-05:00</published><updated>2010-10-11T16:22:58.252-05:00</updated><title type='text'>Websolr: make your application do a REAL man's search</title><content type='html'>Rails 3 and all the various updates that come with it has been a really great experience for me so far.  I'm fond of ActiveRelation, I like the routing updates, and I love the fact that Heroku already supports it for deployment (and has for some time).&lt;br /&gt;&lt;br /&gt;One thing that's not great when using just the baked in tools, though, is search.  Most applications use some kind of searching, but unfortunately many (including a few of my own) do something like this (yes, this is an excerpt from one of my own codebases, I'm sorry, it won't happen again):&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;class User &lt; ActiveRecord::Base&lt;br /&gt;  named_scope :matching_search_term,lambda{|search_term| &lt;br /&gt;    {:conditions=&gt;["users.id = ? OR &lt;br /&gt;       users.last_name LIKE '#{search_term}%' OR &lt;br /&gt;       users.alt_id LIKE '#{search_term}%' OR &lt;br /&gt;       users.email LIKE '#{search_term}%'",search_term]}}&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;I shouldn't have to tell you why this sucks.  But I will.  Firstly, it's really only grabbing the front of the string on each field.  If I'm searching for "Vizitei" then I'll find anybody with the last name "Vizitei", but not anyone who has somehow ended up with " Vizitei" in the database (which would never happen because you sanitize your inputs, right?).  Additionally, those fields all have to be indexed in the database, or there's going to be a problem.  Using OR means that the results of the first part of the query don't actually narrow down what has to be compared in the rest of the query.  That is, even though I might have found a few users with the last name "stevens", when the query gets to the "email" part it still has to check the whole table for emails that match "stevens".  Third, any long text fields (like "description" or "bio") are going to present a performance problem if you're looking for matching a word anywhere in the body of that text.  &lt;br /&gt;&lt;br /&gt;What's great, though, is that the problem of integrating the kind of search that would easily cover a situation like this has already been solved, and pretty simply if you're using &lt;a href="http://heroku.com/"&gt;Heroku&lt;/a&gt; for your deployment platform.  Today I implemented this kind of fulltext search in about 10 minutes by following the instructions &lt;a href="http://docs.heroku.com/websolr"&gt;provided here for the "websolr" addon&lt;/a&gt;, adding the &lt;a href="http://github.com/onemorecloud/websolr-sunspot_rails"&gt;sunspot_rails&lt;/a&gt; gem to my app, and changing my user class to look like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;class User &lt; ActiveRecord::Base&lt;br /&gt;  searchable do&lt;br /&gt;    string  :first_name&lt;br /&gt;    string  :last_name&lt;br /&gt;    string  :alt_id&lt;br /&gt;    string  :email&lt;br /&gt;    text    :bio&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Then I just ran &lt;br /&gt;&lt;br /&gt;&lt;pre&gt;rake sunspot:reindex&lt;/pre&gt;&lt;br /&gt;and we're done.&lt;br /&gt;&lt;br /&gt;Why is this better?  Well, for one I can now just call #search on my model class to do a loose search on all those fields that are indexed by my new solr server.  For another, there are a ton of cool things you can do with solr that I don't want to go into now, but that you can find &lt;a href="http://lucene.apache.org/solr/"&gt;here on the solr welcome page&lt;/a&gt;.  For the impatient: it's faster. Capiche?&lt;br /&gt;&lt;br /&gt;I'm a little stunned that this was so easy, but in todays world of cloud deployments and pluggable architecture, tasks like this are stupid-simple.  Hooray for finishing early and going home for a beer!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-1610011403615640172?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/1610011403615640172/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=1610011403615640172' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/1610011403615640172'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/1610011403615640172'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/10/websolr-make-your-application-do-real.html' title='Websolr: make your application do a REAL man&apos;s search'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-1980758315215732367</id><published>2010-07-13T09:46:00.001-05:00</published><updated>2010-07-13T09:46:13.385-05:00</updated><title type='text'>Test helpers for collections</title><content type='html'>Something that comes up often in a rails application is testing a new scope into existance on a model object.  Often, I pull a collection from the scope, and want to check to make sure that the collection contains an object that I created ahead of time.  The gist linked to below are a couple of naive helper methods I use for this, and you're welcome to them if you like them.  Paste the "assert_contains" and "assert_does_not_contain" methods into your test_helper.rb and you're good to go.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://gist.github.com/473970"&gt;assert_contains helpers&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-1980758315215732367?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/1980758315215732367/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=1980758315215732367' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/1980758315215732367'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/1980758315215732367'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/07/test-helpers-for-collections.html' title='Test helpers for collections'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-4020360867363541403</id><published>2010-07-02T13:47:00.000-05:00</published><updated>2010-07-02T13:47:05.279-05:00</updated><title type='text'>Panic and creativity</title><content type='html'>Nothing sparks ones creativity like an emergency.  I know this intimately from my experience as a firefighter in my &lt;a href="http://fireable.blogspot.com/"&gt;parallel life&lt;/a&gt;, but I encountered a similar situation today when I accidentally axed some data from my production database that I REALLY shouldn't have.  I was playing around in the command line in production (always a brilliant idea) trying to find some elusive data to get rid of, and my practiced fingers typed ".each{|x| x.destroy}" on the end of a query that was bringing back data I really didn't want to get rid off.  Suddenly my console is filling up with text like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;[#&amp;lt;ProviderSchedule id: 11231, start_date: "2009-07-01", end_date: "2009-10-01", start_time: "2000-01-01 07:30:00", end_time: "2000-01-01 15:30:00", valid_flag: true, monday_included_flag: true, tuesday_included_flag: true, wednesday_included_flag: true, thursday_included_flag: true, friday_included_flag: true, saturday_included_flag: false, sunday_included_flag: false, created_at: "2009-09-09 04:34:37", updated_at: "2009-09-09 04:34:37", schedulable_id: 12092, schedulable_type: "Provider"&amp;gt;, #&amp;lt;ProviderSchedule id: 39252, start_date: "2009-10-01", end_date: "2009-12-31", start_time: "2000-01-01 07:30:00", end_time: "2000-01-01 15:30:00", valid_flag: true, monday_included_flag: true, tuesday_included_flag: true, wednesday_included_flag: true, thursday_included_flag: true, friday_included_flag: true, saturday_included_flag: false, sunday_included_flag: false, created_at: "2009-09-18 17:04:44", updated_at: "2009-09-18 17:04:44", schedulable_id: 12092, schedulable_type: "Provider"&amp;gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;and I'm starting to feel a little sick. &amp;nbsp;First lesson learned; &amp;nbsp;for God's sake, don't screw around in the production database.&lt;br /&gt;&lt;br /&gt;However, what was done was done, and I needed a way to restore the data IMMEDIATELY. &amp;nbsp;Coding through a border-line panic, I quickly produced the code linked to at the following gist:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://gist.github.com/461709"&gt;GIST!&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;It's quick and dirty, but it saved my ass today.  If you're running Rails, and you did something as bone-headed as accidentally destroying a ton of data from the command line, this may save your life.  Quickly, before you close your terminal, COPY THE OUTPUT STRING FROM YOUR DELETE OPERATION AND SAVE IT TO YOUR COMPUTER RIGHT NOW!!!!&lt;br /&gt;&lt;br /&gt;Now, open up your command line terminal again in production (for the last time ever), copy the gist in to define the method, put the data dump inside a %Q{} stored in a local variable, and run it through the method like so along with the class object (the object which was destroyed):&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&lt;pre&gt;=&amp;gt;  string = %Q{...huge long output string...}&lt;br /&gt;    restore_from_destroyed_string(string,ClassName)&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt; &lt;br /&gt;&lt;br /&gt;you'll see some output like this:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;creating object 11247...true&lt;br /&gt;creating object 39261...true&lt;br /&gt;creating object 46838...true&lt;br /&gt;creating object 72731...true&lt;br /&gt;creating object 88175...true&lt;br /&gt;creating object 11248...true&lt;br /&gt;creating object 39262...true&lt;br /&gt;creating object 46839...true&lt;br /&gt;&lt;br /&gt;Now, wipe your forehead and pound a shot of scotch, you just averted disaster. &lt;br /&gt;&lt;br /&gt;PS: &amp;nbsp;NEVER do that again.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-4020360867363541403?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/4020360867363541403/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=4020360867363541403' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/4020360867363541403'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/4020360867363541403'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/07/panic-and-creativity.html' title='Panic and creativity'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-6018618569620456220</id><published>2010-07-02T11:03:00.000-05:00</published><updated>2010-07-02T11:03:06.418-05:00</updated><title type='text'>patch release of quarter_time (0.3.2)</title><content type='html'>This is just a minor release of my mini-gem, &lt;a href="http://github.com/evizitei/quarter_time"&gt;quarter_time&lt;/a&gt;.  Up until now, quarter objects have always been created using integer arguments:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;pre&gt;quarter = Quarter.new(2010,3)&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;However, in cucumber tests and in rails controllers, I often have string versions of these arguments available.  After casting in a couple places, I decided to make it part of the gem, so as of quarter_time 0.3.2, you can do this:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;pre&gt;quarter = Quarter.new("2010","3")&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt; &lt;br /&gt;Simple change, but useful.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-6018618569620456220?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/6018618569620456220/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=6018618569620456220' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/6018618569620456220'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/6018618569620456220'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/07/patch-release-of-quartertime-032.html' title='patch release of quarter_time (0.3.2)'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-435726117801698618</id><published>2010-06-30T12:22:00.000-05:00</published><updated>2010-06-30T12:22:00.327-05:00</updated><title type='text'>using regex groups for ruby's gsub</title><content type='html'>I won't spend long on this.  If you've done your due diligence and carefully read up on the way regex matching works in ruby, you wouldn't be on this page.  If you were like me and figured that you could probably just nail it the first time without looking anything up, you might have written something like this:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;pre&gt;cleaned_string = "7.30 AM".gsub(/(\d)\.(\d)/,"#{$1}:#{$2}")&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;This is about as completely wrong as you can get while still looking somewhat right (as I found out today).  You see, the global variables like "$n" do indeed store the matched groups from regex operations....AFTER they've executed.  At the time when these parameters to the gsub method are evaluated, these variables are still nil.  Thus the string you end up with is something like ":0 AM".&lt;br /&gt;&lt;br /&gt;Correct syntax for including matched groups from regex expressions?&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;pre&gt;cleaned_string = "7.30 AM".gsub(/(\d)\.(\d)/,'\1:\2')&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;this way it's evaluated later, after the method has been called.  Note the single tick marks, as doubles will cause the slash-number combinations to be interpreted.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-435726117801698618?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/435726117801698618/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=435726117801698618' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/435726117801698618'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/435726117801698618'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/06/using-regex-groups-for-rubys-gsub.html' title='using regex groups for ruby&apos;s gsub'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-8731172252506376196</id><published>2010-06-22T10:30:00.002-05:00</published><updated>2010-06-22T10:30:46.031-05:00</updated><title type='text'>quarter_time 0.3.1 released</title><content type='html'>A small but useful addition to the &lt;a href="http://github.com/evizitei/quarter_time"&gt;quarter_time&lt;/a&gt; &lt;a href="http://rubygems.org/gems/quarter_time"&gt;gem&lt;/a&gt; I released a couple weeks ago.  Normally the scope added to models that include the QuarterTime::QuarterDriven module would be used with other Quarter objects:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;pre&gt;class SomeModel &lt; ActiveRecord::Base&lt;br /&gt;  include QuarterTime::QuarterDriven&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;q = Quarter.new(2010,2)&lt;br /&gt;model = SomeModel.for_quarter(q).first&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt; However, in cases where I didn't already have a quarter object built or coming from another model, it seemed like overkill to build the object just to pass a couple parameters.  So, here's an additional (equivalent) syntax you can use to select models by quarter:&lt;code&gt;&lt;pre&gt;class SomeModel &lt; ActiveRecord::Base&lt;br /&gt;  include QuarterTime::QuarterDriven&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;model = SomeModel.for_quarter(2010,2)&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-8731172252506376196?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/8731172252506376196/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=8731172252506376196' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/8731172252506376196'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/8731172252506376196'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/06/quartertime-031-released.html' title='quarter_time 0.3.1 released'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-9060736880486197182</id><published>2010-06-19T16:09:00.000-05:00</published><updated>2010-06-19T16:09:35.783-05:00</updated><title type='text'>Off topic but on fire</title><content type='html'>Today I wanted to spend some time working on my &lt;a href="http://github.com/evizitei/lingq"&gt;Lingq.com client gem&lt;/a&gt; that I've been working on this weekend.  It's a fun litte project, and if you're into learning another language I recommend you go check out lingq.com, it's pretty cool.&lt;br /&gt;&lt;br /&gt;But that didn't happen.  No, instead today I spent a few hours trying to figure out how I'd been fraudulently charged for $325.50 over the last 6 months.   I wish this stuff didn't happen very often, but the guys who got me are pros, and if you're someone who has a credit card (and you are), you should immediately go check your statements for anything that looks like one of these:&lt;br /&gt;&lt;br /&gt;MVQ*PRIVACYM&lt;br /&gt;MVQ*CLUBSAVE&lt;br /&gt;MVQ*SHOPESSPLUS&lt;br /&gt;MVQ*SHOPPINGESSENTIALS+&lt;br /&gt;MVQ*BIZMAX&lt;br /&gt;MVQ*BUSINESSMAX&lt;br /&gt;MVQ*DLMAX&lt;br /&gt;MVQ*DEALMAX&lt;br /&gt;MVQ*TWENTY4PROPLUS&lt;br /&gt;MVQ*24PROTECTPLUS&lt;br /&gt;MVQ*TRAVELMEMBER&lt;br /&gt;MVQ*TODAYSESCAPES&lt;br /&gt;MVQ*PRIVACYMID &lt;br /&gt;MVQ*PMIDENTITY&lt;br /&gt;MVQ*HOMERWRDS &lt;br /&gt;MVQ*ATHOMEREWARD&lt;br /&gt;MVQ*VALMAX &lt;br /&gt;MVQ*ValueMax&lt;br /&gt;MVQ*PASSPRT2FUN&lt;br /&gt;MVQ*PASSPORTTOFUN&lt;br /&gt;&lt;br /&gt;I'm sure you see the pattern.  There are tons more, but who has time to copy and paste all that mess 300 times?  &lt;br /&gt;&lt;br /&gt;These guys run a ton of billbord sites like this:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.mvq-dlmax.com/"&gt;http://www.mvq-dlmax.com/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Clue one that this is a problem is how every site of theirs is basically something that says "want to know why this mysterious charge showed up on your credit card statement?".  Check out this excerpt from &lt;a href="http://www.mvq-todaysescapes.com/"&gt;http://www.mvq-todaysescapes.com/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;i&gt;You're probably here because you were searching the billing descriptor MVQ TODAYSESCAPES after finding it on your credit or debit card statement.&lt;br /&gt;The billing descriptor MVQ*TODAYSESCAPES that you saw on your credit or debit card statement is used to indicate a fee for enrolling in Today's EscapesSM, an online membership discount program that offers consumers much-desired discounts on travel plans, dining out, home entertainment and more.&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Oh my freaking God.  If I had signed up for a membership with you, why would I be searching by the BILLING descriptor for your service?  Why in the name of reason would the front page of your website be dedicated to explaining why I shouldn't ask my credit card's fraud department to bring the attorney general down on your slimy ass?&lt;br /&gt;&lt;br /&gt;Yep, I called my credit card company, and what did they say?  "Yeah, we see this a lot".  Wow.  Now, I'm glad they were able to help me, they issued me a new card cancelled my old one and credited the charges, but I have to wonder about how this situation reflects on the world of software developers world wide. Obviously someone has shown they can make a quick buck by obtaining credit card numbers and charging them monthly because most people won't investigate charges smaller than $50.  And even then, once someone cancels their "account" with these criminals, they usually won't go so far as to file fraud, because it's time consuming.  So they have a system that does this automatically, and a phone answering service that lets you cancel your account (it's pretty much the only option on the phone menu, minus some bullshit "explanation of your benefits" speech).  And the money rolls in every month.&lt;br /&gt;&lt;br /&gt;This probably isn't very coherent, because I'll admit, I'm furious right now, but these people will be allowed to operate only as long as we don't report them because it's too much administrative overhead.  Now, I did my part today.  I filed a fraudulent charge claim for every transaction they hit me with since they got my credit card number back in september. Today, right now, go to your credit card account search for MVQ.  If you're being scammed too, please report it to your credit card company's fraud department so that we can get these lowlifes shut down.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-9060736880486197182?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/9060736880486197182/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=9060736880486197182' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/9060736880486197182'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/9060736880486197182'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/06/off-topic-but-on-fire.html' title='Off topic but on fire'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-9178194721186615391</id><published>2010-06-10T08:46:00.001-05:00</published><updated>2010-06-15T17:28:27.726-05:00</updated><title type='text'>quarter_time: progeny of RailsConf</title><content type='html'>Well, it's been a great week so far at RailsConf!&lt;br /&gt;&lt;br /&gt;There have been at least 47 times that I've looked at someone and thought "hey, I know that guy!" recognizing them from their avatars on github.com or their names on twitter.&lt;br /&gt;&lt;br /&gt;Dr Nic Williams, Obie Fernandez, Greg Pollack, I could go on and on.&lt;br /&gt;&lt;br /&gt;Most importantly, I've been overwhelmingly inspired to dive back into self-improvement in my coding ability.  &lt;a href="http://www.drnicwilliams.com/"&gt;Dr. Nic&lt;/a&gt;'s talk especially galvanized me towards engaging more with the gems and libraries I'm taking advantage of, and contributing back my own.  In the spirit of my newly found ambition, I've released a mini-gem today that I use in a couple different applications my startup runs.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://github.com/evizitei/quarter_time"&gt;quarter_time&lt;/a&gt;, here linked to my github profile, is a gem for dealing with the problem of quarter-based business cycles.  For instance, we have a medicaid-billing application that processes claims for every quarter of the year (Quarter 1 2010, Quarter 2 2010, etc).   The claim models, and many other pieces of supporting data, are stored according to what year and quarter they belong to.  Of course, the normal ruby data doesn't have any assistance for moving between quarters, and there are certain things that are common between models that are governed by a quarter (for instance, a scope to find objects by the year/quarter they are part of, a way to find the date range the model covers (start_date and end_date), and so on.  This early-stage gem (well-tested, but not fully featured yet) is here to help solve that problem.  &lt;br /&gt;&lt;br /&gt;Sometimes the best part of going to these conferences is the energy you get from being around so many people who are interested in the same stuff. Thanks RailsConf, and I'll see you next year.&lt;br /&gt;&lt;br /&gt;~Ethan&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-9178194721186615391?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/9178194721186615391/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=9178194721186615391' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/9178194721186615391'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/9178194721186615391'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/06/quartertime-progeny-of-railsconf.html' title='quarter_time: progeny of RailsConf'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-6241323980037120626</id><published>2010-05-19T09:47:00.000-05:00</published><updated>2010-05-19T09:47:15.331-05:00</updated><title type='text'>A designers goldmine for software developers</title><content type='html'>If you're like me, you're mainly into code.  You like writing systems to solve problems that you have and you can do just enough UI design to be functional.  You'd like for your projects to be prettier, but spending a ton of time on tweaking the positioning and color of elements is not your idea of a good day.&lt;br /&gt;&lt;br /&gt;Helpful Hint: &lt;a href="http://themeforest.net"&gt;Theme Forest&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Hundreds of nicely layed out UI skins that you can buy pretty cheap and use for your pet project.  I don't need to say much more about it, just go check it out (This is not an affiliate link, I have no finiancial reason to want you to go buy a template from these guys, I just have bought a couple in the last few weeks and am plenty happy with them).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-6241323980037120626?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/6241323980037120626/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=6241323980037120626' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/6241323980037120626'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/6241323980037120626'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/05/designers-goldmine-for-software.html' title='A designers goldmine for software developers'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-2703125467264596279</id><published>2010-05-11T12:49:00.001-05:00</published><updated>2010-05-11T12:49:40.612-05:00</updated><title type='text'>Cucumber and BackgroundJob</title><content type='html'>The problem:  you want to have some nice integration tests that cover situations where jobs get submitted to the background queue, but haven't gotten around to writing it yet or haven't wanted to deal with it.&lt;br /&gt;&lt;br /&gt;The solution:  we at my office have suffered for you.  If you are a BackgroundJob user (and someone who uses Cucumber for their acceptance testing), this steps file (and the associated BJ Model) should help you with your workflow:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://gist.github.com/397593"&gt;GIST!&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;In short, it uses active record to make sure all your jobs are far enough in the past to get run (a problem with BJ and timezones), executes the queue when you want it to, and has a step for making sure that the job you wanted to submit actually exists.&lt;br /&gt;&lt;br /&gt;As an aside, BJ takes a while to startup before working off the queue, so I wouldn't use this all the time.  There is probably a good way to speed it up that I haven't thought of, but the idiot-proof way would be to switch to DelayedJob (something we're planning on doing in the next couple months).&lt;br /&gt;&lt;br /&gt;Enjoy!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-2703125467264596279?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/2703125467264596279/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=2703125467264596279' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/2703125467264596279'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/2703125467264596279'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/05/cucumber-and-backgroundjob.html' title='Cucumber and BackgroundJob'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-7239733948998585847</id><published>2010-05-10T12:44:00.002-05:00</published><updated>2010-05-10T12:47:41.571-05:00</updated><title type='text'>Heroku, you are amazing</title><content type='html'>I've been spending my weekends trying to keep up with the ruby community.  Building toy projects, testing new gems out, and I've found a lot of cool stuff out there.&lt;br /&gt;&lt;br /&gt;But what has really struck me as more of a game changer than anything else is &lt;a href="http://www.heroku.com"&gt;Heroku&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;You remember when Rails first came out?  I remember experiencing it as an epiphany, of sorts.  I had spent years writing java apps, configuring every application down to the most minute details.  Along come this Rails framework and says "I know what's usually best, and that's what I do by default.  My conventions are king.",  and that changed web development for me.  I could get features done twice as fast because all the config I usually did up front for each piece was already done for me.  Yeah, that meant a loss in flexibility, but I didn't mind much.  If I have to do something special, I'll cross that bridge when I come to it.&lt;br /&gt;&lt;br /&gt;The same sort of feeling came over me this weekend when I tried deploying my first heroku application.  I just ran a simple "heroku create" command from my home directory of my app, and it was deployed in seconds, ready to be updated by a simple git push, and already live and kicking.  Once again, there's this new guy on the block who says "This is what you usually need, so this is what we always do", and it works so beautifully.  Need email?  Push an add-on, it's already preconfigured.  Performance monitoring?  memcached? error notification?  it's push-button simple.&lt;br /&gt;&lt;br /&gt;And the best part?  If you want to spike out an application that you aren't sure you want to build all the way out yet, you can have it deployed and running on their simplest and smallest configuration for FREE! That's right, just try it out, see if it's viable, and when you're ready to bring the traffic you just reconfigure it to spool up more resources.  Everything is billed by a running meter (I think around 5 cents an hour per process), and it just works.&lt;br /&gt;&lt;br /&gt;To the creators of Heroku, I salute you, you've made Rails deployment fun.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-7239733948998585847?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/7239733948998585847/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=7239733948998585847' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/7239733948998585847'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/7239733948998585847'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/05/heroku-you-are-amazing.html' title='Heroku, you are amazing'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-2289595759650789421</id><published>2010-04-29T16:35:00.000-05:00</published><updated>2010-04-29T16:35:59.011-05:00</updated><title type='text'>Bug with BigDecimal in Ruby 1.8.7 (patchlevel 173)</title><content type='html'>got out my brand new iMac today, and when I checked out my project, I had a bunch of tests failing.  I thought I might have checked out an old version, but no everything was current and passing on my old dev machine.  It wasn't in in my code.  It wasn't even in rails.  It was a problem with Ruby 1.8.7 (patchlevel 173). After hours of digging, behold the following console output in irb:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;&gt;&gt; require 'bigdecimal'&lt;br /&gt;=&gt; true&lt;br /&gt;&gt;&gt; BigDecimal.new(103123.98.to_s).to_f&lt;br /&gt;=&gt; 103123.98&lt;br /&gt;&gt;&gt; BigDecimal.new(100123.98.to_s).to_f&lt;br /&gt;=&gt; 101239.8&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Strangely enough, if you have a "0" as the fourth digit to the left from the decimal point when you build a big decimal object, it cuts it out.  I have no idea why this is.  I don't have time to investigate it.  But I do know it doesn't happen with ruby 1.8.6, which is what I'm running in production, so for the moment it's not a real problem for me (except I had to change all my tests that had a 0 in that decimal place temporarily while I sort out bringing an older version of ruby onto my machine). &lt;br /&gt;&lt;br /&gt;*sigh*&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-2289595759650789421?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/2289595759650789421/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=2289595759650789421' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/2289595759650789421'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/2289595759650789421'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/04/bug-with-bigdecimal-in-ruby-187.html' title='Bug with BigDecimal in Ruby 1.8.7 (patchlevel 173)'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-3572419578842446586</id><published>2010-04-29T14:52:00.000-05:00</published><updated>2010-04-29T14:52:47.907-05:00</updated><title type='text'>RMagick on OS X Snow Leopard</title><content type='html'>Just got a new iMac today, so I'm trying to get my project up and running on it. Things were going well until I got to RMagick.  I always forget what a pain in the ass that gem is.  I was lucky this time, after a little blog surfing, to find a great post by "hocuspokus" that provides some excellent help in the form of a shell script you can run to make it all happen (NOTE: you must have XCode developer tools already installed, it's on your Mac OS X Installation DVD).  In the interest of giving credit to the right person, I'm not going to link directly to that script, but am instead just going to give a little link-love to the blog of the guy who made my day so much better when I found his website:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://hocuspokus.net/2010/03/installing-rmagick-on-snow-leopard-leopard"&gt;CLICK HERE TO GO TO THE HOCUSPOKUS BLOG SO THAT YOU CAN STOP TROLLING AROUND THE INTERTUBES TRYING TO GET THAT DAMN GEM INSTALLED ON YOUR NEW MAC!&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-3572419578842446586?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/3572419578842446586/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=3572419578842446586' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/3572419578842446586'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/3572419578842446586'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/04/rmagick-on-os-x-snow-leopard.html' title='RMagick on OS X Snow Leopard'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-6304912941294593737</id><published>2010-04-25T22:53:00.000-05:00</published><updated>2010-04-25T22:53:37.787-05:00</updated><title type='text'>Getting my feet wet with Clojure</title><content type='html'>For the next few weeks, I'll be participating in the "Clojure 101" class at &lt;a href="http://www.rubylearning.org"&gt;Ruby Learning&lt;/a&gt;.  I don't have any plans to switch over from Rails as my primary platform in the near future, but I have a soft spot for the ol' JVM and an incomplete knowledge of functional languages, so I'm excited to have something of a diversion.&lt;br /&gt;&lt;br /&gt;If you're interested in benefiting from my experience, I'm putting my notes/examples/exercises/experiments on my &lt;a href="http://github.com/evizitei/ruby_learning_clojure_101"&gt;Github Account&lt;/a&gt;, and I'll continue to update it as a convenient place to store all my new knowledge (god knows my head's out of room).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-6304912941294593737?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/6304912941294593737/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=6304912941294593737' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/6304912941294593737'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/6304912941294593737'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/04/getting-my-feet-wet-with-clojure.html' title='Getting my feet wet with Clojure'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-7481444265736961516</id><published>2010-04-23T15:06:00.000-05:00</published><updated>2010-04-23T15:06:59.720-05:00</updated><title type='text'>Rails has_many scoping and database indexes</title><content type='html'>If you are a good rails coder, than you probably form your associations like this:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;class Parent &lt; ActiveRecord::Base&lt;br /&gt;  has_many :children&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;class Child &lt; ActiveRecord::Base&lt;br /&gt;  belongs_to :parent&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Which is correct.  Then, to keep things looking pretty, when you want a scoped subset of a given parent's children, you probably write something like this&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;&lt;br /&gt;class Parent &lt; ActiveRecord::Base&lt;br /&gt;  has_many :children&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;class Child &lt; ActiveRecord::Base&lt;br /&gt;  belongs_to :parent&lt;br /&gt;  named_scope :who_can_drive,:conditions=&gt;"age &gt;= 16"&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;parent = Parent.first&lt;br /&gt;children = parent.children.who_can_drive&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Which is well and good.  Except when you care about performance.  You see, I did something like this in my production application.  Both fields on the child model are indexed (the foreign key, and the field used for searching by), but guess which one gets used first?  If you know your named scopes, you know that in practice the named scope that is APPLIED LAST in the chain is used first in the query, and that's the one MySQL decided to use as the primary index.&lt;br /&gt;&lt;br /&gt;Now if a parent has 2 or 3 children, but it's in a table of 15,000,000 kids between the ages of 14 and 21, which index do you think will produce the smaller set to scan?  The one that narrows it down to only the 2 or 3 children of that parent and then checks their age, or the one that narrows it down to the 7 million children over the age of 16 and then checks their parent?&lt;br /&gt;&lt;br /&gt;So to keep my server from crashing and burning, I've started writing my code like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;&lt;br /&gt;class Parent &lt; ActiveRecord::Base&lt;br /&gt;  has_many :children&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;class Child &lt; ActiveRecord::Base&lt;br /&gt;  belongs_to :parent&lt;br /&gt;  named_scope :who_can_drive,:conditions=&gt;"age &gt;= 16"&lt;br /&gt;  named_scope :belonging_to,lambda{|parent| {:conditions=&gt;{:parent_id=&gt;parent.id}}}&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;parent = Parent.first&lt;br /&gt;children = Child.who_can_drive.belonging_to(parent)&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;I don't like it, but it sure improved my scores on my newrelic monitoring graphs.  Anybody have a more elegant solution to this problem?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-7481444265736961516?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/7481444265736961516/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=7481444265736961516' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/7481444265736961516'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/7481444265736961516'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/04/rails-hasmany-scoping-and-database.html' title='Rails has_many scoping and database indexes'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-5431909802069387046</id><published>2010-03-12T22:31:00.001-06:00</published><updated>2010-03-12T22:32:09.624-06:00</updated><title type='text'>Get the gist of data loading (csv,xls,xlsx,ods)</title><content type='html'>Common problem: you need to load some spreadsheet data into your Rails application.&lt;br /&gt;&lt;br /&gt;Common solution: send it to me as a csv file, I'll take care of it.&lt;br /&gt;&lt;br /&gt;Why?  Because a csv file is super easy to parse.  Using the built-in ruby csv reader, every row comes back as an array as you traverse the file, so it's easy to process one row at a time:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;pre&gt;CSV::Reader.parse(file).each do |row|&lt;br /&gt;  #...save some model that this represents...&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;The complication that often comes up is that your customers might not know what a csv file is.  So you'll get xls, xlsx, ods, pretty much anything.  &lt;br /&gt;&lt;br /&gt;You could manually save each file as a csv (kinda frustrating).&lt;br /&gt;&lt;br /&gt;You could also write a cludgy class that handles the interfaces for different libraries differently.&lt;br /&gt;&lt;br /&gt;Or you could use this gist I'm about to provide you for all your parsing needs, taking the power of the &lt;a href="http://github.com/hmcgowan/roo"&gt;"roo"&lt;/a&gt; gem, and the built in "csv" parser, and translating all your file types into the same iterate-able interface that you so love:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://gist.github.com/331103"&gt;GIST!&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Now you can just write:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;pre&gt;SpreadsheetParser.parse(file) do |row|&lt;br /&gt;  #...pretend its a csv file, every row is just an array&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Enjoy!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-5431909802069387046?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/5431909802069387046/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=5431909802069387046' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/5431909802069387046'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/5431909802069387046'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/03/get-gist-of-data-loading-csvxlsxlsxods.html' title='Get the gist of data loading (csv,xls,xlsx,ods)'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-6366664907811456201</id><published>2010-03-09T15:19:00.000-06:00</published><updated>2010-03-09T15:19:46.751-06:00</updated><title type='text'>New Mini-Gem in the Wild</title><content type='html'>Ok, here's a problem I've had:&lt;br /&gt;&lt;br /&gt;I want to deploy my application to a "cloud" setup rather than my current "slice" infrastructure.  This is great, but means I have to refactor out many features that used to make use of the local filesystem because I cannot depend on it.  I could spawn new servers at any time in a cloud infrastructure, and there's no GFS joining them.  For most features, this is no big deal: store your assets on Amazon S3 and get over it.  That's what I've done for user uploads, report files, and all manner of other assets.&lt;br /&gt;&lt;br /&gt;The problem came with some of my data updates.  We have a process at my company where some of our internal users have a page on our application they go to in order to update our data from non-web-enabled sources (mostly through the use of CSV files, since all our data sources seem to be able to generate those).  Currently we store those files on the GFS, than kick off a background job passing the file name and let the background job do whatever processing it needs to do on that file.&lt;br /&gt;&lt;br /&gt;It's harder to do on the cloud, though, because you can't just store it locally, our utility server instance that's running our background jobs won't be able to get to it since it's a different filesystem entirely.  You could put it in the database, and if I were using MongoDB on this project I probably would, but that's not a habit I want to get into with MySQL.&lt;br /&gt;&lt;br /&gt;The patchwork solution we're going forward with for now is ditching the file into s3, passing the key to the background task, and re-downloading the file on that end for processing.&lt;br /&gt;&lt;br /&gt;In order to make this process a little more palatable, I've quickly built and released a mini-gem called &lt;a href="http://github.com/evizitei/cumulus_csv"&gt;Cumulus CSV&lt;/a&gt;.  It just wraps a simple interface around storing an uploaded csv file to S3, and iterating over it later.  It's available from my github account, or on gemcutter as "cumulus_csv", so if you're struggling with the same problem see if it will help you out!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-6366664907811456201?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/6366664907811456201/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=6366664907811456201' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/6366664907811456201'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/6366664907811456201'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/03/new-mini-gem-in-wild.html' title='New Mini-Gem in the Wild'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-2457750775185428717</id><published>2010-03-08T14:28:00.001-06:00</published><updated>2010-03-08T14:29:13.261-06:00</updated><title type='text'>We're Hiring!</title><content type='html'>This is a call to all Rubyists in Columbia, MO; if you're looking for a new (or your first) coding gig, we're interested in talking to you.&lt;br /&gt;&lt;br /&gt;Here's the story:  we (mostly I) went and built this Rails app, therapylog.com.  It lets therapists at public school districts track all of the therapy they do from day to day, in what we think is a pleasant way.  No paper, no hassle!  We even started a &lt;a href="http://therapylog.blogspot.com/"&gt;BLOG&lt;/a&gt; to let our therapists see how our webapp was growing (you can check it out too to see some sample functionality).&lt;br /&gt;&lt;br /&gt;Now the problem: We've grown.  We've got a slew of new feature ideas, plus a bit of refactoring work ahead of us, and I can't do it alone.&lt;br /&gt;&lt;br /&gt;What I'm looking for is somebody at what an enterprise operation might call the "junior" level.  We've got a pretty good code base established, but need someone to step in and keep the gears of progress moving forward.  You would be working with me directly, as locally as possible, and we could have you start as soon as you're ready.&lt;br /&gt;&lt;br /&gt;Things I'd like:&lt;br /&gt;&lt;br /&gt;-at least some experience with Ruby or Python, doesn't have to be professional in nature&lt;br /&gt;-interest in making things better, not just getting the checklist done&lt;br /&gt;-comfortable on a non-windows development environment (*nix, Mac OS X)&lt;br /&gt;-history of making self better &lt;br /&gt;-Not necessarily a design pro, but unwilling to create something ugly.&lt;br /&gt;-good sense of humor (won't help your coding, but should at least help our interaction) :)&lt;br /&gt;&lt;br /&gt;Although we'd love to have a 5-7 year rails vet, fact of the matter is, we're talking a $30,000-$40,000+ position ($20/hr if part time), and being a developer myself I know what you're worth when you've been in the business that long, and I can't see you being happy at our current cashflow level.    &lt;br /&gt;&lt;br /&gt;However, if you're a recent/soon-to-be college grad or a programming hobbyist looking for a career change, this might be for you!  &lt;br /&gt;&lt;br /&gt;You can reach me directly at ethan.vizitei@gmail.com, can't wait to hear from you!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-2457750775185428717?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/2457750775185428717/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=2457750775185428717' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/2457750775185428717'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/2457750775185428717'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/03/were-hiring.html' title='We&apos;re Hiring!'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-3726566143407947577</id><published>2010-03-05T10:35:00.001-06:00</published><updated>2010-03-05T11:18:05.451-06:00</updated><title type='text'>I've been had!</title><content type='html'>Here is a lesson for you that you should take to heart:  Trust your mentors, but find out for yourself.&lt;br /&gt;&lt;br /&gt;When I first heard about batched finding in ActiveRecord (&lt;a href="http://api.rubyonrails.org/classes/ActiveRecord/Batches/ClassMethods.html"&gt;#find_each adn #find_in_batches&lt;/a&gt;), it was a great revelation.  My buddy told me how it worked, and I loved it!&lt;br /&gt;&lt;br /&gt;According to him, find_in_batches used a "batch_size" hash option, defaulting to 1000, to decide how many records to load into memory at a time, and "find_each" would perform a query for each record one at a time.&lt;br /&gt;&lt;br /&gt;Give those options, and wanting to balance my memory conservation against responsible use of database time, I ended up with a lot of blocks like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;Model.find_in_batches do |models|&lt;br /&gt;  models.each do |model|&lt;br /&gt;    ....&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;It seemed tedious to do a nested block every time, but I didn't want to use the "find_each" option which was going to slam my database with a million single row queries.&lt;br /&gt;&lt;br /&gt;Eventually I'd repeated myself enough that I decided I was going to do something about it.  I figured I'd create a plugin that would monkeypatch in a method called "find_each_in_batches" that would do the extra block for you, so you could automatically iterate over one model at a time, but still have the finding occur in batches.  In order to get the namespacing right, I opened up the source file for ActiveRecord's built in batching methods (batch.rb), and imagine my surprise when I see this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;def find_each(options = {})&lt;br /&gt;  find_in_batches(options) do |records|&lt;br /&gt;    records.each { |record| yield record }&lt;br /&gt;  end&lt;br /&gt;  self&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;What do you know!  #find_each actually does exactly what I was planning on making my new plugin do!  My buddy had been misinformed, and had in turn misinformed me, and I just took his word for it!&lt;br /&gt;&lt;br /&gt;If I had checked out this source code myself in the first place, I would have known this in the first place, and wouldn't have those double iteration blocks littered throughout my code base.&lt;br /&gt;&lt;br /&gt;Reading the source code is a good habit anyway, because it introduces you to idioms the pros are using that you may not be familiar with and is a great way to improve your own code.  But even just a glance to confirm what i'd been told with the inline documentation would have prevented a world of hurt.&lt;br /&gt;&lt;br /&gt;So, while I'm here slapping my forehead, I encourage to you take heed to what I said at the top: Trust your mentors, but find out for yourself.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-3726566143407947577?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/3726566143407947577/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=3726566143407947577' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/3726566143407947577'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/3726566143407947577'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/03/ive-been-had.html' title='I&apos;ve been had!'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-2993085255661229766</id><published>2010-02-21T22:21:00.000-06:00</published><updated>2010-02-21T22:21:20.751-06:00</updated><title type='text'>A New Playground</title><content type='html'>Tonight I decided it was time to get my skills up to speed again.  With all the new ideas and gems being thrown around in this time of swift evolution in the Rails community, I've felt left in the dust as I continue to toll away on an old project using an old version of rails and a set of older support gems.&lt;br /&gt;&lt;br /&gt;So, in an effort to become current once again, I've started a new project (which I'll talk about in another post) utilizing the following:&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Rails 2.3.5&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Yeah, I know Rails 3 is coming out soon, so why bother coming up to this minor version at all?  Well, my previous project was on Rails 2.2.3, and I haven't been getting to play with Rack as much as some of the more current developers have.  I think that's one of the major modularity improvements that has pushed rails in the direction it's going with 3.0, and I want on-board.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;MongoDB&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;There's a lot of hype around non-SQL databases right now.  As an early user of DB4O, my whole developer career has been littered with occasional forays into the non-relational, and although I have no objection to relation databases per-se, I love the idea of schema-less data.  &lt;a href="http://www.mongodb.org"&gt;MongoDB&lt;/a&gt; does this beautifully, and it's very popular in the ruby community right now in no small part thanks to the &lt;a href="http://github.com/jnunemaker/mongomapper"&gt;MongoMapper&lt;/a&gt; gem.  Having my data as one hash of properties appeals to my sense of simplicity, and the embedded documents and collections are nifty looking.  Time will tell if I will end up feeling like it suits my needs on a larger project.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Heroku&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;Currently, my large startup project runs on &lt;a href="http://www.engineyard.com"&gt;EngineYard&lt;/a&gt;.  We trust them because their customer service is great and they've done an amazing job of helping us along every step of the way.  Their new cloud accounts are awesome (we're going to be switching over to that from our old "slice" architecture soon), and they have a track record a mile long of proven excellence with difficult scaling problems.  And they cost what they're worth.&lt;br /&gt;&lt;br /&gt;For a smaller project, either of a personal nature or one that's going to be bootstrapped with little revenue to kick it off, it's a little pricy.  &lt;a href="http://heroku.com/"&gt;Heroku&lt;/a&gt;, however, is very reasonably priced, and has a slew of posts on the ruby blog network right now that are titled along the lines of "How to deploy a crazy-cool-new-application on Heroku in under 30 minutes".  Their development account is free, and I want to see what the buzz is about, so I'll be posting how I find the hosting provider after I've played around a bit.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;RSpec&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;OK, this isn't exactly new, but I just haven't had a project yet where I've been willing to start with a totally new testing library that I'm unfamiliar with, so Test::Unit has been my default.  &lt;a href="http://rspec.info/"&gt;RSpec&lt;/a&gt; comes highly recommended, though, and claims to make a nice paradigm shift into the BDD world.  Will it make a difference?  I don't know, but it's popular enough among rubyists that I'd be silly not to give it a try and see how I feel about the different nature of test presentation.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Delayed Job&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;I've been a BackgroundJob user ever since my first queue necessitating feature came into existance, and it's never let me down with it's straightforward simplicity.  However, I've felt on occasion that a fuller feature set would be desirable for some of my more granular queueing needs.  &lt;a href="http://github.com/tobi/delayed_job"&gt;Delayed Job&lt;/a&gt; is at the top of the list as far as widespread adoption goes, so if I'm going to jump to a fuller library this is the first place to look.&lt;br /&gt;&lt;br /&gt;I love playing with new toys, so the weeks ahead promise to be filled with new and exciting blog material chronicling my exploration of that which is new and popular. Enjoy!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-2993085255661229766?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/2993085255661229766/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=2993085255661229766' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/2993085255661229766'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/2993085255661229766'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/02/new-playground.html' title='A New Playground'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-9161016462130240571</id><published>2010-02-17T11:16:00.002-06:00</published><updated>2010-02-17T11:16:54.011-06:00</updated><title type='text'>Time for a change</title><content type='html'>I've been going as "codeclimber" for a long time with the normal ".blogspot.com" domain, but honestly it's just because it was easier to setup that way and I've been too lazy to go register my freaking name as my domain name.  Well, I've finally done it, so from now on "codeclimber.blogspot.com" redirects to "blog.ethanvizitei.com".  Watch out, now it's personal!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-9161016462130240571?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/9161016462130240571/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=9161016462130240571' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/9161016462130240571'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/9161016462130240571'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/02/time-for-change.html' title='Time for a change'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-8411834056299996811</id><published>2010-02-11T13:41:00.001-06:00</published><updated>2010-02-15T10:27:30.139-06:00</updated><title type='text'>Making it Happen</title><content type='html'>I spend a lot of time trying to make my code good.  Maybe too much.  I love my codebase like a child.  When presented with a problem, my first instinct is to make it somehow fit into what my system already does.  Sometimes that's not going to be an easy proposition, though, and in SOME of those cases we need to let the business needs get top priority and just make it happen.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;An Example&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;We have a vendor we ship claims to from time to time, and they use a manual process.  Because they are in the public sector, they require a certain format for their claims, which includes a lot of unnecessary Excel formulas because that's what they audit based on (the formulas themselves).  Does it matter that we could generate an excel file that has every number they need along the way?  No, they must have the FORMULAS in the spreadsheet, or it isn't a valid claim.&lt;br /&gt;&lt;br /&gt;Unfortunately for me, our spreadsheet library we use (the "spreadsheet" ruby gem) doesn't have support for formulas yet.  So our options were to A) spend some time to work on this gem and add formula support (time we didn't really have).  Or B) make the spreadsheet generation a partly manual process on our end as well (Blech, we actually did this for a quarter, generate our numbers and copy them into the claim template one at a time).  &lt;br /&gt;&lt;br /&gt;This quarter, I went a different route:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;Sub Main&lt;br /&gt;  Dim Doc As Object&lt;br /&gt;  Dim InputSheet As Object&lt;br /&gt;  Dim TemplateSheet As Object&lt;br /&gt; &lt;br /&gt;  SetupVariables(Doc,InputSheet,TemplateSheet,ClaimSheet)&lt;br /&gt;  RowNum = 1&lt;br /&gt;  NameCell = InputSheet.getCellByPosition(2,RowNum)&lt;br /&gt;  Do While NameCell.Type &lt;&gt; com.sun.star.table.CellContentType.EMPTY&lt;br /&gt;    ClaimRow = InputSheet.Rows(RowNum)&lt;br /&gt;    ClaimSheet = Doc.createInstance("com.sun.star.sheet.Spreadsheet")&lt;br /&gt;    Doc.Sheets.insertByName(NameCell.getString(), ClaimSheet)&lt;br /&gt;    CopyTemplateToNewClaim(TemplateSheet,ClaimSheet)&lt;br /&gt;    ProcessClaim(InputSheet,ClaimSheet,1)&lt;br /&gt;    RowNum = RowNum + 1&lt;br /&gt;    NameCell = InputSheet.getCellByPosition(2,RowNum)&lt;br /&gt;  Loop&lt;br /&gt;End Sub&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Yep, I used OpenOffice and BASIC and just scripted the damn thing.  Ugly?  Yes.  But it worked, and we don't have to manually calculate our claims.  I just generate our spreadsheet that has the claim numbers, and use the macro to copy each row into it's own template sheet in the workbook.  It doesn't fit into our rails application at all, but we made it happen, and we're collecting on those claims.&lt;br /&gt;&lt;br /&gt;If you're beating your head against the wall in frustration, sometimes it pays to step back and resort to plain old hackery, cause at the end of the day your customers only really care that it works.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-8411834056299996811?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/8411834056299996811/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=8411834056299996811' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/8411834056299996811'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/8411834056299996811'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/02/making-it-happen.html' title='Making it Happen'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-1698975597808087282</id><published>2010-02-04T12:54:00.001-06:00</published><updated>2010-02-04T12:54:58.805-06:00</updated><title type='text'>Tricky Little find_in_batches (watch your :select clause)</title><content type='html'>If you are like me, you have a few background processes that deal with tons of data (reporting, etc).  To run these processes, you may use ActiveRecord's "find_in_batches" method which allows you to only pull so many records into memory at a time (a good idea when processing large numbers of records).  You may also explicitly use a "select" clause in your AR queries from time to time to only pull in the fields you need for a given purpose.  Be wary, as I tripped up over something silly today and you should know about it.&lt;br /&gt;&lt;br /&gt;You see, I had something like this:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;&lt;pre&gt;class SomeModel &lt; ActiveRecord::Base&lt;br /&gt;  named_scope :only_essentials,&lt;br /&gt;              :select=&gt;"some_models.info,some_models.name"&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;class BackgroundProcess&lt;br /&gt;  def run&lt;br /&gt;    SomeModel.only_essentials.&lt;br /&gt;              find_in_batches(:batch_size=&gt;200) do |batch| &lt;br /&gt;      #....some processing of each record in the batch&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;The problem?  It was running way too fast.  Over in mere seconds.  You might think, "Hey, that's not such a bad problem to have", until you realize that the reason it was running so fast is it was only processing the first 200 records (that is, the first batch).&lt;br /&gt;&lt;br /&gt;Why?  It's simple if you know how "find_in_batches" works.  It uses "order" and "limit" to order your models by primary key and limit the result set.  What field did I not include in my named scope?  "some_models.id" is correct.  Add that field so your reference point is maintained, and everything runs as expected.&lt;br /&gt;&lt;br /&gt;Cheers,&lt;br /&gt;&lt;br /&gt;~Ethan&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-1698975597808087282?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/1698975597808087282/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=1698975597808087282' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/1698975597808087282'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/1698975597808087282'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/02/tricky-little-findinbatches-watch-your.html' title='Tricky Little find_in_batches (watch your :select clause)'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-5010578277254608726</id><published>2010-02-01T14:42:00.000-06:00</published><updated>2010-02-01T14:42:39.273-06:00</updated><title type='text'>Smart quotes and dumb errors</title><content type='html'>Wow, this one just sucked.  :)&lt;br /&gt;&lt;br /&gt;I'll make it quick.  We have a model in our rails app that captures large strings and then displays an abbreviated version to the user later. For example:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;class Narrative &amp;lt; ActiveRecord::Base&lt;br /&gt;  validates_presence_of :text&lt;br /&gt;&lt;br /&gt;  def text_in_brief&lt;br /&gt;    return nil if text.nil?&lt;br /&gt;    (text.size &amp;gt; 20) ? (text[0..19] + "...") : text&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Hopefully that made sense.  We want an ellipses to indicate that this value continues on for some time, so if it's longer than 20 characters, just cut it off and add the three periods.&lt;br /&gt;&lt;br /&gt;Enter JSON.  We sometimes want to send this value to JSON.  And every now and then the to_json method blares this exception:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;JSON::GeneratorError: source sequence is illegal/malformed&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;But only very rarely.  What the hell is going on here?&lt;br /&gt;&lt;br /&gt;Analyzing the data shows that all the strings that cause this explosion have one thing in common - a smart quote as the 19th or 20th character.  I mean the one that is actually represented as "\342\200\235".  See where this is going?  &lt;br /&gt;&lt;br /&gt;By splitting off the string at the character level, it's possible to cut off the smart quote somewhere in the middle, because a smart-quote is actually 3 characters long. &amp;nbsp;This causes an invalid string, which bombs the to_json call. &amp;nbsp;F@(#!&lt;br /&gt;&lt;br /&gt;Quick and Dirty solution?&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;class Narrative &amp;lt; ActiveRecord::Base&lt;br /&gt;  validates_presence_of :text&lt;br /&gt;&lt;br /&gt;  before_save :escape_smart_quotes&lt;br /&gt;  &lt;br /&gt;  def escape_smart_quotes&lt;br /&gt;    self.name.gsub! "\342\200\235", '"' &lt;br /&gt;  end  &lt;br /&gt;&lt;br /&gt;  def text_in_brief&lt;br /&gt;    return nil if text.nil?&lt;br /&gt;    (text.size &amp;gt; 20) ? (text[0..19] + "...") : text&lt;br /&gt;  end&lt;br /&gt;end&lt;/pre&gt;&lt;pre style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;/pre&gt;&lt;pre style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;/pre&gt;&lt;br /&gt;Maybe when I've cooled off I'll do this better and extract it to be usable in other models.  For now, just be glad I figured it out at all.  :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-5010578277254608726?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/5010578277254608726/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=5010578277254608726' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/5010578277254608726'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/5010578277254608726'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/02/smart-quotes-and-dumb-errors.html' title='Smart quotes and dumb errors'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-2393013921665489923</id><published>2010-01-27T13:08:00.001-06:00</published><updated>2010-01-27T13:15:04.282-06:00</updated><title type='text'>gem Read-through: slim_scrooge</title><content type='html'>Ok, new project.  I believe it's dangerous to rely on code that you do not understand.  As a rails-developer, I have tons of plugins and gems that I do not understand.  See the problem?&lt;br /&gt;&lt;br /&gt;To rectify this, I'm making it my goal to read through one of my main project's many dependancies each week.  Two side benefits:&lt;br /&gt;&lt;br /&gt;1) I will probably be better at writing my own open source libraries if I've seen a larger sample of how they're usually constructed.&lt;br /&gt;&lt;br /&gt;2) code reading is good for you, but it's tough to find time to just sit down and crack open a library.  This will give me a good reason.  &lt;br /&gt;&lt;br /&gt;So without further ado, today I'm doing a read-through of &lt;a href="http://github.com/sdsykes/slim_scrooge"&gt;http://github.com/sdsykes/slim_scrooge&lt;/a&gt;, a great ActiveRecord optimizing library that has made a difference in the performance of my current main project.  Don't expect anything linear here, I'm just going to record my notes and if you want to use them too you're welcome to them.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Slim Scrooge&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;i&gt;The point of the slim scrooge library is to moniter your active record queries, and optimize them so that they only pull back the columns that you end up using in that section of code.  Let's find out how it works:&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;NOTES&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;1)  First thing I noticed.  There is a test directory, but no tests.  Problem?  maybe....&lt;br /&gt;&lt;br /&gt;2)  Scratch my first note.  It appears that SlimScrooge::ActiveRecordTest actually runs the ActiveRecord tests that are included with Rails.  I guess this makes sense, as a regression test.  Anything that filters activerecord should still pass the activerecord test suite.  Still, this definitely means that the code itself is not under test.  The gem could do nothing, and the tests would still go green.  I'm not here to judge, though.  I've written my own share of untested code.&lt;br /&gt;&lt;br /&gt;3) first included file in the main library is a C extension called 'callsite_hash'. Looking in the /ext directory of the plugin.  My "C" is a little rusty since I've been out of it for 3 years, but I think I get that it's defining the global ruby function "callsite_hash", and mapping it to the c function "rb_f_callsite" in this callsite_hash.c file.  I don't know what it does yet, as it's the rb_f_callsite function is a little dense for my limited C skills, but maybe it will make more sense in context.  So, moving on.&lt;br /&gt;&lt;br /&gt;4) Next inclusion is SlimScrooge::SimpleSet (a subclass of Hash, /lib/slim_scrooge/simple_set.rb).  This class stores a set of keys based on a submitted array, all mapped to the value "true".  Because of the syntax, each time an element is added, it will only create a new entry if it's not already in the set.  So basically it's a set of unique elements with some helper methods to keep operations restricted to only the keys (like a collect method that only runs over the keys array).  Knowing what the gem does, at this point I'm guessing this is the structure that column names are stored in so you know which ones were used and which ones weren't after a query.  We'll see.&lt;br /&gt;&lt;br /&gt;5) Moving on to /lib/slim_scrooge/callsites.rb, which defines the class SlimScrooge::Callsites (no parent class).  This class only has static methods, so I guess it's never instantiated.  It has a class-level variable called @@callsites, which is a hash.  Write access to the hash is synchronized through the uses of a Mutex which is instatiated at the time of class definition as a class-level constant (SlimScrooge::Callsites::CallsitesMutex).  Given that I don't know what's being stored here, I don't feel like I can accurately analyze it.  Therefore, I'm jumping over to the top-level algorithm in /lib/slim_scrooge/slim_scrooge.rg&lt;br /&gt;&lt;br /&gt;6) lib/slim_scrooge/slim_scrooge.rb definately is the meat of the gem.  SlimScrooge uses good old alias_method_chain to bring about "find_by_sql_with_slim_scrooge" (defined in the gem) and "find_by_sql_without_slim_scrooge" (the original "find_by_sql" method in ActiveRecord).  This is how the gem inserts itself into every activerecord call.  In the "find_by_sql_with_slim_scrooge", we see what's being done step by step:  &lt;br /&gt;&lt;br /&gt;A) if the sql passed in is an array (that is, a custom query directly from a programmer writing Model.find_by_sql("blah")), don't bother.  Let it run like normal.&lt;br /&gt;B) if this "callsite" has been seen before, try to optimize it.&lt;br /&gt;C) if it hasn't been seen before, try to monitor it&lt;br /&gt;D) otherwise, let it go (find_by_sql_without_slim_scrooge)&lt;br /&gt;&lt;br /&gt;7) So what is a "callsite"?  How do you know if you've been here before?  Well, apparently that's what the C extension is for "callsite_hash.c".  The query is passed into this black-magic-extension which by some occult method creates a unique key for it (called a callsite_key).  This is then stored in that class-level hash in the "Callsites" class.  &lt;br /&gt;&lt;br /&gt;8)There is logic written in here to pass it through unoptimized if the query is not "scroogable", and there are several conditions that meet that.  For one, if there's any joining, it won't bother.  Also, if it's not a "select" query (that is, it doesn't start with SELECT, include the expected table name, and have a "FROM" in it). [These were limitations I was unaware of before].&lt;br /&gt;&lt;br /&gt;9)  The monitoring of a query is done by attaching a MonitoredHash to each row in the first query.  This hash maintains a reference to the callsite, and can be configured to not monitor certain columns.  Anytime a column is accessed that was previously unseen, the callsite is notified.&lt;br /&gt;&lt;br /&gt;10) next time the query is run, the callsite has a record of which columns were used and uses "scrooged_sql()" to only produce a select query for those columns.&lt;br /&gt;&lt;br /&gt;Well, this was fun.  I feel like I've learned a bit about how my site works under the hood, and a little more qualified to comment on the use of this gem in the future.  Here are a few things I learned that are not directly about the slim_scrooge gem:&lt;br /&gt;&lt;br /&gt;1)  The Mutex class can be used to synchronize access to an object.&lt;br /&gt;&lt;br /&gt;2) ActiveRecord appears to direct all queries through the "find_by_sql" method.  That's the place to hit it if you want to get in some sort of filtering.&lt;br /&gt;&lt;br /&gt;3) C extensions for ruby use an "Init_*" method to integrate themselves into the runtime.&lt;br /&gt;&lt;br /&gt;Until next time,&lt;br /&gt;&lt;br /&gt;~Ethan&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-2393013921665489923?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/2393013921665489923/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=2393013921665489923' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/2393013921665489923'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/2393013921665489923'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/01/gem-read-through-slimscrooge.html' title='gem Read-through: slim_scrooge'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-1393766419095546270</id><published>2010-01-22T14:04:00.000-06:00</published><updated>2010-01-22T14:04:38.732-06:00</updated><title type='text'>Un-Joining your Scopes</title><content type='html'>I had a suprising problem today when one of my tests started failing after I had done a little refactoring.  You see, I'd had this ActiveRecord class that was doing some reporting (massive data extraction) and it was originally using a pretty ugly SQL statement:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;Model.find_in_batches("Massive SQL statement") do |models|&lt;br /&gt;  models.each do |model|&lt;br /&gt;    models_to_compare = Model.scope_with_other_joins&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Naturally, I wanted to make that SQL statement go away, and use a bundle of named scopes instead.  I had good tests wrapping this area already, so I set autotest running and started hacking away:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;Model.scope_with_some_joins.find_in_batches do |models|&lt;br /&gt;  models.each do |mdl|&lt;br /&gt;    other_comparisons = Model.scope_with_other_joins&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Note that both queries (line 1 and line 3) have similar joins involved.  Now, my tests started failing on line 3 -- I get a runtime error showing me that for some reason when running the second query it's maintaining the join scope from the outer query, giving me an "ambigious column" error because there is one table that is joined in from both queries.  Now, this "some reason" is really just that this is the way it's designed....the whole point of a "scope" is to be able to nest other things inside of it.  In my case, though, I needed the line three query to be totally seperate and distinct.  It took some googling and stack-overflowing (love that community), but here's what I discovered:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;Model.scope_with_some_joins.find_in_batches do |models|&lt;br /&gt;  models.each do |mdl|&lt;br /&gt;    Model.send(:with_exclusive_scope) do&lt;br /&gt;      other_comparisons = Model.scope_with_other_joins&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;this protected "with_exclusive_scope" method resets the scope entirely for that model within that block.  Thus, you're able to have a clean query regardless of the surrounding context.  Now, I'm not saying that this hack of sending a protected method is a good idea anytime, but in my case I didn't have an easy way to get around it (other than leaving the SQL statement in place). It's still cleaner to me than having that giant SQL string I had in the code before, and maybe once I do a little more reading on the subject I'll get an even better idea.  Other suggestions welcome!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-1393766419095546270?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/1393766419095546270/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=1393766419095546270' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/1393766419095546270'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/1393766419095546270'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/01/un-joining-your-scopes.html' title='Un-Joining your Scopes'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-3312324548765498930</id><published>2010-01-21T10:46:00.000-06:00</published><updated>2010-01-21T10:46:52.840-06:00</updated><title type='text'>Autotest saves the day</title><content type='html'>This is not a tutorial on how to setup autotest on your machine.  People have already done that plenty of times, a good one is &lt;a href="http://ph7spot.com/musings/getting-started-with-autotest"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I'm just writing to say what a big difference it's made for me.  My first job was at a "test-everything" development shop.  I really agreed with the notion of having solid tests surrounding all possible code, and running them before every commit/deployment.  The problem for me arose when I moved to start my own business.  Without all that peer pressure (I'm working as the only development talent currently), it's easy to fall off the wagon.  Especially when using tools that don't exactly integrate testing into your workflow.  Typically I'd write tests around all new code, and run them before committing, but if I was making a quick change just to format something better or to fix a bug, I was often hurried enough to not only write no new tests, but to not run any of my current tests before committing just to get the damn thing out the door.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://github.com/seattlerb/autotest-rails"&gt;Autotest&lt;/a&gt; silently runs in the background, running your tests anytime you change a file.  Not only that, it can be configured to use &lt;a href="http://growl.info/"&gt;Growl&lt;/a&gt; to notify you every time a test breaks.  Now I don't even have to think about it.  Every time I press command-S, my tests get run and I know that at the least I haven't broken anything that's currently covered.&lt;br /&gt;&lt;br /&gt;Of course the limitation is that you must write tests in the first place.  Having your tests run all the time without any real coverage doesn't save you much.  For me, though, just knowing that my tests are being run consistently gives me more motivation to write more of them, more often.  If you're a rails developer, just try it.  It's really not too much of a time commitment to set up, and if you're like me you'll be suddenly one giant step closer to being the unit-testing-guru that you always wished you were.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-3312324548765498930?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/3312324548765498930/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=3312324548765498930' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/3312324548765498930'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/3312324548765498930'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/01/autotest-saves-day.html' title='Autotest saves the day'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-4033475026199175744</id><published>2010-01-11T16:40:00.000-06:00</published><updated>2010-01-11T16:40:39.394-06:00</updated><title type='text'>My favorite coding music</title><content type='html'>I thought I'd kick off the new year on a slightly less technical note than usual: the tunes I play to boost my productivity.&amp;nbsp; Silence kills me and causes my mind to drift; I personally find that having the right kind of music playing low in the background keeps me going in the right direction when I'm trying work.&lt;br /&gt;&lt;br /&gt;This is a very personal subject, so I can't say that what works for me will work for you, but it can't hurt to try out what I'm playing and see what it does for your work life.&amp;nbsp; So, without further ado, here are my 5 top albums for cranking out code:&lt;br /&gt;&lt;br /&gt;&lt;b&gt;5) &lt;a href="http://www.classicsonline.com/catalogue/product.aspx?pid=314230"&gt;'Marimbach' - Beverley Johnston&lt;/a&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Soft, compelling, and beautiful.  This is an album made entirely from classical marimba pieces (mostly solos). It's not distracting at all, but isn't so slow or quiet as to make you lethargic.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;4) &lt;a href="http://www.amazon.com/Flight-Cosmic-Hippo-Fleck-Flecktones/dp/B000002LOZ"&gt;'Flight of the Cosmic Hippo' - Bela Fleck and the Flecktones&lt;/a&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;A nostalgic favorite of mine from high school, this album is a collection of eclectic instruments playing a genre that is entirely it's own.  Don't be thrown off by the funky title, these are some serious musicians (including the great Victor Wooten) and it is pretty high-energy the whole way through.  My favorite for pressure situations. If I have a deadline today, this is what I play.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;3) &lt;a href="http://www.rhapsody.com/album/hush--1992"&gt;'Hush' - Yo-Yo Ma &amp; Bobby McFerrin&lt;/a&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;This is a collection of duets between a pair of virtuosos: Yo-Yo Ma, the master cellist, and Bobby McFerrin, the most amazing vocalist I've ever listened to. This is a man who has truly made his voice an instrument, and it's almost trance-inducing.  I like to put this on first thing in the morning when I sit down to start, although I can't articulate why.  Just give it a chance.  It's strange at first, but once it grows on you it's an indispensable part of your collection.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;2) &lt;a href="http://www.cduniverse.com/search/xx/music/pid/6713256/a/Evolution.htm"&gt;'Evolution' - Stefon Harris &amp; Blackout&lt;/a&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;As a mallet player myself, there are few contemporary musicians I love as much as Stefon Harris.  This is much richer and fuller music than anything else on my list, but I think it's perfect for the "deep coding" work.  Unhurried, but high energy, and smooth without being "easy listening" (which I despise).&lt;br /&gt;&lt;br /&gt;&lt;b&gt;1) &lt;a href="http://itunes.apple.com/us/album/libertango/id215555399"&gt;'Libertango' - Gary Burton&lt;/a&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;My favorite album in my collection, it's steady rhythm always makes me feel motivated.  If you try nothing else on my list, give this a listen, especially track one (the title track).  It's very dense music, but so accessible even to one who is not a musician.  I can't recommend it enough.&lt;br /&gt;&lt;br /&gt;So if you're looking for a few new tunes to keep you going during those times of loose focus, see if you can find something you like among my favorites, and drop me a line if you do!  I've got more in my back pocket, I just can't share it all in one blog post.  :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-4033475026199175744?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/4033475026199175744/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=4033475026199175744' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/4033475026199175744'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/4033475026199175744'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2010/01/my-favorite-coding-music.html' title='My favorite coding music'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-8591167297993997314</id><published>2009-11-30T14:05:00.000-06:00</published><updated>2009-11-30T14:05:45.385-06:00</updated><title type='text'>Great memory profiler</title><content type='html'>Just wanted to link up this post that's helping me solve a tough memory problem with a really lightweight bit of code.&amp;nbsp; If you've got some memory issues with any bit of code in your rails app, go check this out and make use of it:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://scottstuff.net/blog/2006/08/17/memory-leak-profiling-with-rails"&gt;http://scottstuff.net/blog/2006/08/17/memory-leak-profiling-with-rails&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;It's over three years old, but still works with the current version of rails quite well.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-8591167297993997314?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/8591167297993997314/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=8591167297993997314' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/8591167297993997314'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/8591167297993997314'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/11/great-memory-profiler.html' title='Great memory profiler'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-5257787540936544913</id><published>2009-11-19T11:01:00.001-06:00</published><updated>2009-11-19T11:02:14.552-06:00</updated><title type='text'>How to be Humble</title><content type='html'>I installed the new &lt;a href="http://github.com/flyerhzm/rails_best_practices"&gt;Rails Best Practices&lt;/a&gt; gem today, and I have to admit it kind of kicked me in the fork.  In a good way.&lt;br /&gt;&lt;br /&gt;Sometimes it's good to be reminded of just how many things you've done wrong, as it can really motivate you to dig in and get some of that stuff fixed.  As for me and my statup's codebase? I have 291 violations of the best practices that need to be addressed.  Oh well, at least I've moved from blissful ignorance to gut-wrenching enlightenment.&lt;br /&gt;&lt;br /&gt;Want to get schooled on your own code base?  Need a good swift kick yourself?  Follow these steps:&lt;br /&gt;&lt;br /&gt;&lt;b&gt;1) Install ruby_best_practices&lt;/b&gt;&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size: xx-small;"&gt;&lt;span style="font-size: x-small;"&gt;sudo gem install rails_best_practices --source http://gemcutter.org&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;&lt;b&gt;2) cd to your root directory of your rails project&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;3) execute this line: &lt;/b&gt;&lt;br /&gt;&lt;pre&gt;rails_best_practices . --debug&lt;br /&gt;&lt;/pre&gt;(I used the debug flag because it tells you what file was just examined, that way if there's an explosive error and the process bombs, you'll know which file to investigate).&lt;br /&gt;&lt;br /&gt;Happy self-humbling!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-5257787540936544913?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/5257787540936544913/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=5257787540936544913' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/5257787540936544913'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/5257787540936544913'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/11/how-to-be-humbled.html' title='How to be Humble'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-1475932637927027283</id><published>2009-11-18T11:58:00.000-06:00</published><updated>2009-11-18T11:58:17.779-06:00</updated><title type='text'>What to do with expensive and schema-less data in Rails</title><content type='html'>OK, the title for this post definitely needs work, but this is an important topic. &lt;br /&gt;&lt;br /&gt;Scenario:  I have some invoice that needs to be generated once a month for each customer in my database. Each invoice has a table on it that specifies the amount the customer is being charged for each category of services.   incomplete ruby-code follows:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;class Invoice&lt;br /&gt;  def data_row(category)&lt;br /&gt;    [category.name,&lt;br /&gt;     category.description,&lt;br /&gt;     self.expenses_for(category),&lt;br /&gt;     self.expense_ratio(category)]&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def invoice_data_table&lt;br /&gt;    Category.all.map{|c| data_row(c)}&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;for the sake of argument, assume that "expenses_for" and "expense_ratio" are both defined and are expensive calculations.  Also, the categories list changes from month to month.  Now, how do I store this data so that I don't need to re-calculate it if I need to re-print this invoice later?&lt;br /&gt;&lt;br /&gt;1) &lt;b&gt;static columns&lt;/b&gt;&lt;br /&gt;This is probably the worst one, and the one I see most commonly in large corporate applications.  Basically you write 50 new columns in your invoices table, each one looking like this: category_1_expense_ratio, category_1_expense, category_2_expense_ratio, etc, etc.&lt;br /&gt;&lt;br /&gt;Retrieval is efficient, but every time the category list changes you have to add new columns to your database, and the code for populating these fields is probably ugly and repetitive. &lt;br /&gt;&lt;br /&gt;2) &lt;b&gt;child records in join table&lt;/b&gt;&lt;br /&gt;Better, but still somewhat expensive in relation to it's purpose.  We add an invoice_category_expenses table, and it's modeled as  "category_id, invoice_id, expense_ratio, expenses".  &lt;br /&gt;&lt;br /&gt;This works, and is more robust in the face of change, but in order to reprint the invoice that means you have to fetch 20 other records besides just the invoice.  They're not huge, so it's not a real performance problem, but it does feel wrong to me to grace this flat data table with all the trimmings of a full model class.&lt;br /&gt;&lt;br /&gt;3) &lt;b&gt; serialized data-structure&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;This is what I ended up going with, and I'm maddeningly happy with my decision. For readonly structured primitive data that just needs to be stored for possible re-examination (ie, no other behavior necessary) I don't think you can beat it. &lt;br /&gt;&lt;br /&gt;Rather than stuffing it in another table, or making your invoices table a schema nightmare, you just add one more column: category_expenses_data.  It's a text column with a large size limitation. Now in your invoice model you write the following:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;class Invoice&lt;br /&gt;  serialize :category_expenses_data,Array&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This specifies to rails that this column is going to be a serialized Array (basically a string representation of an array that can easily be re-instantiated).  Now when creating the invoice, you generate the expensive data table, and store it by saying "invoice.category_expenses_data = invoice.invoice_data_table".  The expensive calculations are done once and stored in the database.  When you need to reprint the invoice, you can just ask for "invoice.category_expenses_data" and it will give you back your array (deserialized from the database) ready to be used to generate your PDF again or whatever.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-1475932637927027283?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/1475932637927027283/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=1475932637927027283' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/1475932637927027283'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/1475932637927027283'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/11/what-to-do-with-expensive-and-schema.html' title='What to do with expensive and schema-less data in Rails'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-1366845024783216013</id><published>2009-11-17T14:28:00.000-06:00</published><updated>2009-11-17T14:28:43.051-06:00</updated><title type='text'>RubyMine 2.0</title><content type='html'>I've been using TextMate for Rails development ever since I started programming on a Mac.  I've got to say, though, that after trying out the new &lt;a href="http://www.jetbrains.com/ruby/"&gt;RubyMine IDE&lt;/a&gt;, I'm considering upgrading.  Don't get me wrong, I &lt;b&gt;LOVE&lt;/b&gt; &lt;a href="http://macromates.com/"&gt;TextMate&lt;/a&gt;.  It's been nothing but good to me, and I still plan on using it for Scala and other editing tasks.  That being said, my first IDE when I was just starting to program was Eclipse, and in that regard RubyMine feels like I'm coming home.&lt;br /&gt;&lt;br /&gt;Cool things I've liked so far:&lt;br /&gt;&lt;br /&gt;1) &lt;b&gt;GUI test running reminiscent of JUnit&lt;/b&gt;.  Yeah, I know, it's just eye candy, but what can I say? I like it, and my preference should count for something if I'm the one doing the development.    &lt;br /&gt;&lt;br /&gt;2) &lt;b&gt;GUI Git integration&lt;/b&gt;.  Point and click, baby!&lt;br /&gt;&lt;br /&gt;3) &lt;b&gt;intellisense/code completion/inspection/warnings&lt;/b&gt;.  These can be annoying if things are too bad.  Your code looks like it's been torn apart by a highschool honors english teacher, past a certain point.  But overall I really prefer having the analysis and information built visually into my editor (another eclipse throwback).&lt;br /&gt;&lt;br /&gt;Somethings that make me want to stick with TextMate:&lt;br /&gt;&lt;br /&gt;1) &lt;b&gt;Speed&lt;/b&gt; Compared to RubyMine, TextMate is blazing fast.  It loads quickly, searches quickly, and I definately feel more nimble.&lt;br /&gt;&lt;br /&gt;2) &lt;b&gt;Simplicity&lt;/b&gt;.  TextMate is an editor, and a good one.  Your screen isn't cluttered with a hundred helpful windows.&lt;br /&gt;&lt;br /&gt;I guess I've got some thinking to do before I lay down any money, but I'll post what I decide when I get around to it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-1366845024783216013?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/1366845024783216013/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=1366845024783216013' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/1366845024783216013'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/1366845024783216013'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/11/rubymine-20.html' title='RubyMine 2.0'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-6359923607265661037</id><published>2009-09-30T12:18:00.001-05:00</published><updated>2009-09-30T12:22:57.964-05:00</updated><title type='text'>why your named_scope isn't behaving the way you want</title><content type='html'>Let me go into this as briefly as possible.&amp;nbsp; I had the following named_scope in one of my rails models:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;class Alert &lt; ActiveRecord::Base&lt;br /&gt;  named_scope :in_the_past,&lt;br /&gt;              :conditions=&gt;&lt;br /&gt;                ["start_datetime &lt;= ?",DateTime.now]&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;Seems straightforward, right?  I thought so too.  I used this "in_the_past" method to replace a poorly written load-and-filter algorithm.  This would use much less memory and be much quicker.  Only one problem it didn't work.It passed all the unit tests.  It seemed to work when it was deployed.  But over the next few days more and more people were calling me saying they weren't seeing the alerts they expected to.  I tried to figure out the problem (maybe related to timezones? or bad data?), and every time I deployed a fix all my users would tell me it looked good again; but then a day or so later everyone was complaining once more. I finally got fed up with it and went back to the old way (loading a collection and filtering out the items in the past using a "select" iterator), cursing the day I'd ever started down this road. And then today, I saw this in a &lt;a href="http://pivotallabs.com/users/dwfrank/blog/articles/1023-standup-9-29-2009-half-the-battle-edition"&gt;blog post from pivotal labs&lt;/a&gt;:&lt;b&gt;Public Service Announcement: your named_scope's get evaluated when the class is loaded, not when an instance is created.&lt;/b&gt;I felt like I'd been hit by a truck.  The "DateTime.now" call was being evaluated at server startup, so for the first few minutes it seemed fine.  However, as the amount of time since class-load increased, the "in the past" scope was missing more and more of the items that were actually now in the past because it's measuring stick was stuck at the time of creation.  As a result I dug through my codebase and found no less than 7 scopes to refactor into using a lambda instead so that the system time would be loaded at time of execution instead:&lt;pre&gt;class Alert &lt; ActiveRecord::Base&lt;br /&gt;  named_scope :in_the_past,lambda{&lt;br /&gt;                {:conditions=&gt;&lt;br /&gt;                   ["start_datetime &lt;= ?",DateTime.now]}&lt;br /&gt;              }&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;Thanks to those pros at &lt;a href="http://pivotallabs.com/"&gt;Pivotal Labs!&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-6359923607265661037?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/6359923607265661037/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=6359923607265661037' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/6359923607265661037'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/6359923607265661037'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/09/why-your-namedscope-isnt-behaving-way.html' title='why your named_scope isn&apos;t behaving the way you want'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-55369687711431366</id><published>2009-09-23T18:25:00.001-05:00</published><updated>2009-09-30T12:07:36.936-05:00</updated><title type='text'>GMail hijacking</title><content type='html'>So my email account has been compromised. I don't know how, but what I do know is I'm locked out.&amp;nbsp; I'm working on it with google, but the short version of the story is this:&amp;nbsp; someone sent out an email to everyone in my contacts list saying I need money to get home from london.&amp;nbsp; I'm not in london.&amp;nbsp; i don't need money.&amp;nbsp; Ignore this and any other emails that don't sound like me.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-55369687711431366?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/55369687711431366/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=55369687711431366' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/55369687711431366'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/55369687711431366'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/09/gmail-kind-of-sucks-when-it-comes-to.html' title='GMail hijacking'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-6871635983523320493</id><published>2009-09-15T11:02:00.000-05:00</published><updated>2009-09-15T11:02:12.521-05:00</updated><title type='text'>SpriteMe up, Scotty!</title><content type='html'>This morning as I perused my rss aggregator, I came upon an article over at &lt;a href="http://www.ajaxian.com/"&gt;Ajaxian&lt;/a&gt; about a cool new tool for &lt;a href="http://ajaxian.com/archives/sprite-me"&gt;auto-generating CSS Sprites&lt;/a&gt;.  This seemed like a cool idea, but I didn't think much of it.  After all, I've heard of hundreds of projects that try to do something I think would be really useful, and when I go to try it myself I find it's such a pain to get working it's not worth the effort and I may as well accomplish the original task manually.&lt;br /&gt;&lt;br /&gt;However, I decided to give it a try in this case because one of my current startup projects (&lt;a href="http://www.vacationstxt.com/"&gt;VacationsTxt&lt;/a&gt;) was in dire need of some spriting attention.  You see, there were 22 background images on one page, which means load time was pretty much sucking as 22 http requests had to be made just to get all the images alone, nevermind the javascript files and stylesheets.  So I told myself I'd give it 5 minutes to spike this new tool and if it looked like it was going somewhere I'd maybe invest a little more effort.&lt;br /&gt;&lt;br /&gt;Imangine my surprise, 5 minutes later, when I was done.  Yes, done.  &lt;a href="http://www.stevesouders.com/blog/2009/09/14/spriteme/"&gt;SpriteMe&lt;/a&gt; is just a javascript bookmarklet that you run while you're on the page in question.  It auto-magically scans all background images and tries to combine them into a css sprite.  Then you just download the new image, integrate the necessary css changes that have been applied to your page (live, I might add, right while you're watching it), and you're done. &lt;br /&gt;&lt;br /&gt;So how much did SpriteMe help my homepage?  See the screenshot below to find out:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_vTrR73WnSs8/Sq-59OLaVZI/AAAAAAAABBs/3w2tj7-VnIQ/s1600-h/Picture+8.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/_vTrR73WnSs8/Sq-59OLaVZI/AAAAAAAABBs/3w2tj7-VnIQ/s400/Picture+8.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;I don't know if that's readable or not, but what it says is this "21 requests eliminated".&amp;nbsp; That's 21 fewer http calls that my server has to handle for every visit to the homepage, and 21 fewer requests the user has to wait through on load.&amp;nbsp; &lt;br /&gt;&lt;br /&gt;More importantly, how much work did it save me?  Well, being no graphics expert, I predict I would have spend 4-5 hours trying to get the sprites right for my homepage.  Doing all that in 5 minutes is impressive. Most impressive.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.stevesouders.com/"&gt;Steve Souders&lt;/a&gt;, you've made my day.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-6871635983523320493?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/6871635983523320493/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=6871635983523320493' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/6871635983523320493'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/6871635983523320493'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/09/spriteme-up-scotty.html' title='SpriteMe up, Scotty!'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_vTrR73WnSs8/Sq-59OLaVZI/AAAAAAAABBs/3w2tj7-VnIQ/s72-c/Picture+8.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-1190315650469061381</id><published>2009-09-11T09:16:00.004-05:00</published><updated>2009-09-11T10:58:54.009-05:00</updated><title type='text'>Browser Caching and Rails</title><content type='html'>Alternative title: "Your debugger can't debug what it doesn't execute"&lt;br /&gt;&lt;br /&gt;At my startup, we had been experiencing an intermittent user problem for the last few days, with no clue as to the cause.&lt;br /&gt;&lt;br /&gt;Users have been reporting that their "alerts" aren't showing up when they login...sometimes....and it goes away a few minutes later....sometimes.&lt;br /&gt;&lt;br /&gt;Anytime I hear a report like that, I start dreading the work ahead, because if it's only "sometimes", there's probably not an easy way to reproduce the bug reliably.  It could be data-dependant, or a browser quirk, or dependant on the phase of the moon for all I know.&lt;br /&gt;&lt;br /&gt;So I started digging in to our code base.  Alerts were displayed based on when certain events had gone into the past.  Was there a time-zone problem?  Was the current time being extracted incorrectly?  Was there a problem rendering the partial that handled that block of the homepage?  Maybe the CSS was displaying it improperly if there were other dynamic elements on the page?  &lt;br /&gt;&lt;br /&gt;I started throwing logging in at various points through this controller action.  Hours later, reports were still coming in (slowly, so not everyone was experiencing this problem), and the logs looked fine.  Then I started to notice something.  The people who were complaining the most, had the fewest log reports.  In fact, the production log showed that one of the users who was complaining had not accessed the page in question for 2 days.&lt;br /&gt;&lt;br /&gt;Enter the browser cache.  &lt;br /&gt;&lt;br /&gt;For some unknown reason (damned if I could figure this out), this user's browser decided to cache her homepage. She wasn't seeing the alerts because her browser had chosen to cache this page at a moment when she didn't have any.  Until it decided to reload the html from that page, she would continue to see no alerts.&lt;br /&gt;&lt;br /&gt;With some quick googling, I found a cache buster snippet, and I'll share it here in case anyone else has had the same problem. To summarize:&lt;br /&gt;&lt;br /&gt;CLUES THAT A BROWSER CACHE IS CAUSING YOUR BUG:&lt;br /&gt;&lt;br /&gt;1) only some users report an issue&lt;br /&gt;2) issue is intermittent&lt;br /&gt;3) no errors being reported from your notification software&lt;br /&gt;4) logging shows no data problems or logic errors&lt;br /&gt;5) symptoms indicate "time displacement" (that's what the page would have looked like "at one point")&lt;br /&gt;6) serious frustration and anger on the part of the software developer&lt;br /&gt;&lt;br /&gt;If you are a rails developer, here's the medicine (this goes in application controller):&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def set_cache_buster&lt;br /&gt; response.headers["Cache-Control"] = &lt;br /&gt;   "no-cache, no-store, max-age=0, must-revalidate"&lt;br /&gt; response.headers["Pragma"] = "no-cache"&lt;br /&gt; response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;set this as a :before_filter on any action you want to protect.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-1190315650469061381?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/1190315650469061381/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=1190315650469061381' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/1190315650469061381'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/1190315650469061381'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/09/browser-caching-and-rails.html' title='Browser Caching and Rails'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-7812756295010119519</id><published>2009-09-02T18:04:00.003-05:00</published><updated>2009-09-02T18:30:45.910-05:00</updated><title type='text'>Forwarding Emails with Ruby, IMAP, and SMTP</title><content type='html'>I would hate to tell you just how much it sucked trying to figure this out.  &lt;br /&gt;&lt;br /&gt;Actually, I would LOVE to tell you.  It would be totally cathartic to just unload a days worth of frustration.  However, that wouldn't help you figure out how to do the one thing mentioned in the title of this post (which is probably the only reason you're here), and it sure as hell wouldn't make me seem like much of a professional.&lt;br /&gt;&lt;br /&gt;So I'll skip the tirade except to say this:  Maybe I should have given up 4 hours ago.&lt;br /&gt;&lt;br /&gt;That out of the way, here was my problem.  I have the email box where all these clients send data files to.  I wanted to sort the ones that had attachments (and only ones with attachments) based on who they came from and forward them to different email addresses.  There are all kinds of nice ways this could have been done if I wasn't constrained by some external requirements (like not being allowed to use forwarding rules on the server the emails were sitting on), so for the sake of this post assume this had to be done in ruby using net/imap to fetch the emails, and net/smtp to send them to wherever they needed to go.&lt;br /&gt;&lt;br /&gt;This was not a fun place to start.  These libraries work well, but I'll be honest when I say that MIME and IMAP are a little low level for my accumulated programming experience.  So I started by using as a jumping off point a script I wrote for another blog post a while back when I was trying to &lt;a href="http://codeclimber.blogspot.com/2008/06/using-ruby-for-imap-with-gmail.html"&gt;access my gmail inbox with ruby&lt;/a&gt;.  I got far enough to where I was polling the email inbox successfully, so now I just needed to figure out the "sending" part.  No big deal, right?  &lt;br /&gt;&lt;br /&gt;I can't tell you how wrong you are.&lt;br /&gt;&lt;br /&gt;You see, you can't just pass off a Net::IMAP::FetchData object into the net/smtp library and watch it fly off into the designated mailbox.  NO SIR!  You see, I had that idea, and this is what I tried:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;require 'net/imap'&lt;br /&gt;&lt;br /&gt;imap = Net::IMAP.new('mail.yourserver.org')&lt;br /&gt;imap.login('username','password')&lt;br /&gt;imap.select("INBOX")&lt;br /&gt;imap.search(["SINCE", "8-Aug-2007"]).each do |id|&lt;br /&gt;  email = imap.fetch(id, "BODY[TEXT]")[0].attr["BODY[TEXT]"]&lt;br /&gt;  Net::SMTP.start('smtp.server.org') do |smtp|&lt;br /&gt;    smtp.sendmail(email, 'from',['to'])&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;It was not very successful.  You see, my email client kept showing me the full MIME body, not just the visible components, and attachments were showing up as base64 encoded strings.  This was not useful.&lt;br /&gt;&lt;br /&gt;I decided the problem was most likely that I was just pulling incomplete MIME information from the imap library and that I needed to find the right attribute to ask for that would have the full MIME package in it's raw form.  Enter "BODY[]".  It might look very much like the "BODY[TEXT]" attribute I used above, but you would be deceived.  "BODY[]" is the whole deal, everything that's there.  If you want the raw message, that's what you need.&lt;br /&gt;&lt;br /&gt;So that's what I tried:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;require 'net/imap'&lt;br /&gt;&lt;br /&gt;imap = Net::IMAP.new('mail.yourserver.org')&lt;br /&gt;imap.login('username','password')&lt;br /&gt;imap.select("INBOX")&lt;br /&gt;imap.search(["SINCE", "8-Aug-2007"]).each do |id|&lt;br /&gt;  email = imap.fetch(id, "BODY[]")[0].attr["BODY[]"]&lt;br /&gt;  Net::SMTP.start('smtp.server.org') do |smtp|&lt;br /&gt;    smtp.sendmail(email, 'from',['to'])&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Presto, it worked!  No, I'm lying, same problem.  So what to do now?  Honestly, after the amount of reading I'd done just to arrive at the above conclusion, I was considering giving up.  But I thought to myself: "This should be a solved problem.  Someone out there should have already written something to take care of dealing with MIME formatting so that I DON'T HAVE TO THINK ABOUT IT!"&lt;br /&gt;&lt;br /&gt;Enter &lt;a href="http://www.rfc20.org/rubymail/"&gt;rubymail&lt;/a&gt;.  It looked very promising since the docs said it would parse and generate MIME format behind the scenes of a simple interface.  "Great", I thought, "Let's try it":&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;require 'net/imap'&lt;br /&gt;require 'rmail'&lt;br /&gt;&lt;br /&gt;imap = Net::IMAP.new('mail.yourserver.org')&lt;br /&gt;imap.login('username','password')&lt;br /&gt;imap.select("INBOX")&lt;br /&gt;imap.search(["SINCE", "8-Aug-2007"]).each do |id|&lt;br /&gt;  email = imap.fetch(id, "BODY[]")[0].attr["BODY[]"]&lt;br /&gt;  email = RMail::Parser.read(email).to_s&lt;br /&gt;  Net::SMTP.start('smtp.server.org') do |smtp|&lt;br /&gt;    smtp.sendmail(email, 'from',['to'])&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This looked promising, but for an unknown reason RubyMail was parsing my emails wrong and making things that were part of the body out to be headers.  It wasn't going well.  Convinced I was on the right track with this whole "Let somebody else deal with the MIME" idea, though, I substituted "tmail" (another good ruby email library).&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;require 'net/imap'&lt;br /&gt;require 'tmail'&lt;br /&gt;&lt;br /&gt;imap = Net::IMAP.new('mail.yourserver.org')&lt;br /&gt;imap.login('username','password')&lt;br /&gt;imap.select("INBOX")&lt;br /&gt;imap.search(["SINCE", "8-Aug-2007"]).each do |id|&lt;br /&gt;  email = imap.fetch(id, "BODY[]")[0].attr["BODY[]"]&lt;br /&gt;  email = TMail::Mail.parse(email).to_s&lt;br /&gt;  Net::SMTP.start('smtp.server.org') do |smtp|&lt;br /&gt;    smtp.sendmail(email, 'from',['to'])&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;there you are, scripted forwarding.  May that help someone out there to not have a day like mine. :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-7812756295010119519?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/7812756295010119519/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=7812756295010119519' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/7812756295010119519'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/7812756295010119519'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/09/forwarding-emails-with-ruby-imap-and.html' title='Forwarding Emails with Ruby, IMAP, and SMTP'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-8663664719665456627</id><published>2009-08-14T10:14:00.004-05:00</published><updated>2009-08-14T11:04:02.476-05:00</updated><title type='text'>Communication is sometimes the problem</title><content type='html'>While waiting for a deployment to run this morning, I decided to quickly peruse the tech industry current events list over at &lt;a href="http://news.ycombinator.com"&gt;Hacker News&lt;/a&gt;.  The first article that caught my eye was one by Jeff Atwood where he discussed &lt;a href="http://www.codinghorror.com/blog/archives/001296.html"&gt;the current state of web programming&lt;/a&gt; within the community and the diverse opinions on the subject that are out there.&lt;br /&gt;&lt;br /&gt;There are several directions I could go in to comment on this piece of work.  I could talk about the Michael Braude quote Jeff uses at the beginning of his article and attempt to discredit it; I could talk about Mr. Atwood and his tendancy to state his positions a bit strongly (although we all do that from time to time); but what really caught my attention was the comments, both on his website and on HackerNews.  Why do we in the tech industry so badly want to paint everyone else in the industry with us as moronic and useless?  I hate to oversimplify, but examine these to see if you can find the common thread:&lt;br /&gt;&lt;br /&gt;&lt;i&gt;Michael Braude: "The reason most people want to program for the web is that they're not smart enough to do anything else. They don't understand compilers, concurrency, 3D or class inheritance. They haven't got a clue why I'd use an interface or an abstract class. They don't understand: virtual methods, pointers, references, garbage collection, finalizers, pass-by-reference vs. pass-by-value, virtual C++ destructors, or the differences between C# structs and classes."&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Summary: Web programmers are stupid.  I know better things.  I'm smarter than them.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;i&gt;Jeff Atwood: "Let's put aside, for the moment, the absurd argument that web development is not challenging, and that it attracts sub-par software developers. Even if that was true, it's irrelevant. "&lt;/i&gt;  &lt;br /&gt;&lt;br /&gt;&lt;b&gt;Summary: People who think web programmers are stupid, are stupid.  I know web programming. I'm smarter than them.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;i&gt;pj: "The web is popular for wannabe's because they get exposure. They think they are hackers, but really they are just factory line workers assembling someone else's code."&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Summary: People who use web frameworks are stupid.  I would never use a web framework.  I'm smarter than them&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;i&gt;jeremymcanally: "I wholeheartedly disagree, mostly because there are a lot of people that think he's the next Fred Brooks. He's not. Far from it. He's teaching a new generation of developers these stupid axioms that make no sense. I didn't realize this until I had a few of the more nonsense ones parroted back at me during an interview once."&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Summary: People who like Jeff Atwood are stupid.  I have no use for Jeff Atwood. I'm smarter than them&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Maybe I'm just feeling morose today, but can't we all just get along?  Exchanging opinions doesn't have to occur primarily by assaulting your opponent's intelligence.  Maybe the byproducts of near-ubiquitous communication mediums like blogging and comments on news articles show that it's not necessarily great to give everyone a voice.  Next thing you know, intelligent discussion is tarnished by some otherwise-somewhat-intelligent-software-developer-stricken-by-the-temporary-lunacy-that-comes-with-internet-anonymity screaming "YOUR &lt;b&gt;MOM'S&lt;/b&gt; A COMPILER!", and now how will we ever regain the coherent footing of a reasonable discussion?&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Anyone want to take odds on someone stumbling upon this post and telling me I'm an idiot for writing it?  :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-8663664719665456627?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/8663664719665456627/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=8663664719665456627' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/8663664719665456627'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/8663664719665456627'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/08/communication-is-sometimes-problem.html' title='Communication is sometimes the problem'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-5755625327734873953</id><published>2009-06-12T13:02:00.002-05:00</published><updated>2009-08-15T13:00:34.579-05:00</updated><title type='text'>Internet Explorer, may you die a thousand deaths</title><content type='html'>Title says it all.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-5755625327734873953?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/5755625327734873953/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=5755625327734873953' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/5755625327734873953'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/5755625327734873953'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/06/internet-explorer-may-you-die-thousand.html' title='Internet Explorer, may you die a thousand deaths'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-7786411905274568127</id><published>2009-06-04T18:00:00.006-05:00</published><updated>2009-06-05T11:08:19.370-05:00</updated><title type='text'>Processing large files with Ruby and Rails</title><content type='html'>&lt;b&gt;UPDATE: As "stefano" pointed out in the comments, the standard "gets" method does indeed accept a parameter.  My fault for not checking the documentation!&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;although we as web developers prefer working on neat features for our websites,  sometimes we need to get down and dirty with data processing. I know, I don't like it any more than you do, but if you want to run a business sometimes you have to do stuff that isn't that fun. That's one of the reasons I liked Ruby; the file library for reading and writing files makes data processing a lot simpler than similar tasks I written in C++, Java, and other mainstream  languages. Usually I just do something like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;file = File.open("some_file")&lt;br /&gt;#read all the contents of the file into "str" variable&lt;br /&gt;str = file.read&lt;br /&gt;file.close&lt;br /&gt;...#do some processing...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt; Wow, that was easy!  However, sometimes code like this just won't cut it. For example: if your file is too big to read into memory all wants and could cause performance issues for the server, you may want to process the file iteratively. Again, and Ruby provides a pleasant way for handling the scenario. The "gets" method accepts a block and yields back to you each line of the file one at a time, thus conserving your precious memory. See the example below:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;file = File.open("some_file")&lt;br /&gt;while(cur_line = file.gets)&lt;br /&gt;   ...#do some processing...&lt;br /&gt;end&lt;br /&gt;file.close&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Also pretty easy.  Today, however, I ran into a new problem. What happens when your file is too big to read into memory at one time, but all the data is all in one single line? Don't believe that would ever happen? Check out EDI sometime  and see what you think about it (On second thought, never checked that out. Never ever look at EDI. I wouldn't want to make you cry). Sometimes even XML or HTML files are written all on one line in human readability isn't of any particular concern.&lt;br /&gt;&lt;br /&gt;Well, I have never dealt with that situation before. I had this file I needed to process, roughly 30 MB, all on one line.  now, iterative processing would be okay, because each segment in the  file was in the proper order for processing and it was delineated by a  pipe character, but there just aren't any built-in methods all in the file object that do what the "gets" method does on a delimiter other than newline. So I wrote one:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;class File&lt;br /&gt;  def uber_gets(delimiter)&lt;br /&gt;    segment = ""&lt;br /&gt;    self.each_byte do |byte|&lt;br /&gt;      char = byte.chr&lt;br /&gt;      if char == delimiter&lt;br /&gt;        yield segment&lt;br /&gt;        segment = ""&lt;br /&gt;      else&lt;br /&gt;        segment = "#{segment}#{char}"&lt;br /&gt;      end&lt;br /&gt;    end &lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt; with this modification, you can now do small iterative processing based on any delimiter. In my case, using EDI files, each record is separated by a "~". so, I used the above method as follows:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;file = File.open("some_file")&lt;br /&gt;file.uber_gets("~") do |segment|&lt;br /&gt;   ...#do some processing...&lt;br /&gt;end&lt;br /&gt;file.close&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;There you go.  The whole file is on one line, but the code is still respecting memory consumption. If it helps you, enjoy. I'll post a link to the gist:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://gist.github.com/123924"&gt;http://gist.github.com/123924&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-7786411905274568127?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/7786411905274568127/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=7786411905274568127' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/7786411905274568127'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/7786411905274568127'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/06/processing-large-files-with-ruby-and.html' title='Processing large files with Ruby and Rails'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-6880601610880492419</id><published>2009-06-04T08:39:00.004-05:00</published><updated>2009-06-04T09:54:28.020-05:00</updated><title type='text'>A new way to Blog</title><content type='html'>Most of my day I spend in front of a computer. I write code, I answer e-mails, and then when I get home, I blog. All that typing can be hard on the hands. I try to do most of the right things, ergonomically speaking. But I still end up with tendinitis. Being only 22 years old, this is obviously something I really want to avoid if I'd like to continue a career in the software business.&lt;br /&gt;&lt;br /&gt;Enter dictation. This isn't the first time that I play with the idea of speaking to my computer. Both Windows Vista, which I had installed in my old computer, and Mac OS X, which I have all my new computers, have built-in speech recognition software. However, this is all mostly for command and control. The software helps you do things; you can open new windows, push menu buttons, click links, and do all sorts of other command based tasks. But when it comes to actually writing, these solutions fall short. Today, though, I'm happy to say that I'm now past that point.  every word that you are reading on this page  was put there by dictation software. MacSpeech dictation is the program I'm using, and I have to admit I'm impressed. Everything I say seems to just end up on the screen without me having to use my hands or forearms.&lt;br /&gt;&lt;br /&gt; there are some drawbacks to the software. For one, it's not cheap. $200 for the current release. Now admittedly, that's not the most expensive piece of software ever seen, but for regular consumer consumption of price point seems a bit steep. On top of that, there is no trial version you can download. In fact, even when you buy it you can't download it. You have to have it shipped to you as if we were back in the 90s. so if you decide you want to buy MacSpeech Dictation, be aware that it's all or nothing.&lt;br /&gt;&lt;br /&gt;For someone like me though, the advantage of being able to do my blog posts without my hands far outweighs the drawbacks inherent in MacSpeech's distribution system. I hope that as my experience with the package progresses, I'll be able to say that all my e-mails and all my blog posts are done without putting any unnecessary strain on my forearms. That way, I can save my limited typing capacity for what I enjoy most: code!&lt;br /&gt;&lt;br /&gt;If this is something you'd like to try, check out the link below.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.macspeech.com/"&gt;Mac Speech&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-6880601610880492419?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/6880601610880492419/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=6880601610880492419' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/6880601610880492419'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/6880601610880492419'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/06/new-way-to-blog.html' title='A new way to Blog'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-6996663171216486802</id><published>2009-05-26T22:48:00.002-05:00</published><updated>2009-05-26T23:02:40.347-05:00</updated><title type='text'>Metric-Fu</title><content type='html'>The latest addition to my utility-belt of Ruby tools, &lt;a href="http://metric-fu.rubyforge.org/"&gt;Metric-Fu&lt;/a&gt; is giving me plenty of ideas on how to refactor my codebase.&lt;br /&gt;&lt;br /&gt;Essentially, this library gives you the ability to take 8 of the most common code analyzing tools and run them all on your codebase at once, producing one consolidated report.  I love it!&lt;br /&gt;&lt;br /&gt;The full list can be found at the &lt;a href="http://metric-fu.rubyforge.org/"&gt;Metric_Fu page on rubyforge.org&lt;/a&gt;, but 2 of my favorites are listed here:&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;a href="http://rubyforge.org/projects/roodi/"&gt;Roodi&lt;/a&gt;&lt;/b&gt; gives you some design help as it checks all kinds of common programming problems.  Method have too many parameters? Cyclomatic complexity too high? Forget the else clause on a case statement?  Roodi will give you the heads up you need.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;a href="http://github.com/seattlerb/flay/tree/master"&gt;Flay&lt;/a&gt;&lt;/b&gt; checks out your code for duplicate constructs and segments.  It found several pieces of duplicated logic that I had never noticed before, I was really impressed.  It also does soft matches on "similar" code, things that could probably be combined and simplified if you're clever. Very nice.&lt;br /&gt;&lt;br /&gt;The great thing about metric_fu is that you just run one command, and all the packages get run for you, giving you one page afterwards containing links to the results of whatever package you want.  Check it out, you might be surprised at how much your fingers start itching to go back and fix all the problems you didn't even know you had.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-6996663171216486802?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/6996663171216486802/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=6996663171216486802' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/6996663171216486802'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/6996663171216486802'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/05/metric-fu.html' title='Metric-Fu'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-5470298650883362049</id><published>2009-05-25T12:32:00.003-05:00</published><updated>2009-05-25T12:43:50.732-05:00</updated><title type='text'>Curse you, rake db:migrate!</title><content type='html'>Have you ever been in this situation?&lt;br /&gt;&lt;br /&gt;&lt;i&gt;"Hmm, this feature will require a big change to my database! I mean, we're going to have to touch every single record in that table." &lt;/i&gt;&lt;br /&gt;&lt;br /&gt;If you have, you've probably followed up with this thought:&lt;br /&gt;&lt;br /&gt;&lt;i&gt;"Good thing I'm using Rails!  They make it so easy!"  &lt;/i&gt;&lt;br /&gt;&lt;br /&gt;And then you went and wrote this migration:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def self.up&lt;br /&gt;  Model.all.each do |m|&lt;br /&gt;    #..some important function &lt;br /&gt;    #performed on every object..&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And then you were really proud of how quickly that went, and you run it on your development machine, and it works really well.  But THEN you push it to your staging or production server that has way more data than your dev machine, and you get this staring back at you from your command line:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt; ** [out :: 123.123.123.100:8063] ==  &lt;br /&gt; ** YourCrazyMigration: migrating &lt;br /&gt;  =========================================&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And you stare at that for about 30 seconds before shouting:&lt;br /&gt;&lt;br /&gt;&lt;i&gt;"Mother F%^&amp;er! I did it again! I can't believe I did it again! I built a stupid migration that uses the stupid 'all' method which is now dominating the memory on that box and I can either kill it and pick up the pieces or let it run for the next 3 hours as it pages the hell out of the hard disk!"&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;Well, since I did EXACTLY THAT just now, I decided that from now on we'll be using a new migration task at our development shop called "safety_migrate", which you're welcome to take advantage of if you'd like. It runs through every file in your migration directory, checking for the dreaded "all" method, and WILL NOT run the  migrations unless every file is clean!&lt;br /&gt;&lt;br /&gt;check the gist:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://gist.github.com/117625"&gt;http://gist.github.com/117625&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Happy Migrating!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-5470298650883362049?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/5470298650883362049/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=5470298650883362049' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/5470298650883362049'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/5470298650883362049'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/05/curse-you-rake-dbmigrate.html' title='Curse you, rake db:migrate!'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-8214716720118338870</id><published>2009-05-17T20:10:00.002-05:00</published><updated>2009-05-17T20:35:58.059-05:00</updated><title type='text'>Maximum Impact for Minimal Effort</title><content type='html'>Today was a day for working outside, and I got a lot of mulch spread over the landscaping at my house.  &lt;br /&gt;&lt;br /&gt;I love mulch because it's really easy to use, really cheap to buy, and really makes a difference in the way your yard looks. You can't get much more bang for your buck as far as landscaping goes.&lt;br /&gt;&lt;br /&gt;Believe it or not, I was thinking a lot about work as I was hauling chips of cedar over to my flower beds, and I started considering what sorts of things are "mulchy" in the web development world.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Color&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Unfortunately, people aren't often impressed by performance or functionality nearly as much as they are by aesthetics.  A white page full of blue links that all do really cool stuff is just a huge turn off.  The difference that can be made with a simple header, a 3-column layout, and a pleasant color scheme is phenomenal.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Central Navigation&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Sometimes you have websites that have spaghetti links all over the place.  Trying to get back to the homepage usually means either clicking the back arrow 14 times, or re-typing in the domain address.  A very common and successful approach is to have a set of navigational links as part of the header, and it's successful for a reason: people know where to go.  No matter where they are, the critical places on the website can be accessed easily.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Intelligent defaults&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;If you have a dropdown that has a list of states, and 80% of your users are local to your state, go ahead and default the selection to your state.  If you have 25 reports users can run on your website, and 3 of them are used more often than any others, put those three at the top.  It doesn't take a lot of work to reposition things, but it makes a big difference.&lt;br /&gt;&lt;br /&gt;There you go.  Three easy things to do that will make a large impact on your users.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-8214716720118338870?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/8214716720118338870/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=8214716720118338870' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/8214716720118338870'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/8214716720118338870'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/05/maximum-impact-for-minimal-effort.html' title='Maximum Impact for Minimal Effort'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-6231869487567773863</id><published>2009-05-14T08:18:00.007-05:00</published><updated>2009-05-14T13:32:25.413-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='named_scope'/><category scheme='http://www.blogger.com/atom/ns#' term='Ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='code'/><category scheme='http://www.blogger.com/atom/ns#' term='Rails'/><title type='text'>Joins and named_scopes in ActiveRecord</title><content type='html'>So many of us know how cool named_scopes are in ActiveRecord;  they really make building complex queries quite pleasant compared to writing out big hairy SQL strings all over the place.  However, in the past week as I have refactored my whole web application to use this excellent feature, I've run into some little-discussed items that I feel should be shared somewhere.  Hope you enjoy, and anybody who is more advanced in their Rails-fu is welcome to give me some schooling as to where I've gone wrong on any of these, as most of my discoveries here are through trial and error.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;joining "through" associations&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Say you have a three-layer association. The following would be an example of this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;class Company &amp;lt; ActiveRecord::Base&lt;br /&gt;  has_many :employees&lt;br /&gt;  has_many :children,:through=&amp;gt;:employees&lt;br /&gt;&lt;br /&gt;  validates_inclusion_of :industry,&lt;br /&gt;                 :in=&gt;[“TECHNOLOGY”,”FINANCE”,”AGRICULTURE”]&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;class Employee &amp;lt; ActiveRecord::Base&lt;br /&gt;  belongs_to :company&lt;br /&gt;  has_many :children&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;class Child &amp;lt; ActiveRecord::Base&lt;br /&gt;  belongs_to :employee&lt;br /&gt;  has_one :company,:through=&amp;gt;:employee&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;So a company has many employees, and an employee has many children.  This is pretty straight-forward.  Now, let's say that we want to find all the children who's parents work for any company in the “TECHNOLOGY” industry.  How could we accomplish this?  Well, certainly one way would be to iterate over the associations, starting by finding all the technology companies, then iterating over each employee, and adding their children to a growing array.  It would look something like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;children = []&lt;br /&gt;Company.find_all_by_industry(“TECHNOLOGY”).each do |company|&lt;br /&gt;  company.employees.each do |emp|&lt;br /&gt;    emp.children.each do |child|&lt;br /&gt;      children &lt;&lt; child&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;But that's not great.  That's a lot of lines of code to read for one qeury.  Let's put the meat of this finding algorithm into a named scope instead:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;class Employee &amp;lt; ActiveRecord::Base&lt;br /&gt;  named_scope :works_in_technology, &lt;br /&gt;           :joins=&amp;gt;:company,&lt;br /&gt;           :conditions=&amp;gt;”companies.industry = 'TECHNOLOGY'”&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;children = []&lt;br /&gt;Employee.works_in_technology.each do |emp|&lt;br /&gt;  emp.children.each {|child| &lt;br /&gt;    children &lt;&lt; child &lt;br /&gt;  }&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;That's a little better.  You can add in joins to your query just by giving the symbol name of the association, so that's really nice looking.  However, as long as we're joining on associations, why not just have another named scope in the Child model and do this whole thing without any iteration?&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;class Child &amp;lt; ActiveRecord::Base&lt;br /&gt;  named_scope :parent_works_in_technology,&lt;br /&gt;              :joins=&amp;gt;:company,&lt;br /&gt;              :conditions=&amp;gt;&lt;br /&gt;                ”companies.industry = 'TECHNOLOGY'”&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;children = Child.parent_works_in_technology&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Hey, that's a lot better!  Only one problem: it doesn't work.  Yeah, we do have an association between the child and company models, but it's “through” the employee model, and the named_scope doesn't know how to assemble that query properly (try it, you'll see the sql it generates isn't quite right).  We can still do this as one named_scope join query, but we're going to have to get a little more detailed level of control:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;class Child &amp;lt; ActiveRecord::Base&lt;br /&gt;  named_scope :parent_works_in_technology,&lt;br /&gt;              :joins=&amp;gt;”, employees, companies”&lt;br /&gt;              :conditions=&amp;gt;&lt;br /&gt;                   ”employees.company_id = companies.id and &lt;br /&gt;                    children.employee_id = employees.id and&lt;br /&gt;                    companies.industry = 'TECHNOLOGY'”&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;children = Child.parent_works_in_technology&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Now THAT works just fine.  It's a little more verbose in the named_scope declaration itself, but it's really nice for wherever else you need to use that query in your codebase. Notice that you have to do the foreign key joining yourself in the conditions string, don't forget to do that or you will definitely not get the results you are expecting.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Watch out for duplicates in join queries&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;So now we're joining across multi-layer associations successfully, and now we have a new requirement:  We're rolling out a new children's special to all the companies in our system and we need to get a list of all the companies who have employees with children younger than 12.  Based on the last named_scope we built in the child model, it seems like we could do another one just like it in the company model.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;class Company &amp;lt; ActiveRecord::Base&lt;br /&gt;  named_scope :has_employees_with_young_children,&lt;br /&gt;              :joins=&amp;gt;”, employees, children”&lt;br /&gt;              :conditions=&amp;gt;&lt;br /&gt;                     ”employees.company_id = companies.id and &lt;br /&gt;                      children.employee_id = employees.id and&lt;br /&gt;                      children.age &lt; 12”&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;companies = Company.has_employees_with_young_children&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;If you don't know how table joins work, it might seem like this would be fine.  The problem comes up when you have a company that has more than one child under age 12: because you're asking it to return you one record for every match of the conditions, you'll get a company record back for every child at that company that's under 12!  If  “Innitrode” has 50 employees, and those 50 employees have collectively a total of 75 children under age 12, then this named_scope will return 75 innitrode records rather than the 1 you were expecting.  This certainly would not be the behaviour you were looking for.  Enter the “group” option.  Grouping allows you to take the set of records you get back and compress them depending on a matching value in each one.  For example:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;class Company &amp;lt; ActiveRecord::Base&lt;br /&gt;  named_scope :has_employees_with_young_children,&lt;br /&gt;              :joins=&amp;gt;”, employees, children”&lt;br /&gt;              :conditions=&amp;gt;&lt;br /&gt;                  ”employees.company_id = companies.id and &lt;br /&gt;                   children.employee_id = employees.id and&lt;br /&gt;                   children.age &lt; 12”,&lt;br /&gt;              :group=&amp;gt;”companies.id”&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;companies = Company.has_employees_with_young_children&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Presto, now the results you get back will be flattened so that each record with a company id of &lt;i&gt;n&lt;/i&gt; is grouped into a single record, so you will get no duplicate company records in the array you have returned.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;ReadOnly records &lt;/b&gt;&lt;br /&gt;&lt;br /&gt;So now you're using these great joining named_scopes to find records based on all kinds of cross-table conditions, and things are working great.  However, they will continue to do so until you need to update one of those records you retrieved, and then you will get a bit of a surprise when your save method throws an error saying that the record you are trying to update is read only.&lt;br /&gt;&lt;br /&gt;Actually, there's a very good reason for this, you just have to know a little bit about how active record works.  When you run a query with joins in it, you're getting back single records that have WAY more attributes than just the ones found in the model table you're dealing with.  If you join employees to companies, than your employee object will have a hash of attributes that has all of the data in it's table, PLUS all the data in the table for the company he works for.  So what if you update one of THOSE attributes instead of the ones that are part of the employee table?  The update statement to the employee table wouldn't be able to find the column named “corporate address” or whatever, and would crash.  So,  the records that are returned from a joined query are just marked readonly to prevent that from ever happening.  In a rails action, this would rarely be a big deal because you're usually just passing the ID of the object you want to update, plus the parameters that you need to change, so you'd be loading a new record out of the database anyway based on the ID, and that object will be writable.  However, it's important to know how to combat this in case the situation every arises where you DO need to update one of these records.  There are 2 ways that I can think of around it.&lt;br /&gt;&lt;br /&gt;First, you can just reload the record you want to update by running another find just using the ID of the object.  &lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;  company = Company.find(company.id)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Another approach that works is to update your named_scope declaration to use the “select” option:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;class Child &amp;lt; ActiveRecord::Base&lt;br /&gt;  named_scope :parent_works_in_technology,&lt;br /&gt;              :joins=&amp;gt;”, employees, companies”&lt;br /&gt;              :conditions=&amp;gt;&lt;br /&gt;                ”employees.company_id = companies.id and &lt;br /&gt;                 children.employee_id = employees.id and&lt;br /&gt;                 companies.industry = 'TECHNOLOGY'”,&lt;br /&gt;              :select=&amp;gt;”children.*”&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Now that you're only pulling back the attributes specifically from your model table, the objects won't be read only.  Be careful with this one, though, because you'll run into a runtime error if you try to use anything that modifies the query like the “size” method.  &lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;children_count = Child.parent_works_in_technology.size&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This is going to alter your query to call “Select count(children.*)”, and your database will reject this as invalid.  &lt;br /&gt;&lt;br /&gt;&lt;b&gt;Chaining Join Queries &lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Finally, what if you have a few named scopes on the same model that join the same tables?  Is this a problem?&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;class Company &amp;lt; ActiveRecord::Base&lt;br /&gt;  named_scope :has_employees_with_young_children,&lt;br /&gt;              :joins=&amp;gt;”, employees, children”&lt;br /&gt;              :conditions=&amp;gt;&lt;br /&gt;                ”employees.company_id = companies.id and &lt;br /&gt;                 children.employee_id = employees.id and&lt;br /&gt;                 children.age &lt; 12”,&lt;br /&gt;              :group=&amp;gt;”companies.id”&lt;br /&gt;  named_scope :has_employees_with_male_children,&lt;br /&gt;              :joins=&amp;gt;”, employees, children”&lt;br /&gt;              :conditions=&amp;gt;&lt;br /&gt;                ”employees.company_id = companies.id and &lt;br /&gt;                 children.employee_id = employees.id and&lt;br /&gt;                 children.gender = 'M'”,&lt;br /&gt;              :group=&amp;gt;”companies.id”&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;It's not a problem as long as you don't chain them together.  But as soon as you do this, you're in trouble:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;  Company.has_employees_with_young_children.has_employees_with_male_children&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;You're SQL query will now try to join in both of those other tables twice, which will result in ambiguous column references.  Usually the answer to this problem is just to not chain joined queries together, but if you need to (like I did in one particular case), you can have the join itself be a different named_scope, so that you only run the joins once:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;class Company &amp;lt; ActiveRecord::Base&lt;br /&gt;  named_scope :has_young_children,&lt;br /&gt;              :conditions=&amp;gt;”children.age &lt; 12”&lt;br /&gt;  named_scope :has_male_children,&lt;br /&gt;              :conditions=&amp;gt;”children.gender = 'M'”&lt;br /&gt;  named_scope :join_in_children, &lt;br /&gt;              :joins=&amp;gt;”, employees, children”&lt;br /&gt;              :conditions=&amp;gt;&lt;br /&gt;                ”employees.company_id = companies.id and &lt;br /&gt;                 children.employee_id = employees.id”,&lt;br /&gt;              :group=&amp;gt;”companies.id”&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This will work, you just have to remember to chain it in every time:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;   Company.join_in_children&lt;br /&gt;          .has_male_children&lt;br /&gt;          .has_young_children&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Hope this helps someone.  Any reader with other tricks they've learned with joins, or better ways to do something listed here, put it in the comments and if it's good I'll move it to an update in the post (with credit given to the author, of course).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-6231869487567773863?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/6231869487567773863/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=6231869487567773863' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/6231869487567773863'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/6231869487567773863'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/05/joins-and-namedscopes-in-activerecord.html' title='Joins and named_scopes in ActiveRecord'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-8687057106294177692</id><published>2009-05-11T12:25:00.009-05:00</published><updated>2009-05-11T13:29:12.030-05:00</updated><title type='text'>Git solves all your branching and merging problems!  Almost!</title><content type='html'>The entire development team here at Research to Practice (that is, Ray and I) have been working entirely out of git for a little while now.  The problem was, we were using it just like Subversion.  You see, having experienced a bad branch merge before, it's tough to get up the gumption to try again.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_vTrR73WnSs8/SghlGq6PZSI/AAAAAAAABAo/JcZgYVHupf0/s1600-h/bad_sushi.jpg"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 130px; height: 98px;" src="http://4.bp.blogspot.com/_vTrR73WnSs8/SghlGq6PZSI/AAAAAAAABAo/JcZgYVHupf0/s320/bad_sushi.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5334624923892540706" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Why?  Imagine getting food poisoning from a questionable sushi place in the midwest.  Yeah, you know that it was totally the health code violations and 12-day-old-fish at that place that made you vomit 9 shades of purple, but you'd still have a hard time bringing yourself to eat anything at an upscale sushi restaurant on the coast of japan, wouldn't you?&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_vTrR73WnSs8/SghlJ8MGpEI/AAAAAAAABAw/kEd0P9ZI4DU/s1600-h/good_sushi.jpg"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 127px; height: 95px;" src="http://4.bp.blogspot.com/_vTrR73WnSs8/SghlJ8MGpEI/AAAAAAAABAw/kEd0P9ZI4DU/s320/good_sushi.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5334624980070474818" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Yeah, it's kind of like that.  &lt;br /&gt;&lt;br /&gt;We know that git is good at this stuff, we know that we want to take advantage of the great flexibility that using branches offers, but when it comes down to actually DOING it, there's always a reason why maybe we shouldn't try this just yet.  &lt;br /&gt;&lt;br /&gt;Last week, we decided to lay down the law.  We wanted to have some features be worked on for a 2.0 release of our website, but we also wanted to continue deploying fixes and small enhancements to the currently running application.  No way around it, it was time to branch out.&lt;br /&gt;&lt;br /&gt;So it was with great fear and trepidation that we first unleashed this phrase upon our command line:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$&amp;gt; git checkout -b version_2_features&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Oh Gods, what have we wrought?!&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;...Ok, so it wasn't that bad yet.  The checkout command with the "b" option created a new branch for us, and after developing a small and inconsequential feature (a necessary precaution because we were fully fearing that the demons-of-parallel-development could swoop in at any moment and destroy everything we had done so far) we pushed this branch to the github repository so that we could both develop on it:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$&amp;gt; git push origin version_2_features&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Alright, so far so good.  Now we have our "master" branch, which was esentially "What is currently running in production", and our "version_2_features" branch where we could start developing everything we wanted to put into the version we were planning to release at the end of the summer.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_vTrR73WnSs8/SghjQ4mKyWI/AAAAAAAABAg/rfW-tP8qOyc/s1600-h/borat.jpg"&gt;&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 103px; height: 119px;" src="http://4.bp.blogspot.com/_vTrR73WnSs8/SghjQ4mKyWI/AAAAAAAABAg/rfW-tP8qOyc/s320/borat.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5334622900341885282" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br/&gt;&lt;br /&gt;Great Success!&lt;br /&gt;&lt;br/&gt;&lt;br /&gt;&lt;br/&gt;&lt;br /&gt;So we started developing that way, and everything was moving along splendidly, until we hit something of a snag.  Well, a boat anchor, really.  See, the whole point of having this 2.0 branch be separate was so that it's content would not be deployed until much later, but SOMEhow, SOMEone, did SOMEthing (I'll let you ask Ray for the details behind that), and suddenly the version_2_features branch ended up being accidentally merged into master.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_vTrR73WnSs8/Sghn4HfQe_I/AAAAAAAABA4/Rqgn3bULWNQ/s1600-h/doh.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 100px; height: 124px;" src="http://3.bp.blogspot.com/_vTrR73WnSs8/Sghn4HfQe_I/AAAAAAAABA4/Rqgn3bULWNQ/s320/doh.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5334627972400839666" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;This resulted in an hour of us assuring ourselves that we could probably find a way out of this without having to admit we (he) ever screwed up, and we came up with a list of options.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;1) Use git reset on the repo to bring it back to before the bad merge&lt;/b&gt;&lt;br /&gt;&lt;b&gt;2) Check out a previous commit version (before the bad merge), and then commit that as the head&lt;/b&gt;&lt;br /&gt;&lt;b&gt;3) Use git reset on a local repo, and push the result to github&lt;/b&gt;&lt;br /&gt;&lt;b&gt;4) Revert the merge commit, than when you want to merge for real later revert the revert.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Now out of the first 3 options, it turned out that exactly 0 of them were possible.  There's no way (that we could find) to call "RESET" on our remote github repository, checking out a previous commit disconnected you from the head so you couldn't commit and call it the new head, and trying to push a branch that had been reset to the remote repo was met with a regular level of failure.  Option 4 looked plausible, but beyond our ken, and neither of us is a git wizard just yet.&lt;br /&gt;&lt;br /&gt;So we went with option 5, check out an old version of the repository, copy the entire directory structure into our working directory, commit the deltas, and bam, we're back to premerge state.&lt;br /&gt;&lt;br /&gt;&lt;b&gt; NEVER DO THIS&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Little did we know at the time that this would later take us beyond regular and straight into the epic level of failure.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_vTrR73WnSs8/Sghp8fnB6jI/AAAAAAAABBA/XWQ6slvnhLM/s1600-h/Fail_Dog.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 219px; height: 320px;" src="http://1.bp.blogspot.com/_vTrR73WnSs8/Sghp8fnB6jI/AAAAAAAABBA/XWQ6slvnhLM/s320/Fail_Dog.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5334630246618622514" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;"What's the matter?" you might ask.  "Isn't your directory back to the way you wanted it?"&lt;br /&gt;&lt;br /&gt;The answer to that question is yes, our working directory was back to the production version, which was great for the master branch....but not for the history.  You see, we still needed to be able to merge features that were being added to the version_2_features branch back into the master at some point in the future, and that's really hard to do if your repository version believes that those 2 branches have already been merged!!!&lt;br /&gt;&lt;br /&gt;By the time we realized this, we'd already pushed these changes to the repo, and were beginning to consider just how screwed we were.  Thus began the wailing and gnashing of teeth.&lt;br /&gt;&lt;br /&gt;"We were right! Branching was a horrible idea!  We've done exactly what should never have been begun!" &lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_vTrR73WnSs8/SghsezPYBZI/AAAAAAAABBI/enIAklLCrG4/s1600-h/AngryGod.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 219px; height: 270px;" src="http://2.bp.blogspot.com/_vTrR73WnSs8/SghsezPYBZI/AAAAAAAABBI/enIAklLCrG4/s320/AngryGod.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5334633035026924946" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;So we rebased and merged and copied and merged and went through every combination possible of recursive/3-way/octopus merging until we finally were PRETTY sure that everything was ok (about half a days worth of work), and do you know what I realized after that?&lt;br /&gt;&lt;br /&gt;&lt;b&gt;6) delete the remote github repository and create a new one from the one on your local machine that's still in a good state.&lt;/b&gt;  &lt;br /&gt;&lt;br /&gt;Yes.  It was possible.  It was even a good idea.  if only I had thought of it sooner.&lt;br /&gt;&lt;br /&gt;Moral of the story?  git is great for branching and merging, far better than any solution I've used in the past.  Unfortunately, even git can't protect you from your own stupidity.  No tool is that powerful (except Java, that's what it was designed for).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-8687057106294177692?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/8687057106294177692/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=8687057106294177692' title='10 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/8687057106294177692'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/8687057106294177692'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/05/git-solves-all-your-branching-and.html' title='Git solves all your branching and merging problems!  Almost!'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_vTrR73WnSs8/SghlGq6PZSI/AAAAAAAABAo/JcZgYVHupf0/s72-c/bad_sushi.jpg' height='72' width='72'/><thr:total>10</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-3653388565649492682</id><published>2009-04-30T07:58:00.002-05:00</published><updated>2009-04-30T08:21:19.428-05:00</updated><title type='text'>On Learning</title><content type='html'>In a previous post, I mentioned that in some of my spare time I like to give private music lessons to kids here in Columbia.  Well, that part of my life is now temporarily closed as the demands of my business continue to grow leaving me very little "spare time" left over.  It's been tough having to tell each one of my students that I'm not going to be working with them any more, because I've really enjoyed working with all of them and have kind of become attached to it, and as I said goodbye to each one I would find myself thinking about how much they had improved since we started working together.  Now I'm not going to take all the credit for that, because they are the ones who practiced, so they are responsible for their own gains in skill.  However, I feel that something I was doing worked right, better in fact that my previous experiments in the field of teaching.&lt;br /&gt;&lt;br /&gt;What was the biggest difference this time around?  Well, my approach to teaching in previous years could be summed up by saying "These kids don't know very much yet, so we'd better scale down everything we're working on to make sure it's not overwhelming and just learn a little bit at a time".  That could be compared to my approach this year, which was more along the lines of "This kid already knows how to do X, so let's find something they aren't good at yet and specifically pick a piece to work on that requires that skill".  Although the evidence is anecdotal, my experience seems to show that people learn better when they are challenged than when they are coddled.  &lt;br /&gt;&lt;br /&gt;There are three reasons I can think of that support this idea:&lt;br /&gt;&lt;br /&gt;&lt;b&gt;1) slow going is boring&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Picking up a little at a time is a quick way to lose interest in anything.  This is why I still don't speak Russian very well.  I really want to learn Russian, but I've been trying to just spend a couple hours a week on it, reviewing many of the words I already know and just adding a few more.  You know what?  People who go participate in an immersive language program learn fast.  People who pick up "Russian for Dummies" and peruse it a few times a week don't, they just get a nice paperweight that looks good on their coffee table.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;2) harder to achieve = more satisfaction&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Since most of the readers here are programmers, I can feel confident that this point will garner support.  If you are trying to tackle something tough that you've never had to deal with before, then your feelings upon success are made up mostly of excitement and joy.  If you just finished coding up another CRUD application which you've done 100 of before, your feelings are probably more of relief than anything else.  Difficult and new challenges produce satisfaction that makes you want to come back and do more.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;3) expectations cause results&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;This is probably the most important thing a teacher can do.  If you expect your student to make minimal progress, and you convey that expectation in the assignments you give and in your assessment of their work, than they will definitely make minimum progress.  If, on the other hand, you expect more out of him than even the student himself thinks he is capable of, he will tend to make strides that would surprise anyone because when he is working he is bolstered and driven by the fact that somebody else who has authority in this subject believes that he is capable of doing what he's been assigned.  It's amazing, but true (at least in my limited experience).&lt;br /&gt;&lt;br /&gt;So as I said, I'm sad to leave this part of my life behind for now, but the lessons that I have learned from it are worth taking with me into other parts of my life.  If I want to pick up something new, I think the best way to go about it would be to pick up a challenging project (for my level), dedicate a good amount of time to it, and expect more out of myself than I think I'm capable of.  In fact, I'll go so far as to say this would be a good way for &lt;i&gt;you&lt;/i&gt; to learn anything too.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-3653388565649492682?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/3653388565649492682/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=3653388565649492682' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/3653388565649492682'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/3653388565649492682'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/04/on-learning.html' title='On Learning'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-230034020979921692</id><published>2009-04-28T14:32:00.002-05:00</published><updated>2009-04-28T14:39:24.955-05:00</updated><title type='text'>Net::HTTP  and SSL</title><content type='html'>Just ran into an interesting problem today that I thought I'd post the solution for.  Everybody knows about the net/http library, it's a core bundle that lets you do web requests.  Well I was using it for posting to an experimental web service a friend had set up, and I ran into a problem.  Here was my code:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;url = URI.parse("https://someaddress:443/web/service/path")&lt;br /&gt;xml = generate_some_xml&lt;br /&gt;request = Net::HTTP::Post.new(url.path,{"Content-Type"=&gt;"text/xml"})&lt;br /&gt;http = Net::HTTP.new(url.host, url.port)&lt;br /&gt;response = http.start {|http| http.request(request) }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Seems like it should have worked, but I kept running into this message:&lt;br /&gt;&lt;br /&gt;&lt;i&gt;wrong status line: "\025\003\001\000\002\002"&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;after googling around a bit, I figured out that this error message comes up whenever you are trying to connect to an SSL enabled site with plain text.  I figured that, like curl, if you used "https" in your url, then you were telling the library that you wanted to use SSL.  Well, that's not exactly the way it works.  Although I put my "https" into the original url, when it gets passed into the HTTP library on line 4 of my code up above, I'm just handing it the host and the port, not the protocol.  This requires the use of an aditional line of code:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;url = URI.parse("https://someaddress:443/web/service/path")&lt;br /&gt;xml = generate_some_xml&lt;br /&gt;request = Net::HTTP::Post.new(url.path,{"Content-Type"=&gt;"text/xml"})&lt;br /&gt;http = Net::HTTP.new(url.host, url.port)&lt;br /&gt;http.use_ssl = true&lt;br /&gt;response = http.start {|http| http.request(request) }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;there you have it.  Hopefully by finding this post, you figured this out a lot quicker than I did.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-230034020979921692?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/230034020979921692/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=230034020979921692' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/230034020979921692'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/230034020979921692'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/04/nethttp-and-ssl.html' title='Net::HTTP  and SSL'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-1374763330903635766</id><published>2009-04-24T18:33:00.003-05:00</published><updated>2009-04-24T18:54:59.798-05:00</updated><title type='text'>Improving your Rails-Fu</title><content type='html'>This month I've been making a conscious effort to improve my competence with the Rails stack.  It's not that I feel I'm a bad developer (nobody feels that way, not even bad developers), it's just that the last time I'd been really honing my craft was around rails 1.2, and it's been a while since then (2.3 just released recently).  I knew there were some major changes out there that I wasn't taking advantage of, and that there were several things I could improve upon.  This was evidenced both by my lack of experience with things being discussed on the rails core list, and the reluctance of other developers to contract in for work on my codebase.  Something had to give.&lt;br /&gt;&lt;br /&gt;So I went to the same place I always go when I want to work on my skills: &lt;a href="http://www.pragprog.com"&gt;The Pragmatic Programmers&lt;/a&gt;.  I'm not affiliated with these guys in any way, I just really like their stuff.  In this instance particularly, I selected a series of screencasts called &lt;a href="http://pragprog.com/screencasts/v-rbar/everyday-active-record"&gt;"Everyday Active Record"&lt;/a&gt; done by Ryan Bates of &lt;a href="http://www.railscasts.com"&gt;Railscasts&lt;/a&gt; fame. Sort version of the story: they were excellent.  &lt;br /&gt;&lt;br /&gt;I have been a subscriber of the Railscasts feed for sometime, so I knew that Ryan was good at this stuff, but I learned quite a bit from this series in particular that made me slap my forehead as I thought about all the problems they were discussing that I had gone and wasted time trying to solve myself when it was BUILT INTO ACTIVE RECORD THE WHOLE TIME.  &lt;br /&gt;&lt;br /&gt;A good example of this was named scopes.  If you are a Rails developer, you've probably heard of them already, and you're right now navigating away from my article.  That's fair, I should have been keeping up better with the industry.  Believe me, it didn't take me long to dig into my code and start name_scoping everything more complicated than matching on one column.&lt;br /&gt;&lt;br /&gt;If you are like me and HAVEN'T exactly done a stellar job of following the developments in the rails stack (this was introduced in Rails 2.1), you might not know what I'm talking about.  Let me &lt;a href="http://ryandaigle.com/articles/2008/3/24/what-s-new-in-edge-rails-has-finder-functionality"&gt;point you to a good article on the subject.&lt;/a&gt;  Read it. Cool, huh?&lt;br /&gt;&lt;br /&gt;There's lots more, with easy to follow examples, and at $5 a pop they're a pretty great deal.  Do yourself a favor and check them out.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-1374763330903635766?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/1374763330903635766/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=1374763330903635766' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/1374763330903635766'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/1374763330903635766'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/04/improving-your-rails-fu.html' title='Improving your Rails-Fu'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-8744046095981070509</id><published>2009-04-17T19:59:00.003-05:00</published><updated>2009-04-17T20:19:40.030-05:00</updated><title type='text'>DRY up your views with HAML</title><content type='html'>About 6 months ago, I read an article about this great templating engine called &lt;a href="http://haml.hamptoncatlin.com/"&gt;HAML&lt;/a&gt;.  I thought I might try it out.  I didn't.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://gilesbowkett.blogspot.com/2009/01/haml-brings-seaside-awesome-to-ruby.html"&gt;Giles mentioned it&lt;/a&gt; back in January.  I read that article.  "Oh yeah!", I thought. "I read about this! it's cool, I should give it a try!".  I didn't.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://daniel.collectiveidea.com/blog/2009/2/27/sorry-haml-you-wont-take-over-the-world"&gt;Daniel Morrison&lt;/a&gt; gave it a review in February.  I read that article too.  He said it makes html unmaintainable for non-rubyists.  I thought I should see for myself.  I didn't.&lt;br /&gt;&lt;br /&gt;Yesterday, &lt;a href="http://cadrlife.blogspot.com"&gt;Ray Myers&lt;/a&gt; (employee #1 of my startup) mentioned that he had found this thing called HAML and wanted to try it.  I tried it.&lt;br /&gt;&lt;br /&gt;Good things happened. My templates got short.  I liked reading my markup. Everything was concise, kind of like this blog post.&lt;br /&gt;&lt;br /&gt;Wish I hadn't waited so long.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://haml.hamptoncatlin.com/"&gt;TRY IT.&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-8744046095981070509?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/8744046095981070509/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=8744046095981070509' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/8744046095981070509'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/8744046095981070509'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/04/dry-up-your-views-with-haml.html' title='DRY up your views with HAML'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-4664437863968307060</id><published>2009-04-09T23:02:00.002-05:00</published><updated>2009-04-09T23:10:40.637-05:00</updated><title type='text'>Profitable on Paper</title><content type='html'>This year has been my first year as a startup founder, so I'd never attempted anything like this before.  In my previous jobs, I got a paycheck every 2 weeks, just like clockwork.&lt;br /&gt;&lt;br /&gt;Now, being in the midst of a cash crunch, I sometimes miss that security a little. Not enough to want to go back to being chained to an office working on something I don't care about, mind you.  &lt;br /&gt;&lt;br /&gt;You see, my company has performed services, and invoiced for them, in amounts that would keep our heads above water.  However, some of our clients tend to take upwards of 90 days to pay up.  If you consider receivables our situation looks pretty great, but without cash in the bank it means almost nothing.&lt;br /&gt;&lt;br /&gt;I wrote a post a few months ago that said "Customer Service is King".  I think that in light of new information, I'd rank Customer Service as more of a prince or duke. The real king is cash, and managing it's flow is a major art form.&lt;br /&gt;&lt;br /&gt;Some things I've learned about managing cash:&lt;br /&gt;&lt;br /&gt;1) slash expenses everywhere, all the time.  Any money you aren't spending is money you will have in reserve for things you might suddenly be forced to pay for. Don't cut corners, though.  Angry customers soon become non-customers.&lt;br /&gt;&lt;br /&gt;2) You are an expense.  The less you can live on, the better for the long term.&lt;br /&gt;&lt;br /&gt;3) Early on in a startup, cash is scarce, so your time is less precious than your money.  Spend time negotiating deals or discounts.  It's well worth it.&lt;br /&gt;&lt;br /&gt;I'll post others as I think of them, but the main thrust here is that as long as you have less than 6 months of operating expenses in reserve, one of your main jobs is to watch what money you have like a hawk.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-4664437863968307060?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/4664437863968307060/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=4664437863968307060' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/4664437863968307060'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/4664437863968307060'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/04/profitable-on-paper.html' title='Profitable on Paper'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-6078854119065150741</id><published>2009-03-27T08:09:00.006-05:00</published><updated>2009-03-27T19:50:43.331-05:00</updated><title type='text'>Big Headaches for MYSQL users</title><content type='html'>Here's something I didn't know, but which hopefully will help someone else out in the future:&lt;br /&gt;&lt;br /&gt;DON'T USE NESTED SUBQUERIES!&lt;br /&gt;&lt;br /&gt;Ok, if you know what that sentence means, and why I said it, you're done with this article, continue to the the next item in your RSS reader.&lt;br /&gt;&lt;br /&gt;Now, for all the rest of you, let's start with what a nested subquery is.  Let's say, for example, I have 2 tables in my database:  students, and schools. Students each have a "school_id" as a foreign key.  I want to retrieve from my DB all the students who go to a school that is in the zipcode 65203.  I might write a query like this:&lt;br /&gt;&lt;i&gt;&lt;br /&gt;"select * from students where school_id in (select id from schools where zip = '65203')"&lt;br /&gt;&lt;/i&gt;&lt;br /&gt;Or if I was working in rails (which I usually do):&lt;br/&gt;&lt;br /&gt;&lt;i&gt;Student.find(:all,:conditions=&gt;"school_id in (select id from schools where zip = '65203')")&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;It looks really nice, it's easy to understand, and under certain circumstances it can DESTROY your database performance.  There's a lot of reasons why this is the case, not the least of which is that query optimizers just don't have the time to figure out each one of your subqueries and fix them up.  If you want a nice discussion about the why, I recommend &lt;a href="http://www.selikoff.net/blog/2008/12/10/memo-avoid-nested-queries-in-mysql-at-all-costs/"&gt;This Blog Post&lt;/a&gt; wherein Scott Selikoff does a great job of explaining the problems.&lt;br /&gt;&lt;br /&gt;For those of you who just want to fix it, do a join.  Yeah, it's a little less pleasant to look at, but your hosting provider will thank you (or, in our case, will stop yelling at you for hurting their other client's database performance times).  Here's an example of the previous query joined up:&lt;br /&gt;&lt;br /&gt;&lt;i&gt;&lt;br /&gt;"Select students.* from students, schools where students.school_id = schools.id and schools.zip = '65203'"&lt;br /&gt;&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;And the rails version:&lt;br /&gt;&lt;br /&gt;&lt;i&gt;&lt;br /&gt;Student.find(:all,:conditions=&gt;"students.school_id = schools.id and schools.zip = '65203'",:joins=&gt; ", schools")&lt;br /&gt;&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;May your DB feel better, and may you stop getting angry emails! Happy coding!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-6078854119065150741?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/6078854119065150741/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=6078854119065150741' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/6078854119065150741'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/6078854119065150741'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/03/big-headaches-for-mysql-users.html' title='Big Headaches for MYSQL users'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-939523399272712764</id><published>2009-03-25T15:36:00.004-05:00</published><updated>2009-03-25T15:49:46.308-05:00</updated><title type='text'>Opportunities where you least expect them</title><content type='html'>So for the last year I've been writing about software, trying to become better at what I do.  In the process, I like to think I've become a better writer as well.  Apparently, there are other people who think so too, because this last week I was asked to become a featured writer on firelink.com (related to my new experiences as a recruit firefighter).  Check the link!&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.firelink.com/benefits/articles/6802-firefighter-origins"&gt;http://www.firelink.com/benefits/articles/6802-firefighter-origins?page=1&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-939523399272712764?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/939523399272712764/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=939523399272712764' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/939523399272712764'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/939523399272712764'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/03/opportunities-where-you-least-expect.html' title='Opportunities where you least expect them'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-5632748213854440451</id><published>2009-03-20T10:29:00.003-05:00</published><updated>2009-03-20T16:13:09.513-05:00</updated><title type='text'>What you say and how you say it</title><content type='html'>I was sitting in on a meeting with my wife and one of our business service providers a couple days ago, and an interesting thing happened.&lt;br /&gt;&lt;br /&gt;We were meeting with two individuals from this company, both from the same department, both with approximately equal authority to get things done. We talked about some problems we were having with their software system, and they told us about the schedule they'd be able to address it on.&lt;br /&gt;&lt;br /&gt;After we had left and were driving home, my wife turned to me and said "Which one of those two ladies did you like more?"&lt;br /&gt;&lt;br /&gt;I hadn't thought about it, but I immediately said that it was the woman on the left.  &lt;br /&gt;&lt;br /&gt;"Me too!" my wife said.  &lt;br /&gt;&lt;br /&gt;We talked about it for a while and realized that both of them were telling us the same thing: "We understand your concerns, we're a little backed up, but we'll get them addressed within X timeframe".  However, the woman on the right had mannerisms that were off-putting.  She had her arms crossed the whole time, every time we tried to bring up an issue she was quick to come into direct conflict with it, she never smiled.  The woman on the left, however, leaned in towards us when she talked, let us finish our sentences, smiled generously, and gave us phrased things with a "we" instead of a "you" in front of them.  The first woman seemed to be accusing, the second was accommodating. &lt;br /&gt;&lt;br /&gt;The first woman made me want to find another company to work with.  The second made me want to return the favor sometime in the future.&lt;br /&gt;&lt;br /&gt;It's not the message you're giving that typically affects peoples opinions of you, it's the way you deliver it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-5632748213854440451?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/5632748213854440451/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=5632748213854440451' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/5632748213854440451'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/5632748213854440451'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/03/what-you-say-and-how-you-say-it.html' title='What you say and how you say it'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-3726009805761404821</id><published>2009-03-13T14:32:00.004-05:00</published><updated>2009-03-13T15:03:16.551-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='employment'/><category scheme='http://www.blogger.com/atom/ns#' term='startups'/><title type='text'>3 Stereotypical Types of Software Labor</title><content type='html'>The last month has been really hectic here for both startups that I'm working on.  With more and more that needs to be done, there's less and less time for me to get to it all, so the time finally had to come when I said "There's too much for just me to be working on".   So we decided to hook up with some contract labor to bridge the gap until we could get a full time employee brought on, but the results have been a mixed bag. That's why I thought I'd put up a post that shows the experiences we've had with the 3 most common types of contract labor.  (Please note ahead of time: This is just my honest experience. It does not automatically extrapolate to every person who falls into the categories I will define.)&lt;br /&gt;&lt;br /&gt;&lt;b&gt;1. The Cheap Off-Shore Worker&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Cool! Software development for less than $10/hr!  We couldn't find ANYBODY here to work for that price.  When you're in a startup, cashflow is always at the front of your mind, so there's a big appeal to a little less value for a lot less money.  When we found a gentleman in India who was willing to work for around half of what we would have to pay local labor, we couldn't pass up the chance to at least try it out.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;PROS:&lt;/i&gt; Cheap is good.  We got a lot of features started that had been waiting on the back-burner while I worked on more important stuff, and for the most part what came back to us was in working order.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;CONS:&lt;/i&gt; It can be tough to explain what you're asking for when there's a language barrier.  Also, frequently the finished work that we received had small problems with it that were not difficult enough for us to want to keep paying someone else to go back in and fix them, but were bothersome enough that they took some of my time to fix before we could deploy them to production.  &lt;br /&gt;&lt;br /&gt;&lt;i&gt;VERDICT:&lt;/i&gt; Good for getting little things done, or maybe for giving you a place to start on larger features.  Not to be relied upon for anything mission critical to your application.  ALWAYS look over the code yourself before blindly including it in a deployment.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;2. The Overpriced Consultant&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;I had one guy contact me who was very experienced and had a lot of great items on his resume.  The problem?  He wanted $100/hr to work (but was willing to start at $75/hr).  Are you kidding me?  Don't get me wrong, I'm sure this guy's very good at what he does, but I wasn't kidding when I mentioned cashflow earlier on.  I don't care if this guy has six arms and 3 workstations that he can hack on simultaneously, there's no way he could produce anything that would be worth $100 every hour to a cash-poor startup operating out of the mid-west.  &lt;br /&gt;&lt;br /&gt;&lt;i&gt;PROS:&lt;/i&gt;  The gentleman was very nice to talk to, very experienced, and if we had worked with him I have no doubt that he would have produced excellent code and done fine work.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;CONS:&lt;/i&gt;  We have only so much cash, are barely profitable, and any expenditure at that level would quickly decrease our run-way of operation.  We just can't afford that kind of recurring expense.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;VERDICT:&lt;/i&gt; Better leave these blokes for the deep-pocketed mature companies.&lt;br /&gt;  &lt;br /&gt;&lt;br /&gt;&lt;b&gt;3. The Local Student&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;The dude's never had a shot in the real world yet.  Great!  He's got lots of academic training, he's probably quite bright, but he won't charge as much as someone who's been in the industry for some time.  What a deal, right?  Well, maybe.  You never know someone's ability level until you've tried them out.  In my experience, the huge amount of intelligence you may have in a bright kid doesn't really translate to a great deal of productivity.  Why?  Well mainly because part of the experience you get from the industry is knowing what things have already been implemented and NOT REWRITING THEM YOURSELF.  I don't care if you can implement a super fast bloom filter, what we need right now is the ability to upload pictures for the user's profile.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;PROS:&lt;/i&gt;  Pretty cheap, that's good.  Also they're usually on-site, which is to me much easier than working with a remote developer.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;CONS:&lt;/i&gt;  Be prepared to watch every check-in that is made to the repo, because they probably haven't used attachment_fu yet and they may just decide to write their own image attachment library (which is probably very good, but we just don't have the time for that!). &lt;br /&gt;&lt;br /&gt;&lt;i&gt;VERDICT:&lt;/i&gt; Can consume as much time in supervision as they save you in implementation.&lt;br /&gt;&lt;br /&gt;My conclusion:  The best contractor we've had is the guy we're going to hire full-time anyway.  There's no such thing as a bridge. My impression is that I want the same thing in a contractor that I want in an FTE, but I'm less likely to find it in someone who's not going to have a stake in the outcome.  Thank God we're hiring.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-3726009805761404821?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/3726009805761404821/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=3726009805761404821' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/3726009805761404821'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/3726009805761404821'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/03/3-stereotypical-types-of-software-labor.html' title='3 Stereotypical Types of Software Labor'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-5175712589136967568</id><published>2009-02-13T10:12:00.002-06:00</published><updated>2009-02-13T10:30:02.155-06:00</updated><title type='text'>a little git</title><content type='html'>In a previous post I mentioned that I had switched my SCM solution on the startups I'm working on to "git" away from "svn".  Up until now, though, I hadn't really been making the most use of the new powers git gave me.  My workflow had been very similar to the way I used to do svn: &lt;br /&gt;&lt;br /&gt;checkout the repo (git checkout)&lt;br /&gt;make changes  &lt;br /&gt;run pull in any changes that have been sent to repo during my work time (git pull)&lt;br /&gt;commit my changes to the repository (git push)&lt;br /&gt;repeat&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Recently I had a need to do more, though.  There's one section of one of web applications I'm developing that needs a serious redesign (I mean several days worth of work).  So I'm working through it, but then I have a new "emergency" that comes in that requires I deploy some code NOW.  Usually, I could solve this problem by making the change in my working directory, and then just selectively adding and commiting that change.  However, some of the work I needed to do was in one of the files I had already edited, so there was no way without losing all my work for the day that I could swing getting this fix out (short of checking out from head to a different folder, changing, pushing, and then pulling in to my modded directory again).&lt;br /&gt;&lt;br /&gt;Fortunately, git provides a better way:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$&amp;gt; git stash save&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;I've just saved all my local modifications to the "stash", which you can think of as a temporary shelf you can set stuff on while you're doing branching stuff.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$&amp;gt; git checkout -b working_branch&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Now I've created a new branch named "working_branch" on my machine from this repository.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$&amp;gt; git stash apply&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;My changes that I saved to the stash have now been resurrected and applied to the new branch that I'm working in.  I can now make changes and commit to the "working_branch" branch as often as I need. &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$&amp;gt; git add .&lt;br /&gt;$&amp;gt; git commit -m "commiting to branch"&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Now I need to go back to my master branch to make the emergency change:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$&amp;gt; git checkout master&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;OK, make my emergency fix, then commit:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$&amp;gt; git add .&lt;br /&gt;$&amp;gt; git commit -m "commiting to master"&lt;br /&gt;$&amp;gt; git push origin master &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Now maybe I want to merge these emergency changes into my current working branch so that I can keep the two branches from diverging to far:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$&amp;gt; git checkout working_branch&lt;br /&gt;$&amp;gt; git merge master&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Resolve any conflicts, and commit. Done! &lt;br /&gt;&lt;br /&gt;There you have it, simple branching in git for great gains in organization and productivity (IMHO).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-5175712589136967568?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/5175712589136967568/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=5175712589136967568' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/5175712589136967568'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/5175712589136967568'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/02/little-git.html' title='a little git'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-3442331179118613043</id><published>2009-02-09T15:22:00.002-06:00</published><updated>2009-02-09T16:07:43.968-06:00</updated><title type='text'>PDF Attachments with attachment_fu</title><content type='html'>The great thing about frameworks like Rails (in my opinion) is that the large number of people doing many of the same tasks results in some really great reusable pieces of code that make common complex features almost trivial to implement.  This was brought right to the forefront of my attention again today when I started working on a new feature for our medicaid billing system.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The story is this: We have a physician who writes prescriptions for students who need them.  It's better if we don't have to mail him all the documentation for every student he's writing a script for.  The best conceived solution was to have school districts upload their docs to our website (associated with a student), and let the physician download them on his side.  &lt;br /&gt;&lt;br /&gt;Now, surely I'm not the first person to ever want to have uploaded files associated with an entity (that's basically facebook's whole business model, after all).&lt;br /&gt;&lt;br /&gt;Sure enough, &lt;a href="http://github.com/technoweenie/attachment_fu/tree/master"&gt;&lt;br /&gt;attachment_fu&lt;/a&gt; to the rescue!&lt;br /&gt;&lt;br /&gt;This was dead simple.  After installing the plugin (ruby script/plugin install git://github.com/technoweenie/attachment_fu.git), I dug into the docs and started coding.&lt;br /&gt;&lt;br /&gt;  First I created a new model to house the necessary file for the student (it's called an "IEP", for Individual Education Plan, so don't get thrown by the acronym):&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;class Iep &lt; ActiveRecord::Base&lt;br /&gt;  belongs_to :student&lt;br /&gt;  &lt;br /&gt;  has_attachment :content_type =&gt; "application/pdf", &lt;br /&gt;                   :storage =&gt; :file_system, &lt;br /&gt;                   :path_prefix =&gt; "public/iep",&lt;br /&gt;                   :max_size =&gt; 1000.kilobytes&lt;br /&gt;&lt;br /&gt;  validates_as_attachment&lt;br /&gt;  &lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Small and simple, right?  Here's what it means:&lt;br /&gt;the "belongs_to :student" is the standard ActiveRecord association, indicating that a student entity is the parent of this object (and also implying that the "IEP" object has a foreign key called student_id).&lt;br /&gt;&lt;br /&gt;"has_attachment" is the method that configures the file you're going to upload. :content_type, obviously, is the type of file you expect.  In my case (and yours if google brought you to this entry) that type is "application/pdf", and if anything else comes in it won't be accepted.&lt;br /&gt;&lt;br /&gt;":storage" is how you tell attachment_fu where you want to store these files.  Other options besides the filesystem are the database and amazon_s3, but those are both beyond the scope of this article. I just wanted to get these items onto the disk, and then back off on another computer, and the filesystem was the way to go that required the least configuration.&lt;br /&gt;&lt;br /&gt;":path_prefix" is the file path you want prepended to the storage location for all files stored by attachment_fu.  In my case, "public/iep" is just a directory on my local filesystem, but in production it's a symlink to a location outside of the application folder so that the uploaded files will persist across new deployments (handy strategy for anyone who needs to upload files, thanks to EngineYard for handling that for me).&lt;br /&gt;&lt;br /&gt;":max_size" should be self-explanatory.  If it isn't, you may be in the wrong job.&lt;br /&gt;&lt;br /&gt;Next I set up a couple controller actions to handle the actual uploading of the file:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def upload_iep&lt;br /&gt;  @student = Student.find(params[:id])&lt;br /&gt;  @iep = Iep.new&lt;br /&gt;end&lt;br /&gt;  &lt;br /&gt;def exec_upload_iep&lt;br /&gt;  @student = Student.find(params[:id])&lt;br /&gt;  if @student.iep.nil?&lt;br /&gt;    @iep = Iep.new(params[:iep])&lt;br /&gt;    @iep.student_id = @student.id&lt;br /&gt;  else&lt;br /&gt;    @iep = @student.iep&lt;br /&gt;    @iep.update_attributes(params[:iep])&lt;br /&gt;  end&lt;br /&gt;    &lt;br /&gt;  if @iep.save&lt;br /&gt;    flash[:notice] = 'IEP Uploaded!'&lt;br /&gt;    redirect_to :action =&gt; :all_students&lt;br /&gt;  else&lt;br /&gt;    redirect_to :action =&gt; :upload_iep, &lt;br /&gt;                :id =&gt; @iep.student_id&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;It ain't restful, but it works.  One action to load the form, the other to accept the actual upload.  Nothing in there should look different than dealing with regular rails forms.  Next we have the view:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;% form_for(:iep, :url =&gt; {:action=&gt;:exec_upload_iep}, &lt;br /&gt;    :html =&gt; { :multipart =&gt; true }) do |f| -%&amp;gt;&lt;br /&gt;  &amp;lt;p&gt;&lt;br /&gt;    &amp;lt;label for="iep"&amp;gt;Upload An IEP:&amp;lt;/label&amp;gt;&lt;br /&gt;    &amp;lt;%= f.file_field :uploaded_data %&amp;gt;&lt;br /&gt;    &amp;lt;%= hidden_field_tag :id,@student.id%&amp;gt;&lt;br /&gt;  &amp;lt;/p&amp;gt;&lt;br /&gt;  &amp;lt;p&amp;gt;&amp;lt;%= submit_tag 'Upload' %&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;&amp;lt;% end -%&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The only important bits to remember here are the ":multipart =&gt; true" which makes sure that the form can accept file uploads, and the ":uploaded_data" attribute which is what attachment_fu searches for when manipulating attached files.  Now it's easy to upload an IEP attached with a student anytime.  And the best part is, retrieval is a snap:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;td&amp;gt;&lt;br /&gt;  &amp;lt;a href='#{student.iep.public_filename}'&amp;gt;Download IEP&amp;lt;/a&amp;gt;&lt;br /&gt;&amp;lt;/td&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;that "public_filename" method returns the necessary path to retrieve the file, making it easy for the physician to download the IEP on his computer.  Done and done.  Thanks to &lt;a href="http://github.com/technoweenie"&gt;technoweenie&lt;/a&gt; for writing and maintaining this plugin for the community!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-3442331179118613043?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/3442331179118613043/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=3442331179118613043' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/3442331179118613043'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/3442331179118613043'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/02/pdf-attachments-with-attachmentfu.html' title='PDF Attachments with attachment_fu'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-4158533550013332537</id><published>2009-02-04T11:47:00.003-06:00</published><updated>2009-02-04T12:52:35.997-06:00</updated><title type='text'>Yahoo killed my buzz</title><content type='html'>So I was having a really good week until today.  Billing is coming along nicely for our medicaid project, and the other startup I'm working on will be demoing at the Lake on saturday (I'll post a link on this blog after that date, so that I can get feedback from all of you readers as well).&lt;br /&gt;&lt;br /&gt;Unfortunately, today my experience with &lt;a href="http://www.yahoo.com"&gt;Yahoo&lt;/a&gt; really destroyed that "high-on-life" feeling I'd been having.  You see, we had this idea for handling incoming email with the application.  I wanted to have users be able to send email to the app that would cause things to happen.  So I went to our friendly and efficient hosting provider, &lt;a href="http://www.engineyard.com"&gt;EngineYard&lt;/a&gt;, and asked them to setup a mailbox for us to accept incoming emails.  Like usual, engineyard's customer support got done what we needed and did it with a smile.  They then told me through email that in order to start receiving email there I would need to add an MX record to my DNS listing that would make "msg.domain.com" point to their mail server.  Basically, I just wanted that subdomain to be redirected, while leaving all regular mail sent to "domain.com" to continue as normal.  &lt;br /&gt;&lt;br /&gt;Figuring that this would not be an issue, I asked our business expert to take care of it.  As the title of this post probably indicates to you, it WAS AN ISSUE.  This poor woman spent the first half-hour of this project playing with Yahoo's web interface for their small business registrar system.  No dice.  She called them and spent half an hour waiting, and then longer actually on the phone with a customer service representative who didn't understand what we were asking for.  I got on the next call with her and together we talked with a Yahoo support guy named "Phillip" who told us in an apathetic voice that what we were wanting to do simply wasn't possible. &lt;br /&gt;&lt;br /&gt;Bullshit!  I'm willing to accept something along the lines of "I don't know how to do that", because there are plenty of things that I don't know how to do.  But you're going to say to my face that something is downright impossible you'd better be sure that I'm not going to be able to embarrass you with something as simple as a google search: I have &lt;a href="http://zytrax.com/books/dns/ch8/mx.html"&gt;Many&lt;/a&gt; &lt;a href="http://bobcares.com/index.php/blog/?p=29"&gt;Online&lt;/a&gt; &lt;a href="http://content.websitegear.com/article/subdomain_setup.htm"&gt;Resources&lt;/a&gt; that say this is a perfectly reasonable request that is implemented in DNS records all over the world all the time.  Knowing that I have to demo by Saturday, I'm probably just going to have to work around this by directing all email traffic to the engineyard server and forwarding the regular stuff back to our personal email addresses, so I can get past this, but what a freaking drag!  &lt;br /&gt;&lt;br /&gt;Long story short, don't be suckered in by Yahoo's low prices for domain names.  They will screw you if they get the chance.  As soon as this demo is over, we're switching. &lt;b&gt;UPDATE:&lt;/b&gt; I just checked, and &lt;a href="http://www.godaddy.com"&gt;GoDaddy&lt;/a&gt; supports this kind of MX record, so that's who we'll be switching to.  I recommend you all do the same.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-4158533550013332537?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/4158533550013332537/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=4158533550013332537' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/4158533550013332537'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/4158533550013332537'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/02/yahoo-killed-my-buzz.html' title='Yahoo killed my buzz'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-7259283635517252207</id><published>2009-01-30T10:48:00.002-06:00</published><updated>2009-01-30T11:40:45.767-06:00</updated><title type='text'>Variable Naming Matters!</title><content type='html'>I woke up this morning with a production bug on my plate. Those of you who read this blog regularly probably know that one of the startups I'm working on is a medicaid billing software suite for school districts.  The problem I was having was that there were some transactions being added to our billing batches that shouldn't have been. See, in order to bill a transaction to medicaid, several things must be in place:  the student must have a prescription, the therapy provided must be one of the federally approved therapies, the therapist must have a federally issued NPI number on file, etc.&lt;br /&gt;&lt;br /&gt;These transactions were failing validation, obviously, because they were invalid.  They should never have been billed in the first place.  When I dug into my code, I couldn't see the problem, although based on the title of this post you will probably see it immediately: &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def get_student_detail(s)&lt;br /&gt;  group = s.appts.select{|sa| s.is_medicaid_eligible} &lt;br /&gt;  if group.size &gt; 0&lt;br /&gt;    claim = Claim.new(group,{:batch_id=&gt;self.id})&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I spent a lot of time on this.  I wrote more unit tests, couldn't figure out why they were failing.  I built mocks and tested many underlying methods to prove that they were working properly.  Eventually the only way I figured it out was when I kicked up an "irb" session on my production box and started stepping through this code one piece at a time. what an embarrassment when I realized what I had done.  &lt;br /&gt;&lt;br /&gt;Yes, I went and named my "student" object instance "s".  And then I named my "StudentAppointment" object instance "sa".  And they both have a method called "is_medicaid_eligible" that returns a boolean value.  So, if the student is eligible, all his appointments get billed regardless of whether those appointments are eligible or not, because I mistakenly wrote "s" instead of "sa" in the block on line 2 of the above code.  There are plenty of arguments out there about naming conventions and whether they really matter, and I may not be qualified to participate in many of them, but here's one lesson I won't soon forget:&lt;br /&gt;&lt;br /&gt;if I had used the name "student" for my student variable, and "appointment" for my appoitment variable, I probably wouldn't have spent an hour trying to find this bug.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-7259283635517252207?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/7259283635517252207/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=7259283635517252207' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/7259283635517252207'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/7259283635517252207'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/01/variable-naming-matters.html' title='Variable Naming Matters!'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-4799516394772240352</id><published>2009-01-27T15:15:00.004-06:00</published><updated>2009-01-27T15:17:06.238-06:00</updated><title type='text'>Breaking my own Rules</title><content type='html'>OK, I'm going to post a link to somebody elses blog post. I almost never do this, because I usually feel like there's a comment section on the other person's blog for a reason, so if you have something to say you should "Say it to their face" so to speak.  But in this one case, what I read made so much sense that I had to share it with my audience:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.defmacro.org/ramblings/taming-perfectionism.html"&gt;http://www.defmacro.org/ramblings/taming-perfectionism.html&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Check it out, and note especially the very last sentence.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-4799516394772240352?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/4799516394772240352/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=4799516394772240352' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/4799516394772240352'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/4799516394772240352'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/01/breaking-my-own-rules.html' title='Breaking my own Rules'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-3591140627402711734</id><published>2009-01-24T15:52:00.002-06:00</published><updated>2009-01-24T16:15:22.262-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><title type='text'>Permanent bug fixes</title><content type='html'>One of the startups I'm working on right now involves a lot of SMS communication.  Threading and queueing messages, although not difficult, can present some edge cases that need attention.  One such case was brought to me this week when the application seemed to freeze and error out after a certain message type was sent, followed immediately by a cancellation of the same message.  &lt;br /&gt;&lt;br /&gt;When presented with a bug, my first instinct is always to "think through" the code that I believe is the most probable culprit.  This is usually not the best approach.  Yeah, that's an opinion, and I only have anecdotal evidence to support my viewpoint, but this is MY blog so if you don't want to hear my opinion you don't have to read it.&lt;br /&gt;&lt;br /&gt;To me there are two important components of a GOOD bug fix.  The first is natural: make sure the error/undesireable/unpredictable event stops happening.  The second is too often overlooked: make sure it doesn't happen again.  Because of the second point, and also because you don't always have a stack trace to show you where the problem is (if, for instance, the problem is not an "error" but an undesireable outcome), I think it's important to suppress the "dig in and find it" urge, and to instead start writing tests.  Why?  3 reasons:&lt;br /&gt;&lt;br /&gt;1) unit/functional tests allow you to precisely recreate the circumstances under which a bug occured (or under which you believe it to have occured).  This means that if you don't yet know what's causing the problem, you can use assertions at every step along the way to make sure every bit of state maintained in the code is as you would expect.  In the case of my SMS processing bug, there were many many objects involved in the transaction (because various notifications had to be delivered) and no error was being given so I had no way to know instantly where my data was being monkeyed with.  If I wanted to do an "intuitive" diagnosis (as my insticts would have me do) I'd have been dealing with maybe 60 lines of code to look through.  By setting up my test case and writing assertions at every point, I was able to isolate the place where the problem was occuring (where my first assertion failed) and thus the number of lines of code I had to apply my diagnosis-thought-process to was exactly one.  It's always good to reduce the problem space anyway possible, it can only make your job easier.  (As a bonus, if you're using test-coverage as a metric, then you're improving your numbers WHILE bug-fixing)&lt;br /&gt;&lt;br /&gt;2) By using tests to diagnose the problem, you also have a feedback mechanism that will tell you as soon as it's fixed.  Isn't that handy?  When the test is passing, the bug is dead.  You might write a few more tests afterwards if this bug has opened up a new path in the code that needs coverage, but basically you won't have to go through the process of "Let's try this, deploy it, did it help? no?  Ok, what about this, let me deploy the website again.  How bout now?  No?  Damn! OK, one second".  You still might be following the same path of "try this, try that" but your feedback iteration is reduced significantly.  Running your test suite takes a lot less time than redeploying the code over and over again.&lt;br /&gt;&lt;br /&gt;3) Never experience the same bug twice.  With that test in place (as long as your team is disciplined about running the test suite before deployments) that bug is dead for good.  Were anyone to adjust the code to the point where it would reoccur (something I've seen happen on more than one project), the test will fail, stopping the release cycle until the problem is solved.  You shouldn't ever have to solve the same problem more than once.&lt;br /&gt;&lt;br /&gt;When it comes to pest control, your number one weapon should be an up-to-date suite of unit tests.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-3591140627402711734?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/3591140627402711734/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=3591140627402711734' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/3591140627402711734'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/3591140627402711734'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/01/permanent-bug-fixes.html' title='Permanent bug fixes'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-5310641290295089991</id><published>2009-01-19T16:14:00.002-06:00</published><updated>2009-01-19T16:30:25.316-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='software'/><category scheme='http://www.blogger.com/atom/ns#' term='Rails'/><title type='text'>Advanced Reporting with Excel in Rails</title><content type='html'>This is a bit of an expansion and synthesis of two previous posts, one referring to &lt;a href="http://codeclimber.blogspot.com/2008/11/reporting-in-excel-with-rails.html"&gt;Rails Reporting in Excel&lt;/a&gt;, and one dealing with &lt;a href="http://codeclimber.blogspot.com/2008/12/activerecord-and-large-datasets.html"&gt;Processing large amounts of ActiveRecord Data&lt;/a&gt;.  I had to combine those two skills today as our database size is increasing to the point that some of our larger user reports are having to aggregate more data than is pleasant to store in memory at once.  In the interest of providing a recipe that others could use, I've &lt;a href="http://gist.github.com/49212"&gt;pasted an example of this approach on Gist&lt;/a&gt;.   Essentially, it uses a pagination library (paginating_find, in this case) to page through result sets for reports, limiting the amount of memory space being taken at any one time.  Check it out, leave a comment if it helps you.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-5310641290295089991?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/5310641290295089991/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=5310641290295089991' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/5310641290295089991'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/5310641290295089991'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/01/advanced-reporting-with-excel-in-rails.html' title='Advanced Reporting with Excel in Rails'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-7151204934546423392</id><published>2009-01-16T16:15:00.007-06:00</published><updated>2009-01-16T16:33:00.902-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='software'/><category scheme='http://www.blogger.com/atom/ns#' term='Development Practices'/><title type='text'>Simplify, Simplify</title><content type='html'>I was presented with a challenge in my work today, one which I was not immediately ready to solve.  One of the startups I am working on is a google maps mashup, and a certain requirement we'd decided on earlier involved having certain "markers" on the map blink so that the user's attention would be drawn to them.  The following is a sort of "stream-of-conciousness" recording as I thought about making an image on my google map blink:&lt;br /&gt;&lt;br /&gt;"OK here's my image:"&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_vTrR73WnSs8/SXEH_mPVV8I/AAAAAAAAA_I/ly84vcN99GI/s1600-h/red_pin.gif"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 31px; height: 31px;" src="http://3.bp.blogspot.com/_vTrR73WnSs8/SXEH_mPVV8I/AAAAAAAAA_I/ly84vcN99GI/s320/red_pin.gif" border="0" alt=""id="BLOGGER_PHOTO_ID_5292019826315057090" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;"I need to make it blink.  It's part of a GMarker object (from the google maps API), so maybe there's some sort of animation API call I don't know about yet....Let's go check the Docs [navigate to google maps API docs].....OK, there's a hide method and a show method, those might be useful.....Not much else though.....I guess I could maintain an array of all the markers that need to blink, and use the "SetInterval" javascript function to toggle their visibility once a second.......yeah, that would work, but if I have a hundred or a thousand markers it might slow down a bit.....still, workable....maybe I'll google and see if anyone's done this kind of thing before....[google for "GMarker blink"].....now we're talking...cool, there's like three open source libraries that actually add a "blink" method to the GMarker class, that's handy....OK, so I just need to plugin one of these libraries and then call the "blink" method only on the ones I want to blink, seems pretty simple....It's going to add a whole new javascript file, but hey, that's not really much of a load time drag....."&lt;br /&gt;&lt;br /&gt;By this point I had already constructed the code in my head to an extent, and I was ready to implement it.  Then, as I was downloading the library, I noticed something. As I watched the progress bar on this third party website, I was mesmirized by the little circle-spinny-thingy....you know, it was using that usual symbol for "please wait"......the segmented circle that gets highlighted constantly around and around.  I always thought that was a cool effect, and I remembered that I'd created that effect a couple weeks ago for one of my other projects.  It was pretty neat putting this image together, layer by layer, timing out how long each layer should show for and then saving it as an animated gif. &lt;br /&gt;&lt;br /&gt; Then I slapped my forehead, opened up GIMP, and produced this little baby:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_vTrR73WnSs8/SXEKJNqo8BI/AAAAAAAAA_Q/A6ALhdePfTQ/s1600-h/blinking_red_pin.gif"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 31px; height: 31px;" src="http://4.bp.blogspot.com/_vTrR73WnSs8/SXEKJNqo8BI/AAAAAAAAA_Q/A6ALhdePfTQ/s320/blinking_red_pin.gif" border="0" alt=""id="BLOGGER_PHOTO_ID_5292022190540648466" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Moral of the story, stop and think before you dive into something complicated, there's probably a better way.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-7151204934546423392?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/7151204934546423392/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=7151204934546423392' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/7151204934546423392'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/7151204934546423392'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/01/simplify-simplify.html' title='Simplify, Simplify'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_vTrR73WnSs8/SXEH_mPVV8I/AAAAAAAAA_I/ly84vcN99GI/s72-c/red_pin.gif' height='72' width='72'/><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-1554592536444172383</id><published>2009-01-13T21:17:00.002-06:00</published><updated>2009-01-13T21:26:48.001-06:00</updated><title type='text'>Crunch Time</title><content type='html'>No matter the industry, or the development methodology, or the size of your company, or the scale of your project, everyone has experienced a time of "push".  Call it a hard deadline, or non-negotiable due date,  or whatever you want, it all means the same thing: you'll be working extra hard between now and then on little else.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Well, that's where I am right now.  We're already sending out marketing information regarding a new product that is theoretically ready but perpetually not quite there, and now the race is on to put the finishing touches together.  Stress is high, task list is endless, and I ask myself "Is being part of a startup worth it?"&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Certainly there are people who would answer "no" to this question, and you couldn't fault them for it.  It is perfectly valid to say "I value my home life enough that I never want my work to encroach upon it", and if that were your point of view than working in a large industry where many people share the responsibility for the success or failure of a project would make perfect sense.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I can't quite go there, myself.  It's not that I don't value my home life, it's just that I feel I'm an all around happier person when the things I do at work feel like they have impact:  when I have a stake in the outcome, so to speak.  I would not suggest that this feeling can't be found in a large company, but I guarantee that when you are the sole technical muscle behind a small and rapidly growing project, EVERYTHING you do carries weight.   The cost is just that when it comes to crunch time, you endure it alone.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;A price, yes, but one I pay gladly. &lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-1554592536444172383?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/1554592536444172383/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=1554592536444172383' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/1554592536444172383'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/1554592536444172383'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/01/crunch-time.html' title='Crunch Time'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-2124557860143577086</id><published>2009-01-05T20:16:00.002-06:00</published><updated>2009-01-06T09:14:05.809-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Rails'/><title type='text'>Modifying ActiveRecord fields in place</title><content type='html'>So here's a "gotcha" for Rails that had me chasing my tail for a few minutes today.  &lt;br /&gt;&lt;br /&gt;Imagine that you have a model object that is storing a string that is encoded to represent a large amount of data in a small amount of space.  For instance, let's say you have a calendar item that is using a string that is 31 characters long to represent the state of each day in the month (this is purely hypothetical, of course).  Let's say you want to be able to block of segments of the month at a time, labeling them as "AVAILABLE" or "UNAVAILABLE".  You might have a method on your model object that looks something like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def mark_days(day_1,day_2,status)&lt;br /&gt;    marker = (status == "AVAILABLE") ? "A" : "U"&lt;br /&gt;    while(day_1 &lt;= day_2)&lt;br /&gt;      self.availability[day_1 - 1] = marker[0]&lt;br /&gt;      day_1 += 1&lt;br /&gt;    end&lt;br /&gt;    self.save!&lt;br /&gt;  end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Cool.  Now you run your unit tests, and you notice a strange thing: they're failing.  Adding some debug output, you can see that the model object is indeed having it's field updated correctly, and that all the validation is passing, and that the save method is indicating that it saved properly, but when you reload the object from the database nothing has changed!  &lt;br /&gt;&lt;br /&gt;"Bummer" you might say to yourself.  I know I did.  After some digging into the ActiveRecord documentation, though, I have found the solution, and I hope that in the future someone who needs it comes across this blog post.  &lt;br /&gt;&lt;br /&gt;When dealing with a string field on an AR model object, the model will not save that field unless it thinks it has been changed.  Usually this is done by direct assignment.  For example, if you have a person object named steve, and you want to change his name to fred, you would simply say:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;  person.name = "Fred"&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;When the name attribute is assigned to, AR knows that the field has been changed, and when the next save call comes the name will be persisted.  However, if you will note my example above, I'm not reassigning the field, I'm just modifying parts of it in place.  This causes active record to basically say "Since the reference hasn't changed, the data hasn't changed, and I don't need to save anything".&lt;br /&gt;&lt;br /&gt;Never fear, though, as there's an easy way around it.  If you want to modify a field "in-place", you must notify ActiveRecord of the incoming change.  Observe this corrected code snippet:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def mark_days(day_1,day_2,status)&lt;br /&gt;    marker = (status == "AVAILABLE") ? "A" : "U"&lt;br /&gt;    self.availability_will_change!&lt;br /&gt;    while(day_1 &lt;= day_2)&lt;br /&gt;      self.availability[day_1 - 1] = marker[0]&lt;br /&gt;      day_1 += 1&lt;br /&gt;    end&lt;br /&gt;    self.save!&lt;br /&gt;  end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;By using the  "[attribute name]_will_change!" method, you tell ActiveRecord that despite all of the usual indicators, this field has changed and must be saved next time the call comes in.  Problem solved, and the tests are passing.  May someone benefit from this tidbit of knowledge.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-2124557860143577086?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/2124557860143577086/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=2124557860143577086' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/2124557860143577086'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/2124557860143577086'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/01/modifying-activerecord-fields-in-place.html' title='Modifying ActiveRecord fields in place'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-4699043020649472395</id><published>2009-01-03T15:41:00.002-06:00</published><updated>2009-01-03T15:47:20.882-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Blackberry'/><category scheme='http://www.blogger.com/atom/ns#' term='mobile development'/><category scheme='http://www.blogger.com/atom/ns#' term='employment'/><title type='text'>Contract Work: Blackberry Mobile Development</title><content type='html'>So my startup is coming along nicely, but now I find myself in a unique situation:  we have some work that needs to be done to build a thin blackberry app that does a few basic transactions with our website.  Since my plate is getting a little full, I'm interested in contracting the work out, but I don't know anyone personally who's a great mobile developer.  If that's you, and if you're looking for an opportunity to make some extra cash, email me at my personal address (ethan.vizitei@gmail.com) and let's talk shop.  For the right person, this relationship could turn into a full time position eventually with an equity share.  &lt;br /&gt;&lt;br /&gt;Big pluses:&lt;br /&gt;&lt;br /&gt;-previous experience contracting for small businesses&lt;br /&gt;-worked on mobile apps before, specifically on the blackberry&lt;br /&gt;&lt;br /&gt;I know you're out there, future friend.  Looking forward to hearing from you.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-4699043020649472395?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/4699043020649472395/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=4699043020649472395' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/4699043020649472395'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/4699043020649472395'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2009/01/contract-work-blackberry-mobile.html' title='Contract Work: Blackberry Mobile Development'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-7021884428748756599</id><published>2008-12-31T18:41:00.002-06:00</published><updated>2008-12-31T19:02:42.805-06:00</updated><title type='text'>Happy New Year</title><content type='html'>My first post on this blog happened in January 2008.  At that time, I figured I'd be doing this for a couple months, and then move on to something else.  Instead it's turned into something I truly have enjoyed, as I've gotten all kinds of valuable feedback and advice from readers.  As a final post of the year (before I go find some eggnog and an X-Box party), I just wanted to thank everybody who took the time to read what I had to say and start a conversation with me about it.  I hope those opportunities come up even more frequently ini 2009.&lt;br /&gt;&lt;br /&gt;And now, by the authority of Google Analytics, I give you the 10 most popular posts of 2008:&lt;br /&gt;&lt;br /&gt;10)&lt;a href="http://codeclimber.blogspot.com/2008/11/reporting-in-excel-with-rails.html"&gt;Rails Reporting with Excel&lt;/a&gt; Why write functionality into your webapp that already exists in a spreadsheet program that's been around for years?  Just give your users the data in an excel sheet, and they can manipulate it any way they want (this post will show you how).&lt;br /&gt;&lt;br /&gt;9)&lt;a href="http://codeclimber.blogspot.com/2008/08/centurytel-what-embarrassment.html"&gt;Centurytel: What an Embarrassment&lt;/a&gt;.  Hey kids!  Want to see some comedy?  Well, everyone enjoys a good rant, so step on up and watch as i totally lose my cool and blast my old ISP for doing exactly what all the other ISPs do all the time!&lt;br /&gt;&lt;br /&gt;8)&lt;a href="http://codeclimber.blogspot.com/2008/06/background-jobs-for-rails-applications.html"&gt;Background Jobs in Rails&lt;/a&gt; Have some work that needs to be done in a background thread?  Here's a Q&amp;D way to get it done.&lt;br /&gt;&lt;br /&gt;7)&lt;a href="http://codeclimber.blogspot.com/2008/05/maintaining-discipline.html"&gt;Maintaining Discipline&lt;/a&gt;:  A remorseful post I wrote the morning after I decided to go ahead and "get shit done" without writing tests or refactoring.  A case study in technical debt.&lt;br /&gt;&lt;br /&gt;6)&lt;a href="http://codeclimber.blogspot.com/2008/07/refactoring-in-ruby.html"&gt;Refactoring in Ruby&lt;/a&gt;:  You might call this a "lab" in refactoring.  Basically I just walked through a real-life example of fixing up some bad code that I had run into the day of that post.&lt;br /&gt;&lt;br /&gt;5)&lt;a href="http://codeclimber.blogspot.com/2008/01/remote-nhibernate-5-lessons-learned.html"&gt;Remote NHibernate&lt;/a&gt;  Apparently I'm not the only person who's ever had trouble getting NHibernate to work over a network.  This post doesn't have all the answers, but it was everything I learned when I first attempted it and I've gotten many emails thanking me for the information it does have, so it must be useful to some people.&lt;br /&gt;&lt;br /&gt;4)&lt;a href="http://codeclimber.blogspot.com/2008/05/becoming-less-obtrusive.html"&gt;Becoming Less Obtrusive&lt;/a&gt;  I wrote this post when I finally caught up with the times and started using an unobtrusive approach to javascript.  Not much in the way of implementation here, but if you want to know why I write all my javascript this way now, than this post will tell you.&lt;br /&gt;&lt;br /&gt;3)&lt;a href="http://codeclimber.blogspot.com/2008/06/using-ruby-for-imap-with-gmail.html"&gt;Sending email with Rails and Gmail&lt;/a&gt;.  Ever wanted to have your rails application send out emails, but don't want to configure postfix?  Here's your ticket.  I don't use this trick anymore because I eventually was sending out so many emails to users that google thought I was a spammer, but if your email volume is relatively low this might be the perfect solution for you.&lt;br /&gt;&lt;br /&gt;2)&lt;a href="http://codeclimber.blogspot.com/2008/04/burning-midnight-oil.html"&gt;Burning the midnight oil&lt;/a&gt;.  Honestly I have no idea why this one became as popular as it did.  All I was saying was that I seemed to work better at night, but it became a huge deal.  If you like midnight-coding as well, check it out.&lt;br /&gt;&lt;br /&gt;1) &lt;a href="http://codeclimber.blogspot.com/2008/04/all-i-need-is-programmer.html"&gt;All I need is a programmer&lt;/a&gt;: a short diatribe I wrote after a friend of mine expected me to do a programming project for him for free.  For some reason it must have struck a nerve with a lot of people out there, because I got a lot of very positive feedback from programmers in the same position.&lt;br /&gt;&lt;br /&gt;Thanks again for keeping this blog alive (not through money, because I don't accept donations or run ads, but through attention, because that's really what I crave anyway).&lt;br /&gt;&lt;br /&gt;Happy 2009!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-7021884428748756599?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/7021884428748756599/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=7021884428748756599' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/7021884428748756599'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/7021884428748756599'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2008/12/happy-new-year.html' title='Happy New Year'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-6905602724265075941</id><published>2008-12-31T18:01:00.004-06:00</published><updated>2009-01-01T14:06:27.962-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='software'/><category scheme='http://www.blogger.com/atom/ns#' term='Rails'/><title type='text'>ActiveRecord and Large Datasets</title><content type='html'>So I ran into a neat problem today.  I should clarify right off the bat that when I say "neat" I really mean "ate up all the memory on my server and caused it to slow to a crawl where it was serving requests up at an average of 20 seconds each".  Now on a regular day that would be cause for panic, but our product is used by school districts, so we had next to no traffic today, so I had the luxury of considering my problem a "challenge" instead of an "emergency".  Here's what I found:&lt;br /&gt;&lt;br /&gt;Those of you who are repeat readers of my blog will know that I've done several posts in the past on the topic of generating reports via a background thread so that they don't take up time on one of your mongrels.  That's where my problem was.  One of the "Reports" I can run as the administrator of my startups product is an audit that checks every transaction ever put into our system and reports on the ones that need attention.  The specific business rules aren't important for this post, suffice it to say that we have to filter through every record in our transactions table.  &lt;br /&gt;&lt;br /&gt;Now, the way I had written this before looked something like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;District.all.each do |dist|&lt;br /&gt;    transactions = Transaction.find(:all, :conditions=&gt;"..belongs to district..")&lt;br /&gt;    transactions.each do |trans|&lt;br /&gt;         report_on(trans) if (trans.should_report)&lt;br /&gt;    end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;That's obviously way oversimplified, but the gist of it is there.  In previous months this has worked fine, but every month the transactions table picks up another 200,000 or so records, and I guess this month was the breaking point for the audit report.  You see, the reason I had split up the transactions by school district instead of iterating through all of them at once was to diffuse the problem of bringing too many records into memory at once.  Clearly that's no longer enough of a precaution, because the table has grown to the point where even the number of records for just one school district is just too much to process in memory at once.  We need a new solution.&lt;br /&gt;&lt;br /&gt;ActiveRecord (IMO) is great as a database interface.  What it's not so super at (out of the box) is very large sets of data.  Usually that's not a problem for a website, because for almost everything a user needs to do for a website you'll either be dealing with just one single record per action, or a very small set of records that are pertinent to that specific user.  Now that I need to deal with millions of records in one go, I've kind of exceeded what AR alone is going to be able to do for me.  &lt;br /&gt;&lt;br /&gt;However, let's not forget that there is another case common to web-apps where we need to take a dataset in chunks rather than as a whole.  What happens when you need to show a user 100 records in a table?  Do you just display it all on one page?  You can, but most people wouldn't.  That's why we have so many great plugins that deal with pagination, and that's what I used to dig me out of this hole.  &lt;br /&gt;&lt;br /&gt;At first it seemed unorthodox to use a piece of functionality designed to make views more pleasant as an asset for solving my data processing problem, but after thinking about it for a few minutes I realized that the problem I was trying to solve was exactly the same:  I want to deal with only small chunks of data at a time.&lt;br /&gt;&lt;br /&gt;If you don't have a pagination plugin, I recommend highly the one that I use, &lt;a href="http://www.railslodge.com/plugins/287-paginating-find"&gt;Paginating Find&lt;/a&gt;.  Here's what my code looks like now:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;    arr = Transaction.find(:all, :page=&gt;{:size=&gt;100})&lt;br /&gt;    proceed = true &lt;br /&gt;    while arr.next_page? and arr.next_page! do&lt;br /&gt;        arr.each do |trans|&lt;br /&gt;            report_on(trans) if (trans.should_report)&lt;br /&gt;        end&lt;br /&gt;    end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Done.  Now we're only processing the transactions in batches of 100.  The running time is a little longer, but it happily uses just a very little amount of memory the whole time, so the web-server can keep on cranking.&lt;br /&gt;&lt;br /&gt;Hope someone else out there benefits from the fix!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-6905602724265075941?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/6905602724265075941/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=6905602724265075941' title='12 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/6905602724265075941'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/6905602724265075941'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2008/12/activerecord-and-large-datasets.html' title='ActiveRecord and Large Datasets'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>12</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-8465132727789232298</id><published>2008-12-30T22:51:00.012-06:00</published><updated>2008-12-31T10:27:08.638-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='iTunesU'/><category scheme='http://www.blogger.com/atom/ns#' term='software'/><category scheme='http://www.blogger.com/atom/ns#' term='Algorithms'/><title type='text'>Binary Search</title><content type='html'>This is another journal entry as I follow along with an MIT algorithms class from iTunesU.  If you haven't read the first two posts in this series (&lt;a href="http://codeclimber.blogspot.com/2008/12/insertion-sort.html"&gt;Insertion Sort&lt;/a&gt; and &lt;a href="http://codeclimber.blogspot.com/2008/12/merge-sort.html"&gt;Merge Sort&lt;/a&gt;), what I'm trying to do here is record what I'm learning so that I can solidify it in my mind and catalog any information that others might find useful later.  As I've said in my previous posts, I'm presenting this information as a student, not as a master, so any corrections would be most welcome.&lt;br /&gt;&lt;br /&gt;Today I want to look at the following problem:&lt;br /&gt;&lt;br /&gt;I have a sorted array of 10,000 elements.  I am asked to find a certain element in the array, and return the index of it's location (or some other value like -1 or nil if it is not in the array).  What's the best way to do this?&lt;br /&gt;&lt;br /&gt;Well, before I give you the good answer (which has really already been given away in my title), let's look at the naive solution: iteration.  This is the easy one to implement, and frankly is the first one that would have occurred to me (although I know better now).  Pseudocode for an iterative-searching algorithm follows:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;1   define linear_search(array,token)&lt;br /&gt;2      for each i in 0...(array.size - 1)&lt;br /&gt;3          value = array[i]&lt;br /&gt;4          if(value == token)&lt;br /&gt;5              return i&lt;br /&gt;6          else if(value &gt; token)&lt;br /&gt;7              return -1&lt;br /&gt;8      end&lt;br /&gt;9      return -1&lt;br /&gt;10 end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Not bad really, short and sweet.  Why don't we do a quick analysis to determine what the runtime is?  &lt;br /&gt;&lt;br /&gt;Let's see, I want to do a worst-case analysis, so I'm going to assume that the desired token is the last element in the array.  Line 2 (the loop initializer) is run exactly 1 time, regardless of the size of the array, so there's one operation, and Line 5 is run exactly one time as well (on the loop iteration when we find the token), so now we're up to 2 operations:&lt;br /&gt;&lt;br /&gt;2&lt;br /&gt;&lt;br /&gt;lines 3 and 4 will be run once for every item in the array (in the worst case), so thats 2 operations n times added on at the front:&lt;br /&gt;&lt;br /&gt;2n + 2&lt;br /&gt;&lt;br /&gt;line 6 is run for every element in the array except the last one when it finally finds the token, so that's 1 operation occurring (n-1) times:&lt;br /&gt;&lt;br /&gt;2n + (n - 1) + 2&lt;br /&gt;&lt;br /&gt;which simplifies to:&lt;br /&gt;&lt;br /&gt;3n + 1&lt;br /&gt;&lt;br /&gt;So the naive iteration search runs in O(n) [remember, big-O notation is the upper bound/worst case.  If the token were found near the beginning of the array it would finish much quicker].  You may want to observe as a side note here that our FASTEST sorting algorithm we've looked at so far (merge sort) runs in O(n log2 n), which is actually slower than our slowest searching algorithm.  Be aware that sorting is comparatively expensive, it's just something good to know when working with large collections.&lt;br /&gt;&lt;br /&gt;Now, you probably have guessed from the title of this post that there is a faster way to accomplish all this searching, and even if you've never heard of "Binary Search" before, I guarantee you already know how it works.  To prove it, let me ask you how you might look up the last name "Johnson" in the phone book.  Would you start at the beginning and read through every name until you found the one you were looking for?  Of course not!  You'd just open the book to about the middle and see where you were.  Based on the page you opened to, you'd say either "I'm too far, I need to go back to the left" or "I'm not far enough in, I need to continue somewhere to the right".  In that one action, you've eliminated half of the set of names you have to dig through.  That action, performed recursively, is binary searching.  See the pseudo-code below:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;1    define binary_search(array, token, low, high)&lt;br /&gt;2       if (high &lt; low)&lt;br /&gt;3           return -1 &lt;br /&gt;4       mid = (low + high) / 2&lt;br /&gt;5       if (array[mid] &gt; token)&lt;br /&gt;6           return binary_search(array, token, low, mid-1)&lt;br /&gt;7       else if (array[mid] &lt; token)&lt;br /&gt;8           return binary_search(array, token, mid+1, high)&lt;br /&gt;9       else&lt;br /&gt;10           return mid &lt;br /&gt;11     end&lt;br /&gt;12 end   &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;If you run the benchmarking I've already done for you in my searching code &lt;a href="http://github.com/evizitei/ruby_algorithms/tree/master"&gt;on github&lt;/a&gt;, you will see empirically that this is MUCH faster than our original iterative search.  But, if we want to do this right, we should go ahead and analyze the binary search algorithm so we can give a mathematical comparison between the two.&lt;br /&gt;&lt;br /&gt;First, in order to analyze binary search, we need to produce a recurrence that describes it.  Although we did this in a pretty lengthy way in my post on merge sort, the quick way to do this is to ask "what are the recurrent subproblems" and "how much work is done non-recursively in each call".  For binary search, this is very easy.  As you can see from the pseudo-code above, binary search will decide whether to go to the left or to the right half of the array based on the element it pulls from the middle, so on each successive call it will be considering half of the previous array (n/2).  Unlike merge sort, which performed a recursive call on BOTH halves of the array [2T(n/2)], binary search only considers one or the other [T(n/2)].  So the beginning of our recurrence looks like this:&lt;br /&gt;&lt;br /&gt;T(n) = T(n/2)&lt;br /&gt;&lt;br /&gt;Then the only question we have to ask now is "how much work is being done non-recursively in each recursive call"?  Remember, in merge sort we were merging both halves of the array together, so we had to do one operation for each member of the original array, giving us "n" work per level.  In the case of binary search, we just do the same 3 comparisons regardless of how many elements there are in the array, so there is always some constant "K" of work being done.&lt;br /&gt;&lt;br /&gt;T(n) = T(n/2) + K&lt;br /&gt;&lt;br /&gt;Pretty simple.  Now, we could use substitution and try to guess what the right answer is (this one is simple enough that it would not be too hard to guess "log n", since the problem set halves every time).  If we weren't sure of that, we could use a recursion tree, like we did in merge sort, to show us that we were doing "K" work over "log n" levels, giving us "K * log n" which in Big-O notation would be "O(log n)", and then we could use substitution to prove that log n was a reasonable upper bound.  However, the simplicity and form of the algorithm means this is a great time to introduce the "master theorem", a quick way to solve recurrences of this type. &lt;br /&gt;&lt;br /&gt;In the lecture series I'm listening too, they made the master theorem sound very complex (it could also be because it's all audio, so I couldn't see the blackboard).  After listening to the same section 3 or 4 times though, it began to click. Here's the process:&lt;br /&gt;&lt;br /&gt;The master theorem can only be used to solve recurrences of a certain type:&lt;br /&gt;&lt;br /&gt;T(n) = aT(n/b) + f(n)&lt;br /&gt;&lt;br /&gt;Now, don't balk just because it's a multi-variable equation, it's really quite simple.  "a" is just the number of subproblems (recursive calls).  in merge sort,   we had 2 of these because we called merge_sort once on each half of the original array.  For binary search, "a" is 1, as we just call search again on one half of the array or the other.  It must, logically, be at least one, otherwise you wouldn't need to be using a recurrence because it wouldn't be a recursive algorithm.  "b" is what's used to reduce the size of the problem set on each recursive call.  In both merge sort and binary search, the problem  set is being divided in half for each recursive call, so "b" is 2.  Logically, "b" must be greater than 1, because if it is 1 than the problem set is not getting smaller on each go around, and it will recurse infinitely (or until you run out of memory, pragmatically). "f(n)"  is just some function describing the amount of work done non-recursively in each call.  As we said a moment ago, for binary search there is a constant amount of work being done for each recursive call, so "f(n)" would be "K" (some constant).    Remember that for merge sort, we must merge the two arrays together, which is done in O(n) time, so the work is "n".  Looking again at our recurrence for binary search:&lt;br /&gt;&lt;br /&gt;T(n) = T(n/2) + K&lt;br /&gt;&lt;br /&gt;We can see that binary search does indeed meet the qualifications for the master theorem.  It has an "a" of 1, a "b" of 2, and an "f(n)" of K.  Now, this is where it get's scary if you are intimidated easily.  There are three cases of the master theorem, and you must decide which one your recurrence falls into.  Fortunately, it's easy to remember them because they're all concerned with the relation of "f(n)" to the quantity "n ^(logb a)" (it's hard to show complex equations in a blog.  That last quantity, in english, is "n to the log base b of a".  Although I may be over simplifying, if f(n) is less than "n ^(logb a)", you're in case one.  If f(n) is roughly equal to "n ^(logb a)" (multiplied by log n to some power k, and "k" can be 0), you're in case 2.  And if f(n) is greater than "n ^(logb a)", you're in case three.  Each one of the cases basically tells you what the answer is.&lt;br /&gt;&lt;br /&gt;Case 1 =&gt; T(n) = O(n^[logb a])&lt;br /&gt;Case 2 =&gt; T(n) = O(n^[logb a] * (log n)^(k+1))&lt;br /&gt;Case 3 =&gt; T(n) = O(f(n))&lt;br /&gt;&lt;br /&gt;Now a full proof and analysis of this theorem is beyond the scope of this blog post.  A good resource for more information on the master theorem is &lt;a href="http://cag.csail.mit.edu/~thies/6.046-web/master.pdf"&gt;HERE&lt;/a&gt;. All I want to do is apply the master theorem to my binary search recurrence.  So, first I need to compare f(n) to (n^[logb a]) so I can decide which case to use.  Remember, we said earlier that our "f(n)", or "work done non-recursively on each call", was some constant "K".  Now I need to calculate (n^[logb a]) so I have something to compare it to.  For binary search, b = 2, and a = 1, so we're calculating "n to the log base 2 of 1".  What power do we need to take 2 to in order to produce 1?  0, of course, so we're looking at n^0, which is 1.&lt;br /&gt;&lt;br /&gt;So which case do we fall into?   Is K less than, roughly equal, or greater than n^0?  Well, K is a constant, n^0 is a constant (1), so they're roughly equal.  Looking back, we see that "roughly equal" is defined as equal to n ^(logb a)"(multiplied by log n to some power k, and "k" can be 0)".  we don't really need that log n to some power in order to be roughly equal, so we'll use 0 as our "k" (little k, not to be confused with the big K we're using to represent our "f(n)").  Looking at the above statement of the answer for Case 2, I know that our runtime is:&lt;br /&gt;&lt;br /&gt;O(n^[logb a] * (log n)^(k+1))&lt;br /&gt;&lt;br /&gt;We already showed that n ^(logb a) in our case is n^0, which is 1, so we can make this:&lt;br /&gt;&lt;br /&gt;O(1 * (log n)^(k+1))&lt;br /&gt;&lt;br /&gt;and of course 1 is the multiplicative identity so:&lt;br /&gt;&lt;br /&gt;O((log n)^(k+1))&lt;br /&gt;&lt;br /&gt;and we're using 0 for our little k, so:&lt;br /&gt;&lt;br /&gt;O((log n)^1)&lt;br /&gt;&lt;br /&gt;and 1 is the power identity:&lt;br /&gt;&lt;br /&gt;O(log n)&lt;br /&gt;&lt;br /&gt;so binary search runs in O(log n).  MUCH faster than the O(n) we have for our linear search above.&lt;br /&gt;&lt;br /&gt;So there you have it.  We've now covered all three methods for solving recurrences, and we're successfully analyzing and comparing algorithms.  Pretty neat, huh?  Nothing like a little academic exercise to get your mind going in the morning.  &lt;br /&gt;&lt;br /&gt;As I've mentioned before, I'm implementing each of these algorithms as we discuss them and the code (and the harnesses that benchmark them) are available &lt;a href="http://github.com/evizitei/ruby_algorithms/tree/master"&gt;on github&lt;/a&gt;.  Also, don't forget that I'm learning all of this material off of &lt;a href="http://www.apple.com/education/guidedtours/itunesu.html"&gt;iTunesU&lt;/a&gt;, and you can too.  There are hundreds of interesting courses from top schools available online, for free.  It's an incredibly rich opportunity for self-improvement, and there's no reason not to take advantage of it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-8465132727789232298?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/8465132727789232298/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=8465132727789232298' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/8465132727789232298'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/8465132727789232298'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2008/12/binary-search.html' title='Binary Search'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-9035006016979176724</id><published>2008-12-30T09:00:00.005-06:00</published><updated>2008-12-30T15:51:53.440-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='iTunesU'/><category scheme='http://www.blogger.com/atom/ns#' term='software'/><category scheme='http://www.blogger.com/atom/ns#' term='Algorithms'/><title type='text'>Merge Sort</title><content type='html'>In my last post, I mentioned that I've started listening through an online lecture series on algorithms, and that I'm recording what I'm learning here both to solidify it in my memory, and to try to present the information in a non-intimidating way so that others who share my lack of formal computer science background (and passion for software) can benefit.&lt;br /&gt;&lt;br /&gt;Last time, I demonstrated the "insertion sort" algorithm, and then gave a worst-case analysis that showed that insertion sort performs "n^2 + 2n -2" operations to sort an array, meaning that it scales at On^2.  This is an interesting piece of information in and of itself, but it becomes USEFUL when you can compare insertion sort to other sorting algorithms to see which one is a better choice for your present needs.  To conduct such a comparison, I'll be showing the "merge sort" algorithm in this post, conducting an analysis, and then comparing it to the insertion sort analysis done in my last post.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Merge Sort&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Merge Sort, unlike insertion sort, is a recursive algorithm (if you don't know what that means, the short version is that it's a method that calls itself.   Google for more detail if necessary).  It has two paths on each call: &lt;br /&gt;&lt;br /&gt;1) if the input array has a size of 1 or 0 elements, it's already sorted, and can be returned.&lt;br /&gt;2) if the input array has 2 or more elements, divide the array in half and call merge_sort on each one of them, than merge the two results together.&lt;br /&gt;&lt;br /&gt;Pseudo code for this algorithm is below:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;0  define merge_sort(array)&lt;br /&gt;1     if array.size &lt;= 1&lt;br /&gt;2         return array&lt;br /&gt;3     else&lt;br /&gt;4         split_index = array.size / 2&lt;br /&gt;5         first_half = merge_sort(array[1...split_index])&lt;br /&gt;6         second_half = merge_sort(array[split_index...array.size])&lt;br /&gt;7         return merge(first_half,second_half)&lt;br /&gt;8      end&lt;br /&gt;10 end&lt;br /&gt;11 &lt;br /&gt;12 define merge(left,right)&lt;br /&gt;13    result = []&lt;br /&gt;14    while length(left) &gt; 0 and length(right) &gt; 0&lt;br /&gt;15       if first(left) ≤ first(right)&lt;br /&gt;16           append first(left) to result&lt;br /&gt;17           left = rest(left)&lt;br /&gt;18       else&lt;br /&gt;19           append first(right) to result&lt;br /&gt;20           right = rest(right)&lt;br /&gt;21    end while&lt;br /&gt;22    while length(left) &gt; 0 &lt;br /&gt;23        append left to result&lt;br /&gt;24    while length(right) &gt; 0 &lt;br /&gt;25        append right to result&lt;br /&gt;26    return result&lt;br /&gt;27 end&lt;br /&gt;&lt;/pre&gt;  &lt;br /&gt;&lt;br /&gt;Ok, so far so good.  Now, how do we analyze this?  Well, it's tougher than insertion sort, that's for sure.  Because of the recursive nature of the function, it's difficult to just start adding up the number of times a line gets executed as a function of the size of the input array.  A better place to start to analyze a recursive function is to write a &lt;a href="http://en.wikipedia.org/wiki/Recurrence_relation"&gt;recurrence&lt;/a&gt; for it (A recursive definition of the sequence. ).  How does that work?  Well, let's take a look at the english version of the merge sort algorithm again:&lt;br /&gt;&lt;br /&gt;1) if the input array has a size of 1 or 0 elements, it's already sorted, and can be returned.&lt;br /&gt;2) if the input array has 2 or more elements, divide the array in half and call merge_sort on each one of them, than merge the two results together.&lt;br /&gt;&lt;br /&gt;So the time it takes to sort an array is equal to the time it takes to sort the left half of the array, plus the time it takes to sort the right half of the array, plus the time it takes to merge the two together.  This can be written as follows:&lt;br /&gt;&lt;br /&gt;T(n) = T(n/2) + T(n/2) + (time to merge)&lt;br /&gt;&lt;br /&gt;Which simplifies to:&lt;br /&gt;&lt;br /&gt;T(n) = 2T(n/2) + (time to merge)&lt;br /&gt;&lt;br /&gt;Now what are we going to do with the "time to merge" statement (the one that combines the two sorted arrays)?  Do you see why that's easy to take care of?  Maybe it would be easier if you saw it written out, rather than in psuedo code; the merge function works like this:&lt;br /&gt;&lt;br /&gt;1) take two sorted lists&lt;br /&gt;2) compare the first element in each list.  &lt;br /&gt;3) place the smaller element of the two into the output array (and delete it from the list it came from).&lt;br /&gt;4) repeat until each element has been merged into the output array.&lt;br /&gt;&lt;br /&gt;You should be able to intuitively spot that there will be exactly as many iterations as there are elements to merge, so the runtime is (x*k*n) where x = the number of operations in an iteration (which I think is 4, in our case), k = the constant time it takes to execute one operation, and n is the number of elements in each of the two lists added together.  Thus, in big-O notation, merge has a runtime of On (linear time).  Now, to simplify the final equation, we will take the liberty of saying that since each iteration is a constant number of operations, each iteration runs in constant time, and thus it's easiest to say that it takes n "steps" to merge the two sorted lists. This means that our above time calculation can be rewritten like this:&lt;br /&gt;&lt;br /&gt;T(n) = 2T(n/2) + n&lt;br /&gt;&lt;br /&gt;And this is our "recurrence".  Remember, all that means is that the amount of time it takes to merge_sort a list is equal to the amount of time it takes to merge_sort the first half of the list, plus the amount of time it takes to merge_sort the second half of the list, plus the amount of time it takes to merge them together.&lt;br /&gt;&lt;br /&gt;Now, how do we take that recurrence and turn it into a formula that represents the growth of the runtime in proportion to n (which is what we need to do if we want to compare it to insertion sort)?&lt;br /&gt;&lt;br /&gt;Well, it's not going to be pretty.  The only method I know of solving this is to use substitution, and that method basically goes like this:&lt;br /&gt;&lt;br /&gt;1) guess the right answer&lt;br /&gt;2) prove it&lt;br /&gt;&lt;br /&gt;You can most likely guess that a lot hangs on step one.  Now, you may know off the top of your head that merge sort is "O(n log2 n)", but let's pretend that we didn't know it.  I might just toss out a guess of O(n^2).  What's cool about this is that technically it's correct.  Big O notation just tells us the upper bound, like a &lt;= sign, which means that if an algorithm has a runtime of On^2, it also fits into the set of On^1000 because it's runtime will grow proportionately with n at a rate that is LESS THAN OR EQUAL to n^1000.  So since we know that merge sort's tight upper bound is n log n, we should definitely be able to prove that O(n^2) is a viable loose upper bound.   So, to demonstrate, let's say that I take a guess and say "I think that merge sort will run in less than O(n^2)".  Remember, big-O notation removes all coefficients, so what I'm really saying is that the highest order term will be n^2 and it will have some constant C as it's coefficient.  This means that I'm saying T(n) is actually less than or equal to Cn:&lt;br /&gt;&lt;br /&gt;T(n) &lt;= Cn^2&lt;br /&gt;&lt;br /&gt;This could also be said as follows:  "If I pick a C that is big enough, merge sort will ALWAYS execute with less than C*n^2 operations".  Now, how do we prove it?  Well let's look back at our recurrence:&lt;br /&gt;&lt;br /&gt;T(n) = 2T(n/2) + n&lt;br /&gt;&lt;br /&gt;Since we are trying to prove that T(n) is &lt;= Cn^2, let's substitute in Cn^2 for T(n) in the right side of the equation and solve from there.  Now, we don't actually have any T(n) on the right side, but we do have a T(n/2), and since we know that n/2 is less than n, we can safely say T(n/2) &lt; T(n), so at most T(n/2) is still less than Cn^2, so the following inequality will hold:&lt;br /&gt;&lt;br /&gt;T(n) &lt;= 2C(n/2)^2 + n&lt;br /&gt; &lt;br /&gt;Which can be rewritten to:&lt;br /&gt;&lt;br /&gt;T(n) &lt;= 2C(n^2/4) + n&lt;br /&gt;&lt;br /&gt;and the leading 2 can be eliminated by dividing numerator and denominator by 2:&lt;br /&gt;&lt;br /&gt;T(n) &lt;= C(n^2/2) + n&lt;br /&gt;&lt;br /&gt;and to make it easier to read, I can take out the fraction to the front:&lt;br /&gt;&lt;br /&gt;T(n) &lt;= (1/2)Cn^2 + n&lt;br /&gt;&lt;br /&gt;We're getting close to the inequality we need to prove, which is T(n) &lt;= Cn^2, but we need to get rid of the (1/2) somehow.  The simplest solution in this case is to just add (1/2)Cn^2 to the first term, and then subtract the same amount from the back end of the equation, which gives us this:&lt;br /&gt;&lt;br /&gt;T(n) &lt;= Cn^2 + n - (1/2)Cn^2&lt;br /&gt;&lt;br /&gt;So now we've got Cn^2, but we also have all this trailing junk that's throwing off the inequality.  What you need to remember in order to go through this next step is what an inequality really is.  Consider the following:  &lt;br /&gt;&lt;br /&gt;If I asked you to prove that x &lt; 5, but I already told you before hand that x &lt; 3.  You have no work to do at all.  See, 3 is already less than 5, so anything less than three is less than 5 automatically.  We have the same property in play here, when I rewrite the equation thus:&lt;br /&gt;&lt;br /&gt;T(n) &lt;= Cn^2 - [(1/2)Cn^2 - n]&lt;br /&gt;&lt;br /&gt;It's going to be tough to show that T(n) is less than Cn^2 by itself, but if we can be sure that the quantity we're comparing it to is ALREADY less than Cn^2, then we've no more work to do.  So how do we make sure that  Cn^2 - [(1/2)Cn^2 - n] is less than Cn^2?  well, X minus any positive number is less than X, so as long as everything inside of those brackets comes out to a number that is &gt;= 0, then we know our whole right side is less than Cn^2, and we've proved that On^2 is a reasonable loose upper bound for T(n).  So to prove this, we just need to set up the following inequality:&lt;br /&gt;&lt;br /&gt;(1/2)Cn^2 - n &gt;= 0&lt;br /&gt;&lt;br /&gt;And now we solve:&lt;br /&gt;&lt;br /&gt;(1/2)Cn^2 - n &gt;= 0&lt;br /&gt;(1/2)Cn^2 &gt;= n&lt;br /&gt;Cn^2 &gt;= 2n&lt;br /&gt;&lt;br /&gt;So now we see that as long as C is greater than or equal to 2, the quantity [(1/2)Cn^2 - n] will be greater than 0, which means the entire quantity Cn^2 - [(1/2)Cn^2 - n] will be less than Cn^2, which means that we've shown On^2 to be an upper bound for the merge sort.  Note carefully here that we have not shown that it is the tightest upper bound, only that it is AN upper bound (as is On^3, On^4, On^5, etc).  All we've said is that T(n) is guaranteed to be less than Cn^2 if C is at least 2:&lt;br /&gt;&lt;br /&gt;T(n) &lt;= 2n^2&lt;br /&gt;&lt;br /&gt;So that's an interesting piece of information.  However, it's not super useful.  Remember, we want to compare merge sort to insertion sort.  Insertion sort has a worst case operation count of "n^2 + 2n -2", and the upper bound we've just proved for merge sort (2n^2) is much bigger than n^2 + 2n - 2 for large n.  This let's us know that we haven't proven a very tight upper bound, because empirically if you benchmark insertion sort against merge sort for n = 10000, merge sort actually performs at least two orders of magnitude BETTER than insertion sort (&lt;a href="http://github.com/evizitei/ruby_algorithms/tree/master"&gt;download my example implementations from a github&lt;/a&gt; and run them yourself for proof).&lt;br /&gt;&lt;br /&gt;We need to be able to prove a tighter upper bounds, and it would be helpful if we could do it without guessing.&lt;br /&gt;&lt;br /&gt;The way that the MIT course recommends finding out what sort of bound we're looking for involves using what's called a recursion tree to visually picture the algorithm and determine the time spent based on it.  For merge sort, this is fortunately very simple, as it's hard to convey something as visual as a recurrence tree on a blog.&lt;br /&gt;&lt;br /&gt;The idea is this:  We want to compute the amount of time it will take to perform T(n) (the full sort). T(n) is defined as the sum of T(n/2) [sorting the lefts side] plus T(n/2) [sorting the right side] plus n work on the side that is done non-recursively (that's the merging part).  &lt;br /&gt;&lt;br /&gt;If I were to draw this in a tree, for an 8 element array, it would look something like this:&lt;br /&gt;&lt;br /&gt;                                                    T(n)&lt;br /&gt;                                         /                          \&lt;br /&gt;                             T(n/2)                                T(n/2)&lt;br /&gt;                          /           \                               /             \&lt;br /&gt;               T(n/4)              T(n/4)             T(n/4)              T(n/4)&lt;br /&gt;               /        \            /        \                /     \             /           \&lt;br /&gt;          T(n/8) T(n/8) T(n/8) T(n/8) T(n/8) T(n/8) T(n/8) T(n/8)&lt;br /&gt;&lt;br /&gt;In order to determine the amount of work done, you find the amount of work done on each level, and sum it up.  This is easy in the case of merge sort, because if you look at each level you will notice a pattern.  The root will have to merge 8 elements, which is our n, so level one does n work.  level 2 has two nodes, each one works on 4 elements, which is n/2,  so the amount of work done on that level is n/2 + n/2 which is n.  level 3 has 4 nodes, each one working on 2 elements, which is n/4, so the total work is n/4 + n/4 + n/4 + n/4.  See the pattern?  The sum of work on each level is n, so in order to find the total we just need to know how many levels there are, and multiply that number by the work done on each level (n).  Since we see that an array of 8 elements has 4 levels, and if we had 16 there would be 5 levels, and if we had 32 there would be 6 levels, we can tell immediately that the recursion tree for merge sort will have (log2 n + 1) levels.  What does that give us?  &lt;br /&gt;&lt;br /&gt;n(log2 n + 1)&lt;br /&gt;n log2 n + n&lt;br /&gt;&lt;br /&gt;which is in big-O notation:&lt;br /&gt;&lt;br /&gt;O(n log2 n)&lt;br /&gt;&lt;br /&gt;Alright!  Now we're getting somewhere!  Now, we could use substitution to prove beyond a shadow of a doubt that this is a valid upper bound, but for the purposes of this blog post it's unnecessary.  What's important to us now is the ability to compare one algorithm's runtime to another.  We saw before that insertion sort performs "n^2 + 2n -2" operations, which in big-O notation would be O(n^2).  We just showed that merge sort grows at O(n log2 n).  Which one is better for a large array?  Merge sort, clearly, and the bigger the array the more true it is.  On a small set it hardly matters (in fact, on very small sets insertion sort is actually faster than merge sort), but the more elements you add the greater the disparity between the two.&lt;br /&gt;&lt;br /&gt;That's it for today, so I'll take this last post to remind everyone reading that I'm approaching this subject not as an expert, but as an amateur.  I want to get better, so I welcome your corrections if you find fault in the above, but please don't think that I'm maliciously trying to mislead anyone or that I believe I'm some sort of authority, any mistakes in the above are the honest errors of the apprentice.  &lt;br /&gt;&lt;br /&gt;Again, code for everything I've covered in this series can be obtained from github:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://github.com/evizitei/ruby_algorithms/tree/master"&gt;http://github.com/evizitei/ruby_algorithms/tree/master&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-9035006016979176724?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/9035006016979176724/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=9035006016979176724' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/9035006016979176724'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/9035006016979176724'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2008/12/merge-sort.html' title='Merge Sort'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-4460665808682338891</id><published>2008-12-29T14:50:00.009-06:00</published><updated>2008-12-29T16:38:38.862-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='iTunesU'/><category scheme='http://www.blogger.com/atom/ns#' term='software'/><category scheme='http://www.blogger.com/atom/ns#' term='Algorithms'/><title type='text'>Insertion Sort</title><content type='html'>I've discovered a really fun new past-time this week as I've been looking for ways to increase my facility and skill in programming without necessarily spending any money (cash is short in the startup world).  For those of you who don't know about this great opportunity, go check out &lt;a href="http://www.apple.com/education/guidedtours/itunesu.html"&gt;iTunesU&lt;/a&gt; immediately.  Essentially, it's got real lectures from top universities on pretty much any topic you could be interested in (many in video format as well as audio), and all the content I've run into so far has been free.  Now, is it as good as taking the class yourself?  Probably not, as you obviously don't get college credit, and you can't ask questions to your professor, but for my purposes (pure self-improvement) and with my research ability (I'm pretty sure I can find the answer to any questions I might have online somewhere), I feel like it's something worth my time.&lt;br /&gt;&lt;br /&gt;Currently, I'm listening to the first lecture in an MIT course on algorithms, and as an exercise to see if I'm retaining the information at all, I decided to do a little report here on my blog regarding what I learned in that short lecture recording.  If it all comes out well, I'll continue throughout the series and see if I just might make a computer scientist out of myself yet.  ; )&lt;br /&gt;&lt;br /&gt;If you want to brush up on your familiarity with algorithms, or if you're an expert and would like to critique my presentation of the information for accuracy and clarity [I'm looking at you, &lt;a href="http://cadrlife.blogspot.com/"&gt;(cadr life)&lt;/a&gt;], I welcome you as a reader.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Is performance important?&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;The professor for this course began by asking this question, but in the form of "what is MORE important than performance?".  Many answers came to the front: usability, features, maintainability, security, and a host of others.  The question is than raised, since all these things are often desired above performance, should we care about learning algorithms at all?  Of course, since the professor probably wants to give the other 24 lectures in the series, the answer was "yes".  He says it's because good performance gives you the currency you need to spend on other necessary aspects of the software, and I'm sure there's a long discussion that could be had about what aspects of improving performance for your software are and aren't important, but for the sake of moving on to the real meat of the course I will avoid spending any unnecessary energy on fleshing out that discussion.  Instead, I want to proceed directly to the first example of an algorithm given and the analysis conducted upon it.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Insertion-sort&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;So far I'm not out of my depth;  insertion sort isn't an unfamiliar algorithm to me.  You pull elements off the unsorted portion of the array (which initially is the entire array), and insert them at the correct point in the sorted portion of the array (which starts out as none of the array, but grows by one element with each iteration). Some non-standard over-simplified pseudo-code follows:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;0   define insertion_sort(A)&lt;br /&gt;1        for i = 1 to length[A]-1 do&lt;br /&gt;2           element = A[i]&lt;br /&gt;3           j = i-1&lt;br /&gt;4           while j ≥ 0 and A[j] &gt; element do&lt;br /&gt;5                A[j + 1] = A[j]&lt;br /&gt;6                j = j-1&lt;br /&gt;7           end&lt;br /&gt;8           A[j+1] = element&lt;br /&gt;9        end&lt;br /&gt;10 end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;So, how do analyze this algorithm's runtime in a meaningful way so that we can compare it to other algorithms that accomplish the same thing?  (this is where my lack of a formal background is shown, so correct me where necessary, but be nice).&lt;br /&gt;&lt;br /&gt;As I understand it, one good way to analyze and compare algorithms is to first assume that each "operation", or "step", takes some constant time, and then count how many times each operation in the algorithm is executed in the worst case scenario (ie, operating on the input that would cause it to perform the most work).  What follows is my attempt at performing this analysis on the insertion sort pseudo-code above:&lt;br /&gt;&lt;br /&gt;OK, line 0 is a method definition, so we can discard that as part of the discussion.  Next is line 1, which is really only executed once (since the loop is not redefined on each iteration).  So far, our analysis looks like this:&lt;br /&gt;&lt;br /&gt;L1&lt;br /&gt;&lt;br /&gt;Now, I think I can look at lines 2,3, and 8 all on the same level, since they are each executed once for each iteration of the outer for loop.  Since that loop starts with the second element in the array (index 1) and moves through each element, each of these lines will be executed "n-1" times ("n" being the number of elements in the array to be sorted).  So including each of these in the equation, our analysis now looks like this:&lt;br /&gt;&lt;br /&gt;L1 + (n-1)*(L2+L3+L8)&lt;br /&gt;&lt;br /&gt;The only lines we have left to include in the analysis are lines 5 and 6, but this part is the trickiest of the whole process.  You see, those lines are only run as many times as it takes to find the right spot to insert the current element being considered.  If that element is smaller than all the elements in the already sorted array, it will have to iterate through every sorted element to arrive at the correct insertion point, but if the current element is BIGGER than all the sorted elements, then those lines won't even get executed once on that particular run through the outer for loop.  Thus, in an average scenario, you'll have a variable number of executions for that particular segment.  However, remember now that we're trying to consider the worst case scenario.  That means that we should assume that the list begins sorted in reverse order, and that each element will always have to be inserted at the beginning of the already sorted portion of the array, thus requiring a full traversal of the sorted portion on each iteration of the for loop.  This means that on the first for loop iteration, those lines will be run 0 times, than once, than twice, than three times and so on for each iteration. So on the LAST iteration of the for loop, they will be run "n-1" times (one less than the full size of the array).  So now we're looking at the following for our analysis:&lt;br /&gt;&lt;br /&gt;L1 + (n-1)*(L2+L3+L8) + (L5+L6)*(0+1+2..(n-3)+(n-2)+(n-1))&lt;br /&gt;&lt;br /&gt;Now this probably looks rather intimidating, but it can be simplified rather easily.  First, since we said earlier that we were assuming all operations took some constant time, I can replace each of the line numbers with a "k".  This gives us the following:&lt;br /&gt;&lt;br /&gt;k + (n-1)*(3k) + (2k)*(0+1+2..(n-3)+(n-2)+(n-1))&lt;br /&gt;&lt;br /&gt;That's a little better.  Next, if you've had any higher math, you'll know that the arithmetic series at the right of the equation (the summation of 1 to n-1) can be simplified as well.  I seem to remember that the sum of 1 to n can be written as follows:&lt;br /&gt;&lt;br /&gt;(1+n)*(n/2)&lt;br /&gt;&lt;br /&gt;if this is the case (and I just looked it up again, so I know that it is) than if we only wanted the sum up until "n-1", not all the way to n, all we need to do is subtract the final term in the series (n):&lt;br /&gt;&lt;br /&gt;[(1+n)*(n/2)] - n&lt;br /&gt;&lt;br /&gt;I actually prefer it to be multiplied out, so I would write it this way:&lt;br /&gt;&lt;br /&gt;[(n+n^2)/2] - n&lt;br /&gt;&lt;br /&gt;which gives us the following formula for the length of time in our worst case insertion sort as a function of the size of the input array:&lt;br /&gt;&lt;br /&gt;f(n) = k + (n-1)*(3k) + (2k)*([(n+n^2)/2] - n)&lt;br /&gt;&lt;br /&gt;Still sort of big and ugly, but stick with me here.  The next step towards simplifying it is realizing that we don't want to know the actual runtime, we just want to be able to compare it's runtime to the runtime of similar algorithms.  We want to be able to relate it to other algorithms using a common measuring stick.  As I understand it, this means we aren't looking for "amount of time" so much as "number of operations".  Since k is one operation in our equation, what we are really asking is "how many k's will occur in the worst case scenario?".  This means we can move the k constant out of the equation ("divide both sides by k", if you want to think about it that way) and just look at the whole thing in terms of n:&lt;br /&gt;&lt;br /&gt;f(n)/k = 1 + 3(n-1) + 2([(n+n^2)/2] - n)&lt;br /&gt;&lt;br /&gt;which can be multiplied out to be:&lt;br /&gt;&lt;br /&gt;f(n)/k = 1 + 3n - 3 + n + n^2 - 2n&lt;br /&gt;&lt;br /&gt;And now we can combine like terms:&lt;br /&gt;&lt;br /&gt;f(n)/k = n^2 + 2n -2&lt;br /&gt;&lt;br /&gt;Hey!  That's a quadratic equation!  That's not so bad at all!  (Check my math on this, BTW; I'm no pro and have no easy way to validate my answer).  Now I can say that for any array of size "n", insertion sort (in the worst case) will execute "n^2 + 2n -2" operations to sort it, and I can compare that function to similar functions for other sorting algorithms to get an idea of what's faster.  See? This stuff isn't nearly as hard as it seems.&lt;br /&gt;&lt;br /&gt;This would probably be as good a time as any to mention that you needn't go through this whole process when analyzing an algorithm, as the most significant factor in the final performance appears to be the highest order term with disregard for constants or lower order terms.  Thus, in the common &lt;a href="http://en.wikipedia.org/wiki/Big_O_notation"&gt;Big O notation &lt;/a&gt;, insertion sort's worst-case scenario analysis is really just "On^2".&lt;br /&gt;&lt;br /&gt;Well, this post has probably gone on for long enough.  Check back soon, as I'm having a blast with this and hope to continue down this path for a few posts, and I really encourage everybody to go check out iTunesU, there's a ton of interesting material there.&lt;br /&gt;&lt;br /&gt;By the way, I had to play around with some code while I was doing this to make sure I understood the sorting algorithm correctly.  If you'd like to see my implementation, you can check it out on github here: &lt;br /&gt;&lt;br /&gt;&lt;a href="http://github.com/evizitei/ruby_algorithms/tree/master"&gt;http://github.com/evizitei/ruby_algorithms/tree/master&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-4460665808682338891?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/4460665808682338891/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=4460665808682338891' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/4460665808682338891'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/4460665808682338891'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2008/12/insertion-sort.html' title='Insertion Sort'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-3202524691888835388</id><published>2008-12-23T17:53:00.003-06:00</published><updated>2008-12-23T17:59:30.126-06:00</updated><title type='text'>Merry Christmas to Me!</title><content type='html'>It's funny how my RSS reader structured things for me today.  The first article I picked up was one from a developer's blog where he talked about how Rails was slowing down in it's evolution and was beginning to frustrate him as it failed to reinvent itself in the same exciting ways it did when it was cutting edge.  I could sympathize with that to a point.  After all, that's a natural consequence of having more and more people involved with and dependent on the framework in question.  It was humorous to me, though, that the very next article I looked at was the &lt;a href="http://weblog.rubyonrails.org/2008/12/23/merb-gets-merged-into-rails-3"&gt;announcement&lt;/a&gt; that Merb 2.0 and Rails 3.0 would be the same entity.  Woot!  &lt;br /&gt;&lt;br /&gt;Merb was built after rails, and learned a lot of lessons from it's development, and now those lessons are going to be brought back to their source as all the performance optimizations and component-agnosticism that make Merb such a great framework are brought into Rails to allow the old workhorse to make a major leap forward.  Looks like the beta will be attempting a release around May, but I'm sure that one way or another we'll have a functional version to play with by the end of next year. Hooray for cooperation!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-3202524691888835388?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/3202524691888835388/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=3202524691888835388' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/3202524691888835388'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/3202524691888835388'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2008/12/merry-christmas-to-me.html' title='Merry Christmas to Me!'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-3007579684445839644</id><published>2008-12-23T14:10:00.003-06:00</published><updated>2008-12-23T14:14:00.310-06:00</updated><title type='text'>Ramen Profitable</title><content type='html'>Big news from the startup I'm working on: we've hit a major milestone.  After about 8 months of work, we've finally reached the level of income that Paul Graham describes as &lt;a href="http://www.paulgraham.com/fundraising.html"&gt;Ramen Profitable&lt;/a&gt;.  Yes, this month for the first time we'll actually take in slightly more money than we spent.  No telling if this will continue steadily, but it's a very encouraging sign.  Theoretically, as long as we contain our expenses, this gives us the runway we need to stay alive while we continue to bring on new customers and realize the full potential of the ones who are already on board with us.  So in my house, we'll be celebrating more than just the holidays this year.  ; )&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-3007579684445839644?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/3007579684445839644/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=3007579684445839644' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/3007579684445839644'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/3007579684445839644'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2008/12/ramen-profitable.html' title='Ramen Profitable'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-4301042593549793215</id><published>2008-12-23T09:30:00.002-06:00</published><updated>2008-12-23T09:32:33.724-06:00</updated><title type='text'>Branching Out</title><content type='html'>Just a one time announcement for readers who have an interest in other areas of my life besides technology: I started a &lt;a href="http://fireable.blogspot.com"&gt;second blog&lt;/a&gt; yesterday on blogger that will be following my attempt to become a member of the local volunteer fire department.  If that kind of thing interests you, head on over and take a look.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-4301042593549793215?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/4301042593549793215/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=4301042593549793215' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/4301042593549793215'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/4301042593549793215'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2008/12/branching-out.html' title='Branching Out'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-812395877762070808</id><published>2008-12-16T17:49:00.002-06:00</published><updated>2008-12-22T10:54:14.265-06:00</updated><title type='text'>How to advance medical science while you sleep</title><content type='html'>A friend of mine recently sent me a link to &lt;a href="http://boinc.berkeley.edu/"&gt;BOINC&lt;/a&gt;.  If you're familiar with the program SETI@home, than you already know about this middleware.  Basically, BOINC is the framework that allows you to donate your unused computer time (via a screensaver that kicks in when you aren't working) to the CPU-intensive project(s) of your choice.  That's right, you can contribute to furthering science just by not using your computer from time to time (if you're one of those people who requires sleep, this is particularly easy since you already have 1/3 of the world's rotation cycle where your computer processor is unused that you can give away to needy science projects).&lt;br /&gt;&lt;br /&gt;Personally, I have all three of my computers running &lt;a href="http://boinc.bakerlab.org/rosetta/"&gt;Rosetta@home&lt;/a&gt;.  The full description of what they do is &lt;a href="http://boinc.bakerlab.org/rosetta/rah_graphics.php"&gt;here&lt;/a&gt;, along with the &lt;a href="http://boinc.bakerlab.org/rosetta/rah_medical_relevance.php"&gt;medical relevance&lt;/a&gt; of the project, but the short version is as follows:&lt;br /&gt;&lt;br /&gt;Proteins are made up of amino acids.  There are 20-some amino acids used in the construction of proteins, and each one of them has a different shape, and exerts different forces on it's surroundings.  The forces exerted by the amino acids cause the protein (which could be thought of as a chain of amino acids) to take on a particular shape (which they call a "fold").  The basic goal of Rosetta@home is to run through many likely folds for each protein, trying to produce the one with the lowest energy, which is the greatest predictor of the proteins true shape.  The results of this research can potentially be applied to problems ranging from Malaria, to Cancer, to HIV.  As you can imagine, this is a CPU-intensive project, so distributed computing works quite well.&lt;br /&gt;&lt;br /&gt;Hence, it's a good cause, and you've got a computer anyway that you don't use 24-hours daily.  Why not give?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-812395877762070808?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/812395877762070808/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=812395877762070808' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/812395877762070808'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/812395877762070808'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2008/12/how-to-advance-medical-science-while.html' title='How to advance medical science while you sleep'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-3433531476449674480</id><published>2008-12-11T12:47:00.004-06:00</published><updated>2008-12-11T13:15:29.795-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='software'/><category scheme='http://www.blogger.com/atom/ns#' term='IE7'/><category scheme='http://www.blogger.com/atom/ns#' term='CSS'/><category scheme='http://www.blogger.com/atom/ns#' term='UI'/><category scheme='http://www.blogger.com/atom/ns#' term='HTML'/><title type='text'>Internet Explorer and the Z-Index (or, how I wasted a whole 3 hours of my life)</title><content type='html'>As my most recent post mentioned, I frequently get frustrated over small inconsistencies between the way firefox/safari renders my projects, and the way they look in internet explorer.  However, as any pragmatic web-developer will tell you, IE ain't leaving anytime soon, so I didn't have the option of just telling all my users to switch browsers (though it would have pleased me greatly).  Fortunately, IE-8 looks like it will be fixing a lot of the quirks that IE has had in the past.  UN-fortunately, IE6 is still hanging around on many corporate/government/home systems as a plague to all developers, even a couple years after the release of IE7, which means that if we follow the pattern than we won't be able to rely on windows users having IE8 until a full decade after it's release.  So, until that happy day arrives, here's a little tip that may save someone out there time when they are having a bad day with IE.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;z-index&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;I've got this layout I've been working on for my startup.  It has a header, and three vertical columns on the page (should seem familiar, this is a very common layout scheme).  At one point about halfway down the page, I have this really large element in the center column that expands a bit over the borders of the column it's in.  That's totally intentional, because my design called for this large element to overlap and stand in front of the edges of elements in both of the side columns, making it look kind of like the top-most magazine clipping in a child's collage.  &lt;br /&gt;&lt;br /&gt;Now, the way I was achieving this effect was through the use of the "z-index" css property.  If any of you readers are software developers who are not familiar with HTML/CSS, the "z-index" basically is an integer that you use to guide the browser into placing elements "on top" of each other (with respect to the perspective of the viewer.  x-axis runs left to right, y-axis runs top to bottom, z-axis runs front to back).  The higher an element's z-index, the closer it is to the viewer, or the closer to the "top of the stack" it is.  For the sake of example, let's say I gave the elements in the left and right columns of my layout all a z-index of "1", and then gave my large center element a z-index of "2".  In Firefox and Safari, this works no problem.  Open it in IE, and God only knows what will be on top of what.&lt;br /&gt;&lt;br /&gt;Now, I had no idea what was going on when I saw this, and it took quite a bit of research for me to figure out what the problem was.  Aren't you lucky that you came across this blog post before YOU had to do that kind of research?  I'll answer that for you: "Yes, you are very lucky".  I can say this confidently because the answer is so stupidly simple that you would have cried if you had spent hours trying to find it (I know I did).&lt;br /&gt;&lt;br /&gt;Internet explorer (even version 7) does not respect the z-index property when the elements in question have different parent elements.  In my case, the "div" tags that represented my three columns didn't have z-indexes assigned to them.  The elements in the center column were children of one div tag, the elements in the left column were children of another, and the elements in the right column were children of a third div tag, none of which had z-indexes assigned to them.  Basically, it seems IE only uses z-indexes of children with the same direct parent to calculate positioning.  The fix?  I gave my columns Z-index properties ("1" for each of the flanking columns, "2" for the center) instead of the elements inside of them.  Since the columns were siblings, IE was finally able to figure out what was on top, and now it looks fine in all three browsers.  So there you have it, a special fix for a special browser.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-3433531476449674480?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/3433531476449674480/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=3433531476449674480' title='11 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/3433531476449674480'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/3433531476449674480'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2008/12/internet-explorer-and-z-index-or-how-i.html' title='Internet Explorer and the Z-Index (or, how I wasted a whole 3 hours of my life)'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>11</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-4192603514985868448</id><published>2008-12-09T16:52:00.002-06:00</published><updated>2008-12-09T16:57:17.474-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='IE7'/><category scheme='http://www.blogger.com/atom/ns#' term='Microsoft'/><category scheme='http://www.blogger.com/atom/ns#' term='rants'/><title type='text'>UI Pain</title><content type='html'>Time for a mini-rant.&lt;br /&gt;&lt;br /&gt;Everytime I get something working just the way I want it:  looking a certain way, positioned a certain way, colored a certain way.  Everytime, I end up slapped in the face as soon as I look at it in Internet Explorer.  It's like it's actively rejecting my work, telling me that it just isn't content to render content the way the other browsers do (Firefox and Safari, of course).  No; instead it requires custom attention and special handling, kind of like a worthless train-wreck of a person strung out on crack who happens to be the nephew of a powerful politician. &lt;br /&gt;&lt;br /&gt;Screw it, I guess I've got more work ahead of me.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-4192603514985868448?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/4192603514985868448/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=4192603514985868448' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/4192603514985868448'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/4192603514985868448'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2008/12/ui-pain.html' title='UI Pain'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-6058133095953777328</id><published>2008-12-09T08:38:00.006-06:00</published><updated>2008-12-09T09:18:58.681-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Images'/><category scheme='http://www.blogger.com/atom/ns#' term='startups'/><category scheme='http://www.blogger.com/atom/ns#' term='UI'/><title type='text'>Scaling Images with GIMP</title><content type='html'>Here's a UI tip for all of you who are software developers but happen to need to get something done with an image.&lt;br /&gt;&lt;br /&gt;As readers of this blog probably know, my primary skill-set is software.  That's what I've spent the most time learning, and it's what I'm most comfortable with.  In previous jobs, when it came to the User Interface, I'd have an HTML template handed to me, built by a talented web designer, and then I would make it actually do stuff.  But now, working on a startup, we don't have a dedicated UI guy.  I AM the UI guy.  I'm also the software guy, the server guy, the internal IT guy, and the customer service guy.  Because of the position that puts me in, I've learned a lot about web user interfaces in a sort of trial-by-fire pedagogy, and this morning I figured something out that just hadn't occurred to me before, and thought I might share it with other "non-Ui-guys" who are in the position of doing the best they can. &lt;br /&gt;&lt;br /&gt;For a web page I was working on, I had three images that had to be resized way way down to fit up in a banner.  I have a passing knowledge working with GIMP, so I fired it up and cropped and scaled until it was about the right size.  But you know what?  It looked terrible. It was so pixelated that you couldn't even tell what it was by just looking at it (a speedboat, for the curious).  &lt;br /&gt;&lt;br /&gt;Not knowing what else to do, I started redoing the process from the beginning, using slightly different configuration settings, trying to scale down less, saving to different formats, and it just looked bad and worse.  I had to actually THINK for a minute before I figured out what the deal was.  Basically, when you scale an image using GIMP, you have the following dialog box to work with:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_vTrR73WnSs8/ST6Fsz5-HhI/AAAAAAAAArM/KXmNZyGVVQk/s1600-h/Picture+1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 310px; height: 313px;" src="http://2.bp.blogspot.com/_vTrR73WnSs8/ST6Fsz5-HhI/AAAAAAAAArM/KXmNZyGVVQk/s400/Picture+1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5277802818218892818" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Notice the resolution settings, as I just didn't mess with them as I was scaling.  If you think about it, of course you will lose quality as you shrink a picture.  Why?  Well if you leave the resolution at a constant 72 pixels per inch, then scale the picuture down by half, it can only fit every other pixel from the original image into the new one, so you've lost half the data of the original file.  What I did instead was to increase the resolution by the same percentage that I was decreasing the size of the picture.  Thus, if I was making it a 4th of the original width, I quadrupled the number of pixels per inch along the X-axis (and likewise matched the ratio of heights to the Y-axis resolution), thus preserving all the data from the original picture as the size shrank.  What resulted was a much sharper and clearer tiny picture, that now looks great on our webpage.  &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Overall word to the wise:  I was about to just hire a "professional" to do this stuff for me, and if it was a really complicated layout that I needed it might have made more sense. But for something simple, it's not to challenging to just sit down and grind out something that will work.  If you're smart enough to write good code, I guarantee you that you are capable of understanding HTML/CSS and image editors.  At least give this stuff a shot before you outsource it, it might not be as challenging as you think, and when you're bootstrapping almost any cash savings is a win.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-6058133095953777328?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/6058133095953777328/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=6058133095953777328' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/6058133095953777328'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/6058133095953777328'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2008/12/ui-tip-for-programmers.html' title='Scaling Images with GIMP'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_vTrR73WnSs8/ST6Fsz5-HhI/AAAAAAAAArM/KXmNZyGVVQk/s72-c/Picture+1.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-294680397865315179.post-3908854867517873012</id><published>2008-12-07T07:40:00.005-06:00</published><updated>2008-12-07T08:37:13.162-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='fun'/><category scheme='http://www.blogger.com/atom/ns#' term='3D modeling'/><title type='text'>Cross Training</title><content type='html'>I spend most of the day, every day, writing RoR code for my startup.  It's fun, to a point, but sometimes you need to take a break.   I've got a list of hobbies, things I usually do in my free-time:  Rock-Climbing, Piano, etc.   But recently I've been into video-game development as my down-time filler, and so I've found some new fun things to do in that sector.  One of those things is 3D-modeling.  &lt;br /&gt;&lt;br /&gt;Now I'm not going to claim to be any kind of authority on this subject.  I've been playing around in &lt;a href="http://www.blender.org/"&gt;Blender&lt;/a&gt; for maybe 8 hours total, and my lack of artistic skills is apparent to no-one so much as me.   Nevertheless, I've quickly come to appreciate just how much work artists have to do in order to make the spectacular renderings I've seen in most of the video-games I own.  &lt;br /&gt;&lt;br /&gt;I think that one of the most valuable skills a person can have is the ability to learn new things.  As I've said in &lt;a href="http://codeclimber.blogspot.com/2008/09/honing-your-craft.html"&gt;previous posts&lt;/a&gt;, being an expert in your craft is great, but I'm more interested in a person who's an expert at becoming an expert.  And what's the best way to get good at learning new things?  By learning new things, of course!  So that's why I work on side projects like this.  Not only are they enjoyable, but I think they make me better at my job because they give me practice in continuing the evolution of my skillset.&lt;br /&gt;&lt;br /&gt;Anyway, I've been having a great time going through tutorials and resources on 3D modeling, and I finally have my own first project that I did without any tutorial or guidance.  Yes, I'm proud of it; it's the kind of proud that a 7-year-old feels when he makes a clay pot for his parents not realizing how much more advanced the art of ceramics is beyond his skill level, but proud nonetheless.  So here it is, my primitive model of a Colonial Viper Mark II from the re-imagined Battlestar Galactica series:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_vTrR73WnSs8/STvXoolAy-I/AAAAAAAAAq8/KNW9m_vz8F4/s1600-h/Wireframe_Viper.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 163px;" src="http://4.bp.blogspot.com/_vTrR73WnSs8/STvXoolAy-I/AAAAAAAAAq8/KNW9m_vz8F4/s400/Wireframe_Viper.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5277048481482066914" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_vTrR73WnSs8/STvX1fYjiZI/AAAAAAAAArE/cteSGEESSiQ/s1600-h/Solid_Viper.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 162px;" src="http://2.bp.blogspot.com/_vTrR73WnSs8/STvX1fYjiZI/AAAAAAAAArE/cteSGEESSiQ/s400/Solid_Viper.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5277048702352198034" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Now, before all you real artists pounce on me for my amateurism, let's remember that no field would ever be advanced if not for the encouragement of amateurs.  &lt;br /&gt;&lt;br /&gt;Also, you've been doing it a lot longer than me, so back off, k?  ; )&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/294680397865315179-3908854867517873012?l=blog.ethanvizitei.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.ethanvizitei.com/feeds/3908854867517873012/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=294680397865315179&amp;postID=3908854867517873012' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/3908854867517873012'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/294680397865315179/posts/default/3908854867517873012'/><link rel='alternate' type='text/html' href='http://blog.ethanvizitei.com/2008/12/cross-training.html' title='Cross Training'/><author><name>Ethan Vizitei</name><uri>http://www.blogger.com/profile/16886242271824345907</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://bp3.blogger.com/_vTrR73WnSs8/R41ZdF_kUeI/AAAAAAAAAqU/7ZbY4unHkXs/S220/me_with_glasses.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_vTrR73WnSs8/STvXoolAy-I/AAAAAAAAAq8/KNW9m_vz8F4/s72-c/Wireframe_Viper.png' height='72' width='72'/><thr:total>1</thr:total></entry></feed>
