Jex’s Note

Rails 上傳 Upload

(最後更新: 2016-04-27)

介紹

  • paperclip, 上傳檔案或處理圖片, 對 avatar 可以處理的很乾淨

Paperclip

安裝 paperclip

gem "paperclip", "~> 4.3"

安裝 imagemagick

Mac

brew install imagemagick

Ubuntu

sudo apt-get install imagemagick -y
設定 ImageMagick utilities 的指令路徑

可用以下方法確認你的 convert 指令找的到, 有的話可以先不用設定

$ which convert
/usr/local/bin/convert

如果不能正常運作, 再設定 config/environments/development.rb :

Paperclip.options[:command_path] = "/usr/local/bin/"

上傳 avatar 圖片

1) models/user.rb
has_attached_file :avatar,
  styles: { medium: "300x300>", thumb: "100x100>" },
  # default_url: "/images/:style/missing.png",
  default_url: ->(attachment) { ActionController::Base.helpers.image_path('icons/avatar.png') },      # 在 production 才會正確顯示出來
  url: "/:class/:attachment/:id/:style.:extension",
  path: ":rails_root/public:url"
validates_attachment :avatar, presence: true,
  content_type: { content_type: /\Aimage\/.*\Z/ },                          # 或特定類型 content_type: "image/jpeg" or content_type: ['image/jpeg', 'image/png', 'image/gif']
  size: { in: 0..20.megabyte }                                             # 或 KB { in: 0..20.kilobytes }
  • validates_attachment_content_type :avatar, :content_type => /\Aimage\/.*\Z/
  • 儲存路徑是 : /public/system/users/avatar/000/000/001/original/xxxx.jpg
  • 要注意 nginx 的上傳檔案大小 client_max_body_size 50M;
2) 新增 avatar 所需的 DB 欄位
rails generate paperclip user avatar

產生 db/migrate/20150713155732_add_attachment_avatar_to_users.rb, 內容為

class AddAttachmentAvatarToUsers < ActiveRecord::Migration
  def self.up
    change_table :users do |t|
      t.attachment :avatar
    end
  end

  def self.down
    remove_attachment :users, :avatar
  end
end

執行 rake db:migrate

它會在你的 users 加上這些欄位

t.string   "avatar_file_name"
t.string   "avatar_content_type"
t.integer  "avatar_file_size"
t.datetime "avatar_updated_at"

如果只是要增加欄位

add_attachment :users, :avatar
3) 加上 avatar 相關程式

views/dashboard/welcome/index.html.erb

<%= form_for @user, :url => users_path, :html => { :multipart => true } do |form| %>
  <%= form.file_field :avatar %>
<% end %>

或 simple_form 版

<%= simple_form_for @user, url: dashboard_welcome_update_avatar_path do |form| %>
  <%= form.input :avatar, as: :file %>
  <%= form.submit '上傳' %>
<% end %>

顯示原圖, medium and thumb 大小圖片

<%= image_tag @user.avatar.url %>
<%= image_tag @user.avatar.url(:medium) %>
<%= image_tag @user.avatar.url(:thumb) %>

controllers/dashboard/welcome_controller.rb

class Dashboard::WelcomeController < ApplicationController
  def index
    @user = current_user
  end

  def update_avatar
    if User.update(current_user, avatar_params)
      redirect_to action: :index
    end
  end

  private

  def avatar_params
    params.require(:user).permit(:avatar)
  end
end

config/routes.rb

namespace :dashboard do
  root 'welcome#index'
  patch 'welcome/update_avatar'
end

參數說明

  • :url : host 後面那段 url 路徑,也是檔案相對路徑
  • :path : 檔案儲存位置的完整路徑
  • :default_url : 如果找不到圖片時的預設圖
  • :styles : A hash of thumbnail styles with geometries. If you need copies of uploaded files with particular dimensions then specify them here.

hash

has_attached_file :avatar, {
    :url => "/system/:hash.:extension",
    :hash_secret => "longSecretString"
}

image

has_attached_file :avatar, :styles => {:thumb => 'x100', :croppable => '600x600>', :big => '1000x1000>'}
has_attached_file :cover, :styles => {:small => 'x100', :large => '1000x1000>'}
has_attached_file :sample, :styles => {:thumb => 'x100'}

Dynamic Processor

has_attached_file :avatar, :styles => lambda { |attachment| { :thumb => (attachment.instance.boss? ? "300x300>" : "100x100>") } }

has_attached_file :avatar, :processors => lambda { |instance| instance.processors }
attr_accessor :processors

不檢查檔案格式一定要加上

do_not_validate_attachment_file_type :client_uploading

尺寸的符號

:styles => { :medium => "300x300>", :thumb => "100x100>" }

  • > : 等比例, 將圖片的大小縮到小於這尺寸, 較常用
  • < : 等比例, 將圖片的大小縮到大於這尺寸
  • # : 等比例, 設定的最長邊與圖片的最長邊相接, 裁切多餘部分, 一般用於縮圖或頭像
  • ! : 非等比, 強制圖片長寬和該尺寸一樣大
  • ^ : 等比例, 圖片的大小最小要那麼大
  • : 等比例, 圖片的大小最大要那麼大

儲存路徑參數

  • :style : original
  • :basename : 檔名
  • :id : TABLE primary key
  • :id_partition :
  • :fingerprint
  • :attachment : 欄位名稱 (複數), ex: avatars
  • :extension : .jpeg
  • :class : model name, ex: users

不同大小的 avatar

url: "/:class/:id/avatar/:style.:extension",
path: ":rails_root/public:url",

/users/4/avatars/original.jpeg?1436890118
/users/4/avatars/medium.jpeg?1436890118
/users/4/avatars/thumb.jpeg?1436890118

檔案不公開, 在根目錄建立 private (與 public 同層)

url: "/:class/:id/:basename.:extension",
path: ":rails_root/private:url"

/users/3/profile.png

Custom path

url: "/:class/:uuid/avatar/:style.:extension"

Paperclip.interpolates :uuid do |attachment, style|
  attachment.instance.uuid              # uuid 是欄位名稱
end

Default url

預設

default_url: "/images/:style/missing.png",

如果要使用 assets/images 下的圖片,必須這樣設置,在 production 才會正確顯示出來

default_url: ->(attachment) { ActionController::Base.helpers.image_path('icons/avatar.png') },

其他

刪除檔案

u.avatar = nil
u.save(validate: false)

Private 檔案下載

if @case && @case.original_file && File.exist?(@case.original_file.path)
    send_file @case.original_file.path
end

判斷是否已上傳檔案

@users.avatar.exists?

必須上傳圖片

validates :photo, presence: true

Paperclip + Crop

簡易 crop

加上 convert_options

has_attached_file :avatar,
styles: { medium: "300x300>", thumb: "100x100>" },
url: "/:class/:id/avatar/:style.:extension",
path: ":rails_root/public:url",
convert_options: {
  #the gravity parameter takes "Center" and directions like "northeast, nort, west"
  :thumb => "-gravity northwest -crop 100x100+0+0 +repage", #crop to 100x100 starting at upper left corner (northwest)
  :medium => "-gravity northwest -crop 101x100+100+0 +repage", #crop to 100x100 starting 100 pixels to the right of the upper left corner
}

使用 papercrop

Gemfile

gem 'papercrop', '~> 0.3.0'

application.js

//= require jquery.jcrop
//= require papercrop

application.scss

*= require jquery.jcrop

controller

def upload_avatar
  current_user.update(avatar_params)
  redirect_to action: :edit
end

def crop_avatar
  current_user.update(crop_params)
  redirect_to action: :edit
end

def avatar_params
  params[:user].permit(:avatar)
end

def crop_params
  params[:user].permit(:avatar_original_w, :avatar_original_h, :avatar_aspect, :avatar_box_w, :avatar_crop_x, :avatar_crop_y, :avatar_crop_w, :avatar_crop_h)
end

model

has_attached_file :avatar,
  styles: { medium: "300x300>", thumb: "100x100>" },
  url: "/:class/:id/avatar/:style.:extension",
  path: ":rails_root/public:url",
  processors: [:papercrop]              # 加上它
validates_attachment :avatar, content_type: { content_type: ['image/jpeg', 'image/png', 'image/gif'] }, size: { in: 0..10.megabyte }
crop_attached_file :avatar              # 加上它

view

上傳的 view
<%= form_for @user, url: upload_avatar_info_user_path(@user), method: :PATCH do |f| %>
  <%= image_tag @user.avatar.url %>
  <%= image_tag @user.avatar.url(:thumb) %>
  <%= image_tag @user.avatar.url(:medium) %>
  <%= f.file_field :avatar, as: :file %>
  <%= f.submit 'Save' %>
<% end %>

crop 的 view
<%= form_for @user, url: crop_avatar_info_user_path(@user), method: :PATCH do |f| %>
  <%= f.cropbox :avatar, width: 500 %>
  <%= f.crop_preview :avatar, width: 100 %>
  <%= f.submit 'Save' %>
<% end %>

crop 不會修改 original 的圖片只會改 thumb 及 medium 的圖

Paperclip 上傳到 S3

安裝 & 設定

Install aws-sdk

gem "paperclip", "~> 5.0.0.beta2"           # 注意! 5 版以上才支援 aws-sdk 2 版
gem 'aws-sdk', '~> 2.2.37'

1) 先到 IAM 建立一個有 S3 權限的 User, 並記下 Access Key ID Secret Access Key

2) 到 S3 -> Create Bucket -> 設定 Policy, 設定後簡單使用 command 上傳測試是否 work, 請參考此篇 - AWS-SDK / AWS-CLI 上傳及下載

3) 設定給 paperclip 讀取 s3 的 config。可以設定在 config/application.rb

config.paperclip_defaults = {
  storage: :s3,
  s3_region: 'ap-northeast-1',
  s3_credentials: {
    bucket: 'my-bucket',
    access_key_id: 'Access Key ID',
    secret_access_key: 'Secret Access Key',
  }
}

4) Model 的 Paperclip 設定

s3_host_name: "s3-ap-northeast-1.amazonaws.com",
url: "/:class/:attachment/:id/:style.:extension",
path: ":url"    # path 跟 url 一樣就好了

5) 輸出 @product.photo.url(:thumb) 它就會自動組出正確的 url 了

http://s3-ap-northeast-1.amazonaws.com/my-bucket/products/photos/1/thumb.jpeg?1461739010

6) 完成

區分 Development 使用本機空間, Production 使用 S3

1) 加上環境判斷, config/application.rb

if Rails.env.production?
  config.paperclip_defaults = {
    ...
  }
end

2) (選項)區分 path

# 如果是 development 上傳到網站根目錄的 /public
path: (Rails.env.production?) ? ":url" : ":rails_root/public:url"

# s3_host_name 不需處理它, 即使 development 存在這個參數也無妨
s3_host_name: "s3-ap-northeast-1.amazonaws.com",

3) 完成! 記得要重啟動 Rails

Comments