How to return an empty ActiveRecord relation?

Ruby on-RailsActiverecordRelation

Ruby on-Rails Problem Overview


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.

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

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

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

Solution 2 - Ruby on-Rails

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.

Solution 3 - Ruby on-Rails

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.

Solution 4 - Ruby on-Rails

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.

Solution 5 - Ruby on-Rails

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')

Solution 6 - Ruby on-Rails

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) }

Solution 7 - Ruby on-Rails

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)) }

Solution 8 - Ruby on-Rails

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

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

Solution 9 - Ruby on-Rails

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiondzajicView Question on Stackoverflow
Solution 1 - Ruby on-Railssteveh7View Answer on Stackoverflow
Solution 2 - Ruby on-Railssteveh7View Answer on Stackoverflow
Solution 3 - Ruby on-RailsNathan LongView Answer on Stackoverflow
Solution 4 - Ruby on-RailsBrandonView Answer on Stackoverflow
Solution 5 - Ruby on-RailsbbrinckView Answer on Stackoverflow
Solution 6 - Ruby on-RailsAlexView Answer on Stackoverflow
Solution 7 - Ruby on-RailsPan ThomakosView Answer on Stackoverflow
Solution 8 - Ruby on-RailsfmnoiseView Answer on Stackoverflow
Solution 9 - Ruby on-RailsilgamView Answer on Stackoverflow