概要
みなさんこんにちはcandleです。比較的難しいrailsからs3に画像をアップロードする方法を試してみましょう。
一般にサーバ環境にはスケーラビリティ(アクセスの規模に応じて、動的にサーバを動かしたり止めたりする事)を持たせる為に、ファイル関連はEC2上に置くのではなく、s3というストレージサービスを使用します。もしも、個々のEC2上にファイルを置いてしまうと、複数EC2サーバがある場合他のEC2にアクセスすることができません。
概念図的には下の様になります。
railsの場合、s3にファイルを上げるgemは2つあります。1つが最もポピュラーなaws-sdkです。これはs3に限らず様々なaws環境の処理をrailsから扱えるライブラリです。もう1つがs3特化したaws-s3というがあります。
当初私はaws-s3を使っていたのですが、試行錯誤した結果、aws-sdkを使っています。この記事もaws-sdkを使って説明していきます。
ちなみに、図ではEC2からアップロードしていますが、記事中で開発している場所は家のローカル環境のrailsです。
前提
railsの環境が構築されている
awsのアカウントを持っていて割と自由に使える
今回もtwitterbootstrap gemとscaffoldを使って説明します。別段、bootstrap gemが入っている必要がありませんが、見栄えが多少異なります。
rails4のtwitterbootstrap gem でscaffoldを作る方法は下のURLにあるので、もし、同じように進行したい場合は下の記事を参考にしてください。
https://joppot.s3.amazonaws.com/2014/05/27/1455
aws-gemを用意する
適当なプロジェクトを作成して、そのGemfileに下の内容を書き込みましょう。
私はbootstrap3というプロジェクトを使います。
gem 'aws-sdk'
gemをインストールする為に下のコマンドを入力しましょう。
bundle install
恐らく、古いOSを使っているとnokogiri関連のgemが一緒にインストールされる際に、いろいろ表示されますが、ほっとけば勝手にインストールされます。
gemの準備が完了しました。
s3imageというscaffoldを作る
scaffoldを使って説明していきたいと思います。
scaffoldの詳しいことは別の記事に書いてあるので、ここではコマンドだけを紹介します。
下のコマンドでs3imageというscaffoldを作成します。
rails g scaffold S3image title:string file_name:string comment:string
作成されたマイグレーションファイルをデータベースに反映させます。
bundle exec rake db:migrate
scaffoldが完成しました。
twitterbootstrapのテーマを反映させる
もしも、twitterbootstrap gemが入っているなら下のコマンドでテーマを反映させます。
rails generate bootstrap:install static rails g bootstrap:layout application rails g bootstrap:themed S3images
s3にファイルをアップするためのバケットを作る
railsのセットアップも終わったのでs3にバケットを用意しましょう。
awsのs3のサイトに飛んで、
s3のサービスから「s3contents」というバケットをつくります。
作成できました。
s3contentsのバケットを選択してその中にimagesというフォルダを作ります。
これでs3の準備はできました。
s3へアクセスるためのaccess_keyとsecret_keyを取得する
ここから、やや厄介になります。
s3へファイルアップロードしたり、消したりするのにはもちろん、権限が必要です。誰も彼もs3にファイルをアップロードできては危険だからです。
aws-sdk経由でs3にファイル上げる場合もaccess_keyとsecret_keyは必要不可欠です。それぞれのキーを取得しましょう。
awsのメニューの自分のアカウント名が書かれているプルダウンメニューから「Security Credentials」を選びます。
左のメニューから「Users」を選択します。
「Create New Users」を選択します。
そしたら、名前は何でも良いのですが、例えば、「s3upload」というユーザー名にします。別にs3uploadという名前である必要性はありません。書いたら、Createを押します。
access_keyとsecret_keyが表示されるので、コピペで保存するかダウンロードしましょう。ちなみにこれは後で確認ができなかったと思います。必ず保存しましょう。
今度はこのs3uploadユーザーに権限をもたせます。
ユーザーを選択して「Attach User Policy」を選択します。
「Select Policy Template」から「Amazon S3 Full Access」を選択して「Select」を押します。
次の画面に移動するので、なにも変えずに「Apply Policy」を選択します。
これで、先ほど入手したaccess_keyとsecret_keyを使ってs3にファイルをアップロードできる権限を持つことができました。
s3imageのviewを変更する
では実際にコーディングに移って行きます。
railsサーバを起動して、下のパスに移動すると、
http://localhost:3000/s3images/new
図の様なフォームが現れます。
ここにはファイルをアップロードする項目がないのでそれを加えましょう。
下のパスのファイルを開きます。
app/views/s3images/_form.html.erb
下の記述はテキストフィールドになっているので、これをファイルがアップロードできる様に編集します。
<div class="form-group"> <%= f.label :file_name, :class => 'control-label' %> <div class="controls"> <%= f.text_field :file_name, :class => 'form-control' %> </div> </div>
下の様にします。
<div class="form-group"> <%= f.label :file, :class => 'control-label' %> <div class="controls"> <%= f.file_field :file %> </div> </div>
:file_nameの箇所を:fileに変更しているのは:file_nameは画像の名前をデータベースに入れる項目であり、フォームで書くわけではないからです。:fileは仮のハッシュ名です。もちろん:fileというのはテーブルのカラムには存在しません。この辺のややこしさはform_forになれれば解決します。
もう一度サイトを見るとファイルフィールドになっています。
モデルを変更する
先ほど、フォームで:fileというs3imagesテーブルのカラムとは関係のないものを指定しました。form_forはテーブルのカラムと対応したフォームです。なので、テーブルには無いけれど、一応受け付ける為にモデルに下の記述を書きます。
attr_accessor :file
下の様な感じです。
モデルの設定は完了です。
コントローラーを編集する
app/controllers/s3images_controller.rbを開いて、
クラスの中に上部に下のセッティングソースを書きます。
AWS.config(access_key_id: 'あなたのアクセスキー', secret_access_key: 'あなたのシークレットー', region: 'ap-northeast-1')
ここで、先ほど入手した、アクセスキーとシークレットキーを入力しましょう。
したのような感じになると思います。
regionを見ると、「ap-northeast-1」にしています。これはs3をどこに置いているかをさしています。それぞれ、確認してそれを適切に設定してください。
ちなみに、確認の仕方は、s3のサイトに言った時にURLに表示されます。
フォームからの値を許可する
s3imagesコントローラーが現在許可しているパラメーターは3つですが、
def s3image_params params.require(:s3image).permit(:title, :file_name, :comment) end
上で編集したように:file_nameは:fileにしました。なので、:file_nameは削除して、代わりに:fileを受け入れるようにしましょう。
def s3image_params params.require(:s3image).permit(:title, :file, :comment) end
createアクションを編集する
createアクションをしたのように編集します。
def create @s3image = S3image.new(s3image_params) s3 = AWS::S3.new bucket = s3.buckets['s3contents'] file = s3image_params[:file] file_name = file.original_filename file_full_path="images/"+file_name object = bucket.objects[file_full_path] object.write(file ,:acl => :public_read) @s3image.file_name="http://s3-ap-northeast-1.amazonaws.com/s3contents/images/#{file_name}" respond_to do |format| if @s3image.save format.html { redirect_to @s3image, notice: 'S3image was successfully created.' } format.json { render action: 'show', status: :created, location: @s3image } else format.html { render action: 'new' } format.json { render json: @s3image.errors, status: :unprocessable_entity } end end end
詳しく説明します
@s3image = S3image.new(s3image_params) s3 = AWS::S3.new bucket = s3.buckets['s3contents']
1行目はS3imgeの空のオブジェクトを作成しています。ここにフォームの値を収めたりして、データベースに保存します。
2行目はaws-sdkのAWSのS3クラスからs3インスタンスを作成しています。
3行目はs3インスタンスからbucketというオブジェクトを作成します。この時、バッケットの名前を指定することで、任意のバッケットを対象としたオブジェクトを作れます。
今回の場合ですと先ほど作成した「s3contents」というバッケットですね。
file = s3image_params[:file] file_name = file.original_filename file_full_path="images/"+file_name
S3imagesコントローラーの中のprivate関数の中にあるs3image_params関数を使ってフォームから送られてきた:fileの画像の全ての値をfile変数に収めます。
file.original_filenameを使ってファイル名だけをfile_nameに収めます。
三行目のfile_full_pathとはs3のs3contentsバケットから見て内部のどこにフィアルを置くのかのフルパスを記述します。例えば、s3contentsバケットのimagesフォルダの中にf.pngファイルを置く場合はfile_full_pathは”images/f.png”になります。
object = bucket.objects[file_full_path] object.write(file ,:acl => :public_read) @s3image.file_name="http://s3-ap-northeast-1.amazonaws.com/s3contents/images/#{file_name}"
bucketのobjects関数を使ってobjectというオブジェクトを作ります。
作成したobjectのwrite関数を使ってファイルをs3に保存します。この時、第二引数で:acl=>:public_readとする事で、第三者も見れる様になります。この引数がない場合は公開されません。
最後に@s3imageのfile_nameにs3にアップロードしたファイルのパスを収めます。
これで完成です。
実際にアップロードしてみる
下のURLに移動して
http://localhost:3000/s3images/new
必要事項を埋めて、
Create S3imageを押すとファイルが無事アップロードされました。
うまくいきましたね。
まとめ
手続き的な所が多いですね。とくにS3にアクセススためのアクセスキーやシークレットキーはs3のサービスではなくてIAMというサービスです。このようにawsのサービスをまたい使う場合は知らないとできないことが多いので、頑張ってください。