inherited, ObjectSpace, and Test::Unit

As a part of working on a submission for the homework assignment for Ruby magazine The Rubyist, I wrote a game server so my co-workers and I could test our bots against each other.

Part of what’s needed for a good server is the ability to automatically load classes and instantiate them. If you’re familiar with ObjectSpace and you have a way to identify the bot classes, it’s not too hard. The only thing that identifies a bot class is a move! instance method, but that should be enough:

player_classes = []
ObjectSpace.each_object(Class) do |klass|
  player_classes << klass if klass.instance_methods.include?('move!')
end
player_classes

It’s a little nicer in Ruby 1.9, because if you don’t pass a block to ObjectSpace.each_object, you get an Enumerable::Enumerator, so you can do something like this:

ObjectSpace.each_object(Class).select do |klass|
  klass.instance_methods.include?(:move!)
end

(Ruby 1.9 uses symbols for method names here instead of strings.)

Of course, if you knew that all the player classes would be subclasses of, say, TreasuredRuby::Bot, you could forgo using ObjectSpace altogether and use Class#inherited instead:

class TreasuredRuby::Bot
  PLAYER_CLASSES = []
  def inherited(klass)
    PLAYER_CLASSES << klass
  end
end

Where else are descendant classes treated specially? Test::Unit, specifically Test::Unit::TestCase subclasses. However, Test::Unit uses ObjectSpace to collect its descendant classes. There have been discussions about the problems it creates for JRuby, although JRuby handles ObjectSpace.each_object(Class) because of Test::Unit.

There was recently a patch suggested on the Ruby Core mailing list to replace using ObjectSpace with Class#inherited (and perhaps not the first), but it’s still unapplied. There are arguments against using Class#inherited: if you want to use Class#inherited in your TestCase classes, for example, you had better call super.

We’ll see if Test::Unit changes.

Comments

Adventures in downloading Stuffit Expander

I accidentally found myself in posession of a .sit file and needed to download Stuffit Expander. So, like any normal person, I do a quick search on the old world wide web and get myself to the Smith Micro Stuffit landing page. I think it’s being served on a shared host with someone using RMagick and seeding Tropic Thunder torrents, because it takes forever to load.

A super quick scan of the page and I don’t see a “Download Stuffit Expander” link before I just click on “Which Stuffit is right for me?” I should have looked closer, because this page also takes another forever. But it loads, and I see “Stuffit Expander”, “Free”, “Macintosh” and click that… and eons pass… and I’m taken to a page about Stuffit Deluxe which is only $79.99.

Affirming my resolve, I click on “Stuffit Expander”, and cross my fingers. Sometime after the sun has swelled to engulf the Earth I find myself looking at the Stuffit Expander page. I finally get to click on something that says “Download” and “Free”… but wait…

After yet another slow page load, I’m asked if I’m sure I don’t want to really get Stuffit Standard (normal retail price of $49.99) for free… if only I complete “Just One Offer” from some other company (like Blockbuster or Real Networks, ugh). But no, seriously, I just want to uncompress this one file, so I fill out my name and email address and click “Submit”.

Then I’m told I’ve been sent an email. Great.

The email tells me that by clicking on the download link I’m agreeing to get spam from Smith Micro. Awesome. Whatever. That’s what email filtering’s for. I click the link. I’m taken to a page which says will redirect me shortly, but with the way their server behaves, I know I have a couple of minutes.

But the redirect comes, and I click on what I hope is the last link I need to… and the download begins.

Agreeing to the license twice, I unarchive the file, and promptly delete Stuffit Epander and the .dmg it came in.

Thanks Smith Micro. It only took 7 interactions with their website, and two license agreements before I got to uncompress an archive with a free utility that used to be a shining light in the Mac world. Not anymore. Next time someone sends me a .sit file, I’ll just ask them to recompress with something else.

Comments (1)

DevFi Podcast #3 – Promoting your project, encouraging development

Peter Jones and I finally got around to recording another DevFi podcast, this time on getting people informed and involved in your software projects. If you haven’t listened to our show yet, I’d suggest it if only for the theme song.

Go head and grab episode 3.

Comments

Buy the book!

Even though it might not always be obvious, I’ve been using Ruby since 2002. I got lucky and stumbled upon it early (really early for someone who wasn’t in the software development business, insanely early for someone who was just a graduate student at an art school). When I see comments (like the first comment of this post) that say “I won’t buy a book to learn this”, I cringe. I wasn’t any good with Ruby for three years because I didn’t buy the book. I want to tell Josh, “Buy the book!”

I’ve been poor. I had practically no money throughout graduate school. My rent was $225/month in Chicago, which meant I was in a bad apartment in a bad neighborhood. I got mugged half a block from my building. The mugger got all of 75 cents from me. Not a lot of money, right? But because he got that 75 cents, I didn’t have bus fare the next day. In those days I ate mayonaise sandwiches. My (old) computer’s power supply’s fan wouldn’t start without me spinning it with my finger first. They were desperate times. And you know what? If I could go back in time and say one thing (career-related), I’d say “Buy the book!” It would have been an investment that paid off earlier, rather than later. I would have been able to afford balogna sooner.

I also would have been happier! It’s incredible how much happier you can be when you know what you’re doing. Once you take the mystery out of why things you do don’t work, you fix them faster, you get results faster, you say to yourself “Hey, I could do this for a living” faster. I still buy books. Because every time I read a book, I know more about what I’m doing, and it makes me like what I’m doing more. (It incidentally helps me make more money, if you’re in to that kind of thing.)

I recieved an MFA in Writing from the School of the Art Institute of Chicago (not to be confused with the Art Institutes chain schools). After I left, some people got their novels picked up, some people (like my wife) went on to PhD programs, and most people started teaching literature and writing. I got yet another job doing tech support and tried for something better (and part of that was buying the book). The English professors at the University of Denver know, because of my wife, that I got a graduate degree in writing, and occasionally they’ll ask if I’m “getting any writing done”. And I’m not. And it doesn’t bother me. It probably wouldn’t be any good. I’ve stopped doing the things that real writers do, like read new writing. I used to read fiction all the time. It helped me when I was writing fiction. Now I read programming books for the same reason. It helps me program better. And all I have to do is buy the book.

This isn’t even really about programming. It’s about what you’re interested in. I read a lot about programming (and the languages I use). I have a cousin who reads a lot of scripts (she writes for a television show). I have a sister who reads a lot of cookbooks (she manages a restaurant). My wife reads poetry all the time. Everyone I know that really wants to be great (or even merely good) at what they do reads about it. They buy the books.

When I see someone write “[I'm] too cheap to buy a book” all I see is “I don’t care”, and that’s a really uninteresting followup comment to a blog post.

Comments

Sinatra, Passenger, HTTParty: Small, Fast, Now

Last week at work I gave a little lunchtime presentation on Sinatra (the coolest microwebframework on the block) to some co-workers. It went over well.

Over the last couple of weekends, I played around with Passenger (the artist formerly known as mod_rails) and set up a whole bunch of little sites. It went over well.

Then today I read about HTTParty (like a DSL for creating REST clients) and that went over well, too.

We can all be making tiny, simple REST applications (with Sinatra), putting them on the internet with, like, no configuration and in no time (with Passenger), and then consuming them after doing next to no work (with HTTParty).

The barriers to putting up and pulling down easy-to-use resources is practically non-existent. I’d like to find out what happens when we start taking advantage of that. Maybe it won’t change the world, but I bet it’ll be like the day you figured out how to effectively use pipes on the command line: totally awesome for you.

Comments (1)

Deploying Sinatra on DreamHost - Internal Server Error with views (RESOLVED!)

I’ve been playing around with Sinatra (it’s awesome!) and thought I’d deploy something to my mess-around DreamHost account. I followed these instructions which totally worked and was so easy. But when it came time to actually deploy my real app with real views, I got nothing but “Internal Server Error”.

Internal Server Error is not very informative, but if you’re seeing it when you’re calling views (like erb(:page)), you might need to change that to:

erb(File.read('views/page.erb'))

That stopped the errors and let my app work. Locally, there was no problem, and a cursory glance through Sinatra didn’t tell me why it worked here and not there. I guess I’m programming by accident this morning.

Update: I was able to get erb(:page) working by fixing the view path in my config.ru. In the options merged into Sinatra::Application.default_options, I added :views => '/path/to/app/views'. Before it was getting set to something like ‘path/to/app/Rack: /path/to/app/views’. tusho in #sinatra suggested that “Passenger is odd.”

Comments (2)

Ruby vulnerabilities? Segfaults with Rails? “Try JRuby!”

I love this message Charles Oliver Nutter posted to the Ruby Mailing List. It’s so pragmatic and just a little snarky.

(Edit: Link was pointing to the wrong message. Whoops.)

Comments

DevFi podcast #1!

Peter Jones (good friend, awesome developer) and I started a podcast, DevFi, and we released our first episode just moments ago. We do a little high level talk about buying vs. building vs. forking, and then jump into an interview with Dan Berger, the guy who forked the Ruby interpreter to create the Sapphire programming language.

The podcast isn’t Ruby-centric (like this blog), but it should still be worthwhile. Hope you enjoy it. (We’re also taking comments and suggestions if you don’t enjoy it enough.)

Comments

Will Rubinius have fewer dark corners?

Section 3.4.2 of The Ruby Programming Language talks about defining (or overriding) the hash and eql? methods of a class in order to control how objects are treated as keys in a hash… except for strings. Strings are handled specially because they’re “mutable but commonly used as hash keys.”

So when someone asked today on comp.lang.ruby about case-insensitive hash access, the answer, sadly, could not be as simple (and possibly dangerous) as “Just override hash and eql? in String.” Or could it?

Well, I whipped up something quickly:

class String
  alias old_hash hash
  def hash
    self.downcase.old_hash
  end

  def eql?(other)
    self.hash == other.hash
  end
end

require 'test/unit'

class TestCaseInsensitiveHashAccess < Test::Unit::TestCase
  def test_case_insensitivity
    h = {'a' => 1, 'B' => 'buzz'}
    assert_equal(1, h['A'])
    assert_equal('buzz', h['b'])
  end
end

I ran it a few times. Ruby failed. Ruby 1.9 failed. JRuby failed. But Rubinius passed.

And why shouldn’t it pass? For every other class, this sort of monkeying with hash and eql? will get you what you want: hash key access mayhem. Why not String?

Well, there might be something sinister hiding in the “mutable but commonly used as hash keys” description. Something subtle. Maybe it’s not subtle and I just haven’t been thinking very clearly. I don’t know.

It looks to me, in the short amount of time I’ve been thinking about this, that Rubinius is more consistently Ruby-ish than Ruby is (at least in this case). Are there more ways in which that’s true?

Comments

Evil: knowing when a method got passed the default value

Let’s say you’re crazy. Now, as a crazy person, you might have a method like this:

def greet_world(salutation="Hello")
  "#{salutation} World!"
end

You’re not so crazy that you can’t write working Ruby code. Anyway, it’s totally useful:

greet_world          # => "Hello World!"
greet_world('Hi')    # => "Hi World!"
greet_world('Yo')    # => "Yo World!"
greet_world('Hola')  # => "Hola World!"

But then you see someone do this:

greet_world('Hello')  # => "Hello World!"

And it drives you even crazier. Being totally crazy, you feel a compulsion to make fun of people who pass the default value for an argument of a method. But how in the world can you tell?

Well, it turns out (and you probably know this already) that the argument list of a method is a proper venue for executing code. You know this because you’ve seen this a thousand times: def something(time=Time.now). That Time.now happens when you call the method without that optional argument. It doesn’t happen when Ruby first parses the method and adds it to the method table for the class. That wouldn’t be too helpful.

So… you can execute arbitrary code in the method argument list. But what kind? What’s the scope? Well, anything you can do in a method body is fair game (and anything you can’t is not). You can’t define a class, for instance. And the scope? The method body itself.

You’re crazy, remember? But not so crazy that you don’t remember that every statement has a return value. Like assignment. Assignment returns a value. And you’ve got a place in the argument list that’s scoped with the method body. And you’ve got a deep desire to mock people who pass default values.

BOOM!

def greet_world(salutation=(salutation_not_given='Hello'))
  if salutation == 'Hello' and not salutation_not_given
    "YOU HAVE NO IMAGINATION!"
  else
    "#{salutation} World!"
  end
end

greet_world('Hello')  # => "YOU HAVE NO IMAGINATION!"
greet_world           # => "Hello World!"
greet_world('Hi')     # => "Hi World!"

salutation_not_given is nil if you’ve given a value. It’s one of those things.

ANYWAY. Use that at your own peril.

Comments (4)

« Previous entries