前提
以下の前提で、ひとまずローカル環境(Rails s
)での検索を実現する
- Ruby on Rails で多対多のモデル(例としてPostとCategoryとします)を使っている
- Ancestryを使ってモデルを多階層にしている(Categoryが多階層です)
- Elasticsearch と Kibana がインストール済み
- Searchkick(Elasticsearchを簡単にするGem)を使って検索をしたい
- Postを検索するときに検索対象にCategory.nameを含めたい
Post
とCategory
が中間テーブルを使って多対多の関係であり、Categoryだけ多階層になっている。
構築がまだの方へのおすすめ記事
中間テーブル実装後にカテゴリ別ページを表示
https://qiita.com/otterminal/items/a8859a1fad0027a9bc5b
多階層カテゴリでancestryを使ったら便利すぎた
https://qiita.com/Sotq_17/items/120256209993fb05ebac
ElasticsearchとKibanaのインストール(HomebrewでOSS版をインストール)
https://qiita.com/maztak/items/0f722ad01c982a96de59
速攻で検索機能を実装できるsearchkickを調べた(Ruby)
https://qiita.com/kentosasa/items/f0af67f62f4692d68370
ただしElasticsearch、Ancestry、Searchkickなどは更新により変更が大きいので色んな方法がネットに転がっているので注意。
公式リポジトリ
Searchkick
https://github.com/ankane/searchkick
Ancestry
https://github.com/stefankroes/ancestry
コード
普通にsearch_data
の中でcategories.map(&:name)
としてやれば良い。
class Post < ApplicationRecord
searchkick
has_many :post_category_relations
has_many :categories, through: :post_category_relations
def search_data
{
name: name,
description: description,
category_name: categories.map(&:name)
}
end
end
Categoryのインデックスはまだしてないのだが、今のところ以下のようになっている。after_commit
以降のコードはカテゴリーが更新された時にPostをReindexしてね、というコードなので、今回の件とは直接は関係しない。
class Category < ApplicationRecord
has_many :post_category_relations
has_many :posts, through: :post_category_relations
has_ancestry
after_commit :reindex_post
def reindex_post
post.reindex
end
end
Searchkick開発者の回答👇
Rails: Elasticsearch :through association mapping
https://stackoverflow.com/questions/19721200/rails-elasticsearch-through-association-mapping/19751244
Reindex は Rails Console の再起動の必要あり
あくまで開発環境(ローカル・developmentインデックス?)での話だが、Modelでsearch_data
メソッドやMappingを変更した後に Rails Console を再起動せずにPost.reindex
しても、変更前の情報でインデックスしてしまうので注意。
Kibanaでの検証Tips
http://localhost:5601/app/kibana#/dev_tools/console
Kibanaでのインデックス一覧やマッピング、検索結果の確認・検証において、上記コンソールを使うが、ここは(SwiftのPlaygroundのように)記述したブロックごとの実行が可能。なのでいちいちHistroyからメソッドを全部書き換えないでも、よく使うメソッドを全部貼り付けて適宜ブロックごとに実行すればいい。
# kiban_console
# インデックスの削除(エイリアスでの指定は不可)
DELETE /posts_development_20200708003504586
# インデックス一覧の確認
GET /_aliases?pretty
# マッピングの確認
GET /posts_development/_mapping?pretty
# すべてのドキュメントを返す(上限100件)
GET /posts_development/_search
{
"query": {
"match_all": {}
},
"size": 100
}
# 複数のフィールドを検索対象にして検索
GET /posts_development/_search
{
"query": {
"multi_match": {
"fields": [ "name", "description", "category_name"],
"query": "美容"
}
}
}
#
GET posts_development/_analyze
{
"text" : "美容"
}
その他のメソッドはこちらを参照
その他
as_json
を使った関連付けされたデータの追加というQiitaもあったが、これは1対多で中間テーブルがない場合の記法のようである。