Controllers are supposed to be stupid. They’re just a connector between the model and the view layers, handling information about the request and send it where it needs to go.
This tutorial is open source. Please contribute fixes or additions to the markdown source on GitHub.
Get the Blogger project from GitHub and run setup procedures:
1 2 3 4 5
All existing tests should pass. Optionally, run the tests continuously while developing by running
Pushing Responsibility Down the Stack
In reality, most controllers do way more than simple request dispatch. They shepherd data, they react to many failure conditions, and they represent business logic. That’s bad.
Why is it bad? Because controllers trap logic. They’re more difficult to test than models, and the logic is almost impossible to reuse. Placing business logic inside a controller immediately violates the Single Responsibility Principlce.
So let’s look at ways to reduce complexity in the controller. One method is to push domain logic down into the model layer.
Limiting Logical Switching
The way many controllers get crazy is through parameter switching. "If this parameter is present, do one thing, otherwise, look for this other parameter, if it’s there, do another thing" and so on.
Just because Rails structures REST into seven methods doesn’t mean your controller is constrained to those. If you find an action growing beyond about 8 lines of code, use the extract method refactoring to pull it into another controller method.
But don’t stop there. This is a sign that you have logic to push down to the model. Don’t have an appropriate model? Make one! Maybe it’s a facade, maybe it’s more of a worker. Whatever the need, push that logic down to the model layer and activate it from the controller.
Then get rid of your custom method. As we’ve said, Rails doesn’t restrict your controllers to the seven RESTful ones. But it’s a darn good idea to strive for.
In the implementation of the
index action, we’ve pushed all the logic down to the model. In this case, we might want to further refactor the model, which might be too complex, but it’s good enough to illustrate the process we’re talking about.
In the controller, we have:
1 2 3
Our goal here is to find the articles to list by the provided tag name. Down in the model we’ve defined:
1 2 3 4 5 6 7 8
If the tag name passed is
nil or the empty string, the method will return
Article.scoped, which is a lazy query version of
Article.all. If a name is provided, it will find the tag with that name and return both the tag’s articles and the tag. If the name was not found, it will return an empty array and nil.
This isn’t the most complex logic ever, but testing it in the controller would be a bit of work. Check out
article_spec.rb to see how easy the unit tests are.
Good object-oriented design relies on loose coupling – having distinct roles and responsibilities tied together with as few strings as possible.
Rails controllers and Rails views are, according to the premise of MVC, distinct responsibilities. They should be separate objects who communicate by just a few well-defined connection points.
But that’s not how Rails works.
How do you get data from inside a controller action to the view template? Instance variables. Look at a familiar form of controller action inside a controller,
1 2 3 4 5 6 7
When Rails renders the view for the
show action, it copies instance variables from the controller into the rendered view, clearly coupling together those two components.
Any time you use an instance variable in Ruby, ask yourself: “Is there a better way?” The answer is almost always “yes.”
Limiting Instance Variables
A normal controller action is going to have one instance variable. Many actions will use two or three variables, but if you’re getting up above that it’s a sign that you’re missing a domain abstraction.
What is the essential "link" between these objects? Why do they all belong on the same page? Whatever the reason, that should probably be a domain object. Check out the Facade pattern.
A Better Interface
Can we do without instance variables all together? How do we normally get data from an object?
Frequently we use accessor methods. How do we write accessors for a controller? It’s actually quite easy. Let’s look again at this controller and action:
1 2 3 4 5 6 7
View Template Usage
In the view template, we access that object like this:
If we weren’t using an instance variable, what would we want it to look like? How about this:
How can we make that work? Rails is going to look for a helper method named
article. No problem.
Implementing a Helper Method
Let’s go back to the controller. We can add an
article method like this:
1 2 3 4 5 6 7 8 9 10 11
We don’t have to pass in
params to our new method, since it’s still in the controller context and can access the normal
params just as any action can.
Load the view and it won’t work. Just defining the method on the controller isn’t enough, we have to expose it to the view as a helper:
1 2 3 4 5 6 7 8 9 10 11 12 13
Now the view will work. But what about that
show action? If the view is accessing the
article helper, let’s get rid of the instance variable all together:
1 2 3 4 5 6 7 8 9 10 11 12
You can even remove the
show method, if you like. Change the references in the view template from
article and you’ll be good to go.
show was straightforward, what about the
1 2 3
In the view template we expect
@article to be our new, blank object. If we use the existing helper, it will try to do a lookup based on
No problem. Just add logic that reacts to
params[:id] in the helper method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
We eliminate the code from the
new method and change the view template to use the helper instead of the instance variable.
Whoah, did you notice the console log when looking at the
show view? If you haven’t, take a look. It seems we’re kicking off a bunch of queries, one for each call to our helper.
Not a problem: we’ll use an instance variable to memoize the object after the first request.
Memoization is an optimization technique that avoids expensive recomputation by storing the result of an operation. It is related to caching.
Here’s an example:
1 2 3 4 5 6 7 8 9 10 11
Why is the variable called
@cached_article? I don’t want to use
@article, or someone will come along and start using the instance variable in the views. If you want to talk to my article, you should use the accessor method I’ve defined and I want to strongly encourage you to do that.
But maybe you want to make the code even cleaner. You see lookup logic and you want to press it down to the model. Great idea! You could build something like this:
1 2 3 4 5 6 7 8 9 10
But that’s unnecessary. You can take advantage of the built in
1 2 3 4 5 6 7
find_or_initialize_by method will return the record with the given id if it’s found and otherwise will return a new instance of the class, which in this case is
Some people will choose to wrap that finder with a method in their model to hide `ActiveRecord`, but I believe that’s silly for such a simple case.
- Rewrite the
editaction and view so they use the
- Rewrite the
destroyaction so it uses the method
updateto minimize the code repetition and instance variables
updateis not too tricky because you can call
.update_attributeson the return value of
create, using the same style as
updatebut, instead of calling
.attributes=which does not trigger the save implicitly. For example:
2.1.1 :001> 2.1.1 :002> 2.1.1 :003>
- Try integrating this approach with the decorator pattern. It’s probably best to write a wrapper method in the
Articlemodel that’s similar to
find_or_initialize_by_id, but handles the decoration.
By adding just a little bit of code to your controller, you have:
- Eliminated a lot of duplicated logic
- Cut down the total code in your controller
- Improved the interface between controller and view template
Should you rewrite this in every controller? Probably not! What would it take to write more general forms which could be reused across controllers? You would probably define a method that’s called from each controller which generates the helper methods on the fly.
That’s exactly what Basic Assumption (https://github.com/mattyoho/basic_assumption) does. It’s a very helpful but simple gem I strongly recommend you try out.