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

概要 原著者の許諾を得て翻訳・公開いたします。 英語記事: 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に与える必要が生じることは多々あり、それ自体はまったく問題ありません。しかし選択の余地があるならば、柔軟かつスコープの明快な方式を選ばない理由があるでしょうか。 ツイートより おっ、そうだな(白目)> いったんproductionにデプロイされた後でデータベースをPostgreSQLからMySQLに(あるいはその逆)に移行することは、実際にはまずありません。よほどマゾヒスティックな性格の方なら別ですが。https://t.co/xzZoA5FA2H — ぷりんたい (@spacepro_be) June 11, 2018 "PostgreSQLからMySQLに(あるいはその逆)に移行することは、実際にはまずありません。"まあそうだけどだから軽視してもいいとは思えんけどな。 / “Rails: ActiveRecord::Relationで生SQL…” https://t.co/2zZyyMHONn — るっか@🇦🇷 (@luccafort) June 13, 2018 関連記事 Rubyでの文字列出力に「#+」ではなく式展開「#{}」を使うべき理由