程序中一个常见的任务是上传某种文件,可以是用户的照片,或者 CSV 文件包含要处理的数据。处理文件上传功能时有一点要特别注意,表单的编码必须设为 "multipart/form-data"
。如果使用 form_for
生成上传文件的表单,Rails 会自动加入这个编码。如果使用 form_tag
就得自己设置,如下例所示。
下面这两个表单都能用于上传文件:
<%=
form_tag({action:
:upload
}, multipart:
true
)
do
%>
<%=
file_field_tag
'picture'
%>
<%
end
%>
<%=
form_for
@person
do
|f|
%>
<%=
f.file_field
:picture
%>
<%
end
%>
|
像往常一样,Rails 提供了两种帮助方法:独立的 file_field_tag
方法和处理模型的 file_field
方法。这两个方法和其他帮助方法唯一的区别是不能为文件选择框指定默认值,因为这样做没有意义。正如你所期望的,file_field_tag
方法上传的文件在 params[:picture]
中,file_field
方法上传的文件在 params[:person][:picture]
中。
5.1 上传了什么
存在 params
Hash 中的对象其实是 IO
的子类,根据文件大小,可能是 StringIO
或者是存储在临时文件中的 File
实例。不管是哪个类,这个对象都有 original_filename
属性,其值为文件在用户电脑中的文件名;还有个 content_type
属性,其值为上传文件的 MIME 类型。下面这段代码把上传的文件保存在 #{Rails.root}/public/uploads
文件夹中,文件名和原始文件名一样(假设使用前面的表单上传)。
def
upload
uploaded_io = params[
:person
][
:picture
]
File
.open(Rails.root.join(
'public'
,
'uploads'
, uploaded_io.original_filename),
'wb'
)
do
|file|
file.write(uploaded_io.read)
end
end
|
文件上传完毕后可以做很多操作,例如把文件存储在某个地方(服务器的硬盘,Amazon S3 等);把文件和模型关联起来;缩放图片,生成缩略图。这些复杂的操作已经超出了本文范畴。有很多代码库可以协助完成这些操作,其中两个广为人知的是 CarrierWave 和 Paperclip。
如果用户没有选择文件,相应的参数为空字符串。
5.2 使用 Ajax 上传文件
异步上传文件和其他类型的表单不一样,仅在 form_for
方法中加入 remote: true
选项是不够的。在 Ajax 表单中,使用浏览器中的 JavaScript 进行序列化,但是 JavaScript 无法读取硬盘中的文件,因此文件无法上传。常见的解决方法是使用一个隐藏的 iframe
作为表单提交的目标。