Lean models with service objects
In this post I want to describe a pattern I use a lot these days. Let me give you some background first.
While developing Rails applications it is quite common to add a lot of code into models.
For example my User class might have an instance method 'stats'. These method could do a lot of work and call many other helper methods on the way. You will often end up with models with lots of code on them.
Even worst will be the tests for that model. You will end up with a test file that covers a huge number of methods and could end up very hard to maintain.
Enter service objects
A nice way to avoid having all this logic in your model is to put your code in a different class. For example GetStatsForUserService. Then you would:
serv = GetStatsForUserService.new(user) stats = serv.run
The neat thing of service objects is that your test will be very focused and easy to maintain.
Now the problem is that service objects are not very discoverable, some other developer might not realise that there is a way to get stats for a user just by looking at the user class.
Wrap your service objects in an object oriented API
To deal with discoverability a simple thing to do is to just wrap the service object inside a method in the User class itself. e.g.
class User def get_stats_service @get_stats_service ||= GetStatsForUserService.new(self) end def stats get_stats_service.run end end
In this way you will be able to do user.stats
and get the benefits of an OO approach and service objects all together.
Another benefit if that you can easy stub expensive methods in your tests. E.g.
user.stub(get_stats_service).and_return(double.as_null_object)
Then you don't have to worry about your test calling a chain of expensive processes in case something happens to call user.stats
.
In conclusion
- Move expensive methods to service objects
- Make focused test for those service objects
In your models:
- Wrap those service objects in class/instance methods
- In your model test just check that the wrapper method delegates to the service object, don't test the logic in the service object itself (this is already tested in the service object tests).
- In most tests just stub the reference to the service object to return something expected.
Popular Articles by Our Team
Our expert team of designers and developers love what the do and enjoy sharing their knowledge with the world.
-
The Benefits of Ruby on Rails
-
Heroku Forcing Password Resets as GitHub Investigation Continues
-
How to Maximise your ROI with a Custom Application Built in Sa...
We Hire Only the Best
reinteractive is Australia’s largest dedicated Ruby on Rails development company. We don’t cut corners and we know what we are doing.
We are an organisation made up of amazing individuals and we take pride in our team. We are 100% remote work enabling us to choose the best talent no matter which part of the country they live in. reinteractive is dedicated to making it a great place for any developer to work.
Free Community Workshops
We created the Ruby on Rails InstallFest and Ruby on Rails Development Hub to help introduce new people to software development and to help existing developers hone their skills. These workshops provide invaluable mentorship to train developers, addressing key skills shortages in the industry. Software development is a great career choice for all ages and these events help you get started and skilled up.