Bother

Posted August 6, 2007

Something’s been bothering me for a while about Rails models. Instead of talking about the bother immediately, though, I’d like to give some context, to go over the line of thinking that led me to it. Hopefully this’ll also serve to explain why I find the issue so bothersome, and if I’m lucky it might inspire some of you to help me solve it.

People have been saying recently1 that business logic doesn’t belong in controllers. Controllers should be “thin.” They should really only serve to push data around. This data goes to that view, that data is passed to these attributes, that sort of thing. I find myself agreeing with this philosophy; it seems to result in cleaner code all around. Not just in the controller, but also where the logic ends up.

The philosophy is also vocal about where this should be: the model. Models should be “fat.” Using the power of advice2, this is reasonably straightforward. Instead of the controller running this blog post through RedCloth in the show action (which is called when you go to /posts/20), for example, the model runs it through when the render method is called.

This has all sorts of advantages. I can access the rendered content from places other than the one action. If I suddenly have more than twenty readers, I can optimize by pre-rendering the posts and nothing but the model will change. “Thin controllers, fat models” is clearly useful.

That’s not what’s bothering me, although it is related. With Haml, we’ve been advocating a similar philosophy: thin views. Or if not thin, more or less logic-less. It’s great to have lots of actual document structure in there, but not so much lots of logic or big method calls. These are subtly discouraged, in a syntactic vinegar sort of way.

For example, if you want to write a method call in Haml that is longer than one line, you have to add |, the pipe character, to the end of each line. This not only violates the convention of having the character be every line but the last, but it’s also hideous. We get two or three mailing list posts a month about this, which I would normally take as a sign that it was a major usability bug. But we always tell the people “try moving your gargantuan method call into a helper” and almost to a one they are much more satisfied with that if the huge method call had worked in the first place.

We’re getting close to my annoyance now. The issue is that sometimes, stuff just doesn’t make sense as a helper. Or, rather, it would make more sense as a model method. Again with the fat models. I don’t want to have an avatar_for helper; I want to have User#avatar defined. And here, faithful reader, is the heart of the problem: you can’t move helpers into models.

Take that avatar_for helper. It should generate an <img> tag that points to the user’s avatar, stored somewhere on the server. Linking random stuff is fun, so let’s link it to their site, if they have one. It might look something like this:

def avatar_for(user)
  img = image_tag user.avatar_path, :alt => user.name
  user.link ? link_to(img, user.link) : img
end

If we try to move this into the model, it gets a bit cleaner code-wise and a lot nicer design-wise:

def avatar
  img = image_tag avatar_path, :alt => name
  link ? link_to(img, link) : img
end

This won’t work, of course. Neither image_tag nor link_to are defined in the model. Moreover, there’s no way to get access to the ActionView template where they are defined. It’s possible, although terribly kludgey, to create a new ActionView instance and just hope that none of the methods you want to call need the specifics of the current request. That’s not really satisfactory, though.

For the time being, I’m mostly just sighing and getting on with the ugly-but-apparently-necessary non-object-oriented non-encapsulated helpers. But if Haml, make_resourceful, and Ruby in general has taught me anything, it’s that there’s no such thing as “ugly-but-necessary.”

So I’m trying to think of nice, clean ways to allow models to render stuff with ActionView. I think that, for purity’s sake, the model-helpers would have to be set off from the rest of the model’s methods. Since the view is only created after all the controller stuff is done, it should be made clear that these methods can’t be used all the time.

This could be done relatively easily by defining the methods in a special block, à la make_resourceful. There are still technical barriers, though. Unless I’m missing some obvious function, Rails doesn’t make it easy to get a reference to the current view from an arbitrary context.

What I’d love is some ActionView.current_view class method, but it doesn’t exist, and I’d feel wrong adding it. It’s not there for a reason, likely a perfectly justified abhorrence of global variables. Adding it would be a hack. It would be much preferable to add something to each model.

But I want to do this in a non-intrusive manner. You shouldn’t have to call some template= method on every new model you create. That should be done for you. But I’m not entirely sure how. Perhaps advice would suit this situation well, too. I’m not sure.

I’ll continue mulling this over. If you have any suggestions, that’s what the comments are for. If I do hit on something elegant and useful, hey, maybe I’ll have yet another project I can try to fit into my all-to-busy schedule.

1 What I really mean by this is “at RailsConf,” which I realize was a good few months ago. Time moves entirely too fast for my tastes.

2 Advice is the Lispy term for wrapping old methods with new functionality. Rails uses this quite a bit with before filters and suchlike in both models and controllers. Wrapping model attribute setters is also a particularly useful example.

Pete Forde said August 06, 2007:

Yes! Yes!

I think the much more difficult and important example is that of localization and translation. I admit that I’m terrified of my clients needing a regionalized application. It translates to “my views will be ugly and this project is going to suck to build”.

If you pull this off in a happy way, I will make sure that nice things happen to you.

Hampton said August 06, 2007:

Dude, I’m already working on this….. me and Jeff talk about it incessantly.

-Hampton “You Can’t Stop the Refactor” Catlin.

Jeffrey Hardy said August 07, 2007:

I think this is where presenters come in…

Nathan said August 07, 2007:

Presenters are one solution to this, I suppose, but I’m not terribly satisfied with it. The pattern was designed to make it simple to display complicated data relationships, and it’s great for that. It’s also somewhat serviceable as a solution for my problem, delegating some rendering to models. Unfortunately, it just boils down to the same thing as calling template= on every model, because the Presenter needs to wrap a model anyway. If we’re going that route, it would be more practical to just build this stuff into the model itself.

Anonymous said September 18, 2007:

This is really interesting. I have faced this same problem and have not been able to figure out a solution. Its the classic “chicken-and-egg” conundrum.

If u guys figure it out, I will be sincerely impressed.

Make your comments snazzy with Textile!