orderメソッドの使い方やパフォーマンスについて
orderメソッドとは?
カラムの値を昇順・降順に並び替えることができるメソッドです。(基本的な使い方は下記参照。)
いくつかorderメソッドの応用した使い方やorderメソッドを使用した時のパフォーマンスについて紹介します。
orderメソッドに引数を複数渡すことで、先に渡した引数を優先的に並び替えるようにすることができます。
また、あるカラムを降順(DESC)にし、もう一方のカラムを昇順(ASC)にすることもできます。下記が使用例です。
Book.order(created_at: :desc, price: :asc)
上記のコードは、Bookモデルのレコードを、まずcreated_atで降順に並べ替え、次にpriceで昇順に並べ替えて返すようにしています。
orderメソッドは、モデル上でもorderメソッドを使用することができます。
モデル上でアソシエーション先のカラムを並び替えるには、アソシエーション先に対してスコープを使用し、アソシエーション先のカラム順に並び替えることもできます。(下記の記事で「スコープブロック」でページ内のキーワード検索すると関連情報が出てきます。)
使用例は下記のような感じです。
class Author < ApplicationRecord
has_many :books, -> { order "date_confirmed DESC" }
end
上記のコードの -> { order "date_confirmed DESC" } は、 has_many メソッドに渡される引数で、取得したAuthorレコードに対応するBookレコードをdate_confirmedカラムの値で降順に並び替えています。
このように、has_manyメソッドでアソシエーションを定義することで、関連するモデルの情報を容易に取得することができ、-> { order "date_confirmed DESC" } のような指示を追加することで、取得する情報の並び順を設定することができます。
コントローラ上でアソシエーション先のカラム順に並び替えるには、上記のモデルに書いたときと同じようにorderの引数にアソシエーション先のカラムを記述することでアソシエーション先のカラムで並び替えを行うことができます。
少し使用例を紹介します。
例えば、UserモデルがPostモデルとhas_manyのアソシエーションを持っている場合、Postモデルのcreated_atカラムで降順に並び替えたい場合は、以下のように記述します。
@user.posts.order(created_at: :desc)
また、UserモデルがPostモデルとhas_manyのアソシエーションを持っており、PostモデルがCommentモデルとhas_manyのアソシエーションを持っている場合、Commentモデルのcreated_atカラムで昇順に並び替えたい場合は、以下のように記述します。
@user.posts.joins(:comments).order("comments.created_at ASC")
上記の例では、joinsメソッドを使用して、commentsテーブルとINNER JOIN (内部結合のことで、テーブル同士を結合するためのSQL。)を行っています。その後、orderメソッドでcommentsテーブルのcreated_atカラムを昇順に並び替えています。
ちなみに、orderの引数に comments を記述している理由は、joinsメソッドが結合先のテーブル(今回の場合commentsテーブルのこと。)に対してActiveRecordを用いて条件を指定するとき、結合先のテーブル名を明示しなければいけないからです。
joinsメソッドを用いた時の結合先の条件指定について
最後にorderを使用した時のパフォーマンスについてですが、orderの引数にはハッシュではなく、文字列を使用することでパフォーマンスを向上させることができます。
orderメソッドの引数をハッシュにしたとき
@user.posts.joins(:comments).order({comments: {created_at: :asc}})
orderメソッドの引数を文字列にしたとき(上記の「orderメソッドの引数をハッシュにしたとき」と同じ内容です。)
@user.posts.joins(:comments).order("comments.created_at ASC")
orderメソッドの引数にハッシュを使用する代わりに、文字列を使用することでパフォーマンスが向上する理由は、Rubyのハッシュを解析するオーバーヘッド(何らかの処理を実行する際にかかる時間的・空間的なコスト、負荷のこと)がなくなるためです。
ハッシュを使用する場合、RubyのハッシュオブジェクトをSQL文に変換する必要がありますが、文字列を使用する場合は、SQL文の文字列を直接渡すことができます。このため、文字列を使用する方が高速であるとされています。
ただし、文字列を使用する場合は、SQLインジェクション(SQL文を作成し、データを書き換えること)のリスクがあるため、引用符のエスケープを行う必要があります。
たとえば、以下のようなコードでは、params[:column]に不正な文字列が渡された場合、SQLインジェクション攻撃を受ける可能性があります。
@user.posts.joins(:comments).order("#{params[:column]} ASC")
このような攻撃を防ぐためには、sanitize_sqlメソッドを使用して、引用符をエスケープする必要があります。
@user.posts.joins(:comments).order(sanitize_sql(["? ASC", params[:column]]))
このように、sanitize_sqlメソッドを使用することで、安全にSQL文を構築することができます。
※sanitize_sql メソッドについて
sanitize_sqlメソッドは、SQLクエリを安全に構築するために使用されます。
引数に渡された文字列を安全な形式に変換し、SQLクエリに挿入することSQLインジェクション攻撃を防止することができます。
また、sanitize_sqlメソッドは、RailsのActiveRecordクエリインターフェースで使用されるSQLフラグメント(ActiveRecordを使用してSQLクエリを構築する際に、SQLクエリの一部として直接文字列で渡されるSQLコードのこと)や、手動で構築されたSQLクエリのセキュリティを向上させるために役立ちます。
※SQLフラグメント
SQLフラグメントとは、ActiveRecordを使用してSQLクエリを構築する際に、SQLクエリの一部として直接文字列で渡されるSQLコードのことを指します。例えば、以下のようなコードが該当します。
User.where("name = 'John' AND age > 18")
このコードでは、whereメソッドの引数に直接SQLコードが指定されており、nameカラムがJohnであり、ageカラムが18より大きいユーザーを取得するクエリを生成しています。ここで、"name = 'John' AND age > 18"がSQLフラグメントとなります。
Discussion
追記
orderメソッドは関連先データで並び替えることができる。
この例では、
joins(:category)でArticleとCategoryを結合し、order('categories.name ASC')でカテゴリの名前で昇順にソートしています。