こんにちは!鳥貴士です!

今回はRails5.2から追加されたActiveStorage機能を使ってファイルのアップロード、そしてダウンロードリンクの実装をします!

具体的にはpdfファイルのアップロード、ダウンロードを行います。

イメージとしてはユーザーがたくさんのpdfモデルを持っている、そのpdfモデルには1つのpdfファイルが割り当てられる感じです。

pdf以外でもできますが、今回作ったものがpdf絡みのものだったのでこんな例になってます。

この例でのファイルの保存先はlocalですが、AWSやGCPなどを使うことも可能です。

セットアップ

まず最初にRailsにActiveStorageのためのテーブルを作成します。

$ rails active_storage:install
$ bundle exec rake db:migrate

を実行し、activestorage_blobとactivestorage_attachmentテーブルが作成されていることを確認します。
マイグレーションに関しては飛ばします。
マイグレーションは見なくてもActiveStorageはわかります。

 モデルの設定

テーブルが作成出来たら個々のモデルの設定に移りましょう。

ユーザー

まずユーザーはたくさんのpdfを持っている仕様でしたね。
ですからユーザーモデルは以下のような関係性を保持しています。
これはActiveStorageとは関係のない普通のコードです。
ログインとかパスワードとかは関係ないので省略。

class User < ApplicationRecord
  has_many :pdfs
end

pdf

pdfモデルには一つのpdfファイルが紐づけられています。
そしてpdfの所有者すなわちアップロード者はただ一人です。
ですからpdfモデルは以下のような関係性を保持しています。

ただなぜoptional: trueを指定したのかは忘れてしまった。
確かアップロード周りでこけていたはず。

class Pdf < ApplicationRecord
  belongs_to :user, optional: true
  has_one_attached :pdf_file
end

ルーティングの設定(抜粋)

見ればわかる

get  "upload", to: "pdf_upload#new"
post "upload", to: "pdf_upload#create"

resources :uploaded_pdfs, only: [:index] do
end

コントローラの設定

諸々のページについては省略して、アップロードとダウンロードに関係するコントローラのみ取り上げます。
まずはアップロードから。

class PdfUploadController < ApplicationController
  def new
    @pdf = Pdf.new
  end

  def create
    @pdf = Pdf.new(upload_params)
    if @pdf.save
      redirect_to uploaded_pdfs_path
    end
  end

  private def upload_params
    params.require(:pdf).permit(:user_id, :name, :details, :pdf_file)
  end
end

という感じ。
newメソッドは説明いらないでしょ。
createメソッドでupload_paramsを通ってきたデータをデータベースに保存し、完了したら自分がアップロードしたものをみられるページに飛びます。

アップロードしたものがみられるページでは、アップロードしたファイルのダウンロードが可能です。
ダウンロード側のコントローラはこのようになっています。

class UploadedPdfsController < ApplicationController
  def index
    @pdfs = @current_user.pdfs
  end
end

です。説明ほぼいらないですよね。
Userモデルに定義されたhas_many :pdfsの関係ということぐらいです。

ビューの設定

View周りは結構苦手なんですよね。
form_withのオプションわけわからん、書き方何通りあるの。
あとerbなのでタグが<%…%>とか<%=…%>なのも慣れない。
かといってangularみたいになるのもインポートしたりまわりが面倒だし…

まぁ使っているうちに慣れるのはわかってますけどね。

Haml使えっていうのもわかります、言われたことないけど。
言ってることはわかり哲也ですがやる気がでません。

さぁ文句はここまでにしてコードを見ましょう。

アップロードページ

余計なコードは消したのですっきりしています。

<%= form_with model: @pdf, url: upload_path do |f| %>
  <div>
    <%= f.label "name" %>
    <%= f.text_field :name %>
  </div>
  <div>
    <%= f.label "details" %>
    <%= f.text_area :details %>
  </div>
  <div>
    <%= f.label "select" %>
    <%= f.file_field :pdf %>
  </div>
  <%= f.hidden_field :user_id, value: @current_user.id %>
  <%= f.submit "send" %>
<% end %>

form_withでフォームを生成しています。

file_fieldでファイルを指定、hidden_fieldでuser_idをくっつけてsubmitします。

form_withに何も指定しないとpostとして送信されるので、フォームで生成されたデータはPdfUploadController#createに投げられます。

ダウンロードページ

@pdfsはログイン中のユーザーがアップロードしたpdfの配列を返します。
そのためイテレータで処理します。

<% @pdfs.each do |pdf|| %>
  <%= link_to pdf.name, rails_blob_path(pdf.pdf_file) %>
  <%= pdf.details %>
<% end %>

という具合です。

リンクを生成するためにlink_toを使い、どこからファイルを持ってくるのかはrails_blob_pathメソッドに対してattachしているファイルを指定してあげればよいです。

これでファイルのアップロード、そしてダウンロードリンクの生成ができるようになりましたね!

それではまた!