dzajic
dzajic

Reputation: 3102

How to return an empty ActiveRecord relation?

If I have a scope with a lambda and it takes an argument, depending on the value of the argument, I might know that there will not be any matches, but I still want to return a relation, not an empty array:

scope :for_users, lambda { |users| users.any? ? where("user_id IN (?)", users.map(&:id).join(',')) : [] }

What I really want is a "none" method, the opposite of "all", that returns a relation that can still be chained, but results in the query being short-circuited.

Upvotes: 289

Views: 95337

Answers (10)

Andres Rosales
Andres Rosales

Reputation: 151

Looking at the docs:

(As of Rails 4.0.2) Calling .none on any ActiveRecord::Relation will return a chainable relation with zero records.

Upvotes: 0

steveh7
steveh7

Reputation: 6726

There is a now a "correct" mechanism in Rails 4:

>> Model.none 
=> #<ActiveRecord::Relation []>

Upvotes: 567

ilgam
ilgam

Reputation: 4420

It is possible and so that's:

scope :for_users, lambda { |users| users.any? ? where("user_id IN (?)", users.map(&:id).join(',')) : User.none }

http://apidock.com/rails/v4.0.2/ActiveRecord/QueryMethods/none

Correct me if I'm wrong.

Upvotes: 2

fmnoise
fmnoise

Reputation: 109

There are also variants, but all of these are making request to db

where('false')
where('null')

Upvotes: 2

Nathan Long
Nathan Long

Reputation: 125882

Coming in Rails 4

In Rails 4, a chainable ActiveRecord::NullRelation will be returned from calls like Post.none.

Neither it, nor chained methods, will generate queries to the database.

According to the comments:

The returned ActiveRecord::NullRelation inherits from Relation and implements the Null Object pattern. It is an object with defined null behavior and always returns an empty array of records without quering the database.

See the source code.

Upvotes: 46

bbrinck
bbrinck

Reputation: 416

scope :none, limit(0)

Is a dangerous solution because your scope might be chained upon.

User.none.first

will return the first user. It's safer to use

scope :none, where('1 = 0')

Upvotes: 26

Alex
Alex

Reputation: 4190

I think I prefer the way this looks to the other options:

scope :none, limit(0)

Leading to something like this:

scope :users, lambda { |ids| ids.present? ? where("user_id IN (?)", ids) : limit(0) }

Upvotes: 15

steveh7
steveh7

Reputation: 6726

A more portable solution that doesn't require an "id" column and doesn't assume there won't be a row with an id of 0:

scope :none, where("1 = 0")

I'm still looking for a more "correct" way.

Upvotes: 76

Brandon
Brandon

Reputation: 2604

You can add a scope called "none":

scope :none, where(:id => nil).where("id IS NOT ?", nil)

That will give you an empty ActiveRecord::Relation

You could also add it to ActiveRecord::Base in an initializer (if you want):

class ActiveRecord::Base
 def self.none
   where(arel_table[:id].eq(nil).and(arel_table[:id].not_eq(nil)))
 end
end

Plenty of ways to get something like this, but certainly not the best thing to keep in a code base. I have used the scope :none when refactoring and finding that I need to guarantee an empty ActiveRecord::Relation for a short time.

Upvotes: 43

Pan Thomakos
Pan Thomakos

Reputation: 34340

Use scoped:

scope :for_users, lambda { |users| users.any? ? where("user_id IN (?)", users.map(&:id).join(',')) : scoped }

But, you can also simplify your code with:

scope :for_users, lambda { |users| where(:user_id => users.map(&:id)) if users.any? }

If you want an empty result, use this (remove the if condition):

scope :for_users, lambda { |users| where(:user_id => users.map(&:id)) }

Upvotes: 3

Related Questions