Differences between `any?` and `exists?` in Ruby on Rails?

12,422

Solution 1

#any and #exists? are very different beasts but query similarly.

Mainly, #any? accepts a block — and with this block it retrieves the records in the relation, calls #to_a, calls the block, and then hits it with Enumerable#any?. Without a block, it's the equivalent to !empty? and counts the records of the relation.

#exists? always queries the database and never relies on preloaded records, and sets a LIMIT of 1. It's much more performant vs #any?. #exists? also accepts an options param as conditions to apply as you can see in the docs.

Solution 2

The use of ActiveRecord#any? is reduced over ActiveRecord#exists?. With any? you can check, in the case of passing a block, if certain elements in that array matches the criteria. Similar to the Enumerable#any? but don't confuse them.

The ActiveRecord#any? implements the Enumerable#any? inside the logic of its definition, by converting the Relation accessed to an array in case a block has been passed to it and yields and access the block parameters to implement in a "hand-made" way a "Ruby" any? method.

The handy else added is intended to return the negation of empty? applied to the Relation. That's why you can check in both ways if a model has or no records in it, like:

User.count # 0
User.any?  # false
# SELECT  1 AS one FROM "users" LIMIT ?  [["LIMIT", 1]]
User.exists? # false
# SELECT  1 AS one FROM "users" LIMIT ?  [["LIMIT", 1]]

You could also check in the "any?" way, if some record attribute has a specific value:

Foo.any? { |foo| foo.title == 'foo' } # SELECT "posts".* FROM "posts"

Or to save "efficiency" by using exists? and improve your query and lines of code:

Foo.exists?(title: 'foo') # SELECT  1 AS one FROM "posts" WHERE "posts"."title" = ? LIMIT ?  [["title", "foo"], ["LIMIT", 1]]

ActiveRecord#exists? offers many implementations and is intended to work in a SQL level, rather than any?, that anyways will convert the Relation what you're working with in an array if you don't pass a block.

Solution 3

The answers here are all based on very outdated versions. This commit from 2016 / ActiveRecord 5.1 changes empty?, which is called by any? when no block is passed, to call exists? when not preloaded. So in vaguely-modern Rails, the only difference when no block is passed is a few extra method calls and negations, and ignoring preloaded results.

Solution 4

I ran into a practical issue: exists? forces a DB query while any? doesn't.

user = User.new
user.skills = [Skill.new]
user.skills.any?
# => true
user.skills.exists?
# => false

Consider having factories and a before_create hook:

class User < ActiveRecord::Base
  has_many :skills

  before_create :ensure_skills

  def ensure_skills
    # Don't want users without skills
    errors.add(:skills, :invalid) if !skills.exists?
  end
end


FactoryBot.define do
  factory :user do
    skills { [association(:skill)] }
  end
end

create(:user) will fail, because at the time of before_create skills are not yet persisted. Using .any? will solve this.

Share:
12,422
J3RN
Author by

J3RN

Web developer, coffee addict, connoisseur of text editors and programming languages.

Updated on July 15, 2022

Comments

  • J3RN
    J3RN almost 2 years

    In Ruby on Rails, there appear to be two methods to check whether a collection has any elements in it.

    Namely, they are ActiveRecord::FinderMethods’ exists? and ActiveRecord::Relation’s any?. Running these in a generic query (Foo.first.bars.exists? and Foo.first.bars.any?) generated equivalent SQL. Is there any reason to use one over the other?

  • Will
    Will over 4 years
    Thanks for this. I've found any? to be problematic as it can give the wrong answer and then break subsequent queries, eg to get the first record of the has_many as rails seems to remember the (wrong) result returned by any?. It's a shame because it reads nicely.
  • thisismydesign
    thisismydesign over 2 years
    Why is exists much more performant when it never relies on preloaded records, and AFAICT otherwise results in the same query as a non-block any?