2012-05-21 81 views
0

我有一個Heroku服務器上的文件上傳問題。 (另外一個暗示正確的方式做這種類型的東西與鐵路將不勝感激 - 我在RoR是非常新的)。Rails文件上傳和Heroku

所有這些代碼是關於上傳一些CSV文件,然後允許用戶調整幾個設置,並解析文件畢竟。這通常在本地主機上工作(有幾次,我會得到存儲在會話中的值的麻煩),但在Heroku上它總是死在上傳。

在附近的其中一個問題中,Heroku僅在單實例運行時才存儲文件,但在Heroku的文檔中仍然找不到關於此的任何內容。我應該在上傳後將文件數據存儲在數據庫中,因此在這種情況下,它始終可用?缺點 - 文件可能相當大,大約10-20Mb,看起來不太好。

Heroku的日誌說:

2012-05-21T19:27:20+00:00 app[web.1]: Started POST "/products/upload" for 46.119 
.175.140 at 2012-05-21 19:27:20 +0000 
2012-05-21T19:27:20+00:00 app[web.1]: Processing by ProductsController#upload 
as HTML 
2012-05-21T19:27:20+00:00 app[web.1]: Parameters: {"utf8"=>"тЬУ", "authenticit 
y_token"=>"aqJFg3aqENfxS2lKCE4o4txxkZTJgPx36SZ7r3nyZBw=", "upload"=>{"my_file"=> 
#<ActionDispatch::Http::UploadedFile:0x000000053af020 @original_filename="marina 
-AutoPalmaPriceList_2011-07-30.txt", @content_type="text/plain", @headers="Conte 
nt-Disposition: form-data; name=\"upload[my_file]\"; filename=\"marina-AutoPalma 
PriceList_2011-07-30.txt\"\r\nContent-Type: text/plain\r\n", @tempfile=#<File:/t 
mp/RackMultipart20120521-1-10g8xmx>>}, "commit"=>"Upload"} 
2012-05-21T19:27:20+00:00 app[web.1]: 
2012-05-21T19:27:20+00:00 app[web.1]: LoadError (no such file to load -- CSV): 
2012-05-21T19:27:20+00:00 app[web.1]: app/controllers/products_controller.rb:8 
2:in `upload' 
2012-05-21T19:27:20+00:00 app[web.1]: 
2012-05-21T19:27:20+00:00 app[web.1]: 
2012-05-21T19:27:20+00:00 app[web.1]: cache: [POST /products/upload] invalidate, 
pass 

代碼本身:

的ProductsController:

def import 
    respond_to do |format| 
    format.html 
    end 
end 

def import_adjust 
    case params[:commit] 
    when "Adjust" 
     @col_default = params[:col_data] 
     #abort @col_default.to_yaml 
     #update csv reader with form data, restore filters from params 
    when "Complete" 
     #all ok, read the whole file 
     #abort params.to_yaml 
     redirect_to import_complete 
    else 
     @col_default = nil 
    end 
    #read first part of the file 
    @tmp = session[:import_file] 
    @csv = [] 
    source = CSV.open @tmp, {col_sep: ";"} 

    5.times do 
    line = source.readline 
    if line.size>0 
     @line_size = line.size 
     @csv.push line 
    end 
    end 

    #generate a selection array 
    #selection = select_tag 'col_data[]', options_for_select([['name','name'], ['brand','brand'], ['delivery_time','delivery_time'], ['price','price']]) 
    #@csv = [selection * line_size] + @csv 
end 

def import_complete 
    #remove all items 
    #todo check products with line items will not be destroyed. 
    Product.destroy_all 
    #abort params.to_yaml 
    map = {} 
    cnt = 0 
    #todo check for params count. 
    params[:col_data].each do |val| 
    map[cnt] = val if val != 'ignore' 
    cnt += 1 
    end 

    source = CSV.open session[:import_file], {col_sep: ';'} 
    source.each do |row| 
    cnt += 1 
    if row.size > 0 
     item = Product.new 
     map.each do |col, attr| 
     item[attr] = row[col] 
     end 
     item[:provider_id] = params[:adjust][:provider] 
     item.save 
     #abort item.to_yaml 
    end 
    end 

    #abort map.to_yaml 
    #todo response needed. 
end 

def upload 
    require 'CSV' #looks like I dont need this in fact. 
    @tmp = params[:upload][:my_file].path #tempfile 

    @csv = [] 
    #source = CSV.open @tmp, {col_sep: ";"} 

    session[:import_file] = params[:upload][:my_file].path 

    respond_to do |format| 
    format.html { redirect_to action: 'import_adjust' } 
    end 
end 

upload.html.erb:

<h1>Uploaded</h1> 
<%= @tmp %> 

<% @csv.each do |val| %> 
    <%= val %> 
<% end %> 

_form_import.html.erb:

<%= form_for :upload, :html => {:multipart => true}, :url => {action: "upload"} do |f| %> 
<%= f.file_field :my_file %> 
<%= f.submit "Upload" %> 
<% end %> 

import_adjust.html.erb:

<h1>New product</h1> 


<%= form_for :adjust, :url => {action: "import_adjust"} do |f| %> 
<% if @csv %> 
<table> 
    <tr> 
    <% @line_size.times do |cnt| %> 
     <td> 
     <%= select_tag 'col_data[]', 
       options_for_select([ 
        ['--ignore--', 'ignore'], 
        ['name','name'], 
        ['brand','brand'], 
        ['delivery_time','delivery_time'], 
        ['price','price'] 
     ], @col_default!=nil ? @col_default[cnt] : nil) %> 
     </td> 
    <% end %> 
    </tr> 

    <% @csv.each do |val| %> 
    <tr> 
    <% val.each do |cell| %> 
     <td> 
     <%= cell %> 
     </td> 
    <% end %> 
    </tr> 
    <% end %> 
</table> 
<% end %> 


    <%= f.label :delimiter, 'Разделитель' %> 
    <%= f.text_field :delimiter %> 
    <br> 
    <%= f.label :provider, 'Поставщик' %> 
    <%#todo default empty option needed! Human mistakes warning! %> 
    <%= f.select :provider, Provider.all.collect { |item| [item.name, item.id] } %> 
    <br> 
    <%= f.label :delimiter, 'Разделитель' %> 
    <%= f.text_field :delimiter %> 
    <br> 
    <%# Adjust for proceed adjusting or Complete for parsing %> 
    <%= f.submit "Adjust" %> 
    <%= f.submit "Complete" %> 
<% end %> 


<%= link_to 'Back', products_path %> 

回答

0

我有一個與Lifecoder相同的場景,用戶上傳文件,使用map_fields插件(Andrew Timberlake)命名列,然後解析並處理該文件。下面是我如何處理它:

file_field = params[options[:file_field]] 
    map_fields_file_name = "map_fields_#{Time.now.to_i}_#{$$}" 

    bucket = S3.buckets[CSV_COUPON_BUCKET_NAME] # gets an existing bucket 
    obj = bucket.objects[map_fields_file_name] 
    obj.write(file_field.read) 

    # Save the name and bucket to retrieve on second pass 
    session[:map_fields][:bucket_name] = map_fields_file_name 

然後在第二次處理文件,我打開該文件,其讀回溫的測功機工藝:

# Get CSV data out of bucket and stick it back into temp, so we pick up where 
    # we left off as far as map_fields is concerned. 
    bucket = S3.buckets[CSV_COUPON_BUCKET_NAME] 
    obj = bucket.objects[session[:map_fields][:bucket_name]] 
    temp_path = File.join(Dir::tmpdir, "map_fields_#{Time.now.to_i}_#{$$}") 
    File.open(temp_path, 'wb') do |f| 
     f.write obj.read 
    end 

我不得不使用插件,所以我可以修改代碼,顯然這個gem由Heroku來處理,並且不允許修改。

2

你能粘貼整個控制器代碼?問題是#82線,但我不能百分之百地相信,如果你已經剝離了類def和before_filters,那麼這條線是什麼。

這就是說,它看起來像是一個CSV.open行問題。您試圖設置session[:import_file]的方式無法保證正常工作。如果您曾經在多臺測功機上運行該應用程序,則可能會有第一個請求由您的web.1 dyno服務,第二個請求由web.2服務,並且它們具有不同的文件系統,並且無法看到相同的臨時文件。

我建議下列之一:

  • 立即請在上傳所有的處理,避免重新定向。
  • 改進之處在於上傳將數據存儲在共享和可訪問的位置(數據庫或S3),並啓動後臺作業/進程來執行處理。
  • 最重要的是直接上傳到S3(我相信S3 Uploader庫可以做到這一點,有可能是其他),併發出回調來創建後臺作業來處理。

這最後的選擇意味着你的web DYNOS從不綁起來處理大量的上傳和你不負擔給用戶等待參與上傳延遲到服務器 - >專賣店S3->計劃後臺作業 ,從他們的角度簡單地減少到店S3

+0

我稍後會發布完整的代碼。不同的臨時文件存儲不是這個項目的問題,但感謝您的提示。立即做所有的過程是很難的。網站應該允許用戶上傳文件,然後調整文件結構的幾件事情 - 換行符,分隔符等。在沒有附加服務器請求的情況下做這樣的事情看起來是不可能的。此外,看起來客戶不想使用S3(也許Heroku也是如此 - 現在它只是用於測試部署的最簡單的主機)。 – lifecoder

+0

趕上吧!實際上,它確實在#82行(我自己錯過了這個日誌字符串),但它並沒有打開 - 它是「require'CSV'」。要求在* nix上區分大小寫,但在Windows上完全不敏感,所以我在localhost上錯過了這個問題。 – lifecoder