Published on May 17, 2013 21:00
I started doing some real rails work back in the days when the mantra sounded "slim controllers, fat models". But I realized that the reason that we had that mantra, was there where a lot of logic going on in the controller layer, which didn't belong there. Much of this was moved to the models, and rightly so. But this presented another problem — for me at least — keeping state, and validating the right things, based on the context that the record was being manipulated in.
An example where I use this is creating a user. In the application I'm building at the moment, the initial creation of the user is as an invite, after which the user needs to complete his profile afterwards. This of course calls for different validation in the different states, in which the user object is in the process. To achieve this, I could make an advanced state machine out of the user object, to keep track of the state it's in, and which validations to do on the user object at the given state.
I found Virtus, which can make any old ruby object quack like an active record object. This allows you to make classes for a very specific contexts. So you could make a sign-up object, a sign-up-complet object, which each validate something different. The sign-up object — which is the invitation in the example above — should validate that an email, a name and an account (to which the user should be attached), but it should validate for no more. The completion object could validate that a password was entered, and that the user had filled out various other bits of information. So the user object, when saved to the database is always valid, in comparison to the context which last manipulated it.
Another use for the contextual classes are signing in. So instead of having the logic of signing in, in the controller layer, you could have a simple ruby class do that for you.
class SignIn include Virtus extend ActiveModel::Naming include ActiveModel::Conversion include ActiveModel::Validations attr_reader :user attribute :password, String attribute :email, String validates :email, presence: true validates :password, presence: true def persisted? false end def save if valid? persist! true else false end end private def persist! # stor user session or what not, to keep track of his sign in end end
Some of the clear benefits of this type of responsibility delegation is that each of these simple classes are easy to test in separation from the rest of the application, without having to mock or stub a whole lot. And as you go along — because the classes are simple and small — they are easier to refactor and extract even simpler objects from.
Lastly, I just want to touch on a design philosophy about object oriented programming, which is: Don't be afraid to have a large number of objects. Be more afraid to have large, complex objects.
Additionally, if you're having problems reaching a state, where you can do this type of extraction and refactoring, I encourage you to read Bryan Helmkamp's great blogpost: 7 patterns to refactor fat models