General


Ruby is a great language. Really. How many other languages let you extend other classes? Modify their behavior? Overload operators? With great power comes great opportunity, and in this post I’ll cover one of my recent extensions to Ruby that was recently accepted into Rails’ core.

Date and Time

Managing dates and times is not exactly fun in any language. There are time zones, leap years, two meridians, twenty-four hours, 365 days (sometimes), and a mess of inconsistently long months. Those of us who use Ruby frequently are familiar with how to get objects corresponding to the current time and the current day:

  1. >> Time.now
  2. => Thu Jan 25 20:12:50 -0800 2007
  3. >> Date.today
  4. => #<Date: 4908251/2,0,2299161T>

Sexy methods…

Rails has these really cool extensions to Numeric that allow computing lengths of time in a more human-readable way. For example, if I wanted to figure out how long 12 hours is, I could just call 12.hours and get back the number of seconds in 12 hours, which could then be conveniently added to an instance of Time, like so:

  1. >> t = Time.now
  2. => Thu Jan 25 19:31:17 -0800 2007
  3. >> t + 12.hours
  4. => Fri Jan 26 07:31:17 -0800 2007

Sweet! What’s even cooler though is that, in addition to Numeric with seconds, minutes, hours, days, weeks, fortnights, months, and years, we also get from_now, ago, since, and until:

  1. >> 1.day.from_now
  2. => Fri Jan 26 19:34:20 -0800 2007
  3. >> 2.weeks.ago
  4. => Thu Jan 11 19:34:35 -0800 2007

but kinda dumb.

Nice! These are so easy to read. These were great for simple things, but since they returned numbers, they couldn’t take into account the current date and how many days were in a month, leap year, etc. Notice that this causes problems when the month doesn’t have 30 days in it:

  1. >> Time.now
  2. => Thu Jan 25 21:01:31 -0800 2007
  3. >> 1.month.from_now
  4. => Sun Feb 24 21:01:34 -0800 2007

So it’s good for approximations, but not much else. Well what now?

A smart method

To compensate for the lack of accuracy in the above sexy methods, Time#advance was added to make it easy to do smart addition to Time instances. Here’s some of the above using advance:

  1. >> t = Time.now
  2. => Thu Jan 25 21:11:32 -0800 2007
  3. >> t.advance(:hours => 12)
  4. => Thu Jan 25 21:11:32 -0800 2007
  5. >> t.advance(:months => 1)
  6. => Sun Feb 25 21:11:32 -0800 2007

Excellent! Not quite as sexy, but definitely smart. A method suitable for Serious Business.

Smart n’ Sexy

I welcomed Time#advance, like everyone else, but I didn’t want to give up the sexy methods. Fortunately, Ruby is great for letting objects masquerade as other objects (the whole duck typing thing), and the de-facto way to do this in Rails is by using Builder::BlankSlate, whose instance undefine almost all of their methods, allowing you to easily proxy all or some methods to something else. The two best examples of its use in Rails are AssociationProxy, which is used by Active Record, and JavaScriptGenerator, which is used by RJS.

To solve the problem, all we need to do is make the duration methods on Numeric return a proxy for the number they used to return which acts accordingly around Time and Date objects. This new class is located in ActiveSupport::Duration, and simply accumulates lengths of time for when it is used around a Time or Date, and a number for use around things that expect it to act like a number (i.e. for backward compatibility). Here’s the new, smart and sexy, methods on Numeric:

  1. >> t = Time.now
  2. => Thu Jan 25 21:21:44 -0800 2007
  3. >> t + 1.month
  4. => Sun Feb 25 21:21:44 -0800 2007
  5. >> t + 1.week
  6. => Thu Feb 01 21:21:44 -0800 2007
  7. >> 1.year.from_now
  8. => Fri Jan 25 21:22:00 -0800 2008
  9. >> 4.years.from_now
  10. => Tue Jan 25 21:22:12 -0800 2011
  11. >> 3.weeks
  12. => 21 days

How’s that last one for overriding inspect? To try it out all you have to do is freeze edge in a Rails project near you!

$ rake rails:freeze:edge

Update: I should add that this recent change also corrected handling around Date objects. I implied that above, but I should be explicit. Before Duration, adding Date.today and 1.day didn’t yield the expected results at all:

  1.  
  2. >> (Date.today + 1.day).to_time
  3. ArgumentError: time out of range
  4.  

Now it behaves as expected:

  1.  
  2. >> (Date.today + 1.day).to_time
  3. => Sat Jan 27 00:00:00 -0800 2007
  4.  

So it’s real. And it looks like a wet dream of a phone. Let’s see, how does it compare to my previous list of must-haves and would-like-to-haves?

Bluetooth + WiFi

Yep, it’s got this. And 2.0 EDR at that. To top it off, Apple will also offer a Bluetooth headset that, at first blush, is quite small and sexy. This is a must-have that is quickly becoming standard on most cell phones. It has WiFi too! Gah!

Sync Support

Well, duh. Apple would be insane not to do this, and though Apple is many things, it is not insane. I expect full support for Address Book, iCal, iTunes, and iPhoto.

Google Maps

Yep, and it seems to even have GPS support! Maybe Apple got Cingular to pull its head out of its ass and support this feature. From what I understand GSM networks have technical issues with this, but it is technically possible to do it on GSM. Google Maps on the iPhone looks even sexier than the one on Google’s website.

Email

POP (Gmail, etc) and IMAP (corporate, .edu) support. Will we see push email like Blackberry? I bet we will.

Web Browsing

As a web developer I would like to have frequent access to the internet, and the iPhone will do this extremely well. It has Safari with zooming via two-finger pinching.

The Rest

It’s also a video iPod, so you can play movies, tv shows, music, etc. It looks truly amazing. So it sounds awesome, but guess how much it costs! It’s only $499 for 4GB! Wow, that’s… uh.. expensive. Damn it!

All the cool kids are doing it:

Your results:
You are Dr. Doom

Dr. Doom
60%
Apocalypse
53%
Lex Luthor
50%
Juggernaut
49%
Green Goblin
49%
The Joker
47%
Mr. Freeze
47%
Magneto
42%
Dark Phoenix
40%
Riddler
38%
Venom
37%
Catwoman
37%
Two-Face
33%
Kingpin
33%
Poison Ivy
30%
Mystique
29%
Blessed with smarts and power but burdened by vanity.
Click here to take the “Which Super Villain am I?” quiz…

To be precise, her character in Lost in Translation is hot. She’s attractive, she’s witty, she’s a mellow offset to the crazies her husband hangs out with, she graduated from Yale, and she wears conservative clothing that still compliments her body quite well. On top of that I know the feeling of being a bit lost in unfamiliar territory. My ideal woman.

In tribute to the gods of San Francisco weather and to the gods of high fashion, I’ve bought myself an early Christmas present in the form of a leather jacket (it’s black - Mom, you’d be so proud).

Today I replaced “.NET” with “JavaScript” in my byline above. After all, I haven’t done .NET programming in well over two years and I do JavaScript on a daily basis. And with that simple flip of some bits, balance has been restored to the force, and the peasants rejoiced.

Well, of course I knew this, but it didn’t really hit home until today when the latch that keeps my laptop closed broke off. So now I can’t actually close my computer and keep it shut, making sleep difficult. I need to find my sleeve for it.

Is my PowerBook destined to be a desk queen? Eventually, but not right now. I still like having it at work, and I’ll probably continue using it there so long as I don’t have two screens.

Technorati Tags: ,

The article makes some good points, and is at least an easy read.

Technorati Tags: ,

What a concept. Hahaha!

Technorati Tags: ,

I just realized the benefit of something I saw a while ago in a Rails ticket. It is mostly about acts_as_vertical, which is cool and has its uses, but what I’m talking about now is the dynamic StylesheetsController. The way it works in the ticket is that it renders stylesheet based on database values and caches the resulting CSS file.

For CSS this is sort of a novelty until you really become a nazi about the one-place-for-any-given-piece-of-information rule as espoused by the Pragmatic Programmers. When reading about the holy grail of 3-column layout at A List Apart, it struck me that the CSS he’d written, while quite simple, encoded the same pieces of information multiple times - namely widths and padding.

My solution was to reinvent the StylesheetController, which I only later realized I’d seen before. My version looks like this:

class StylesheetsController < ApplicationController
  caches_page :content

  def content
    headers['Content-Type'] = 'text/css'
    render :action => params[:name]
  end
end

It requires a route that looks like this:

map.connect 'stylesheets/:name', :controller => 'stylesheets', :action => 'content'

Now move your style.css from /public/stylesheets to app/views/stylesheets and rename it style.css.rhtml. Not the prettiest thing ever, but without a new template extension that’s what we have.

Perhaps more important is the realization that this works just as well for javascript files. Now I can use all the Rails helper goodness to construct my static javascript files! How awesome is that? Why hello visual_effect. Hello remote_function.

In case you’re interested, here’s my style.css.rhtml from the article:

< %
right  = {:width => 130, :padding => {:horizontal => 10}}
left   = {:width => 180, :padding => {:horizontal => 10}}
center = {:padding => {:vertical => 10, :horizontal => 20}}
[right, left, center].each do |column|
  column[:full_width] = column[:width] + 2 * (column[:padding][:horizontal] || 0) rescue 0
  column[:padding] = {:horizontal => 0, :vertical => 0}.merge(column[:padding])
end
-%>
body {
  min-width: < %= 2 * (left[:full_width] + 2 * center[:padding][:horizontal]) + right[:full_width] %>px;
}
#container {
  padding-left: < %= left[:full_width] %>px;
  padding-right: < %= right[:full_width] + 2 * center[:padding][:horizontal] %>px;
}
#container .column {
  position: relative;
  float: left;
}
#center {
  padding: < %= center[:padding][:vertical] %>px < %= center[:padding][:horizontal] %>px;
  width: 100%;
}
#left {
  width: < %= left[:width] %>px;
  padding: < %= left[:padding][:vertical] %>px < %= left[:padding][:horizontal] %>px;
  right: < %= left[:full_width] + 2 * center[:padding][:horizontal] %>px;
  margin-left: -100%;
}
#right {
  width: < %= right[:width] %>px;
  padding: < %= right[:padding][:vertical] %>px < %= right[:padding][:horizontal] %>px;
  margin-right: -< %= right[:full_width] + 2 * center[:padding][:horizontal] %>px;
}
#footer {
  clear: both;
}

/*** IE Fix ***/
* html #left {
  left: < %= right[:full_width] %>px;
}

« Previous PageNext Page »