Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails: ActiveRecord::Relationで生SQLは避けよう(翻訳)

概要

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

訳注: 原文の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に与える必要が生じることは多々あり、それ自体はまったく問題ありません。しかし選択の余地があるならば、柔軟かつスコープの明快な方式を選ばない理由があるでしょうか。

おたより発掘

関連記事

Rubyの文字列連結に「#+」ではなく式展開「#{}」を使うべき理由


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。