In my day job, I oversee a B2B app (Dynamix CCW — sorry; invite only) that tracks/facilitates all aspects of our business, and helps my engineering firm interface with clients and architectural and construction firms. It’s fairly complex, with many layers of business logic and account restrictions. RESTful_ACL and Scribd_Fu were extracted from this app in fact.

I’m a big fan of RSpec and testing in general (though strict BDD is frankly a bit much for my tastes), but I have to admit that I never wrote controller specs. The code was just too boring and boilerplate to write the same specs over and over again for 100+ controllers, so yeah, I just found other things to do and didn’t write them.

About a year ago, I was introduced to resource_controller during a weeklong visit with Hashrocket. resource_controller is a ruby gem written by James Golick and it aims to DRY up your app’s boilerplate controller code. You know the code; the boring crap that looks like this:


class CrapsController < ApplicationController
  # GET /craps
  # GET /craps.xml
  def index
    @craps = Crap.all
 
    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @craps }
    end
  end
 
  # GET /craps/1
  # GET /craps/1.xml
  def show
    @crap = Crap.find(params[:id])
 
    respond_to do |format|
      format.html # show.html.erb
      format.xml  { render :xml => @crap }
    end
  end
 
  # GET /craps/new
  # GET /craps/new.xml
  def new
    @crap = Crap.new
 
    respond_to do |format|
      format.html # new.html.erb
      format.xml  { render :xml => @crap }
    end
  end
 
  # GET /craps/1/edit
  def edit
    @crap = Crap.find(params[:id])
  end
 
  # POST /craps
  # POST /craps.xml
  def create
    @crap = Crap.new(params[:crap])
 
    respond_to do |format|
      if @crap.save
        flash[:notice] = 'Crap was successfully created.'
        format.html { redirect_to(@crap) }
        format.xml  { render :xml => @crap, :status => :created, :location => @crap }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @crap.errors, :status => :unprocessable_entity }
      end
    end
  end
 
  # PUT /craps/1
  # PUT /craps/1.xml
  def update
    @crap = Crap.find(params[:id])
 
    respond_to do |format|
      if @crap.update_attributes(params[:crap])
        flash[:notice] = 'Crap was successfully updated.'
        format.html { redirect_to(@crap) }
        format.xml  { head :ok }
      else
        format.html { render :action => "edit" }
        format.xml  { render :xml => @crap.errors, :status => :unprocessable_entity }
      end
    end
  end
 
  # DELETE /craps/1
  # DELETE /craps/1.xml
  def destroy
    @crap = Crap.find(params[:id])
    @crap.destroy
 
    respond_to do |format|
      format.html { redirect_to(craps_url) }
      format.xml  { head :ok }
    end
  end
end

By using resource_controller, you can have the functionality of this code by doing:


class CrapsControllers < ResourceController::Base
end

Awesome, eh? Of course, this is just the tip of the iceberg; resource_controller allows you to do far more than boilerplate RESTful resources.

During a recent lull in development, and in light of all these missing controller specs, I decided to finally implement resource_controller in my app. The refactoring was really quite painless, as was writing the specs for the remaining controller code. I found that most of my controllers simply redirected to non-standard locations after an object was modified (ie, the parent object, or the :index action instead of the :show action).

After devoting a few days worth of time to this refactoring, I had removed over 1000 lines of controller code. Check out the stats here:


Before:
 
+----------------------+-------+-------+---------+---------+-----+-------+
| Name                 | Lines |   LOC | Classes | Methods | M/C | LOC/M |
+----------------------+-------+-------+---------+---------+-----+-------+
| Controllers          |  5354 |  4064 |      74 |     466 |   6 |     6 |
| Helpers              |  1534 |  1206 |       0 |     121 |   0 |     7 |
| Models               |  9778 |  5168 |     117 |     934 |   7 |     3 |
| Libraries            |   209 |   116 |       1 |      16 |  16 |     5 |
| Model specs          | 11301 |  8770 |       0 |       5 |   0 |  1752 |
| View specs           | 19340 | 14929 |       0 |     295 |   0 |    48 |
| Controller specs     |  5760 |  4356 |       0 |      73 |   0 |    57 |
| Helper specs         |  2098 |  1488 |       0 |       0 |   0 |     0 |
+----------------------+-------+-------+---------+---------+-----+-------+
| Total                | 55374 | 40097 |     192 |    1910 |   9 |    18 |
+----------------------+-------+-------+---------+---------+-----+-------+
  Code LOC: 10554     Test LOC: 29543     Code to Test Ratio: 1:2.8
 

After:
 
+----------------------+-------+-------+---------+---------+-----+-------+
| Name                 | Lines |   LOC | Classes | Methods | M/C | LOC/M |
+----------------------+-------+-------+---------+---------+-----+-------+
| Controllers          |  4145 |  3062 |      74 |     242 |   3 |    10 |
| Helpers              |  1552 |  1220 |       0 |     124 |   0 |     7 |
| Models               |  9897 |  5266 |     117 |     951 |   8 |     3 |
| Libraries            |   209 |   116 |       1 |      16 |  16 |     5 |
| Model specs          | 11535 |  8915 |       0 |       5 |   0 |  1781 |
| View specs           | 19506 | 15059 |       0 |     299 |   0 |    48 |
| Controller specs     |  5711 |  4403 |       0 |     181 |   0 |    22 |
| Helper specs         |  2134 |  1518 |       0 |       0 |   0 |     0 |
+----------------------+-------+-------+---------+---------+-----+-------+
| Total                | 54689 | 39559 |     192 |    1818 |   9 |    19 |
+----------------------+-------+-------+---------+---------+-----+-------+
  Code LOC: 9664     Test LOC: 29895     Code to Test Ratio: 1:3.1

In fact, this week long refactoring was a giant win in many ways. Aside from trimming 25% of all controller lines of code, I also wrote 450+ controller unit tests and increased the overall code to test ratio by 30%.

Inspired by this progress, I decided to also move all inline RJS to their own templates. RJS is the red-headed step child of the Rails world, but it does the trick. This secondary refactoring removed a further 364 lines of code from my controllers.

Counting both refactorings, my controllers have lost exactly 36% of their overall weight. Not bad for a few days work!


+----------------------+-------+-------+---------+---------+-----+-------+
| Name                 | Lines |   LOC | Classes | Methods | M/C | LOC/M |
+----------------------+-------+-------+---------+---------+-----+-------+
| Controllers          |  3619 |  2600 |      74 |     244 |   3 |     8 |
| Helpers              |  1535 |  1209 |       0 |     123 |   0 |     7 |
| Models               |  9899 |  5268 |     117 |     951 |   8 |     3 |
| Libraries            |   209 |   116 |       1 |      16 |  16 |     5 |
| Unit tests           |    32 |    24 |       4 |       0 |   0 |     0 |
| Model specs          | 11548 |  8929 |       0 |       5 |   0 |  1783 |
| View specs           | 19649 | 15170 |       0 |     301 |   0 |    48 |
| Controller specs     |  5782 |  4462 |       0 |     181 |   0 |    22 |
| Helper specs         |  2157 |  1536 |       0 |       0 |   0 |     0 |
+----------------------+-------+-------+---------+---------+-----+-------+
| Total                | 54430 | 39314 |     196 |    1821 |   9 |    19 |
+----------------------+-------+-------+---------+---------+-----+-------+
  Code LOC: 9193     Test LOC: 30121     Code to Test Ratio: 1:3.3
Bryce Thorntonabout 1 year ago

I’ve been looking to try one of these types of plugins lately. Did you give make_resourceful a try? It seems like resource_controller is the most popular, but make_resourceful is by the guy who wrote HAML/SASS. There’s also inherited_resources, which can be used as a gem instead of a plugin. So many choices! I’m sure they’re all pretty much the same. Just curious if you’ve tried any others. I’ll probably give each one a quick spin to see what feels best.

Thanks for the post!

Matt Darbyabout 1 year ago

Actually, I didn’t try make_resourceful or inherited_resources, but I’d give anything Hampton wrote a fair shake!

Say your thing

Name:
Email:
URL:
$0.02: