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')
でカテゴリの名前で昇順にソートしています。