Tracks: Part 2

From The Rails Way - all, over 2 years ago, 0 views

Here we are, #2 in the series of articles exploring the GTD application Tracks. In part 1, Koz discussed the finer points of code reuse. Here, I’ll talk about action filters in your controllers.

I’m focusing primarily on the ContextController and ProjectController in this article—both have many similarities. Their actual purpose is not really relevant here, so I won’t describe what they actually do. Rather, let me just point out one of the first things I noticed when I started reading through the code. This pattern was startlingly prevalent:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def list
  init
  ...
end

def show
  init
  init_todos
  ...
end

def create
  ...
end

def add_item
  self.init
  ...
end

Note that the same method gets called at the start of many of these actions. The intent here, is to do some kind of common setup, or initialization, prior to the meat of each action. Rails provides a cleaner way to do this, via filters. Filters are simply methods that get called before or after an action, and this code is a textbook example of when you ought to be using them.

In general, filters that you want executed before an action are defined via the before_filter macro:


  
before_filter :init, :init_todos

Defining the filter like that would cause both init and init_todos to be called prior to every action in the current controller (and its subclasses, since filters in a superclass are inherited by subclasses).

What we want in this instance is something a bit more sophisticated. We don’t want the filters to trigger on every action, only on some of them. In fact, there are only one or two actions that need the init_todos filter. Fortunately, before_filter (and the filter macros in general) provide a very simple, but very powerful, mechanism for including or excluding actions:

1
2
before_filter :init, :except => :create
before_filter :init_todos, :only => :show

The above specifies that the init method should be called prior to every action, except create, and init_todos should be called prior only to the show action. Both except and only allow arrays of action names to be given, as well.

Using filters like this can help you to reduce the “noise� in your actions, leaving you free to implement only the tasty bits within the action itself. It really makes your code easier to read, and to maintain.

Another (related) thing I noticed, this time in the ApplicationController, was the following:

1
2
3
4
5
6
7
8
before_filter :set_session_expiration

# ...

def set_session_expiration
  return if @controller_name == 'feed' || ...
  # ...
end

The top-level ApplicationController specifies a before filter, set_session_expiration, that will trigger on every single action in all subclasses. It has some logic to determine what the session’s expiration should be. Note, though, that if the controller is determined to be the FeedController (the name of the controller is “feed�), that it just returns.

In general, if you need to refer to the current controller or action name explicitly in your code, you could probably refactor it to be cleaner. In this case, you can let Rails’ filtration code do the conditional work for you. In ApplicationController, simply omit that condition altogether. Then, in the FeedController, do:

1
2
3
4
5
class FeedController < ApplicationController
  skip_before_filter :set_session_expiration

  # ...
end
Koz says: The reality is most aggregators don’t work with sessions anyway, what you probably want in FeedController is session :off. This will disable sessions for that controller, and stop your sessions table from filling up with empty, aggregator-generated sessions.

The skip_before_filter macro allows subclasses to override filter behavior configured in their parent class. Here, we tell Rails that we want to “skip�, or ignore, the set_session_expiration filter for all actions in this controller. (You can use the only and except parameters here, as well, if you need finer tuning.) By using the skip_before_filter macro, the code that applies to the FeedController is in the FeedController, and the ApplicationController remains blissfully unaware of its subclasses.

(While we’re on the topic of filters, I might just add that filters in edge rails have one minor drawback: they cause your stacktrace to become ridiculously long, because they are processed recursively. A way to process them without making recursive calls would be a wonderful patch. Hint, hint!)

comments

No comments yet.

You must be logged in to add your own comment.