【问题标题】:Rails file uploading and HerokuRails 文件上传和 Heroku
【发布时间】:2012-05-21 20:42:53
【问题描述】:

我在 Heroku 服务器上上传文件时遇到问题。 (也非常感谢您提供有关使用 rails 做此类事情的正确方法的提示 - 我是 RoR 的新手)。

所有这些代码都是关于上传一些 CSV 文件,然后允许用户调整几个设置,然后解析文件。这通常在 localhost 上工作(有几次我在会话中存储的值遇到问题),但在 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

代码本身:

产品控制器:

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

上传.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 %>

【问题讨论】:

    标签: ruby-on-rails-3 file-upload heroku


    【解决方案1】:

    您能粘贴整个控制器代码吗?问题出在第 82 行,但如果你去掉了类 def 和 before_filters,我不能 100% 确定那是哪一行。

    也就是说,问题似乎出在 CSV.open 行之一上。您尝试设置 session[:import_file] 的方式不能保证有效。如果您曾经在多个测功机上运行该应用程序,您的第一个请求可能由您的 web.1 测功机服务,第二个请求由 web.2 服务,并且它们具有不同的文件系统,并且无法看到相同的临时文件.

    我建议以下之一:

    • 在上传时立即进行所有处理,避免重定向。
    • 对此的改进是让上传将数据存储在共享和可访问的位置(数据库或 S3),并启动后台作业/进程来进行处理。
    • 最好的办法是直接上传到 S3(我相信 S3 Uploader 库可以做到这一点,可能还有其他库)并发出回调以创建要处理的后台作业。

    最后一个选项意味着您的网络测功机永远不会被处理大量上传,并且您不会因为等待上传到服务器->存储在 S3 中->调度后台作业所涉及的延迟而给用户带来负担 em>,从他们的角度来看,它被简化为存储在 S3 中

    【讨论】:

    • 稍后我会发布完整的代码。不同的临时文件存储对于这个项目来说不是问题,但感谢您的提示。立即完成所有流程是……很难。站点应该允许用户上传文件,然后对文件结构进行一些调整——换行符、分隔符等。如果没有额外的服务器请求,这样做似乎是不可能的。此外,看起来客户不想使用 S3(也许 Heroku 也不想使用 - 现在它使用最简单的主机来测试部署)。
    • 抓住它!事实上,它确实在#82 行(我自己错过了这个日志字符串),但它没有打开 - 它是“需要'CSV'”。 Require 在 *nix 上区分大小写,但在 Windows 上完全不区分大小写,所以我错过了 localhost 上的这个问题。
    【解决方案2】:

    我有一个与 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
    

    然后在第二遍处理文件时,我打开文件并将其读回 temp 以供测功机处理:

        # 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 处理的,不允许修改。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-02-22
      • 2014-05-07
      • 1970-01-01
      • 2017-09-06
      • 1970-01-01
      • 2017-01-18
      • 2011-03-27
      • 2011-09-21
      相关资源
      最近更新 更多