Railsでpaperclipを使ってリサイズしつつS3へ画像をアップロードする
RubyonRailsアプリで、画像アップロード機能を作るにあたって、paperclipを試すことにしたので、技忘録。
想定するユースケース
- エンドユーザ向け画面/管理画面で画像アップロード機能を作りたい
- アップロードした画像はいくつかのサイズにリサイズして使うことを想定する
- 画像はWebサーバ内部ではなく、オンラインストレージを使用して保存する
- アップした画像はWebアプリから参照したい
- アップロードはWebアプリからのみ許可したい
- なるべくシンプルに作りたい
構成
- Ruby 2.1.4
- Rails 4.1.7
- Imagemagick 6.8
- Paperclip 4.2
これらをOSX Yosemite上で実行し、オンラインストレージにはAmazon S3を使います。
前提
事前準備
- ImageMagickのインストール
画像のリサイズにはimagemagickを使うのでbrewでインストール
$ brew install imagemagick
:warning: CentOSとかならyumでインストールしてください
- Gem追加
paperclipとaws-sdk, rmagickのGemを追加して
gem 'paperclip' gem 'aws-sdk' gem 'rmagick'
bundle install します
$ bundle install
Gemfile.lockが更新されます。
Model
- Modelに画像保存用の設定を追加する
class Hoge < ActiveRecord::Base has_attached_file :image, :styles => { :thumb => "90x60", :medium => "180x120", :large => "600x400" }, :storage => :s3, :s3_permissions => :private, :s3_credentials => "#{Rails.root}/config/s3.yml", :path => ":attachment/:id/:style.:extension" validates_attachment_content_type :image, :content_type => ["image/jpg", "image/jpeg", "image/png"] def authenticated_image_url(style) image.s3_object(style).url_for(:read, :secure => true) end end
- Migrationを作る
$ rails g migration AddAttachmentImageToHoges
- migrationファイルの中身はこんな感じ
class AddAttachmentImageToHoges < ActiveRecord::Migration def change change_table :hoges do |t| t.has_attached_file :image end drop_attached_file :hoges, :image end end
- Migrate
$ rake db:migrate
これで
- image_file_name
- image_content_type
- image_file_size
- image_updated_at
の4つのカラムがDBに追加されます。
Controller, View
次にControllerとViewを作っていきます。
$ rails g controller hoges index show new edit
Controller
class HogesController < ApplicationController before_action :set_hoge, only: [:show, :edit] def index @hoges = Hoge.all end def new @hoge = Hoge.new end def show end def edit end # とりあえず、createだけ追加 def create @hoge = Hoge.new(hoge_params) respond_to do |format| if @hoge.save format.html { redirect_to @hoge, notice: 'Hoge was successfully created.' } format.json { render action: 'show', status: :created, location: @hoge } else format.html { render action: 'new' } format.json { render json: @hoge.errors, status: :unprocessable_entity } end end end private def set_hoge @hoge = Hoge.find(params[:id]) end def hoge_params params.require(:hoge).permit(:name, :image) end end
View
- 新規登録画面
<div class="field"> <%= f.label :name %> <%= f.text_field :name %><br> <%= f.label :image %> <%= f.file_field :image %> </div> <div class="actions"> <%= f.submit %> </div>
- 一覧画面
<% @hoges.each do |hoge| %> <tr> <td><%= hoge.id %></td> <td><%= image_tag(hoge.authenticated_image_url(:thumb)) if hoge.image.present? %></td> <td><%= link_to 'show', hoge %></td> <td><%= link_to 'edit', edit_hoge_path(hoge) %></td> <td><%= link_to 'destroy', hoge, method: :delete, data: { confirm: 'are you sure?' } %></td> </tr> <% end %>
- 部分テンプレート
<%= @hoge.name %> <%= image_tag(@hoge.authenticated_image_url(:large)) if @hoge.image.present? %>
Routes
hogesへのrouteを追加します。
resources :hoges
S3
Bucketを作る
AWSのManagementConsole上でS3のBucketを作ります。


接続情報
config/s3.ymlとして、S3への接続情報を記述します
bucket: 'bucket_name' access_key_id: '12345678' secret_access_key: '12345678901234567890' s3_host_name: 's3-ap-northeast-1.amazonaws.com' # tokyoリージョンの場合
これで準備は完了です。
実行
Railsアプリを動かして /hoges/ へアクセスして、"new hoge"リンクを押下して画像データを追加します。
CreateしたらAWSのコンソール上でS3のBucketの中身を確認します。

所感
- paperclip便利
- めちゃ便利です
- S3、ImageMagickとの連携が非常に手軽に行えます
- 静的コンテンツをWebサーバに置かなくていいのはラク
- 静的コンテンツをWebサーバ内に置かなくていいのは非常に良いですね
- S3ならそのままCloudFrontでCDN対応もできます
- Webサーバをスケールアウトしても静的コンテンツはそのままで良いし、運用もラクです
- 画像ファイルのURL生成をRails側で処理することについては...
- 一方、画像URL生成をRailsで処理させることになるので、ここはフラグメントキャッシュを使って、キャッシュした方が良い部分かもしれません
- CarrierWave
- 対抗馬と目される、Carrierwaveも試してみようと思います
参考
TravisCIを使ってHerokuへ自動デプロイする
前回のエントリで RubotyをHerokuにデプロイして、Slackで動かす についてまとめました。
今回はGitHubのリポジトリを変更した際に、TravisCIを使ってGitHubに変更があったときに自動でHerokuへデプロイされるようにしたいと思います。この手のお話は結構エントリがあるので、今更感もありますが、自分的に整理しておきたいので技忘録。

前提・準備
デプロイするアプリケーション
- ruboty-templateをForkしたリポジトリをデプロイ対象とします
- TravisCIへのSign Upは完了しているものとします
TravisCIの設定
該当リポジトリをTravisCIの実行対象にする

travis gem のインストール
$ gem install travis
.travis.ymlの作成
- ローカルにCloneしたリポジトリルートへ移動して、以下のコマンドを実行します
$ travis init
Shell completion not installed. Would you like to like to install it now? |y| y
Detected repository as kentana20/ruboty-template, is this correct? |yes| yes
Main programming language used: |Ruby| Ruby
.travis.yml file created!
これで、 .travis.yml が作成されます。Credentialのエラーが出た場合は travis login —auto コマンドを実行して認証をしてから再度実行してみてください。travis report を実行するとエラーの詳細が確認できます。
.travis.ymlにHerokuの設定を追加する
$ travis setup heroku
Heroku application name: |ruboty-template| $heroku_app_name
Deploy only from kentana20/ruboty-template? |yes| yes
Encrypt API key? |yes| yes
Heroku application nameはHeroku上のアプリケーション名を入力します。これで、 .travis.yml にHerokuの設定が追加されます。出来上がったファイルは以下。
language: ruby rvm: - 2.0.0 deploy: provider: heroku api_key: secure:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX app: $heroku_app_name on: repo: kentana20/ruboty-template
設定ファイルができたらGitHubへpushしておきます。
$ git push origin master
これでTravisCIの設定は完了です。
動作確認
Pull RequestでTravisCI
- まずはmasterブランチから別のブランチへ作業ブランチを切り替えて、何らかの作業をします。今回は単なる
bundle updateでGemfile.lockを更新してPull Requestを作成します
$ bundle update
$ git add .
$ git commit -m “gem update”
$ git push origin f/bundle_update
GitHubでPull Requestを作成すると、以下のようにコミットしたリビジョンに対してTravisCIが走り、実行結果をGitHub上で見ることができます。

これで、master merge前にCIを走らせて、masterに毒が仕込まれるのを防ぐことができます。また、このときのCIジョブはPull Request起因なので、Herokuへのデプロイは行っていません。

master mergeでTravisCI
- 先ほどのPull Requestをmergeすると、もう1度TravisCIが動いて、今度はHerokuへのデプロイも実行されます。

これで、前回のエントリで紹介したHerokuへのデプロイ git push heroku master をすることなく、masterにmergeするだけで、Herokuへのデプロイが完了します。便利ですね〜。
所感
- Travis Gem超便利
- TravisCIはちょっと遅い
- TravisCIは変更を検知してからの実行が気持ち遅いかな、と感じます。無料版なので贅沢は言えないですが、もう少しサクサク動くことを期待したいです。
- master mergeからの自動デプロイはラク
- 会社でも少しは自動化していますが、やはりmaster mergeをフックにデプロイができると良いですね。デプロイ運用に関わっているメンバーの心理的負担も減らせますし、お決まりの作業はどんどん自動化するべきだと強く感じます。
- コミットリビジョン毎にTravisの実行結果が見られるのも良い
- リビジョン単位でCIを走らせることで「このPull RequestはCIが通っている」と一目でわかるので、安心してmergeができてこれも心理的負担を軽くできます。メンバーの人数やCIジョブのボリュームによっては「CI待ち」みたいな状態を作りかねないので注意が必要ですが、スピードアップを目指せば良いわけで、これも会社のスタンダードにしていきたいです。
- ブランチ毎にデプロイ先を分けて運用なども
- 参考にしたOnishiさんのエントリにもありましたが、devブランチはStaging環境へ、masterブランチはProduction環境へデプロイする、みたいな設定を作っておけば、どちらもデプロイを自動化できて非常にラクにデプロイ運用を行う事ができてTravis便利だと思いました。
- CircleCIはどうなんだろう
- 最近はCircleCIの方が流行りっぽいので、今後はCircleCIも試してみたいと思いました。
- ForkしたリポジトリはPull Requestの送り先に注意
- 完全に本題とは外れますが、個人的な反省の意味を込めて。今回のデプロイ対象リポジトリはid:r7kamuraさんのリポジトリをForkしてるんで、Pull Requestの送り先を間違えるとFork元に飛んでしまいます。今回は実際にPull Requestを飛ばしてしまって「間違ってるよ」と指摘をいただいてしまいました。。反省です。

これで自動デプロイの設定までできたので、もうちょいためになる挙動を足していこうと思います。個人的にBrowserStackが気になっているので、次回は「Rubotyに話しかけてBrowserStackの結果をSlack上で確認する」をお届けしようと思います。
参考
RubotyをHerokuにデプロイしてSlackで使う
人気のチャットアプリであるSlackと id:r7kamura さんが作ったRuby製Hubotクローンである Ruboty を試してみたかったので、技忘録。

最近チームメンバーに編成があって、「まゆゆ」というニックネームの仲間が少しの間、旅に出ることになり、とっても寂しいので寂しさを紛らわすために「まゆゆbot」を作ってみることにします。
前提
RubotyはHerokuにデプロイしますので、Herokuアカウントの作成やHeroku Toolbeltのインストールは済ませてあることを前提とします。
Slack側の設定
Gatewayの設定

Botアカウントを作る
- AdministrationのManage Your Teamを開き、Invite New Membersから適当なメールアドレスを入力してInvitationメールを送信します

- Invationメールが届いたら、Activationして適当に画像を設定しておきます
Ruboty
ruboty-templateをForkする
Rubotyをカスタマイズしますので、r7kamuraさんの ruboty-templateをForkします。
ForkしたRubotyをHeroku上にデプロイする
ForkしたリポジトリのREADMEにあるHerokuボタンを押して、RubotyをHerokuにデプロイします。
デプロイ画面で以下の内容を入力してDeploy for Freeをクリックします
- SLACK_PASSWORD
- SLACK_ROOM
- botを常駐させたいRoom Nameを入力
- SLACK_TEAM
- Team Name(xxx.slack.com のxxx部分)
- SLACK_USERNAME
- botアカウントの名前(いまはmayubot)

これで、HerokuにRubotyがデプロイされます。

動作確認
Rubotyがデプロイできたら、Slack上で話しかけてみます。

う、う、うごいた〜!! ここまで、コードは1行も書いてません。オドロキ。
RubotyはHubotとは異なり、Rubyで動作しており、その挙動はすべてGemfileによってGemプラグインとして管理されています。そのため、Botに新しい役割を任せたい・挙動を増やしたい場合には新しくGemを作って公開する必要があります。
Hubotの場合は script ディレクトリ内に .coffee ファイルを置いてBotを再起動すれば挙動が追加できたので、手軽さという意味ではHubotの方が良いかもしれませんが
- Rubyで挙動が書ける
- Gemで管理できる(配布のラクさ)
といった点ではRubotyにも良い点はありますね。
プラグインを追加する
GemをGistで作成する
さて、無事SlackとRubotyが連携できたので、自作のプラグインを作って新しい挙動を追加してみます。「まゆゆ」はおさわり探偵小沢里奈のなめこが大好きだったので、「@mayubot なめこ」と唱えると、「んふんふ」と返すプラグインを作ってみます。Gemで公開する必要があるので、GistでGemを公開したいと思います。
この2つをGistで作成します。こんな感じです。
Gemfileに追加
続いて、ローカルにCloneしたリポジトリのGemfileに以下を追記して、 bundle install します。
source "https://rubygems.org" gem "rake" gem "ruboty-alias" gem "ruboty-cron" gem "ruboty-google_image" gem "ruboty-redis" gem "ruboty-slack" gem "nfu", :git => "https://gist.github.com/e0f5147312c921ba6ee0.git” # これを追記
Gemがインストールできたら、herokuへpushしてデプロイします。
$ git add .
$ git commit -m “add nfu gem”
$ git push heroku master
なめこを唱えてみる
デプロイが完了したら、いよいよSlack上で @mayubot へ向けて呪文を発動。

動いた〜!!!!!カンタンにプラグインを追加できました。
所感
- Rubyでプラグインが作れるのがGood
- HubotはNode.jsでCoffeeScriptでプラグインを書くのですが、Rubyで書けるのはうれしいですね
- Gemで管理できるのも良い
- プラグインをGemで管理するのはエレガントというか、スマートなので賛否あると思うけど個人的には好き
- GistでGemを公開するのはすばらし
- これもr7kamuraさんのエントリを参考にしました。すばらし。普通Gemを公開するなら、
bundle gemして、gem buildしてgem pushするみたいなことが必要なんですが、省略できます。
- これもr7kamuraさんのエントリを参考にしました。すばらし。普通Gemを公開するなら、
- Herokuボタンすごい
- Herokuボタン、すごいです。超カンタンにアプリをHeroku上にデプロイできます。
- Ruboty-Template
- こいつをForkすれば自分が好きにいじれるのでTravisCIを組み合わせればmaster mergeを契機にデプロイを走らせるみたいなこともできてGoodです
- Botを擬人化するのがオモロイ
おまけ(Herokuの設定)
HerokuアプリをローカルにCloneして、デプロイする
SSH公開鍵の登録
$ heroku keys:add
で公開鍵を登録します。鍵がなければ ssh-keygen コマンドを使って鍵ペアを作成して再度上記コマンドを実行します。
HerokuアプリをCloneする
$ heroku git:clone -a $heroku_project_name
でHerokuアプリをCloneできます。
Herokuアプリをデプロイする
$ git push heroku master
でHerokuへローカルのリポジトリをpushしてデプロイできます。
次回は「RubotyサーバとGitHubを連携して自動デプロイする」をお送りします(仮)。
参考
Railsアプリのバックグラウンド処理をWheneverを使ってCron化する
Railsアプリの話。というかWheneverの話。
Railsアプリケーションを実装していて、「この処理はオンラインではなくて、バックグラウンドでバッチ処理にして、Cronで定期実行したいな」っていうとき、ありますよね。そんな時は whenever というgemを使えば、カンタンにバッチ処理をCron化できます。
Wheneverを使ってバックグラウンド処理をCron化する
Wheneverのインストール
まずはBundlerを使って、Wheneverをインストールします。
# Gemfile gem 'whenever', :require => false
Gemfileに書いたら、bundle install します。
$ bundle install
インストールできたら、一応 gem list でwheneverがインストールできているかを確認します。
$ gem list
インストールされているgemにwheneverが追加されていればOKです。
Wheneverで動かすバックグラウンド処理を作る
続いて、Wheneverで動かすバックグラウンド処理(バッチプログラム的なもの)を作っていきます。Railsのお作法的には、バックグラウンド処理は
- /lib/tasks/
配下に書くのが正しいカタチのようなので、お作法に従ってバックグラウンド処理を書いていきます。
# /lib/tasks/test.rb class Test def self.hoge puts "hoge" end end
続いて、 /lib/ 配下をロードするように /config/application.rb に以下を追加します。
# /config/application.rb module xxx # railsアプリ名 class Application < Rails::Application config.autoload_paths += %W(#{config.root}/lib) config.autoload_paths += Dir["#{config.root}/lib/**/"] end end
ここまで出来たら、以下のコマンドを実行してバックグラウンドで動作する処理が正しく動くことを確認します。
$ rails runner Test.hoge # 'hoge'と返ってくればOK
Wheneverを使ってCronジョブを設定する
以下のコマンドを入力して、wheneverを動かすためのひな形(schedule.rb)を作成します。
$ wheneverize . # /config/schedule.rb が作成されます
作成した schedule.rb にCronで動作させたい処理を記述します。記述方法についてはこちらを参照くださいませ。
# /config/schedule.rb set :environment, :development set :output, {:error => 'log/error.log', :standard => 'log/cron.log'} # Testなので、3分毎に動作するように設定します every 3.minute do # cronのコマンドライン上で動くので、二重引用符で囲っておきます runner "Test.hoge" end
schedule.rb の内容をCronへ登録します。
$ bundle exec whenever --update-cron
これで、crontabにジョブが登録されます。登録内容を確認したければ、
$ bundle exec whenever
とか
$ crontab -e
とかで確認可能です。
登録したジョブを削除(Cron設定の解除)をしたい場合は
$ bundle exec whenever --clear-cron
とすれば削除できます。いや〜、カンタンですね。
所感
今回、初めてWheneverを触りましたが
- バックグラウンドに移したい処理が既にある
- Cronの設定方法をある程度理解している
という状況であれば、学習コストは殆どナシでCron化できて便利です。また、Railsアプリの一部(レポジトリを1つ)としてCron設定を管理できるので
など、イロイロな面でメリットがあるな、と感じました。
また、Capistranoとかと連携すればアプリのデプロイ時に
/lib/tasks/配下/config/schedule.rb
に変更があった場合は bundle exec whenever --update-cron する、みたいなことを deploy.rb に書いておいてCron設定まで自働化しておけば、「一部のノードだけCron設定を更新し忘れた」みたいなこともなくなるハズなので、いい感じでアプリを管理できそうです。
Railsの便利さ(Wheneverの便利さ?)を改めて感じた3連休最終日でした。
OS XでRedisをインストールしてRuby on Railsで使う(gem 'redis')
連休中にRuby on Railsを使ってWebアプリを作っていて、Redisにデータ登録するとこまでやったので備忘録のために書いておきます。
Redisは高速で動作するインメモリ型のKVSでありながら、非同期でディスクに書き込みを行い、データを永続化してくれるありがたい代物です。
公式サイトは英語ですが、こちらにドキュメントの日本語訳があります。

日本語ドキュメントがあるので、Redisについての詳細な説明は省きますが、以下のような特徴があります。
Redisの特徴
- インメモリ型
- インメモリ型で動作するため、非常に高速
- データ永続化
- 非同期でディスクへ書き込むため、データを永続化できる
- データ構造
- リスト型、セット型、ハッシュ型といった多彩なデータ構造をサポートしている
- 豊富な言語サポート(ライブラリ)
- レプリケーション
- master/slaveレプリケーションが設定ファイル1つで非常に容易に設定できる
MacOS X で Ruby on Rails + Redisなアプリを動かす
Redisのインストール
まずはHomebrewでRedisをインストールします。
brew install redis
インストールが完了したら、redis-serverコマンドでバージョンを確認します。
redis-server --version
redis.conf編集
インストールが終了したら、/usr/local/etc/redis.conf を編集します。これは、デフォルトの状態だと /usr/local/var/ 直下にdbファイルなどが作られてしまうための対応です。
/usr/local/var/ を /usr/local/var/redis/ に変更
Redisを起動し、redis-cliで動作確認
confファイルを変更したら、Redisを起動し、redis-cliというコマンドラインで使えるRedisのクライアントルールを起動し、動作確認を行います。
redis-server /usr/local/etc/redis.conf
"set $key $value"でデータ登録、"get $key"でデータ取得ができます。カンタンですね〜。
redis-cli 127.0.0.1:6379> set key val 127.0.0.1:6379> get key ' valが返却
Gemのインストール
redis-cliでの動作確認が完了したら、'redis'というgemをインストールして、RailsアプリケーションのGemfileに'redis'を追加します。
- gem 'redis' のインストール
gem install 'redis'
- Gemfile
gem 'redis'
Redis接続情報を作成
/config/inisitalizers/redis.rb を作成し、Redisの接続情報を設定します。
require 'redis' Redis.current = Redis.new(:host => '127.0.0.1', :port => 6379)
Redisへのデータ登録
Redisでキャッシュしたいデータについて、登録処理を記述します。(ここでは、コントローラに書いています)
class TestController < ApplicationController require 'redis' def test Redis.current.set("hoge", "fuga") end end
登録したデータをredis-cliで確認
redis-cli 127.0.0.1:6379> get "hoge" ' "fuga"が返却
ホントはlib/配下にクラスかモジュールを作って動かすか、app/model/配下にモデルクラスを作ってActiveRecordと連携して使うのが実用的っぽいんですが、今回はRedisを素のままでつかってみたかったので、こういうカタチにしました。
所感
Redisに対する知識は殆どゼロの状態でスタートして、半日程度でWebアプリに組み込むことができたので、学習コストはそれほど高くないという印象です。
ただし、実運用を行う場合は
といった面で、しっかり設計しなければいけないと感じました。 とは言え、キャッシュサーバとして利用するには良いKVSなのでは、と思います。
このアプリのソースはGitHubに上げてあるので、「使い方が違うんじゃ、、」とか「こういう利用方法もある」とか、ご指摘・コメントをいただけたら、とても嬉しいです。
そのうち、EC2上で公開しようと思っていますので、今後をお楽しみに〜!