Rails でのファイルのアップロードとダウンロードを試してみます。 調べると「CarrierWave」や「ActiveStorage」を使う記事を見かけることが多いです。 今回は「CarrierWave」と「ActiveStorage」を使ってみます。
目次
参考
そもそも gem を使わないと実装できないのか? 一応 gem を使わないで実装できないのか確認したので、参考に記載します。
モデル作成 1 2 rails generate scaffold uploadfile name:string data:binary rails db:migrate
ルーティングの編集 config/routes.rb を編集して、ファイルダウンロード用のルーティングを追加。 アップロードしたファイルを編集ということもないでしょうから、:edit と:update を resources から除きます。
config/routes.rb 1 2 3 4 Rails .application.routes.draw do get 'uploadfiles/download/:id' ,to: "uploadfiles#download" ,as: "download" resources :uploadfiles ,only: [:index ,:show ,:create ,:destroy ] end
以上を設定して``rails routes`を実行すると、ルーティングを確認できる。
1 2 3 4 5 6 Prefix Verb URI Pattern Controller#Action uploadfiles GET /uploadfiles(.:format) uploadfiles#index POST /uploadfiles(.:format) uploadfiles#create new_uploadfile GET /uploadfiles/new(.:format) uploadfiles#new uploadfile GET /uploadfiles/:id(.:format) uploadfiles#show DELETE /uploadfiles/:id(.:format) uploadfiles#destroy
コントローラの編集 create アクションで、入力された名称とアップロードされたファイルの拡張子をつないで、新しいファイル名を作成。 アップロードされたファイルのデータを格納してデータベースに保存します。
app/controllers/uploadfiles_controller.rb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class UploadfilesController < ApplicationController before_action :set_uploadfile , only: [:show , :edit , :update , :destroy , :download ] def create file={} file[:name ]= "#{uploadfile_params[:name ]} .#{uploadfile_params[:data ].original_filename.split('.' )[1 ]} " file[:data ]=uploadfile_params[:data ].read @uploadfile =Uploadfile .new(file) respond_to do |format | if @uploadfile .save format.html { redirect_to @uploadfile , notice: 'Uploadfile was successfully created.' } format.json { render :show , status: :created , location: @uploadfile } else format.html { render :new } format.json { render json: @uploadfile .errors, status: :unprocessable_entity } end end end def download send_data(@uploadfile .data,:filename=> @uploadfile .name) end end
ビューの編集 app/views/uploadfiles/index.html.erb app/views/uploadfiles/index.html.erb はルーティングから取り除いた edit の削除、uploadfile.data
の表示を削除します。
app/views/uploadfiles/index.html.erb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <p id ="notice" > <%= notice %></p > <h1 > Uploadfiles</h1 > <table > <thead > <tr > <th > Name</th > <th colspan ="2" > </th > </tr > </thead > <tbody > <% @uploadfiles .each do |uploadfile | %> <tr > <td > <%= uploadfile.name %></td > <td > <%= link_to 'Show' , uploadfile %></td > <td > <%= link_to 'Destroy' , uploadfile, method: :delete , data: { confirm: 'Are you sure?' } %></td > </tr > <% end %> </tbody > </table > <br > <%= link_to 'New Uploadfile' , new_uploadfile_path %>
app/views/uploadfiles/show.html.erb app/views/uploadfiles/show.html.erb は、<%= link_to 'download', download_path(@uploadfile.id) %>
を追加します。 uploadfiles_controller.rb の download で処理されるように、download_path(@uploadfile.id)
の様に記述します。
app/views/uploadfiles/show.html.erb 1 2 3 4 5 6 7 8 9 10 11 12 13 <p id ="notice" > <%= notice %></p > <p > <strong > Name:</strong > <%= @uploadfile .name %> </p > <p > <strong > Data:</strong > <%= link_to 'download' , download_path(@uploadfile .id) %> </p > <%= link_to 'Back' , uploadfiles_path %>
app/views/uploadfiles/_form.html.erb app/views/uploadfiles/_form.html.erb を編集して、以下のようにします。 ファイルアップロードの口としてのフォームには、.file_field を使用します。
app/views/uploadfiles/_form.html.erb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <%= form_with(model: uploadfile, local: true ) do |form | %> <% if uploadfile.errors.any? %> <div id ="error_explanation" > <h2 > <%= pluralize(uploadfile.errors.count, "error" ) %> prohibited this uploadfile from being saved:</h2 > <ul > <% uploadfile.errors.full_messages.each do |message | %> <li > <%= message %></li > <% end %> </ul > </div > <% end %> <div class ="field" > <%= form.label :name %> <%= form.text_field :name %> </div > <div class ="field" > <%= form.label :data %> <%= form.file_field :data %> </div > <div class ="actions" > <%= form.submit %> </div > <% end %>
動作確認 rails s
で起動して localhost:3000/uploadfiles にアクセスします。 以下のようになるはずです。
「New Uploadfile」を開くと、以下のように名前の入力とファイル選択ができるようになります。 「Create Uploadfile」でアップロードします。
「download」をクリックすることでアップロードしたファイルをダウンロードできます。
ファイルのアップロード、ダウンロードができました。
CarrierWave でやってみる 導入 Gemfile に以下を追記する。
Gemfileに追記 1 gem 'carrierwave', '~> 2.0'
bundle install
を実行。
モデル作成 ファイルを取り扱うモデルを account として作成します。 以下のコマンドを実行。
1 2 rails generate scaffold account name:string icon:string rails db:migrate
アップローダーの作成 以下コマンドの実行により、app/uploaders/icon_uploader.rb が作成されます。 app/uploaders/icon_uploader.rb には、IconUploader が定義されています。
1 rails generate uploader icon
アップローダーとモデルの関連付け app/models/account.rb に、IconUploader を関連付けします。
app/models/account.rb 1 2 3 class Account < ApplicationRecord mount_uploader :icon , IconUploader end
ルーティングの編集 後から作る download メソッドを実行するためのルーティングを追加します。
1 2 3 4 5 6 7 Rails .application.routes.draw do get 'accounts/download/:id' ,to: "accounts#download" ,as: "download_icon" resources :accounts end
コントローラーの編集 dounload メソッドを追加。 set_account メソッドが download を呼び出したときにも実行されるようにします。
1 2 3 4 5 6 7 8 9 10 11 12 class AccountsController < ApplicationController before_action :set_account, only: [:show, :edit, :update, :destroy,:download] # 省略 def download send_data(@account.icon.read,:filename=>@account.icon_identifier) end # 省略 end
ビューの編集 app/views/accounts/_form.html.erb は、<%= form.text_field :icon %>
を<%= form.file_field :icon %>
に書き換えます。
app/views/accounts/_form.html.erb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <%= form_with(model: account, local: true ) do |form | %> <% if account.errors.any? %> <div id ="error_explanation" > <h2 > <%= pluralize(account.errors.count, "error" ) %> prohibited this account from being saved:</h2 > <ul > <% account.errors.full_messages.each do |message | %> <li > <%= message %></li > <% end %> </ul > </div > <% end %> <div class ="field" > <%= form.label :name %> <%= form.text_field :name %> </div > <div class ="field" > <%= form.label :icon %> <%= form.file_field :icon %> </div > <div class ="actions" > <%= form.submit %> </div > <% end %>
app/views/accounts/show.html.erb は<%= @account.icon %>
を<%= image_tag @account.icon.url %>
に書き換えます。<%= link_to 'ダウンロード',download_icon_path(@account) %>
を追加します。
app/views/accounts/show.html.erb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <p id ="notice" > <%= notice %></p > <p > <strong > Name:</strong > <%= @account .name %> </p > <p > <strong > Icon:</strong > <%= image_tag @account .icon.url %> <%= link_to 'ダウンロード' ,download_icon_path(@account ) %> </p > <%= link_to 'Edit' , edit_account_path(@account ) %> | <%= link_to 'Back' , accounts_path %>
app/views/accounts/index.html.erb は、<%= account.icon %>
を<%= account.icon_identifier %>
に書き換えます。
app/views/accounts/index.html.erb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <p id ="notice" > <%= notice %></p > <h1 > Accounts</h1 > <table > <thead > <tr > <th > Name</th > <th > Icon</th > <th colspan ="3" > </th > </tr > </thead > <tbody > <% @accounts .each do |account | %> <tr > <td > <%= account.name %></td > <td > <%= account.icon_identifier %></td > <td > <%= link_to 'Show' , account %></td > <td > <%= link_to 'Edit' , edit_account_path(account) %></td > <td > <%= link_to 'Destroy' , account, method: :delete , data: { confirm: 'Are you sure?' } %></td > </tr > <% end %> </tbody > </table > <br > <%= link_to 'New Account' , new_account_path %>
動作確認 rails s で起動して localhost:3000/accounts にアクセスします。 以下のようになるはずです。
画像を読み込んで、「Create Account」クリックします。
読み込んだ画像が表示されます。
アップロードしたファイルはどこにあるのか? アップロードしたファイルは、public/uploads/account/icon/[account の id]の中に保管されていました。 app/uploaders/icon_uploader.rb の中に以下の記述があります。
1 2 3 4 5 6 7 8 9 10 11 class IconUploader < CarrierWave::Uploader::Base storage :file def store_dir "uploads/#{model.class .to_s.underscore} /#{mounted_as} /#{model.id} " end
どうやら、app/uploaders/icon_uploader.rb の記述通りの場所にファイルがあるようです。
試しに"uploads/#{model.class.to_s.underscore}_#{mounted_as}_#{model.id}"
と書き換えしました。 public/uploads/accounticon [account の id]以下にファイルが作成されていました。
CarrierWave を使用してファイルのアップロードとダウンロードができました。 CarrierWave を資料を見ながら触りましたが、一瞬でファイルアップローダができるので思わず「すげー」と声が出ました。 CarrierWave を使わずに作ったときと比べて、create 時のコントローラの記述が不要ですからね。びっくりします。
CarrierWave でやってみる。(AWS S3 へアップロード) CarrierWave はクラウドへのアップロードを備えているので、試してみます。 ここまで作った者を AWS S3 ように書き換えてみます。
導入 Gemfile に以下を追記する。
Gemfileに追記
bundle install
を実行。
carrierwave 設定変更 config/initializers/carrierwave.rb を作成して、以下を記述します。
config/initializers/carrierwave.rb 1 2 3 4 5 6 7 8 9 10 11 12 CarrierWave .configure do |config | config.fog_credentials = { provider: 'AWS' , aws_access_key_id: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' , aws_secret_access_key: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' , region: '[リージョン]' , } config.fog_directory = '[バケット名]' config.fog_public = false config.fog_provider = 'fog/aws' end
app/uploaders/icon_uploader.rb の記述を変更します。 以下のようにstorage :file
をstorage :fog
に書き換えます。
app/uploaders/icon_uploader.rb 1 2 3 4 5 6 7 class IconUploader < CarrierWave::Uploader::Base storage :fog end
本来は、開発なのか本番なのかで条件分けするようですが、今回は直接記述とします。
動作確認 rails s で起動して localhost:3000/accounts にアクセスし、再度ファイルをアップロードしてみます。 AWS マネジメントコンソールで S3 を開くと、アップロードしたファイルが確認できました。 確かに AWS にアップされています。
ダウンロードすることも問題ありません。 表示しているページを開発者ツールで開いて画像の URL を確認すると以下のようになっていました。
1 https://[バケット名].[リージョン名].amazonaws.com/uploads/account/icon/[accountのid]/[ファイル名]?X-Amz-Expires=600&X-Amz-Date=20191118T155402Z&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA5W531FYFMAMCKVF2%2F20191119%2Fap-northeast-1%2Fs1%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Signature=1faa1bc52488c57f41c670801066887bd48d088a06b415db18a0bbf2d5dbccc0
長ーいですね。 試しにこの URL から、ファイル名の後ろのクエリ部分を除くとエラーになりました。 また、URL を保存しておいて大体 10 分後くらいに、直接ブラウザへ入力したら以下のエラーを表示しました。
発行されている URL(とクエリ)は時間限定であることを確認できました。
ActiveStorage でやってみる 導入 以下コマンドを実行。
1 2 rails active_storage:install rails db:migrate
/config/database.yml を編集し、以下を追記。
1 2 3 local: service: Disk root: <%= Rails.root.join("storage") %>
モデル作成 ファイルを取り扱うモデル名を customer として作成します。 以下コマンドを実行します。
1 2 rails generate scaffold customer name:string filename:string rails db:migrate
ActiveStrage とモデルの関連付け app/models/customer.rb を編集します。has_one_attached :photo
を記述します。
app/models/customer.rb 1 2 3 class Customer < ApplicationRecord has_one_attached :photo end
ルーティングの編集 後から作る download メソッドを実行するためのルーティングを追加します。
1 2 3 4 5 6 7 Rails .application.routes.draw do get 'customers/download/:id' ,to: "customers#download" ,as: "download_photo" resources :customers end
コントローラーの編集 ActiveStrage では自動で、アップロードしたファイルの名称を補完してくれないようです。 以下のように自前で実装しました。 取得したファイルの名称を filename に保管します。
app/controllers/customers_controller.rb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 class CustomersController < ApplicationController before_action :set_customer , only: [:show , :edit , :update , :destroy ,:download ] def create @customer = Customer .new(customer_params) @customer .filename=customer_params[:photo ].original_filename respond_to do |format | if @customer .save format.html { redirect_to @customer , notice: 'Customer was successfully created.' } format.json { render :show , status: :created , location: @customer } else format.html { render :new } format.json { render json: @customer .errors, status: :unprocessable_entity } end end end def update params={ :name=>customer_params [:name ], :filename=>customer_params [:photo ].original_filename, :photo=>customer_params [:photo ], } respond_to do |format | if @customer .update(params) format.html { redirect_to @customer , notice: 'Customer was successfully updated.' } format.json { render :show , status: :ok , location: @customer } else format.html { render :edit } format.json { render json: @customer .errors, status: :unprocessable_entity } end end end def download send_data @customer .photo.download,:filename =>@customer .filename end private def customer_params params.require (:customer ).permit(:name ,:photo ) end end
ビューの編集 app/views/customers/_form.html.erb は、filename カラムを利用者に編集させないため、編集項目から削除します。 またいつもと同様に、画像の読み込みとため.file_field を使用します。 変更したものが以下の通りです。
app/views/customers/_form.html.erb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <%= form_with(model: customer, local: true ) do |form | %> <% if customer.errors.any? %> <div id ="error_explanation" > <h2 > <%= pluralize(customer.errors.count, "error" ) %> prohibited this customer from being saved:</h2 > <ul > <% customer.errors.full_messages.each do |message | %> <li > <%= message %></li > <% end %> </ul > </div > <% end %> <div class ="field" > <%= form.label :name %> <%= form.text_field :name %> </div > <div class ="field" > <%= form.label :photo %> <%= form.file_field :photo %> </div > <div class ="actions" > <%= form.submit %> </div > <% end %>
app/views/customers/show.html.erb は、画像とダウンロードリンクが表示されるように変更します。
app/views/customers/show.html.erb 1 2 3 4 5 6 7 8 9 10 11 <p id ="notice" > <%= notice %></p > <p > <strong > Name:</strong > <%= @customer .name %> <%= image_tag @customer .photo %> <%= link_to "ダウンロード" ,download_photo_path(@customer ) %> </p > <%= link_to 'Edit' , edit_customer_path(@customer ) %> | <%= link_to 'Back' , customers_path %>
app/views/customers/index.html.erb には特に変更事項ありません。
app/views/customers/index.html.erb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <p id ="notice" > <%= notice %></p > <h1 > Customers</h1 > <table > <thead > <tr > <th > Name</th > <th > Filename</th > <th colspan ="3" > </th > </tr > </thead > <tbody > <% @customers .each do |customer | %> <tr > <td > <%= customer.name %></td > <td > <%= customer.filename %></td > <td > <%= link_to 'Show' , customer %></td > <td > <%= link_to 'Edit' , edit_customer_path(customer) %></td > <td > <%= link_to 'Destroy' , customer, method: :delete , data: { confirm: 'Are you sure?' } %></td > </tr > <% end %> </tbody > </table > <br > <%= link_to 'New Customer' , new_customer_path %>
動作確認 rails s で起動して localhost:3000/customers にアクセスします。
これまで同様にファイルのアップロードとダウンロードができました。
アップロードしたファイルはどこにあるのか? /storage 以下に 2 文字のディレクトリが 2 段階分作成され、ハッシュ化されたファイル名で保存されていました。 これだと、どのディレクトリにあるのが、どのモデルの画像なのかわからなさそうです。 修正方法は判然としませんでした。
今回は、Rails でファイルアップロードを「CarrierWave」や「ActiveStorage」でやってみました。 CarrierWave を使ったとき、README に従い操作しただけでアップローダーができたとき、思わず声が出てしまいました。 ActiveStorage も簡単ではあるもののファイル名が変更されたりしてしまうので、DB に直接 blob のカラムで保管するのと比べて優位性が見いだせていません。 このあたり理解が誤っているのであれば、改めたいところです。
ではでは。