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:

1
2
3
4
5
class User < ActiveRecord::Base
  def award_points(points)
    Redis.current.zincrby("leaderboard", points, id)
  end
end

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:

app/models/leaderboard.rb
1
2
3
4
5
class Leaderboard
  def award_points_to_user(user_id, points)
    Redis.current.zincrby("leaderboard", points, user_id)
  end
end
app/models/user.rb
1
2
3
4
5
class User < ActiveRecord::Base
  def award_points(leaderboard, points)
    leaderboard.award_points_to_user(id, points)
  end
end

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

Did you like this post? You should read these:

Finished another Rails tutorial and still don’t know how to start?

Have you slogged through the same guide three times and still can't retain enough to write apps on your own?

In my free 7-part course, you’ll discover the fastest way to learn and remember new Rails ideas, so you can use them when you need them. And you'll learn to use what you already know to build your own Rails project.



Comments