tail -f judofyr

2013-08-21

cat 19:00:34 UTC
Structuring web apps in Ruby

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]

  def index
    @posts = Post.all
  end

  def show
  end

  def edit
  end

  def create
    @post = Post.create(params[:post])
  end

  def update
    @post.update(params[:post])
  end

  def destroy
    @post.destroy
  end
end
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.

cat 19:33:51 UTC

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
    the match.

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?

Solution 1:

before_filter :find_players_for_new_match, :only => [:new, :create]

def new
  ...
end

def create
  ...
end

before_filter :find_match_for_dispute, :only => [:show_dispute, :dispute]

def show_dispute
  ...
end

def dispute
  ...
end
Solution 2:
class RegisterMatch
  before_filter :find_players

  def new
    ...
  end

  def create
    ...
  end
end

class DisputeMatch
  before_filter :find_match

  def show_dispute
    ...
  end

  def dispute
    ...
  end
end
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 definition.