Paperclip 圖片上傳與額外說明


#1

Paperclip 是一個檔案上傳用的 gem,它很好用,可以做盡任何想做的事情,不過你要會用它才行…

首先,最基本的用法類似官方的 demo

class User < ActiveRecord::Base
  has_attached_file :avatar,
    :styles => { :medium => "300x300>", :thumb => "100x100>" },
    :default_url => "/images/:style/missing.png"
  validates_attachment_content_type :avatar, :content_type => /\Aimage\/.*\Z/
end

尺寸後面有幾個表述式可以加,詳細可以看這邊,而 # 是 paperclip 用演算法算出來的,不在 imagemagick 內,切記

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

其餘略過…請自己去玩去測,而上面語法中其實還可以加很多的東西,例如,上面沒寫的,如何補白邊(縮到設定的框內然後少的地方填白)

has_attached_file :avatar,
  :styles => {:original => ['1024x1024>' , :jpg] , :public => ['640x640>' , :jpg] , :view => ['360x360#' , :jpg]},
  :convert_options => {
    :original => '-coalesce -sample "1024x1024>" -colorspace sRGB' ,
    :public => '-background white -gravity center -extent 640x640 -colorspace sRGB -quality 70',
    :view => '-colorspace sRGB -quality 60'
  },
  :path => ":rails_root/public/uploads/images/:id_:style_:fingerprint.:extension",
  :url => "/uploads/images/:id_:style_:fingerprint.:extension"

… 這邊做了很多的事情 … 首先,style後面可以加入固定的檔案格式,然後 convert_options 可以增加 imagemagick 的補述,類似強制使用 sRGB 的 colorspace (不然上傳圖檔過來可能會是 CMYK ),再來因為是 jpg 所以手工自訂 quality 壓縮比,而:public 那一長串是設定補白邊的動作,最後的:path 和 :url 做的是網址指定,且使用了:fingerprint,也就是增加一個 MD5 的 hash 來做檔案命名的動作,教學 Paperclip 的 github 的首頁就有,不過要 db 增加一個 column 就是

然後還有一個重點, Paperclip 會自動幫你上 original 這個 style ,即使你用不到,所以如果不要讓硬碟空間噴掉,記得把 original 也用上並設定就是

然而,額外的,如何最省 column 的情況下製作 paperclip …
以下 evil way 小朋友不要學,& 注意,這邊只用於新專案,舊專案會找不到關連或是檔案極度的麻煩

首先 … 一般來說 Paperclip 會幫你增加以下的column,並加上我的補述

'photos' , 'image_file_name' , :string   #原始檔名:要這做啥,md5命名就好了啊
'photos' , 'image_content_type' , :string   #原始類型:要這做啥,我的style內有指定它最終類型了
'photos' , 'image_file_size' , :integer  #原始大小:要這做啥,我又不賣檔案空間
'photos' , 'image_updated_at' , :datetime  #原始大小:要這做啥,原record有另外一個updated_at了

#然後我要這個column
'photos' , :image_fingerprint , :string #該圖片的MD5編碼

okay…所以以上的 column 可以全部移除 X"D …移除的先決條件的情況下會是,要 fingerprint 命名,然後 style 要進行圖檔格式的限制(才抓得到 :extension ),包括 original 也要照做,所以可以學上上面的code的範例來宣告 paperclip ,可是這樣下去它會噴些 error出來,類似會找不到 image_file_name 和 image_content_type ,然而其實少的 column 我們可以 fake 出來,寫一下最終的 code

class Photo < ActiveRecord::Base
  has_attached_file :image ,
    :styles => {:original => ['1024x1024>' , :jpg] , :public => ['640x640>' , :jpg] , :view => ['360x360#' , :jpg]},
    :convert_options => {
      :original => '-coalesce -sample "1024x1024>" -colorspace sRGB' ,
      :public => '-background white -gravity center -extent 640x640 -colorspace sRGB -quality 70',
      :view => '-colorspace sRGB -quality 60'
    },
    :path => ":rails_root/public/uploads/images/:id_:style_:fingerprint.:extension",
    :url => "/uploads/images/:id_:style_:fingerprint.:extension",
    :default_url => "/images/:style/missing.png"

  validates_attachment_file_name :image, :matches => [/png\Z/, /jpe?g\Z/]

  attr_accessor :image_file_name , :image_content_type
  def image_file_name
    return "#{self.image_fingerprint}.jpg";
  end
end

簡單的來說把 validates 改成使用 filename ,然後 fake file_name 和 content_type 的 methods ,並讓 validates 會通過,至於如果怕 content type spoofing 應該也不用擔心,因為這邊所有的圖片都會被強制轉檔過,而非原檔直接上傳之類的 :smile:

最後,其實…MD5編碼的那個 column 其實也可以刪除,不過後續的東西效能不好且麻煩|||(要算出來才知道網址是啥…)

okay以上,請自己多多玩玩就是了


影片day2.5 中出現 constant ItemController
文件控管?
paperclip 自訂路徑(+ processor 解說)
#2

請問一下 jc…

他的 : path 是不是就自動會把檔案放在這目錄 ?

看官網有個 storage 的動作…

我只套你的這個程式碼… (controller的 new , create + model 的這段…)

沒有看到檔案有上傳到 server …他的 params 有寫存到 /tmp/xxxxxx (這有看到檔案)…

paperclip 有把檔案存到 path 的功能 ? 還是要另外補 code ?

thx

==continue==
傳回來的 params…在 image(上傳的變數為 image) 中帶有以下的參數…

“image”=>{“image”=>#<ActionDispatch::Http::UploadedFile:0x00000003e66448 @tempfile=#Tempfile:/tmp/RackMultipart20141105-31965-1odvtpk,
@original_filename=“pic-201410291956491.jpg”,
@content_type=“image/jpeg”,
@headers=“Content-Disposition: form-data; name=“image[image]”; filename=“pic-201410291956491.jpg”\r\nContent-Type: image/jpeg\r\n”>},
“commit”=>“Create”}

他的檔案有上傳到~
像這種 image 內的 @tempfile 之類的要怎讀取?
params[:image] ??
只要讀的到~再把檔案放到自已要的地方 & 寫入資料庫~應就大功告成了??

在 paperclip 中這段是否自已要寫 ?

還是我誤會了什呢?

怎拿到 params 的 photo 下的@tmpfile ??


#3

Orz…請搞懂你的 params = hash

params["photo"]["image"]

看懂你的 hash key & <ActionDispatch ... > 的界定範圍唄
& 你的 code 應該是類似

@photo = Photo.new(photo_permit)

裡面的 params 要過一層 permit 唄,自幹的話會類似

@photo = Photo.new
@photo.image = params[:photo][:image]
@photo.save

#取得網址 => @photo.image.url(:style)

:path 可不上,預設是public資料夾裡面會多個資料夾啥鬼的就是

然後你的上傳檔案是依屬於 photo ,所以 photo 沒塞 image 給它也沒 save ,自然不會丟去 public 的啊…(這時有丟才奇怪了,會多一票沒記錄在 DB 的垃圾之類的)


#4

依 jc 的設定~~

圖的路徑好像跑掉了`~~

model 的設定

controller 的設定

出來的路徑

view 的碼

paperclip 存到 db 內的資料好像被蓋掉了~~

會變成 /images/xxxx…但有看到 uploads/images/xxxx

有存進去 db…就最後讀取的值好像被蓋成 defualt 的值~~

這是什問題?


#5

A … 你的 @user.avatar 是不是被 permit_params 過濾掉了,增加 column / method 接 form 的 params 輸入時應該要先新增 permit 的column,不然就要自己指定之類的,類似上個問題的回答

圖片失敗的原因是 missing.png 也就是根本沒有那張圖片的啊…
這樣說我的範例設定錯誤這樣對咪|||,&該 method 一定會回傳 paperclip 的物件,否則如果是 nil 的話,是沒辦法用 user.avatar.url 的,會是 nil no method name : "url"

簡單的測試

@user = User.new
@user.avatar #=> 和你相同的輸出
@user.avatar.url(:original) #=> /../../missing.png

@user.avatar.class #=> Paperclip::Attachment #可以用這線索去查doc
@user.avatar       # => 在此處按下tab會出現 methods
@user.avatar_file_name #=> nil,去 call column name 看 DB 內是否有資料

#建立覆蓋,純demo顯示nil與paperclip物件的差異性
class User
  def avatar
    return nil
  end
end
@user.avatar #=> nil
@user.avatar.url(:original) #=> NoMethodError: undefined method `url' for nil:NilClass

而所謂的 :default_url 的那張 missing.png 的路徑的意思是,當圖片沒被上傳時或沒資料要用那張圖片替代的意思

然而另外方面,對我而言你的 MySQL / Ruby 概念不夠熟才會到處碰壁之類的,首先圖片上傳錯誤應該要先檢查 DB 的 Table 的該 Record (Row) 的該 column 是否有 data,才去檢查 Ruby 或是檔案有沒有到資料夾,這是因果關係 … 而不是說去找檔案,然後去找 Ruby,最後才發現 DB 沒東西 … 這樣 debug 的順序不對唄|||


#6

這個要分享一下失敗的經驗…

就 PHPer 來看~~

個人覺的有幾個問題是 Ruby 的「物件」程式和 PHP 的簡易模式有些不同…也就造成一直在感覺鬼打牆…

⑴ Ruby 的物件是怎存到資料庫的??
因為就 PHP 來看就是表單拿到一個 array, 再一個一個 array 的值存到資料庫的欄位去.
而 Ruby 的 params 傳回來是一個物件…有點不了解物件的「屬性」(也就是數值)怎存到資料庫…直接用個 XX.create(params[:xx]) 或 @xxx.save 就存好了…在 rails console 下比較簡單(也就是上課有demo到)…表單傳過來的 params 怎存到 db?可能 demo 太快就自已練習一直覺的卡關…更何況這個是一個「檔案」…又不是一個欄位的值…在老手看來很簡單,自已試因為沒有示範code就覺的怎一直有問題…(php的就把檔案的路徑由/tmp捉到路徑檔名再用php來移動到你要的位置…) … 就搞不懂 paperclip 的檔案是怎移動如何存檔名(paperclip都做好了~但就看不懂…)

⑵至於 debug 的補充說明…
在測試的過程中~有看到 db 有存這筆資料(但用單筆看會出現 nil, ex. User.last 會出現 avatar 的值為 nil…用 User.last.avatar 居然有值…)而檔案有上傳上去…
去讀取 User.last.avatar.uri (uri 應是一個「方法」去讀值)…居然不是正確縮圖路徑…自然就是掉圖了…只能說 ruby 真的很不熟+gem的文件不熟悉…

db有資料,路徑有錯誤/檔案有上去→就造成這樣囉…

就一個 ruby 新手來看…
params 的寫入到 db 這塊還是很不熟…畢竟和 PHP 來比~熟悉了用 key-value 的 array 寫是熟悉的~但 ruby 的物件用 hash 來寫是不了解&不熟悉的…

這方面還是需要練習&理解…
⑴ params(form的傳送) 的檔案是怎移動和讀取資料?
⑵ params的資料怎存到 db 的欄位?
以上應該是這次最大的問題點…(畢竟可以用 params 列出傳了什東西…)

繼續測試


#7

hmm … 我感覺你還是扯遠了,看不清一些東西

params = controller 包出來的 hash
ORM ~= ActiveRecord = DB介接

所以你的概念上一直都少一層最重要的ORM包裝就是,anyway最近開始比較不忙,我等等發一篇 Active Record in pure Ruby的文章好了


#8

這~歷經千辛萬苦終於 ok…但有個小地方不知 bug 在哪…

圖片有上傳到了…

Model 照上面的 code 來弄

也有上傳到目錄…

用 image_tag 在網頁也正常…

就是圖片後面又多一串碼?(應該是存到 db 的地方多了什?)

有多了一個 image_fingerprint 的欄位的存取也正常…

但 image.url(:view), (:public)…後面都多了一串 ?..也指的路徑也怪怪的…

是在 model 那個地方存錯了嗎??看不太出來呢…也就是 image.url(:XXX) 這邊的資料應該入有問題…但撈出來的頁面圖檔又是對的(結尾怪怪的)…

這是那段 code ?

thx


#9

我大概說一下後面那串在做啥… & 那並不是bug,而是Rails超級好心幫你做的 timestamp 標記,拿來防止瀏覽器 cache 的機制

你可以在 irb 下打類似

Time.now.to_i
#=> 1415855165
Time.at(1415851347) #圖片後面的數字
#=> 2014-11-13 12:02:27 +0800

(現在看到14開頭的數字都很可能是 timestamp 因為是 Unix Timestamp 的標記方式)

簡單的來說 Rails 把檔案的 timestamp 加在那邊,瀏覽器快取時會把網址和檔案綁在一起,而如果檔案有變更的話 timestamp 會一起變更,就不會一直讀到舊的檔案的意思

檔名後面加個問號其實就是要傳GET的意思,而這種手法很常見,類似 jQuery 的 ajax 裡面也都有,類似

http://api.jquery.com/jquery.ajax/

的 settings 的 cache 段,如果設定為 false 會自動在網址後面加上 "_={timestamp}",這是防止瀏覽器快取很常見的方式就是了,簡單的來說就是讓網址不同即可


#10

之前好像有問過~~
算圖的 fingerprint 是要怎算?要 require 還是??.

因為在 return “#{self.image_fingerprint}.jpg” 這會噴 fingerprint 的 no-method 錯誤…


#11

image_fingerprint是自行加入的欄位,用md5產生的一串亂碼,來做為圖片名稱。
要產生的話

require 'digest'
Digest::MD5.digest "some string"
# => "Z\xC7I\xFB\xEE\xC96\a\xFC(\xD6f\xBE\x85\xE7:"

#12

…hmm

image_fingerprint 沒記錯的話是 paperclip 用檔案內容幫你算 ( 等同 created_at / updated_at 之類的 magic column ),請你不要自己算||||

以最上面的 demo 來說,增加一個 string,命名為 image_fingerprint 就會自己幫你塞了,請自行嘗試,& 上面不是都有 demo code 了,包括 migration||| … 做這個變換記得該表不能有之前上傳過的內容,否則你就要自己重新 rebuild 所有 filename

& rebuild…也不是用上面的 md5 用法(沒人用 binary 的md5的啦…|||都是 hexdigest)

require 'digest'
md5 = Digest::MD5.hexdigest(File.open('upload.jpg' , 'r').read)
#=> "d41d8cd98f00b204e9800998ecf8427e"

如果該 md5 不重要且不用管的話,直接用

require 'securerandom'
fake_md5 = SecureRandom.hex(16) #=> len = 32
#=> "f8d92100d533609195351c36c5195195"

即可,以上


#13

想請問一下,如果我參考 RailsCast 來安裝 paperclip,而我的Rails版本是 2.3.14,用 script/plugin 可以安裝的起來,但是後面 generate 時就會噴錯!想請問可否指定 paperclip 安裝的版本?


#14

Gemfile 可以指定安裝版本

參考
https://ihower.tw/rails4/environments-and-bundler.html


#15

from https://github.com/thoughtbot/paperclip#ruby-and-rails

Requirements

Ruby and Rails

Paperclip now requires Ruby version >= 2.0.0 and Rails version 3.2, >= 4.1 (Only if you’re going to use Paperclip with Ruby on Rails.)

If you’re still on Ruby 1.8.7 or Ruby on Rails 2.3.x, you can still use Paperclip 2.7.x with your project. Also, everything in this README might not apply to your version of Paperclip, and you should read the README for version 2.7 instead.


#16

我的Rails 2.x 沒有 Gemfile XD


#17

了解,謝謝JC大大 :slight_smile:

http://www.rubydoc.info/gems/paperclip/2.7.0