ポンコツ.log

ひよっこエンジニアのちょっとしたメモ。主に備忘録。たまに雑記。

【Rails】アップロードしたPDFから数枚画像に切り出したい【Grim】

何気に「このPDF、数ページだけ画像にして保存したい…」という時があると思うのです。 …なかったとしても、万が一発生した時にGrimというgemが使えそうだったのでメモ。

github.com

処理の流れ

想定している処理は、
1. PDFを保存&S3にアップロード
2. 保存したPDFから3枚画像に切り出す
3. 切り出した画像を保存&S3にアップロード
です。では早速。

モデル準備

f:id:mr_96:20170214231326j:plain ↑のイメージでモデルを作ります。
DocumentはPDFを持つように、PdfImageはcapture(=PDFから切り出した画像)を持つようにします。
Documentは複数枚の画像を持つので、has_manyで。
PDFと切り出した画像のアップロードはpaperclipを使います。 今回はpaperclipは入っている前提で進めます。

必要なファイルを生成

$ rails g model PdfImage
$ rails g scaffold documents

migrationファイルに必要なフィールドを追加。

# db/migrate/201702140001_create_documents.rb
class CreateDocument < ActiveRecord::Migration[5.0]
  def change
    create_table :douments do |t|
      t.string     :name
      t.attachment :pdf

      t.timestamps
    end
  end
end

# db/migrate/201702140000_create_pdf_images.rb
class CreatePdfImage < ActiveRecord::Migration[5.0]
  def change
    create_table :pdf_images do |t|
      t.attachment :capture
      t.references :document, foreign_key: true

      t.timestamps
    end
  end
end

migrationファイルができたら、コマンド実行。

$ RAILS_ENV=development rails db:migrate

モデルにattachmentのvalidationなど、必要項目を足していきます。

# app/models/pdf_image.rb
class PdfImage < ApplicationRecord
  has_attached_file :capture
  belongs_to :document

  validates_attachment_content_type :capture, :content_type => 'image/png'
end

# app/models/document.rb
class Document < ApplicationRecord
  has_attached_file :pdf
  has_many :pdf_images, dependent: :destroy

  validates_attachment_content_type :pdf, :content_type => 'application/pdf'
end

PDFの場合、白背景が透過されてしまい、白い背景が真っ黒になる場合もあるので、その時はhas_attached_fileのオプションにImageMagickでの変換指定することで回避できます。

has_attached_file :pdf, convert_options: {all: '-flatten'}

Grim処理のworkerを作成

PDFから画像を切り出して保存&S3でアップロードまでやろうとすると時間がかかるので、Workerで処理するようにします。

class CreatePdfImagesWorker
  include Sidekiq::Worker
  sidekiq_options queue: :create_pdf_images, retry: false

  def perform(document_id, target_file)
    document = Document.find(document_id)
    old_pdf_images = document.pdf_images.presence

    pdf = Grim.reap(target_file)
    # 切り出した画像の一時的な保存先を生成
    path = FileUtils.mkdir_p(Rails.root.join "tmp", "pdfs", document_id.to_s).first

    # 切り出す枚数(デフォルト3ページ分)
    page_num = 3
    page_num = pdf.count if pdf.count < 3

    page_num.times do |page|
      pdf_file_name = document.pdf_file_name.split(".")[0]
      pdf_image_path = "#{path}/#{pdf_file_name}_#{page}.png"
      # 指定ページを画像として切り出し
      if pdf[page].save(pdf_image_path)
        file = File.open(pdf_image_path)
        PdfImage.create(image: file, document_id: document_id)
        file.close
      end
    end

    # 古い画像の削除
    old_pdf_images.each{|pdf_image| pdf_image.delete} if old_pdf_images

    # アップが完了した作業ファイル、ディレクトリを削除
    FileUtils.remove_entry_secure path
    FileUtils.remove_entry_secure target_file
  end
end

controllerでworker呼び出し

controller内でやることは2つです。
1. PDFのtmpファイルを自分の扱いやすいところにコピーする
2. workerを呼び出す

1. PDFのtmpファイルを自分の扱いやすいところにコピーする
privateメソッドとして/tmp以下にあるPDFをコピーするメソッドを作ります。

# 画像切り出し用に、/tmp以下に配置されるPDFのtmpファイルをコピーしておく
# 戻り値はコピーしたtmp fileのpath
def copy_temp_pdf
  tempfile = document_params[:pdf]&.tempfile
  return unless tempfile

  pdf_dir = FileUtils.mkdir_p(Rails.root.join "tmp", "pdfs").first
  # PDFのtmpファイルをプロジェクトルートのtmp以下にコピー
  FileUtils.cp tempfile, pdf_dir
  "#{pdf_dir}/#{File.basename(tempfile)}" # File.join(pdf_dir, File.basename(tempfile)) でも同じ
end

document = Document.new 等で生成したインスタンスをsaveすると、PDFをアップロードする際にtmpファイルも消えてしまうので、saveする前にtmpファイルをコピーする copy_temp_pdf メソッドを呼び出します。
コピーしたファイルのパスが返ってくるので、変数に入れておきます。

2. workerを呼び出す
documentのidが必要になるので、workerの呼び出しはインスタンスsave後に行います。

class DocumentsController < ApplicationController
  # --- 諸々省略 ---
  def create
    @document = Document.new(document_params)
    target_file = copy_temp_pdf

    if @document.save
      CreatePdfImagesWorker.perform_async(@document.id, target_file) if target_file
      format.html
    end
  end
  # --- 諸々省略 ---
end

ざっとこんな感じでした。 ゴリゴリ気合いでやるか、複数枚アップロードするしかないのかなと思っていた矢先にこのGem。 やることが少ない分、使いやすい&わかりやすかったです。 tempファイルごにょごにょしてるあたり、もっと上手くやれたらなー。

【Linux】ディスクフル?と思ったら

以前のエントリでも出てきたのですが、ディスクフルな状態に遭遇することがままあります。 今となっては慣れつつありますが、たまに「ええっと?」となるので、備忘録的に。

ディスクの使用量確認

主に使うのはdfコマンドとduコマンド。
参考:
Linuxコマンド集 - 【 df 】 ディスク・ドライブの使用量を表示する:ITpro Linuxコマンド集 - 【 du 】 ディレクトリ内のファイル容量を表示する:ITpro

dfコマンドで本当にディスクフルなのか、どこが容量を食っているのかを確認します。
hオプションを付けると、人間が見やすいように1Mや1Gなどの単位付きで容量が表示されます。

$ df -h
ファイルシステム    サイズ     使用    残り   使用%    マウント位置
/dev/disk1      465Gi    29Gi  436Gi     7%     /
devfs           178Ki   178Ki    0Bi   100%     /dev

コマンドを実行すると↑のように表示されます。
見たまま、使用%が100%であればディスクフルです。アウト。

ファイル/ディレクトリの容量確認

上の例はめちゃくちゃ余裕ありますが、「やれ、/が残り少ないぞ」とわかったら、次にduコマンドを使って、容量を使いまくっている場所を調べていきます。
sオプションで指定したファイル/ディレクトリの総計、hオプションで、単位付きで容量が表示されます。
/*のようにディレクトリを指定すると、/以下のディレクトリも確認することができます。

$ du -sh /*
2.6G    /private
1.0M    /sbin
4.0K    /tmp
509M    /usr

ルート権限でないと見れず「Permission denied」のエラーが出ることもあるので、その場合はsudoをつけて実行
パイプ(|)でsortコマンドを繋げることで、容量の大きい順/小さい順にソートすることもできます。
参考: Linuxコマンド集 - 【 sort 】 行を並び替える:ITpro

sortも使う際は、duコマンドにhオプションをつけているとうまくソートされないことがあるので、その場合はhオプション抜きで実行。
nオプションで先頭の数値や記号を数値と見なし、rオプションで逆順にソートします。

# hオプションあり
$ du -sh /* | sort -nr
509M    /usr
4.0K    /tmp
2.6G    /private
1.0M    /sbin

# hオプション無し
$ du -s /* | sort -nr
5433696 /private
1042688 /usr
2072    /sbin
8       /tmp

何が容量を使っているのかが判明したら、後は不要ファイルを削除していくだけです。

iノード枯渇説

「ディスク使用率はまだ余裕あるんだけどなぁ…」という時は、iノード数が原因だったということもあるようです。
過去一度だけ遭遇したことがあります。 qiita.com ノード数の確認はdfコマンドのiオプションで調べられます。

iノードについてはこちらがわかりやすかったです。
参考:iノード(inode)とは

雑感

本当はディスクフルになる前に対応できれば良いんだけどなーと思いつつ、ちゃんと監視しているものはアラートが来ても監視されていないものは気づけない現実。 ちゃんと対応できれば良いんですけどね…。

【MySQL】突然のIncorrect key file for table...エラー対応メモ

結果から書くとディスクフルだっただけでしたが、MySQL経験が浅いということもあり、初めて見るエラーに心臓止まるかと思った出来事でした。

- *** -


エラー文の「Incorrect key file for table」でググるとこちらのページがひっかかります。
MySQL :: MySQL 5.6 リファレンスマニュアル :: 15.2.4.1 MyISAM テーブルの破損

「え?破損?」と、早速ビビリポイントが出てくるのですが、落ち着いて中を見ます。

次の状況が発生した場合、テーブルが破損するおそれがあります。
・ mysqld プロセスは、書き込みの最中に強制終了されます。
・コンピュータが予期せずシャットダウンされます (たとえば、コンピューターの電源が切られた場合など)。
・ハードウェア障害。
・サーバーが修正中のテーブルを、外部プログラム (myisamchkなど) を使用して同時に修正しています。
MySQL または MyISAM コードのソフトウェアバグです。
MyISAM テーブルのヘルスを CHECK TABLE ステートメントを利用して確認でき、破損した MyISAM テーブルを REPAIR TABLE を利用して修復できます。

プロセスやサーバ類になにも問題ないことを確認し、もしかしたら本当にテーブルぶっ壊れたかもしれない…と思いながらmysqlcheckコマンドでテーブルのヘルスチェックを試します。
mysqlcheckは下記が参考になりました。 d.hatena.ne.jp

念のため全テーブルをチェックしましたが、全てOKで返ってきました。
頭を抱えながら再度「Incorrect key file for table」でググると、こちらの記事にたどり着きます。
d.hatena.ne.jp 容量の問題かも、という文に「そんなまさか」と思いながらdfコマンドを叩くと、案の定ディスク使用量が100%でした…。

よく考えると、確かにディスクフルだと実行できないよなぁ。

2016年と2017年と

年も明けたので去年を軽く振り返りつつ、今年を考えつつ。

2016年の初めにマンダラチャートを書いていたので、それを見ながら振り返っていると、半分も出来ていない現実に出くわしてただのガチクズじゃないかと絶望しています。
中でも一番出来ていないところが、知識の「インプット&アウトプット」のアウトプット部分。
このブログを眺めるだけでも全然書けてない感がにじみ出ている(かといって他になにかしらを投稿し続けたわけでもない)ので、言い訳しようもないですね。
インプットに関しても、Web上の記事ならすぐ読んでいたものの、積ん読状態の本が発生していたので、2017年は年末に積ん読本がない状態にしたいです。
この部分は2017年も引き続き手を入れたいところ。

それと、いろいろなことに繋がるのが時間管理が下手すぎたというのも。
ワークライフバランスで言うとワークに寄ったところもあったので、うまくバランスをとって行きたかったものの、半年ぐらいしか保たず、結局ワーク寄りに…。
そもそもバランスをとりたいと思ったのも、趣味の時間を確保したい&先を考えてのことなので、ここも2017年引き続きになりそうです。

反面、エンジニアとしての技術的なところは割とできたかな…という印象です。
機械学習とかの話題の分野もちょこっとつま先突っ込んでみたり、競技プログラミング再入門してみたり。
誰でもそうだと思いますが、興味持って進められるところはやっぱり出来るんだろうなー。
今まで付け焼刃なところもあるので、2017年はもうちょっと突っ込んだところの理解もできるようにしていきたいです。サーバ周りやフロント周りを特に。
ハード関わるところが「興味ある〜」止まりだったので、何かしらのアクションを起こせるようにしたいです。

相変わらず反省するところしかないなーということに気づいたので、年末振り返る時には「ただのガチクズ」卒業できていることを祈ります。

利尻&稚内へ行ってきました - 2日目 利尻&稚内編

利尻&稚内旅メモの続編になります。
->利尻編はこちら

2日目は14時頃のフェリーで稚内へ渡る予定だったので、それまでの間に姫沼まで足を伸ばしました。
バス本数が少なく、時間が合わなかったので徒歩でフラフラと。
後から思えばフェリーのレンタルサイクル利用すれば良かったなと思いました…早く気付けよと…。

鴛泊のフェリーターミナルから姫沼までは4キロと少し。徒歩1時間ほどでした。
ところどころ、先の台風による土砂崩れの痕跡が残っていたりして、本当に台風来ていたんだなあと。
恐らく上の方にあった木が歩道の横まで滑り落ちていたり、土嚢が積まれていたり、歩道が泥だらけだったり。
台風の影響を気にしながら進むと姫沼への案内板があったので、案内の通りに右折。
山の方へ向かうのでずっと上り坂になります。上り坂の途中に展望台がありました。

▼姫沼展望台 f:id:mr_96:20160922215959j:plain

展望台を出てもう少し坂を登ると、やっと姫沼につきます。
▼姫沼 f:id:mr_96:20160924222734j:plain

姫沼からも利尻富士が見えます。
▼姫沼からの利尻富士 f:id:mr_96:20160924225128j:plain

姫沼は周回の道があったのですが、うっかり反時計回りに歩いてしまい途中団体客とすれ違う時に数分足止めになったりしました。
周回は時計回りのようです。気をつけます。
ここでも土砂崩れの影響か、大きめの木が根から横倒しになっていたりしました。

帰りは下り坂なので、行きと違って楽ですね…。
途中、海に寄って遊んだりしながらフェリーターミナルに戻り、しばし休憩タイム。

鴛泊-稚内間はフェリーで約1時間40分ほど。
ハートランドフェリーでは、2等室を指定する場合は予約不要のため、当日自動券売機にて購入するようです。
他のフェリーでは連絡先等を申込書に記入し、窓口で受付してから乗船券を発行していたので、「この簡単手続きで良いのだろか…」と不安になったものの、特に問題なく乗船できました。
時期によっては、フェリーからイルカを見ることもできるようですが…乗船後数分で寝てしまったため、今回はイルカの確認どころか景色もほとんど見ずにフェリータイム終了。

稚内に着いた時間がもう夕方だったので、この日は宿へ直行しました。

利尻&稚内へ行ってきました - 1日目 利尻編

帰省に合わせて、ふと行ってみたくなった利尻と稚内へ2泊3日で行ってきました。
その記録を兼ねた旅メモ。

13時10分新千歳空港発の飛行機で利尻空港へ。
移動時間はおよそ50分。14時に利尻空港へ到着しました。
空港出口のところに、ホテル名が書かれた旗を持った方がたくさんいらっしゃいました。
空港から宿泊地まで距離があるからか、送迎の車を出してくれるところが多いようです。
慣れない土地に来た身としては嬉しいですよね。

宿泊先で自転車が借りられたので、自転車を借りて早速観光へ。
利尻はサイクリングロードなる自転車専用道路(途中途中で、歩道との併用区間あり)があり、それも綺麗に舗装されていたりと結構良い道だったので景色を眺めながらサイクリングが楽しめました。
滅多に人も通らないので、マイペースで進んでも問題なし。
途中、野生のイタチに遭遇するというラッキーハプニングもありました。むっちゃ可愛い…。

鴛泊から出発して、沓形方面へ反時計回りにまっしぐら。
道中にちょっとした休憩スポット数箇所もありました。
▼名称忘れてしまいましたが、設置されていた日時計利尻富士
f:id:mr_96:20160922224207j:plain

出発が14時30分頃というのもあって、沓形の手前辺りで折り返して鴛泊方面へ。
夕日ヶ丘展望台へ行こうとしたところ、手前に「富士野園地」と書かれた看板が目に入ったので、フラフラと寄り道。
知らずに行っていたのですが、『北のカナリアたち』のロケ地にもなったところでした。
簡易トイレもあり、休憩スポットでもあるようです。
ポンモシリ島という小島がよく見えます。
▼ポンモシリ島
f:id:mr_96:20160922223539j:plain

そして夕陽ヶ丘展望台。 自転車で爆走した後に展望台頂上までの階段を登り、すっかり息が上がっていましたが、そんな疲労感も吹っ飛ぶような景色でした。…体力つけねば。
▼夕陽ヶ丘展望台頂上からの眺めその1 f:id:mr_96:20160922220020j:plain ▼夕陽ヶ丘展望台頂上からの眺めその2 f:id:mr_96:20160922225609j:plain ▼夕陽ヶ丘展望台頂上からの眺めその3
夕焼けには少し早めでしたが、十分綺麗でした。 f:id:mr_96:20160922225828j:plain

1日目は早々に宿泊先へ戻ったので、続きはまた後日。

【Rails】simple_form製ラジオボタンのラベルにクラスを指定する

simple_form、使うとform_forがすっきりするので良いですよね。
そんなsimple_formを使ってラジオボタンのラベルにクラスをつけようとすると、思いっきりはまって、「あれ、simple_form使えない…?」と思ってしまったのでメモ。
結果的にはsimple_formで解決しました。 github.com

※ viewテンプレートはSlimを使っています。

ノーマルラジオボタン

まず普通にラジオボタンを表示させるのであれば、下記のような書き方で問題なく表示されます。

# plans = {1 => 'もう半分終わるらしい' , 0 => 'まだ半分あるらしい'}

= simple_form_for :sample, url: test_path, html: {method: :post, class: 'sample_form'} do |f|
  p 2016年
  = f.collection_radio_buttons :plans, plans.to_a, :first, :last

実際の表示↓ f:id:mr_96:20160619231800p:plain

HTMLだとこんな感じです。

<form class="simple_form sample_form" novalidate="novalidate" action="/test" accept-charset="UTF-8" method="post">
  <input name="utf8" type="hidden" value="✓">
  <input type="hidden" name="_method" value="post">
  <input type="hidden" name="authenticity_token" value="AUTHENTICITY_TOKEN">
  <p>2016年</p>
  <span>
    <label for="payment_plans_1">
      <input type="radio" value="1" name="payment[plans]" id="payment_plans_1">
      <label class="collection_radio_buttons" for="payment_plans_1">もう半分終わるらしい</label>
    </label>
  </span>
  <span>
    <label for="payment_plans_0">
      <input type="radio" value="0" name="payment[plans]" id="payment_plans_0">
      <label class="collection_radio_buttons" for="payment_plans_0">まだ半分あるらしい</label>
    </label>
  </span>
</form>

ラジオボタンのラベルにクラスをつける

やりたいことはノーマルラジオボタンでなく、ラベルにクラスをつけて、独自のスタイルを適用することです。
この「ラベルにクラスをつける」方法がわからず、ドはまりしました。
いろいろと調べているうちに、StackOverflow内にて回答を発見。
blockを使えば良いらしい。
ruby - simple_form's collection_radio_button and custom label class - Stack Overflow

# plans = {1 => 'もう半分終わるらしい' , 0 => 'まだ半分あるらしい'}

= simple_form_for :sample, url: test_path, html: {method: :post, class: 'sample_form'} do |f|
  p 2016年
  = f.collection_radio_buttons :plans, plans.to_a, :first, :last do |b|
      - b.radio_button + b.label(class: 'sample_radio')

実際の表示↓ f:id:mr_96:20160619231903p:plain

HTMLだとこんな感じです。

<form class="simple_form sample_form" novalidate="novalidate" action="/test" accept-charset="UTF-8" method="post">
  <input name="utf8" type="hidden" value="✓">
  <input type="hidden" name="_method" value="post">
  <input type="hidden" name="authenticity_token" value="AUTHENTICITY_TOKEN">
  <p>2016年</p>
  <span>
    <label for="payment_plans_1">
      <input type="radio" value="1" name="payment[plans]" id="payment_plans_1">
      <label class="sample_radio" for="payment_plans_1">もう半分終わるらしい</label>
    </label>
  </span>
  <span>
    <label for="payment_plans_0">
      <input type="radio" value="0" name="payment[plans]" id="payment_plans_0">
      <label class="sample_radio" for="payment_plans_0">まだ半分あるらしい</label>
    </label>
  </span>
</form>

若干わかりにくいですが、無事、ラベルに「sample_radio」クラスが当たっています。

item_wrapper_classオプションでできるよ!」とか「input_htmlオプションでいけるかも!」といったものを見たので色々試してみたのですが、あえなく全滅し、「simple_form全然シンプルじゃない!」と思って一瞬form_forに傾きかけました。
シンプルじゃないとか思ってスミマセン。

【Itamae】remiリポジトリのPHPをインストールする

1ヶ月ほど前にItamae入門しました。

Itameを使ってvagrant(centOS6.5)上にphpをいれようとすると、バージョン5.3.3がインストールされるのですが、諸事情により5.3.6以上のバージョンを使いたかったので、remiリポジトリからインストールすることに。
そのときのItamaeレシピメモ。

ちなみに、諸事情というのは、PDO×MySQLPHPのプロジェクトで、インスタンス指定時にcharsetが使われていたため。
5.3.6以上であればcharset指定が有効なので、文字化けが回避できる。
5.3.6以前のバージョンでは、charsetではなくSET NAMESで指定することで回避できるらしいです。
参考:MySQLとPDOの組み合わせでcharsetを指定する - Qiita

remiリポジトリの追加

epelリポジトリのインストール+rpmコマンドでremiリポジトリを追加する。

# cookbooks/yum/default.rb

package 'epel-release' do
  action :install
end

package "http://rpms.famillecollet.com/enterprise/remi-release-#{node[:platform_version][0]}.rpm" do
  not_if 'rpm -q remi-release'
end

phpをインストール

追加したremiリポジトリからphpをインストール
ついでに必要なパッケージ群もインストール

# cookbooks/php/default.rb

%w( php php-mbstring php-xml php-gd php-pear php-mysql ).each do |pkg|
    package pkg do
      options '--enablerepo=remi'
      action :install
    end
end

確認

あとはinclude_recpephpインストールするdefault.rbを指定して、itamaeコマンドを実行すると、remiリポジトリからのphpインストールが完了する。
itamaeが無事終了後、phpバージョンを確認

[vagrant@vagrant-centos65 ~]$ php -v
PHP 5.4.45 (cli) (built: May 29 2016 09:17:38)
Copyright (c) 1997-2014 The PHP Group
Zend Engine v2.4.0, Copyright (c) 1998-2014 Zend Technologies

5.3.6以上のバージョン。勝てる。
ついでにinfoも。

[vagrant@vagrant-centos65 ~]$ yum info php
Installed Packages
Name        : php
Arch        : x86_64
Version     : 5.4.45
Release     : 9.el6.remi
Size        : 9.4 M
Repo        : installed
From repo   : remi

remiリポジトリからインストールされていることを確認。

capistranoでデプロイしたときにunicornが再起動しない対応

capコマンドを使ってデプロイしたあと、unicornが再起動していないことが頻発します。

$ ps -ef | grep unicorn | grep -v grep
user  1234     1  0 19:08 ?        00:00:03 unicorn master -E staging -c <PRJ_ROOT>/current/config/unicorn/staging.rb -D
user  5678  1234  0 19:08 ?        00:00:00 unicorn worker[0] -E staging -c <PRJ_ROOT>/current/config/unicorn/staging.rb -D
user  9102  1234  0 19:08 ?        00:00:00 unicorn worker[1] -E staging -c <PRJ_ROOT>/current/config/unicorn/staging.rb -D

プロセスの起動時間がデプロイ時間より古ければ明らかにおかしいので、ひとまずログを確認。
すると、下記のようなログがあります。

I, [2016-06-06T16:01:57.399854 #26684]  INFO -- : forked child re-executing...
<HOME_DIRECTORY>/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/bundler-1.12.5/lib/bundler/definition.rb:23:in `build': <PRJ_ROOT>/releases/20160603064119/Gemfile not found (Bundler::GemfileNotFound)

Bundler::GemfileNotFound なんて言われているけれど、確認するとちゃんとGemfileはあるのでオカシイ。
よく見ると、Gemfileを読み出そうとしている/releases以下のディレクトリ、この時点では既に過去のバージョンのものなので、ディレクトリ自体なかったりします。
なぜか古いGemfileを読み出そうとして、GemfileNotFoundを引き起こしている模様。

調べてみると、ENV['BUNDLE_GEMFILE']の値に古いGemfileのパスが入っていて、その値を利用してプロセスを起動するため「Gemfileないよ!」と言われているらしい。
対応として、before_execで明示的にパスを指定してあげれば、新しいGemfileを読みだすようになる。

# config/unicorn/staging.rb
current_dir = "<PRJ_ROOT>/current"
before_exec do |server|
  ENV['BUNDLE_GEMFILE'] = File.expand_path('Gemfile', current_dir)
end

なんで起きるんだろう。

参考:
Capstarano3でデプロイ時にUnicornが再起動しているようで失敗している時の対処法 - Qiita

S3上にあるファイルがうまく参照できないときのCORS設定

RailsでassetsファイルをS3に上げて、そこにある画像を参照しようとすることはよくあると思うのですが、その際にCORSではまったので対応メモ

Font-Awesomeを使おうとしてエラー

Font-Awesomeを使って表示しているところが、いわゆる豆腐状態(本来フォントが表示されて欲しいところが、□しか表示されない残念な状態)になっていたので開発者ツールのConsoleを確認。
すると、下記のようなエラーが表示されていました。

Font from origin 'https://hoge-bucket.s3.amazonaws.com' has been blocked from loading by Cross-Origin Resource Sharing policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://hogehuga.com' is therefore not allowed access.

※ URLはダミーのものです

エラー対応

おとなしくググってみると、S3上でAccess-Control-Allow-Originを設定しなければないけない、とのこと。

S3のバケット一覧へアクセスし、該当のバケットのプロパティを表示。
プロパティを表示すると画像のような表示が見えるので、アクセス許可のメニューを展開し、CORS設定の編集を選択。 f:id:mr_96:20160601235329p:plain

すると、CORS 構成エディターなるモーダルが表示されるので、テキストフォームにCORSルールを記述します。 <サンプル>

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>*</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
        <AllowedHeader>Authorization</AllowedHeader>
    </CORSRule>
</CORSConfiguration>

f:id:mr_96:20160602000020p:plain

S3側での設定は終わったので、Web上で確認すると、期待した通りに表示されています。
一件落着。

参考:
Cross-Origin Resource Sharing (CORS) - Amazon Simple Storage Service

CORSについて:
CORSまとめ - Qiita