始めに
投稿のいいね数の順番によってソートする機能実装中に遭遇したエラーに関するメモです。
前提
Ruby 2.6系
Rails 5.2系
Slim導入済
devise導入済
実装されている内容
1.User、Post、Favoriteモデルが存在する。
2.favoritesテーブルを中間テーブルとして、usersテーブルとpostsテーブルが関連付けされている。
Favoriteモデル
app/models/favorite.rbclass Favorite < ApplicationRecord belongs_to :user belongs_to :post end
Postモデル
app/models/post.rbclass Post < ApplicationRecord belongs_to :user has_many :favorites end
Userモデル
app/models/user.rbclass User < ApplicationRecord has_many :favorites has_many :favorite_posts, through: :favorites, source: :post has_many :posts end
3.セレクトボックスフォームのコード。
app/views/index.html.slim= form_with url: search_path, method: :get, local: true do |form| = form.select :keyword, [ ['いいねが多い順', 'likes'], ['いいねが少ない順', 'dislikes'], ] = form.submit
4.ルーティング。
config/routes.rbRails.application.routes.draw do #検索結果をpostsコントローラのsearchアクションへ送信 get 'search' => 'posts#search' resources :posts end
5.条件に一致したPostを取得するpostsコントローラのsearchアクション。
app/controllers/posts_controller.rbdef search @selection = params[:keyword] if @selection == 'likes' @posts = Post.find(Favorite.group(:post_id).order('count(user_id) desc').pluck(:post_id)) elsif @selection == 'dislikes' @posts = Post.find(Favorite.group(:post_id).order('count(user_id) asc').pluck(:post_id)) end end
エラー地点
postsコントローラのsearchアクション内。
app/controllers/posts_controller.rbdef search @selection = params[:keyword] if @selection == 'likes' @posts = Post.find(Favorite.group(:post_id).order('count(user_id) desc').pluck(:post_id)) elsif @selection == 'dislikes' @posts = Post.find(Favorite.group(:post_id).order('count(user_id) asc').pluck(:post_id)) end end
エラー内容
DEPRECATION WARNING: Dangerous query method (method whose arguments are used as raw SQL) called with non-attribute argument(s): "count(user_id) desc". Non-attribute arguments will be disallowed in Rails 6.0. This method should not be called with user-provided values, such as request parameters or model attributes. Known-safe values can be passed by wrapping them in Arel.sql().
読んでみると、"count(user_id) desc"
に問題があって、Known-safe values can be passed by wrapping them in Arel.sql().
を適用させれば解決できるとのこと。
なので、該当箇所を以下のように記述します。
app/controllers/posts_controller.rb
def search
@selection = params[:keyword]
if @selection == 'likes'
@posts = ... ...order(Arel.sql('count(user_id) desc'))... ...
elsif @selection == 'dislikes'
@posts = ... ...order(Arel.sql('count(user_id) asc'))... ...
end
end
先ほどとの違いは、order内のcountメソッドを使った文字列をArel.sql()で囲んでるところ。
こうしなければならない理由は、Rails6.0以降SQLインジェクションの脆弱性ゆえに、第三者から勝手にパラメータを潜り込ませるのを防ぐためとかなんとか。
しかしこれで解決はできました、良かったです。