按照项目需求,需要实现从本地上传文件到rails服务器的功能。我们选择使用carrierwave来实现这一功能。
github:https://github.com/carrierwaveuploader/carrierwave
开发前需要明确的问题
在开发时,我们不能盲目的去实现功能。为此,在实践之前,我们需要提出一些问题来引导我们解决问题:
- 整个文件上传的流程是怎样的?
- carrierwave 扮演了流程中的什么角色?
- carrierwave 提供了哪些上传文件的接口?
- 我们需要实现那些功能来完成整个系统?
提出这些问题,是为了让自己能够更深刻地理解整个系统的工作,在整体上把握,而不仅仅是把功能实现。
问题解决
rails文件上传流程
我们需要结合rails 服务器的MVC框架来分析文件上传的整个流程。
我们需要将本地的文件上传到服务器,那么首先我们需要一个界面(view层),这个界面上提供读取本地文件和上传文件的功能。浏览器通过发送http请求来访问服务器,我们需要上传的文件信息也应该存在请求中。
http请求通过路由(routing)选择特定的控制器(controller),执行特定的动作(action)来获取http请求中的文件信息,获取到信息后我们即可执行将文件存储到特定位置的操作了,但MVC框架告诉我们不应该这么做,数据逻辑和业务逻辑都应该放在model中执行。
我们还需要一个model来进行业务逻辑的实现,获得的信息我们既可以存到数据库中,也可以存到服务器的任意位置。这里,我们需要将文件存储到服务器的特定位置,而不需要存储到数据库中。因此,我们的model实际是不需要对应table(表)的,只需要在model层实现存储文件于特定位置的功能即可。
carrierwave 在流程中的实现
理解了rails服务器中文件上传的流程,我们再来看看carrierwave能为我们实现什么功能。我们来看github中的描述:
Getting Started
Start off by generating an uploader:
rails generate uploader Avatar
this should give you a file in:
app/uploaders/avatar_uploader.rb
从中我们可以看到,carrierwave为我们实现的是model层的功能,也就是实现的是将文件存储到rails服务器中的功能。
carrierwave 提供的部分重要接口
- 文件存储接口:
uploader = AvatarUploader.new
uploader.store!(my_file)
- 文件存储位置接口:
class Avatar < CarrierWave::Uploader::Base
def store_dir
'public/my/upload/directory'
end
end
- 临时文件存储位置接口:
class Avatar < CarrierWave::Uploader::Base
def cache_dir
'/tmp/projectname-cache'
end
end
- 文件格式白名单接口(不设置时允许所有类型格式的文件):
class Avatar < CarrierWave::Uploader::Base
def extension_whitelist
%w(jpg jpeg gif png)
end
end
我们需要实现的功能
根据上面的分析,我们发现carrierwave已经帮我们实现了model层的内容,并为我们提供了重要的接口,我们需要做的就是实现view层和controller层的功能,使上传文件的整个系统能够正常运行。下面,我们就来完成整个系统。
上传文件系统开发流程
我们会用一个非常简单的demo来说明上传文件的流程,这个demo实现了上传文件的最基本的功能,虽然很简单,但每一步都是不可或缺的,缺少任何一个部分,都会导致上传文件的失败。
1. 安装carrierwave(Gemfile.rb
)
gem 'carrierwave', '>= 1.0.0.beta', '< 2.0'
2. 生成uploader(model层)
rails generate uploader Myuploader
3. 生成文件:app/uploaders/myuploader_uploader.rb
,在这个文件中定义了存储文件位置等接口。现在我们需要在view层添加接口
<%= form_tag 'mystore', :multipart => true do%>
<%= file_field_tag :localfile %>
<%= submit_tag "上传"%>
<% end %>
我们在页面中添加一个最小的接口单元,在 form_tag
中添加 :multipart => true
即可实现文件上传,否则会导致无法上传。
第一行中 'mystore'
是指提交按钮发送请求(post)的url,这是一个相对路径,具体内容请复习表单内容。
4. 接着我们需要通过controller来连通整个系统。一方面,controller需要获得提交的http请求中包含的有效的文件信息,另一方面,需要将获得的信息送到model中进行处理。在rails的控制器文件中(*_controller.rb
)添加:
def mystore
up_file = params[:localfile]
myfile = MyuploaderUploader.new
myfile.store!(up_file)
end
注意,`submit_tag` 发送http请求时的方式是post,因此需要在 `routes.rb` 文件中添加post方法。
通过上述四个步骤,我们便已经成功地实现了本地文件上传rails服务器的功能。
如果需要修改文件存储的位置,文件属性等问题,在 app/uploaders/myuploader_uploader.rb
文件中修改对应的参数即可。
其中,给上传文件配置合理文件名是很重要的事情,这里采用根据文件内容进行MD5加密来给文件命名的方法:
def filename
if original_filename
@name ||= Digest::MD5.hexdigest(File.open(current_path, "rb") { |f| "#{f.read}" })
"#{@name}.#{file.extension}"
end
end
这样,整个系统最基本的功能便实现了,我们可以愉快地上传文件了。
总结
carrierwave着实让文件上传变得轻松且愉快,虽然写了这么多,但实际需要自己写的东西是十分少的。然而在自己刚开发时,走了很多弯路,总结起来还是太急功近利了,为了实现功能而实现功能,没有很好地理解文件上传流程,也没有从整体上把握,最终自己给自己挖下了很多坑。
这篇blog写了这么多,不仅仅是介绍carrierwave这个gem包的开发,更重要的是为自己总结gem包开发时的思路:把握整体,弄清系统框架,了解gem包实现的功能,明确gem提供的接口,注重细节,才能更有效地去做rails gem开发。