While you’re writing your Rails app, you might run into a problem that you could solve more easily with a different data store. For example, you might have a leaderboard that ranks users by the number of points they got for answering questions on your site. With Redis Sorted Sets, a lot of the implementation is done for you. Awesome! But where do you put the code that interacts with Redis?
User model could talk to Redis:
But now your
User model holds responsibility for representing a
user, talking to Redis, and managing the leaderboard! This makes
User harder to understand and harder to test, and that’s the
opposite of what you want.
Instead, you could wrap the Redis requests in a new object that
represents what you’re using Redis for. For example, you could
Leaderboard class that wraps the Redis communication,
doesn’t inherit from
ActiveRecord::Base, but still lives in the
app/models directory. This would look like:
Both of these classes can live in
app/models, but now you’re not
contaminating your ActiveRecord models with extra logic. The
Leaderboard class manages the communication with Redis, so the
User class no longer has to care about how leaderboards are
also makes it easier to test.
You gain a lot by creating new classes for new responsibilities, and
app/models is a great place to keep them. You get the benefits of
leaning on another service for your feature’s implementation, while
also keeping your code easy to work with.
Try it out
Can you think of any network service communication that’s called directly from your ActiveRecord models, controllers, or views? Try moving that code into a non-ActiveRecord data model, and see if your code becomes easier to understand, work with, and test. Then, send me an email and let me know how it went!
All problems in computer science can be solved by another level of indirection.
— David Wheeler
…except for the problem of too many layers of indirection.
— Kevlin Henney