Rails tips: Active Recordの`#from`を使ってorderとdistinctを1つのクエリにする(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

Rails tips: Active Recordの#fromを使ってorderとdistinctを1つのクエリにする(翻訳)

重複のない一意の結果を得ようとしたことがある方は、単純なクエリでこれを実現できないことにおそらくお気づきかと思います。Active Recordでは、操作する各カラムを明示的にselectすることが要求されるためです。わかりやすくするため、次の事例を考えてみましょう。

問題

Location.joins(:users).where(users: {enabled: true}).distinct.order('locations.name')

目的は、enabled: trueuserlocationごとに1件以上表示することです。locationのリストをnameでソートする必要もあります。上のクエリは動作しません。実行すると以下のエラーが発生します。

ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'locations.name'

解決方法

基本的には2つのクエリが必要です。

  1. enabled: trueuserを1件以上持つlocationをフェッチするクエリ
  2. locationnameで並べ替えるクエリ

Active Recordのfromメソッドを使えば、これを1つのクエリにできます。そのためには、Locationモデルを少々リファクタリングしなくてはなりません。クエリロジックを分離するためにクラスメソッドを2つ追加します。変更後のモデルは次のようになります。

class Location < ActiveRecord::Base
  has_many :users

  def self.enabled
    joins(:user).where(users: {enabled: true}).distinct
  end

  def self.by_name
    order(:name)
  end
end

上のリファクタリングについて詳しく知りたい方は、過去記事をご覧下さい。

この構成にすることで、fromメソッドを使えるようになります。

Location.from(Location.enabled, :locations).by_name

このfromメソッドをもう少し詳しく見てみましょう。このメソッドは引数を2つ取っています。

  1. FROMで使われるクエリ
  2. 後で操作できるようにするためのサブクエリとして使われる名前

生成されるSQLクエリは次のようになります。

SELECT `locations`.* FROM(SELECT DISTINCT `locations`.* FROM `locations` INNER JOIN `users` ON `users`.`location_id` = `locations`.`id` WHERE `users`.`enabled` = 1) ORDER BY `locations`.`name`

お知らせ: コードを正しくテストするには

コードを正しくテストするのは何かと困難であり、しかも最も大変なのはテストを書き始めるときです。テストを書き始めるときに役立つRSpec & Test Driven Developmentの無料ebookをご自由にダウンロードいただけます。

関連記事

Rails tips: モデルのクエリをカプセル化する2つの方法(翻訳)

Rails tips: belongs_to関連付けをリファクタリングしてDRYにする(翻訳)

Ruby on RailsによるWEBシステム開発、Android/iPhoneアプリ開発、電子書籍配信のことならお任せください この記事を書いた人と働こう! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833 コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。 これまでにRuby on Rails チュートリアル第2版の半分ほど、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れてそれぞれ一部を翻訳。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 実は最近Go言語が好き。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

週刊Railsウォッチ

インフラ

BigBinary記事より

ActiveSupport探訪シリーズ