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?
Your 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
create a 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
implemented. This
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!
Just remember:
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