LoginSignup
7
7

More than 3 years have passed since last update.

Rails carrierwave について

Last updated at Posted at 2019-06-17

はじめに

gemのnested_formを使ってますが、最新コミットが2013年で止まっているので、最近はcocoonを使ってます。
使い方はnested_formに似ています。

参考リンク↓
https://github.com/nathanvda/cocoon

gemの追加

carrierwaveはバージョンが合わないとエラーが結構出て苦しめられたので、githubに合わせるようにした。

gem 'carrierwave', '>= 2.0.0.rc', '< 3.0'
gem 'mini_magick'

今回は S3に画像を保存するためにgem 'fog'を追加します。

gem 'fog'
$ bundle

画像が1枚だけの時の例(rails tutorialから引用)

アップローダーの作成

$ rails generate uploader Picture

画像を保存するカラムをつくる

$ rails generate migration add_picture_to_microposts picture:string
$ rails db:migrate

モデルのカラムとアップローダーを紐付けます。

models/micropost.rb
class Micropost < ApplicationRecord
  mount_uploader :picture, PictureUploader
end

viewに反映させる

まずはフォーム

<%= form_for(@micropost) do |f| %>
  <%= f.file_field :picture %>
<% end %>

次にモデル(ストロングパラメーターにカラムを追加)

private

  def micropost_params
    params.require(:micropost).permit(:content, :picture)
  end

ビューに表示する

<%= image_tag micropost.picture.url if micropost.picture? %>

rails tutorialの内容なのでざっくりと書いてます。次が複数枚登録の場合です。

画像が複数枚ある時の例

アップローダーの作成

まず、Pictureという名前のアップローダーを作成します。

$rails generate uploader Picture

今回はBlogモデルにアップローダーのPictureを紐付けます。
Blogに複数画像を所持してもらいたいため、BlogとPictureを直接紐付けるのでなく、
Imageモデルを通して紐付けます。
Imageにタイトルも欲しかったのでつけました。
*Blogモデルは前もって作成してます。

$rails g model Image title:string picture:string blog:references
マイグレーションファイル
class CreateImages < ActiveRecord::Migration[5.2]
  def change
    create_table :images do |t|
      t.string :title
      t.string :picture
      t.references :blog, foreign_key: true

      t.timestamps
    end
  end
end

このようなマイグレーションファイルができると思います。

$rails db:migrate

Imageモデルを以下のように変更します。

models/image.rb
class Image < ApplicationRecord
  belongs_to :blog
  mount_uploader :picture, PictureUploader #Pictureアップローダーとimageモデルの:pictureを紐付けてます。
end
models/blog.rb
class Blog < ApplicationRecord
  validates :title, presence: true, length: { minimum: 2, maximum: 50 }
  validates :body, presence: true, length: { minimum: 5, maxmum: 500 }
  validates :user_id, presence: true
  belongs_to :user
#------------ここから上は関係ないです。-------------------------
  has_many :image, dependent: :destroy #この一行を追加する。dependent: :deleteはblogが消えたらimageも一緒に消えるように
end

1つのフォームで多数のモデルのデーターを送信する

BlogモデルとImageモデルを同じフォームで作成したいため、gem "nested_form"を追加します。
詳しくは以下サイトを見てください。

fields_forの上手な使い方-Qiita
Rails フォーム(form_for,nested_form,fields_for) による複数同時投稿(親子、親子孫、他人)-Qiita
ryanb / nested_form-Github

この3つを読めばだいたいわかるはず。

gem "nested_form"
$bundle

gem jquery-railsの追加

"ensted_form"を使うのにJqueryがいるみたいなので追加します。

gem 'jquery-rails'
$bundle

以下の3つを追加、たしか//= require rails-ujsの上に追加したほうが良かったような気がします。

assets/javascripts/application.js
//= require jquery3
//= require popper
//= require bootstrap-sprockets

モデル、コントローラーの実装

Blogモデルにメソッドを追加します。

models/blog.rb
class Blog < ApplicationRecord
  has_many :images, dependent: :destroy
  accepts_nested_attributes_for :images, allow_destroy: true #追加した行
end

nested_formを追加したら、accepts_nested_attributes_forが使えるようになっています。
allow_destroy: trueでBlogモデルのデータを削除した紐付いたimageも消えるそうです。

controllers/blogs_controller.rb

class BlogsController < ApplicationController
  before_action :set_blog, only: %i[show edit update destroy]
  skip_before_action :authenticate_user!, only: %i[index show]

  def index
    @blogs = Blog.all
  end

  def show
    @blog = Blog.find(params[:id])
    @user = @blog.user
    @images = @blog.images
  end

  def new
    @blog = Blog.new
    @image = @blog.images.build # 1 この行を追加
  end

  def create
    @blog = Blog.new(blog_params)
    @blog.user_id = current_user.id
    if @blog.save
      flash[:success] = "#{@blog.title} を作成しました。"
      redirect_to @blog
    else
      render :new
    end
  end

  def edit
    @image = @blog.images.build # 2 この行を追加
  end

  def update
    if @blog.update(blog_params)
      flash[:success] = "#{@blog.title} を編集しました。"
      redirect_to @blog
    else
      render :edit
    end
  end

  def destroy
    @blog.destroy
    flash[:success] = "#{@blog.title} を削除しました。"
    redirect_to blogs_path
  end

  private
#---------------------追加分---------------------------------------------------
  def blog_params
    params.require(:blog).permit(:title, :body, images_attributes: [:id, :title, :picture, :_destroy])
  end
#---------------------追加分---------------------------------------------------

  def set_blog
    @blog = Blog.find(params[:id])
  end
end

自分がつくったコントローラーを全部貼り付けていますが、実際追加した部分は#1, 2と追加分と書かれているストロングパラメーターの中身だけです。
create,update,destroyは普通のCRUD処理でいじってません。また、Imageのコントローラーも全く触りません。
#1,2はフォームの処理で@imagesがほしいので作ってます。
追加分と書いてある部分はaccepts_nested_attributes_for :images, allow_destroy: trueとモデルに追加した時に、images_attributesというメソッドが出来ているみたいです。
このへんは3つの参考資料を読んでください。

次にフォーム(パーシャルでnewとedit共通のフォームです)

blogs/_form.html.erb

<%= nested_form_for(@blog) do |f| %> # 1変更部分です。
  <%= render 'shared/error', object: @blog %>
  <div class='form-group'>
    <%= f.label :title, class: 'label-title' %>
    <%= f.text_field :title, class: 'form-control', placeholder: 'タイトル', autofocus: true %>
  </div>

  <div class='form-group'>
    <%= f.label :body, class: 'label-body' %>
    <%= f.text_area :body, class: 'form-control', rows: 10, placeholder: '記事の内容' %>
  </div>

#--------------------------追加部分---------------------------------------------
  <%= f.fields_for :images, :html => { multipart: true } do |f| %> # 2 追加部分
    <div class='form-group'>
      <%= f.label :title, class: 'label-title' %>
      <%= f.text_field :title, class: 'form-control', placeholder: 'タイトル' %>
    </div>
    <div class='form-group'>
      <%= f.label :picture, class: 'label-picture' %>
      <%= f.file_field :picture, class: 'form-control' %>
    </div>
    <%= f.link_to_remove "写真リンクの削除", class: "btn btn-outline-success mb-3" %>
  <% end %>
  <%= f.link_to_add "画像の追加", :images, class: "btn btn-outline-success mb-3" %>
#--------------------------追加部分---------------------------------------------

  <div class='form-group'>
    <%= f.submit nil, class: 'btn btn-outline-success'%>
  </div>
<% end %>

"ensted_form"については本題でないため、説明を省きますが、# 2 追加部分の:html => { multipart: true }で複数の画像が保存できるようになっています。
適当にshowページも作ってみたので貼って置きます。

views/blogs/show.html.erb

<h1><%= @blog.title %></h1>
<p><%= link_to @user.name, user_path(@user) %></p>
<p><%= @blog.body %></p>

<% @images.each do |image| %>
  <p><%= image.title %></p>
  <p><%= image_tag image.picture.url if image.picture? %></p>
<% end %>
<%= link_to "編集", edit_blog_path(@blog), class: "btn btn-outline-success" %>
<%= link_to "削除", blog_path(@blog), 
                   method: :delete, 
                   class: "btn btn-outline-success", 
                   data: { confirm: "#{@blog.title} を削除します。よろしいですか?"} %>

画像のリサイズ MiniMagick

はじめに追加したminimagickを使います。
主なメソッドは

・resize_to_fit
・resize_to_limit
・resize_to_fill

このサイトのパクリです。↓
CarrierWave+MiniMagickで使う、画像リサイズのメソッド-Qiita

minimagickをローカルで使うにはimagemagickをインストール必要があるみたいです。macを使っているのであれば、Homebrewなどでインストールしてください。ってrails tutorialに書いてありました。
rails tutorial-13.4.3-画像のリサイズ

views/blogs/show.html.erb
 <p><%= image_tag image.picture.url, size: "100x100" if image.picture? %></p>

に書けば、画像のサイズをビューを表示する前に変更してくれるのですが、おそらく、毎回もとの画像からサイズを変更することになるので、無駄に時間がかかってしまいます。
あと、タテとヨコの比率の無視されてました。
なので、画像が保存されるタイミングでリサイズをしたいと思います。

uploaders/picture_uploader.rb

class PictureUploader < CarrierWave::Uploader::Base
  # Include RMagick or MiniMagick support:
  # include CarrierWave::RMagick
  include CarrierWave::MiniMagick # 1 コメントを外す
#画像保存時に800x700より大きい画像はこのサイズでリサイズされます。
  process resize_to_fit: [800, 700]

#バージョン指定することで、別のサイズでリサイズすることが出来ます。
  version :thumb do
    process resize_to_fill: [300, 300, "Center"]
  end

  #以下省略してます↓↓↓

end

ビューにバージョンを適応させるには

views/blogs/show.html.erb
<p><%= image_tag image.picture.thumb.url if image.picture? %></p>

画像に適応させたいバージョンを付け加えるだけです。
注意が1つありましてバージョンを後から付け加えた場合、もともと保存されていた画像には適応されません。
適応したい場合は以下のサイトを参考にしてください。ちゃんとメソッドが用意されているみたいです。↓

carrierwave で新しいリサイズのバージョンを既存の画像ファイルに適用させる(Rails)-Qiita

バリデーションについて

rails tutorialのパクリです。そっちを見てください。↓

rails tutorial-13.4.2-画像の検証

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7