Christoph Gockel

Counting Rails Associations

22 Aug 2014

With Rails and ActiveRecord it’s relatively easy to forget that you’re interacting with a database in the background. There are many convenience methods available to help a developer “focussing on the important things”.

Unfortunately these conveniences introduced by ActiveRecord often come with a cost – if you’re not aware of what is going on behind the scenes.

For this example I’m using ActiveRecord associations in a blogging application. There’s one table Users and one Posts.

To get the number of post of a user, ActiveRecord provides three ways to get the result.

Users.comments.count
Users.comments.length
Users.comments.size

They all return the same result but the way they are getting the result differ from each other.

Calling .count on the association triggers an execution of a SELECT COUNT... statement on the database.

Calling .length falls back to the implementation on a collection of things (like Array#length). Which means that all comments are loaded/read and then the resulting collection is asked about its .length.

When using .size the implementation used depends on whether all entries of that association have been requested before (e.g. with a call to .length or .all). If this is the case, that value will be used as the return value of .size – it will not trigger a new call to .length. If not, it will call .count.

At a first glance I thought .count would be the preferable way no matter what. A simple SELECT COUNT... will probably always be more performant than counting the elements of a list that needs to be loaded from a database.

After I had a second look though, I realised that it’s not that simple.

For cases where ActiveRecord knows the number of elements in an association already, calling .size will probably be faster than issuing a new statement to the database. Because it will just return an “already known” value.

Currently .size looks like a good tradeoff to me, because it gives you the best of both worlds. When the association has already been read completely, it uses the value that is already known. If not, a SELECT COUNT... will be executed to get the size (i.e. the count) of the association.