You are following the “Fat Models, Skinny Controller” pattern in Rails. Your controllers being the HTTP endpoints are nice and clean. Short action methods and brief assignments. However, your models keep growing and growing, you are losing track of its responsibilities and you are trying to figure out why.
That is a problem. Rails makes you push all your domain code into the persistance layer. Avoiding the oh so dreaded “over-abstraction”, bloating a handful of ActiveRecord classes seems to be a better strategy. Also, the idea of three – and only three – possible places for your business logic makes it easy to justify the growing model: Controllers must be “skinny” and views shouldn’t contain logic, so… if not pushing into the “model”, where else should I put it?
To make it short: Stop thinking in database tables and relations and start modeling your domain with dedicated classes and instances – that is what object-orientation was made for!
What Is My Domain?
If your concern is “I want to export a series of chat messages to a JSON document” then write a new class
MessagesDocument. That is your domain. That is the problem you as the programmer are supposed to solve. So do it.
class MessagesDocument def to_json(messages) messages.collect do |msg| msg.to_json end.to_json end end
We all know that this simple example will soon get bloated with code lines since we want a little message filter here and a special formatting method for dates there. So, why would you put all this logic into the
Message class? It has nothing to do with your message domain at all. And, no, this ain’t no over-engineering, this reflects your concern of compiling a document.
— UPDATE: There was some discussion whether “compiling JSON” is part of “the” domain or not. Well, let’s presume we’re designing a document management tool for librarians (very interesting people, BTW!). In order to send archived files to other systems, one feature is an exporter to serialize documents to JSON – then this component is surely part of your domain.
And, The Persistance Layer?
Strictly speaking, your
Message class should be responsible only for synchronizing the model instance with the corresponding row in the database. It is a persistance layer. And this layer should be free of business code. I know, it is handy to simply push in a formatter method for the
created_at field – at the cost of an ever-growing class, thou. Think about it. How many methods could you move to dedicated new classes since those behaviours don’t need persistance?
But Isn’t That Over-Engineered?
If Rails and its legendary “model” layer would be good practice, I wouldn’t see dozens of projects all over the world with model files having 600 LOC each, including programmers complaining about too many responsibilites in their classes, starring at me, eyes wide open, blearily, asking where to put all that code. Rails does allow you additional assets, but the framework itself doesn’t answer those questions.
Your fat models are killing your software design. Do you know how hard it is to figure out the concern of a set of methods in a fat class? Of course you do! So why don’t you just generously push a set of logically associated methods into its own class? If that hurts too much you can still move it back into your all-mighty “model”.
What Do We Get From That?
Please don’t get me wrong – I don’t tell you to perfectly abstract your code “Java-like” (hate that comparison) into twohundredseventyfive classes. Relax, and refactor your code step by step. Parts that do not necessarily need the persistance should be separated. You will keep track of what happens where more easily with your refactorings and have faster tests that might encourage you to assert more edge cases.
In addition, that process makes you think about interfaces – the interaction of your new classes with your existing models. Interfaces are a good thing. Don’t fear the class. Hopefully, the next posts in this series can help. I want to talk about mass-assignment problems, lifecycle callbacks in ActiveRecord, form objects, and a lot more including ways to do that practically.
Several speakers were talking about the same problem: Unbloat your models in Rails and focus on your domain instead. “There’s something in the water.” – that was the conclusion of the LoneStar RubyConf 2012 last weekend, a fantastic conference in beautiful Austin, TX with even more fantastic people.