How to return an empty ActiveRecord relation?
Ruby on-RailsActiverecordRelationRuby 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.