Wednesday, March 7, 2012

A Month with Ruby and Rails

My current project is moving slowly, so I've been making changes to our corporate website. It uses Ruby on Rails, so has given me the opportunity to learn both in the context of a real project. At this point I think I'm comfortable enough with both Ruby and Rails to make some comments, albeit still from a novice's perspective. And my first comment is that I don't love Ruby, but I don't hate it either. I was told that I'd go down one of those two paths, but it hasn't happened yet.

There's a lot to like about Ruby, and my favorite is that Nil is an object, allowing constructs like foo.present?. This doesn't eliminate null-pointer errors, but it reduces the number of times that you have to worry about them. For example, the conditional expression foo == "bar" doesn't throw, it returns false (which is just what you'd want).

I also like the way that Ruby allows any method to take an optional block. This is the base for standard Ruby constructs like each, also allows for a form of “progressive enhancement.” A nice example of this is in the event_calendar gem, where you can pass in a block that decorates each event (in the README, it's used to wrap each event in a link). If you don't pass that block, you get a basic calendar.

OK, now it's time for my dislikes, and chief of these is that objects and hashes are two different things. One of the nice features of JavaScript is that foo["bar"] and foo.bar do the same thing: they retrieve a member from an object. Ruby, however, is surprisingly formal in its approach to member access — indeed, it's much like Java in that member variables are private and you have to create accessor methods for them. And while Ruby gives you some syntactic sugar to make the process easier, it's still annoying boilerplate, especially when you have to manually extract hash arguments in initialize.

I can understand the desire to limit access to member variables. But then Active Record throws it's own wrench into the mix, by defining a [] method. Which some libraries (like event_calendar) use because it has the nice property of returning Nil if the member doesn't exist. Which in turn means that, if you want to pass your non-Model DTO to those libraries, you have to implement the following reflective code:

def [](attr_name)
  instance_variable_get("@#{attr_name}")
end

That brings me to my second dislike, which is focused more on Rails than Ruby: the “DSL” approach to programming can quickly become uncontrolled. When I was first exposed to Rails, I really liked the way that it leveraged method_missing to provide convention-driven operations such as find_person_by_last_name. After working with it for a while, I've come to the conclusion that it's really 10,000 helper methods to cover corner cases where convention doesn't work. And they're all defined by modules that are transparently mixed into your classes.

This wouldn't be so bad, except the documentation is truly horrendous. Take ActiveRecord: it's a module that consists solely of other modules, so its documentation page just lists those modules. There's no index, so finding anything becomes a treasure hunt, often involving the method method. Even then, I find myself reading source code. For example, the [] method is defined by ActiveRecord::Base, but there's no mention of it in the API docs. Google truly has become my friend.

I'll close with StringInquirer. This is a class that lets you write foo.value? rather than foo == "value". When I discovered this (trying to understand Rails.env), I sent an email to a friend that described it as “either a brilliant use of method_missing or an utter perversion of same.” I suspect that my final answer to that question will determine whether I love or hate Ruby.

No comments: