RailsとCarrierWaveを使ったファイルアップロードの実装ガイド
本記事は「Railsでのアップロード」シリーズの1つです。今回は、Rails向けファイルアップロードソリューションとして最も人気のあるCarrierWaveを紹介します。CarrierWaveは導入が非常に簡単で、標準で豊富な機能を備えている点が魅力です。さらにコミュニティによって多数のノウハウ記事が公開されているため、迷うことなく開発を進められます。
この記事では、以下の内容を学ぶことができます。
- RailsアプリへのCarrierWaveの組み込み
- バリデーションの追加
- リクエスト間でのファイル保持
- ファイルの削除
- サムネイル画像の生成
- リモートURLからのファイルアップロード
- 複数ファイルアップロードへの対応
- クラウドストレージの利用
本記事のソースコードはGitHubで公開しています。それでは始めましょう!
基盤となるアプリケーションの作成
まずは、いつものように新しいRailsアプリケーションを作成します。
rails new UploadingWithCarrierwave -T
このデモではRails 5.0.2を使用します。なお、CarrierWave 1系はRails 4以降とRuby 2以降にのみ対応している点に注意してください。まだRails 3を使っている場合は、CarrierWave 0.11をインストールする必要があります。
CarrierWaveの動作を確認するため、Postモデルだけを持つシンプルなブログアプリケーションを作成します。主な属性は以下の通りです。
title(string)body(text)image(string)—投稿に添付される画像(正確にはファイル名)を格納するフィールドです
マイグレーションを生成して適用しましょう。
rails g model Post title:string body:text image:string rails db:migrate
続いてルーティングを設定します。
config/routes.rb
resources :posts root to: 'posts#index'
そして、基本的なコントローラーを作成します。
posts_controller.rb
class PostsController < ApplicationController
before_action :set_post, only: [:show, :edit, :update]
def index
@posts = Post.order('created_at DESC')
end
def show
end
def new
@post = Post.new
end
def create
@post = Post.new(post_params)
if @post.save
redirect_to posts_path
else
render :new
end
end
def edit
end
def update
if @post.update_attributes(post_params)
redirect_to post_path(@post)
else
render :edit
end
end
private
def post_params
params.require(:post).permit(:title, :body, :image)
end
def set_post
@post = Post.find(params[:id])
end
end
次に一覧画面(index)のビューを作成します。
views/posts/index.html.erb
<h1>Posts</h1> <%= link_to 'Add post', new_post_path %> <%= render @posts %>
対応するパーシャルも用意します。
views/posts/_post.html.erb
<h2><%= link_to post.title, post_path(post) %></h2> <p><%= truncate(post.body, length: 150) %></p> <p><%= link_to 'Edit', edit_post_path(post) %></p> <hr>
ここではRailsのtruncateメソッドを使い、本文の先頭150文字のみを表示しています。他のビューとフォームパーシャルを作成する前に、まずはCarrierWaveをアプリに組み込みましょう。
CarrierWaveの組み込み
Gemfileにgemを追加します。
Gemfile
gem 'carrierwave', '~> 1.0'
以下のコマンドを実行します。
bundle install
CarrierWaveの設定は「アップローダー」と呼ばれるファイル内に記述し、それをモデルに組み込みます。アップローダーを生成するには、次のコマンドを使用します。
rails generate uploader Image
app/uploadersディレクトリにimage_uploader.rbというファイルが生成されます。このファイルには便利なコメントやサンプルコードが含まれているので、参考にするとよいでしょう。本デモではActiveRecordを使用しますが、CarrierWaveはMongoid、Sequel、DataMapperにも対応しています。
次に、このアップローダーをモデルにマウント(include)します。
models/post.rb
mount_uploader :image, ImageUploader
アップローダーにはすでに妥当なデフォルト設定が含まれていますが、少なくともアップロードされたファイルの保存先は指定する必要があります。ここではまずファイルストレージを採用しましょう。
uploaders/image_uploader.rb
storage :file
デフォルトでは、ファイルはpublic/uploadsディレクトリに保存されます。そのため、バージョン管理システムから除外しておくのがおすすめです。
.gitignore
public/uploads
アップローダー内のstore_dirメソッドを変更すれば、別の場所に保存することも可能です。
これで準備が整いました。新しいビューとフォームパーシャルを作成して、ファイルのアップロードを開始できるようにしましょう。
views/posts/new.html.erb
<h1>Add post</h1> <%= render 'form', post: @post %>
views/posts/_form.html.erb
<%= form_for post do |f| %>
<div>
<%= f.label :title %>
<%= f.text_field :title %>
</div>
<div>
<%= f.label :body %>
<%= f.text_area :body %>
</div>
<div>
<%= f.label :image %>
<%= f.file_field :image %>
</div>
<%= f.submit %>
<% end %>
image属性はすでに許可済みなので、PostsControllerを変更する必要はありません。
最後に編集画面を作成します。
views/posts/edit.html.erb
<h1>Edit post</h1> <%= render 'form', post: @post %>
これで完了です!サーバーを起動して、画像付きの投稿を作成してみてください。ただし、現状では画像がどこにも表示されないので、次のセクションで詳細ページを追加しましょう。
画像の表示
まだ作成していないビューはshowだけです。追加しましょう。
views/posts/show.html.erb
<%= link_to 'All posts', posts_path %> <h1><%= @post.title %></h1> <%= image_tag(@post.image.url, alt: 'Image') if @post.image? %> <p><%= @post.body %></p> <p><%= link_to 'Edit', edit_post_path(@post) %></p>
ご覧の通り、添付ファイルの表示はとても簡単です。@post.image.urlと書くだけで画像のURLを取得できます。ファイルへのパスが必要な場合はcurrent_pathメソッドを使用します。また、CarrierWaveは添付ファイルの有無を確認するためのimage?メソッドも提供しています(ファイルが存在しなくてもimageメソッド自体は決してnilを返さない点に注意してください)。
これで投稿ページにアクセスすると画像が表示されますが、サイズ制限をかけていないため、画像が大きすぎる可能性があります。CSSで縮小表示することもできますが、アップロード後にサムネイルを生成する方がはるかに効率的です。これにはいくつか追加の手順が必要になります。
サムネイルの生成
画像の切り抜きやリサイズを行うには、専用のツールが必要です。CarrierWaveは標準でRMagickとMiniMagickというgemに対応しており、これらはImageMagickを通じて画像を操作します。ImageMagickは既存画像の編集や新規画像の生成を可能にするオープンソースソフトウェアなので、先に進む前にダウンロードしてインストールしておきましょう。その後、2つのgemのどちらかを選択します。ここではインストールが簡単でサポートも充実しているMiniMagickを採用します。
Gemfile
gem 'mini_magick'
以下を実行します。
bundle install
次に、MiniMagickをアップローダーに組み込みます。
uploaders/image_uploader.rb
include CarrierWave::MiniMagick
あとは、アップローダーに新しい「バージョン」を定義するだけです。「バージョン」(スタイルとも呼ばれます)の概念は多くのファイルアップロードライブラリで採用されており、元の添付ファイルをもとに、異なるサイズや形式の追加ファイルを生成することを意味します。ここではthumbというバージョンを定義してみましょう。
uploaders/image_uploader.rb
version :thumb do
process resize_to_fill: [350, 350]
end
バージョンは好きな数だけ定義でき、しかも他のバージョンをベースにすることもできます。
uploaders/image_uploader.rb
version :small_thumb, from_version: :thumb do
process resize_to_fill: [20, 20]
end
すでに画像をアップロード済みの場合、それらにはサムネイルが存在しません。しかし心配いりません。Railsコンソールから再生成できます。
rails c
Post.find_each {|post| post.image.recreate_versions!(:thumb) if post.image?}
最後に、元画像へのリンク付きでサムネイルを表示しましょう。
views/posts/show.html.erb
<%= link_to(image_tag(@post.image.thumb.url, alt: 'Image'), @post.image.url, target: '_blank') if @post.image? %>
サーバーを起動して結果を確認してみてください!
バリデーションの追加
現在、アップロード自体は機能していますが、ユーザーの入力に対するバリデーションがまったくありません。これは明らかに問題です。画像のみを扱いたいので、.png、.jpg、.gifの拡張子をホワイトリストとして許可しましょう。
uploaders/image_uploader.rb
def extension_whitelist
%w(jpg jpeg gif png)
end
content_type_whitelistメソッドを定義すれば、コンテンツタイプのチェックも追加できます。
uploaders/image_uploader.rb
def content_type_whitelist
/image\//
end
逆に、実行ファイルなど特定の種類のファイルを拒否したい場合は、content_type_blacklistメソッドでブラックリスト方式を取ることも可能です。
ファイルの種類と拡張子のチェックに加えて、ファイルサイズを1MB未満に制限しましょう。そのために、ActiveModel向けのファイルバリデーションを提供する追加のgemが必要です。
Gemfile
gem 'file_validators'
インストールします。
bundle install
必要なバリデーションを追加します(titleとbodyのチェックも併せて追加しています)。
models/post.rb
validates :title, presence: true, length: {minimum: 2}
validates :body, presence: true
validates :image, file_size: { less_than: 1.megabytes }
次に、CarrierWaveのエラーメッセージに対するI18n翻訳を追加します。
config/locales/en.yml
en:
errors:
messages:
carrierwave_processing_error: "Cannot resize image."
carrierwave_integrity_error: "Not an image."
carrierwave_download_error: "Couldn't download image."
extension_whitelist_error: "You are not allowed to upload %{extension} files, allowed types: %{allowed_types}"
extension_blacklist_error: "You are not allowed to upload %{extension} files, prohibited types: %{prohibited_types}"
現状ではバリデーションエラーがどこにも表示されないので、共有パーシャルを作成しましょう。
views/shared/_errors.html.erb
<% if object.errors.any? %>
<h3>Some errors were found:</h3>
<ul>
<% object.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
<% end %>
このパーシャルをフォーム内で使用します。
views/posts/_form.html.erb
<%= render 'shared/errors', object: post %>
不正なファイルをアップロードして挙動を確認してみてください。正常に動作するはずですが、有効なファイルを選んだ状態でタイトルや本文を空のまま送信すると、チェックに失敗してエラーが表示されます。ところが、このときファイルフィールドがクリアされてしまい、ユーザーはもう一度画像を選び直さなければなりません。これはあまり使い勝手がよくありません。この問題を解決するには、フォームにもう1つフィールドを追加する必要があります。
リクエスト間でのファイル保持
フォーム再表示時のファイル保持は、実はとても簡単に実現できます。隠しフィールド(hidden field)を新しく追加し、コントローラーでその属性を許可するだけです。
views/shared/_form.html.erb
<%= f.label :image %> <%= f.file_field :image %><br> <%= f.hidden_field :image_cache %>
posts_controller.rb
params.require(:post).permit(:title, :body, :image, :image_cache)
これでimage_cacheが自動的に設定され、画像が失われなくなります。加えてサムネイルを表示しておくと、画像が正常に処理されたことをユーザーが視覚的に確認できるので便利です。
views/shared/_form.html.erb
<% if post.image? %>
<%= image_tag post.image.thumb.url %>
<% end %>
画像の削除
レコード編集時に添付ファイルを削除できる機能も、非常によく使われます。CarrierWaveならこの機能も簡単に実装できます。フォームに新しいチェックボックスを追加しましょう。
views/shared/_form.html.erb
<% if post.image? %>
<%= image_tag post.image.thumb.url %>
<div>
<%= label_tag :remove_image do %>
Remove image
<%= f.check_box :remove_image %>
<% end %>
</div>
<% end %>
そしてremove_image属性を許可します。
posts_controller.rb
params.require(:post).permit(:title, :body, :image, :remove_image, :image_cache)
これで完成です!手動で画像を削除したい場合は、remove_image!メソッドを使用します。
@post.remove_image!
リモートURLからのアップロード
CarrierWaveには、URLを指定してリモートのファイルをアップロードできる優れた機能が標準で備わっています。新しいテキストフィールドを追加し、対応する属性を許可するだけで、この機能を利用できます。
views/shared/_form.html.erb
<%= f.text_field :remote_image_url %> <small>Enter URL to an image</small>
posts_controller.rb
params.require(:post).permit(:title, :body, :image, :remove_image, :image_cache, :remote_image_url)
なんと便利な機能でしょう。その他のコード変更は一切不要で、すぐにこの機能を試せます!
複数ファイルアップロードへの対応
投稿に複数の添付ファイルを持たせたい場合を考えてみましょう。現在の構成では不可能ですが、幸いCarrierWaveはこのようなケースにも対応しています。この機能を実装するには、シリアライズ型フィールド(SQLiteの場合)またはJSON型フィールド(PostgresやMySQLの場合)を追加する必要があります。ここでは後者を採用するため、データベースアダプターを変更しましょう。Gemfileからsqlite3 gemを削除し、代わりにpgを追加します。
Gemfile
gem 'pg'
インストールします。
bundle install
データベース設定を以下のように変更します。
config/database.yml
default: &default adapter: postgresql pool: 5 timeout: 5000 development: <<: *default database: upload_carrier_dev username: 'YOUR_USER' password: 'YOUR_PASSWORD' host: localhost
対応するPostgresデータベースを作成したら、マイグレーションを生成して適用します。
rails g migration add_attachments_to_posts attachments:json rails db:migrate
SQLiteを使い続けたい場合は、CarrierWaveの公式ドキュメントに記載の手順に従ってください。
次に、アップローダーをマウントします(複数形になっている点に注目してください!)。
model/post.rb
mount_uploaders :attachments, ImageUploader
ここでは添付ファイルにも同じアップローダーを使用していますが、もちろん異なる設定を持つ新しいアップローダーを生成しても問題ありません。
フォームに複数ファイル選択フィールドを追加します。
views/shared/_form.html.erb
<div>
<%= f.label :attachments %>
<%= f.file_field :attachments, multiple: true %>
</div>
attachmentsフィールドは配列を格納するため、コントローラーでは次のように許可します。
posts_controller.rb
params.require(:post).permit(:title, :body, :image, :remove_image, :image_cache, :remote_image_url, attachments: [])
最後に、投稿の添付ファイルを反復処理して、通常どおり表示できます。
views/shared/show.html.erb
<% if @post.attachments? %>
<ul>
<% @post.attachments.each do |attachment| %>
<li><%= link_to(image_tag(attachment.thumb.url, alt: 'Image'), attachment.url, target: '_blank') %></li>
<% end %>
</ul>
<% end %>
各添付ファイルには、ImageUploaderで設定したとおりサムネイルが自動的に生成されます。素晴らしいですね!
クラウドストレージの利用
ファイルストレージを使い続けることが常に望ましいとは限りません。たとえばHerokuではカスタムファイルを保存できないためです。そこで「CarrierWaveとAmazon S3クラウドストレージを連携させるにはどうすればいいのか?」という疑問が生じます。実は、これも非常に簡単な作業です。CarrierWaveはfog-aws gemに依存してこの機能を実現しています。
Gemfile
gem "fog-aws"
インストールします。
bundle install
CarrierWave用のイニシャライザを作成し、クラウドストレージをグローバルに設定しましょう。
config/initializers/carrierwave.rb
CarrierWave.configure do |config|
config.fog_provider = 'fog/aws'
config.fog_credentials = {
provider: 'AWS',
aws_access_key_id: ENV['S3_KEY'],
aws_secret_access_key: ENV['S3_SECRET'],
region: ENV['S3_REGION'],
}
config.fog_directory = ENV['S3_BUCKET']
end
その他にも利用可能なオプションがあります。詳しくはドキュメントを参照してください。
ここではdotenv-rails gemを使って環境変数を安全に設定していますが、他の方法を選んでも構いません。ただし、S3のキーペアが公開されないよう必ず注意してください。公開されてしまうと、誰でもあなたのバケットに何でもアップロードできてしまいます!
次に、storage :fileの行を以下に置き換えます。
uploaders/image_uploader.rb
storage :fog
S3以外にも、CarrierWaveはGoogle StorageやRackspaceへのアップロードに対応しています。これらのサービスも簡単にセットアップできます。
まとめ
今回は以上です!CarrierWaveの主要な機能をすべて網羅しました。これであなたのプロジェクトでも活用できるはずです。さらに追加のオプションも用意されているので、ぜひドキュメントを一読してみてください。
もし行き詰まったら、遠慮なく質問を投稿してください。また、CarrierWaveのWikiには多くの一般的な疑問に答える役立つノウハウ記事が掲載されているので、覗いてみる価値があります。
最後までお読みいただきありがとうございました。Happy Coding!
-
Vue、Vuex、Railsを使用したフルスタックアプリケーションの構築
スケーラビリティを念頭に置いてフルスタックアプリケーションを構築することは、特に、完全なタイプスクリプトをサポートする最新バージョンのVueおよびVuexを使用して構築する場合、威圧的になる可能性があります。この記事では、不健康な家畜への治療の処方を管理するCRUDアプリケーションを探索することで、APIリクエストとデータベースの相互作用を処理するVuex4.0を使用した状態管理からスケーラブルなフルスタックアプリケーションを構築するために知っておく必要のあるすべてを読者に教えます。バックエンドはRailsで構築され、フロントエンドによる統合のために基本的なCRUDAPIを公開します。 ほと
-
Rails5でのAngularの使用
あなたは前にその話を聞いたことがあります。分散型で完全に機能するバックエンドAPIと、通常のツールセットで作成されたフロントエンドで実行されているアプリケーションがすでにあります。 次に、Angularに移動します。または、AngularをRailsプロジェクトと統合する方法を探しているだけかもしれません。これは、この方法を好むためです。私たちはあなたを責めません。 このようなアプローチを使用すると、両方の世界を活用して、たとえばRailsとAngularのどちらの機能を使用してフォーマットするかを決定できます。 構築するもの 心配する必要はありません。このチュートリアルは、この目的のた