博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
rails文件上传_使用Rails和ActionCable上传文件
阅读量:2511 次
发布时间:2019-05-11

本文共 17132 字,大约阅读时间需要 57 分钟。

rails文件上传

This is the second part of the tutorial that explains how to enable real-time communication and file uploading with Rails and . In the we have created a Rails 5.1 chatting application powered by (for authentication), ActionCable (for real-time updates) and (for broadcasting). In this part you will learn how to integrate the and utilize the to enable file uploading over .

这是本教程的第二部分,介绍了如何使用Rails和启用实时通信和文件上传。 在我们创建了一个由 (用于身份验证),ActionCable(用于实时更新)和 (用于广播)支持的Rails 5.1聊天应用程序。 在这一部分中,您将学习如何集成和如何使用启用通过上传文件。

As a result, you will get an application similar to this one:

The source code for the tutorial is available at
.

结果,您将获得与此应用程序类似的应用程序: 。

( )

First things first: we need a solution to enable file uploading functionality for our application. There are a handful of gems that can solve this task, including , and , but I'd suggest sticking with a solution called that I've stumbled upon a couple of months ago. I like this gem because of its modular approach and vast array of supported features: it works with ActiveRecord and Sequel, supports Rails and non-Rails environments, provides background processing and more. Another very important thing is that this gem enables easy uploading of files sent as which is cruical for our today's task (later you will see why). You may read by Janko Marohnić, author of the gem, explaining the motivation behind creating Shrine. By the way, I wanted to thank Janko for helping me out and answering some questions.

首先,我们需要一个解决方案来为我们的应用程序启用文件上传功能。 有很多宝石可以解决此任务,包括 , 和 ,但我建议坚持使用几个月前偶然发现的名为的解决方案。 我喜欢这个gem,因为它的模块化方法和受支持的功能广泛:它与ActiveRecord和Sequel一起使用,支持Rails和non-Rails环境,提供后台处理等等。 另一个非常重要的事情是,该gem可以轻松上传作为发送的文件,这对于我们今天的任务至关重要(稍后您将了解原因)。 您可以阅读宝石作者JankoMarohnić的 ,其中解释了创建神社的动机。 顺便说一句,我要感谢Janko帮助我并回答了一些问题。

So, you know what to do—drop a new gem into the Gemfile:

因此,您知道该怎么做–将新的gem放入Gemfile中

gem'shrine', '~> 2.6'

Then run:

然后运行:

bundleinstall

Next you will require at least basic setup for Shrine that is stored inside an initializer file:

接下来,您将至少需要对Shrine进行基本设置,并将其存储在初始化程序文件中:

# config/initializers/shrine.rbrequire "shrine" # corerequire "shrine/storage/file_system" # plugin to save files using file systemShrine.storages = {
cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"), store: Shrine::Storage::FileSystem.new("public", prefix: "uploads/store"),}Shrine.plugin :activerecord # enable ActiveRecord support

Here we simply provide paths where the uploaded files and cached data will be stored. Also, we enable support for ActiveRecord by using . Most of the Shrine's functionality is packed in a form plugins, and you can configure a totally different setup by using the ones listed on the .

在这里,我们仅提供将存储上传文件和缓存数据的路径。 此外,我们通过使用启用对ActiveRecord的支持。 Shrine的大多数功能都包装在一个表单插件中,您可以使用上列出的设置来配置完全不同的设置。

Please note that it is probably a good idea to exclude public/uploads directory from version control to avoid pushing images uploaded for testing purposes to GitHub. Add the following line to the .gitignore file:

请注意,从版本控制中排除public / uploads目录可能是个好主意,以避免将出于测试目的上传的图像推送到GitHub。 将以下行添加到.gitignore文件中:

public/uploads

An attachment attribute is required for our model as well. It should have a name of <some_name>_data and a text type. This attribute stores all the information about the uploaded file, so you don't have to add separate fields for a name, metadata etc. Create and apply a new migration:

模型的附件属性也是必需的。 它的名称应为<some_name>_datatext类型。 此属性存储有关上载文件的所有信息,因此您不必为名称,元数据等添加单独的字段。创建并应用新的迁移:

rails g migration add_attachment_data_to_messages attachment_data:textrails db:migrate

All attachment-specific logic and individual plugins are listed inside the uploader class:

所有特定于附件的逻辑和各个插件均在uploader类中列出:

class AttachmentUploader < Shrine  # app/uploaders/attachment_uploader.rbend

Later we will hook up some necessary plugins inside this class. Now the uploader can be included in the model:

稍后,我们将在此类中连接一些必要的插件。 现在可以将上传器包含在模型中:

# models/message.rbinclude AttachmentUploader[:attachment]

Note that the same uploader may be included for multiple models.

请注意,多个模型可能包含相同的上传器。

添加文件验证 (Adding File Validations)

Before proceeding, let's also secure our uploads a bit. Currently any file with any size is accepted which is not particularly great, so we are going to introduce some restrictions. This can be done using the . I will define validations globally inside the initializer, but this can also be done per-uploader:

在继续之前,我们还需要确保上传的内容安全。 当前,任何大小的文件都可以被接受,这并不是特别好,因此我们将引入一些限制。 这可以使用来完成。 我将在初始化程序中全局定义验证,但是也可以按每个上载器进行验证:

# config/initializers/shrine.rbShrine.plugin :determine_mime_type # check MIME TYPEShrine.plugin :validation_helpers, default_messages: {
mime_type_inclusion: ->(whitelist) {
# you may use whitelist variable to display allowed types "isn't of allowed type. It must be an image." }}Shrine::Attacher.validate do validate_mime_type_inclusion [ # whitelist only these MIME types 'image/jpeg', 'image/png', 'image/gif' ] validate_max_size 1.megabyte # limit file size to 1MBend

This code is pretty much self-explaining. Apart from validation helpers, we use that, well, checks MIME types for the uploaded files based on their content. The default analyzer for this plugin is which is not available on Windows by default. You may use some third-party solution instead (or even multiple solutions at once) as . Do test, however, that all whitelisted file types are really accepted because recently I had troubles when uploading .txt files with set as an analyzer.

此代码几乎是不言自明的。 除了验证帮助程序之外,我们还使用 ,该根据文件的内容检查MIME类型。 此插件的默认分析器是 ,默认情况下Windows上不提供该 。 如您可以改用某些第三方解决方案(甚至一次使用多个解决方案)。 但是,请进行测试以确保所有列入白名单的文件类型都确实被接受,因为最近我在上载带有设置为分析器的.txt文件时遇到了麻烦。

Next, inside the Shrine::Attacher.validate do we allow PNG, JPEG and GIF images to be uploaded and restrict their size to 1 megabyte.

接下来,在Shrine::Attacher.validate do内部,我们允许上传PNG,JPEG和GIF图像并将其大小限制为1 MB。

Lastly, I'd like to change our validation rule for the body attribute inside the Message model. Currently it looks like

最后,我想更改Message模型中body属性的验证规则。 目前看起来像

# models/message.rbvalidates :body, presence: true

but I'd like to modify it:

但我想修改它:

# models/message.rbvalidates :body, presence: true, unless: :attachment_data

This way we allow messages to have no text if they have an attached file. Great!

这样,如果邮件具有附件,我们将允许它们不包含任何文本。 大!

( )

观看次数 (Views)

Shrine is now integrated into the application and we can proceed to the client-side and add a file field to the form rendered at the views/chats/index.html.erb view:

Shrine现在已集成到应用程序中,我们可以继续进行客户端操作,并将文件字段添加到在views / chats / index.html.erb视图中呈现的表单中:

<%= form_with url: '#', html: {id: 'new-message'} do |f| %>  <%= f.label :body %>  <%= f.text_area :body, id: 'message-body' %>
<%= f.file_field :attachment, id: 'message-attachment' %>
Only PNG, JPG and GIF images are allowed
<%= f.submit %><% end %>

Remember that the form_with helper does not add any ids automatically, so I've specified them explicitly.

请记住, form_with帮助器不会自动添加任何ID,因此我已明确指定了它们。

Also let's tweak the views/messages/_message.html.erb partial to display an attachment if it is present:

另外,让我们调整views / messages / _message.html.erb部分以显示附件(如果存在):

<%= message.user.email %> says: <%= message.body %>
at <%= l message.created_at, format: :short %> <% if message.attachment_data? %>
<%= link_to "View #{message.attachment.original_filename}", message.attachment.url, target: '_blank' %> <% end %>

The link will simply display the file's original name and open the attachment in a new tab when clicked. Your page should now look like this:

链接将仅显示文件的原始名称,并在单击时在新选项卡中打开附件。 您的页面现在应如下所示:

CoffeeScript (CoffeeScript)

Great, the views are prepared as well and we can write some CoffeeScript. It may be a good idea to make yourself a coffee because this is where things start to get a bit more complex. But fear not, I'll proceed slowly and explain what is going on.

太好了,视图也已经准备好了,我们可以编写一些CoffeeScript。 自己煮一杯咖啡可能是个好主意,因为这会使事情变得更加复杂。 但是不要担心,我会慢慢进行并解释发生了什么。

The first thing to mention is that we have to send the chosen file over web sockets along with the entered message. Therefore, the file has to be processed with JavaScript. Luckily, there is a available that allows us to read the contents of files. It works with all major browsers, including support for IE 10+ (by the way, web sockets ).

首先要提到的是,我们必须通过Web套接字将所选文件与输入的消息一起发送。 因此,该文件必须使用JavaScript处理。 幸运的是,有一个可以使我们读取文件的内容。 它适用于所有主流浏览器,包括对IE 10+的支持(顺便说一句, Web套接字)。

To start off, let's change the condition inside the submit event handler. Here is its current version:

首先,让我们更改submit事件处理程序中的条件。 这是它的当前版本:

# app/assets/javascripts/channels/chat.coffee# ...$new_message_form.submit (e) ->  $this = $(this)  message_body = $new_message_body.val()  if $.trim(message_body).length > 0    App.chat.send_message message_body  e.preventDefault()  return false

I want to send the message if it has some text or if an attachment is selected (remember that we've added server-side validation inside the Message model that follows the same principle). Therefore, the condition turns to:

我想发送包含某些文本或选择了附件的消息(请记住,我们已经在遵循相同原理的Message模型内添加了服务器端验证)。 因此,条件变为:

# We check that either the body has some contents or a file is chosenif $.trim(message_body).length > 0 or $new_message_attachment.get(0).files.length > 0

Now, if the attachment is chosen, we need to instantiate a new FileReader object, so add another condition inside:

现在,如果选择了附件,我们需要实例化一个新的FileReader对象,因此在其中添加另一个条件:

# ...if $.trim(message_body).length > 0 or $new_message_attachment.get(0).files.length > 0    if $new_message_attachment.get(0).files.length > 0 # if file is chosen      reader = new FileReader()  # use FileReader API      file_name = $new_message_attachment.get(0).files[0].name # get the name of the first chosen file    else      App.chat.send_message message_body

Here I am also storing the original file name as it has to be sent separately—there will be no way to determine it on the server-side otherwise.

我还在这里存储原始文件名,因为它必须单独发送-否则无法在服务器端确定它。

The reader can now be used to carry out its main purpose: read the contents of a file. We'll use the that returns a base64 encoded string:

现在可以使用reader执行其主要目的:读取文件的内容。 我们将使用 ,该返回base64编码的字符串:

if $.trim(message_body).length > 0 or $new_message_attachment.get(0).files.length > 0    if $new_message_attachment.get(0).files.length > 0 # if file is chosen      reader = new FileReader()       file_name = $new_message_attachment.get(0).files[0].name      reader.readAsDataURL $new_message_attachment.get(0).files[0] # read the chosen file    else      App.chat.send_message message_body

Of course, reading a file may take some time, therefore we must wait for this process to finish before sending the message. This can be done with the help of the that is fired as soon as the file is read. Inside this callback we have access to the reader.result that contains the actual base64 encoded string. So, apply this knowledge to practice:

当然,读取文件可能需要一些时间,因此在发送消息之前,我们必须等待此过程完成。 可以在读取文件后立即触发的的帮助下完成此操作。 在此回调中,我们可以访问reader.result ,其中包含实际的base64编码的字符串。 因此,将这些知识应用于实践:

if $.trim(message_body).length > 0 or $new_message_attachment.get(0).files.length > 0    if $new_message_attachment.get(0).files.length > 0       reader = new FileReader()       file_name = $new_message_attachment.get(0).files[0].name      reader.addEventListener "loadend", -> # perform the following action after the file is loaded        App.chat.send_message message_body, reader.result, file_name       reader.readAsDataURL $new_message_attachment.get(0).files[0] # read file in base 64 format    else      App.chat.send_message message_body

That's pretty much it. Our file reader is finished!

就是这样。 我们的文件阅读器完成了!

The send_message function requires some changes as we wish to pass the file's contents and the original filename to it:

send_message函数需要进行一些更改,因为我们希望将文件的内容和原始文件名传递给它:

# ...send_message: (message, file_uri, original_name) -># send the message to the server along with file in base64 encoding         @perform 'send_message', message: message, file_uri: file_uri, original_name: original_name

All that is left to do is enable support for base64 encoded strings on the server-side, so let's move on to the next section.

剩下要做的就是在服务器端启用对base64编码字符串的支持,因此让我们继续下一节。

( )

At the beginning of this tutorial I mentioned that Shrine supports uploading files in the form of data URIs. We're at the point where it becomes really important, as we need to process the base64 encoded string sent by the client and save the file properly.

在本教程的开头,我提到Shrine支持以数据URI形式上传文件。 我们正变得非常重要,因为我们需要处理客户端发送的base64编码的字符串并正确保存文件。

To achieve this, enable the for the uploader (though this can also be done globally inside the initializer):

为此,请为上载器启用 (尽管这也可以在初始化程序中全局进行):

# uploaders/attachment_uploader.rbplugin :data_uri

It appears that having this plugin in place, uploading files as data URIs means simply assigning the proper string to the attachment_data_uri attribute:

看来,有了此插件,将文件作为数据URI上传就意味着只需将正确的字符串分配给attachment_data_uri属性即可:

# channels/chat_channel.rbdef send_message(data)    message = current_user.messages.build(body: data['message'])    if data['file_uri']      message.attachment_data_uri = data['file_uri']    end    message.saveend

That's pretty much it, the uploading should now be carried out properly. Note that we do not have to make any changes to the after_create_commit callback or to the background job.

差不多了,现在应该正确执行上传了。 注意,我们不必对after_create_commit回调或后台作业进行任何更改。

存储原始文件名 (Storing the Original Filename)

Another thing we need to take care of is storing the original file's name. Strictly speaking, it is not required, but this way users will be able to quickly understand which file they've received (including its original extension).

我们需要注意的另一件事是存储原始文件的名称。 严格来说,这不是必需的,但是通过这种方式,用户将能够快速了解​​他们收到的文件(包括其原始扩展名)。

The original name can be stored inside the attachment's metadata, so another is needed. As the name implies, it allows to provide or extract meta information from the uploaded file. This process is performed inside the uploader or the initializer. At this point, however, we do not have access to the original filename sent by the client anymore:

原始名称可以存储在附件的元数据中,因此需要另一个 。 顾名思义,它允许从上载的文件中提供或提取元信息。 此过程在上载器或初始化器中执行。 但是,在这一点上,我们不再可以访问客户端发送的原始文件名:

# uploaders/attachment_uploader.rbplugin :add_metadataadd_metadata do |io, context|    {
'filename' => ???}end

Luckily, we do have access to the context (that is, the actual record), so the filename can be saved inside the virtual attribute called, for instance, attachment_name:

幸运的是,我们确实可以访问context (即实际记录),因此可以将文件名保存在称为attachment_name的虚拟属性中:

# uploaders/attachment_uploader.rbadd_metadata do |io, context|    {
'filename' => context[:record].attachment_name}end

All we need to do is set this virtual attribute:

我们需要做的就是设置这个虚拟属性:

# channels/chat_channel.rbdef send_message(data)    message = current_user.messages.build(body: data['message'])    if data['file_uri']      message.attachment_name = data['original_name']      message.attachment_data_uri = data['file_uri']    end    message.saveend

Don't forget to define the getter and setter for this attribute inside the model (or use attr_accessor):

不要忘记在模型中为此属性定义getter和setter(或使用attr_accessor ):

# models/mesage.rbdef attachment_name=(name)    @attachment_name = nameenddef attachment_name    @attachment_nameend

And—guess what—we are done! You may now boot the server and try to upload some image (remember that we've restricted its size to 1MB though). The link to the file should be displayed under the message. Note that the original filename is preserved:

You will see an output in the terminal similar to this one:

并且-猜怎么着-我们完成了! 现在,您可以启动服务器并尝试上传一些图像(请记住,尽管我们将其大小限制为1MB)。 该文件的链接应显示在消息下方。 请注意,原始文件名被保留:

然后,该消息将广播到所有客户端:

( )

We've come to the end of the tutorial. The chat application is finished and the user can now communicate and exchange files with ease in real-time which is quite convenient. In this tutorial you have learned how to:

我们到了教程的结尾。 聊天应用程序完成后,用户现在可以轻松,实时地交流和交换文件,这非常方便。 在本教程中,您学习了如何:

  • Integrate Shrine gem to enable file uploads

    集成Shrine gem以启用文件上传
  • Add file validations

    添加文件验证
  • Work with Shrine's plugins

    使用Shrine的插件
  • Utilize FileReader Web API

    利用FileReader Web API
  • Process files as Data URIs

    将文件作为数据URI处理
  • Store file's metadata

    存储文件的元数据

Of course, there is more to this app that can be done: for example, it would be great to display validation errors if the message cannot be saved. However, this will be left for you as an exercise. All in all, this task is not that complex: you can, for example, set a condition inside the ChatChannel#send_message and send an error back to the author if the saving of the message failed.

当然,此应用程序还有很多可以做的事情:例如,如果无法保存消息,则显示验证错误会很棒。 但是,这将留给您作为练习。 总而言之,此任务并不那么复杂:例如,您可以在ChatChannel#send_message内设置一个条件,并在消息保存失败时将错误发送给作者。

Share your solutions in the comments and don't hesitate to post your questions as well. I thank you for staying with me and happy coding!

在评论中分享您的解决方案,也不要犹豫发表您的问题。 感谢您与我在一起并祝您编程愉快!

翻译自:

rails文件上传

转载地址:http://nhuwd.baihongyu.com/

你可能感兴趣的文章
团队编程项目作业2-团队编程项目开发环境搭建过程
查看>>
Stax解析XML示例代码
查看>>
cookie
查看>>
二级图片导航菜单
查看>>
<Using parquet with impala>
查看>>
OpenGL渲染流程
查看>>
委托异步回调
查看>>
扩展欧几里得算法
查看>>
いつでもどこでも本格的に麻雀&チュートリアルが充実!iPhone/iPod touch/iPad向け「雀龍門Mobile」をiPadで遊んでみました...
查看>>
如何重置mysql中的root密码
查看>>
bzoj 3171: [Tjoi2013]循环格 最小费用最大流
查看>>
关于IO的一些数字
查看>>
高放的c++学习笔记之模板与泛型编程
查看>>
bzoj 1089: [SCOI2003]严格n元树
查看>>
mybatis 日期比较
查看>>
更新jdk
查看>>
string与StringBuilder之性能比较
查看>>
python3----练习题(购物车)
查看>>
IOS不错的学习资源特别是图片效果的处理上
查看>>
HDU 2072(字符串的流式操作,学习了)
查看>>