Models on a diet – Part II
“Thin controller, fat model” – noooooo!!!
We probably all agree that “Thin controller, fat model” was a misconception. When “thin controller” sounds good, “fat model” is just a pain in the…let’s say – lower back ;)
Last week Ania Ślimak was talking about putting model on a diet. She presented a nice approach of reducing fat from models using validation factory. This solution is useful in many cases, but may not solve all the problems, so I wanted to continue this topic.
In the validation factory approach, validations are still stuck to the model and this responsibility is not fully extracted.
Validation rules and the model schema are like to change during their lifetime.
Every time you add/remove a rule or field, you may make existing records invalid in terms of the validation rules connected to the model. You have to maintain their validity, which is often unnecessary.
Validations are often contextual.
Sticking them with models may make using them in other places (e.g. another action or admin panel) is much harder and ends with adding plenty of ifs and unnecessary attributes describing the context.
The same issue happens with other stuff like sending emails in callbacks, which are often (unnecessarily) the model’s responsibilities.
The solution
Let’s not only move validations to the service object, but make model fully isolated from them and keep the data representation as its only job.
Validation service defines validations’ rules, takes a record as an argument and delegates it to the record. Some general logic is extracted to BaseValidator to be usable by other validation services:
And the happy model doesn’t know anything about validations:
But, where to actually call the validations? Model? – we said ‘no’ to that already. Controller? – well… we don’t need to put more responsibilities than handling the request and responding there.
So, let’s introduce another service object called Handler (Creator would a good name too), which sticks everything together:
What’s about the controller? It’s thin and happy as well:
Additionally, we could extract parameter sanitization logic to a service object and make controller even more clean.
Outcome
This way we have code:
- with reduced model responsibilities
- a way easier to maintain
- easy to (unit) test
- more OOP & closer to Single Responsibility Principle.