Dry Controllers with Ruby on Rails
For the past 17 weeks I’ve been on a pretty hefty Ruby on Rails project. The following is a combination of what we did to make our controllers drier on that project and some experimentation I did on some side projects.
Why do your controllers need to be dry? Well keep reading!
How You’re Probably Taught Poorly
This is how http://guides.rubyonrails.org/ encourages you to write controllers.
Chances are, this is how you have been writing controllers or at least how you have been taught early on.
So, this method is good and fine, until you need to use this logic in another controller or you need access to your user variables in other views. This happens in pretty much every Rails project ever. Also, all logic around interacting with your database is in the public interface. If another controller action needs to use the same CRUD operation, we would end up with duplicated code.
This style of development does not adapt very well to change and can make future development on your app costly and bloated. We can do better than this.
Moving Out Of The Public Interface
This is how a lot of our controller classes ended up looking for this project. This operates on the fact that most Rails controller methods require just a few default actions to work properly.
- finding all records
- finding a specific record to modify or delete
- initializing a new record
- saving a new record
We also assume that all views for a controller will require at least one user, or a blank user object.
This may look like more code, but we have done a few important thigns here.
- Adding a new action on this controller is just as simple as adding its name to one of the arrays in the blank, found, or created methods. We have sped up future development and increased our flexibility tremendously.
- The logic for interacting with your database is now separate from everything else. This will be important in the next section.
- By making the private methods helper methods, we no longer need to create instance variables all over our controllers. User and users, are now Rails helper methods and accessible directly from the view.
Still, this doesn’t solve the problem of needing to use users in other controllers.
Concerns are Awesome!
Concerns are Ruby modules that let us share methods between other models and controllers. The idea is, that common logic goes in that module, maybe called Userable, and now that controller knows how to deal with users. Luckily for us, we split out everything involving interacting with users into private methods already.
Now our code can look like this.
A Quick Word About “Userable” and Decorators
Userable might seem like an odd way to name a class, but think about what is actually happening here. It makes no sense to name the class like a noun when these methods can be used on hypothetically anything.
For example, dogs and cats being animals would be an example of classical inheritance, but things other than animals may be able to make noise. Cars, instruments, computers, etc, would all need to have a methods built around making noise. When a variety of unrelated or abstract things require certain functionality, we use a concern, which is more commonly known as using the decorator pattern.
We call the module Userable because when we include it, we are declaring that this class can do everything revolving around users, but is not a user itself.
A more clear example would be if you had a Flyable concern that had methods revolving around flight. This can then be used with Airplane or Bird like so.
Back to the example
All of this may not seem necessary, but let’s say we now want to make a home page that uses users. For this example, the index action will do nothing but display users in an unordered list. To make this work, this is all we need to do now.
Super easy right!? Any new page using users can now be built this way!
There was also a reason why we just changed the names of our found, create, and blank methods. Now, since they’re named specifically to use users, we can include other concerns in our controller. This let’s us work with multiple resources!
If we want to add something like events to that home page, this is all we have to do now, assuming we have an Eventable concern already built.
When you have clients that are still figuring out what exactly their app is supposed to do on a week by week basis, methods like these really help. All it takes is a single change in direction, when you don’t engineer for change, to have you cursing your code base for weeks on end. I’ve had good results with this method so far. Still, I keep working to keep the inevitable new feature request or shift in architecture from forcing me to take too many trips to the bar.