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.
Then you don't have to worry about your test calling a chain of expensive processes in case something happens to call
- 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.
Five UX Tips for Better Dashboards
Dissecting Code With Ruby's caller Method
Handling deletes with Null Object Pattern in Ruby
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.
Webinars are our online portal for tips, tricks and lessons learned in everything we do. Make the most of this free resource to help you become a better developer.
The Ruby on Rails Installfest includes a full setup of your development environment and step-by-step instructions on how to build your first app hosted on Heroku. Over 1,800 attendees to date and counting.
The Ruby on Rails Development Hub is a monthly event where you will get the chance to spend time with our team and others in the community to improve and hone your Ruby on Rails skills.