wonko.com

Hi! I'm Ryan Grove: Sorcerer at SmugMug, lover of movies, eater of pie, connoisseur of awesome.

Posts tagged with “programming”

Displaying items 61 - 70 of 78

Why I've avoided Ruby on Rails

Someone over at the Joel on Software forums has posted an excellent rant detailing his gripes with Ruby on Rails. I couldn't have said it better myself. The only thing I'd add is maybe a paragraph or two about the headaches involved in deploying Rails in a production environment, especially when Lighttpd isn't an option. I'm too lazy to write those paragraphs now, but suffice it to say that it can be a huge pain in the ass.

PHP's developers finally learn why namespaces are a good idea

PHP is a wonderfully flexible and powerful language, but it's notorious for having a very polluted global namespace and no way to define separate namespaces other than wrapping things in classes. Users have been begging for a better solution for years, but to no avail. It looks like that may finally change, though.

The release of PHP 5.1.0 just before Thanksgiving introduced a new native Date class for (not surprisingly) manipulating dates and times. Unfortunately, PHP's developers either didn't notice or didn't care that there was already a very popular PEAR class called Date. So anyone who was using the PEAR Date class and upgraded to PHP 5.1.0 suddenly found themselves using an entirely different and incompatible native Date class, which broke hundreds, if not thousands, of applications.

The PHP mailing lists were instantly abuzz with users complaining about the breakage and developers proposing half-baked ideas for solving the problem by — at long last — introducing namespaces. Thankfully, cooler heads realized that namespaces are a big change and the most important thing was to fix the immediate problem, so PHP 5.1.1 was quickly released minus the new Date class.

This incident seems to have hammered home to the PHP team just how important namespaces are and how painful it is to try and design a language as big as PHP without them. Maybe after this they'll finally get their shit together and add namespaces in PHP 6.

I hate you, PayPal

I've been fighting with PayPal's Website Payments Pro API for the last week at work. If you're considering using PayPal for anything more than accepting basic payments through their website, here's my advice to you: kill yourself.

The documentation is disorganized, incomplete, and full of errors. The page numbers in the Website Payments Pro Integration Guide aren't even correct. Good luck finding chapter six on page 83; there is no chapter six (although there are two chapter fives), and page 83 is just a few pages after page 56 and a few pages before page 105.

If you're dumb enough to actually try to use the PHP SDK PayPal provides, you'll quickly discover that it doesn't work. At all. Until you completely disable error reporting, that is. And even then, chances are it still won't work because the documentation doesn't provide instructions on how to use it correctly and the one example (which is mentioned nowhere, and can only be found by manually poking through the directory structure of the downloaded SDK) doesn't even work.

I use PayPal's standard payment system to accept Jetpants subscription payments and it works fine, but for godssake, do yourself a favor and stay away from their so-called "professional" API. There's nothing professional about it.

5 things I hate about Ruby

Don't let the title fool you. On the whole, I love Ruby. But, like any programming language, there are some things about it that really bug me.

  1. Ruby is slow.

    With very few exceptions, it has been my experience that Ruby is just plain slow when compared to other interpreted languages like PHP, Perl, and Java. There are plenty of reasons for this, but it's not hard to find places where performance could be improved just by poking through the source code of the Ruby interpreter for a few minutes. That's not very comforting.

    Hopefully the eventual move to YARV will improve things, but that brings me to my next point...

  2. Ruby's development process is slow.

    By which I mean that the development of the Ruby language itself is slow, not that developing software using Ruby is a slow process (it isn't).

    Ruby's development status is hard to get a handle on because there doesn't seem to be any single source of reliable information other than Matz himself. The fact that I don't speak Japanese makes it even more difficult, because many of the core Ruby developers don't speak or write in English.

    Not that the communication issues really matter, because not much ever really seems to happen with Ruby. Sometimes six months or even a year will go by between minor releases. That can be very frustrating.

  3. Ruby's "official" documentation is sparse and, in some cases, nonexistent.

    Learning the basics of Ruby isn't very hard; there are plenty of beginner's guides out there, including the brilliant Why's (Poignant) Guide to Ruby, but once you get past the basics and want to really get your hands dirty, you're going to find yourself doing a lot of Google searches and coming up empty a lot of the time.

    Ruby (and most other programming languages) could learn a lot from PHP in this respect. The PHP documentation isn't by any means perfect, but it's organized in a sensible way and the user comments often provide valuable clarifications or examples when the documentation itself falls short.

    Ruby's official docs don't seem to have been written with much care. The latest revisions have even inexplicably removed all documentation for the Kernel object, which contains some of Ruby's most important and frequently-used features. I emailed the maintainer about this several weeks ago and even got a response, but they have yet to do anything about the problem.

  4. Screw the Ruby Way, sometimes I just want to get things done.

    Spend any time using Ruby and you'll hear a lot about the Ruby Way. People love to talk about Ruby as if it's a religion rather than a programming language. The term refers to Ruby's general philosophy of simplicity, elegance, and adherence to the Principle of Least Surprise.

    On the surface this seems like a good thing, and it really should be, but the truth is that the Ruby Way is different depending on who you ask. And since Ruby programmers can't stand the idea of implementing something in any way other than the Ruby Way, they'll often end up spending days, months or years talking about the best way to implement something without ever actually implementing it. Spend five minutes trying to find actual solid information in the RubyGarden wiki and you'll see what I mean.

  5. Ruby has shitty XML support.

    There are two reasons for this. The first is that the Ruby crowd seems to have a beef with XML. They'd much rather force you to use YAML, even though there are plenty of situations in which YAML is absolutely the wrong tool for the job.

    The second reason for Ruby's shitty XML support is that nobody can agree on the Ruby Way to implement XML support. So we're stuck with REXML, a slow, buggy, incomplete XML processor that provides an API that bears no similarity to the XML API of any other programming language. Most languages provide out-of-the-box support for the DOM API and perhaps one or two other standard APIs, but not Ruby. You either use REXML or you use (or write) a third-party extension and make your application less portable.

    My hatred of REXML is a personal matter, obviously, and there are plenty of people who like it, but the problem is that there aren't alternatives. At least, not as far as Ruby's standard libraries are concerned. And I hate that, because as much as I like YAML, I often prefer to use XML, and using XML in Ruby is a pain in the ass, so I generally end up using PHP instead.

WordPress's idea of SQL injection security: addslashes

In the spirit of improving the quality of open source software, I’ve decided to launch a whole new category here on wonko.com. I’m calling it Crappy Code. Whenever I come across a really crappy piece of code in an open source application, I’ll write a cantankerous, insult-laden post all about how crappy it is and then, when applicable, I’ll offer up a suggestion for improving said code.

For our first installment in this series, we’ll turn to WordPress. If I had to list all the things I hate about WordPress, I’d probably kill myself instead. But here’s an example of the sorts of things that might be on that list if my suicide attempt were to fail:

  1. They have a stupid motto. “Code is poetry”. Blah blah. I’d be less annoyed by this if their code was actually good, but the fact that it sucks just annoys me even more.
  2. WordPress is one of those applications that uses 100 lines of code to accomplish a task that could have been accomplished in 10 lines if the developers had bothered to put some fucking thought into it.
  3. Nobody who uses WordPress actually gives a shit about how crappy the code is, so it’s insanely popular. Wound: check. Salt: check.

So now that we’ve established that I’m insanely biased, let’s get to the part where I make fun of some code. It was pretty tough finding just one thing to make fun of. I have a feeling WordPress is going to show up quite a lot in future Crappy Code posts. But to get things started I wanted something short, sweet, and simple. Lo and behold, I found it.

WordPress 1.5.2; wp-includes/wp-db.php; line 76:

// ====================================================================
//  Format a string correctly for safe insert under all PHP conditions

function escape($str) {
  return addslashes($str);
}

Oh dear God.

The comment is what really gets me. What genius came to the conclusion that addslashes would make all possible strings safe for use in SQL queries under all possible conditions? The addslashes function just puts a backslash (\) in front of single-quotes (‘), double-quotes (“) and NUL characters. And depending on the value of PHP’s magic_quotes_sybase setting, it might escape single-quotes with another single-quote rather than a backslash. What it doesn’t do is escape newlines, carriage-returns, or the control character \x1a, all of which need to be escaped when sending string values to MySQL.

Instead of addslashes, they should be using mysql_real_escape_string, which actually does escape every unsafe character “for safe insert under all PHP conditions”. Their code should look like this:

function escape($str) {
  return mysql_real_escape_string($str, $this->dbh);
}

WordPress is one of the most widely used PHP applications in the world. They have a history of SQL injection vulnerabilities. You’d think they’d know by now which function to use for escaping strings being sent to MySQL.

Update: A helpful reader has pointed out that the issue was fixed way back in July and the fix will be in the 1.6 release.

A Simple and Elegant PHP/MySQL Web Application Framework, Part 3: My God, it's Full of Objects!

Objects are your friends. PHP’s object-oriented features don’t get nearly as much use as they should. This is probably because, in PHP 4, they weren’t worth writing home about. But PHP 5 has improved things greatly, and while PHP still isn’t anywhere near as object-oriented a language as Java or—be still my heart—Ruby, it’s come a long way. And we’re going to take advantage of it, because it’ll make our lives a whole lot easier.

First we’ll design a class which will allow us to manipulate users in the user table. Columns will be represented by public class variables. If we wanted to be anal about it, we could use __get and __set methods (which PHP confusingly calls overloading for some reason) to represent our class properties, but that would be a bit overkill for our needs. We’ll keep it simple by sticking with public variables. If you were designing this class to be used by third-party developers, then it’d be worth going to the trouble of using getters and setters to validate your property values, but we’re just designing it for us, and we trust us not to do something silly like assigning a string to a property that should be an integer.

The MonkeyUser class will have a mix of static functions (which can be called without instantiating the class) and instance functions (which will apply only to a single instance). The static functions will serve as object factories; for example, we’ll call MonkeyUser::getByUsername when we want to retrieve a MonkeyUser object representing a specific user. The static getByUsername function will retrieve the appropriate row from the user database table, then return a MonkeyUser object representing that user.

classes/MonkeyUser.php

class MonkeyUser
{
  public $id;
  public $username;
  public $password;
  public $email;
  public $fur_color;
  public $height;
  public $weight;

  // -- Public Static Methods -------------------------------------------------
  public static function getByUsername($username)
  {
    $result = Monkey::query('user.getByUsername', array(
      'username' => $username
    ));

    if ($result && $row = $result->fetch_assoc())
      return MonkeyUser::load($row);

    return false;
  }

  public static function load($row)
  {
    return new MonkeyUser($row['id'], $row['username'], $row['password'],
      $row['email'], $row['fur_color'], $row['height'], $row['weight']);
  }

  // -- Public Instance Methods -----------------------------------------------
  public function __construct($id = 0, $username = '', $password = '',
    $email = '', $fur_color = '', $height = 0, $weight = 0)
  {
    $this->id        = $id;
    $this->username  = $username;
    $this->password  = $password;
    $this->email     = $email;
    $this->fur_color = $fur_color;
    $this->height    = $height;
    $this->weight    = $weight;
  }
}

You’ll notice that the getByUsername function doesn’t create the MonkeyUser object itself; it calls the load method to do that. This way we can add any number of functions to retrieve MonkeyUser objects, and we won’t need to duplicate the instantiation code; each function will just pass an associative array representing a database row to the load function, and it’ll return an instantiated object.

Don’t forget to add a line to common.php to include our new class:

require_once 'classes/MonkeyUser.php';

Now whenever we want to retrieve a user object, all we have to do is call a single function, like so:

<?php
$user = MonkeyUser::getByUsername($_GET['username']);
?>

<h2>Hello, <?php echo htmlentities($user->username); ?></h2>

<p>
  Your fur is <?php echo htmlentities($user->fur_color); ?> and you claim
  to be <?php echo $user->height; ?> centimeters tall.
</p>

We don’t need to do anything special to sanitize the user-submitted value $_GET['username'] because our Monkey::query function will take care of that automatically. Whenever we display user-modifiable strings, we sanitize them with htmlentities to ensure that the user can’t include HTML or JavaScript that could be used to carry out a cross-site scripting attack. It’s not necessary to use htmlentities on numerical data, since we know that the database wouldn’t allow strings to exist in numerical columns.

The MonkeyUser class can be expanded with a new static function whenever you have a need for a new way of retrieving users. You could even add a getAll function that would return an array of MonkeyUser objects for every user in the database (just remember to add the associated user.getAll SQL query in db/queries.xml):

public static function getAll()
{
  $result = Monkey::query('user.getAll');

  $users = array();

  while($result && $row = $result->fetch_assoc())
    $users[] = MonkeyUser::load($row);

  return $users;
}

If we want to be able to modify the values of a MonkeyUser object (or create a brand new user) and save it to the database, we can add a save instance function. It might look something like this:

public function save()
{
  if ($this->id == 0)
  {
    // The id is 0, so this is a new user.
    $result = Monkey::query('user.insert', array(
      'username'  => $this->username,
      'password'  => $this->password,
      'email'     => $this->email,
      'fur_color' => $this->fur_color,
      'height'    => $this->height,
      'weight'    => $this->weight
    ));

    // If the new user was inserted successfully, grab the new id.
    if ($result)
      $this->id = Monkey::$db->insert_id;
  }
  else
  {
    // The id isn't 0, so we're updating an existing user.
    $result = Monkey::query('user.update', array(
      'id'        => $this->id,
      'username'  => $this->username,
      'password'  => $this->password,
      'email'     => $this->email,
      'fur_color' => $this->fur_color,
      'height'    => $this->height,
      'weight'    => $this->weight
    ));
  }

  return $result;
}

That’s all there is to it. Any values you pass to the database are automatically escaped, so you don’t need to worry about doing that manually a zillion times throughout your application. And any values that come out of the database only need to be run through htmlentities before being displayed. If you actually want to allow your users to use HTML in certain database fields, you should use the strip_tags function to limit the tags they can use. Or, for more advanced HTML filtering, check out Cal Henderson’s excellent lib_filter.

So. Now you have the beginnings of a fairly complete framework, except that you haven’t got a presentation layer. You have a multitude of options at this point. For smaller projects, I recommend sticking with HTML and minimal inline PHP, using CSS for formatting and layout. But if you want to do it right, an XML/XSLT-based template system is the way to go. Never, under any circumstances, use a template system like Smarty. If you do, I’ll point and laugh.

Someday I’ll write another article discussing how to use XML and XSLT for your presentation layer. It’s not as complicated as you might think. Right now I’m going to go have a sandwich. I hope you’ve found this series useful.

A Simple and Elegant PHP/MySQL Web Application Framework, Part 2: Getting Started

There are three things you should never ever do in PHP:

  1. Use short tags (<? and ?>) or ASP-style tags (<% and %>) rather than the standard <?php and ?>
  2. Use the addslashes or stripslashes functions
  3. Enable magic_quotes_gpc or magic_quotes_runtime

Actually there are a lot more things you should never ever do in PHP, but we’ll start with these.

The first rule should be self-explanatory. Many servers have short tags disabled, and I doubt there’s a single server in the entire universe with ASP-style tags enabled, so the only way to ensure that your application will work everywhere is to use the standard long tags.

I could write several articles all about the evils of magic quotes and why doing the slash-dance with addslashes and stripslashes is a surefire way to produce unsecure and unmaintainable code, but plenty of articles have already been written on this subject. Suffice it to say that if you find yourself using addslashes or stripslashes, it’s time to stop and rethink the design of your application. You should never need to use these functions. Ever. I’m about to describe a much better (and far easier) way to protect yourself from SQL injection attacks.

Now, let’s pretend we’re developing a web portal for monkeys and we’re calling it, oddly enough, Monkey.

First we’ll create a common script that will be included by every other script in our application. In this file, we’ll define our database settings, include our application’s classes, and initialize our framework. Because we’re so original, we’ll call it common.php. This concept shouldn’t be anything new to you if you’ve been developing PHP apps for any length of time.

common.php:

<?php
// Database settings.
define('DB_HOST', 'localhost');
define('DB_USER', 'myuser');
define('DB_PASS', 'mypass');
define('DB_NAME', 'monkey');

// Load classes.
require_once 'classes/Monkey.php';

// Initialize the application.
Monkey::init();
?>

Notice the last line: we’ve made a call to the static function init of class Monkey. This is the base class of our application, and init is the function that’ll open a database connection and get everything ready to go.

We’re using PHP 5 because we’re not silly, so we’ll make Monkey an abstract class. In essence, the Monkey class will serve as our application’s main namespace, keeping our own methods and variables separate from those of PHP. Since PHP’s global namespace is incredibly polluted, this will make our life easier down the road.

The init function will do two things: connect to the database and load an XML file containing all the SQL queries our application will be using. By keeping our queries in this XML file, separate from our code, we’ll make our application vastly more maintainable. Mixing SQL queries with PHP code makes for a big mess. In addition, we’ll use an incredibly simple (but powerful) form of parameter substitution to ensure that every single piece of data we send to MySQL will be properly escaped, thus protecting us from SQL injection attacks, all with almost no effort on our part.

Here’s an example showing how our XML file (which we’ll name queries.xml) might look:

db/queries.xml

<?xml version="1.0"?>
<queries>
  <!-- Table: user -->
  <query name="user.getByUsername">
    <![CDATA[
      SELECT *
      FROM user
      WHERE username = :username
    ]]>
  </query>

  <query name="user.insert">
    <![CDATA[
      INSERT INTO user (
        username,
        password,
        email,
        fur_color,
        height,
        weight
      )
      VALUES (
        :username,
        :password,
        :email,
        :fur_color,
        :height,
        :weight
      )
    ]]>
  </query>
</queries>

Note that you’ll want to either restrict access to the directory containing queries.xml or place it outside your web directory (I forgot to mention this in the first version of this article; thanks to Brett for pointing it out). You wouldn’t want the whole world viewing your SQL queries.

As you can see, we’ve substituted placeholders like :username and :email for incoming values. We’ve also named our queries descriptively in the form “table.action”. Later, when we start executing these database queries from PHP, this will help us understand what the queries are doing. If we were using MySQL 5 or PostgreSQL, we could accomplish something similar using stored procedures, but I actually tend to prefer this method (mostly because I find stored procedures harder to maintain and a bit overkill for most purposes).

Now that we’ve created our query file, let’s create our Monkey class, along with the init function and a query function, which we’ll call whenever we want to execute a database query.

classes/Monkey.php

<?php
abstract class Monkey
{
  public static $db;
  public static $queries;

  public static function init()
  {
    // Open database connection.
    self::$db = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);

    // Load predefined queries.
    $queriesXml = simplexml_load_file('db/queries.xml');

    self::$queries = array();

    foreach($queriesXml as $query)
      self::$queries[(string)$query['name']] = (string)$query;
  }

  public static function query($name, $parameters = array())
  {
    if (!isset(self::$queries[$name]))
      throw new Exception("Undefined query: $name");

    $sql = self::$queries[$name];

    if (count($parameters))
    {
      $formattedParams = array();

      // Prepend a ':' to each parameter name and escape the value properly.
      foreach($parameters as $paramName => $paramValue)
      {
        if (!is_numeric($paramValue))
        {
          if (is_null($paramValue))
            $paramValue = 'NULL';
          else
            $paramValue = "'".self::$db->real_escape_string($paramValue)."'";
        }

        $formattedParams[":$paramName"] = $paramValue;
      }

      // Replace placeholders in the query with assigned values.
      $sql = strtr($sql, $formattedParams);
    }

    // Display the query if SHOWSQL is true (for debugging purposes).
    if (defined('SHOWSQL') && SHOWSQL)
      echo htmlentities($sql)."<br />\n";

    // Execute the query and return the result.
    return self::$db->query($sql);
  }
}
?>

Take a close look at the query function. This little function will save us tons of time and effort since it will do all the work of executing SQL queries and automatically escaping our input data.

Mull this over in your head a bit. Think about how you’d go about using the query function. Then check back tomorrow for the next part, in which I’ll provide some examples.

A Simple and Elegant PHP/MySQL Web Application Framework, Part 1: The Problem

I’ve been developing PHP/MySQL applications for years, both professionally and as a hobby, and if there’s one thing I’ve learned, it’s that there are an infinite number of wrong ways to do it and very few right ways. The vast majority of open source PHP/MySQL web apps are horrendous pieces of garbage. Even the ones that look pretty and clean on the outside are, more often than not, an utter mess inside. This is because the vast majority of PHP/MySQL developers—even the ones who think they’re the hottest shit that was ever shat—place more importance on the end result than on how they get there. And believe me, the ends rarely justify the means when it comes to programming.

It’s an easy trap to fall into, especially in web development. The thing that makes web development so sexy is the instant gratification; you write some code, you click a button, and you’re looking at the result of the code you just wrote. Since the dawn of time (by which I mean the 1970s), books and teachers have been using the concept of instant gratification to get people interested in programming and to keep them coming back for more. This is why the first program you learn to write in a new language will always be “Hello World”; it gives you a very simple taste of the language, provides instant gratification, and leaves you thinking, “Well, that was easy. I want to learn more!”

This makes people lazy, and this laziness is enhanced when the language is as simple to learn and implement as PHP. Why spend a lot of time coming up with an elegant way to separate logic, content, and presentation when you could just toss everything together, copy some example code off a few websites, say a prayer, and get an instant result?

I was certainly guilty of this sort of thinking once upon a time. PHP makes it ridiculously easy to write messy, unsecure, unmaintainable code that nevertheless produces beautiful results. I should know; I’ve written my fair share of crappy PHP code. But with a little thought and some attention to detail, PHP also makes it easy to write beautiful, elegant, secure, maintainable code that produces beautiful results.

After years of writing framework after framework for my web applications and never being fully satisfied, I’ve finally hit on one that works well and makes me happy. It evolved over a period of about two years, gradually getting simpler, more elegant, and more maintainable, until it finally reached a stage where I was able to use the same concept in several web applications of various sizes without altering it. It does away with many of the most common PHP annoyances, makes security painless, and is simple enough and flexible enough that maintaining old features and adding new ones is a breeze.

Over the next few days, I’ll describe the concepts behind the framework, how they evolved, and how I’ve chosen to implement them. There will be lots of example code and lots of me making fun of old code I’ve written. So if this sort of thing interests you at all, stay tuned. Otherwise, be patient. It’ll all be over soon, I promise.

Comment spam defeated by...a cookie?

When I decided not to implement user accounts in [The weblog software behind this site.|Pants], I knew I'd have to do something to prevent comment spam. The most popular method for fighting comment spam on sites that don't require user registration seems to be maintaining a huge blacklist of URLs or words frequently used by spammers and checking each comment against the blacklist. Some sites use Captchas, but those are complicated to implement and a real pain in the ass for vision-impaired users. I really didn't want to have to use either of those methods. So I figured I'd wait and see what the spammers did and then figure out something simple to keep them at bay.

Sure enough, within minutes of bringing the Pantsified wonko.com online, I had my first comment spam. It was obviously just an automated spambot filling out forms. By the next morning, I'd had several more spams, and I decided to try the simplest idea I could think of, just to see if it would have any effect.

So I implemented a very basic behind-the-scenes authentication system in about five lines of PHP. When the "Post a comment" form is displayed, Pants generates a key that's unique for each IP and changes every hour. This key is sent to the user's browser as a cookie. When the form is submitted, Pants checks to see whether the cookie is set and the key is valid. If everything checks out, the comment gets posted. Otherwise, no comment for you. It's completely transparent to the user and so simple I didn't think it would actually work.

Obviously, all a spammer would need to do to bypass this system is support cookies. And yet, since implementing it a week ago, I haven't had a single spam comment.

I still find it very hard to believe that defeating comment spam is this simple. I find it even harder to believe that nobody else has ever bothered doing this before. Have I just been lucky, or is every other comment spam prevention system horribly over-engineered?

A brief comparison of cross-platform GUI toolkits (from Ruby's perspective)

Ruby has certainly won me over as far as command-line scripting is concerned, but I want to do more than that. Apart from wanting to write web apps with it (which I’ll probably discuss in a later post), I also want to use Ruby to write small, cross-platform GUI applications. So I spent a good deal of my free time last week doing just that, and found it to be at times rewarding and at times incredibly frustrating.

There’s a huge selection of GUI bindings for Ruby, allowing you to work with a wide variety of GUI toolkits. Unfortunately, most of these toolkits were intended to be used by C or C++ developers. As a result their APIs tend to be stodgy and, well, C-like, rather than Ruby-like, which is annoying since it can effectively suck the joy out of using Ruby.

That said, they’re not all bad; but none of them is all good either. Here’s a brief collection of thoughts on each of the toolkits I investigated.

Tk

The crotchety old man of cross-platform GUI toolkits. Took one look and ran away screaming. The API is a badly-documented mess, the widgets are non-native and ugly, and everyone who’s used it seems to hate it. Screw that.

FOX

I want to like FOX. Its Ruby API, provided by FXRuby, is good and well-documented, but FOX itself is a young project, and it shows. It seems to be developed primarily by one person, which is okay except that this person doesn’t have access to a Mac OS X system and thus can’t provide an OS X port. For me, this is a killer: Windows and OS X are my primary targets, followed by FreeBSD and Linux.

wxWidgets

A very stable, very mature toolkit with a good selection of widgets. Its most appealing feature is the fact that it uses native widgets whenever possible, thus ensuring a consistent look and feel with other applications on each platform. Furthermore, wxWidgets has excellent support for all the platforms I care about.

Unfortunately, the API is very C++-oriented, and the bindings provided by wxRuby do almost nothing to make it more Rubyish. What’s more, WxRuby has no documentation aside from a virtually useless auto-generated class and function list. They seem to expect developers to use the wxWidget API reference, which is, admittedly, well-written and very complete. However, WxRuby inexplicably leaves out a surprising number of API functions and, even more inexplicably, sometimes uses function signatures and constant names that differ from those of wxWidgets, occasionally even going so far as to change the actual purpose of a function. The result is a muddled mess, even though everything seems to work well if you actually take the time to cross-reference the wxWidgets API documentation with the WxRuby source code to figure out what you should be doing.

GTK+

By far the easiest to use of all the toolkits I looked at, which is due in no small part to Glade, a free UI designer that makes it a piece of pie to throw together a GTK+ GUI quickly with very little effort. The API bindings provided by Ruby-GNOME2 are excellent as well, and mostly do things in the Ruby Way™. The Ruby-GNOME2 project also provides excellent documentation and tutorials.

Unfortunately, GTK+ has its drawbacks. Windows support isn’t bad, and even uses native widgets in many places under Windows XP, but it requires a hefty set of runtime libraries. Unix support is excellent, of course, with the sad exception of—you guessed it —Mac OS X. It’s possible to run GTK+ apps in OS X using the X Windowing System, but this means your app looks inconsistent and ugly compared to the rest of the OS X interface.

To sum up, I’m frustrated. I want to like FOX, but it’s useless to me without OS X support. I think wxWidgets is superior to the others in almost every respect, except that it’s also useless to me because wxRuby is crap. And GTK+, while easy and painless and an absolute joy to use, has crappy OS X support and requires annoying runtimes on Windows.

Nevertheless, I think I’ve pretty much decided to use GTK+ for now. I’ve already developed one complete application with it (which I’ll release shortly), and I didn’t find it at all painful, especially compared to wxWidgets, which, thanks to WxRuby, makes me want to kill things.