labs

Dumb Controllers, Layered Models

“I like to make my views so dumb, there’s no reason to test them.”

Uncle Bob Martin said that at some point, or something very close to it. I’d like to take that a step further: I like to make my (Rails) controllers so dumb, there’s no reason to test them (unless there’s no higher level acceptance test that would exercise the actions in them).

Complicated controllers are painful. Each controller action is like a mini main() function. That’s a lot main()s. The more each action knows about your underlying application, the more brittle your application gets. Think of an action as a launching point into your business domain.

I have some rules (that I occasionally break). It goes something like this:

1) Controllers should be RESTful. No custom actions. new, create, update, destroy, edit, show. That’s it.
2) Controllers should manage a single resource. If you’re instantiating more than a single object in your controller, you’re probably going to regret it.

There’s some great gems out there that can support this restrictive approach to Rails development:

1) Responders (https://github.com/plataformatec/responders). Responders are built into Rails, but the FlashResponder in the responders gem is essential. Replace all of your tedious flash message management code with defaults that can be overriden in your localization files. You can create your own responders to replace any tedious bookkeeping in your controllers.

2) Informal::Model (https://github.com/joshsusser/informal). Since you’re limited to instantiating a single object in your controllers, it’s likely that you won’t get by with just ActiveRecord models. You’ll need to create higher level models that can coordinate the work of all your lower level database models (and action mailers, etc.). Heads up, Rails 4 will obsolete this gem with ActiveModel::Model.

3) ActiveModel::Serializer (https://github.com/rails-api/active_model_serializers). This only applies if you’re creating a JSON API. But if you are, consider this gem. It’s convention over configuration for your API. It works with responders and makes all of the JSON format choices for you so that you can focus on more important things.

I’ll leave with you this:

= simple_form_for @widget_search do |f|
  = f.input :query
  = f.submit

= render @widget_search.results
class WidgetSearchController
  def new
    @widget_search = WidgetSearch.new
  end

  def create
    @widget_search = WidgetSearch.create(query: request.query_parameters)
  end
end
class WidgetSearch
  include Informal::Model

  attr_accessor :query

  validates_presence_of :query

  def self.create(options={})
    new(options).tap &:save
  end

  def save
    return false unless valid?
    @results = Widget.periscope query
  end

  def results
    @results || []
  end
end

You might have noticed a “periscope” method in there. Yet another fantastic gem.