Over the holidays I experimented with an old question I had. "How to find the places in codebase where .present? or .any? is called on a collection?". The answer is important because calling those methods instead of .exists? forces the records in the collection to get loaded from DB into Ruby memory which, depending on the size of the collection, can be a huge performance hit.
After looking into Rails' source, I came up with a monkeypatch that only touches method definitions on ActiveRecord collections. The module that needs prepending is ActiveRecord::Relation
.
# in config/application.rb
module CollectionQueryingSafety
def present?
raise_warning_about_performance_problems(__method__)
end
def blank?
raise_warning_about_performance_problems(__method__)
end
private
def raise_warning_about_performance_problems(method_name)
raise(
".#{ method_name } called on an AR collection. "\
"See if .exists? can be used instead."
)
end
end
unless Rails.env.production?
ActiveRecord::Relation.prepend(CollectionQueryingSafety)
end
This snippet will raise errors in non-production environments. Given decent coverage, running your test suite should show most places where suboptimal calls occur.
For example, there could be offensive code like this:
# bad
@my_model.some_has_many_association.present?
# good
@my_model.some_has_many_association.exists?
Now, besides .present?
and .blank?
there are other array methods - .any?
, .none?
, and .many?
- which in most cases should not be called on collections.
Unfortunately, since these methods accept blocks, I did not feel confident overriding them outright, but in the cases where no block is passed to .any?
, .exists?
is a much faster and correct alternative.