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

Don't miss out on my next essay

Sign up below to get my weekly Ruby column. I'll send you original articles and advice every Friday to help make you a smarter, better Ruby developer. Drop your name in the box!

Did you like this post? You should read these:

Comments