I'll be speaking at FRUUG tonight about WalkingBoss and Ruby on Rails. The meeting info is posted here if you're interested. The first half will be about my experience creating the site. In the second half I'll give an overview of what Ruby on Rails is and how it facilitates creating nice web applications in a short amount of time. If you're already using Rails at your job or at school, the second half may not be that interesting. But if you're new to it, or you're wondering what it's all about, then this talk will probably have some useful information for you. Hope to see you there!

Incidentally, I found that somehow my talk was also posted as a colloqium on the CU computer science page. I had to look that word up in the dictionary to get an idea of what I was supposed to be doing!

UPDATE: I want to thank Steve Gaede and everyone at FRUUG last night for being such great hosts. There was a very good showing, despite the snowy weather, and I was really impressed with the level of interest and discussion at this group--people seemed more interested in Rails and Ruby at FRUUG than they have at some of the Ruby groups I've spoken at!

I also wanted to point out some local groups to FRUUGers who may be interested in learning more about Ruby or Rails. For those of you in Boulder, there's the Boulder-Denver Ruby Group, which meets on Pearl Street. For those of you closer to Denver, there's the DeRailed Ruby/Rails User Group which meets at the Tattered Cover in LoDo.

I've posted my slides from the talk as a PDF if you're interested. Thanks again FRUUG!

WalkingBoss got a shout-out from Google Maps Mania a few days ago. If you've never been to Google Maps Mania, it's a great blog that attempts to keep track of the many interesting mashups people are making based on Google Maps.

Taiwan !?!

11.28.06


Wow, I never thought I would see this: WalkingBoss in Asia!

I'm not sure who the poster is or how he found out about the site, but it sure is cool to see this being used halfway across the world. And, in the process, I find out that the site has no problem with Taiwanese characters. I guess I can check off that test case...

Salida Trip

11.21.06


We went up to Salida last weekend.
DSC_0122.JPG

I had never really hung out around town before. My only previous trip was compressed into one afternoon, most of which I spent fishing a few miles outside of town on the Arkansas. This time, Elly, Tova, and I stayed at the Thomas House, which was within walking distance of basically everything (except Sonic).

On Friday night, we went to Amica's Pizza, which was extremely good. I ate way too much but I do not regret it. If you go to Salida, definitely eat here at least once. We also went to this store called Slim Pickens, which sold insanely high quality Pendleton flannels for an insanely low price ($13.99 for any shirt). These shirts sell for around $90 online. So, yes, this store was a gold mine if you like wool shirts! I bought two and Elly bought a cape (wonderwoman style).

On Saturday, we walked around town, played on the playground, interviewed strangers about life in Salida (in case we ever decide to move up there), watched skaters at the skate park, and ate dinner at Bongo Billy's. We also took a stroll by the river so Tova could check out the fix and throw rocks into the water.

On Sunday, we stopped by Buena Vista on the way home. Since the yarn store was closed and the coffee shop had a power outage, we only stayed for a few minutes. We got home Sunday in time to grill some burgers and watch the few remaining Robot Chickens and NYPD Blues on our Tivo. A fun weekend!

I'll be talking about Rails Plugins this evening at Boulder-Denver Ruby Group. I'm planning to talk about the plugin architecture in general, common use cases for plugins, and how you can make your own plugins for public release or just for personal convenience. Hopefully I'll see you there!
For the 30x speedup it offers!

I've been playing around with MySQL Spatial Extensions this morning, attempting to optimize some zip code search features. I was surprised to find the difference between using MySQL spatial indexes and using regular arithmetic greater than/less than comparisons in a query.

With spatial indexes enabled on a table of approximately 80,000 zip codes:

SELECT * FROM zip_codes WHERE 
   MBRContains(GeomFromText('Polygon(
    (38.1922003089928 -122.206571969555,
     38.1922003089928 -121.283430030445,
     38.9170856910072 -122.206571969555,
     38.9170856910072 -121.283430030445,
     38.1922003089928 -122.206571969555))'), pt);
 ...
198 rows in set (0.01 sec)
Without spatial indexes on the same table:
SELECT * FROM zip_codes WHERE 
    ((latitude > 38.1922003089928 AND 
      longitude > -122.206571969555 AND 
      latitude < 38.9170856910072 AND 
      longitude < -121.283430030445 ));
...
198 rows in set (0.33 sec)
Although my ZipCodeSearch Plugin uses the slower of these two methods in order to be database-agnostic, you can easily convert it to use MySQL's spatial indexing. Here's how:
  1. First, open a new migration. [The code in the following three steps should be placed within this migration.]
  2. Then, make sure your engine is of a type that supports the spatial extensions:
    execute("ALTER TABLE zip_codes ENGINE=MyISAM;")
    
  3. Add the column that will hold your lat/lon POINTs and populate it:
    execute("ALTER TABLE zip_codes ADD pt POINT;")
    ZipCode.find_all.each do |z|
        lat = z.latitude
        lon = z.longitude
        execute("UPDATE zip_codes SET zip_codes.pt = 
          (GeomFromText('POINT(#{lat} #{lon})')) WHERE zip_codes.id = #{z.id};");
    end
    
  4. Add the index. Note that the column has to be non-null in order for the index to be created:
    execute("ALTER TABLE zip_codes MODIFY pt Point NOT NULL;")
    execute("ALTER TABLE zip_codes ADD SPATIAL INDEX(pt);")
    
  5. Finally, modify the code in that calls "find_objects_within_radius" to pass in a finder_block that uses the spatial extension functions:
    @zip_code.find_objects_within_radius(radius.to_i) do |min_lat, min_lon, max_lat, max_lon|
       p1 = "#{max_lat} #{max_lon}"
       p2 = "#{max_lat} #{min_lon}"
       p3 = "#{min_lat} #{min_lon}"
       p4 = "#{min_lat} #{max_lon}"
       sql = %{SELECT * FROM zip_codes WHERE \
    			MBRContains( \
    			GeomFromText('Polygon((#{p1},#{p2},\
    			#{p3},#{p4},#{p1}))'),pt);}
       zips_to_search_for = ZipCode.find_by_sql(sql)
    end
    

Some things to note about the query in the last step:

  • The Polygon must be closed (see how p1 is the first and last point in the Polygon definition?).
  • The Polygon is comprised a set of LineStrings. That is why there are two parentheses wrapping the set of points in our Polygon definition. If you wrapped those points with just one set of parentheses, this query would fail.

There you have it. Now go enjoy your spatial indexes!

These days, the free web APIs are multiplying like rabbits. Every hot new 2.0 application is providing a feature-rich API almost as soon as it launches. Most APIs are based around some form of XML exchange between client and server, whether that be with SOAP, XMLRPC, RSS, Atom, or some proprietary XML "protocol." It seems like there are more XML-over-HTTP flavors than there are in the Ben and Jerry's Flavor Graveyard. Anyway, in all of my recent mash-up fun, one thing that has remained constant is the speed and flexibility that ruby brings to the coding for these APIs.

Up until now, there hasn't been any one feature of ruby that stuck out as the reason it lends itself so readily to writing XML APIs. But this weekend, as I was writing a ruby Blogger module, I found the DelegateClass to be especially useful. Since the new API for Blogger is Google's GData, based on RSS and Atom, I've been able to leverage FeedTools extensively. In fact, my "library" will end up being no more than a few lines to wrap FeedTools' parsing and generation, thanks to ruby's delegate lib. For example:

   class Entry <   DelegateClass( FeedItem )
      attr_accessor  :edit_link, :self_link

      def initialize(feed_item)
         super(feed_item)
         @edit_link = self.links.find { |l| l.rel == 'edit' }.href
         @self_link = self.links.find { |l| l.rel == 'self' }.href
      end
This example declares a class Entry which represents a blog entry. Since an entry in the GData API is just an Atom entry element, I'm able to delegate 99% of the functionality to the FeedItem class from FeedTools. Thanks to the power of delegation, I only have to define a few extra methods here and there to "Google-ize" the FeedTools objects.

Pretty cool!

Happy Lobster!

11.01.06


Tova had fun at the Munchkin Masquerade yesterday: