環境
Rails 6.0.1
Ruby 2.6.3
PostgreSQL 11.16
UserモデルにPost作成日でnilが最後に来るようにソートするスコープがあったとき、
created_at_sort
に入ってきた値がdesc
かasc
で降順か昇順にソートしている。
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_sort
にdesc
かasc
のどちらが渡ってきてもちゃんとソートできるので条件分岐が必要ない。
あとすっきりして美しい!
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"
参考