僕は何を隠そうRebuild.fmのヘヴィリスナーです。
Rebuildは毎回新しい技術ネタが豊富なのですが、話題が出てから数ヶ月してそれが流行りだしてから「これRebuildで聞いたやつだ!」となり改めて当時のエピソードを聞き直してみたい、となることが多々あります。
最近だとReduxについての話題がそうでした。
Episode.114で初めて話題に登った時にはなんとなく聴き流していたのですが(これに関してはMCのmiyagawa氏も「それ流行るの?」的なリアクションだったけども)、
各所で名前を見るようになり、Web+DBPressの記事を読んでからそういえばと思い出して改めてEpisode.114を聴き直したことがつい最近ありました。
というわけで、Rebuildで話題になったネタについて話しているエピソードを楽に検索したいと思いつきました。
RSSで各エピソードやShownoteの情報が取れるので、それをDBに突っ込んでしまえば検索できるだろうと。
勉強がてらElasticsearchやReactを使っていまが、まだ1使いこなせているとは言えないですね。。
開発時はローカルでRailsアプリを構築し、ElasticsearchはVagrant上に構築しています。
Elasticsearch
VagrantへのElasticsearchセットアップにはitamaeでレシピを書きました。
Elasticserch本体と、幾つかのプラグインも一緒に入れています。
レシピやVagrantFileもろもろはRailsアプリケーションとは別に以下のリポジトリにまとめています。
cookbook/elasticsearch/default.rb
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サンプルアプリの作成
ほとんど上記のサンプルコードのままです。
ElasticsearchはVagrant上で構築したのでVMのホスト名をモデル内で設定しています。
config/environments/development.rb
Rails.application.configure do
# elasticsearch server host
config.elasticsearch_server_host = "192.168.33.10:9200"
end
app/models/shownote.rb
# == 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
# 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の特集に載っていたものを参考にしています。
(余談ですが、Vol.92にはReduxの記事以外にもRails+React構成でのTODOアプリ作成サンプルなどが載っており大変参考になりました。)
サーバーサイドへのリクエストはsuperagentを使い、レスポンスをJSONフォーマットで受け取っています。
frontend/src/app.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を楽に良い感じに分離する
$ 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のコンポーネントを使って構成しています。
なんだかんだで一番時間がかかったのがこのデザイン部分だった気がします。
配色は当初bulmaのデフォルトのままだったのが、ちまちまあれでもないこれでもないといじっていました。
コンポーネントの配置は変えておらず、背景や色などをチマチマいじっていたのですが、なかなか好みの感じになりません。。
以下は修正途中のものです。見づらいですね。
現在のものは結構いい感じになったと思いますがどうでしょうか。
テーマカラーと背景画像がバッチリ着地した感があったので決定としました。
本当はこれぐらいのものを一発で作り上げたいものです。
Comments