So I’ve been trying to enforce some halfway-arbitrary-but-plausibly-correct standards in my own code lately. Specifically, I’ve been looking at a certain class of helper method naming patterns we’ve been using for a while, and thinking about how to be most consistent across some different scenarios.
I’m pretty sure that the pattern leading up to this decision is actually pretty well known and well followed, but I’ll pretend I don’t know that, and walk through the evolution of how I got to where I am, so you’ll understand what’s keeping me up at night.
Let’s say you have an application with posts. They’re probably written by users. I bet they’re all about interesting things, too. But, you’ve got to get them!
First you do something like this…
class PostsController < ApplicationController def show @post = Post.find params[:id] end end
Ok, that’s fine. This uses a normal rails route, will raise (and be caught for a 404) if it doesn’t exist, etc. Let’s say v1 of this app launches to great acclaim, as the entire internet embraces the interesting stuff you say in your posts.
Ok, requirements change - posts#show needs to be scoped to the user who is logged in. Not only that, but lots of other stuff in v2 of the app is going to need to know about the user who is logged in, and the design team wants some way to refer to this user in the views.
So you do this in the application controller…
class ApplicationController < ActionController::Base before_filter :load_user protected def load_user @user = User.find_by_id session[:user_id] end end
Ok, great. Now the entire application can refer to @user and know what it
means. Also, if it’s
nil, designers can do something special to not refer to
Now you can do this in your
posts_controller, which will scope the Post that
gets loaded on #show to only load if it’s owned by the logged in user.
class PostsController < ApplicationController def show @post = @user.posts.find params[:id] end end
Great, the requirements are met. Version 2 launches to even more critical acclaim, as all site users applaud your efforts to only show them their own posts.
Now the business team gets together and they want site users to be able to view
other users and then be frustrated when they can’t view their posts. So you set
about implementing the
users_controller and a new set of views for it. But
wait! What about the
users_controller’s #show action!?
class UsersController < ApplicationController def show @user = User.find params[:id] end end
Thats not gonna fly. We’re already setting @user up at the global level. Maybe that was a bad idea. Yup, that was a bad idea. We can’t hold our actions responsible for things they didn’t do, can we?
Let’s refactor that into a method instead of a
before_filter in the
class ApplicationController < ActionController::Base protected helper_method :current_user def current_user @_current_user ||= User.find_by_id session[:user_id] end end
…and now change your posts controller as well…
class PostsController < ApplicationController def show @post = current_user.posts.find params[:id] end end
Thats much better. We satisfy all the requirements, and now the users controller can use @user until the cows come home. Version 3 is wildly popular, since the egomaniacal user base that’s been reading their own posts for so long turns out to really love the opportunity to see other users whose posts they cannot read!
Well, some time goes by and we introduce commenting. The business team, geniuses that they are, have realized that users who love reading their own posts are really gonna go nuts about commenting to themselves! So we’re Modern Web Designers, and we want to build an application with RESTful urls. For example, the collection of all comments on a post page might be something like…
So in the
comments_controller we’re going to do…
class CommentsController < ApplicationController def index @post = Post.find params[:post_id] @comments = @post.comments.find :all end end
Well, we had a tight deadline, but we got the feature done. The users of version 4 are happy with their new commenting abilities. But no one likes that code. Why do I need that @post variable? We’re not even using it in the views, it’s there for nothing. What could have been a one line action is a two line action, and that’s one line too many, hence I’m going to be kept up at night until we sort this out in the 4.x series.
While we’re looking at removing that extra line, we realize that using
params[:post_id] all over the place is getting a bit tedious as well. Why
dont we build another helper method?
class ApplicationController < ActionController::Base protected helper_method :current_post def current_post @_current_post ||= Post.find_by_id params[:post_id] end end
Ok, that works. What does that do to our controller?
class CommentsController < ApplicationController def index @comments = current_post.comments.find :all end end
Ah, that’s better, isn’t it? We’re back to one line, and our designers can use
#current_post in the same way they’ve become accustomed to using
#current_user throughout the views. Good job.
Now, in the midst of our refactoring release we find out that the next version is going to use the account as subdomain pattern (where http://<account-name>.someapp.host scopes which account is in use). To solve that problem, we’re going to embrace this pattern again and do something like…
class ApplicationController < ActionController::Base protected helper_method :current_account def current_account @_current_account ||= Account.find_by_keyword request.subdomains.first end end
Man, isn’t this great? Our designers have access to
#current_user to get the
logged in user from the session,
#current_post to get the requested post from
the params/routes, and
#current_account to inspect the subdomain from the
hostname and get an account. They’re just giddy, and we can sit back and look
at some pretty well organized code.
Our account as subdomain feature release goes really well. The sort of person that loves to comment on their own posts which no one else can see, REALLY likes to get a username.service.host hostname to do this all on!
So now the business team wants to add a bunch of features and we run into a bit of a naming crunch.
- When they ask to list all posts by a user under an account, we dont know if
#current_accountmeans the account at this subdomain or the account from
- When they ask to show the accounts held by a user (yes, a user can have
multiple vanity subdomains), we don’t know if
#current_useris from the session or the params
Clearly a big mess, clearly a lot of sleep to lose, clearly some refactoring to be done.
Now, to step back. Remember at the beginning when I said there were some patterns I took for granted. That’s everything up to here. Now is where I introduce some draconian naming policies and insist they always be followed.
This pattern we have is great. We avoid creating more instance variables then
we need to, and (in my opinion), the readability of all the
is better than having either instance vars or params references everywhere. But
now we’re using the same
#current_* method naming for three different
|Get something from the session||ie,
|Get something from the routes/path||ie
|Get something from the hostname||ie,
Having gone through all of that, I’m on a quest for some new naming.
My current thinking is to use
request_ as the
method prefixes, to refer to getting something from the
request/host information, respectively. Examples…
|#session_user||User record based on
|#current_post||Post record based on
|#request_site||Site record based on
Does anyone else use this pattern? Is there a better naming convention to use? Was my story entirely too long to explain a relatively simple question? Inquiring minds want to know.