LoginSignup
10
27

More than 5 years have passed since last update.

Railsで検索機能を自作で実装する方法

Posted at

はじめに

以前書いた、下記の続き。

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に設置します。

app/views/top/index.html.erb
・・・
<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すると、

スクリーンショット 2019-03-24 06.52.34.png

の様に出力され、ログには下記の様に出力されます。

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に渡せます。

app/controllers/top_controller.rb
class TopController < ApplicationController
  def index
    @articles = Article.all.search(params[:search])
  end
end

.searchメソッドは自作メソッドです。このままだとエラー(NoMethodError)になります。なので、Modelにメソッドを書きます。

Model側の実装

app/models/article.rb
class Article < ApplicationRecord
  def self.search(search)
    search ? where('title LIKE ?', "%#{search}%") : all
  end
end

自分自身の値を返すので、クラスメソッドです。

では、試しに、フォームに「title 1」と検索してみましょう。

Image from Gyazo

これによって発行されるSQLは、ログから見ることができます。

SELECT `articles`.* FROM `articles` WHERE (title LIKE '%title 1%')

SQLを見るとtitleカラムから特定の文字列title 1が含まれるデータのあいまい検索(ワイルドカードを用いた部分一致検索)を行ったことが分かります。

Railsのセキュリティ対策の話

DB操作の際にSQLを自前で組み立てたりするとSQLインジェクションの可能性があります。

例えば、下記の様に書くことも可能なのですが、

app/models/article.rb
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インジェクションを防ぐ、という方法が慣習になっているそうです。

今回は、以上です!

参考

10
27
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
10
27