paperclip 自訂路徑(+ processor 解說)


#1

paperclip 裡面有一個設定

在 models 底下 一個是讀取的路徑 一個是真正的檔案放置路徑

has_attached_file :file_ga ,
{ 
	:url => "/system/:class/:id/:basename.:extension", 
	:path => ":rails_root/file_root/:class/:id/:basename.:extension"
}

但是這樣子當檔案累積到一定程度的時候 其實資料夾都是 :id編出來的資料夾

真的檔案一多好難找檔案

但有辦法 把網頁的 params[:id] 或是個別的功能頁 個別動態賦予資料夾id 掛進去嗎

來辨別原始的上傳功能是從哪個地方 或哪個步驟所執行上船的

查一下應該是要用覆蓋的方式 把功能蓋掉 但是不知道要怎樣加進去 :roll_eyes:


#2

可以啊,為何不哈哈,單純沒有該 methods 而已

paperclip 會對 url / path 做 interpolations (插值) (其實就是過濾 string 把別的 string 插進去而已),但其沒有對 activerecord 的 obj 做緊密結合,因為預設該條件必須在"所有" model 都有的狀況下才能變成預設值,否則『可能沒有』或是『可能重複』還有『未來還會被修改』都會引發災難的|||(類似同名會刪除一樣的檔案 / 沒有則無法儲存等等 … )所以官方的寫法都必須增加為 method 才行

所以基於這個條件:id / fingerprint 都是符合的選項,但類似 updated_at / created_at 或是任何的自我輸入 params 都是不合規格的,因為這些都和圖片或檔案的『唯一值』無關才是,且『看似可以被修改』,所以你即使加上了類似這篇所說的

:style 或是任何自我輸入的欄位,你還是必須要加上我剛剛說的 :id / :fingerprint 才行

so~ 以上是基本規則,其餘的 why not 哈哈 but 請記得該欄位『不允許修改,且新增時必須要有』,否則你就找不到檔案了,因為人生只有一次,paperclip 的存檔也是,然而因為他是 method,所以必須『做』出來才行

對我而言 code 就是最好的 example,你需要修正兩個檔案,或是一個

你可以對照 updated_at 段有兩個,其中 attachment 指的是 paperclip 物件,而 attachment.instance_read 指的是原本 activerecord obj column 去做讀取,看到這邊你就完成了 98% 的動作了 … 而你可以選擇對 attachment.rb & interpolations.rb 增加 method,或是單純的 interpolations.rb 使用 .instance_read 去讀 activerecord obj 即可

之後就是很單純的覆寫與包裝了,我單純對 interpolations.rb 做覆寫

# [%RAILS_APP%/config/initialize/paperclip_custom_interpolate.rb]
module Paperclip
  module Interpolations
    def hello_kitty attachment, style_name
      attachment.instance_read(:hello_kitty)
    end
  end
end

之後你應該就可以用 :hello_kitty 了?不過上述的 code 未測,但應該 ok,你玩玩看唄?

其實 paperclip 好玩的應該不是 path,而是

預設的 processor 只有用 linux 中的 imagemagick 的 convertidentify 這兩個最基本的指令,但覆寫後變成 Thumbnail 這個預設的 processor,就可以讓你自訂縮放之類的功能,所以你也可以寫一個 processor 做更多處理

這規則讀懂後,你應該可以用 paperclip 做任何事,包括影音 / 音樂轉檔,上傳 PDF 轉一票圖片(線上電子書),還有任何你在 linux command 下可以做到的東西,只是看你有沒有創意罷了哈哈,以上


#3

有些複雜 沒頭緒

thoughtbot/paperclip/blob/master/lib/paperclip/interpolations.rb

thoughtbot/paperclip/blob/master/lib/paperclip/attachment.rb

這些是要去改它的原始模組(安裝起來的gem) 直接去做變更嗎 專案裡也沒有這個東西 ??


#4

… 我的 demo code 裡面有一段

# [%RAILS_APP%/config/initialize/paperclip_custom_interpolate.rb]

也就是在你的專案下的 config/initialize 下增加一個叫做 paperclip_custom_interpolate.rb 的檔案,然後複製貼上就有惹,通常 %RAILS_APP% 這種寫法指的是你的專案哩,在任何 model 中增加一個叫做 hello_kitty 的欄位,然後用類似 item.hello_kitty 這個 field 和 paperclip 設定 url / path 內用 :hello_kitty 這個 interpolations 做例子就是


#5

試了很久還是沒辦法

還跑去找了其他人的用法 結果應該是舊版本

我大概貼出來

我在db 裡面定義資料表

class CreateDocumentFiles < ActiveRecord::Migration
  def change
    create_table :document_files do |t|
      t.string :file_name, null: false, default: "" # 檔案名稱
      t.attachment :file_cw  # file  附件&path
      t.timestamps null: false
    end
  end
end

在PATH底下要多一個 parent_id 所以

models 底下

class DocumentFile < ActiveRecord::Base

has_attached_file :file_cw ,
{ 
	:url => "/system/:class/:parent_id/:id/:basename.:extension", 
	:path => ":rails_root/file_root/:class/:parent_id/:id/:basename.:extension"
}


end

新增一個檔案於
config/initialize/paperclip_custom_interpolate.rb

內容

module Paperclip
	module Interpolations
		def parent_id attachment, style_name
			attachment.instance_read(:parent_id)
		end
	end
end

然後到這裡都沒問題 如果此時直接新增 其實就是把 :parent_id 當成一般字

所以 :parent_id 會變成資料夾名稱

接著 要把 :parent_id 當成 接收目前上傳頁面的 params[:id]

所以

 def create
 @my_document = DocumentFile.new
 @my_document.parent_id = params[:id]
  if @my_document.save
   redirect_to my_document_path(params[:id])
 end
end

不過create 這一段參考jc大的寫法 但好像還是有問題 是哪裡有少寫還是 我自己哪裡寫錯 :hushed:

試了一下午 這一篇大概是最接近的一篇 但結果還是失敗

重點在 attr_accessor :current_user

用來接受 Controller 賦予值 attr_accessor 取值來用??

如果只用

Paperclip.interpolates :parent_id do |attachment, style|
@parent_id = "#{Date.today.to_s}/"
end

這樣是ok的

但如果加上要賦予當前使用者email

Paperclip.interpolates :parent_id do |attachment, style|
@parent_id = "#{Date.today.to_s}/#{@current_user.email}"
end

結果就是取不到 @current_user.email

:face_with_raised_eyebrow:


#6

很好,剛剛花了些時間驗證,我錯了 X"DD … 我少看東西了 Orz"

def instance_read(attr)
  getter = :"#{@name_string}_#{attr}"
  if instance.respond_to?(getter)
    instance.send(getter)
  end
end

所以他的 instance_read 是有前綴的意思,類似你的 image_filename 的那種前綴,你可以弄得和它一樣,所以之前的 hello_kitty 就必須叫成 image_hello_kitty 才行,或是另外這樣寫

# [%RAILS_APP%/config/initialize/paperclip_custom_interpolate.rb]
module Paperclip
  module Interpolations
    def hello_kitty attachment, style_name
      attachment.instance.send(:hello_kitty)
    end
  end
end

attachment.instance 就代表是你的 post 物件,用 .send(:hello_kitty) 就是 call method 的意思哩

剛測試是 ok 的就是了,至於其他的,你的物件觀念理解的非常非常差 … 你作用域的不同是無法取得 @current_user 這個物件的 Orz"(要也是從 post 為出發點重新獲得),且不可能使用 email 來當 path … 還沒有過濾就餵進去!?你的 @ 字元就這樣變成資料夾名稱!?

再來,email 你確定不會重複?使用者未來不會改 email!?你確定你把 email 來當作唯一的 key 是正確的嗎?未來不會引發災難?類似使用者表不小心砍光了,就連一個檔案都無法拉回正確的 path,使用者砍掉的同時,如果先砍使用者,你連檔案都無法刪除的,因為沒有完整參照 …,或是儲存時資料重複到一個不可思議的地步,因為你把 1 : N 的關聯拉進來了?

如果你的系統有漏洞,使用者可以改 email,email 內被置入了 bash 攻擊語法,你的這個做法瞬間從部分資料庫被盜轉變為取得 shell 權限的啊 … 所以最差最差最差也至少要把過濾做進去的啊 …

最後,為何會有這個需求?單純是你在 linux 下比較好用 find?那你另外寫個輔助系統用正向的方式來找尋會不會好點?還可以把亂數檔名轉成原本的檔案名稱來讓使用者顯示和下載( 請見 controller 內的 send_file / send_data 的 :filename / :disposition 段 )因為我在任何大型系統上面沒看過這類的需求 … 尤其 email 最長可以到 254 字元時 … 實體檔案和邏輯檔案對應可以完全切離的 …

anyway 如果你多看些 CDN / Google Image / imgur 如何管他們圖片的儲存方式時,再回頭看一下你的系統,之間的抉擇,這樣會不會好點?


#7

其實是 之前那個文件管理系統的應用

某校方要用

35個科系 + 一般辦公室 + 行政部門

單個單位 檔案2~3萬個檔案node 是很一般的 還包含 過去的歷史檔案 要補上去

我都還在擔心這樣玩 acts_as_nested_set 撐不撐得住

沒有一定要用email 來當作資料夾名稱 但是必要做出某種程度的簡單分類

不然 全部檔案都被放在同一層 然後藉由 path 底下 的 :id 去大量新增

那樣子 只是系統能動 後面有找東西 要翻東西 倒楣的也是我自己

因為同一層 有幾萬個資料夾 而且都是用數字無意義編號出來 我會很想死 ==

我先把 前端可以接到為主

然後某程度的利用機制去做個簡單的儲存分類

不一定要用user_id 或mail丟進來的值 我目前有考慮用role的狀態別+各單位簡稱 來做簡單的分類處理

然後判斷後 預設給他一個我自定義的命名 別直接使用一樣拋過來的值

畢竟 url 跟 path 還是可以分開處理 不一定要弄到一模一樣 但是遊戲規則一但執行 就不可以再換了

不然會造成路徑的浩劫 (別說之後在改 就連我在開發 我自己都快被搞死了 何況是之後還要改的話…)

我是有考慮 要自己打一個 簡單的接到值之後 然後進行特殊處理的機制 無論是重新命名 或是加密命名的資料夾

這個我自己確實有在思考 但是錢不多阿 價碼連7位數的一半都沒有 某程度還要自己去做服務 蠻多實務上的考量需求

能夠安全 又可以我方便後續做事情 蠻多要想的

之前的東西已經放上去試跑 但是儲存這一塊我馬上就知道 不去劃分 死的早晚會是我…

我先試看看jc大的實際內容
:joy:


#8

… 我之前的作品 … 檔案已經到達十萬還百萬級,不過的確你說的地方我有修正過,不過我修正的方式是這樣的

/public/images/{YYYY}/{MM}/{id}_{fingerprint}_{style}.{ext}

而 timestamp 用的是 created_at,因為這樣以後要切離,我可以把年份移走,然後 ln 回來即可,就可以分散在不同儲存媒介上了,其餘都是輔助的管理系統哩,anyway 給你參考就是,以上缺一不可,且就算亂光光還是可以回推的就是,當然如同你說的,你還是可以加上 /{role}/,不過該欄位必須儲存在檔案上傳的 table 內,不允許修改且必須要有就是,不能用參照的,請重複儲存

and 這種檔案系統基本上是"不允許"你自己去翻的,一定是用『DB => 檔案』去驅動,否則光是 ln 可能就會導致 io / system 死光光,所以任何異動搜尋都要用正向去管理,也就是『DB => 檔案』,此方式不可逆,切記


#9

我倒是不擔心分散儲存的問題

因為這個案子校方有去買一個 50還70tb的nas

除非你給我拿來儲存影片檔 不然最好有辦法存文件可以把這種容量給弄爆

acts_as_nested_set 目前我是覺得還好

只是一值用 每年都是幾10萬的node在增加 不知道哪時候出問題

不過真的出問題 也只能老實說 當初這東西就不適要給這樣子的數量用的= =

他們還想要搞 刪除 其實只是註記 也就是說 所有的動作 都不會讓node減少

只是讓使用者看不到 所以 拉基node到時候也會是一堆 哀 …

這種專案 搞到後面 有時候人為因素 真的是很想罵髒話

特別是這種體系龐大的單位結構

每個人都想要玩一種 然後上面的人也依值在鬥來鬥去

想要偷窺裡面有啥 (翻白眼

所以 又要安全 又要好做事 又不要搞自己

沒多少錢的案子搞得我心力交瘁…

說不完阿 所以 我時常會提一些看起來很白目的東西 有時候我自己都考慮好久忍不住才提出來問 = =

因為我知道 我應該又要被笑了 呵呵 不過jc還是肯回文 我都快哭惹:joy:

先謝過 jc大 我還要繼續趕工了…


#10

嘛,沒人笑你,即使笑你也是笑過去的自己,曾幾何時自己也曾經想這樣搞過

(包括把 image raw data 放 DB 都用過哩哈哈)

單純有些東西因為後來經驗而直覺不能那樣開罷了 … 然而和業主收斂需求也是非常重要的
簡單的來說你摸過的東西越多,會的手法越多,就越知道該如何收斂需求,並提出一個雙方合理的方案就是
然而很多時候都要有氣魄,寧願先說不行,或是給自己多些緩衝時間研究,之後才提成可以,也不要
一開始就對自己沒把握的東西就直接變成定案,很危險就是了

不過這些都需要練習,怪需求來了很多時候都需要阻擋,權限劃分,只有你能做這系統的上帝
不允許別人插手,尤其他想做超過他權限該做的事情的時候,對他比中指唄,並把安全性考量納進去

『如果你的帳號被入侵了,你的權限是最高權限,你要對哪些事情做負責?,或是拿你的職位/身家財產做擔保?』

的思維丟給對方,叫對方閉嘴唄 … 很多東西都要練習,取得一個折衷的方式,別讓自己心血長成自己都不愛的樣子唄 …