概要
原著者の許諾を得て翻訳・公開いたします。
- 英語記事: Avoid Writing SQL When Using ActiveRelation - Andy Croll
- 原文公開日: 2018/02/18
- 著者: Andy Croll
訳注: 原文の
ActiveRelation
は訳文でActiveRecord::Relation
に変更してあります。
Rails: ActiveRecord::Relationで生SQLは避けよう(翻訳)
ActiveRecord::Relationは、ActiveRecordの検索やクエリエンジンを強化する、柔軟で強力なツールです。
次のようには書かないこと
ActiveRecord::Relation
の#where
メソッド内で生SQL文字列を式展開(interpolation)で直接書く。
Person.where("name = #{ params[:name] } AND hidden_at IS NULL")
これもやらないこと
?
による安全性の高い式展開を用いてユーザー入力を「arrayスタイル」で生SQL文字列で書く。
Person.where('name = ? AND hidden_at IS NULL', params[:name])
次のように書くこと
「hashスタイル」の構文で書く。
Person.where(name: params[:name], hidden_at: nil)
そうすべき理由
最初の2つで使われている生SQL手書きは、ActiveRecord::Relation
がRailsに(バージョン3.0で)マージされたときまでは、データベースクエリを指定する唯一の方法でした。しかし、上述の「hashスタイル」の方が柔軟性においても安全性においても上です。
1つ目の例は非常に危険です。ユーザーから渡されたパラメータを文字列の式展開で直接使っていますが、こういう書き方は絶対にしないでください。さもないとSQLインジェクション攻撃にさらされ、インターネットに潜む悪意のあるユーザーがあなたのデータベースに対して破壊的またはデータをさらけ出すステートメントの実行を試みることができてしまいます。
2つ目の例で用いた「arrayスタイル」では、渡されるデータがサニタイズされるため、1つ目の「stringスタイル」による#where
よりはましな方法です。しかし、SQLを生書きしないといけない点は変わりません。せっかくRubyで楽しくコードを書いているというのに、ActiveRecord::Relation
が生成してくれる完璧なSQLでハッピーになれる道を選ばない理由があるでしょうか。
3つ目の例で用いた「hashスタイル」は、2つ目の「arrayスタイル」よりも短く、クリーンで、エディタのシンタックスハイライトもよりきれいに表示されます。コードは、書くときよりも読むときの方が重要なのです。
#where
に文字列を渡すと、渡した文字列がそのまま生成されたSQLで使われます。テーブル名も不要ですし、タイポとも無縁です。
# hashスタイルの場合
> Person.where(name: 'Andy', hidden_at: nil).to_sql
=> "SELECT \"people\".* FROM \"people\" WHERE \"people\".\"name\" = 'Andy' AND \"people\".\"hidden_at\" IS NULL"
# stringスタイルの場合
> Person.where('name = ? and hidden_at is null', 'Andy').to_sql
=> "SELECT \"people\".* FROM \"people\" WHERE (name = 'Andy' and hidden_at is null)"
この「hashスタイル」構文が評価されると、データベースのテーブル名がクエリに含まれ、SQLの精度も向上します。これにより、複数のモデルでJOINしたりクエリを生成したりするときのエラーを削減できます。
hashスタイルのさらに便利な点は、データベースアダプタが変更されたときにも生成されるSQLの互換性が保たれることです。
そうすべきではない理由があるとすれば
文字列による条件を使うと、使っているデータベースの特定のSQL方言(フレーバー)への依存が生じる可能性があります。ただし、これが問題になるとすればよほど難解なデータベース固有SQL(検索や地理情報クエリなど)を使う場合ぐらいです。上述の例のように素直なSELECT
構文なら問題になりません。
さらに、いったんproductionにデプロイされた後でデータベースをPostgreSQLからMySQLに(あるいはその逆)に移行することは、実際にはまずありません。よほどマゾヒスティックな性格の方なら別ですが。
また、本記事でご紹介した例は非常にシンプルなものです。現実には、使っているデータベース固有の文字列引数を#where
に与える必要が生じることは多々あり、それ自体はまったく問題ありません。しかし選択の余地があるならば、柔軟かつスコープの明快な方式を選ばない理由があるでしょうか。