跳至主要内容

[Rails] carrierwave uploader

keywords: file upload

產生 uploader

執行下面其他步驟前,要先建立 uploader

# create  app/uploaders/fieldname_uploader.rb
rails generate uploader Avatar

上傳單一檔案

Generator

rails g migration add_avatar_to_users avatar:string

Model

在 model 中掛入 uploader

# ./app/models/user.rb
class User < ActiveRecord::Base
mount_uploader :avatar, AvatarUploader
end

Controller

# ./app/controllers/users_controller.rb
def user_params
params.require(:user).permit(:name, :avatar)
end

View

在 View 中使用,這時候送出表單,就會自動將檔案上傳到 ./public/uploads/<fieldName> 資料夾中,同時將檔名存入相對應的 table field 中。

<!-- ./app/views/users/_form.html.erb -->
<%= form_with(model: user, local: true) do |form| %>

<div class="field">
<%= form.label :avatar %>
<%= form.file_field :avatar, id: :product_avatar %>
</div>

<div class="actions">
<%= form.submit %>
</div>

<% end %>

利用在 View 中可以透過:

product.avatar              # 取得圖檔相對路徑
product.avatar.url # 取得圖檔相對路徑(和上面一樣)
product.avatar.current_path # 取得圖檔絕對路徑
product.avatar_identifier # 取得檔名(是用底線 _ )

product.avatar.file.nil? # 判斷檔案是否存在

注意:product.avatar 永遠不會回傳 nil,即使沒有和它關連的圖片也一樣。可以透過 product.avatar.file.nil? 來檢驗是否有相關連的圖片。

上傳多個檔案 multiple files upload

Generator

上傳多個檔案時,欄位名稱加上 s ,且欄位格式為 json

rails g migration add_avatars_to_users avatars:json
rails db:migrate

Model

原本的 mount_uploader:avatar 都要變成複數:

# ./app/models/user.rb
class User < ActiveRecord::Base
mount_uploaders :avatars, AvatarUploader
end

Controller

# 這是透過 AJAX 上傳圖檔
# ./app/controllers/users_controller.rb
def update_images
add_more_images(params[:product][:photos]) unless params[:product][:photos].nil?
msg = {
:status => 'get data',
:photos => @product.photos.map(&:url)
}
render :json => msg
end


# controller
# new_images 代入 params 傳入的圖檔,例如 params[:product][:photos]
def add_more_images(new_images)
images = @product.photos
images += new_images
@product.photos = images
@product.save
end

View

file_field 要記得加上 multiple: true

<!-- ./app/views/users/_form.html.erb -->

<%= form_with(model: user, local: true) do |form| %>
<div class="field">
<%= form.label :avatars %>
<%= form.file_field :avatars, id: :user_avatars, multiple: true %>
</div>

<div class="actions">
<%= form.submit %>
</div>
<% end %>

透過 form.file_field 產生的 input tag 會長像下面這樣,其中 name 會是 name="user[avatars][]"

<input id="user_avatars" multiple="multiple" type="file" name="user[avatars][]" />

上傳後 db 的 table 中會以陣列的方式儲存["Chrome.jpeg","html5.png","JS.png"]

在 View 中可以透過下面方式來採用:

<!--  identifier 目前無法在 multiple upload 使用 -->

<%= "url:#{avatar.url}" %>
<%#= "identifier: #{avatar.identifier}" %>
<%= "current_path: #{avatar.current_path}" %>
<%= "file.nil?: #{avatar.file.nil?}" %>

移除上傳的檔案

下面這是可以透過 AJAX 的方式刪除圖檔,其中 @product.write_attribute(:photos, []) if @product.photos.empty? 這行是修復 CarrierWave 已經的 Issue,如果沒有這行,將無法刪除最後一張圖片:

# controller
def delete_images
remove_images(params[:remove_photos_url]) unless params[:remove_photos_url].nil?
msg = {
:status => 'delete data',
:photos => @product.photos.map(&:url)
}
render :json => msg
end

def remove_images(photos_url)
remain_images = @product.photos # copy the array
remain_images = remain_images.reject do |image|
photos_url == image.url
end
@product.photos = remain_images # re-assign back
@product.write_attribute(:photos, []) if @product.photos.empty? # Fix carrierwave issues when deleting last file
@product.save
end

Known Issue

參考

相關閱讀