Over the past year, I have learned to split up my super fat ActiveRecord models into smaller plain Ruby classes. In this post I list down the some guidelines that I follow when I decide to extract code from a model into a plain Ruby class.
You end up not running tests when you have test suites that take too long to run. When you skip running tests, you will be more likely to break something, or checking Twitter while your tests run, just to forget to get back to work 10 minutes later.
Furthermore, code becomes difficult to maintain with huge classes. The class ends up doing too much.
Having a large model that does too much starts to become difficult to test. Throw in some callbacks, and you’re in for a slow test suite, because you will most likely be forced to create objects in the database just to trigger all the necessary callbacks. I have had my fair share of writing test suites that take 10-15 to run. In my opinion, unit tests should take less than 10 seconds to run. Integration tests, however, could (not should) take longer.
A Solution: Extract Class
By extracting methods from your model to other classes, you will get the benefits of having code that answers the problems above. They will be easier to test, and not to mention faster.
When I see a pattern in the methods I add to a model, I ask myself: what class can I put these methods?
As I place these methods into some external class, I try to make sure that the name of the class I am dumping these methods into properly describes that all the methods do. If there is a method that does something else, it probably belongs to its own class.
When I find myself wanting to test private methods, then it probably belongs to its own class as a public interface of that class.
A talk entitled The Deep Synergy Between Testability and Good Design by Michael Feathers shows a good example of extract class and testing private methods.
If you have been diligent with your podcasts and blogs, you’ll notice a lot of parallelisms with the (good) trend Plain Old Ruby Objects.
A lot of the links in this blog post I picked up from this excellent podcast from Ruby Rogues: Practical Object-Oriented Design in Ruby with Sandi Metz.
Tip: Let the instance do the work
The instances should do the work to avoid leakage of variables. Stop using class methods to do the work. Let the instance of the class do the work:
You might argue that doing so will make calls to that class so much longer. Add this to the class as a convenience method:
RuleTokenizer.new(:some => "args").tokenize
so that you may call
def self.tokenize(args) self.new(args).tokenize end
RuleTokenizer.tokenize(:some => "args")
I want to hear from you folks: what did I miss? Has there been anything that has been particularly helpful when extracting methods into classes?