Farewell to "--include-dependencies"

By Phil Matarese on December 20th, 2007

Tagged with: rake, gems, rails

Of Course I Want Dependencies

With the latest version of RubyGems (1.0.0), you no longer have to explicitly state that you want dependencies included.  The new default mode is to assume that you'll want whatever dependencies are needed to make this new gem work.

gem install rails

This was a great design choice, as I can't think of a time when I've ever wanted to skip the dependencies.  Anyone else?

This Is Nice, Too

Are you tired of typing gem list | grep ^[A-z] to get a nice simple list of all your installed gems?  All anyone really wants to know is "which gems" and "what version".  Well, now you can just type gem list, and you're all set.  (If you really want all the gory details, you can add --gory-details.  Just kidding, you add --details.  No, I'm serious this time.  Really, try it.)

gem list

Another win for convention over configuration!  I don't think it gets any better than this.

(Update) There's More

The latest update to rake also adds some task-listing niceties. See, all the formatting is a lot nicer:

rake --tasks
sudo gem up rake
rake --tasks

Elegant (Non-Standard) Time Formatting

By Phil Matarese on December 3rd, 2007

Tagged with: ruby, elegance

I want to show you a neat snippet of Ruby code that I wrote for specialized time formatting.  I was outputting some audio information as XML for a major online music store, and I needed to encode durations in a non-standard format.  This unnecessarily, ambiguously named store likes their durations to look like PT00M00S, so a duration of 12345 seconds becomes PT205M45S.  Egad, how non-standard!  The code to do this might get a little kludgy – not complicated, just kludgy.  But secretly, I'm getting giddy because I know I'll get to write some custom code.  And after all, writing code is the reason we got into this programming stuff to begin with.

But, before I show you this code, you have to promise not to use it until you've read this entire post.  There are a few goodies in here that are useful for so much more than time formatting.  I wouldn't want you to just cut and paste this code without understanding the magic behind it – that would be bad for all of us.

  >> duration = 12345
>> format('PT%02dM%02dS', *duration.divmod(60))
=> "PT205M45S"

Ecce, Carbunculus!

Look, Ruby!

My favorite thing about this one line of code is the fact that it's just one line of code. I like some other things about it, too, so let's go take a look.  C'mon.

Kernel.format

The first thing to notice is format, which is really Kernel.format.  Basically, if you're not formatting your numbers with this you're a jerk.  What's the matter with you anyway?  FYI, this is an alias for sprintf which comes from C.  In our example, format is being used to guarantee that minutes and seconds will be padded with zeros.  The %02d means 'render an integer, taking up at least 2 spaces.'  An integer like 205 will be unaltered, but 0 becomes 00, and 5 becomes 05, and so forth.  If you're not familiar with this stuff, you should definitely check it out – jerk.

Numeric.divmod

As we continue on our tour, if you look to the right, you'll see a function called divmod.  This function is great for two reasons: firstly its specifically useful without cluttering the language and secondly because it returns 2 values.  Returns 2 values?  How does that work?  It's pretty simple.  See.  (Or try it yourself with irb.)

  >> quot, rem = 22.divmod(7) # return as 2 variables
>> quot
=> 3
>> rem
=> 1

>> q_r = 22.divmod(7) # return as an array to 1 variable
=> [3, 1]

Check out Pickaxe's Parallel Assignment section for more on this.  But, this still leaves us with catching the results in 2 variables or 1 array.  And, if we try to pass the result of divmod into format, we get a conversion error.

Splat

Here we are with our single array, yet format wants an argument list of 2 arguments (because our pattern has 2 wildcards).  Uh-oh.  I'm starting to feel like my quest for elegance will fall short.  If only there was a way to 'unpack' the elements of an array and use them as an argument list...  Well, this is Ruby, where all of your wildest dreams come true!  Of course there's an 'unpack' operator, and better yet, it's called 'splat'.  OMG!  HOLY CRAP!  WOW!  (Don't act so surprised, I already showed you the code, you knew this was coming.  But, OMG, nonetheless.)  Simply apply the * operator to any array, and splat it's a list of arguments.  What could be easier – it's just like magic.  So, that explains that funky asterisk in the middle of the code, and brings our tour to an end.

Let Me Sum Up

Now, for your viewing pleasure, feast your eyes on these examples and behold the happiness of pretty code.  I had to change duration to d so the ugly one would fit.

  # ugly code, bad (no format, divmod, or splat)
"PT
#{'0' if (d/60).floor<10}#{(d/60).floor}M#{'0' if d%60<10}#{d%60}S"

# pretty code, happy happy happy
format('PT%02dM%02dS', *d.divmod(60))

Fine Ruby Porto

Mmm, Ruby

In case you didn't notice, time formatting is just a red herring, a clever ploy to hook you with practical applicability so I could assault you with some Ruby elegance.  But now you've seen, now you know, now you're beginning to understand.  You've seem a glimpse of its beauty, so sit back, take a sip of Ruby, and write some elegant one-liners.

Automated development environment startup

By John Berry on November 19th, 2007

Tagged with: applescript, dev environment

Here's a little AppleScript I use to get my development environment up and running. It launches iTerm, gets the server running, creates a new tab in my project root, launches a TextMate project and starts CocoaMySQL.

The AppleScript:

 1  tell application "iTerm"
2 set PROJECT_DIR to "cd /YOUR/RAILS/PROJECT/DIR >"
3
4 tell the last terminal
5
6 activate current session
7
8 tell the last session
9 set name to "Server"
10 write text PROJECT_DIR
11 write text "clear"
12 write text "script/server"
13 end tell
14
15 launch session "Default Session"
16 tell the last session
17 set name to "Project"
18 write text PROJECT_DIR
19 write text "clear"
20 end tell
21
22 end tell
23 end tell
24
25 tell application "Finder"
26 activate
27 open document file "MyProject.tmproj" of folder "Desktop"
of folder "myusername" of folder "Users" of startup disk
28
29 end tell
30
31 tell application "Finder"
32 activate
33 open application file "CocoaMySQL.app" of folder
"Applications" of startup disk
34 end tell

You can create your own AppleScripts with the OS X Script Editor application. (in Applications -> AppleScript)

 

Rescue Me

By Phil Matarese on November 12th, 2007

Tagged with: ruby, error handling

I'm about to write a cool new program that leverages the power of somebody else's module.  But, I have no idea exactly what kind of errors this module I'm using might generate.  I know it connects to web servers around the world looking up important information about dinosaurs and mountain ranges and movie stars and some other things that I need to know about.  I know that with all that looking-up, a lot can go wrong, so lemme just catch everything...

1  # My erroneous first draft
2 things_to_look_up.each do |thing|
3 begin
4 thing.look_on_internet!
5 rescue
6 # log it so the sysadmin can check on it tomorrow
7 thing.log $!, $@
8 end
9 end

Looks good.  All of the errors will be neatly trapped so the rest of the things can be looked up.  If anything goes wrong, the rest of the app will keep running, and I can come back to the error tomorrow.  This will make my program nice and robust, and everybody will want to be my friend.  Life is good.

... 

Uh-oh, what's this?

/1.8/timeout.rb:54:in `rbuf_fill': execution expired (Timeout::Error)
       from /1.8/timeout.rb:56:in `timeout'
       from /1.8/timeout.rb:76:in `timeout'
       from /1.8/net/protocol.rb:132:in `rbuf_fill'
       from /1.8/net/protocol.rb:86:in `read'

Something's gone wrong!  It wasn't supposed to be like this.  I wrote robust code!  I caught all the errors!  I did the right thing!  I made friends.  Life was good.  Now I feel shame, but I don't know why.

There has to be an explanation for this.  Ruby is wonderful.  Ruby would never let me down.  Digging on the Internet, I see that someone's had a similar problem and they've fixed it by adding code directly to protocol.rb.  My stomach hurts.  This feels wrong.  I need some pepto.  I don't feel any better as I read the top of protocol.rb and see:

# WARNING: This file is going to remove.
# Do not rely on the implementation written in this file.

Yikes!  As the last shreds of hope are leaving me, I tried fixing protocol.rb as described on the Internet.  Low and behold, it works!  Strange.  The fix was to specify that timeout throw a ProtocolError (which inherits from StandardError) rather than the default Timeout::Error.  Why did that make the error trappable?  Looking further it seems that Timeout::Error inherits from Interrupt, which is a different kind of exception than StandardError.  A key difference has revealed itself, but I'm still confused.  Back to the Internet...

Ah-ha!  The PickAxe tells all:

How does Ruby decide which rescue clause to execute? It turns out that the processing is pretty similar to that used by the case statement. For each rescue clause in the begin block, Ruby compares the raised exception against each of the parameters in turn. If the raised exception matches a parameter, Ruby executes the body of the rescue and stops looking. The match is made using $!.kind_of?(parameter), and so will succeed if the parameter has the same class as the exception or is an ancestor of the exception. If you write a rescue clause with no parameter list, the parameter defaults to StandardError.

Interrupt (and therefore Timeout::Error) doesn't inherit from StandardError, so it won't be matched by the default rescue.  Whew!  That wasn't so bad.  I can put my Ruby files back to the way they started, and implement a fix in my code.

1  # My beautiful and robust code
2 things_to_look_up.each do |thing|
3 begin
4 thing.look_on_internet!
5 rescue StandardError, Interrupt
6 # log it so the sysadmin can check on it tomorrow
7 thing.log $!, $@
8 end
9 end

Before I add this fix back into my project, I need to add a spec that demonstrates the error.  No problem with Rspec, just write a quick stub that raises a Timeout::Error.

1  # My erroneous spec
2 it "should handle errors gracefully" do
3 Thing.stub!(:look_on_internet!).and_raise(Timeout::Error)
4 lambda{ Thing.look_up! }.should_not raise_error
5 end

Bonk!  Wrong number of arguments?  Grr...  Timeout::Error requires that an argument be passed to the constructor, and to do that in rspec I need to pass an instance of the exception class.  Ugh...  Almost there...

1  # My perfectly correct spec
2 it "should handle errors gracefully" do
3 Thing.stub!(:look_on_internet!).and_raise(Timeout::Error.new(''))
4 lambda{ Thing.look_up! }.should_not raise_error
5 end

Done.  Success!  All tests pass.  Code is robust.  Happiness returns.  Life is good, again.  Time for a celebratory hot chocolate!

Beginner's Guide to Learning Ruby on Rails

By John Berry on November 8th, 2007

I've gone through the process of helping rails-curious developers get up to speed often enough that it's time to capture some recommendations. This is a high-level list of activities to get you up to speed on Rails.

Buy the Pickaxe Book

The Pickaxe BookSeveral of the folks I've helped out were pretty seasoned programmers from the Java world. One consistent theme is that there's often difficulty in separating what's pure Ruby vs. what Rails provides. 

Unless you already are comfortable with Ruby (otherwise, you're probably not reading this...), I strongly suggest you start with Programming Ruby - The Pragmatic Programmers' Guide (AKA The Pickaxe Book). This is everything you need to learn your way around Ruby. You only need to spend a few hours getting the basics down before you'll be able to understand / write Ruby code, and you'll rely on it later for reference.

Buy the Agile Web Development with Rails book

Uggh. Another book? Yep, but you'll appreciate it later. There are some really good tutorials out there, but nothing gives you as comprehensive a view of the Rails framework as this book. Just like Pickaxe gave you the fundamentals of Ruby, Agile Web Development with Rails will get you smart quickly.  

Slog through "Depot"

Depot is the famous (infamous?) hands-on tutorial in Agile Web Development with Rails. You'll be guided through your first Rails app; again, giving you the basics. I know you want to get started on your Digg/Cute Overload/Conservapedia mashup, but you'll probably get lost quickly without having done the Depot dry run. 

Hang out on IRC 

A lot of help in the Rails community is provided via IRC. #rubyonrails on freenode.net is where you can ask just about any Rails-related question. The range of topics is vast, so even if you don't have anything to ask, it's worth lurking to see what difficulties others are having. You'll learn a ton of tidbits just by checking the channel occasionally. 

For OS X, Colloquy is a great IRC client. (for Windows... I'm not so sure, maybe mIRC?).

If you're interested in what things are being talked about at the Rails-core level, check out the #rails-contrib channel. 

Use the source

Once you've reached a resonable degree of comfort with Ruby and Rails, referencing the actual Rails source code will be invaluable. Personally, I've created a TextMate project just containing the Rails source, which I can use to quickly search and browse. You're only a few clicks away from getting to the bottom of any confusion around why Rails is behaving the way it is. 

Know your resources

There's a wealth of Rails-related information out there. Here are a few essentials:

Contribute 

By now you're probably hopelessly hooked on Rails. The community welcomes and encourages contributions. In fact, it can't survive without developers testing, documenting and contributing. If you're interested in joining in, you can start with the Rails Trac homepage, which has a good overview on how to get started. Also, Josh Susser gave a good presentation on how to contribute at RailsConf '07.