LoginSignup
7
6

More than 3 years have passed since last update.

form objectで検索フォームを実装する。(複数単語、or 検索)

Last updated at Posted at 2021-01-26

何をしたか

form objectで検索フォームを実装しました。
「赤い りんご」のように、空白で区切られた検索ワードが入力された時には、空白で区切って複数単語のOR検索にしました。

実装環境は以下の通りです。

  • Rails 5.2.3
  • Ruby 2.6.0

まずは1単語の検索フォームを作る

今回作るのは、投稿postの検索フォームです。

まずはシンプルに、1単語の検索フォーム(フォームオブジェクトではないもの)から作り始めました。

実際に書いたコードは以下の通りです。(装飾等に使用したclassは省いています。以下同。)

/controllers/posts_controller.rb
def search
  @posts = Post.where('body LIKE ?', "%#{params[:body]}%")
end
/views/posts/index_html.slim
= form_with url: search_posts_path, method: :get, local: true do |f|
 = f.search_field :body
 = f.submit

 # ページ下方で = render @posts をして、検索結果を呼び出しています

非常にシンプルですね:relaxed:

この辺の基本的なコードの読み解き方は、以下の記事にまとめておりますので、よろしければご覧ください。

Railsの検索メソッドの基礎(whereでLIKE句を使う)

form objectにする

では、こちらのコードをform objectにしていきます。

/controllers/posts_controller.rb
def search
  @posts = SearchForm.new(search_post_params)
end

# 中略

def search_post_params
  params.fetch(:q, '').permit(:body) # 解説します
end
/views/posts/index_html.slim
= form_with form_with model: search_form, url: search_posts_path, scope: :q, method: :get, local: true do |f|
 = f.search_field :body  # ↑上記、解説します 
 = f.submit
/forms/search_form.rb
class SearchForm
  include ActiveModel::Model
  include ActiveModel::Attributes

  attribute :body, :string

  def search
    Post.includes(:user, :images).where('body LIKE ?', "%#{body}%")
  end
end

form objectにすることで、検索ロジックをフォームオブジェクトに移動できました:relaxed:

解説:scope: :qfetch(:q, '')について

ところで、viewにあったscope: :qが謎ですよね。(↓この部分です)

= form_with form_with model: search_form, url: search_posts_path, scope: :q, method: :get, local: true do |f|

これは、「送るパラメーターをどのハッシュでまとめるのか」を指定している部分で、

scope: :qがない時のparams


Processing by PostsController#search as HTML
  Parameters: {"utf8"=>"✓", "body"=>"りんご", "commit"=>"SEARCH"}
Unpermitted parameters: :utf8, :commit

scope: :qが ある時のparams

Processing by PostsController#search as HTML
  Parameters: {"utf8"=>"✓", "q"=>{"body"=>"りんご"}, "commit"=>"SEARCH"}

のように、scope: :qの記述によって、検索単語を"q"=>{ ... }の中にまとめることができます。

キーワードを入力するフォールドが一つの検索フォームだとほとんど意味がありませんが、入力するフィールド数が2つ、3つにになってくると、

"q"=>{ "フィールド名"=>"検索単語", "フィールド名"=>"検索単語", ... }

の形でparamsがまとまってくれるので、便利です。:relaxed:

また、先ほどはcontrollerstrong paramaterに以下のように設定していましたが...

controller
def search_post_params
  params.fetch(:q, '').permit(:body)
end

これによって、paramsの中から、"q"=>{ ... }の部分だけを取り出しています。なお、fetch(:q, '')''の部分は、キーである:qがなかった時のデフォルト値です。

fetchの詳しい説明はこちら
instance method Hash#fetch

複数単語検索にする

それでは、これを複数単語のor検索に変えていきます。

色々やり方はありましたが、今回はこちらの記事と、そのコメントを参考に、以下のように実装しました。

/forms/search_form.rb
class SearchForm

  # 変わっていないので中略

  def search
    keywords = body.split(/[[:blank:]]+/)

    @posts = Post.none

    keywords.each do |keyword|
      @posts = @posts.or(Post.where('body LIKE ?', "%#{keyword}%"))
    end

    return @posts
  end
end

何点かピックアップして解説します。

  • .split(/[[:blank:]]+/)...検索ワードを空白で複数単語に分け、配列に入れます。
  • Post.none...空のActive Recordを作ります。空の配列を作る[]のActive Record版です。
  • @posts.or(...)...詳細下述します。

解説:@posts.or(...)について

この部分については、記述が複雑になるのでピックアップします。Railsorメソッドは、データベースへの複数検索のorを実行するメソッドです。

例えば、以下のコードで

Fruit.where(name: 'バナナ').or(Fruit.where(name: 'グレープ'))

以下のようなSQLが発行されます。(サンプルのコードはこちらからお借りしました)

Fruit Load (1.5ms) SELECT `fruits`.* FROM `fruits` WHERE (`fruits`.`name` = 'バナナ' OR `fruits`.`name` = 'グレープ')

実装に用いたコードでは、このように書くことで、

@posts = Post.none

keywords.each do |keyword|
  @posts = @posts.or(Post.where('body LIKE ?', "%#{keyword}%"))
end

return @posts

keywordの数だけ

Post.where(body: 'keyword').or(Post.where(body: 'keyword').or(Post.where(...)))

を繰り返し、複数語検索を実現しています。

実際にはリファクタリングを繰り返し、上記orのロジックは消えてしまったのですが、面白い書き方だったので記事にしてとっておくことにしました:relaxed:

7
6
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
7
6