[ruby-on-rails] ActiveRecord.find (array_of_ids), 순서 유지

Something.find(array_of_ids)Rails에서 수행 할 때 결과 배열의 순서는array_of_ids .

주문을 찾고 보존 할 수있는 방법이 있습니까?

ATM ID 순서에 따라 수동으로 레코드를 정렬하지만 다소 절름발이입니다.

UPD : :orderparam과 어떤 종류의 SQL 절 을 사용하여 순서를 지정할 수 있다면 어떻게할까요?



답변

대답은 mysql에만 해당됩니다.

mysql에는 FIELD () 라는 함수가 있습니다.

.find ()에서 사용하는 방법은 다음과 같습니다.

>> ids = [100, 1, 6]
=> [100, 1, 6]

>> WordDocument.find(ids).collect(&:id)
=> [1, 6, 100]

>> WordDocument.find(ids, :order => "field(id, #{ids.join(',')})")
=> [100, 1, 6]

For new Version
>> WordDocument.where(id: ids).order("field(id, #{ids.join ','})")

업데이트 : 이것은 Rails 6.1 Rails 소스 코드 에서 제거됩니다.


답변

이상하게도 아무도 다음과 같은 제안을하지 않았습니다.

index = Something.find(array_of_ids).group_by(&:id)
array_of_ids.map { |i| index[i].first }

SQL 백엔드를 사용하는 것 외에도 효율적입니다.

편집 : 내 대답을 개선하려면 다음과 같이 할 수도 있습니다.

Something.find(array_of_ids).index_by(&:id).slice(*array_of_ids).values

#index_by그리고 #slice꽤 편리 추가됩니다 ActiveSupport 각각 배열과 해시에 대한이.


답변

마이크 우드 하우스가 에 명시된 그의 대답 이는 후드 아래, 레일이 가진 SQL 쿼리를 사용하고, becase 발생 WHERE id IN... clause한 쿼리에서 모든 레코드를 검색 할 수 있습니다. 이 방법은 각 ID를 개별적으로 검색하는 것보다 빠르지 만 검색중인 레코드의 순서를 유지하지 않습니다.

이 문제를 해결하기 위해 레코드를 조회 할 때 사용한 원래 ID 목록에 따라 애플리케이션 수준에서 레코드를 정렬 할 수 있습니다.

Sort an array of the elements of another array 에 대한 많은 훌륭한 답변을 기반으로 다음 솔루션을 권장합니다.

Something.find(array_of_ids).sort_by{|thing| array_of_ids.index thing.id}

또는 조금 더 빠른 것이 필요하면 (하지만 다소 읽기 어려운) 다음과 같이 할 수 있습니다.

Something.find(array_of_ids).index_by(&:id).values_at(*array_of_ids)


답변

이것은 postgresql ( source ) 에서 작동하는 것으로 보이며 ActiveRecord 관계를 반환합니다.

class Something < ActiveRecrd::Base

  scope :for_ids_with_order, ->(ids) {
    order = sanitize_sql_array(
      ["position((',' || id::text || ',') in ?)", ids.join(',') + ',']
    )
    where(:id => ids).order(order)
  }
end

# usage:
Something.for_ids_with_order([1, 3, 2])

다른 열에 대해서도 확장 할 수 있습니다 (예 : name열의 경우 position(name::text in ?)


답변

여기 에서 대답했듯이 다음과 같이 네이티브 SQL 주문을 수행 할 수 있는 gem ( order_as_specified )을 출시했습니다 .

Something.find(array_of_ids).order_as_specified(id: array_of_ids)

내가 테스트 할 수있는 한 모든 RDBMS에서 기본적으로 작동하며 연결할 수있는 ActiveRecord 관계를 반환합니다.


답변

불행히도 모든 경우에 작동하는 SQL에서는 가능하지 않습니다. 독점 기술을 사용하여 작동하도록 만드는 방법이 있지만 각 레코드 또는 주문에 대해 단일 찾기를 작성해야합니다.

첫 번째 예 :

sorted = arr.inject([]){|res, val| res << Model.find(val)}

매우 비효율적

두 번째 예 :

unsorted = Model.find(arr)
sorted = arr.inject([]){|res, val| res << unsorted.detect {|u| u.id == val}}


답변

@Gunchars 대답은 훌륭하지만 Hash 클래스가 주문되지 않았기 때문에 Rails 2.3에서 상자 밖으로 작동하지 않습니다. 간단한 해결 방법은 index_byOrderedHash 클래스를 사용하도록 Enumerable 클래스를 확장하는 것입니다 .

module Enumerable
  def index_by_with_ordered_hash
    inject(ActiveSupport::OrderedHash.new) do |accum, elem|
      accum[yield(elem)] = elem
      accum
    end
  end
  alias_method_chain :index_by, :ordered_hash
end

이제 @Gunchars의 접근 방식이 작동합니다.

Something.find(array_of_ids).index_by(&:id).slice(*array_of_ids).values

보너스

module ActiveRecord
  class Base
    def self.find_with_relevance(array_of_ids)
      array_of_ids = Array(array_of_ids) unless array_of_ids.is_a?(Array)
      self.find(array_of_ids).index_by(&:id).slice(*array_of_ids).values
    end
  end
end

그때

Something.find_with_relevance(array_of_ids)