ウェブエンジニア珍道中

日々の技術的に関する経験を書いていきます。脱線もしますが助けになれば幸いです。

docker-composeでのRails環境にElasticsearchを組み込む

dockerを用いたRailsの開発環境に新たにElasticsearhを組み込んだのでまとめます。

Dockerの設定

docker-compose.ymlを以下のように修正します。

version: '2'

services:
  rails:
    # ~ imageとか元々の設定(省略) ~ 

    # 追加
    depends_on:
      - elasticsearch

  # 追加
  elasticsearch:
    image: elasticsearch:5.6.10


  # 追加
  kibana:
    image: kibana
    ports:
      - 5601:5601
    depends_on:
      - elasticsearch

Elasticsearchが動くコンテナを追加して、depends_onで起動順を制御しています。

kibanaも入れています、ブラウザ上で操作・確認がしやすいからです。

Rails側の設定

gemを追加

続いてGemfileにelasticseach用のgemを追加します。使うgemはこちらです。

github.com

以下をGemfileに記述します。

gem 'elasticsearch-model', git: 'https://github.com/elasticsearch/elasticsearch-rails.git', branch: "5.x"
gem 'elasticsearch-rails', git: 'https://github.com/elasticsearch/elasticsearch-rails.git', branch: "5.x"
bundle install

branch: "5.x"でバージョンを指定しているのに注意して下さい。elasticsearchが今回5系を入れているためgemのバージョンも5系に合わせています。これを合わせないと動きません。最初6.xと5.xの組み合わせでハマりました。詳しくはこちら。

File: README — Documentation for elasticsearch-model (5.1.0)

設定ファイルを追加

config/initializers/elasticsearch.rbを用意し、以下を記述します。

Elasticsearch::Model.client =
  Elasticsearch::Client.new hosts: [
    {
      host: 'elasticsearch',
      port: '9200'
    }
]

このファイルで接続するためのhostとportを指定します。docker-composeで作成したため、hostはコンテナ名と同じelasticsearchでOKです。詳しくは割愛しますが、DNSが内部的に作られ解決してくれています。portはデフォルトで9200です。

Modelに組み込む

実際にElasticsearchを扱うModelに記述していきます。今回は例としてBookクラスに記述します。

require 'elasticsearch/model' # 追加

class Book
  include Elasticsearch::Model # 追加
end

実際に動かしてみる

これで必要最低限の準備が整いました。rails consoleでデータの入れ込みをしてみます。

docker-compose run rails rails c
[1] pry(main)> Book.__elasticsearch__.create_index!
# インデックスが作られる

[2] pry(main)> Book.__elasticsearch__.import
# 元のDB(mysqlなど)から1000件ずつ全データを取ってきてinsertする

Elasticsearch::Modelをincludeすることで上記のメソッドが使えるようになり、データの作成ができます。

試しに検索をしてみます。

[3] pry(main)> Book.__elasticsearch__.search(query: {match_all: {}}).records.to_a
=> [(Bookクラスのインスタンスの配列…)]
# 全件取得(elasticsearchのデフォルト設定により10件)されたBookモデルのインスタンスが取れる

[4] pry(main)> Book.__elasticsearch__.search(query: { match: { name: "hoge" } }).records.to_a
=> [(Bookクラスのインスタンスの配列…)]
# nameに"hoge"が含まれたデータを取得

__elasticsearch__.searchメソッドの引数にElasticsearchのクエリを入力することで検索ができます。これで最低限の環境を揃えることができました。

localhost:5061にアクセスすることでkibana上で確認することもできます。

f:id:te-nu:20180726194041p:plain

また今度はリレーション先のデータをimportする時の設定などをまとめたいと思います。では。

Elasticsearch実践ガイド (impress top gear)

Elasticsearch実践ガイド (impress top gear)

Railsで特定のModelだけ別DBを操作するようにしてみた

システム間連携等の事情で、別DBを作ってデータを流し込みたくなったので実装してみました。

手順

例としてBookモデルを作り、別DBを対象にするようにします。

環境はmysqlです。元々使っているDBはmain_database、新しく使うDBはsub_databaseという名前にしています。

対象となるDBと、テーブルを作っておく

別DBによりmigrationの管理外なので、予めスキーマ(DB)とテーブルを作っておきます。

CREATE DATABASE sub_database;

use sub_database;

CREATE TABLE `books` (
  `id`         int(11) NOT NULL AUTO_INCREMENT,
  `name`       varchar(255) NOT NULL,
  `author`     varchar(255) NOT NULL,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

config/database.ymlに別DB用の設定を書く

元々の設定をベースに新しい設定項目を増やします。

環境(stagingとか)にprefixを付ける形で名付けるとRails.env + 名前の要領で指定できるので環境毎に用意がしやすいです。

今回は同じmysql内の違うデータベースに保存するので元の設定をベースにdatabaseの欄だけ上書きしたものを用意します。

# 元々の設定
development: &development
  adapter: mysql2
  database: main_database
  encoding: utf8mb4
  charset: utf8mb4
  username: test_user
  password: any_password
  host: localhost

# 追加する
another_db_development:
  <<: *default
  database: sub_database

モデルに別DB用の設定を使うように指定する

他のモデルと同様app/modelに定義します。

booksテーブルの場合Bookクラスといった要領で単数系→アッパーキャメルケースにしてあげましょう。勝手にテーブルを見つけてくれます。ここは元のRailsのお作法通りです。

class Book
  establish_connection("another_db_#{Rails.env}".to_sym)
end

establish_connectionconfig/database.ymlで作った設定を指定することで別のDBを参照するようにしています。

使ってみる

普通のmodelを使うときと同じノリでOKです。

Book.create(name: "こころ", author: "夏目漱石")

Book.first # ↑で作ったものが取れる

別DBなのでjoinは動きませんが、手軽にアクセスができる環境ができるのでRailsはすげーなーと改めて思いました。

migrationが別管理なので、今後の課題です。(別PJによる管理とかなら問題なさそう)

Rubyのtapメソッドが便利なので紹介する

RubyのObject#tapが便利だったので紹介します。(昔からあるらしいけど)

tapメソッドとは

ブロック文を渡すと中身の処理を走らせてレシーバを返すメソッドです。

hoge = "test".tap do |string|
  puts string
  true
end
puts hoge # "test"

Objectクラスに定義されているので大体のクラスで使えます。

メリット

レシーバを返さない破壊的メソッドにレシーバを返させることができます。

いちいち変数を作って入れて…って処理を省いたりできます。

Railsのsaveとか

例えば「DBに保存してActiveRecordのインスタンス変数からidを取り出して返すメソッド」なんてものを作る時には

def save_something_record
  some_model = SomeModel.new(params)
  some_model.save # saveの返り値はtrue/false
  some_model.id
end

saveメソッドがtrue/falseを返すために変数に一度入れないといけないところが

def save_something_record
  SomeModel.new(params).tap(&:save).id
end

と1行で書けます。saveメソッドはtrue/falseを返しますが、tapメソッドにより保存処理後のレシーバが返されます。

RSpecなんかでちょっとしたテストデータを用意する時もちょっとした小技として使えます。

HogeServiceを作って実行した後のインスタンスが欲しいなんて時には以下でOKです。

let(:executed_hoge_service) { HogeService.new.tap(&:execute) }

変数名とかを考える手間が減って便利な小技でした。

RailsでSlimを使った時に起こる「開発環境だけ見た目が崩れる問題」の解消

問題について

https://github.com/slim-template/slim-railsを使って開発をしていると、特定の条件で開発環境下(development)でのみ見た目が崩れる減少が起きていました。

長らく原因がわからず解消できませんでしたが、先日ようやく何とかできたので紹介します。

発生条件

CSSで以下の設定が入っているDOM要素で起こりました。

white-space: pre-wrap;

これは「スペースは詰めずにそのまま表示してね」というもので、意図して入力された空白をそのまま表示するために入れていました。

このCSSの設定を入れると開発環境でのみ見た目が崩れてました。

対策

config/environments/development.rbにこの設定を書き換えると直ります。

Slim::Engine.set_options pretty: false

原因

slim-railsがdevelopment環境下ではHTMLの見やすさのためか、不要なスペース(タブ)を入れていたのが原因です。

CSSの設定によってはそのまま表示してしまうため、見た目が崩れてしまいます。

またその処理がなくてもブラウザのデベロッパーツールで見ると整形してくれているので、いらないケースが多いです。

なので上記の設定を書き加えて無効化してやるわけですね。

本番環境では大丈夫ですが、開発時に見た目が崩れてチェックができなかったりしたので何とかできて良かったです。

RubyでURLをaタグに書き換える処理を書いた

DBから取ってきた文字列を表示する際に、URLが混ざっていた場合aタグに置き換えて表示する必要があったためその処理を書きました。

コードはこんな感じです。

require 'uri'

def convert_url_into_a_tag(text)
  text.gsub(URI.regexp(['http', 'https'])) do |text|
    "<a href='#{text}'>#{text}</a>"
  end
end


# 検証用の文字列作成
text = <<EOS
私のブログのURLは
http://www.te-nu.com/です!
もう一度いいます
http://www.te-nu.com/です!
EOS

# 実行した結果を表示
puts convert_url_into_a_tag(text)

実行結果

私のブログのURLは
<a href='http://www.te-nu.com/'>http://www.te-nu.com/</a>です!
もう一度いいます
<a href='http://www.te-nu.com/'>http://www.te-nu.com/</a>です!

ざっくり解説

String#gsubURI.regexpを組み合わせました。 URI.regexpでURLを抜き出すための正規表現を取得することができるので、あとは gsubメソッドで置換を行うことで上手くaタグに変換することができました。

Railsの場合は app/helpers/application_helper.rbあたりに定義して必要な場合に使ったりするといいんじゃないかなーと思います。