Reputation: 3559
I have an array if ids from a table e.g:
b = [659, 658, 656, 645, 644, 657, 634, 643, 649, 650, 651, 636, 633, 607, 605, 604, 648, 647, 675, 674, 667, 499]
I use it for retrieving a list of objects from tat table:
k = Photo.where(id:b)
What I get is a list of objects which are NOT sorted in the same order as the array.
How can I do that?
Upvotes: 2
Views: 2132
Reputation: 451
b.map { |m| Photo.find(m) } if the ID's in b array are unique. If not then run this b.uniq.map { |m| Photo.find(m) } One drawback of this code is it runs a query for every elements present inside b array.
Upvotes: 0
Reputation: 54223
You can use index_by and values_at :
k = Photo.where(id: b).index_by(&:id).values_at(*b)
Example :
b = [5,3,1]
Country.where(id: b)
#=> [#<Country id: 1, name: "France">, #<Country id: 3, name: "Ukraine">, #<Country id: 5, name: "Spain">]
Country.where(id: b).index_by(&:id)
#=> {1=>#<Country id: 1, name: "France">, 3=>#<Country id: 3, name: "Ukraine">, 5=>#<Country id: 5, name: "Spain">}
Country.where(id: b).index_by(&:id).values_at(*b)
#=> [#<Country id: 5, name: "Spain">, #<Country id: 3, name: "Ukraine">, #<Country id: 1, name: "France">]
With b = [5,3,3,1]
, the first method would output :
#=> [#<Country id: 5, name: "Spain">, #<Country id: 3, name: "Ukraine">, #<Country id: 3, name: "Ukraine">, #<Country id: 1, name: "France">]
You can use index_by and slice:
k = Photo.where(id: b).index_by(&:id).slice(*b).values
With :
b = [5,3,3,1]
Country.where(id: b)
#=> [#<Country id: 1, name: "France">, #<Country id: 3, name: "Ukraine">, #<Country id: 5, name: "Spain">]
Country.where(id: b).index_by(&:id)
#=> {1=>#<Country id: 1, name: "France">, 3=>#<Country id: 3, name: "Ukraine">, 5=>#<Country id: 5, name: "Spain">}
Country.where(id: b).index_by(&:id).slice(*b)
#=> {5=>#<Country id: 5, name: "Spain">, 3=>#<Country id: 3, name: "Ukraine">, 1=>#<Country id: 1, name: "France">}
Country.where(id: b).index_by(&:id).slice(*b).values
#=> [#<Country id: 5, name: "Spain">, #<Country id: 3, name: "Ukraine">, #<Country id: 1, name: "France">]
Of course, you could also use the first method with b.uniq
Upvotes: 5
Reputation: 434665
An ActiveRecord query like Photo.where(id: some_array)
produces SQL like this:
select ... from photos where id in (...)
but the order of elements in the IN
has no relation to what order the database returns the rows in; in general, the only thing that determines the order of the rows is the ORDER BY clause.
You can get the database to order things to match your b
array with a CASE:
order by case id when 659 then 0 when 658 then 1 ... end
and that's fairly easily to construct in Ruby:
Photo.where(id: b)
.order(%Q{case id #{b.map.each_with_index { |id, i| "when #{id} then #{i}" }.join(' ')} end })
Not terribly pretty I suppose but it gets the database to do the work (which might be important if you're chaining more things into this query) and you can easily hide the ugliness inside a class method on Photo:
def self.for_ids_in_order(ids)
where(id: ids).order(%Q{case id #{ids.map.each_with_index { |id, i| "when #{id} then #{i}" }.join(' ')} end })
end
and then say Photo.for_ids_in_order(b)
.
Of course, this does assume that you know where the b
array came from and that you know it contains integers. If you're not certain of that then you can throw a connection.quote
call in to make sure things are properly escaped:
ids.map.each_with_index { |id, i| "when #{connection.quote(id)} then #{i}" }...
Upvotes: 1