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.

1 comment:

Anatoly said...

love honest writeups.

Did not yet have a chance to really "dive in" to Rails, mostly due to not using it for work, and not being _too_ interested.

I guess there are only two categories of people: ones who love Ruby and others who love Python ( <= I am here, although I _like_ Ruby ).

What turned me away from Rails 3 years ago was community => mostly loud mouthes yelling how they hate Java, and how Rails will save the world. Although every time I had conversations with them (mostly via conferences, IRCs and forums), they had no clue what Java is beyond that it is "awful".

Since Rails picked up huge, e.g. most of Github projects are Ruby, a LOT are Rails, community kind of normalized, so it is not an issue anymore.

What turns me away from Rails now is how bloated it has become. They say JEE stack is bloated, but in comparison to today's Rails, JEE is shrinking and gets leaner and meaner, while Rails just eating donuts for breakfast, lunch and dinner. Funny thing is Yehuda Katz (Rails core dev) actually started a "Let's make Rails .. easy again!": http://www.kickstarter.com/projects/1397300529/railsapp and he is a huge Rails advocate.

Hence even if I do have time to play with Ruby web stack, it most likely be Merb, Sinatra, etc..

Completely agree on your observation about how poor documentation is . Although it is improving (http://guides.rubyonrails.org/) it is still far from being mature (as they claim Rails is).

ActiveRecord from my limited experience playing with it is total garbage. People hate Hibernate... well I guess those people did not have to deal with ActiveRecord beyond CRUD. It is especially confusing when you start to mix persistence backends (SQL/NoSQL, etc..) God forbid try to write a plugin... welcome to a mixed in world where mix ins are mixed into mix ins.

Plugin echo system is awesome and awful at the same time. Awesome cause there is just so much. Awful because most of plugins are not maintained of require different versions of Ruby/Rails/other gems, etc..

If you get excited from things like "foo.present?", I would recommend to also look at Groovy. I actually find Groovy cleaner than Ruby, but Ruby is of course WAY more popular, and I don't really like Grails either [that is a whole different rant] :)

I think, at the end, it makes a lot more sense to love Ruby than to hate it ( yes, "love" in this case is a quite rational thing ), and if "Keith.ruby.love?" is a turning point for you, I hope it'll return true ( or 42. whichever you love more :) ).

/Anatoly