67
votes

I feel this should be very simple but my brain is short-circuiting on it. If I have an object representing the current user, and want to query for all users except the current user, how can I do this, taking into account that the current user can sometimes be nil?

This is what I am doing right now:

def index
  @users = User.all
  @users.delete current_user
end

What I don't like is that I am doing post-processing on the query result. Besides feeling a little wrong, I don't think this will work nicely if I convert the query over to be run with will_paginate. Any suggestions for how to do this with a query? Thanks.

11
In my opinion when I query for something I should let the part of my application optimized for querying handle that: the database. So post-processing the results of a query to further filter it, to me, is putting the logic in the wrong place. - SingleShot

11 Answers

160
votes

It is possible to do the following in Rails 4 and up:

User.where.not(id: id)

You can wrap it in a nice scope.

scope :all_except, ->(user) { where.not(id: user) }
@users = User.all_except(current_user)

Or use a class method if you prefer:

def self.all_except(user)
  where.not(id: user)
end

Both methods will return an AR relation object. This means you can chain method calls:

@users = User.all_except(current_user).paginate

You can exclude any number of users because where() also accepts an array.

@users = User.all_except([1,2,3])

For example:

@users = User.all_except(User.unverified)

And even through other associations:

class Post < ActiveRecord::Base
  has_many :comments
  has_many :commenters, -> { uniq }, through: :comments
end

@commenters = @post.commenters.all_except(@post.author)

See where.not() in the API Docs.

32
votes
@users = (current_user.blank? ? User.all : User.find(:all, :conditions => ["id != ?", current_user.id]))
16
votes

You can also create named_scope, e.g. in your model:

named_scope :without_user, lambda{|user| user ? {:conditions => ["id != ?", user.id]} : {} }

and in controller:

def index
  @users = User.without_user(current_user).paginate
end

This scope will return all users when called with nil and all users except given in param in other case. The advantage of this solution is that you are free to chain this call with other named scopes or will_paginate paginate method.

7
votes

Here is a shorter version:

User.all :conditions => (current_user ? ["id != ?", current_user.id] : [])
6
votes

One note on GhandaL's answer - at least in Rails 3, it's worth modifying to

scope :without_user, lambda{|user| user ? {:conditions => ["users.id != ?", user.id]} : {} }

(the primary change here is from 'id != ...' to 'users.id !=...'; also scope instead of named_scope for Rails 3)

The original version works fine when simply scoping the Users table. When applying the scope to an association (e.g. team.members.without_user(current_user).... ), this change was required to clarify which table we're using for the id comparison. I saw a SQL error (using SQLite) without it.

Apologies for the separate answer...i don't yet have the reputation to comment directly on GhandaL's answer.

3
votes

Very easy solution I used

@users = User.all.where("id != ?", current_user.id)
1
votes

User.all.where("id NOT IN(?)", current_user.id) will through exception undefined method where for #<Array:0x0000000aef08f8>

User.where("id NOT IN (?)", current_user.id)
0
votes

Another easy way you could do it:

@users = User.all.where("id NOT IN(?)", current_user.id)
0
votes

an array would be more helpful

arrayID[0]=1

arrayID[1]=3

User.where.not(id: arrayID)

0
votes

User.where(:id.ne=> current_user.id)

-5
votes

What you are doing is deleting the current_user from the @users Array. This won't work since there isn't a delete method for arrays. What you probably want to do is this

def index
  @users = User.all
  @users - [current_user]
end

This will return a copy of the @users array, but with the current_user object removed (it it was contained in the array in the first place.

Note: This may not work if array subtraction is based on exact matches of objects and not the content. But it worked with strings when I tried it. Remember to enclose current_user in [] to force it into an Array.