Rebuild.fmのShownoteを検索するサービスを作りました


僕は何を隠そうRebuild.fmのヘヴィリスナーです。
Rebuildは毎回新しい技術ネタが豊富なのですが、話題が出てから数ヶ月してそれが流行りだしてから「これRebuildで聞いたやつだ！」となり改めて当時のエピソードを聞き直してみたい、となることが多々あります。

最近だとReduxについての話題がそうでした。
Episode.114で初めて話題に登った時にはなんとなく聴き流していたのですが（これに関してはMCのmiyagawa氏も「それ流行るの？」的なリアクションだったけども）、
各所で名前を見るようになり、Web+DBPressの記事を読んでからそういえばと思い出して改めてEpisode.114を聴き直したことがつい最近ありました。


というわけで、Rebuildで話題になったネタについて話しているエピソードを楽に検索したいと思いつきました。

http://rebuildsearch.jacoyutorius.com/

RSSで各エピソードやShownoteの情報が取れるので、それをDBに突っ込んでしまえば検索できるだろうと。
勉強がてらElasticsearchやReactを使っていまが、まだ1使いこなせているとは言えないですね。。


開発時はローカルでRailsアプリを構築し、ElasticsearchはVagrant上に構築しています。


## Elasticsearch

VagrantへのElasticsearchセットアップにはitamaeでレシピを書きました。
Elasticserch本体と、幾つかのプラグインも一緒に入れています。
レシピやVagrantFileもろもろはRailsアプリケーションとは別に以下のリポジトリにまとめています。

https://github.com/jacoyutorius/itamae-centos7-nginx-ruby-serverspec

cookbook/elasticsearch/default.rb

```ruby
package "java-1.8.0-openjdk.x86_64" do
  action :install
end

# https://www.elastic.co/guide/en/elasticsearch/reference/current/setup-repositories.html
template "/etc/yum.repos.d/elasticsearch.repo" do
  user "root"
  group "root"
  source "./templates/etc/yum.repos.d/elasticsearch.repo.erb"
  not_if "test -e /etc/yum.repos.d/elasticsearch.repo"
end

package "elasticsearch" do
  action :install
end

# kuromoji
# https://www.elastic.co/guide/en/elasticsearch/plugins/2.0/analysis-kuromoji.html
execute "install analysis-kuromoji" do
	user "root"
	cwd "/usr/share/elasticsearch"
	command "bin/plugin install analysis-kuromoji"
  not_if "test -e /usr/share/elasticsearch/plugins/analysis-kuromoji"
end

template "/etc/elasticsearch/elasticsearch.yml" do
  user "root"
  group "root"
  source "./templates/etc/elasticsearch.yml.erb"
end

# user,groupをelasticsearchにしないとサービス起動でエラーになる
execute "change owner to elasticsearch" do
	user "root"
	cwd "/etc/"
	command "chown -R elasticsearch elasticsearch;chgrp -R elasticsearch elasticsearch"
end
```

## Rails + Elasticsearch

Railsにelasticsearch-rails gemを組み込むのは以下の記事を参考にしています。

[Elasticsearchを使ったRailsサンプルアプリの作成](http://morizyun.github.io/blog/elasticsearch-rails-tutorial/)

ほとんど上記のサンプルコードのままです。
ElasticsearchはVagrant上で構築したのでVMのホスト名をモデル内で設定しています。

config/environments/development.rb

```ruby
Rails.application.configure do
  # elasticsearch server host
  config.elasticsearch_server_host = "192.168.33.10:9200"
end
```

app/models/shownote.rb

```ruby
# == Schema Information
#
# Table name: shownotes
#
#  id         :integer          not null, primary key
#  episode_id :integer
#  title      :string
#  link        :string
#  created_at :datetime         not null
#  updated_at :datetime         not null
#

class Shownote < ActiveRecord::Base
  belongs_to :episode, foreign_key: :episode_id, class_name: :Episode

  include Elasticsearch::Model

  self.__elasticsearch__.client = Elasticsearch::Client.new host: RebuildFulltextSearch::Application.config.elasticsearch_server_host, log: true

  INDEX_FIELDS = %w(title link).freeze
  index_name "rebuild_shownote_#{Rails.env}"

  settings do
    mappings dynamic: "false" do
      indexes :title, analyzer: "kuromoji", type: "string"
      indexes :link,  analyzer: "kuromoji", type: "string"
    end
  end

  def as_indexed_json(options = {})
    self.as_json.select{|k, _| INDEX_FIELDS.include?(k) }
  end

  def self.create_index!
    client = __elasticsearch__.client
    client.indices.delete index: self.index_name rescue nil
    client.indices.create(
      index: self.index_name,
      body: {
        settings: self.settings.to_hash,
        mappings: self.mappings.to_hash
      }
    )
  end
end
```

app/controllers/shownotes_controller

```ruby
# GET /shownotes
# GET /shownotes.json
def index
  @shownotes = if params[:search]
    Shownote.search(query:{match: {_all: params[:search] }}).records
  else
    Shownote.includes(:episode).all
  end

  if request.format.to_s == "application/json"
    @shownotes = @shownotes.map{|r|
      {
        id: r.id,
        episode_id: r.episode_id,
        title: r.title,
        link: r.link,
        episode_no: r.episode.episode_no,
        episode_title: r.episode.title ,
        episode_subtitle: r.episode.subtitle,
        episode_link: r.episode.link,
        episode_pubdate: r.episode.pubdate
      }
    }
  end

end

```


## React

フロント側はReact+Reduxで実装しています。
あまり動きの無いアプリケーションなのでReduxを使っている意味はあまりないですね。。
コードはWeb+DBPress Vol.92の特集に載っていたものを参考にしています。

http://gihyo.jp/magazine/wdpress/archive/2016/vol92

(余談ですが、Vol.92にはReduxの記事以外にもRails+React構成でのTODOアプリ作成サンプルなどが載っており大変参考になりました。)


サーバーサイドへのリクエストはsuperagentを使い、レスポンスをJSONフォーマットで受け取っています。

frontend/src/app.jsx

```jsx

  onKyewordChange(e){
    this.setState({keyword: e.target.value});

    var url = "/shownotes.json?search=" + this.state.keyword
    request.get(url)
      .accept("application/json")
      .end((err, res) => {
        if(err || !res.ok){
          console.error(this.props.url, status, err.toString())
        }
        else{
          this.setState({data: res.body})
        }
      })
  }

```

jsxのコンパイルはwebpackを使っていますが、これらをRailsの構成ディレクトリにどう組み込めば良いのか、悩みましたが以下の記事を参考にfrontendディレクトリを作成してその中にpackage.jsonほか一式を入れてしまいました。
jsxを触るときはfrontendディレクトリに移動して"webpack --watch"しています。

[WebPackを使ってRailsからJavaScriptを楽に良い感じに分離する](http://qiita.com/necojackarc/items/afa674ab10aafa9784eb)

```
$ tree -L 3
.
├── app
│   ├── assets
│   │   ├── images
│   │   ├── javascripts
│   │   └── stylesheets
│   ├── controllers
│   │   ├── application_controller.rb
│   │   ├── concerns
│   │   ├── episodes_controller.rb
│   │   ├── main_controller.rb
│   │   └── shownotes_controller.rb
│   ├── helpers
│   ├── mailers
│   ├── models
│   │   ├── concerns
│   │   ├── episode.rb
│   │   └── shownote.rb
│   └── views
│       ├── episodes
│       ├── layouts
│       ├── main
│       └── shownotes
├── bin
├── config
├── config.ru
├── db
├── frontend
│   ├── package.json
│   ├── src
│   │   ├── app.jsx
│   │   └── index.html
│   └── webpack.config.js
├── lib
│   ├── assets
│   └── tasks
│       ├── elasticsearch.rake
│       └── rebuild.rake
```

## 画面デザイン

bulmaのコンポーネントを使って構成しています。

http://bulma.io/

なんだかんだで一番時間がかかったのがこのデザイン部分だった気がします。
配色は当初bulmaのデフォルトのままだったのが、ちまちまあれでもないこれでもないといじっていました。

コンポーネントの配置は変えておらず、背景や色などをチマチマいじっていたのですが、なかなか好みの感じになりません。。
以下は修正途中のものです。見づらいですね。

![](https://s3-ap-northeast-1.amazonaws.com/jacoyutorius/pictures/design_1.png)

現在のものは結構いい感じになったと思いますがどうでしょうか。
テーマカラーと背景画像がバッチリ着地した感があったので決定としました。
本当はこれぐらいのものを一発で作り上げたいものです。


![](https://s3-ap-northeast-1.amazonaws.com/jacoyutorius/pictures/design_2.png)