158

When I have array of ids, like

ids = [2,3,5]

and I perform

Comment.find(ids)

everything works fine. But when there is id that doesn't exist, I get an exception. This occurs generaly when I get list of IDs that match some filter and than I do something like

current_user.comments.find(ids)

This time I may have a valid comment ID, which however does not belong to given User, so it is not found and I get an exception.

I've tried find(:all, ids), but it returns all of the records.

The only way I can do it now is

current_user.comments.select { |c| ids.include?(c.id) }

But that seems to me like super inefficient solution.

Is there better way to select ID in Array without getting exception on non-existing record?

Jakub Arnold
  • 85,596
  • 89
  • 230
  • 327

6 Answers6

251

If it is just avoiding the exception you are worried about, the "find_all_by.." family of functions works without throwing exceptions.

Comment.find_all_by_id([2, 3, 5])

will work even if some of the ids don't exist. This works in the

user.comments.find_all_by_id(potentially_nonexistent_ids)

case as well.

Update: Rails 4

Comment.where(id: [2, 3, 5])
Snowman
  • 31,411
  • 46
  • 180
  • 303
prismofeverything
  • 8,799
  • 8
  • 38
  • 53
  • this is my preferred solution, it seems cleaner than the exception handling route – Sam Saffron Sep 17 '09 at 23:25
  • 7
    As another extension to this, should you need to chain complex conditions, you could even do Comment.all(:conditions => ["approved and id in (?)", [1,2,3]]) – Omar Qureshi Sep 18 '09 at 08:23
  • 14
    this will be deprecated in Rails 4: http://edgeguides.rubyonrails.org/4_0_release_notes.html#active-record-deprecations – Jonathan Lin Jan 14 '13 at 03:21
  • 3
    @JonathanLin is correct, mjnissim's answer should be preferred: http://stackoverflow.com/a/11457025/33226 – Gavin Miller Jul 31 '13 at 22:00
  • 8
    This returns an `Array` instead of an `ActiveRecord::Relation`, which limits what you can do with it afterwards. `Comment.where(id: [2, 3, 5])` does return an `ActiveRecord::Relation`. – Joshua Pinter Jun 26 '14 at 17:51
  • this is deprecated but Comment.where(id: [1,2,3,4,5,6,7]) does the job – Stef Hej Jun 20 '16 at 14:49
  • thank you - how to protect from sql injection - how can one escape the array? – BenKoshy Sep 22 '16 at 05:37
151

Update: This answer is more relevant for Rails 4.x

Do this:

current_user.comments.where(:id=>[123,"456","Michael Jackson"])

The stronger side of this approach is that it returns a Relation object, to which you can join more .where clauses, .limit clauses, etc., which is very helpful. It also allows non-existent IDs without throwing exceptions.

The newer Ruby syntax would be:

current_user.comments.where(id: [123, "456", "Michael Jackson"])
mjnissim
  • 3,102
  • 1
  • 19
  • 22
  • Thanks for confirming the `where` syntax when comparing to an array. I thought I might have to code the SQL with an `IN` statement, but this looks cleaner and is an easy replacement for the deprecated `scoped_by_id`. – Mark Berry Jun 08 '15 at 17:49
  • 1
    What is this called and how does it work? Is it Rails magic?! As a colleague commented, its like 'comparing an integer with an object list". – atw Aug 25 '15 at 14:57
29

If you need more control (perhaps you need to state the table name) you can also do the following:

Model.joins(:another_model_table_name)
  .where('another_model_table_name.id IN (?)', your_id_array)
Joshua Pinter
  • 45,245
  • 23
  • 243
  • 245
Jonathan Lin
  • 19,922
  • 7
  • 69
  • 65
  • Is there any way to keep the order of the `your_id_array` when you get the objects back? – Joshua Pinter Sep 09 '15 at 18:16
  • @JoshPinter I don't think this is a reliable way to expect the database to return things in the same order. Perhaps add a ORDER BY query at the end to ensure the right order of things. – Jonathan Lin Sep 12 '15 at 02:23
  • @JonathanLin Thanks for the response Jonathan. You're certainly right. Using an `ORDER BY` won't work in my situation because the order is not based on an attribute. However, there is a way to do it via SQL (so it's fast) and someone has even created a gem for it. Check out this Q&A: http://stackoverflow.com/questions/801824/clean-way-to-find-activerecord-objects-by-id-in-the-order-specified/29785864#comment52840252_29785864 – Joshua Pinter Sep 13 '15 at 19:47
14

Now .find and .find_by_id methods are deprecated in rails 4. So instead we can use below:

Comment.where(id: [2, 3, 5])

It will work even if some of the ids don't exist. This works in the

user.comments.where(id: avoided_ids_array)

Also for excluding ID's

Comment.where.not(id: [2, 3, 5])
Sumit Munot
  • 3,748
  • 1
  • 32
  • 51
  • 5
    https://github.com/rails/activerecord-deprecated_finders .find and .find_by_id methods are NOT deprecated in rails 4. – Canna Jan 14 '15 at 08:26
0

To avoid exceptions killing your app you should catch those exceptions and treat them the way you wish, defining the behavior for you app on those situations where the id is not found.

begin
  current_user.comments.find(ids)
rescue
  #do something in case of exception found
end

Here's more info on exceptions in ruby.

rogeriopvl
  • 51,659
  • 8
  • 55
  • 58
  • 1
    yep this solves the problem, but it's not really a clean solution – Jakub Arnold Sep 18 '09 at 01:39
  • 3
    If you're going to catch an exception you should declare the exception you expect to catch, otherwise you risk it catching something you weren't expecting and hiding an actual problem. – Haegin May 09 '14 at 13:25
0

You can also use it in named_scope if You want to put there others conditions

for example include some other model:

named_scope 'get_by_ids', lambda { |ids| { :include => [:comments], :conditions => ["comments.id IN (?)", ids] } }

mtfk
  • 890
  • 6
  • 12