Basic image upload

我們會使用 CarrierWave 處理圖片上傳,並把圖片和 Micropost model 關聯起來。所以要在 Gemfile 增加 carrierwave gem;此外,也加上了 mini_magick 和 fog gem,前者用來處理調整圖片尺寸,後者是處理在 production 環境中上傳圖片:

Gemfile

source 'https://rubygems.org'

gem 'rails',                   '4.2.2'
gem 'bcrypt',                  '3.1.7'
gem 'faker',                   '1.4.2'
gem 'carrierwave',             '0.10.0'
gem 'mini_magick',             '3.8.0'
gem 'fog',                     '1.36.0'
gem 'will_paginate',           '3.0.7'
gem 'bootstrap-will_paginate', '0.0.10'
.
.
.

然後執行 bundle:

$ bundle install

CarrierWave 自帶了一個 Rails 產生器,用於建立圖片上傳程序(image uploader),我們要建立一個名為 picture 的上傳程序:

$ rails generate uploader Picture

CarrierWave 上傳的圖片應該對應於 Active Record model 中的一個屬性,這個屬性只要儲存圖片名稱的字串即可,增加這個屬性的 Micropost data model 如下:

為了增加這個屬性,我們就要建立一個遷移,然後在 development database 中執行遷移:

$ rails generate migration add_picture_to_microposts picture:string
$ bundle exec rake db:migrate

我們要使用 mount_uploader 方法告訴 CarrierWave 把圖片和 model 關聯起來。第一個參數是屬性的 symbol 形式,第二個參數是上傳程序的 class name:

mount_uploader :picture, PictureUploader

PictureUploader class 是在 picture_uploader,rb 文件裡,之後才會編輯到,目前使用預設內容即可。

接著把上傳程序加進 Micropost model 裡:

app/models/micropost.rb

class Micropost < ActiveRecord::Base
  belongs_to :user
  default_scope -> { order(created_at: :desc) }
  mount_uploader :picture, PictureUploader
  validates :user_id, presence: true
  validates :content, presence: true, length: { maximum: 140 }
end

在某些系統中,可能必須重啟 Rails server,這樣測試才能通過。(如果使用 Guard,也必須重啟)

然後要在 microposts 表單加上 file_field tag:

app/views/shared/_micropost_form.html.erb

<%= form_for(@micropost, html: { multipart: true }) do |f| %>
  <%= render 'shared/error_messages', object: f.object %>
  <div class="field">
    <%= f.text_area :content, placeholder: "Compose new micropost..." %>
  </div>
  <%= f.submit "Post", class: "btn btn-primary" %>
  <span class="picture">
    <%= f.file_field :picture %>
  </span>
<% end %>

注意 form_for 指定了下面這個參數:

html: { multipart: true }

為了支持檔案上傳功能,必須指定這個參數。

最後,我們必須把 picture 增加到可以透過 web 修改的屬性列表中,也就是要修改 micropost_params 方法:

app/controllers/microposts_controller.rb

class MicropostsController < ApplicationController
  before_action :logged_in_user, only: [:create, :destroy]
  before_action :correct_user,   only: :destroy
  .
  .
  .
  private

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

     def correct_user
      @micropost = current_user.microposts.find_by(id: params[:id])
      redirect_to root_url if @micropost.nil?
    end
end

圖片上傳之後,我們可以使用 image_tag 輔助方法來渲染圖片:

app/views/microposts/_micropost.html.erb

<li id="micropost-<%= micropost.id %>">
  <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
  <span class="user"><%= link_to micropost.user.name, micropost.user %></span>
  <span class="content">
    <%= micropost.content %>
    <%= image_tag micropost.picture.url if micropost.picture? %>
  </span>
  <span class="timestamp">
    Posted <%= time_ago_in_words(micropost.created_at) %> ago.
    <% if current_user?(micropost.user) %>
      <%= link_to "delete", micropost, method: :delete,
                                       data: { confirm: "You sure?" } %>
    <% end %>
  </span>
</li>

注意這邊使用了 picture? 布林值方法,如果沒有圖片就不顯示 image_tag。這個方法由 CarrierWave 自訂建立,名稱由儲存圖片檔案名的屬性而定。

自己手動上傳圖片後的畫面如下:

針對圖片上傳的測試留做練習題。