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での文字列出力に「#+」ではなく式展開「#{}」を使うべき理由

デザインも頼めるシステム開発会社をお探しならBPS株式会社までどうぞ この記事を書いた人と働こう! 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探訪シリーズ