Rails / ActiveRecord count, size, and lengthPosted: August 5, 2021 | Author: ThomasPowell | Filed under: rails | Tags: activerecord, count, rails, ruby | 1 Comment »
When trying to be sensitive to
n+1 queries and memory usage, knowing the differences between
length in ActiveRecord is important. It had been a while since I reviewed the usage, and I wanted to ensure that I hadn’t made some bad assumptions along the way that somehow stuck. The reality is that each method is pretty close to indicating what it will do, with
size being the method that will load the data on (or for) you.
Back in the old days count was a more sophisticated member of
ActiveRecord::Calculations::ClassMethods module. You could pass conditions to the method, or column names… basically a combination
The column/distinct counting moved to
ActiveRecord::Calculations without all the extra conditionals, joins, and including. Note that you do not need a query to “count”:
irb(main):011:0> Model.count(:special_data) # count Model records with non-nil special_data (191.9ms) SELECT COUNT(`models`.`special_data`) FROM `models` => 41828 irb(main):012:0> Model.distinct.count(:special_data) # count Model records with DISTINCT non-nil special_data (17.6ms) SELECT COUNT(DISTINCT `models`.`special_data`) FROM `models` => 1909 irb(main):013:0> Model.count # count all records (3790.8ms) SELECT COUNT(*) FROM `models` => 594383
If you’re just looking for a count of records for a query that has not been loaded, that’s now a member of
irb(main):015:0> Model.all.count (744.2ms) SELECT COUNT(*) FROM `models` => 594383 irb(main):017:0> Model.where('special_data is not null').count (24.0ms) SELECT COUNT(*) FROM `models` WHERE (special_data is not null) => 41828
length will load all of the records indicated by a collection, which might be useful if calling length on an association that you’re going to use the data from anyway, but not if you are throwing that data away. You’ll be wasting time (and memory) on the operation.
irb(main):018:0> Model.where('special_data is not null').length Model Load (647.9ms) SELECT ... . . . => 41828
You also can’t call
length on a model’s class name, as it is not a collection itself:
irb(main):020:0> Model.length Traceback (most recent call last): 1: from (irb):20 NoMethodError (undefined method `length' for #<Class:0x00007f810ed6ec28>)
size also requires a collection, but does not attempt to load that collection, instead wrapping a
COUNT around its query:
irb(main):022:0> Model.where('special_data is not null').count (22.8ms) SELECT COUNT(*) FROM `models` WHERE (special_data is not null) => 41828
length, this doesn’t work:
irb(main):023:0> Model.size Traceback (most recent call last): 1: from (irb):20 NoMethodError (undefined method `size' for #<Class:0x00007f810ed6ec28>)
The behavior of these methods isn’t all that surprising, but sometimes we can let our guard down in Ruby and think of methods as synonyms when they actually have distinct behaviors. This is especially risky if you are working in more than one language or framework and might otherwise gravitate toward a method such as
length because it’s second nature elsewhere.