Reputation: 3102
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
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
Reputation: 6726
There is a now a "correct" mechanism in Rails 4:
>> Model.none
=> #<ActiveRecord::Relation []>
Upvotes: 567
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
Reputation: 109
There are also variants, but all of these are making request to db
where('false')
where('null')
Upvotes: 2
Reputation: 125882
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
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
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
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
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
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