Lately I've been thinking about how we structure web applications with
controllers. Let's use Rails as an example:
class PostsController < ApplicationController
before_action :set_post, only: [:show, :edit, :update, :destroy]
@posts = Post.all
@post = Post.create(params[:post])
This looks simple now, but in real applications it becomes messy when
you need more granularity in the actions. For instance, you often want
index/show to be public, but edit/create/update/destroy should only be
available to the owner of the post. There might be multiple actions to
view or edit an post, and these might want to share helper methods.
Having all of these different contexts in the same controller can be
confusing. You need to wire up all of the before filters correctly, and
it's easy to miss one. All of the helper methods (inside the controller)
and the helpers (inside the helpers file) are available to *all* the
actions, although you only need it for some specific actions.
Let me show you an example from an application I'm working on.
We have the following actions:
matches#index - Dashboard about the recent matches
matches#for_day - All the matches played on a single day
matches#new - A page for selecting the loser you played against
matches#create - A POST action that registers the match
matches#show_dispute - A page where you can click "Dispute this match"
matches#dispute - A POST action for disputing a match
And the following "constraints":
- #index and #for_day is public.
- The templates for #index and #for_day re-uses the logic for
rendering a match.
- #new and #create requires you to be a member of the league.
- #new and #create need to parse the query params to find the
winners/losers of the match.
- #show_dispute and #dispute requires you to be one of the players of
This is a pretty simple controller, but Rails doesn't really have a way
to structure it. I could move the dispute-logic into a separate
controller (and have disputes#new and disputes#create), but I don't want
to move it to a different file. There's really no way to separate
index/for_day and new/create.
(You might wonder: Why do I want to separate the logic, but not have it
in a separate file? Guess what, Ruby supports multiple classes in the
same file. It's completely legitimate.)
Or you might wonder: Why do I want to separate the logic at all? Because
it makes it easier to reason about before filters. Which of these two
are easiest to understand and less likely to break during a refactoring?
before_filter :find_players_for_new_match, :only => [:new, :create]
before_filter :find_match_for_dispute, :only => [:show_dispute, :dispute]
It's much easier to see that #find_match is invoked for show_dispute and
dispute when it's nested. Instead of parsing a flat structure for
before_filters that includes :only, you just look at the class