入門シリーズが続いている.
CarrierWave使うサンプル多いので, 使わないで実装してみる.
Railsは5.1.6
サンプルの内容
掲示板的に
- メッセージ
- 画像
のCRUDができるものにする.
とりあえず足場を作る
CRUD実装自体は目的ではないのでScaffoldで.
$ rails g scaffold message message:string $ rails db:migrate
とりあえずメッセージ登録ができるようになる.
画像をDBへ保存するか, ファイル保存してパスを保存するか
一般的にはパス保存といわれている気がするし, 迷ったけど今回はDBへ保存する実装にした.
理由は
- バックアップとリカバリが楽
- アクセス制御も楽
特にアクセス制御についてはパスを保存の場合は, どうやってうまく制御するのかがわからなかった.
publicに入れてパスを保存というのが一般的なのかな?という気もするけど, エンタープライズだと画像もアクセス制御に気を付けないとならない.
UUIDみたいな当てようもないものをファイル名にしてパスを保存したりするのだろうか?
この辺よくわからない.知りたいなぁ.
まぁ, Oracleもポジショントークではあるもののこんな事言ってますし, DB保存もありではないかと.
画像保存用のモデルを追加する
imageモデルを作成し, 上述で作成したmessageモデルと関連させることにする.
$ rails g model image message:references filename:string data:binary $ rails db:migrate
アソシエーションも追加.
# app/models/message.rb class Message < ApplicationRecord has_one :images end
class Image < ApplicationRecord belongs_to :message end
アップロード機能を作る
下ごしらえが済んだので実装する.
view
_form.html.erbにfile_fieldを加えてやる.
1対1でアソシエーション張ったので一応fields_for使った.
<div class="field"> <%= form.label :message %> <%= form.text_field :message, id: :message_message %> </div> <!-- アップロード用に追加 --> <%= form.fields_for(message.image) do |f| %> <div class="field"> <%= f.label :image %> <%= f.file_field :data, id: :image_data %> </div> <% end %>
controller
登録フォームを呼び出すときにイメージモデルのインスタンスを用意しておく.
# GET /messages/new def new @message = Message.new @message.build_image # 追加 end
追加時にファイルを保存するように変更.
まずbuild_imageでImageモデルのインスタンスを用意しておく.
ストロングパラメータも新しく用意した.
で, データを取得してDBへ入れたいのだけど, image_params[:data]で取得できるのはActionDispatch::Http::UploadedFileのインスタンスらしい.
アップロード画像は付加情報も含めインスタンスで表現される.(実際のファイルは仮ファイルで別の場所へ保存)
ファイルのデータはこのインスタンスからreadメソッドで読み込める.
ファイル名はoriginal_filenameメソッドで取得できる.
リファレンス読むとcontent_typeも取得できるので, このサンプルのImageモデルに拡張子の項目を追加しなかったことを若干後悔.
# POST /messages # POST /messages.json def create @message = Message.new(message_params) # ここから @image = @message.build_image @image.data = image_params[:image][:data].read @image.filename = image_params[:image][:data].original_filename # ここまで追加 respond_to do |format| if @message.save format.html { redirect_to @message, notice: 'Message was successfully created.' } format.json { render :show, status: :created, location: @message } else format.html { render :new } format.json { render json: @message.errors, status: :unprocessable_entity } end end end private # Never trust parameters from the scary internet, only allow the white list through. def message_params params.require(:message).permit(:message) end # 追加したパラメータ取得メソッド def image_params params.require(:message).permit(image: [:data]) end
ここまででアップロードは動作した.
若干ごちゃつく.
表示する
せっかくなら表示もさせたいので.
routeの追加
config/routes.rbへアップロードした画像を得るimageというルートを追加する.
resources :messages do member do get 'image' end end
viewの変更
app/views/messages/show.html.erbへ表示用のタグを追加.
<p> <strong>Message:</strong> <%= @message.message %> </p> <%= image_tag(image_message_path(@message), alt: @message.image.filename) %>
controllerの変更
app/controllers/messages_controller.rbへ画像取得のアクションを追加する.
# scaffoldで生成したサンプルなのでbefore_actionにimageも追加してやる before_action :set_message, only: [:show, :edit, :update, :destroy, :image] [中略] # GET /messages/1/image def image send_data @message.image.data, type: 'image/jpeg' end
ダウンロードリンクをつける
ついでに試した.
view
app/views/messages/show.html.erb
<%= image_tag(image_message_path(@message), alt: @message.image.filename) %> <!-- 追加 --> <%= link_to 'download', image_message_path(@message) %>
controller
app/controllers/messages_controller.rbのimageアクションを変更.
ファイル名をURLエンコードしてsend_dataへ渡してやるだけ.
# GET /messages/1/image def image filename = ERB::Util.url_encode(@message.image.filename) send_data @message.image.data, type: 'image/jpeg', filename: filename end