概要
- TechRachoの週間RailsウォッチをみてAlgolia Searchに興味を持ったので、Algoliaを使ったアプリを作りました
- 東京都府中市の粗大ゴミシール料金を検索して表示するアプリです。
アプリ
こちらから使えます。(もし使えなかったら、それはalgoliaの使用上限に達したからです。あしからず。)
https://fuchu-gomi-sticker.herokuapp.com
ソース
動作イメージ
解説
概要
- モデル作成と府中市の粗大ゴミシール料金表データのインポート
- algoliaを使ってサーバーサイドで検索
- algoliaを使ってフロントエンドでオートコンプリート
- Bulmaを使って飾り付け
モデル作成と府中市の粗大ゴミシール料金表データのインポート
府中市が公開している粗大ゴミシールのCSVは下記の様になっています。
基本はこれを取り込みます。
Trashモデルをつくります。カラム構造は以下の通りです。
class CreateTrashes < ActiveRecord::Migration[6.0]
def change
create_table :trashes do |t|
t.string :name # 品目
t.string :name_furi # 品目のふりがな(ひらがな)
t.integer :disposal_cost # 粗大ゴミシールの料金
t.boolean :disposable, default: true # 引き取ってもらえるかかどうか(今回は説明しません)
t.string :import_file # データcsvファイル名(今回は説明しません)
t.string :memo # メモ(今回は説明しません)
t.date :updated_on # データ更新日(府中市HPに書かれている日付です。)
t.timestamps
end
end
end
imortはrake taskで行います。
namespace :db do
desc '府中市のcsvをインポート'
# https://www.city.fuchu.tokyo.jp/kurashi/gomirisaikuru/dashikata/sodaigomidasikata.html
# docker-compose exec web rake db:import['tmp/gojuon3012.csv','2019-01-05']
task :import, %w[filename updated_on] => :environment do |_task, args|
raise if args['filename'].nil? || args['updated_on'].nil?
date = Date.parse(args['updated_on']).to_date
Trash.import_csv(args['filename'], date) # データを取り込む
Trash.all.each(&:update_name_furi) # フリガナをふる
end
end
impor rakdeで使っている、importするメソッドをTrashにはやします。importは acitiverecord-import
を使います。
配布されているCSVのエンコードcp932であり、UTF-8ではないので変換してあげてから取り込んでいます。
require 'csv'
class Trash < ApplicationRecord
def self.import_csv(file, date)
list = []
ActiveRecord::Base.transaction do
CSV.foreach(file, encoding: 'cp932', headers: true) do |row|
import_file = File.basename(file)
list << Trash.new(trash_params(row).merge(import_file: import_file, updated_on: date))
end
import list # activerecord-import のメソッド
end
end
def self.trash_params(row)
{
name: row['品目'],
disposal_cost: row['料金'] == '不可' ? nil : row['料金'].sub(',', ''), # csvを見ると 不可書かれているデータがあったことからその場合の変換を追加。
disposable: row['料金'] == '不可'# csvを見ると 不可書かれているデータがあったことからその場合の変換を追加。
}
end
end
ただし、このまま取り込むと、ひらがなの検索ができないので、使い勝手がいまひとつ。なので、漢字をひらがなに変換して別カラム name_furi
に取り込みます。
Trash.all.each(&:update_name_furi)
変換はrubyfuriを使いました。使うためにはYahoo Developer APIの入手が必要なので事前に取得して .env
に記述してください。
def update_name_furi(force: false)
return if name_furi.present? || !force
rubyfuri = Rubyfuri::Client.new(ENV['YAHOO_JAPAN_DEVELOPER_CLIENT_ID'])
update(name_furi: rubyfuri.furu(name))
end
これでデータの準備は完了です。
algoliaを使ってサーバーサイドで検索
Algolia API入出
まずalgoliaでアカウント作成しAPIを入手し、.env
に記述してください。
algoliasearch-railsインストール
gem algoliasearch-railsをつかいます。設定はこのgemのREADMEを元に行います
algoliasearch-rails設定
APIキーを記述します。本アプリではAPIキーはdotenvを使っているので ENV[XXXX]
でよびだしています。直接かいてもいいです。
algoliaはkaminariとwill_paginateにも対応しています。 pagination_backend
にお使いのgemを記述してください。
AlgoliaSearch.configuration = {
application_id: ENV['ALGOLIA_APPLICATION_ID'],
api_key: ENV['ALGOLIA_ADMIN_API_KEY'],
connect_timeout: 2,
receive_timeout: 30,
send_timeout: 30,
batch_timeout: 120,
search_timeout: 5,
pagination_backend: :kaminari # 使用しているページネーションのgemを記載
}
TrashモデルにAlgoliaを使うためのコードを追加します。
class Trash < ApplicationRecord
include AlgoliaSearch
algoliasearch do
attributes :name, :name_furi, :disposal_cost # インデックスしたいカラムを書く
searchableAttributes %w[name name_furi] # 実際検索対象となるカラムを書く
tags do # タグをつけたい場合は書く
t = [disposable ? '粗大ゴミ可' : '粗大ゴミ不可']
t << '無料' if disposal_cost&.zero?
t
end
customRanking ['desc(disposal_cost)'] # 検索結果の重みが同順位の場合更にソートしたい場合は書く(今回はシールの値段が高い順にならべました)
end
インデックスを作成するためのコマンドを実行します。
rails c
> Trash.reindex
これで検索の準備は完了です。
Algoliaを使って検索するには、以下の通りです。 Tarsh.search
でもよいのですが、 もしransackを入れている場合ぶつかりますので、 algolia_search
がおすすめです。
paginationのページ番号は page:
にかいてください。
class HomeController < ApplicationController
def index
@trashes = Trash.algolia_search(params[:query], hitsPerPage: 10, page: params[:page])
end
end
あとは、普通のrailsと同じです。
ここまでのgithubのコードはこちらのリリースです。
algoliaを使ってフロントエンドでオートコンプリート
サーバーサイドで検索しましたので、今度はフロントエンドから検索して、オートコンプリートを実現します。
基本的なやり方は algolia/autocomplete.jsをご覧下さい。railsで使うためのインストールを解説します。(別にCDNを使ってライブラリを使う場合は適当に読み飛ばしてください。)
必要なlibraryをインストールします。
yarn add autocomplete.js
yarn add algoliasearch
呼び出すためのjsファイルを追加します。
const algoliasearch = require('algoliasearch'); // 呼び出す
const autocomplete = require('autocomplete.js'); // 呼び出す
const algolia_application_id = 'ALGOLIA_APPLICATION_ID'; // API KEY
const algolia_search_only_api_key = 'ALGOLIA_SEARCH_ONLY_API_KEY'; // API KEY
const client = algoliasearch(algolia_application_id, algolia_search_only_api_key);
const index = client.initIndex('Trash');
autocomplete('#query', {hint: false}, [ // id=queryの inputフォームを対象
{
source: autocomplete.sources.hits(index, {hitsPerPage: 5}), // 何件表示するか
displayKey: function (suggestion) {
return suggestion.name
},
templates: {
suggestion: function (suggestion) {
return `${suggestion._highlightResult.name.value} / ${suggestion._highlightResult.name_furi.value}`; // 候補表示する文字列
},
empty: '検索キーワードを見直してください' // 検索結果がない時の表示
}
}
]).on('autocomplete:selected', function (event, suggestion, dataset, context) {
console.log(event, suggestion, dataset, context);
if (context.selectionMethod === 'click') {
window.location.assign(`/?query=${suggestion.name}`); // 候補をクリックしたときの動作。今回は候補名をもとにGETリクエストを送ります。
return;
}
});
上記のjsファイルを呼び出します。
// inputフォームのidがquery。もしこれが異なる場合は上記の #queryを置き換えてください。
= f.text_field :query, value: params[:query], class: 'input', autocomplete: 'off'
// 省略
= javascript_pack_tag 'suggestionForm'
これで、autocompleteで表示されるようになります。
このままだと文字が重なって見にくいので、autocomplete.jsで紹介されているcssを追加しておきます。
.algolia-autocomplete {
width: 100%;
}
.algolia-autocomplete .aa-input, .algolia-autocomplete .aa-hint {
width: 100%;
}
.algolia-autocomplete .aa-hint {
color: #999;
}
.algolia-autocomplete .aa-dropdown-menu {
width: 100%;
background-color: #fff;
border: 1px solid #999;
border-top: none;
}
.algolia-autocomplete .aa-dropdown-menu .aa-suggestion {
cursor: pointer;
padding: 5px 4px;
}
.algolia-autocomplete .aa-dropdown-menu .aa-suggestion.aa-cursor {
background-color: #B2D7FF;
}
.algolia-autocomplete .aa-dropdown-menu .aa-suggestion em {
font-weight: bold;
font-style: normal;
}
ここまでのリリースはこちらを参照ください。
Bulmaを使って飾り付け
Bulmaをインストールします。
yarn add bulma
application.cssをapplication.scss にリネームしてBulmaをimporとする記述を追加する。
/*
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
* vendor/assets/stylesheets directory can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
* files in this directory. Styles in this file should be added after the last require_* statement.
* It is generally better to create a new file per style scope.
*
*= require_tree .
*= require_self
*/
@import "bulma/bulma"
あとは、以下のような感じで書きます。
h2.title 東京都府中市 粗大ごみの料金検索
div
p 府中市が公開している「粗大ごみの料金表」より該当品のシール代金を表示します
p 粗大ゴミとして廃棄したい物品の名前を入力して検索してください。
p シールの購入方法はこちらの
p = link_to '府中市粗大ごみの出し方', 'https://www.city.fuchu.tokyo.jp/kurashi/gomirisaikuru/dashikata/sodaigomidasikata.html'
p からお願いします。
= form_with url: root_path, method: :get, local: true, autocomplete: 'off' do |f|
.field.has-addons
.control.is-expanded
= f.text_field :query, value: params[:query], class: 'input', autocomplete: 'off'
.control
= f.submit '検索', class: 'button is-info'
.container
div.columns.is-multiline
- @trashes.each do |trash|
div.column.is-6.is-12-mobile
.card
header.card-header
p.card-header-title = trash.name
.card-content
.content
p = "#{trash.disposal_cost} 円"
footer.card-footer
.card-footer-item
= trash.updated_on
= paginate @trashes
= javascript_pack_tag 'suggestionForm'
以上。
今後
- geo locationをつかって、距離で検索もできるのでそちらを試します。 https://github.com/algolia/algoliasearch-rails#geo-search
- いい感じなので、そのうち、 https://www.dokode.work/ に適用します。