I want to talk about something that’s been bugging me for a long time in Rails. I want to talk about it, but a lot of smart people have already said what I have to say recently. They’ve said it rather well.
Here’s the upshot: Rails makes it look like you’re supposed to have model objects (subclasses of
ActiveRecord::Base), controller classes (subclasses of
ActionController::Base), and view templates, and that’s it. Maybe some observers (subclasses of
ActiveRecord::Observer). For the most part, Rails style is to push as much behavior into the model as possible. “Skinny controllers, skinny views, fat models,” they said.
Well, object obesity is a growing problem in the Rails world, because we’ve been shoving so much responsibility on our poor model objects that they can barely hold themselves up. They’re particularly hard to test, because while you’re mainly interested in testing your domain logic, to make one of these objects in a test you often need to persist it in the database first. That’s just crazy. And slow.
Rails is no more exempt from the utility of the Single Responsibility Principle than any other environment. What if we separated the concerns of domain logic and persistence?
For a while I thought I might be the crazy one, but it turns out I’m not alone in thinking this. We’re seeing a renaissance of classical OO patterns in the Rails world as people realize that throwing everything in a handful of objects doesn’t scale with application complexity.
The earliest post I’ve seen is from James Golick in early 2010, who calls it Crazy, Heretical, and Awesome. As James writes,
Ever wondered why it seems impossible to write a really good state machine plugin — or why file uploads always seem to hurt eventually, even with something like paperclip? It’s because these things don’t belong coupled to persistence. [...] A file upload handler shouldn’t have to worry about how the name of the file gets stored to the database, let alone where it is in the persistence lifecycle and what that means. Are we in a transaction? Is it before or after save? Can we safely raise an error?
I also love the objection he hears the reader raise:
“But then I’ll have all these extra classes in my app!”
I’ve heard this objection too. I don’t get it. Some people seem to have the idea that the fewer classes you have, the easier it is to maintain the app. Maybe it comes from being used to 3000-line classes. Having more of those would be a pain. But by breaking up responsibility into cohesive units, each class becomes comprehensible, which is of the utmost importance in software development, especially as the complexity of the app and the size of the team grow.
This past July, the storm began to gather, as Avdi Grimm got all Fowler on Rails‘ ass. He also gives a shout out to Jeff Casimir’s Draper, which I haven’t gotten to use yet but I am itching to try. People who have used it are saying good things.
Then Piotr Solnica wrote about “Making ActiveRecord Models Thin“. Don’t miss the rich discussion in the comments. Also, make sure you read the section “Well Defined API“. The part where he writes,
Your Domain Model should have an interface to every action your application should be able to perform. If you have an online shop where a user can buy a product then with a well-written Rails application you should be able to fire up the console and be able to easily perform this operation. If it’s not so simple then you probably want to think about your model implementation again.
Mmm, yeah. That’s good medicine.
Coming out of that discussion in the comments, Giles Bowkett re-raised a point from James’ piece, that not all apps will need this kind of treatment, because not all apps will be complex enough to feel the pain. Giles writes,
I don’t think there’s really any debate here at all, except for one crucial question: where do you mark the threshold? How do you decide when your code needs this split?
Next up, the great Steve Klabnik calls Plain Old Ruby Objects “The Secret to Rails OO Design“, focusing on the ways they can improve the structuring of view logic. He explains the problem with Rails this way:
[T]here’s something special about Rails which seems to lure you into the trap of never breaking classes down. Maybe it’s that
lib/feels like such a junk drawer. Maybe it’s that the fifteen minute examples only ever include ActiveRecord models.
He followed that post up with another awesome one about writing presenters. Do give them a read.
Any good articles I’ve missed? Leave a comment and let me know.