【Ruby on Rails】ログ出力とログローテートの設定まとめ【Logger】

概要

Rails では ActiveSupport::Logger クラスを利用してログ出力ができます。

railsguides.jp

今回、ログの設定変更やログローテート方法、古いログの削除、オススメのプラグインなどをまとめました。

 

ログの設定

基本的には app/config/environments/*.rb を変更します。

環境毎に設定を分けたくない場合は app/config/application.rb に記載しても OK です。

ログレベルの変更

Logger クラスで指定可能なログレベルは全部で 6 つあります。

レベル 概要 説明
debug デバッグ用の情報 システムの動作状況に関する詳細な情報
info 情報 実行時の何らかの注目すべき事象(開始や終了など)
warn 警告 エラーに近い事象など、実行時に生じた異常とは言い切れないが正常とも異なる何らかの予期しない問題
error エラー 予期しないその他の実行時エラー
fatal 致命的なエラー プログラムの異常終了を伴うようなもの
unknown 不明なエラー ログレベルの把握できない原因不明のエラー

参考:log4j - Wikipedia

ログレベルは debug < info < warn < error < fatal < unknown の順に高くなっています。

ログに出力されるメッセージのログレベルが、設定済みのログレベル以上になった場合に、対応するログファイルにそのメッセージが出力されます。

デフォルトログレベルは全環境で debug です。

デフォルトのログレベルを変更するには以下のような設定を追加します。

config.log_level = :ログレベル

ログファイルの変更

デフォルトのログの保存場所は app/log 配下となります。

ログのファイル名には、アプリケーションが実行されるときの環境名が利用されます。

環境 ログファイル PATH
 development  app/log/development.log
 test  app/log/test.log
 production  app/log/production.log

 

デフォルトのログファイルを変更するには以下のような設定を追加します。

config.logger = Logger.new('ログファイル PATH')

ログフォーマットの変更

ログを出力する call メソッドは 4 つの引数 (severity, time, program name, message) を受け取ります。

call メソッドの返り値は文字列にしてください。

参考:class Logger (Ruby 2.4.0)

config.logger.formatter = proc { |severity, datetime, progname, message|
  # ここでログフォーマットを自由にカスタマイズする
  "[#{datetime}] [#{severity}] -- #{message}\n"
}

 

ログ出力

アプリケーション内でログ出力したい場合は、それぞれのログレベルに対応したメソッドを利用します。

設定したログレベル以下のメソッドは、使用しても出力されませんので注意して下さい。

logger.debug('Hello')
logger.info('Hello')
logger.warn('Hello')
logger.error('Hello')
logger.fatal('Hello')
logger.unknown('Hello')

 

ログローテート

アプリケーションログをいつまでも同一のファイルに出力していると、ログファイルが肥大化して、サーバ容量を圧迫してしまいます。

定期的にログローテートして、古いログファイルは削除することをお勧めします。

ログローテートは「日付」か「ファイルサイズ」のどちらかで行うことができます。

日付でログローテート

「日付」でのログローテートは、何か問題が発生した場合に、発生日時からログを検索しやすいのが利点だと思います。

ログローテートは daily、weekly、monthly から選びます。

ただし、1 日 3 回朝昼晩などの設定ができないので、アクセス量の多いアプリケーションには不向きかもしれません。

config.logger = Logger.new('log/trace.log', 'daily')
config.logger = Logger.new('log/trace.log', 'weekly')
config.logger = Logger.new('log/trace.log', 'monthly')

ファイルサイズでログローテート

「ファイルサイズ」でのログローテートは、サーバリソースを上手く使えるのが利点だと思います。

アクセス量が少ないアプリケーションでは、毎日ログローテートする必要も無いですし、逆にアクセス量の多いアプリケーションでは1日に数回ログローテートした方がいい場合があります。

また「ファイルサイズ」でログローテートした場合、一定期間ログファイルを保存して、期間を過ぎた古いログファイルを削除することができます。

以下の設定ではファイルサイズが 10 M となったらログローテートして、14日以上経過した古いログファイルは削除してくれます。

config.logger = Logger.new('log/trace.log', 14, 10 * 1024 * 1024)

これは Logger クラスの第二引数がログファイルを保持する数か、ログファイルを切り替える頻度 (daily, weekly, monthly) となっているためです。

そのため「日付」でログローテートする場合、古いログファイルの削除ができません。

参考:class Logger (Ruby 2.4.0)

 

その他

ログローテートを本格的にやるのであれば、Logger クラスのオプションでは少ないと思うので、logrotate.conf を作成して、定期的にlogrotate コマンドを叩くのがいいとお思います。

これは Ruby on Rails の話ではなくサーバサイドの話になってしまいますが。。

qiita.com 

まとめ

今回の設定を app/config/environments/development.rb に反映させました。

※ ログ出力周りの設定だけを抜粋しています。

Rails.application.configure do
# ログレベルの設定 config.log_level = :info
# ログ出力ファイルの設定、ログローテートの設定 config.logger = Logger.new('/var/log/rails_app.log', 14, 10 * 1024 * 1024)
# ログフォーマットの設定 config.logger.formatter = proc { |severity, datetime, _progname, message| "[#{datetime}] [#{severity}] -- #{message}\n" }
end

ログを確認すると、指定されたログファイルに指定されたフォーマットで出力されるようになりました。

$ tail -f /var/log/rails_app.log
[2017-12-25 14:11:37] [INFO] -- Started GET "/v1/users/9" for 127.0.0.1 at 2017-12-25 14:11:37 +0900
[2017-12-25 14:11:37] [INFO] -- Processing by UsersController#show as */*
[2017-12-25 14:11:37] [INFO] --   Parameters: {"id"=>"9"}
[2017-12-25 14:11:37] [INFO] -- Hello
[2017-12-25 14:11:37] [INFO] -- Completed 200 OK in 24ms (Views: 0.1ms | ActiveRecord: 12.0ms)

 

【Ruby on Rails】API のレスポンスを生成するメソッドを紹介

概要

Ruby on Rails 5 からは API モードが導入され、API 専用のアプリケーションを簡単に作成できるようになりました。

railsguides.jp

Ruby on Rails では CRUD と呼ばれる HTTP メソッドを利用して、RESTful なアプリケーション(API)を作成できるように設計されています。

CRUD 名 操作 HTTP メソッド Ruby on Rails のデフォルトアクション
CREATE 作成 POST create
READ 読み込み GET index, show, new, edit
UPDATE 更新 PUT/PATCH update
DELETE 削除 DELETE destroy

そのため、リクエストを受ける API のエンドポイント(URL)は比較的悩むことなく、スムーズに作成できるかと思います。

しかし、API のレスポンスは、自分で考えて作成しないといけません。

自分なりのルールがあれば良いのですが、無い場合は設計に悩みます。

今回、自分がレスポンスを作成する際に、過去のソースコードから毎回コピペして使っているメソッドがあるので、それを紹介します。 

 

紹介する前に

GET を利用したリクエストは、『読み込み(取得)』なので、リクエストされたデータをそのままレスポンスで返せば良いと思います。

ユーザ ID 1 番のデータに GET リクエストされた場合は、ユーザ ID 1番のデータを返すのが普通ですよね?

リクエストを投げた人は、そのユーザの名前だったり、性別だったり、年齢だったりを取得したいと思ったはずです。

では POST、PUT/PATCH、DELETE を利用したリクエストを受けた場合は、どうでしょうか?

多分なんですけど ...

一番知りたい情報は、作成・更新・削除 が成功したかどうかだと思います。

なので最低限、成功・失敗のステータスを返せればレスポンスとして成り立ちます。

失敗した場合は、失敗した理由(エラーメッセージ)も返せれば尚良いと思います。

しっかりやりたい人は作成・更新・削除したデータもレスポンスに含めるかもしれません。

 

コピペして使っているメソッド

GET 以外のリクエストを受けた際に利用します。

各 Controller でレスポンスを返す度に、render を記載するとコードが汚くなるので、共通メソッドとしてまとめました。

app/controllers/application_controller.rb などに記載して下さい。

# 200 Success
def response_success(class_name, action_name)
  render status: 200, json: { status: 200, message: "Success #{class_name.capitalize} #{action_name.capitalize}" }
end

# 400 Bad Request
def response_bad_request
  render status: 400, json: { status: 400, message: 'Bad Request' }
end

# 401 Unauthorized
def response_unauthorized
  render status: 401, json: { status: 401, message: 'Unauthorized' }
end

# 404 Not Found
def response_not_found(class_name = 'page')
  render status: 404, json: { status: 404, message: "#{class_name.capitalize} Not Found" }
end

# 409 Conflict
def response_conflict(class_name)
  render status: 409, json: { status: 409, message: "#{class_name.capitalize} Conflict" }
end

# 500 Internal Server Error
def response_internal_server_error
  render status: 500, json: { status: 500, message: 'Internal Server Error' }
end

レスポンスには HTTP ステータスコードとメッセージしか記載していません。

HTTPステータスコード - Wikipedia

HTTP ステータスは沢山ありますが、実際に API で使うのは限られます。

ケース・バイ・ケースで増やしたり減らしたりして下さい。

ステータスコード メッセージ 使う場面(例)
200 Success リクエストに成功した場合
400 Bad Request リクエストに必要なバラメータが欠けていた場合
401 Unauthorized トークンの認証が不正だった場合
404 Not Found 取得しようとしたデータが削除されていた場合
409 Conflict DB に重複するデータを格納しようとした場合
500 Internal Server Error DB や NW で障害が発生した場合

 

使い方

実際に利用しているソースコードを用意しました。

response_success などは何のアクションが成功したのか分かるように、引数にクラス名とアクション名を入れます。

  • response_success(:user, :create) でユーザの作成に成功した
  • response_success(:shop, :update) でお店の更新に成功した
  • response_success(:image, :delete) で画像の削除に成功した

などの情報がレスポンスで返ります。

class UsersController < ApplicationController
  before_action :set_user, only: :show

  # GET /users/1
  def show
    render json: response_fields(@user.to_json)
  end

  # POST /users
  def create
    @user = User.new(user_params)
    if @user.name.blank?
      # 必須パラメータが欠けている場合
      response_bad_request
    else
      if User.exists?(email: @user.email)
        # 既に登録済みのメールアドレスで登録しようとした場合
        response_conflict(:user)
      else
        if @user.save
          # ユーザ登録成功
          response_success(:user, :create)
        else
          # 何らかの理由で失敗
          response_internal_server_error
        end
      end
    end
  end

  private

  def set_user
    @user = User.find_by(id: params[:id])
    # 取得しようとしたユーザが存在しない
    response_not_found(:user) if @user.blank?
  end

  def user_params
    params.require(:user).permit(:name, :email)
  end
  
  # 他のコントローラでも除外したいフィールドが同じであれば、共通メソッドとして扱っても良い
  def response_fields(user_json)
    user_parse = JSON.parse(user_json)
    # レスポンスから除外したいパラメータ
    response = user_parse.except('created_at', 'updated_at', 'deleted_at')
    # JSON を見やすく整形して返す
    JSON.pretty_generate(response)
  end
end

共通メソッドにはしていませんが、最後の response_fields メソッドも結構コピペで使い回します。

created_at, updated_at, deleted_at は運用の観点で DB に必要なカラムなので、レスポンスで返す必要はないと思います。

 

動作確認

API にリクエストを投げて、返ってきたレスポンスの中身を確認します。

GET リクエストを受けた際には、pretty_generate  と言う JSON モジュールを使って、見やすく整形する事をオススメします。

module function JSON.#pretty_generate (Ruby 2.4.0)

GET リクエストに失敗した場合や、GET 以外のリクエストを受けた際のレスポンスは、HTTP ステータスコードとメッセージだけとなっています。

# ユーザの取得に成功
$ curl -X GET "http://localhost/v1/users/1"
{
  "id": 1,
  "name": "aaa",
  "email": "bbb"
}

# 取得しようとしたユーザが存在しない
$ curl -X GET "http://localhost/v1/users/9"
{"status":404,"message":"User Not Found"}

# 既に登録済みのメールアドレスで登録しようとした場合
$ curl -X POST "http://localhost/v1/users" -H "Content-Type: application/json" -d '{"user":{"name":"ccc","email":"bbb"}}'
{"status":409,"message":"User Conflict"}

# 必須パラメータが欠けている場合
$ curl -X POST "http://localhost/v1/users" -H "Content-Type: application/json" -d '{"user":{"name":"","email":""}}'
{"status":400,"message":"Bad Request"}

# ユーザ登録成功
$ curl -X POST "http://localhost/v1/users" -H "Content-Type: application/json" -d '{"user":{"name":"xxx","email":"yyy"}}'
{"status":200,"message":"Success User Create"}

 

おまけ

ページが存在しません

response_not_found は config/routes.rb に記載する事で、存在しないページにアクセスされた時のレスポンスにも利用できます。

get '*path', controller: 'application', action: 'response_not_found'

 

最後に

API のレスポンスを生成するメソッドを紹介しました。

GET リクエストでは、リクエストされたデータをそのまま返せば良いと思いますが、GET 以外のリクエストはどこまで情報を返せば良いか悩みます。

自分は HTTP ステータスコードとメッセージを返しています。

レスポンスでどこまでの情報を返すかは、開発者の優しさだと思います。

情報が多ければ多いほど、一緒に開発しているメンバーは API 周りの開発工数やテストの工数を削減できるかもしれません。

また、公開 API であれば利用者のトラブルが少なくなるかもしれません。

必要であれば、今回のメソッドを拡張して使って下さい。

今回、紹介していませんが、サーバ運用の観点からレスポンスの結果をログに吐き出したりするのも良いと思います。

【備忘録】RAID とは? RAID の種類と仕組みまとめ

概要

最近、仮想サーバばかり扱っていて、実機サーバを自分で購入する機会が無かったので、RAID の種類と仕組みを忘れてしまいました。

忘れないように RAID について調べたことを簡単にまとめました。

 

RAID とは

複数の HDD を組み合わせて、1 つの論理的な記録領域として管理する仕組みです。

HDD が 1 本(又は複数)壊れたとしても、他の HDD でシステムを停止せずに稼働し続けれるように HDD の冗長化をとった仕組みです。

主にシステムの可用性を向上する役割があります。

RAID の種類によっては HDD が故障しても、データを復元できるものもあります。

 

RAID の種類

  • RAID 0:ストライピング
  • RAID 1:ミラーリング
  • RAID 5:分散パリティ
  • RAID 0+1:ストライピングしたデータをミラーリングする
  • RAID 1+0:ミラーリングしたデータをストライピングする

実際に使用される可能性が高いものは、上の種類ぐらいかなと思ったので、これらの役割だけをまとめました。

RAID 0(ストライピング)とは

f:id:kyamanak83:20171119205018p:plain

複数の HDD に対して分散してデータの書き込みを行います。

読み込む際も、複数の HDD から並列に読み込めるので、処理の高速化が図れます。

ただし、冗長化をしていないため、HDD が 1 本でも故障すると全てのデータを失います。

RAID 1(ミラーリング)とは

f:id:kyamanak83:20171119205036p:plain

同一のデータを複数(2本)の HDD に対して書き込みを行います。

一方の HDD が故障しても、他方の HDD にデータが残っているため、処理を継続して行うことができます。

ただし、全く同じデータを複数(2本)の HDD に対して書き込むため、書き込み速度は低下します。また、利用できるディスク容量も半分になります。

RAID 5(分散パリティ)とは

f:id:kyamanak83:20171119205049p:plain

パリティと呼ばれる誤り訂正コードを付与して、複数(3本以上)の HDD に書き込みを行います。

HDD が 1 本故障した場合でも、パリティを元にデータを復元できます。

RAID 0+1 とは

f:id:kyamanak83:20171119211042p:plain

RAID 0 のストライピンググループを、RAID 1 でミラーリングした構成です。

最低でも HDD が 4 本必要となります。

ストライピングのグループが 2 つあった場合、グループ 1 で HDD 故障が発生しても、グループ 2 だけでシステムを稼働し続けることができます。

ただし、グループ 1 とグループ 2 の両方で HDD 故障が発生すると RAID 全体が死亡します。

RAID 1+0 とは

f:id:kyamanak83:20171119211857p:plain

RAID 1 のミラーリンググループを、RAID 0 でストライピングした構成です。

最低でも HDD が 4 本必要となります。

ミラーリングのグループが 2 つあった場合、グループ 1 の HDD がどれか 1 本故障しても、同じグループ内のもう一方でシステムを稼働し続けることができます。

ただし、グループ内で同じデータを格納している HDD が故障すると RAID 全体が死亡します。

 

おまけ

RAID 0+1 と RAID 1+0 の比較

  • RAID 0+1 では各グループで HDD 故障が発生してしまうと RAID 全体が死亡します。
  • RAID 1+0 ではグループ内の同じデータを格納している HDD が故障してしまうと RAID 全体が死亡します。

発生頻度で考えると、グループ内の同じデータを格納している HDD が故障する確率の方が低いですから、RAID 1+0 の方が可用性が高いと思います。

参考:

itpro.nikkeibp.co.jp

 

最後に

RAID の種類と仕組みを簡単にまとめました。

詳しく知りたい方はこちらのサイトがいいと思います。

参考:https://note.cman.jp/server/raid/

【AWS】OpsWorks を使って Chef サーバの構築とノード追加

概要

Chef サーバを構築してサーバの構成管理を行いたいと思っています。

AWS には OpsWorks と言うサービスがあって、簡単に Chef サーバを構築できるようです。

OpsWorks を使って Chef サーバを構築して、ノード追加を行う工程までをまとめました。

参考:AWS OpsWorks for Chef Automate (マネージド型 Chef サーバー) | AWS

 

Chef サーバを構築

AWS ではコンソール経由で簡単に Chef サーバを構築できます。

1.『Go to OpsWorks for Chef Automate』を使ってみる

f:id:kyamanak83:20171105195143p:plain

 

2.『Create Chef Automate server』で Chef サーバを作成

f:id:kyamanak83:20171105195415p:plain

 

3. 設定の入力

今回、試しに使ってみたかったので、ほとんど設定はいじってないです。

f:id:kyamanak83:20171105195738p:plain

f:id:kyamanak83:20171105195815p:plain

 

f:id:kyamanak83:20171105200223p:plain

 

4. Chef サーバの起動

設定が完了すると、Chef サーバが起動します。起動まで少々時間がかかります。

その間に credential と Starter Kit をダウンロードしてください。

f:id:kyamanak83:20171105200755p:plain

5. Chef サーバにログイン

先ほどダウンロードした『credential』にユーザ名とパスワードが記載されています。

f:id:kyamanak83:20171105200942p:plain

ログインすると Chef Automate サーバのダッシュボードが表示されます。

f:id:kyamanak83:20171105201631p:plain



Chef Automate サーバの構築はこれで完了です。

現段階では、Chef Automate サーバで管理するノードがないので、これからノード追加を行います。

 

デベロッパーキットのインストール

ノード追加は Workstation と呼ばれる環境から行います。

これから自分に PC に Workstation を構築します。

Chef Workstation は、Chef のインフラストラクチャで重要なもう一つのコンポーネントで、ラップトップやデスクトップ PC など、業務のほとんどを行う作業場に当たります。 たとえば、Chef Workstation を使用して、クックブックとレシピの著作、組織のポリシーの構成、ノードのブートストラップを実行します。

以下の URL から Developer Kit をダウンロードしてください。

https://downloads.chef.io/chefdk

Developer Kit をダウンロードしたら、自分の PC にインストールしてください。

f:id:kyamanak83:20171105201540p:plain

これでローカル(ターミナル)で knife コマンドが実行できるようになります。

$ which knife
/usr/local/bin/knife

 

ノード追加

先ほど(4. Chef サーバの起動)ダウンロードした Starter Kit を解凍してください。

Starter Kit の中には chef-repo と Chef サーバの秘密鍵、設定ファイル(knife.rb)などが含まれています。

# chef-repo のディレクトリ構成
$ tree -a
.
├── .chef
│   ├── ca_certs
│   │   └── opsworks-cm-ca-2016-root.pem
│   ├── knife.rb # どの Chef サーバと接続するかなど記載された設定ファイル
│   └── private.pem # 秘密鍵
├── Berksfile
├── README.md
├── chefignore
├── cookbooks
│   └── README.md
├── environments
│   └── README.md
├── roles
│   └── README.md
├── userdata.ps1
└── userdata.sh

ノード追加は chef-repo からしか実行できませんので注意してください。

ノード追加は自分の PC 上に構築した Workstation から該当インスタンスに SSH ログインして必要な設定をインストールします。

そのため、ノード追加したいインスタンスのインバウンドルールの編集を先に行って、PC から SSH できるようにしておいて下さい。f:id:kyamanak83:20171112220829p:plain

インバウンドルールの設定が完了したら、bootstrap と言うコマンドを叩いて Chef サーバにノードを追加します。

サーバ OS によって実行ユーザが異なるようなので注意して下さい。

参考:Chef サーバーで管理するノードを追加する - AWS OpsWorks

# ノード追加を行う際は、chef-repo に移動します
$ cd my-chef-automate-3xxxxx3xxxxxxxxx

# ノード追加(bootstrap)実行
$ knife bootstrap <パブリック IP アドレス> -N <インスタンス名(何でもいい)> -x ec2-user --sudo --identity-file <自分のAWS にログインするときの鍵パス>
Creating new client for kyamanak.aws.instance
Creating new node for kyamanak.aws.instance
Connecting to XX.XXX.XXX.XX
XX.XXX.XXX.XX -----> Installing Chef Omnibus (-v 13)
XX.XXX.XXX.XX downloading https://omnitruck-direct.chef.io/chef/install.sh
XX.XXX.XXX.XX   to file /tmp/install.sh.23029/install.sh
XX.XXX.XXX.XX trying wget...
XX.XXX.XXX.XX el 6 x86_64
XX.XXX.XXX.XX Getting information for chef stable 13 for el...
XX.XXX.XXX.XX downloading https://omnitruck-direct.chef.io/stable/chef/metadata?v=13&p=el&pv=6&m=x86_64
XX.XXX.XXX.XX   to file /tmp/install.sh.23034/metadata.txt
XX.XXX.XXX.XX trying wget...
XX.XXX.XXX.XX sha1	2e1390896c8376268f88cc693cca475a76cd1e64
XX.XXX.XXX.XX sha256	ca26f2c9feef419cb3ae0a6ea03843b7c34275b7c0f6a1a18ab56b63383fc341
XX.XXX.XXX.XX url	https://packages.chef.io/files/stable/chef/13.6.0/el/6/chef-13.6.0-1.el6.x86_64.rpm
XX.XXX.XXX.XX version	13.6.0
XX.XXX.XXX.XX downloaded metadata file looks valid...
XX.XXX.XXX.XX downloading https://packages.chef.io/files/stable/chef/13.6.0/el/6/chef-13.6.0-1.el6.x86_64.rpm
XX.XXX.XXX.XX   to file /tmp/install.sh.23034/chef-13.6.0-1.el6.x86_64.rpm
XX.XXX.XXX.XX trying wget...
XX.XXX.XXX.XX Comparing checksum with sha256sum...
XX.XXX.XXX.XX Installing chef 13
XX.XXX.XXX.XX installing with rpm...
XX.XXX.XXX.XX ... 省略 ...
XX.XXX.XXX.XX Running handlers:
XX.XXX.XXX.XX Running handlers complete # 完了 
XX.XXX.XXX.XX Chef Client finished, 10/10 resources updated in 07 seconds

これでノード追加が完了です。

今回、kyamanak.aws.instance と言うインスタンス名で登録しました。

f:id:kyamanak83:20171105203259p:plain

 

最後に

Chef サーバを構築してノードを追加する工程までをまとめました。

OpsWorks を使うことで、今まで構築に時間をかけていた Chef サーバが数分で構築できるようになりました。

見よう見まねで直ぐに構築できると思うので、是非参考にして下さい。

 

【画像解析 API を使ってみる】食べ物の画像は解析できるのか?

概要

最近、食べ物の写真を撮って SNS にシェアする人が増えています。

自分も SNS に食べ物の写真をアップロードした経験があります。

ラーメン屋に行くと、サラリーマンのおっちゃんまで写真を撮っている時代です。( ˙灬˙ ) 

そんな時代背景もあってか、友達にもっと食べ物を美味しそうに見えるフィルターや、食べ物専用のスタンプが充実したアプリを作りたいと相談されたことがあります。

当時、作りたかったアプリはこれに近いのかなと思います。

linecorp.com

最終的にアプリをリリースするまでに至らなかったのですが、開発中に食べ物の画像を解析したいと思った部分があり、画像解析 API を使ってみたので、その時のことをまとめました。

 

画像解析 API

当時、使ってみた画像解析 API は以下の 3 つです。

  1. Google Cloud Vision API
  2. IBM Visual Recognition API
  3. Microsoft Computer Vision API

それぞれの API で出来ることを示しておきます。(サイトから引用しました)

Google Cloud Vision API

cloud.google.com

  • ラベル検出
    乗り物や動物など、画像に写っているさまざまなカテゴリの物体を検出できます。
  • 不適切なコンテンツの検出
    アダルト コンテンツや暴力的コンテンツなど、画像に含まれる不適切なコンテンツを検出できます。
  • ロゴ検出
    画像に含まれる一般的な商品ロゴを検出できます。
  • ランドマーク検出
    画像に含まれる一般的な自然のランドマークや人工建造物を検出できます。
  • 光学式文字認識(OCR)
    画像内のテキストを検出、抽出できます。幅広い言語がサポートされており、言語の種類も自動で判別されます。
  • 顔検出
    画像に含まれる複数の人物の顔を検出できます。感情や帽子の着用といった主要な顔の属性についても識別されます。 ただし、個人を特定する顔認識には対応していません。
  • 画像属性
    画像のドミナント カラーや切り抜きのヒントなど、画像の一般的な属性を検出できます。
  • ウェブ検出
    類似の画像をインターネットで検索できます。

IBM Visual Recognition API

www.ibm.com

  • 大量の画像・映像コンテンツに対して、自動的にタグ付けや分類を行う
  • 食事や食品に関する画像を検出し、写っている料理や食べ物を分析する (食べ物に特化した機能あり)
  • 画像に写っている人物の顔を検出し、年齢層・性別や名前をタグ付けする
  • 画像認識により書類やエビデンスを仕分けする
  • 製造ラインにおける画像検査により不良品を検出する
  • ドローンが撮影した画像を使って、家屋や鉄塔の破損箇所を検出する
  • 人工衛星が撮影した画像から、知見を得るための分析・分類を行う
  • 自社サイトに顧客がアップロードした画像から、不適切な画像を検出する
  • SNS等の画像から自社製品を探し出し、コメントをマーケティングに使用する

Microsoft Computer Vision API

azure.microsoft.com

  • 画像内のテキストの読み取り
    光学式文字認識 (OCR) により画像内のテキストを検出し、認識した語句をマシンに抽出して、判読可能な文字ストリームに変換します。画像を分析して埋め込みテキストを検出し、文字ストリームを生成し、検索を有効にします。テキストをコピーする代わりに写真を撮ることで、時間と労力を節約します。

  • 著名人およびランドマークの認識
    ドメイン固有モデルの例として、著名人モデルおよびランドマーク モデルがあります。著名人認識モデルでは、ビジネス、政治、スポーツ、エンターテイメント分野での 200,000 人の著名人を認識できます。ランドマーク認識モデルでは、世界中の 9000 種類の自然物や人工物のランドマークを認識できます。ドメイン固有モデルは Computer Vision API で継続的に進化を遂げている機能です。

  • ほぼリアルタイムでビデオを分析
    ほぼリアルタイムでビデオを分析。ご使用のデバイスでビデオのフレームを抽出し、それらのフレームをお好きな API 呼び出しに送信することで、任意の Computer Vision API をビデオ ファイルに使用できます。ビデオの結果はすぐに返ってきます。

  • サムネイルの生成
    あらゆる入力画像に基づいて、高品質でストレージ効率の高いサムネイルを生成します。サムネイル生成機能を使用して、サイズ、形、スタイルのニーズに最も適したものに画像を変更できます。スマート トリミングを適用すれば、元の画像とは異なる縦横比であるものの、関心領域を維持したサムネイルを生成できます。

 

API の実行結果

それぞれプロモページでデモが試せるので、『ハンバーグ』の写真を読み込ませて実行結果を確認します。

Google Cloud Vision API

f:id:kyamanak83:20171104144530p:plain 

IBM Visual Recognition API

f:id:kyamanak83:20171104144601p:plain

Microsoft Computer Vision API

f:id:kyamanak83:20171104145337p:plain

3 つの API 共に食べ物であることは問題なく認識してくれます。

API によっては、実行結果に『Salisbury steak』とあるのでハンバーグであることも認識してくれます。

更に『Garnish』とあるので、ハンバーグがメインで、ポテトやサラダがつけ合わせというところまで認識してくれる API もありました!

当時、ハンバーグの画像を API に読み込ませた時は『カレー』って認識されていたのですが、今回は『カレー』の項目がなくなっていたので、認識精度は日々上がっているんだなと感じました。

食べ物の画像解析だけで言えば、Google と IBM の API が良さそうだと感じました。

 

まとめ

食べ物の画像を解析することは出来ました!

写真に食べ物が写っているかどうかの判定は高確率で成功すると思います。

写真に写っている食べ物を正確に判定できる確率は 60 % ぐらいかなと感じました。

ただし、認識精度は日々上がっているので、今後もっと良くなることが期待出来ます。

ラーメン・餃子セットのように複数のメニューが並んでいる場合にも対応してくれます。

なので、概要で話したアプリを作る場合、食べ物が写真に写っている場合、特別なフィルターを適用する仕組みなどは実装できそうです。

ただし、暖かい食べ物が写っていたら赤系のフィルターを、冷たい食べ物が写っていたら青系のフィルターを出し分けて適用する仕組みなどはちょっと難しいかなと感じました。

また、写真の何処に食べ物が写っているのかの位置情報は取れないので、食べ物を中心に(焦点を当てて)フィルターを適用する仕組みなども難しいと感じました。

まぁ、ここら辺はユーザーが自分自身で選択するアプリにすれば良いのですが。

 

最後に

アプリを開発中に有識者に相談したところ、本格的に画像解析をやりたいのであれば OpenCV を使えと言われました。

opencv.jp

ただし、画像解析の精度を上げるにはたくさんのサンプル画像を集める必要があると言われました。

ハンバーグのサンプル画像が 100 枚と 1000 枚では、1000 枚の画像を機会に認識(学習)させた方が解析する時の精度は上がるそうです。

これは個人では時間と労力がかかり過ぎてキツそうなのでやめました。

【Ruby on Rails】重複しているレコードを取得する【MySQL】

概要

Ruby on Rails で DB から重複しているレコードを取得したい時がありました。

MySQL で重複しているレコードを取得する方法は分かるのですが、Ruby on Rails で同じ処理を書く場合、どうしたら良いのか分からなかったので調べて見ました。

MySQL で書く場合と、Ruby on Rails で書く場合の 2 パターン用意しています。

 

はじめに

今回利用するテーブルの中身を確認します。

ユーザ名 aaa が 3 件重複、bbb が 2 件重複、ccc が重複無しとなっています。

mysql> select * from users;
+----+------+---------------------+---------------------+------------+
| id | name | created_at          | updated_at          | deleted_at |
+----+------+---------------------+---------------------+------------+
|  1 | aaa  | 2017-10-21 06:30:06 | 2017-10-21 06:30:06 | NULL       |
|  2 | aaa  | 2017-10-21 06:30:07 | 2017-10-21 06:30:07 | NULL       |
|  3 | aaa  | 2017-10-21 06:30:09 | 2017-10-21 06:30:09 | NULL       |
|  4 | bbb  | 2017-10-21 06:30:12 | 2017-10-21 06:30:12 | NULL       |
|  5 | bbb  | 2017-10-21 06:30:13 | 2017-10-21 06:30:13 | NULL       |
|  6 | ccc  | 2017-10-21 06:30:16 | 2017-10-21 06:30:16 | NULL       |
+----+------+---------------------+---------------------+------------+
6 rows in set (0.00 sec)

重複しているレコードを取得

これは単に重複しているレコードを知りたい場合に利用する事が多いです。

今回はユーザー名 aaa と bbb が重複している名前だと言う事だけが分かります。

MySQL の場合

group by 句 と having 句を利用することで、重複しているレコードを取得することができます。

mysql> select * from users group by name having count(*) >= 2;
+----+------+---------------------+---------------------+------------+
| id | name | created_at          | updated_at          | deleted_at |
+----+------+---------------------+---------------------+------------+
|  1 | aaa  | 2017-10-21 06:30:06 | 2017-10-21 06:30:06 | NULL       |
|  4 | bbb  | 2017-10-21 06:30:12 | 2017-10-21 06:30:12 | NULL       |
+----+------+---------------------+---------------------+------------+
2 rows in set (0.00 sec)
Ruby on Rails の場合

Ruby on Rails でも group と having と言う、MySQL と似たようなメソッドがあることが分かりました。

irb(main):001:0> User.group(:name).having('count(*) >= 2')
=> #<ActiveRecord::Relation [
#<User id: 1, name: "aaa", created_at: "2017-10-21 06:30:06", updated_at: "2017-10-21 06:30:06", deleted_at: nil>, 
#<User id: 4, name: "bbb", created_at: "2017-10-21 06:30:12", updated_at: "2017-10-21 06:30:12", deleted_at: nil>]>

重複しているレコードを全て取得

重複している全てのレコードを取得することができます。

この結果からユーザ名 aaa が 3 回重複、bbb が 2 回重複していることも分かります。

MySQL の場合

先ほどの sql 文をサブクエリで扱い、where in 句でそれと一致するユーザ名を取得しています。

mysql> select * from users where name in (
    -> select name from users group by name having count(*) >= 2);
+----+------+---------------------+---------------------+------------+
| id | name | created_at          | updated_at          | deleted_at |
+----+------+---------------------+---------------------+------------+
|  1 | aaa  | 2017-10-21 06:30:06 | 2017-10-21 06:30:06 | NULL       |
|  2 | aaa  | 2017-10-21 06:30:07 | 2017-10-21 06:30:07 | NULL       |
|  3 | aaa  | 2017-10-21 06:30:09 | 2017-10-21 06:30:09 | NULL       |
|  4 | bbb  | 2017-10-21 06:30:12 | 2017-10-21 06:30:12 | NULL       |
|  5 | bbb  | 2017-10-21 06:30:13 | 2017-10-21 06:30:13 | NULL       |
+----+------+---------------------+---------------------+------------+
5 rows in set (0.00 sec)
Ruby on Rails の場合

先ほどのワンライナーに pluck( :name ) を混ぜてユーザ名だけのリストを作ります。

ユーザ名だけのリストを where 文の引数に使用して条件検索を行います。

irb(main):001:0> duplicate_user_names = User.group(:name).having('count(*) >= 2').pluck(:name)
=> ["aaa", "bbb"]
irb(main):002:0> User.where(name: duplicate_user_names)
=> #<ActiveRecord::Relation [
#<User id: 1, name: "aaa", created_at: "2017-10-21 06:30:06", updated_at: "2017-10-21 06:30:06", deleted_at: nil>, 
#<User id: 2, name: "aaa", created_at: "2017-10-21 06:30:07", updated_at: "2017-10-21 06:30:07", deleted_at: nil>, 
#<User id: 3, name: "aaa", created_at: "2017-10-21 06:30:09", updated_at: "2017-10-21 06:30:09", deleted_at: nil>, 
#<User id: 4, name: "bbb", created_at: "2017-10-21 06:30:12", updated_at: "2017-10-21 06:30:12", deleted_at: nil>, 
#<User id: 5, name: "bbb", created_at: "2017-10-21 06:30:13", updated_at: "2017-10-21 06:30:13", deleted_at: nil>]>

 

おまけ

条件を満たさない重複レコードを削除する

重複しているレコードを取得した後に何がしたいか考えると、ある条件を満たさない場合に削除(又は更新)することだと思います。

今回はレコードの作成日時が最新のレコード以外は論理削除するサンプルを用意しました。

MySQL の場合

はじめに、where 句を繋げてユーザ名が重複しているレコードの中で、作成日時が最新のレコード以外を取得します。

MySQL ではサブクエリの from 句と更新のターゲットの両方に同じテーブルを使用することができません。

参考:MySQL :: MySQL 5.6 リファレンスマニュアル :: 13.2.10.9 サブクエリーのエラー

そのため、最初に出てくるサブクエリのテーブルを as 句を使って一時的に tmp テーブルに置き換えて、テーブルの更新(論理削除)を行います。( id も user_id に置き換えます)

mysql> update users set deleted_at=now() where id in (
    -> select user_id from (
    -> select id as user_id from users where name in (
    -> select name from users group by name having count(*) >= 2)
    -> and created_at not in (
    -> select max(created_at) from users group by name having count(*) >= 2)
    -> ) as tmp);
Query OK, 3 rows affected (0.01 sec)
Rows matched: 3  Changed: 3  Warnings: 0

# 論理削除されたのか確認(deleted_at にタイムスタンプが挿入されれば OK)
mysql> select * from users; +----+------+---------------------+---------------------+---------------------+ | id | name | created_at | updated_at | deleted_at | +----+------+---------------------+---------------------+---------------------+ | 1 | aaa | 2017-10-21 06:30:06 | 2017-10-21 06:30:06 | 2017-10-21 14:43:41 | | 2 | aaa | 2017-10-21 06:30:07 | 2017-10-21 06:30:07 | 2017-10-21 14:43:41 | | 3 | aaa | 2017-10-21 06:30:09 | 2017-10-21 06:30:09 | NULL | | 4 | bbb | 2017-10-21 06:30:12 | 2017-10-21 06:30:12 | 2017-10-21 14:43:41 | | 5 | bbb | 2017-10-21 06:30:13 | 2017-10-21 06:30:13 | NULL | | 6 | ccc | 2017-10-21 06:30:16 | 2017-10-21 06:30:16 | NULL | +----+------+---------------------+---------------------+---------------------+ 6 rows in set (0.00 sec)
Ruby on Rails の場合

はじめに maximum( :created_at ) を使って、重複しているユーザ名で作成日時が最新のレコードを取得します。

出力した hash を key と value のリストに変換して where 文に挿入します。

取得した user_ids 以外のレコードを destroy_all で論理削除します。

irb(main):001:0> hash = User.group(:name).having('count(*) >= 2').maximum(:created_at)
=> {"aaa"=>Sat, 21 Oct 2017 06:30:09 UTC +00:00, "bbb"=>Sat, 21 Oct 2017 06:30:13 UTC +00:00}
irb(main):002:0> user_ids = User.where(name: hash.keys, created_at: hash.values).pluck(:id)
=> [3, 5]
irb(main):003:0> User.where(name: hash.keys).where.not(id: user_ids).destroy_all
=> [
#<User id: 1, name: "aaa", created_at: "2017-10-21 06:30:06", updated_at: "2017-10-21 15:46:31", deleted_at: "2017-10-21 15:46:31">, 
#<User id: 2, name: "aaa", created_at: "2017-10-21 06:30:07", updated_at: "2017-10-21 15:46:31", deleted_at: "2017-10-21 15:46:31">, 
#<User id: 4, name: "bbb", created_at: "2017-10-21 06:30:12", updated_at: "2017-10-21 15:46:31", deleted_at: "2017-10-21 15:46:31"> ]>

 

ちなみに、Ruby on Rails はデフォルトでは論理削除にならないので、事前に論理削除用の gem を導入することをお勧めします。

kyamanak.hatenablog.com

 

まとめ

Ruby on Rails で DB から重複するレコードを取得する方法をまとめました。

MySQL では基本的に group by 句と having 句を利用することで、重複するレコードが取得できます。

Ruby on Rails でも group と having と言う、MySQL と似たようなメソッドが用意されていることが分かりました。

最後のおまけの sql 文はサブクエリを使いまくっているので、データ量の多い DB では処理が重いかもしれません。もっと良い方法あれば教えてください m(_ _)m

【Ruby on Rails】画像は public と app/assets/images のどちらに設置すべき?

概要

Rails ではアプリケーション内で使用される画像の設置場所に、現在 2つの場所が存在します 。

  1. public ディレクトリ
  2. app/assets/images ディレクトリ

どちらに設置しても画像の読み込みに困りませんが、設置場所によってどんな違いがあるのか、今回調べてみました。

 

画像を読み込む際のパスが違う

まず、設置場所によって画像を読み込む際のパスが異なります。

public ディレクトリに配置した画像を読み込む場合、パスを / から始める必要があります。

<%= image_tag('/tennis_ball.png') %>
# 生成される img タグの中身
# <img src="/tennis_ball.png" alt="Tennis ball" />

 

app/assets/images ディレクトリに設置した画像を読み込む場合、パスを / から始める必要はありません。

<%= image_tag('tennis_ball.png') %>
# 生成される img タグの中身
# <img src="/assets/tennis_ball-aa455d01913a920bcfe8749e327219bd4caaca41f7ce325fc5edba8e46372843.png" alt="Tennis ball" />

生成される img タグの中身を見て下さい。

パスを / から始めたかどうかで、src 値が大きく異なることが分かります。

また、app/assets/images ディレクトリに画像を設置した場合、src 値の画像ファイルパスに MD5 ハッシュ値が挿入されていることが分かります。

この MD5 ハッシュ値は、フィンガープリント と呼ばれるもので、アセットパイプラインで使われます。

 

アセットパイプラインについて

Rails では app/assets ディレクトリ以下は、アプリケーション自身が保有するアセットの置き場として奨励されており、アセットパイプラインの対象となります。

アセットパイプラインとは、JavaScript や CSS の アセット を最小化(スペースや改行を詰める、コメントを削除するなど)または圧縮して 1 つのファイルに連結するためのフレームワークです。

Rails ではこの機能がデフォルトで ON になっています。

ファイルを連結して 1 つにすることで、ブラウザからサーバへのリクエスト数を減らすことができ、ページの読み込み時間が大きく短縮されます。

また圧縮することによっても、ファイルサイズが小さくなり、ブラウザからの読み込み時間が短縮されます。

ただし、画像ファイルはバイナリデータなので、アセットの最小化や圧縮の話はあまり関係ない(効果がない)かなと思っています。

画像ファイルは連結してくれません。画像ファイルを 1 つに連結するとか無理です。

アセットパイプラインはファイル名にフィンガープリントを挿入し、アセットファイルがブラウザでキャッシュされるようにしています。

アセットファイルの中身が少しでも変更されると、フィンガープリントが自動で更新されて、ブラウザでキャッシュされていた既存のアセットが無効になります。

この話は、画像ファイルを扱う場合に、大きく関係しています。

画像ファイルを同じ名前で新しい画像に更新した場合、ブラウザにキャッシュされた画像が残っていると、新しい画像が表示されず、キャッシュされた画像が表示されてしまいます。

これを回避するにはファイル名を変更するか、ブラウザのキャッシュを削除してもらうかしかありません。

app/assets/images ディレクトリに設置した画像は、同じ名前で違う画像に差し替えても、画像ファイルの中身からフィンガープリントを自動で更新して、画像ファイル名に挿入するので、ブラウザでキャッシュされた古い画像が表示される心配がありません。

ただし、config.assets.digest = true の場合のみです。(デフォルト true)

参考:

railsguides.jp

 

CSS で使えるプロパティが違う

CSS で使えるプロパティが違うのは production モードの話です。

development モードで開発していて、いざ本番リリースをしようと production モードに切り替えたら背景画像が読み込めなかった経験がありませんか?

自分は初めて Rails を触った時に結構苦戦しました。。

まず、production モードでプリコンパイルされたファイルは public/assets に置かれます。

プリコンパイルされたファイルは、Webサーバによって静的なアセットとして扱われます。

以下の設定を変更して、Webサーバから静的ファイルを読み込めるようにする必要があります。

$ vim config/environments/production.rb
# Disable serving static files from the `/public` folder by default since
# Apache or NGINX already handles this.
config.public_file_server.enabled = true # true に変更

参考:Configuring Rails Applications — Ruby on Rails Guides

 

CSS から画像を読み込む際に background プロパティを利用することがあると思います。画像の設置場所によって、使える値が異なります。

public ディレクトリに設置した画像は、以下の CSS の書き方で背景に画像を適用できます。

  • background: url('画像パス')
  • background: image-url(’画像パス')
  • background-image: url('画像パス')
  • background-image: image-url('画像パス')

app/assets/images ディレクトリに設置した画像は、sass-rails の image-url ヘルパーを使わないと背景に画像を適用できません。

  • background: image-url(’画像パス')
  • background-image: image-url('画像パス')

ちなみに、image-url('画像パス') は url(/assets/画像パス) に変換され、image-path('画像パス') は '/assets/画像パス' に変換されます。

 

public ディレクトリに置くケース

もし、同じアプリケーション内で JavaScript を利用しており、JavaScript 内で画像を扱うのであれば、app/assets/images ディレクトリの画像はフィンガープリントが付いてしまうので(画像の URL が変わってしまうので)扱いにくいです。

こういう場合は public ディレクトリに置くしかないと思います。(体験談)

 

まとめ

画像の設置場所による違いをまとめました。

自分が見つけた違いは 3 つです。(他にもあるかも)

  1. 画像を読み込む際のパス
  2. ブラウザのキャッシュ対策
  3. production モードで使える CSS プロパティ

特に大きかったのは、app/assets ディレクトリに画像を設置した場合、アセットパイプラインの対象となり、色々な恩恵を受けることができることです。

アプリケーション自身が保有するアセットの置き場として奨励されていますし、app/assets/images ディレクトリに画像を設置するのが正しいのではないかと思いました。

特に理由がない場合は、app/assets/images ディレクトリに画像を設置することをお勧めします。

【AWS】サーバ間でファイルのリアルタイム同期を行う【rsync + xinetd + lsyncd】

概要

サーバを運用してきた人なら分かると思いますが、サーバとは突然死ぬものです。

例えサーバの稼働率が 99.99% のクラウドサービスを使っていたとしても、何かあった時のためにサーバは冗長化して構築するのが好ましいです。

今回、サーバにファイルを保存する(アップロードする)システムを作ることになりました。

サーバが死んでも、ファイルが見れなくならないようにしっかり冗長化したいと考えています。

ただし、複数のサーバにファイルをアップロードすると時間がかかってしまうので、1 つのサーバにファイルをアップロードして裏側で同期を取るシステムを作ろうと思います。

サーバ間ではファイルをリアルタイムで同期できるように rsync, xinetd, lsyncd と言うコマンドやデーモンプロセスを利用しました。

その時対応した内容をまとめます。

 

はじめに

rsync とは?

リモート・ローカルに関わらずディレクトリやファイルを高速にコピーできるプログラムです。

ローカルとリモート間のディレクトリ同期やバックアップによく使われます。

xinetd とは?

スーパーサーバと呼ばれるポート監視用のデーモンプログラムです。

あるポートに対してアクセスがあった場合、設定ファイルを元にポートに対応したサービスを起動することができます。

lsyncd とは?

あるディレクトリ以下のファイルにイベント(作成・更新・削除)が発生した場合、それを検知して即時にファイルの同期(デフォルトは rsync を使う)を行うことができるプログラムです。

 

対応手順

パッケージのインストール

標準レポジトリでは提供されていないパッケージ(lsyncd)もあるので、EPEL レポジトリを指定してインストールします。

$ sudo yum --enablerepo=epel install rsync xinetd lsyncd

rsync の設定

設定ファイル(rsyncd.conf)を作成します。

オプション参考:rsyncd.conf

$ sudo vim /etc/rsyncd.conf
#------------------
# GLOBAL PARAMETERS
#------------------
uid           = root
gid           = root
log file      = /var/log/rsyncd.log
pid file      = /var/run/rsyncd.pid
hosts allow   = (IPv4 パブリック IP)/32 # 複数指定する場合はカンマ区切り
hosts deny    = *
read only     = false # クライアントからのアップロードを許可
exclude       = *
include       = *.txt # txt ファイルだけの転送を許可
dont compress = *.gz *.tgz *.zip *.pdf *.sit *.sitx *.lzh *.bz2 *.jpg *.gif *.png

#------------------
# MODULE PARAMETERS
#------------------
[module1]
    comment = rsync test dir1
    path    = /tmp/rsync/test1/ # 同期を取りたいディレクトリ

[module2]
    comment = rsync test dir2
    path    = /tmp/rsync/test2/ # 同期を取りたいディレクトリ

ポート解放

AWS でインバウンドのルールの編集を行います。

サーバ間でファイル転送ができるように、rsync 用のポート873 番 を解放します。

f:id:kyamanak83:20170919134722p:plain

rsync の起動

rsync はデーモンモードで起動し、rsync サーバとして利用します。

# デーモンモードで起動します
$ sudo rsync --daemon --config /etc/rsyncd.conf

# 873 番ポートが rsync で動いていることを確認します
$ sudo lsof -i:873
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
rsync   3049 root    4u  IPv4  13778      0t0  TCP *:rsync (LISTEN)
rsync   3049 root    5u  IPv6  13779      0t0  TCP *:rsync (LISTEN)

# rsync が起動したことを確認します
$ ps `cat /var/run/rsyncd.pid`
  PID TTY      STAT   TIME COMMAND
 3049 ?        Ss     0:00 rsync --daemon --config /etc/rsyncd.conf

xinetd の設定

xinetd で rsync 用のポート 873 番を監視するための設定変更を行います。

$ sudo vim /etc/xinetd.d/rsync
# default: off
# description: The rsync server is a good addition to an ftp server, as it \
#       allows crc checksumming etc.
service rsync
{
        disable = no # no に変更する
        flags           = IPv6
        socket_type     = stream
        wait            = no
        user            = root
        server          = /usr/bin/rsync
        server_args     = --daemon
        log_on_failure  += USERID
}

xinetd の起動

システム起動時に自動起動するように chkconfig の設定を行います。

設定が完了したら xinetd を起動します。

$ sudo chkconfig xinetd on
$ sudo service xinetd start
xinetd を起動中:                                           [  OK  ]

rsync の動作確認

ファイルの同期を取りたい双方のサーバでここまでの作業が完了したら、ファイル転送の動作確認をします。

はじめに、同期を取りたいディレクトリに適当なファイルを作成して下さい。

ファイルを作成したら、以下のコマンドを実行して、ファイルが転送できるか確認して下さい。

# (IPv4 パブリック IP)は自分の転送先のサーバの IP を指定して下さい
$ sudo rsync -ahv --contimeout=5 /tmp/rsync/test1/* (IPv4 パブリック IP)::module1
sending incremental file list
file.txt # /tmp/rsync/test1 以下に作成した file.txt が転送された

sent 89 bytes  received 27 bytes  232.00 bytes/sec
total size is 0  speedup is 0.00 # 転送成功

lsyncd の設定

設定ファイル(lsyncd.conf)を作成します。

$ sudo vim /etc/lsyncd.conf
settings {
    logfile 	   = "/var/log/lsyncd.log",
    pidfile 	   = "/var/run/lsyncd.pid",
    statusFile 	   = "/var/log/lsyncd-status.log",
    statusInterval = 1, # ステータスの更新頻度(秒)
    delay          = 0, # rsync を呼び出す時間差
    nodaemon 	   = false # デーモンモードで起動する
}

sync {
    default.rsync,
    source = "/tmp/rsync/test1/",
    target = "(IPv4 パブリック IP)::module1",
    rsync = { # ファイルの Permission を引き継ぐ
	owner = true,
	group = true
    }
}

sync {
    default.rsync,
    source = "/tmp/rsync/test2/",
    target = "(IPv4 パブリック IP)::module2",
    rsync = { # ファイルの Permission を引き継ぐ
	owner = true,
	group = true
    }
}

lsyncd の起動

システム起動時に自動起動するように chkconfig の設定を行います。

設定が完了したら lsyncd を起動します。

$ sudo chkconfig lsyncd on
$ sudo service lsyncd start
lsyncd を起動中:                                           [  OK  ]

リアルタイム同期の動作確認

ファイル作成の同期

サーバ1 でファイルを作成します。

$ touch /tmp/rsync/test1/file.txt /tmp/rsync/test2/file.txt

サーバ2 で同期されたことを確認します。

$ tree /tmp/rsync/
/tmp/rsync/
├── test1
│   └── file.txt
└── test2
    └── file.txt

2 directories, 2 files

ファイル削除の同期

サーバ1 でファイルを削除します。

$ rm /tmp/rsync/test1/file.txt

サーバ2 で同期されたことを確認します。

$ tree /tmp/rsync/
/tmp/rsync/
├── test1
└── test2
    └── file.txt

2 directories, 1 file

双方向なので逆のパターンでも大丈夫です。

 

まとめ

サーバ間でファイルのリアルタイム同期を行う方法をまとめました。

以下の3ステップで簡単に導入することができます。

  1. rsync の設定・起動
  2. xinetd の設定・起動
  3. lsyncd の設定・起動

ちなみに『rsync の設定・起動』と『xinetd の設定・起動』が完了していれば、クーロンを起動して定期的にサーバ間でファイルをバックアップする仕組みにも使えます。

利用方法はたくさんあると思うので、是非、参考にしてみて下さい。

【Ruby on Rails】deleted_at を使って論理削除をしよう

概要

Rails には DB のカラム名に『created_at』『updated_at』と言う、設定しておくだけで、作成日時と更新日時を自動記録してくれる、お決まりの便利な機能が用意されています。

このカラムのデータはアプリケーション内で利用するデータと言うよりも、DB の運用管理上あると便利なデータだと思っています。

例えば、ユーザーから不具合の申告を受けた場合、不具合の発生日時や原因を突き止めやすくなります。

ただし、DB の主なデータ操作には、この他に『削除』があります。

自分は DB からデータを削除する場合は、全て論理削除で行い、物理削除は利用しません。

これは、間違えて DB からデータを削除してしまった場合に簡単に復元できるからです。

また、ユーザーから不具合の申告を受けた場合、削除したデータが残っていた方が対応しやすいケースも存在するからです。

Rails ではデフォルトで論理削除の機能は用意されていないので、今回、論理削除の導入方法をまとめました。

 

はじめに

物理削除と論理削除の違いを簡単に説明します。

  • 物理削除は DELETE を実行した際に、DB から完全にデータを削除することです。
  • 論理削除は DELETE を実行した際に、削除用フラグを付けて、データを削除扱いにすることです。DB にデータは残っています。

 

導入方法

gem の紹介

Rails で論理削除を導入するにはたくさんの gem が存在しています。

論理削除の gem で有名なものだとここら辺の名前が上がるかと思います。

github.com

github.com

github.com

論理削除は gem を利用した導入方法が一番楽だと思います。

今回は paranoia と言う gem を利用して説明します。

Gemfile に paranoia を追記してインストールしてください。

$ vim Gemfile
gem 'paranoia', '~> 2.3', '>= 2.3.1'

# インストール $ bundle install --path vendor/bundle

 ApplicationRecord に設定追加

app/models/application_record.rb に以下の設定を追記します。

$ vim app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  acts_as_paranoid # 追加
  self.abstract_class = true
end

カラムの追加

論理削除を行うには『deleted_at』と言う削除フラグ用のカラムを新しく追加する必要があります。

データベースにログインして以下の sql コマンドを実行してください。

mysql> ALTER TABLE <テーブル名> ADD deleted_at datetime;
Query OK, 0 rows affected (0.04 sec)
Records: 0 Duplicates: 0 Warnings: 0

『deleted_at』のカラムが追加されたことを確認します。

mysql> desc <テーブル名>;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | int(11)      | NO   | PRI | NULL    | auto_increment |
| name       | varchar(255) | NO   | UNI | NULL    |                |
| created_at | datetime     | NO   |     | NULL    |                |
| updated_at | datetime     | NO   |     | NULL    |                |
| deleted_at | datetime     | YES  |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+
5 rows in set (0.00 sec)

 

以上で Rails アプリケーションへの論理削除の導入が完了しました。

おさらいすると以下の3つの作業が必要となります。

  1. gem の導入(Gemfile へ記載してインストール)
  2. ApplicationRecord への設定追加
  3. DBに『deleted_at』と言うカラムの追加

これで destory や destroy_all メソッドを実行しても、DB のデータが論理削除されるように変わりました。

destroy_all - リファレンス - - Railsドキュメント

 

動作確認

本当に論理削除が出来ているかを確認します。

はじめに DB の中身を確認します。この時点では全てのデータが存在しています。

mysql> select * from users;
+----+-----------------------+---------------------+------------------+------------+
| id | name | created_at | updated_at | deleted_at |
+----+-----------------------+---------------------+------------------+------------+
| 1 | aaaaa | 2017-05-01 06:02:54 | 2017-05-01 06:02:54 | NULL |
| 2 | bbbbb | 2017-05-01 06:02:54 | 2017-05-01 06:02:54 | NULL |
| 3 | ccccc | 2017-05-01 06:02:54 | 2017-05-01 06:02:54 | NULL |
+----+-----------------------+---------------------+------------------+------------+
3 rows in set (0.00 sec)

 

コンソール経由で削除を実行します。

$ rails c
Loading development environment (Rails 5.0.2)
irb(main):001:0> User.destroy_all(id: 3) # id:3 のレコードを削除します
=> [# User id:="" 3="" name:="" created_at:="" 2017-05-01="" 06:02:54="" updated_at:="" 2017-09-16="" 06:10:23="" deleted_at:="" gt="" code="">

 

すると、id: 3 のレコードの deleted_at にタイムスタンプが記録されました。

destory_all メソッドを実行しても物理削除されていないことが分かります。

mysql> select * from users;
+----+-----------------------+---------------------+---------------------+------------+
| id | name | created_at | updated_at | deleted_at |
+----+-----------------------+---------------------+---------------------+------------+
| 1 | aaaaa | 2017-05-01 06:02:54 | 2017-05-01 06:02:54 | NULL |
| 2 | bbbbb | 2017-05-01 06:02:54 | 2017-05-01 06:02:54 | NULL |
| 3 | ccccc | 2017-05-01 06:02:54 | 2017-09-16 06:10:23 | 2017-09-16 06:10:23 |
+----+-----------------------+---------------------+---------------------+---------------------+
3 rows in set (0.00 sec)

 

find_by メソッドでデータが取得出来るか確認します。

$ rails c
Loading development environment (Rails 5.0.2)
irb(main):001:0> User.find_by(id: 3)
=> nil

irb(main):002:0> User.with_deleted.find_by(id: 3)
=> # User id: 3, name: "トレンド", created_at: "2017-05-01 06:02:54", updated_at: "2017-09-16 06:10:23", deleted_at: "2017-09-16 06:10:23">

論理削除されているデータは find_by メソッドを実行しても取得できません。

ちなみに論理削除されたデータは with_deleted と言うオプションを付ければ find_by メソッドで取得できます。

 

最後に

restore と言う復元用のメソッドも用意されており、簡単に元に戻すこともできます。

$ rails c
Loading development environment (Rails 5.0.2)
irb(main):001:0> User.with_deleted.find_by(id: 3).restore
=> # user id:="" 3="" name:="" created_at:="" 2017-05-01="" 06:02:54="" updated_at:="" 2017-09-16="" 09:52:34="" deleted_at:="" nil="">

 

まとめ

Rails アプリケーションで 論理削除を導入する方法をまとめました。

導入までのステップは 3つだけでかなり簡単でした。

運用管理上『created_at』『updated_at』『deleted_at』は必要なカラムだと思います。

これらのカラムの存在は不具合対応などを迅速に処理できるようサポートしてくれます。

まだ、導入していないアプリケーションでは是非導入を検討してみて下さい。

【Ruby on Rails】require と permit の使い方がよく分からない

概要

Rails 4 からストロングパラメータと言う新機能が導入されました。

具体的には require と permit と言うメソッドのことです。

Scaffold で Controller を作成する際にデフォルトで適用されるのですが、使い方がよく分からず、いつも削除していました。。

今回、require と permit の使い方を調べてまとめました。

 

はじめに

「名前」と「メールアドレス」を属性に持つ User モデルを Scaffold で作成してみます。

$ rails generate scaffold User name:string email:string

すると、Controller にストロングパラメータを適用した private メソッドがデフォルトで用意されました。

private
  # Never trust parameters from the scary internet, only allow the white list through.
  def user_params
    params.require(:user).permit(:name, :email)
  end

この user_params こそが、使い方がよく分からず削除していたメソッドです。。

これから順を追って中身を説明していきます。

 

params の中身を確認

初めに params の中身を確認します。

curl コマンドで user ハッシュを POST した結果は以下になります。

$ curl -X POST 'http://localhost/users' -H "Content-Type: application/json" -d '{"user":{"name":"aaa","email":"bbb"}}'
{
  "action" : "create",
  "user" : {
    "name" : "aaa",
    "email" : "bbb"
  },
  "controller" : "users"
}

返り値を見ると controller 名や action 名などもパラメータに含まれていることが分かります。

これがリクエストを投げたときの全ての返り値です。

 

params.require の中身を確認

次に params.require( :user ) の中身を確認します。

先ほどと同じように user ハッシュを POST した結果が以下になります。

$ curl -X POST 'http://localhost/users' -H "Content-Type: application/json" -d '{"user":{"name":"aaa","email":"bbb"}}'
{
  "name" : "aaa",
  "email" : "bbb"
}

user ハッシュの値だけになりましたね!

require メソッドを利用することで、引数に設定した key の 値だけを取得することができます。 

余談ですが、、、

params.require( :controller ) とすれば  "users" が取得できます。

params.require( :action ) とすれば "create" が取得できます。

 

params.require.permit の中身を確認

最後に params.require( :user ).permit( :name, :email ) の中身を確認します。

params.require( :user ) のままでは、user ハッシュの全ての値を取得してしまいます。

そこで permit( :name, :email ) を利用します。

permit メソッドは許可したいパラメータだけをフィルタしてくれます。

例えば、User モデルに存在しない「電話番号」を一緒に POST したとしても、許可されていないので返り値に存在しません。

$ curl -X POST 'http://localhost/users' -H "Content-Type: application/json" -d '{"user":{"name":"aaa","email":"bbb","tel":"111"}}'
{
  "name" : "aaa",
  "email" : "bbb"
}

これは API を作成する場合など、予期せぬパラメータの付与を避けたい場合に非常に便利です。

 

おまけ

配列のパラメータを受け取りたい場合

ちなみに「メールアドレス」が複数ある場合など、配列でパラメータを受け取りたい場合は以下のように記載します。

private
  # Never trust parameters from the scary internet, only allow the white list through.
  def user_params
    params.require(:user).permit(:name, email: []) # 配列の場合
  end

user ハッシュを POST して見ると、以下のような返り値が取得できます。

$ curl -X POST 'http://localhost/users' -H "Content-Type: application/json" -d '{"user":{"name":"aaa","email":["aa","bb","cc"]}}'
{
  "name" : "aaa",
  "email" : [
    "aa",
    "bb",
    "cc"
  ]
}

 

まとめ

3 段階に分けて返り値を確認しました。

  1. params の中身を確認
  2. params.require の中身を確認
  3. params.require.permit の中身を確認

返り値を比べることで require と permit メソッドの役割が分かったかなと思います。

基本的には create や update メソッドで 今回のストロングパラメータが使われることが多いです。

ストロングパラメータは mass assignment 脆弱性の対策に導入されたと言われています。ユーザ操作によって本来更新すべきでないカラムが更新されるのを防ぐためです。

ストロングパラメータを利用して安全で綺麗なコードを書きましょう。

【AWS】.ssh/config を利用して、ログイン時のコマンドを省略する

概要

インスタンスに毎回ログインする際、ユーザ名と鍵 PATH を記載するのが面倒くさいので、設定ファイル (.ssh/config) にログインに必要な情報を記載して、ログイン時のコマンドを省略しようと思います。

 

はじめに

.ssh/config とは

SSH を利用してインスタンスへログインする際に利用される設定ファイルです。

AWS にログインする際のユーザ名

サーバ OS によって、ログイン時に利用するユーザ名が異なる様です。

サーバ OS ユーザ名
 Amazon Linux  ec2-user
 RHEL  ec2-user または root
 Ubuntu  ubuntu または root
 Centos  centos
 Fedora  ec2-user
 SUSE  ec2-user または root

参考:SSH を使用した Linux インスタンスへの接続 - Amazon Elastic Compute Cloud

 

キーペアの作成

コマンドラインで ssh-keygen を実行する必要はありません。

AWS ではインスタンスを作成したときに、キーペアを作成して(作成した秘密鍵を)ダウンロードする事が出来ます。

一度作成したキーペアはインスタンスを生成する度に使い回すことが可能です。

もし秘密鍵を無くしてしまった場合、既存のキーペアは再ダウンロードできないので、新しく作り直してください。

f:id:kyamanak83:20170815001757p:plain

 

ログイン方法

SSHコマンドでログイン

ssh -i <鍵 PATH> <ユーザ名>@<ホスト名> のフォーマットでコマンドを実行します。

$ ssh -i amazon_private_key-20160805.pem ec2-user@ec2-00-00-000-000.us-west-2.compute.amazonaws.com

.ssh/config を利用してログイン

設定ファイルの記載内容を確認します。ホスト名や鍵名は自分のものに書き換えてください。

$ cat .ssh/config
Host AWS
    HostName ec2-00-00-000-000.us-west-2.compute.amazonaws.com
    User ec2-user
    IdentityFile amazon_private_key-20160805.pem

設定ファイルに記載しておくと、簡単なコマンドでログインができます。

$ ssh AWS
Last login: Wed Nov 23 06:38:46 2016 from aa200000000000000000.userreverse.dion.ne.jp

       __|  __|_  )
       _|  (     /   Amazon Linux AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-ami/2016.09-release-notes/

 

その他

ダウンロードした秘密鍵を他のフォルダに移動したりする事があると思います。

秘密鍵の Permission は 600 じゃないといけないので注意してください。

Owner(所有者) だけが Read&Write できる権限です。

# Permission が悪いと以下のようなエラーが表示されます
$ ssh AWS
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for 'amazon_private_key-20160805.pem' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "amazon_private_key-20160805.pem: bad permissions"
ec2-user@ec2-user@ec2-00-00-000-000.us-west-2.compute.amazonaws.com: Permission denied (publickey).

# Permission を 600 に変更してください
$ sudo chmod 600 amazon_private_key-20160805.pem

$ ls -l amazon_private_key-20160805.pem
.rw-------@ 1.7k kyamanak  5 Aug  2016 amazon_private_key-20160805.pem

 

まとめ

今回は、Amazon Linux を利用してログインしてみました。

インスタンスにログインするための最低限の .ssh/config の設定をまとめました。

【Ruby on Rails】ルーティング member と collection の違い

概要

Rails には index, show, new, edit, create, update, destroy の 7 つのデフォルトアクションが用意されています。

もし、これ以外のアクションを新しく追加したい場合、ルーティングに設定を追記する必要があります。

この場合、routes.rb をどう書くのか調べたところルーティングのメソッドに、member と collection と言うものがありました。

この 2 つの使い方を簡単にまとめました。

 

member の使い方

member は特定のデータに対するアクションに利用します。

例えば、ユーザーのフォローを行う follow アクションを新しく追加したいとします。

この場合、フォローを行うのは、特定のユーザーです。

『 A さんが B さんをフォローする』の様な処理になると思います。

なので、URL を作成するとしたら、下記の様になります。

特定のユーザーを示す id が含まれていることに注目してください。

http://$(DNS)/users/1/follow

member を使った routes.rb の記載方法は下記の様になります。

$ cat config/routes.rb
Rails.application.routes.draw do
  resources :users do
    post :follow, on: :member
  end
end

 

collection の使い方

collection は全部のデータに対するアクションに利用します。

例えば、ユーザーの検索を行う search アクションを新しく追加したいとします。

この場合、検索対象は全部のユーザーです。

A さんが検索しても、B さんが検索しても同じ結果になるはずです。

なので、URL を作成するとしたら、下記の様になります。

http://$(DNS)/users/search

collection を使った routes.rb の記載方法は下記の様になります。

$ cat config/routes.rb
Rails.application.routes.draw do
  resources :users do
    get :search, on: :collection
  end
end

 

まとめ

member と collection の使い方をまとめました。

member は特定のデータに対するアクションに利用します。

collection は全部のデータに対するアクションに利用します。

和訳すると collection は集団、member は (集団の) 一員 みたいな意味なので、そう覚えておけば分かりやすいですね。

【Ruby on Rails】ルーティング scope と namespace の違い

概要

Rails で API を作成するときに、URL 設計を気にすると思います。

例えは、ユーザ情報を操作する users API のエンドポイントを下記のようなパスで作成したいとします。

http://$(DNS)/api/v1/users

この場合、routes.rb をどう書くのか調べたところルーティングのメソッドに namespace と scope と言うものがありました。

この 2 つの使い方を簡単にまとめました。

 

namespace の使い方

namespace を使うときは、Controller に URL のパス同様に /api/v1/ のディレクトリを作成する必要があります。

$ tree app/controllers/
app/controllers/
├── api
│   └── v1
│       └── users_controller.rb
├── application_controller.rb
└── concerns

app/controllers/api/v1/users_controller.rb の中身は、module を使って URL のパス同様の構成にします。

$ cat app/controllers/api/v1/users_controller.rb
module Api
  module V1
    class UsersController < ApplicationController
      def index
       render json: { status: 200, message: 'Success' }
      end
    end
  end
end

routes.rb は namespace を利用すると、このように記載できます。

$ cat config/routes.rb
Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      resources :users, only: :index
    end
  end
end

ルーティングを確認します。

ひとまず、作成したい API のエンドポイントが用意できました。

$ rake routes
      Prefix Verb URI Pattern             Controller#Action
api_v1_users GET  /api/v1/users(.:format) api/v1/users#index

最後にエンドポイントにアクセスできるか確認してみます。

$ curl 'http://localhost/api/v1/users'
{"status":200,"message":"Success"}

出来ました!レスポンスに想定通りの結果が返ってきました!

 

scope の使い方

scope を使うときには、Controller にわざわざディレクトリを作成する必要はありません。そのままで大丈夫です。

$ tree app/controllers/
app/controllers/
├── application_controller.rb
├── concerns
└── users_controller.rb

app/controllers/users_controller.rb の中身もそのままで大丈夫です。

$ cat users_controller.rb
class UsersController < ApplicationController
  def index
    render json: { status: 200, message: 'Success' }
  end
end

routes.rb は scope を利用すると、このように記載できます。

$ cat config/routes.rb
Rails.application.routes.draw do
  scope :api do
    scope :v1 do
      resources :users, only: :index
    end
  end
end

ルーティングを確認します。

ひとまず、作成したい API のエンドポイントが用意できました。

$ rake routes
Prefix Verb URI Pattern             Controller#Action
 users GET  /api/v1/users(.:format) users#index

最後に、エンドポイントにアクセスできるか確認してみます。

$ curl 'http://localhost/api/v1/users'
{"status":200,"message":"Success"}

こちらの方法でも、想定通りの結果を受けることが出来ました!

 

まとめ

namespace と scope で URL を設計する方法をまとめました。

routes.rb の記載方法はどちらも同じです。

大きく異なるのは namespace を利用するときは、Controller 内に実際の URL パスと同様のディレクトリ構成を作る必要があるという点です。

namespace と scope どちらを使って URL 設計すれば良いかは、アプリケーションによると思います。

例えば、Web サーバの機能の一部に API の機能が混じっているアプリケーションの場合、Contoroller 内に API 用のディレクトリを作成して、namespace を使った URL 設計を行うのが綺麗な気がします。

API 機能しかないアプリケーションであれば、コントローラー内にわざわざディレクトリを作成せずに、scope を使ってを URL 設計を使うのが綺麗な気がします。

【AWS】Ruby on Rails + Nginx + Unicorn + MySQL 環境構築

概要

AWS で初めて Rails のアプリを作成した時の手順をまとめました。

手順に沿って実行して頂ければ、Rails アプリの起動まで出来ると思います。

 

環境

こちらの環境でアプリケーションを作成しました。

サーバOS   Amazon Linux
Web サーバ   Nginx
Rack サーバ   Unicorn
データベース   MySQL
フレームワーク   Rails 5.0.X
プログラミング言語

  Ruby 2.4.X

 

タイムゾーンの設定

初めにタイムゾーンの設定を変更します。

Amazon Linux では、デフォルトのタイムゾーンが UTC (協定世界時間) に設定されています。

このままでは扱いづらいので、JST (日本標準時間) に変更します。

$ sudo vim /etc/sysconfig/clock
ZONE="Asia/Tokyo" # ZONE エントリを書き換えます
UTC=true          # 変更しないでください

$ sudo ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
$ sudo shutdown -r now

$ date
Tue Jul 25 21:57:06 JST 2017 # JST に変更されています

参考:Linux インスタンスの時刻の設定 - Amazon Elastic Compute Cloud

 

ロケールの設定

次にロケールの設定を行います。

基本的に UTF-8 に変更しようと思います。

$ sudo vim /etc/sysconfig/i18n
LANG=ja_JP.UTF-8
LC_CTYPE=ja_JP.UTF-8
LC_NUMERIC=ja_JP.UTF-8
LC_TIME=ja_JP.UTF-8
LC_COLLATE=ja_JP.UTF-8
LC_MONETARY=ja_JP.UTF-8
LC_MESSAGES=ja_JP.UTF-8
LC_PAPER=ja_JP.UTF-8
LC_NAME=ja_JP.UTF-8
LC_ADDRESS=ja_JP.UTF-8
LC_TELEPHONE=ja_JP.UTF-8
LC_MEASUREMENT=ja_JP.UTF-8
LC_IDENTIFICATION=ja_JP.UTF-8

先ほどと date コマンドを実行してみると、日本語表記になりました。

$ date
2017年  7月 25日 火曜日 21:58:56 JST

参考:ロケールとは - 国際化対応言語環境の利用ガイド

 

Yum インストール

標準ライブラリ

$ sudo yum install gcc-c++ glibc-headers openssl-devel readline libyaml-devel readline-devel zlib zlib-devel

Git インストール

$ sudo yum install git

MySQL インストール

$ sudo yum install mysql mysql-devel

Nginx インストール

$ sudo yum install nginx

 

Ruby インストール

現在の Ruby の Version を確認します。

(※ デフォルトで Ruby が入っていない環境もあると思います。)

$ ruby -v
ruby 2.0.0p648 (2015-12-16) [x86_64-linux]

Rails 5.0.0 以上は Ruby 2.2.2 以上が必要となります。

Rails 5.0.0 以上を使いたいので、Ruby の Version を 2.4.0 にアップデートします。

$ git clone git://github.com/sstephenson/rbenv.git ~/.rbenv
$ git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ source ~/.bash_profile

$ rbenv install -v 2.4.0
$ rbenv rehash
$ rbenv global 2.4.0
$ ruby -v
ruby 2.4.0p0 (2016-12-24 revision 57164) [x86_64-linux]

参考

 

Bundler インストール

gem を使って Rails をインストールするので、先に gem 同士の互換性を管理してくれる bundler をインストールします。

$ gem install bundler

 

Rails インストール

Rails 5.0.X 系の最新バージョンをインストールします

$ gem install --no-ri --no-rdoc rails --version="~>5.0.0"
$ rails -v
Rails 5.0.4

 

Rails アプリ作成

hogehoge アプリケーションを作成します。

# 作業用ディレクトリ確保します
$ sudo mkdir /var/workspace
$ sudo chown ec2-user /var/workspace
$ cd /var/workspace

# データベースは MySQL を指定します
# Gemfile を修正したいので、この段階では bundle install を skip します
$ rails new hogehoge -d mysql --skip-test --skip-bundle

# 作成したアプリケーションに移動してください
$ cd hogehoge

Gemfile を修正

Rails では アプリケーションで利用したい gem パッケージを管理した、Gemfile というものがあります。

この Gemfile に unicorn の設定を追加してインストールする必要があります。

ちなみに、Javascript を利用するために必要なので、therubyracer も追加してください。(既に Gemfile に記載されているので、コメントアウトを外すだけでもいいです。)

# この 2 行を Gemfile に追加してください
$ vim Gemfile
gem 'unicorn', '~> 5.3'
gem 'therubyracer', platforms: :ruby

# Gemfile に書かれた gem パッケージと、その依存パッケージをインストールします
$ bundle install --path vendor/bundle

Nginx の設定

3 箇所ほど修正点があります。

$ sudo vim /etc/nginx/nginx.conf
user ec2-user; # user 変更
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;

include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    include /etc/nginx/conf.d/*.conf;

    index   index.html index.htm;
    
   # unicornapp 追加
    upstream unicornapp {
        server unix:/var/workspace/hogehoge/tmp/unicorn.sock;
    }
    server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  localhost;
        root         /usr/share/nginx/html;

        include /etc/nginx/default.d/*.conf;

        location / {
            proxy_pass http://unicornapp; # proxy_pass 追加
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }
}

設定ファイルを更新したら、Nginx を再起動します

$ sudo service nginx restart
nginx を停止中:                                            [  OK  ]
nginx を起動中:                                            [  OK  ]

 EC2 ダッシュボードに戻って、セキュリティグループにインターネットからの HTTP アクセス許可のルールを追加してください。

f:id:kyamanak83:20170725235134p:plain

ここまで完了すると、インスタンスのパブリックDNS、又はパブリック IP にブラウザからアクセスした時、Nginx のエラー画面が見れるはずです。

 

f:id:kyamanak83:20170726223713p:plain

データベース接続の設定

Rails アプリケーションで利用する、データベースとの接続設定を行います。

データベースは既にRDSインスタンスに用意してあるものとして話を進めます。

$ vim config/database.yml
default: &default
 adapter: mysql2
 encoding: utf8
 pool: 5
 port: 3306
 username:  # ユーザ名
 password:  # パスワード
development:
 <<: *default
 host: # ホスト名
 database: # データベース名

RDS ダッシュボードに戻って、セキュリティグループに、データベースに接続したい EC2 インスタンスのプライベート IP を追加してください。

f:id:kyamanak83:20170726225648p:plain

Unicorn の設定

最後に Rails アプリケーションの画面を表示するために、Unicorn の設定を行います。

Google で検索すると色んな人の設定が出てくると思います。

自分は下記のように設定しました。(自分も Google 検索で誰かのをパクりました^^;)

新しく config/unicorn.rb ファイルを作成します。

$ vim config/unicorn.rb
rails_root = File.expand_path('../../', __FILE__)

worker_processes 2

working_directory rails_root
timeout 30
preload_app true

# unicorn.sock ファイルの PATH を変更する場合は nginx.conf の修正も必要です
listen "#{rails_root}/tmp/unicorn.sock"
pid "#{rails_root}/tmp/unicorn.pid"

stderr_path File.expand_path('../../log/unicorn_stderr.log', __FILE__)
stdout_path File.expand_path('../../log/unicorn_stdout.log', __FILE__)

preload_app true

before_fork do |server, worker|
  old_pid = "#{server.config[:pid]}.oldbin"
  if old_pid != server.pid
    begin
      sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
      puts "Sending #{sig} signal to old unicorn master..."
      Process.kill(sig, File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end

  sleep 1
end

after_fork do |server, worker|
  ActiveRecord::Base.establish_connection if defined?(ActiveRecord::Base)
end

Unicorn を起動させます

$ bundle exec unicorn -c config/unicorn.rb -E development -D

 

最後に

ここまで完了すると Rails の画面が表示されると思います。

 

f:id:kyamanak83:20170726225834p:plain

おしまい。

【Ruby on Rails】緯度経度から 2 点間の距離を算出する

概要

最近では企業が多くの API を公開しており、それらを利用して簡単にアプリケーションを作成できるようになりました。

その中には、店舗情報を扱う API も数多く存在します。

店舗情報を扱う API の多くは、店舗の位置情報を表すため緯度経度を利用しています。

位置情報を利用したアプリケーションを作成する場合、緯度経度から 2 点間の距離を求めたいと思うことがあるかもしれません。

例えば、店舗情報 API と組み合わせて、『現在地周辺の半径 ○○ m 以内のレストランを近い順に表示したい』と思った場合、これに該当します。

今回、緯度経度から 2 点間の距離を算出するプログラムを作成する機会があったので、その時のことをまとめました。

 

計算式

地球は球体なので、地球上の 2 点間の距離を算出するには、大円距離を求める必要があります。

大円距離 - Wikipedia

大円距離の計算式はいくつかあるようですが、全ての距離に対して用いることのできる Vincenty 法を利用することとします。

Vincenty法 - Wikipedia

 

プログラムを作成

Vincenty 法に当てはめていきます。

def distance(lat1, lng1, lat2, lng2)
  # ラジアン単位に変換
  x1 = lat1.to_f * Math::PI / 180
  y1 = lng1.to_f * Math::PI / 180
  x2 = lat2.to_f * Math::PI / 180
  y2 = lng2.to_f * Math::PI / 180
  
# 地球の半径 (km) radius = 6378.137
# 差の絶対値 diff_y = (y1 - y2).abs
calc1 = Math.cos(x2) * Math.sin(diff_y) calc2 = Math.cos(x1) * Math.sin(x2) - Math.sin(x1) * Math.cos(x2) * Math.cos(diff_y)
# 分子 numerator = Math.sqrt(calc1 ** 2 + calc2 ** 2)
# 分母 denominator = Math.sin(x1) * Math.sin(x2) + Math.cos(x1) * Math.cos(x2) * Math.cos(diff_y)
# 弧度 degree = Math.atan2(numerator, denominator)
# 大円距離 (km) degree * radius end

 

abs は数値の絶対値を取得するメソッドです。

絶対値を取得する - 数値(Numeric)クラス - Ruby入門

数値計算用のメソッドはこちらを参考にしてください。

module Math (Ruby 2.4.0)

 

算出結果の確認

先ほどのプログラムで新宿駅と渋谷駅の距離を求めます。

# 新宿駅
lat1 = 35.689407
lng1 = 139.700306

# 渋谷駅
lat2 = 35.658034
lng2 = 139.701636

distance = distance(lat1, lng1, lat2, lng2)

# 小数点 6 桁で四捨五入
puts "#{distance.round(6)} km"

プログラムを実行します。

$ ruby distance.rb
3.494497 km

 

下記サイトで 2 点間の緯度経度を入力した結果と同じになりました。

2地点間の距離と方位角 - 高精度計算サイト

 

まとめ

緯度経度から 2 点間の距離を算出する場合、大円距離を求める必要があります。

プログラムは Vincenty 法の公式に当てはめれば実装できます。

 

最後に

Ruby on Rails には位置情報を扱う gem がいくつか用意されています。

github.com

github.com

今回、作成した 2 点間距離を求めるプログラムも機能として盛り込まれています。

他にも多くの機能が用意されているようなので gem を使ってみるのも有りだと思います。

自分のアプリケーションにあった方法を採用してください。