ひとつの入力語に対して複数カラムを対象にして検索を行い結果をまとめる

「空気を読む」検索

Rails の検索には Ransack が重用されます。Ransack では基本的にモデルの単一カラムに対して cont(contain) や eq(equal) といった条件で検索が可能です。この場合、ユーザーがひとつの用語で複数のカラムを検索したい場合、別々の入力フォームで複数回の入力を求めることになります。ここで、ひとつの検索語で複数カラムを一気に実行できたらどうでしょうか。これはいわゆる「空気を読む」検索と言えるかもしれません。

ゲームでの例を挙げます。例えば「龍飛」という入力をして検索をするユーザーはアイテム名「龍飛剣」を探しているのかもしれませんし、スキル名「龍飛の心得」を探しているのかもしれません。もしくは両方かもしれません。

Ransack でこの「空気を読む」検索を実現するためには入力語に対してモデルの複数カラムで検索を行い、結果の集約(と重複排除やソート)を行う必要があります。ここで Ruby の Runnable Object(Proc) を使用すると便利です。この実装で The Well-Grounded Rubyist, Third Edition の Chapter 14 で紹介されている Callable and runnable objects をはじめて有効活用できた気がします。

class EndpointsController < ApplicationController
  def search
    @input = params[:input]

    if (@input.empty?) then
      @input = "nothing"
    end

    @items = Item.search(@input)
    @skills = Skill.search(@input)

    @outcome = empty?([@items, @skills])
  end

  def empty?(arg)
    (... snip ...)
  end
class Item < ApplicationRecord

  class << self

    def search(arg)
      result = {}
      result[:by_name] = search_by_name(arg)
      result[:by_description] = search_by_description(arg)
      result[:by_parameter] = search_by_parameter(arg)
      result
    end

    def search_by_name(arg)
      p = Proc.new do |x|
        { name: x.name, level: x.level, description: x.description,
          rarity: x.rarity.name, skills: x.skills.map(&:name) }
      end
      Item.ransack(name_cont: arg).result.map(&p) end
    end

    def search_by_description(arg)
      (... snip ...)
    end

(... snip ...)

参考情報

www.manning.com