はじめに
以前書いた、下記の続き。
Railsのroutingにおけるscope / namespace / module の違い - Qiita
今回は、gemを使わず簡単な検索機能を自作する方法について書きます。
環境
- macOS HighSierra 10.13.6
- Rails 5.2.0
- Ruby 2.5.1
- mysql 5.7.22
- mysqlはdockerに作成
- Docker version 18.03.1-ce
検索機能の概要
- フォームに検索キーワード(文字列)を入力
- フォームの検索キーワード(文字列)をController → Modelにパラメータとして渡す
- パラメータとして渡って来た文字列をMySQLであいまい検索(
SELECT ??? FROM table WHERE LIKE '%文字列%'
)をかける - 検索結果をViewに表示
View側の実装
まず、検索できるフォームをViewに設置します。
・・・
<h2>検索</h2>
<%= form_tag(root_path, method: :get) do %>
<%= text_field_tag :search %>
<%= submit_tag 'Search', name: nil %>
<% end %>
・・・
1つづつ説明して行きます。
form_tagとは、ActionView::Helpersを継承した、簡単に検索入力フォームを作成できるヘルパーの1つです。form_tagは2つ引数を取っていて、検索結果を表示するURL(今回は名前付きルートの指定)と、パラメータとして送るためにHTTPメソッド(GET)を指定しています。
text_field_tagもヘルパーの1つです。以下のHTMLをレンダリングしています。
<input type="text" name="search" id="search">
submit_tagもヘルパーで、これは入力ボタンです。nameは識別子のようなものなので、複数のsubmitボタンがない限りnil
でいいと思います。
参考: Railsで1フォームに複数のサブミット(Submit)ボタンを配置するTips - Rails Webook
この時点で検索して見ると
まだ、絞り込みはできないのですが、入力フォームに「test」と入力してSubmitすると、
の様に出力され、ログには下記の様に出力されます。
Started GET "/?utf8=%E2%9C%93&search=test" for 127.0.0.1 at 2019-03-24 06:52:27 +0900
Processing by TopController#index as HTML
Parameters: {"utf8"=>"✓", "search"=>"test"}
Rendering top/index.html.erb within layouts/application
Article Load (3.8ms) SELECT `articles`.* FROM `articles`
↳ app/views/top/index.html.erb:10
Rendered top/index.html.erb within layouts/application (15.6ms)
Completed 200 OK in 40ms (Views: 32.6ms | ActiveRecord: 3.8ms)
これを見ると、指定した通りsearch
という名前でtest
という文字列をGETパラメータで渡していることがわかります。今度はControllerやModelで受け取って処理して見ましょう。
Controller側の実装
.search(params[:search])
を追記しました。これによって、Viewのformで取得したパラメータをModelに渡せます。
class TopController < ApplicationController
def index
@articles = Article.all.search(params[:search])
end
end
.search
メソッドは自作メソッドです。このままだとエラー(NoMethodError
)になります。なので、Modelにメソッドを書きます。
Model側の実装
class Article < ApplicationRecord
def self.search(search)
search ? where('title LIKE ?', "%#{search}%") : all
end
end
自分自身の値を返すので、クラスメソッドです。
では、試しに、フォームに「title 1」と検索してみましょう。
これによって発行されるSQLは、ログから見ることができます。
SELECT `articles`.* FROM `articles` WHERE (title LIKE '%title 1%')
SQLを見るとtitleカラムから特定の文字列title 1
が含まれるデータのあいまい検索(ワイルドカードを用いた部分一致検索)を行ったことが分かります。
Railsのセキュリティ対策の話
DB操作の際にSQLを自前で組み立てたりするとSQLインジェクションの可能性があります。
例えば、下記の様に書くことも可能なのですが、
class Article < ApplicationRecord
def self.search(search)
search ? where("title='#{search}'") : all
end
end
この場合、検索フォームに
'); select * from articles;--('
と書いてsubmitした場合吐き出されるログは、下記の様になります。
SELECT `articles`.* FROM `articles` WHERE (title='');
select * from articles;
--('')
上を、MySQL上で実行すると、2行目のSELECT文は普通に実行されてしまいます。そうなると、DROP TABLE articles;
(テーブル削除コマンド)という、やられては困るコマンドも実行される可能性があるということです。
そこで、
where('title LIKE ?', "%#{search}%")
?
というSQLプレースホルダーという機能を利用することで、SQL構文を決定した後に引数の文字列展開などが起こるので、結果的に、引数search
からのデータがエスケープされ、SQLインジェクションを防ぐ、という方法が慣習になっているそうです。
今回は、以上です!