ひよっこエンジニアの雑多な日記

なんとかWeb系のエンジニアをやっています。

【Nginx】ELBを通して認証用tokenをリクエストにのせる

一週間ぶりくらいの更新。週1では更新していくように勤めたい(手抜き記事だったとしても)

今回は完全に手抜き記事かつ備忘録

概要

今回Railsで簡単なToken認証をできるようにして無駄にAPIを叩かれないように対処したのですがEC2一台構成の環境では認証をパスすることができたものの、ELBを使って冗長な構成になっている環境ではうまく実行されなかった。

実行したかったのはAPIにリクエストをかけるような感じのcurl

curl https://example.com/api/v1/hoge -H 'Authorization: Token token' -X PUT

ELBを通してリクエストをかけるとAuthorizationの情報が抜け落ちてしまって認証が通らなくなっていた。

対応内容

NginxでリクエストヘッダーにAuthorizationの情報を乗せるようにすればうまくいきました。 設定は以下の感じ(locationのティレクティブに設定する感じ)

proxy_set_header Authorization $http_authorization;

一行入れれば解決でした。 $http_authorizationにリクエストに乗せたAuthorizationの情報が入っているのでそれをheaderに設定すればいけるって感じっぽいですね!

さいごに

最近開発のモチベはあるものの、その他雑務のせいでモチベが削られるという微妙な状態になっている…

【Rails】多対多のアソシエーションに別名をつけたいあなたに

最近涼しくなってきましたねー…と思ったら次の日は暑かったりで体を壊しそうになります。(2週間前にすでに壊した)

今日は最近コードをレビューする機会があって、多対多のアソシエーションをうまいこと設定できていなくて指摘したかったものの、どうやって設定するか微妙に調べることになったのでもう忘れないようにブログにしておこうと思った次第。

想定

会員がブログを投稿できる機能があって、他の人が書いたブログをお気に入りできるみたいな機能を作る想定で
以下のような感じのテーブル構成をイメージ

f:id:kimuraysp:20170905230916p:plain

とりあえずわかりやすいところから

各モデルのアソシエーションのとりあえずわかりやすいところから記載

# user.rb
class User < ApplicationRecord
  has_many :blogs
  has_many :favorites
end

# blog.rb
class Blog < ApplicationRecord
  belongs_to :user
  has_many :favorites
  has_many :users, through: :favorites
end

# favorite.rb
class Favorite < ApplicationRecord
  belongs_to :user
  belongs_to :blog
end

多対多の関連がある時はthroughオプションをつけてあげると中間テーブルを経由して関連先のモデルを取得できるようになります。
throughをつけないと中間テーブルの先のレコードを取得するのが一手間必要になり微妙なので設定するのが吉です。

# blogからuserを取得する例

# through設定した場合
blog.users

# through設定しない場合
 blog.favorites.map { |favorite| favorite.user } 

設定しない場合の取得方法はあくまで例ですが何かしら下処理が必要になってしまいます…

で、それならuserにもthroughを設定してblogを取得できるようにしたらいーじゃん!ってなりますよね。
ただ今回はblogとuserにはすでにアソシエーションが存在します。
こんな時は別名をつけてあげれば良いのです!

多対多で中間テーブルの先のモデルとアソシエーションする

こんな感じで実現できます。

# user.rb
class User < ApplicationRecord
  has_many :blogs
  has_many :favorites
  has_many :favorite_blogs, through: :favorites, source: :blog
end

sourceオプションを設定することでuser.favorite_blogsでuserがお気に入りしたブログを一気に取得することができます。

Railsは外部キーの名前などでアソシエーションの判断をしていますが、アソシエーションの名前を外部キーの名前から外れた名前にする場合はそれをオプションをつけて教えてあげる必要があります。
sourceオプションがthroughする際にアクセスすべきモデルを教えるオプションとなります。
source: :blogとすることでfavorite.rbで設定しているbelongs_to :blogを参照するようになり、favorite_blogsというblog_idに紐づくという判断ができない名前であってもblog.rbに辿り着けるようになるといった感じです!(説明わかりづらい説)

なのでこのようなアソシエーションを作成したいとなった場合はsourceオプションを活用しましょう!

ちなみに1対多で違った名前を付ける際の対処

たとえば何らかの理由でhas_many :blogshas_many :write_blogsにしなければいけなくなった際は

class User
  has_many :write_blogs, class_name: 'Blog', foreign_key: :user_id
  # 以下省略
end

みたいな感じで、このアソシエーションはどこのモデルとのアソシエーションなのか、どの外部キーを用いるのかをオプションで明示する必要があります。

最後に

NEW GAMEを見てるとみんな才能があって素敵に思う。
ねねっちを弊社にスカウトしたいのだけどどこで出会えるのかしら

Rubyのヒアドキュメントで改行文字が認識されなくて戸惑った話

できるだけ気づきがあったらブログを更新しようと努力したいきむらです。
今回の記事はめっちゃ初歩的かつ、なんならヒアドキュメント関係ない記事です…笑

概要

SlackBotに通知を投げさせるような処理を作っていて文面をヒアドキュメントで作成しようとしていました。
配列に値を格納してヒアドキュメント上でjoinを使って改行文字(\n)で連結表示させようとしていた時のことです。 joinの引数にもたせた改行文字がうんともすんともいわないホラー的な現象がおきていました。

書いていたプログラムは下のような感じです。

str_ary = ['hoge', 'fuga', 'piyo']

<<~EOS
  文字列一覧です
  #{str_ary.join('\n')}
EOS

これで

文字列一覧です
hoge
fuga
piyo

という結果を期待していたのですが実際に帰ってきた結果は以下

文字列一覧です
hoge\nfuga\npiyo

これは無情ですね。悲しみしかありません。
ヒアドキュメント上だと改行文字が認識されないのかな?とか思ってしまって無駄に調べ物して時間を潰してしまいました。

原因

調べたと言うより途中で気づいてしまいました。真犯人に。

結果的に言うとヒアドキュメントは1mmも悪いことはしていませんでした…

何が原因だったのかと言うと

str_ary.join('\n')

こいつでした。セパレート文字の指定の仕方が悪かったのです。

解決法

犯人であるセパレート文字の指定の方法を

str_ary.join("\n")

シングルクォーテーションからダブルクォーテーションに変更しました。
すると、なんということでしょう、期待した結果が得られるではありませんか。

シングルクォーテーションだと式展開ができないって言うのは知っていたのですが特殊文字エスケープされてしまって効かなくなるとは知りませんでした。

つまりヒアドキュメントは何も悪くない。文字列の指定の仕方が悪かったんだ。
めっちゃ細いし初歩的なことなんですが、僕みたいに迷える子羊となってしまう人が現れるかもしれないのでメモメモ。

さいごに

最近仕事をこなしてもこなしても増えるんだよなあ
自分、分裂できないかなあ

AWS CodeDeployを使ってGitHubにあげているリポジトリをデプロイする

AWS CodeDeployを使ってみたら思いの外、簡単に設定できてデプロイができたので覚書きです!
そういえばAWSには結構お世話になってるけど記事を書くのは初めてという事実…

CodeDeployとは

AWS CodeDeployは読んで字のごとくEC2インスタンスなどにコードをデプロイしてくれるサービスです。

aws.amazon.com

デプロイ対象のEC2にエージェントをインストールする

CodeDeployを利用するためにはCodeDeployのエージェントをEC2インスタンスにインストールしておく必要があります。
エージェントのインストールにはaws-cliでS3からエージェントをダウンロードする必要があるのでEC2にS3のread権限のロールを設定してあげる必要があります。

EC2にロールを設定する

まずはIAMにアクセスします。
https://console.aws.amazon.com/iam

ロール>新しいロールの作成でロールの作成を行う。

作成するロールは以下

ロールタイプ ポリシー
Amazon EC2 AmazonS3ReadOnlyAccess

作成したロールをデプロイ対象のEC2に設定します。

CodeDeployのエージェントをインストールする

EC2にロールを設定後、EC2にSSHログインをします。

以下コマンドを実行していきCodeDeployのエージェントをインストールします。

$ sudo yum install aws-cli
$ aws s3 cp s3://aws-codedeploy-ap-northeast-1/latest/install . --region ap-northeast-1
$ chmod +x ./install
$ sudo ./install auto

エージェントのインストールはこれで完了です!!

デプロイするアプリケーションにappspec.ymlを作成

CodeDeployでどのようにデプロイを実行するかを設定するファイルappspec.ymlをデプロイするアプリケーションのプロジェクトルートに配置します。
今回はAmazonLinuxを想定しています。

version: 0.0
os: linux
files:
  - source: /
    destination: /var/www/application

上記の設定はアプリケーションのファイル全体をEC2の/var/wwwapplication配下にデプロイするという設定を行っています。
設定内容は今回は省略しますが、以下の記事がわかりやすかったです。

dev.classmethod.jp

appspec.ymlを作成したらコミットしてGitHubにプッシュしておきます。
(CodeDeployはGitHubのデフォルトブランチをデプロイするのでプッシュしたブランチがデフォルトブランチではない場合はマージしておきます。)

CodeDeployを使ってデプロイを実行する

いよいよCodeDeployを利用してデプロイしていきます。

CodeDeploy用のロールを作成する

実行する前にCodeDeployのアプリケーションを作成するためにはCodeDeployのロールが必要なので再びIAMのダッシュボードにアクセスします。

作成するロールは以下になります。

ロールタイプ ポリシー
AWS CodeDeploy AWSCodeDeployRole

アプリケーションを作成する

AWSのダッシュボードからCodeDeployにアクセスします。
初めてCodeDeployを利用する際は以下のような画面になるので今すぐ始めるをクリックする。

f:id:kimuraysp:20170824233426p:plain

チュートリアルをやるか問われますが実施しなくても良いのでカスタムデプロイを選択してウォークスルーのスキップをクリックする。

f:id:kimuraysp:20170824233507p:plain

ウォークスルーをスキップするとアプリケーションの作成画面に遷移するので、以下の設定を行う。

項目 設定内容
アプリケーション名 任意
デプロイグループ名 任意
デプロイタイプ インプレースデプロイ
環境設定 Amazon EC2インスタンス > デプロイしたいインスタンス
デプロイ設定 CodeDeployDefault.OneAtATime
サービスロール CodeDeploy用に作成したロール

デプロイを実行する

CodeDeployダッシュボードから先ほど作成したアプリケーションのリンクにアクセスします。
デプロイグループを選択して、新しいリビジョンのデプロイをクリックします。

f:id:kimuraysp:20170824235553p:plain

画面遷移したら以下の設定を行います。

項目 設定内容
リポジトリタイプ アプリケーションはGitHubに格納されています
デプロイメントの説明 任意
GitHubアカウント GitHubの認証を行います
リポジトリ GitHubリポジトリ名(アカウント名/リポジトリ名の形式で)
コミットID デプロイしたいコミットID(省略形のコミットIDではなくフルの方で)

その他の設定は任意で行いデプロイボタンをクリックします。

するとデプロイが開始され指定したEC2へデプロイが実行されます!

最後に

設定する内容はちょいちょいありますが設定さえ行ってしまえば簡単にデプロイを実行することができるようになります!
さらにGitHubの設定をもう少し行うことでデフォルトブランチに変更が加わったのを検知して自動でデプロイを起動するといったことも可能です!(次の記事はそれかこうかな…笑)

いやー、AWSは本当に便利。設定するだけでなんでもできちゃうんじゃないかな。(ただし財力が必要)

ロードバランサーを経由してPOST投げたりするとdue to access control checkと言われて困り果てた時の対処

コンスタントに記事を更新と言ってからはや2ヶ月くらいでしょうか。。。笑
備忘録がてら出会ったことがないエラーにあったので久々に更新

概要

最近、ConoHaでアプリケーションサーバー2台の冗長環境を作ってくれないかと言われ割とお安めな価格で対処していた時のことです。
クライアントが新環境でアプリケーションの動作確認を行ってもらっていたところ

Safariでログインとかサインアップできないんだけど調査おなしゃす!!」

というあまりやりたくない系のお願いをされたのがことの発端。

それで実際にSafariで動作確認してみると確かにログイン系の処理ができない。
超勘弁。ブラウザの要素検証したらdue to access control checkという文字が。
調べて見たらCORSの対策が打ててない時にでるっぽかった。

構成

インフラ

ConoHaロードバランサー
ConoHaVPS(アプリケーションサーバー) × 2
ConoHaVPS(DBサーバー) × 1

アプリケーション

Ruby on Rails 4.2.3
Ruby 2.3.0
MariaDB

解決策

Nginxの設定でCORSに関するヘッダー情報をリクエストに追加する設定を追記

location / {
        # ここから
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods "POST, GET, OPTIONS";
        add_header Access-Control-Allow-Headers "Origin, Authorization, Accept";
        add_header Access-Control-Allow-Credentials true;
        # この辺まで追記
        proxy_set_header X-Real-IP  $remote_addr;
        proxy_set_header Client-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
        proxy_set_header Host $http_host;
        proxy_redirect  off;
        proxy_pass http://oceanworks-puma;
}

これでログインできるようになった。

その他

最近、疲れが取れない。。。

Rails 5.1のform_withを使ってうまくredirectできないあなたへ

かなり気まぐれですがネタができたらコンスタントにブログを投稿しようという気持ちになり始めました。
今年一年で50投稿を目指して頑張ります。

概要

Rails5.1 + sorceryを使って認証周りをサクッと作ろうとしたのですが、5.1から出てきたform_withに微妙にはめられました。
結構初歩的っぽいですが僕と同じような気持ちになる人がいるかもしれないので備忘録。
というかRailsの4.x系とか5.0系やってきた人が試しに5.1使ってみよってなったら割とハマるんじゃないかと勝手に思ったり…
※ ブログの内容的にはsorceryはもはや関与しません

事象

以下みたいな感じでログインフォームを作ってました。

<%= form_with url: sign_in_path, method: :post do %>
  <div class="field">
    <%= label_tag :email %><br />
    <%= text_field_tag :email %>
  </div>
  <div class="field">
    <%= label_tag :password %><br />
    <%= password_field_tag :password %>
  </div>
  <div class="actions">
    <%= submit_tag 'Login' %>
  </div>
<% end %>

これでログインできるか確認するためにLoginボタンを押すと

ログイン成功してるはずなのに画面遷移しない・・・・!!!

いやいやと思ってLoginボタンを連打してみるも状況は変わらず。
しかし画面再読み込みしたらログインできてる。

ログ見たら

Started POST "/sign_in" for 127.0.0.1 at 2017-07-08 23:03:12 +0900
Processing by UserSessionsController#create as JS

なんかJSをレンダリングしようとしてますやん!
そりゃ画面遷移しないわけだ。

解決策

ちゃんとform_withの仕様を把握してから使えばよかったのですが、解決策としてはなんてことない感じでした。
解決したコードがこちら

<%= form_with url: sign_in_path, method: :post, local: true do %>
  <div class="field">
    <%= label_tag :email %><br />
    <%= text_field_tag :email %>
  </div>
  <div class="field">
    <%= label_tag :password %><br />
    <%= password_field_tag :password %>
  </div>
  <div class="actions">
    <%= submit_tag 'Login' %>
  </div>
<% end %>

ほぼ変わってない感じですが、form_withlocal: trueオプションをつけてあげてます!
これでHTMLをレンダリングしてくれるので意図した動きになります!

どうもform_withはデフォルトで生成されるformタグにdata-remoteがついちゃうっぽいです。
local: trueを設定することでこれをつかないようにできるとのこと。

よくよく考えたらscaffoldで生成された_form.html.erbみるとform_withlocal: trueがついてんだよなあ
まあしかしこれで大人の階段をまた一つ登ることができました

余談

scaffoldで生成されるerbファイルのform_withのブロック変数がformになってるんですね!
いままではfがブロック変数でしたが、やはり可読性を考えると一文字よりも意味のある単語の方がよいんですかねえ

CircleCIでelasticsearchにkuromojiをインストールして起動させる

CircleCIで自動テストをしたいもののelasticsearchの処理を含んだ処理周りがうまくテストされなくて結構ハマったので備忘録。

やりたいこと

アプリケーションの機能として簡単な全文検索をelasticsearchを使って実現していたので、CircleCIにもelasticsearchを導入して全文検索用のindexを作成をアプリケーションのテストコードで行なっていた。
CircleCIを使ってGitHubでプルリクエストを上げた瞬間に自動でテストが走らせるCI環境を構築して、全テストをパスさせたい!

問題点

しかし、elasticsearchを導入してpluginとしてkuromojiを導入する必要があり、それを実現するのに四苦八苦。
elasticsearchを動かすだけならできるのだが、kuromojiを導入する方法がわからない…

ひとまずここみたら全てが解決しました。

解決法

circle.ymldependenciesに以下を追記してあげるだけでOK

dependencies:
  post:
    - wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.3.2.tar.gz
    - tar -xvf elasticsearch-5.3.2.tar.gz
    - elasticsearch-5.3.2/bin/elasticsearch-plugin install analysis-kuromoji
    - elasticsearch-5.3.2/bin/elasticsearch: {background: true}
    - sleep 10 && wget --waitretry=5 --retry-connrefused -v http://127.0.0.1:9200/

これ書いておくとelasticsearchをwgetでダウンロードして、解凍して、kuromojiインストールして、elasticsearchを起動してくれる。

毎回wget走るの重くなりそうでいややなーとなる場合は以下みたいな感じに書き換える。

dependencies:
  cache_directories:
    - elasticsearch-5.3.2
  post:
    - if [[ ! -e elasticsearch-5.3.2 ]]; then wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.3.2.tar.gz && tar -xvf elasticsearch-5.3.2.tar.gz && elasticsearch-5.3.2/bin/elasticsearch-plugin install analysis-kuromoji; fi
    - elasticsearch-5.3.2/bin/elasticsearch: {background: true}
    - sleep 10 && wget --waitretry=5 --retry-connrefused -v http://127.0.0.1:9200/

cache_directoriesを使うとディレクトリをキャッシュできるようなので、wgetして解凍したディレクトリをキャッシュしてくれるようになる。
! -e elasticsearch-5.3.2のところでディレクトリの存在確認をしてくれるのでキャッシュされてなかったらwget、解凍、インストールしてくれるし、キャッシュされてたらこの処理を飛ばしてくれるようになるので無駄なオーバーヘッドなくなるぽい。

これでelasticsearchをCircleCI上でも使えるようになってめでたしめでたし。

ちなみに

machine:
  services:
    - elasticsearch

最初、これでelasticsearch立ち上がるやん!と喜んでいたのですが、この記述をしているとwgetで落としてきたelasticsearchとは別物の子が動いちゃうので、いくらkuromojiをインストールしてもpluginがインストールされていないことになってテストが全く通らなくて禿げ上がりそうでした。
この記述に気づかず1時間ぐらい消費したのは愚かでした…