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






