1
0

More than 1 year has passed since last update.

【Rails】生SQLを書きそうになったらsanitize_sql_for_orderを検討しよう

Posted at

環境

Rails 6.0.1
Ruby 2.6.3
PostgreSQL 11.16

UserモデルにPost作成日でnilが最後に来るようにソートするスコープがあったとき、
created_at_sortに入ってきた値がdescascで降順か昇順にソートしている。
orderの中で生のSQLを書くことになってしまいセキュリティとしてあまり良くない。
あと美しくない。

models/user.rb
scope :sort_by_created_at, ->(created_at_sort) do
    if created_at_sort == 'desc'
        eager_load(:posts)
            .order('posts.created_at DESC NULLS LAST')
            .select('users.*', 'posts.created_at AS posts_created_at')
    else
        eager_load(:posts)
            .order('posts.created_at ASC NULLS LAST')
            .select('users.*', 'posts.created_at AS posts_created_at')
    end
end

sanitize_sql_for_orderを使おう

sanitize_sql_for_orderを使うことでSQLインジェクション対策ができる。
created_at_sortdescascのどちらが渡ってきてもちゃんとソートできるので条件分岐が必要ない。
あとすっきりして美しい!

models/user.rb
scope :sort_by_created_at, ->(created_at_sort) do
    sort_sql = sanitize_sql_for_order("posts.created_at #{created_at_sort}")

    eager_load(:posts)
        .order("#{sort_sql} NULLS LAST")
        .select('users.*', 'posts.created_at AS posts_created_at')
end

今回の場合、sanitize_sql_for_orderは結果だけ見るとただ文字列に変換してるだけのように見えるが、

created_at_sort = 'asc'
sanitize_sql_for_order("posts.created_at #{created_at_sort}")
#=> "customer_support_plans.start_date asc"

引数の値が配列だったりすると内部でしっかりサニタイズしてくれる。

sanitize_sql_for_order([Arel.sql("field(id, ?)"), [1,3,2]])
# => "field(id, 1,3,2)"

sanitize_sql_for_order("id ASC")
# => "id ASC"

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0