【Rails】自作のWEBアプリに検索機能を追加した

2018年4月16日

さて数日前に検索機能の仕様変更が入った所ですが、今日はその実装部分を投稿しています。

検索機能の流れ

次の順で検索機能を動かすことにしました。

1.ユーザーがキーワードを入力する

2.自前のDBの中をあいまい検索

3.Yahoo地図APIにキーワードを投げて検索

2と3の結果をマージして表示

このWEBアプリを公開するときに予め店舗テーブルに数万件のデータを入れておく予定です。なのでまずは自前のテーブル内を検索し、その後にYahooのAPIを借りて検索しようと思いました。

まず自前のDBの中をあいまい検索

ユーザーが検索したらそのキーワードを取ってきて、自分が用意した店舗テーブルの中からあいまい検索をします。

検索窓は2つ


検索窓を1つから2つに変更しました。

[地名] x [店名 または ジャンル]

という風になっています。なので検索のパターンは次の3つを想定しました。

①地名だけが入力された場合
②地名と店名またはジャンルが入力された場合
③店名またはジャンルが入力された場合

検索時のルール

上の3つのパターンに合わせて

1.地名しかはいってない時は、地名のみで店舗住所カラムと最寄り駅カラムをあいまい検索
2.地名と店名またはジャンルが入ってる時は、地名 & (店舗名 or ジャンル) をあいまい検索
3.店名またはジャンルしかはいってない時は、店舗名 or ジャンルをあいまい検索

という風に考えて実装しました。

コンソールでテストしてみます

ルール①の確認

地名(@area)だけで「住所カラム」と「最寄駅カラム」をあいまい検索する文を考えてみました。

> @area = ‘柏市’
> Shop.where(“(address like ?) OR (moyorieki like ?)”, “\%#{@area}\%”, “\%#{@area}\%”).order(‘created_at DESC’)

=> #<ActiveRecord::Relation [
#<Shop id: 30, name: “京まる 柏店”, address: “千葉県柏市末広町4-16 小田山第2ビルB1”, moyorieki: “JR在来線/東武鉄道 柏駅 徒歩1分”, gyosyu: “居酒屋、ビアホール”>
#<Shop id: 29, name: “サイゼリヤ 柏6丁目店”, address: “千葉県柏市柏6-8-28”, moyorieki: “JR在来線/東武鉄道 柏駅 徒歩11分”, gyosyu: “ファミレス、ファストフード、ファミレス”>
#<Shop id: 25, name: “個室居酒屋 トサカモミジ 柏店”, address: “千葉県柏市柏2-2-5 オアゾビル3F”, moyorieki: “JR在来線/東武鉄道 柏駅 徒歩1分”, gyosyu: “居酒屋、ビアホール”>
#<Shop id: 24, name: “チーズ&肉バル Mucho DE Mucho 柏店”, address: “千葉県柏市柏2-2-5 オアゾビルB1”, moyorieki: “JR在来線/東 武鉄道 柏駅 徒歩1分”, gyosyu: “居酒屋、ビアホール”>]> ,

柏市柏2にしてみます。

> @area = ‘柏市柏2’
> Shop.where(“(address like ?) OR (moyorieki like ?)”, “\%#{@area}\%”, “\%#{@area}\%”).order(‘created_at DESC’)
=> #<ActiveRecord::Relation [
#<Shop id: 25, name: “個室居酒屋 トサカモミジ 柏店”, address: “千葉県柏市柏2-2-5 オアゾビル3F”, moyorieki: “JR在来線/東武鉄道 柏駅 徒歩1分”, gyosyu: “居酒屋、ビアホール”>,
#<Shop id: 24, name: “チーズ&肉バル Mucho DE Mucho 柏店”, address: “千葉県柏市柏2-2-5 オアゾビルB1”, moyorieki: “JR在来線/東武鉄道  柏駅 徒歩1分”, gyosyu: “居酒屋、ビアホール”>]> ,

柏市柏2丁目のみ取得できました。これが出来たのでRailsの書き方は間違えてなさそうです。

ルール②の確認

> @area = ‘柏’
> @tenpo_genre =’ファミレス’
> Shop.where(“((address like ?) OR (moyorieki like ?)) AND ((name like ?) OR (gyosyu like ?))”
=> #<ActiveRecord::Relation [
#<Shop id: 29, name: “サイゼリヤ 柏6丁目店”, address: “千葉県柏市柏6-8-28”, moyorieki: “JR在来線/東武鉄道 柏駅 徒歩11分”, gyosyu: “ファ ミレス、ファストフード、ファミレス”>
#<Shop id: 31, name: “デニーズ柏増尾台店”, address: “千葉県柏市増尾台3丁目6−1”, moyorieki: “東武鉄道 増尾駅 徒歩8分”, gyosyu: “ファミレス、ファストフード、ファミレス”>
#<Shop id: 32, name: “デニーズ立川店”, address: “東京都立川市柏町1丁目5−2”, moyorieki: “多摩モノレール 泉体育館駅 徒歩3分”, gyosyu: “ファミレス、ファストフード、ファミレス”>
#<Shop id: 33, name: “COCO’S 柏布施店”, address: “千葉県柏市布施810-8”, moyorieki: “JR在来線 北柏駅 徒歩21分”, gyosyu: “ファミレス、ファストフード、ファミレス”>]>

住所か最寄駅に「柏」が入ってるレコードが取れました。

次は「柏駅」に指定してみます。泉体育館駅と増尾駅が消えるでしょうか。

> @area = ‘柏駅’
> Shop.where(“((address like ?) OR (moyorieki like ?)) AND ((name like ?) OR (gyosyu like ?))”
=> #<ActiveRecord::Relation [
#<Shop id: 29, name: “サイゼリヤ 柏6丁目店”, address: “千葉県柏市柏6-8-28”, moyorieki: “JR在来線/東武鉄道 柏駅 徒歩11分”, gyosyu: “ファ ミレス、ファストフード、ファミレス”>,
#<Shop id: 33, name: “COCO’S 柏布施店”, address: “千葉県柏市布施810-8”, moyorieki: “JR在来線 北柏駅 徒歩21分”, gyosyu: “ファミレス、ファストフード、ファミレス”>]> ,

消えました。RailsのSQL文は成功してそうです。

店舗名=デニーズにしてみます。

> @area = ‘柏’
> @tenpo_genre = ‘デニーズ’
> Shop.where(“((address like ?) OR (moyorieki like ?)) AND ((name like ?) OR (gyosyu like ?))”, “\%#{@area}\%”, “\%#{@area}\%”, “\%#{@tenpo_genre}\%”, “\%#{@tenpo_genre}\%”)
=> #<ActiveRecord::Relation [
#<Shop id: 31, name: “デニーズ柏増尾台店”, address: “千葉県柏市増尾台3丁目6−1”, moyorieki: “東武鉄道 増尾駅 徒歩8分”, gyosyu: “ファミレス、ファストフード、ファミレス”>
#<Shop id: 32, name: “デニーズ立川店”, address: “東京都立川市柏町1丁目5−2”, moyorieki: “多 摩モノレール 泉体育館駅 徒歩3分”, gyosyu: “ファミレス、ファストフード、ファミレス”>]>

ちゃんと取得できています。

ルール③の確認

最後はルール③の確認です。店舗名のみで検索時のコーディング。

> @tenpo_genre =’デニーズ’
> Shop.where(“(name like ?) OR (gyosyu like ?)”, “\%#{@tenpo_genre}\%”, “\%#{@tenpo_genre}\%”)

=> #<ActiveRecord::Relation [
#<Shop id: 10, name: “デニーズ東五反田店”, address: “東京都品川区東五反田1丁目21−10”, moyorieki: “JR在来線/東急池上線 五反田駅 徒歩3分”, gyosyu: nil>,
#<Shop id: 31, name: “デニーズ柏増尾台店”, address: “千葉県柏市増尾台3丁目6−1”, moyorieki: “東武鉄道 増尾駅 徒歩8分”, gyosyu: “ファミレス、ファストフード、ファミレス”>,
#<Shop id: 32, name: “デニーズ立川店”, address: “東京都立川市柏町1丁目5−2”, moyorieki: “多摩モノレール 泉体育館駅 徒歩3分”, gyosyu: “ファ ミレス、ファストフード、ファミレス”>]> ,

OKそうです。

検索利用シーンとしてはあまりないけど、業種のみで検索

> @tenpo_genre = ‘ファミレス’
> Shop.where(“(name like ?) OR (gyosyu like ?)”
=> #<ActiveRecord::Relation [
#<Shop id: 29, name: “サイゼリヤ 柏6丁目店”, address: “千葉県柏市柏6-8-28”, moyorieki: “JR在来線/東武鉄道 柏駅 徒歩11分”, gyosyu: “ファ ミレス、ファストフード、ファミレス”>,
#<Shop id: 31, name: “デニーズ柏増尾台店”, address: “千葉県柏市増尾台3丁目6−1”, moyorieki: “東武鉄道 増尾駅 徒歩8分”, gyosyu: “ファミレス、ファストフード、ファミレス”>,
#<Shop id: 32, name: “デニーズ立川店”, address: “東京都立川市柏町1丁目5−2”, moyorieki: “多摩モノレール 泉体育館駅 徒歩3分”, gyosyu: “ファミレス、ファストフード、ファミレス”>,
#<Shop id: 33, name: “COCO’S 柏布施店”, address: “千葉県柏市布施810-8”, moyorieki: “JR在来線 北柏駅 徒歩21分”, gyosyu: “ファミレス、ファストフード、ファミレス”>]> ,

取れました。

これでDB内のあいまい検索の実装はできそうです。

ごちゃごちゃしてるんですけど、このように書きました。もっとスマートな書き方があれば教えて欲しい(; ・`ω・´)

if @area.present? && !@tenpo_genre.present? then #areaしか入ってないなら
@shops_db = Shop.where("(address like ?) OR (moyorieki like ?)", "\%#{@area}\%", "\%#{@area}\%")#住所か駅カラムに含まれるか
elsif @area.present? && @tenpo_genre.present? then #areaとtenpo_genreが入っているなら
@shops_db = Shop.where("((address like ?) OR (moyorieki like ?)) AND ((name like ?) OR (gyosyu like ?))", "\%#{@area}\%", "\%#{@area}\%", "\%#{@tenpo_genre}\%", "\%#{@tenpo_genre}\%")
elsif !@area.present? && @tenpo_genre.present? then #tenpo_genreしか入ってないなら
@shops_db = Shop.where("(name like ?) OR (gyosyu like ?)", "\%#{@tenpo_genre}\%", "\%#{@tenpo_genre}\%")
end

店舗に対する口コミ件数の多い順に並べ替え

ここで考えたのが店舗に刺さっている口コミ件数が多い順から降順で並べ替えてインスタンスを生成することです。

残念ながら私の知識ではこれをやるためのSQL文を思いつきませんでした。

なので1度インスタンスの配列を作っておいてその後で並べ替えを行いました。

ranking = shops.sort{|a, b| b.kuchikomis.size <=> a.kuchikomis.size}

インスタンスの配列にインスタンスの配列をつなげる

自前のテーブル内からあいまい検索して取得した店舗達と、YahooAPIで検索して取得した店舗達をつなげます。

DBから生成した店舗インスタンスの配列に、YahooAPIから生成した店舗インスタンスの配列をつなげる方法を探していました。

コンソールで色々テストしてみました。

これまでpushメソッドを使って1個ずつインスタンスを追加していく方法を学んだので以下のようにやってみました。

2.4.1 :001 > aaa = [1,2,3]
 => [1, 2, 3] 
2.4.1 :002 > bbb = [5,6,7]
 => [5, 6, 7] 
2.4.1 :003 > aaa.push(bbb)
 => [1, 2, 3, [5, 6, 7]] 

配列が入れ子になっちゃった(; ・`ω・´)

更に調べていたらflatten!というメソッドがあるそうです。これをやってみると。

2.4.1 :004 > aaa.flatten!
 => [1, 2, 3, 5, 6, 7] 

理想の配列になりました。でもちょっと微妙です。調べていたら+メソッドでできるそうです。

2.4.1 :005 > aaa = [1,2,3]
 => [1, 2, 3] 
2.4.1 :006 > bbb = [5,6,7]
 => [5, 6, 7] 
2.4.1 :007 > ccc = aaa + bbb
 => [1, 2, 3, 5, 6, 7] 

できました!

でもこれ問題があって、aaaかbbbがnilだとエラーが出てしまいました。
nilClassの+メソッドは使えないぞ、というエラーです。

そこで考えたのがこのプログラミングコードです。

if @db_shops && @yh_shops then     		#どっちもあるなら
	@shops = @db_shops + @yh_shops		#DBから取得した店舗の後ろに、Yahooから取得した店舗を追加
elsif @db_shops || @yh_shops then 		#どっちかしかないなら
	@shops = (@db_shops || @yh_shops)	#あるほうを代入
else  									#どっちもないならnilを代入
	@shops = nil
end

またもやRailsらしいスマートな書き方じゃないけど、一応動いたのでこれでOKとしました。

これで検索機能の実装はほぼ完了になりました。

検索画面で入力すれば

検索するとこのように一覧表示ができました^^