Techioz Blog

Rails では、デリゲート メソッドでクエリを注文できますか?

概要

デリゲート メソッドでクエリを注文するのが困難です。私は、かなり大規模な Rails 3 アプリケーションを Rails 4 にアップグレードするのを手伝うことを任されています。インデックス アクションでこのクエリを見つけました。

# measurements_controller.rb
def index
  @measurements = Measurement.includes(:identifier).order(:name)
end

Rails 4 では、次のエラーが発生します。

ERROR: column items.name does not exist LINE 1: ...D (item_names.name LIKE '%') ORDER BY "item...

そこでモデルを調べてみたところ、次のことがわかりました。

# measurement.rb
class Measurement < Item
  ...
end

# item.rb
belongs_to :item, class_name: 'ItemName'
delegate :name, :name=, to: :item

# item_name.rb
# This object has a name column in the database

ターミナルを使用していて、Measurement のインスタンスがある場合、.name メソッドを簡単に呼び出すことができ、うまく機能します。しかし、 .order(:name) に関しては、列が存在しないため機能しません。

解決策を 1 つ見つけましたが、それはデリゲート メソッドの目的に反するようです。コントローラーでは、次のように変更できます。

@measurements = Measurement.includes(:item).order('item_names.name')

Item が name メソッドを ItemNames に委任していることはわかっているので、コントローラーはその委任先を知る必要はないと思います。

既存のコードを保持したままデリゲート メソッドで順序付けする方法はありますか?

解決策

はい、ただし、最初にすべてのレコードをインスタンス化する必要があります (パフォーマンスが低下します)。

@measurements = Measurement.joins(:item).sort_by &:name

このコードは、すべての測定値をロードしてインスタンス化し、Ruby メソッド .name で並べ替えます。

説明

デリゲートは、ActiveRecord モデルのインスタンスにのみ影響します。 SQL クエリには影響しません。

この行は SQL クエリに直接マッピングされます。

Measurement.includes(:item).order(:name)

お気づきのとおり、測定テーブルで名前の列を探しますが、何も見つかりません。 ActiveRecord モデルのインスタンスは名前が識別子にのみ存在することを知っていますが、SQL データベースはそれを知らないため、どのテーブルで列を見つけるかを明示的に指示する必要があります。