Railsで「Chewy」を用いて簡単に検索機能を実装する

##はじめに
[Chewy](https://github.com/toptal/chewy#installation)は、RailsのmodelをElasticsearchで扱うためのGemの1つです。
Chewyの日本語記事が大変少ないため、今回記事を書いてみようと思いました。

この記事では、Chewyを用いてとりあえず簡単な検索ができるまでの流れをまとめてみました。

とりあえず動かすことを目的としているので細かな解説は行なっていませんが、
今後少しずつまとめていけたらなと思います。

##目標
「タイトル、著者名、出版社名から書籍を検索する」というシンプルな検索をChewyで実装する。


##環境
ruby 2.5.3
rails 6.0.0
chewy 0.8.3
Elasticsearch 6.5.4


##環境構築

###1. Dockerfile,docker-compose.ymlなどを作成する。

```
chewy_sample
 ┣ docker
 ┃ ┣ es
 ┃ ┃ ┗ Dockerfile
 ┃ ┗ mysql
 ┃ ┃ ┣ Dockerfile
 ┃ ┃ ┗ my.cnf
 ┣ Dockerfile
 ┗ docker-compose.yml
```

```dockerfile:./Dockerfile
FROM ruby:2.5.3-stretch

ENV BUNDLE_GEMFILE=/app/Gemfile \
    BUNDLE_JOBS=2 \
    RAILS_ENV=development \
    LANG=C.UTF-8

RUN apt-get update -qq
RUN apt-get install -y build-essential 
RUN apt-get install -y libpq-dev
RUN apt-get install -y nodejs

# ワーキングディレクトリの設定
RUN mkdir /app
WORKDIR /app
```

```yml:./docker-compose.yml
version: "3"
services:
  es:
    build: ./docker/es
    container_name: es
    environment:
      - cluster.name=rails-sample-cluster
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - chewy_sample_data:/usr/share/elasticsearch/data
    ports:
      - 9200:9200
  kibana:
    image: docker.elastic.co/kibana/kibana:6.5.4
    environment:
      SERVER_NAME: localhost:5601
      ELASTICSEARCH_URL: http://chewy_sample:9200
    ports:
      - 5601:5601
    depends_on:
      - es
  db:
    environment:
      - MYSQL_ROOT_PASSWORD=docker
      - MYSQL_PASSWORD=docker
      - MYSQL_USER=docker
      - MYSQL_DATABASE=chewy_sample
    build: ./docker/mysql
    ports:
      - "3306:3306"
  rails:
    build: .
    volumes:
      - .:/app
      - vendor_bundle:/user/local/bundle
    ports:
      - "3003:3000"
    links:
      - db
      - es
    environment:
      - RAILS_DATABASE_USERNAME=root
      - RAILS_DATABASE_PASSWORD=docker
      - RAILS_DATABASE_NAME=rails_chewy_sample
      - RAILS_DATABASE_HOST=db
    tty: true
    stdin_open: true

volumes:
  chewy_sample_data:
    driver: local
  vendor_bundle:
    driver: local
```

```dockerfile:./es/Dockerfile
FROM docker.elastic.co/elasticsearch/elasticsearch:6.5.4
# elasticsearchで日本語を扱うために、kuromojiプラグインを導入
RUN bin/elasticsearch-plugin install analysis-kuromoji
```


```dockerfile:./mysql/Dockerfile
FROM mysql:5.7
RUN touch /var/log/mysql/mysqld.log # 指定の場所にログを記録するファイルを作る
```

```cnf:my.cnf
# http://dev.mysql.com/doc/mysql/en/server-system-variables.html

[client]
port    = 3306
socket    = /var/run/mysqld/mysqld.sock
default-character-set=utf8

[mysqld_safe]
pid-file  = /var/run/mysqld/mysqld.pid
socket    = /var/run/mysqld/mysqld.sock
nice    = 0

[mysqld]
user    = mysql
pid-file  = /var/run/mysqld/mysqld.pid
socket    = /var/run/mysqld/mysqld.sock
port    = 3306
basedir   = /usr
datadir   = /var/lib/mysql
tmpdir    = /tmp
lc-messages-dir = /usr/share/mysql
explicit_defaults_for_timestamp
character-set-server=utf8
expire_logs_days = 2
default-time-zone = '+9:00'

# Instead of skip-networking the default is now to listen only on
# localhost which is more compatible and is not less secure.
#bind-address = 127.0.0.1

#log-error  = /var/log/mysql/error.log

# Recommended in standard MySQL setup
sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES

# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
```

###2. railsプロジェクトの作成
```
$ docker-compose up -d

# Nameの確認
$ docker ps 

# docker ps で確認したNameでrailsのコンテナに入る
$ docker exec -it chewy_sample_rails_1 /bin/bash
```

コンテナ内で下記を実行する

```
/app# bundle init
```

Gemfileが生成されるので下記のように編集する

```:Gemfile
# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

# コメントアウトを外す
gem "rails"
```

Gemfile編集後、コンテナ内で下記コマンドを実行する

```
# railsのコンテナ内
/app# bundle install
/app# bundle exec rails new .
```

###3. 今回用いるGemを追加する

Gemfileに下記の行を追加する

```:Gemfile

# mysql2を追加
gem 'mysql2' 

# chewyを追加
gem 'chewy', '~> 0.8.3'

# fakerを追加
gem 'faker'
```

```
app# bundle install
```

###4. 起動

```
app# rails s -p 3000 -b '0.0.0.0'
```

ここまでできたら、[http://localhost:3000/](http://localhost:3000/)にきちんと動作しているかアクセスして確認してみます。
<img width="661" alt="welcome_rails.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354595/780e6084-b97f-ffe8-1ecd-0e57bd6e546b.png">
おなじみの画面が表示されたら、OK



###Railsの日本語化
必要ないかもしれませんが、Railsの日本語化対応を行います。
下記の記事を参考にRailsの日本語化をさせて頂きました。
[[初学者]Railsのi18nによる日本語化対応](https://qiita.com/shi-ma-da/items/7e5c3d75c9a9f51abdd5)

また、fakerの日本語化を行います。
[ja.yml](https://github.com/faker-ruby/faker/blob/master/lib/locales/ja.yml)をダウンロードして、lib/locales/ja.ymlに配置します。



##モデルの作成
書籍とその著者や出版社のデータを格納するテーブルを用意します。

```shell-session
$ rails g model Book title:string author_id:integer publisher_id:integer price:integer
$ rails g model Author name:string
$ rails g model Publisher name:string
```

```rb:app/models/author.rb
class Author < ApplicationRecord
  has_many :books
end
```
```rb:app/models/book.rb
class Book < ApplicationRecord
  belongs_to :author
  belongs_to :publisher
end
```
```rb:app/models/publisher.rb
class Publisher < ApplicationRecord
  has_many :books
end
```

```shell-session
$ rails db:migrate
```

##Chewyの設定
###Chewyの設定ファイルの作成
config/chewy.ymlの生成

```
$ rails g chewy:install
```

```yml:config/chewy.yml
# config/chewy.yml
# separate environment configs
test:
  host: "es:9250"
  prefix: "test"
development:
  host: "es:9200"

```

##Indexの作成
インデックスを作成します。
今回は、本のタイトル、著者名、出版社名で検索

```
$ mkdir app/chewy
$ touch app/chewy/books_index.rb
```
```rb:app/chewy/books_index.rb
class BooksIndex < Chewy::Index
  define_type Book do
    field :title, type: 'text'
    field :author, type: 'text', value: ->(book) { book.author.name }
    field :publisher, type: 'text', value: ->(book) { book.publisher.name }
  end
end
```

##テストデータの作成
検索できるか試すために、Fakerを用いてテストデータを作成します。
Fakerのロケールの関係で著者名だけが日本人名という変なデータになっちゃいますが、ご愛嬌ということで……

Chewy.strategyを用いることで、データの作成・更新更新に応じて、インデックスも作成・更新されるようになります。

```rb:db/seedsrb
Chewy.strategy(:atomic) do
  50.times do |i|
    Author.create(
      name: Faker::Book.author
    )

    Publisher.create(
      name: Faker::Book.publisher
    )

    Book.create(
      title: Faker::Book.title,
      author_id: i+1,
      publisher_id: i+1,
      price: 450,
    )
  end
end
```

```
$ rails db:seed
```

もし何らかの不具合で、データとインデックスに相違が生じた場合、下記のコマンドでインデックスのリセットができます。

```
$ rails chewy:reset
```
インデックスの作成を終えれば、検索を行うための準備は終了です。

##検索してみる
検索を行うための準備が整いました。
rails consoleを開いて、書籍を検索してみましょう。

```
# 本のタイトルで検索
> result = BooksIndex.query(match: {title: '<検索したい文字列>'})
> result.to_a
```
```
# 著者名で検索
> result = BooksIndex.query(match: {author: '<検索したい文字列>'})
> result.to_a
```
```
# 出版社で検索
> result = BooksIndex.query(match: {publisher: '<検索したい文字列>'})
> result.to_a
```

##終わりに
とりあえず検索はできるようになりました。
細かい解説は今後少しずつ上げていこうと思います。
自分自身まだわからないところもたくさんあるので誤っている点など多々あると思いますが、ご指摘いただけると幸いです。

##参考にした記事
https://github.com/toptal/chewy#installation
https://qiita.com/yamashun/items/6ecaa6f161b4cf283db3