• AWS Summit Tokyo 2014 ~「あなた」のクラウドがここに ~

[Ruby on Rails]画像をアップロード・ダウンロードするコントローラの呼び出しとRSpec

rails

はじめに

画像をアップロード・ダウンロードする処理をRuby on Railsで作成する際、画面がないなどの理由で、コンソールから画像をアップロードする事や、ControllerのRSpecにて動作を確認することがあります。
今回はそれらの具体的なソースと、簡単なRailsの実装について書いてみたいと思います。

1.今回作成するアプリについて

先に書いたように、画像をアップロード・ダウンロードする処理をRuby on Railsにて実装します。アップロードした画像はアプリサーバ内の/public/imgフォルダ内に保存します。ダウンロード時にはURLパラメータにてファイル名を指定し、/public/imgフォルダ内よりファイルを取得してクライアントに送る仕組みです。(ファイル名を直に指定するのは、サンプルなので簡略化するためです。)

2.ルーティング

アップロード処理はarticles/uploadをpostで呼び出し、ダウンロード処理はarticles/downloadをgetで呼び出すようにしました。これらを定義したroutes.rbは以下のようになります。

/config/routes.rb

Rails.application.routes.draw do
  post 'articles/upload'
  get 'articles/download'

3.アップロード

Rails

では、アップロード処理についてです。まずはRailsのソースについてです。

/app/controllers/articles_controller.rb

class ArticlesController < ApplicationController

  def upload
    file = params[:img]
    name = file.original_filename

    File.open("public/img/#{name}", 'wb') { |f|
      f.write(file.read)
    }

    render nothing: true, status: 200
  end
(以下略)

アップロード処理は「upload」というアクション名で作成しました。4行目で「img」パラメータとしてPOSTされてきた画像を取得し、8行目で読み込んでいます。同時にローカルのファイルとして書き出すことで、ローカルフォルダ内に画像を保存しています。

コンソールからの画像のアップロード

上記のコントローラの動きを確認するため、実際に画像をアップロードするためには、curlを使用します。コンソールを開き、以下のコマンドを実行することで、上記のコントローラを呼び出すことができました。

$ curl -X POST -F img=@spec/fixtures/img/IMG_0192.JPG http://localhost:3000/articles/upload

curlコマンドの引数に「POST」を指定することで、文字通りPOST送信を行います。また「-F」を指定することで、送信するフィールドと値を指定します。値に「@」をつけることで、multipart/form-dataで送信されます。

RSpec

上記のコントローラを呼び出すRSpecには、以下のようになります。

/spec/controllers/articles_controller_spec.rb

require 'rails_helper'
require "fileutils"

RSpec.describe ArticlesController, :type => :controller do

  IMG_FILE_NAME = 'IMG_0192.JPG'
  img_file_path = 'img/' + IMG_FILE_NAME
  upload_file_path = 'public/img/' + IMG_FILE_NAME

  describe "POST 'upload'" do


    before(:all) do
      File.unlink(upload_file_path) if File.exist?(upload_file_path)
    end

    it "succeed file upload." do
      post 'upload', :img => fixture_file_upload(img_file_path, 'image/jpg')
      expect(response).to be_success
      is_upload = File.exist?(upload_file_path)
      expect(is_upload).to be(true)
    end
  end

ポイントは18行目の「fixture_file_upload」で、これを使いアップロードするファイルパスを指定し、画像をアップロードしています。今回はコントローラを呼び出した後、ローカルのフォルダ内にファイルが保存されたかを20、21行目でテストしました。

4.ダウンロード

Rails

次にダウンロード処理についてです。まずはRailsのソースについてです。

/app/controllers/articles_controller.rb

class ArticlesController < ApplicationController
  (中略)
  def download
    send_data(
        File.read("public/img/#{params[:filename]}"),
        type: 'application/octet-stream',
        filename: params[:filename]
    )
  end
end

クラウアントに画像を送信するため、4行目で「send_data」メソッドを呼び出しています。引数には指定されたファイル名、ヘッダーのタイプ、送信するファイル名を指定しています。

RSpec

このコントローラのRSpecは以下の通りです。尚、ダウンロード処理はGETリクエストであるため、実際の操作確認はブラウザから行うことが出来ます。

/spec/controllers/articles_controller_spec.rb

  describe "GET 'download'" do
    before(:all) do
      FileUtils.copy(Rails.root.to_s + '/spec/fixtures/' + img_file_path, Rails.root.to_s + '/' + upload_file_path) if !File.exist?(upload_file_path)
    end

    it "succeed file download." do
      get 'download', filename: IMG_FILE_NAME
      expect(response).to be_success
      expect(response.headers["Content-Disposition"]).to eq("attachment; filename=\"#{IMG_FILE_NAME}\"")
      expect(response.content_type).to eq("application/octet-stream")
    end
  end
 

何をもってファイルがダウンロードされるのかをテストするのかは難しい問題である部分もありますが、今回は送信されるレスポンスの種類、ファイル名を9、10行目でテストしました。

まとめ

普段はブラウザから送信する画像を指定して動作を確認するなどしていましたが、APIを作成する場合など、画面がないケースもあるかと思います。APIとして公開するインターフェースにもよるかと思いますが、ブラウザを使用しないで画像のアップロード・ダウンロードを行う際の助けにでもなれば幸いです。

  • AWS Summit Tokyo 2014 ~「あなた」のクラウドがここに ~