Rails中如何更加优雅的处理文件上传

阅读更多

  通常,在rails中处理文件上传,我们会这么做,在view中生成相应html tag:

  然后,在controller中,我们可以通过params[:my_uploaded_file]得到上传文件,进行相应处理。
  假如,现在作为controller的开发人员,我不知道view开发人员将input的name设置成什么?那应该如何处理呢?可能这个例子有些极端,绝大部分时候不存在这个问题。那再假如,现在我们要利用rails实现一个api,该api的功能是实现文件上传,rails后台得到该文件,并进行处理。由于这是一个开放api,用户可能通过jsp,rails,php,甚至桌面程序等来访问我们的api,这个时候又该如何处理呢?(当然,你可以在api specification中定死,说,我的地盘我做主,你们要使用我的api上传文件,form-data的name必须是XXX!)
  在asp.net中倒是可以很简单的解决这个问题,我记得asp.net中的form提供了一个参数,开发人员可以直接访问该参数得到此form中所有上传的文件,而不必管name是什么。但是在rails中,似乎没有这一机制(如果是我疏忽,望知情同学告知!)。不过,现在稍加修改,rails也同样能象asp.net那样处理上传文件。
  首先,我先贴出解决方案,为方便起见,在environment.rb中加入如下代码:

module ActionController
  class AbstractRequest
    class << self
      alias_method :_original_read_multipart_, :read_multipart
      
      def read_multipart(body, boundary, content_length, env)
        params = _original_read_multipart_(body, boundary, content_length, env)
        file_data = params.values.dup.flatten.select do |form_data| 
          form_data.respond_to?(:original_path) && !form_data.original_path.blank?
        end
        params.merge!({"file_data[]" => file_data})
      end
      
      private :_original_read_multipart_, :read_multipart
    end
  end
end
 

  我先大致解释下这段代码:在view中的某个form中,当你加入{:multipart => true}参数,rails会调用AbstractRequest中的私有类方法read_multipart,得到form中所有的参数。所以,我的解决方案就是截获此方法,调用原始版本后,遍历form中的所有值,将所有上传的文件放到一个file_data中去,塞回params中。这样,在controller中,就可以通过params[:file_data]得到form中所有上传文件进行处理,而并不用理会input的name到底是什么。这样做,我们甚至可以处理这种情况:


 

  就算这两个input的名字相同,通过file_data,仍然可以得到两个文件。这样,controller得到上传文件的代码便独立了,和view没有任何关系,你爱咋写name都行。另外,这行代码有两个地方值得注意:

params.merge!({"file_data[]" => file_data})

  1. 关于key,不能想当然的用:file_data[]。因为rails内部处理参数的时候,调用了key.include?,所以,使用Symbol必然出错。
  2. [] 是必须的,否则rails不能将所有的上传文件塞到一个数组中去,这样,在controller中,你将只能通过file_data得到其中一个文件。
  另外,上述代码是基于rails 2.0.2。加入你目前正在使用rails 1.*。则可以使用如下代码实现相同的功能:

class CGI #:nodoc:
  module QueryExtension
    
    alias_method :_original_read_multipart_, :read_multipart
    def read_multipart(boundary, content_length)
      params = _original_read_multipart_(boundary, content_length)
      file_data = params.values.dup.flatten.select do |form_data| 
        form_data.respond_to?(:original_path) && !form_data.original_path.blank?
      end
      params.merge!({"file_data[]" => file_data})
    end

    private :_original_read_multipart_, :read_multipart
  end
end
 

  2008.7.19  22:22  星期六

你可能感兴趣的:(Rails,ASP.net,ASP,.net,PHP)