Skip to content

Ruby on Rails - 文件上传

Ruby on Rails - 使用 Active Storage 进行现代文件上传

Section titled “Ruby on Rails - 使用 Active Storage 进行现代文件上传”

许多 Web 应用程序都需要用户上传文件(例如,个人资料图片、文档等)。Rails 提供了一个内置解决方案,称为 Active Storage (活动存储),用于处理文件上传以及将文件附加到 Active Record 模型。Active Storage 支持将文件存储在本地磁盘(用于开发/测试)以及各种云存储服务(如 Amazon S3、Google Cloud Storage、Microsoft Azure Storage)上(用于生产环境)。

让我们构建一个简单的 Rails 应用程序来上传简历。

步骤 1:创建新的 Rails 应用程序

Section titled “步骤 1:创建新的 Rails 应用程序”
$ rails new resume_uploader_app
$ cd resume_uploader_app

Active Storage 需要数据库表来存储上传文件的元数据。运行安装器:

$ bin/rails active_storage:install

此命令会生成一个数据库迁移文件。现在,运行迁移:

$ bin/rails db:migrate

这将创建 active_storage_blobs 和 active_storage_attachments 表。

打开 config/storage.yml 文件。默认情况下,它配置为在开发和测试环境中使用本地磁盘存储:

test:
service: Disk
root: <%= Rails.root.join("tmp/storage") %>
local:
service: Disk
root: <%= Rails.root.join("storage") %>

在 config/environments/development.rb(以及 production.rb)中,指定要使用的服务:

# In config/environments/development.rb
Rails.application.configure do
# ... other configurations ...
config.active_storage.service = :local
end

对于生产环境,您会在此处配置一个云服务,如 Amazon S3。

让我们生成一个 Resume 模型和一个 ResumesController:

$ bin/rails generate model Resume name:string
$ bin/rails db:migrate
$ bin/rails generate controller Resumes index new create destroy --no-helper --no-assets --no-test-framework

修改 Resume 模型 (app/models/resume.rb),声明一个附件:

class Resume < ApplicationRecord
has_one_attached :attachment
validates :name, presence: true
validates :attachment, presence: true # Ensure a file is uploaded
# For more specific validations (content type, size):
# validates :attachment, content_type: ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
# size: { less_than: 5.megabytes , message: 'file is too large (max 5MB)' }
end

has_one_attached :attachment 告诉 Active Storage,一个 Resume 可以有一个附加文件,该文件被引用为 attachment。

更新 config/routes.rb:

Rails.application.routes.draw do
resources :resumes, only: [:index, :new, :create, :destroy]
root "resumes#index"
end

编辑 app/controllers/resumes_controller.rb:

class ResumesController < ApplicationController
def index
@resumes = Resume.all
end
def new
@resume = Resume.new
end
def create
@resume = Resume.new(resume_params)
if @resume.save
redirect_to resumes_path, notice: "The resume '#{@resume.name}' has been uploaded."
else
render :new, status: :unprocessable_entity
end
end
def destroy
@resume = Resume.find(params[:id])
@resume.destroy
redirect_to resumes_path, notice: "The resume '#{@resume.name}' has been deleted.", status: :see_other
end
private
def resume_params
params.require(:resume).permit(:name, :attachment)
end
end

注意,:attachment 在 resume_params 中被允许了。

创建 app/views/resumes/index.html.erb:

<h1>Resumes</h1>
<% if flash[:notice].present? %>
<p style="color: green;"><%= flash[:notice] %></p>
<% end %>
<%= link_to "Upload New Resume", new_resume_path %>
<hr>
<% if @resumes.any? %>
<table>
<thead>
<tr>
<th>Name</th>
<th>Attachment</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<% @resumes.each do |resume| %>
<tr>
<td><%= resume.name %></td>
<td>
<% if resume.attachment.attached? %>
<%= link_to resume.attachment.filename, url_for(resume.attachment), target: "_blank" %>
(<%= number_to_human_size(resume.attachment.blob.byte_size) %>)
<% else %>
No attachment
<% end %>
</td>
<td><%= button_to "Delete", resume_path(resume), method: :delete, data: { turbo_confirm: "Are you sure?" } %></td>
</tr>
<% end %>
</tbody>
</table>
<% else %>
<p>No resumes uploaded yet.</p>
<% end %>

创建 app/views/resumes/new.html.erb:

<h1>Upload New Resume</h1>
<%= form_with(model: @resume, local: true) do |form| %>
<% if @resume.errors.any? %>
<div style="color: red;">
<h2><%= pluralize(@resume.errors.count, "error") %> prohibited this resume from being saved:</h2>
<ul>
<% @resume.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div>
<%= form.label :name %><br>
<%= form.text_field :name %>
</div>
<div>
<%= form.label :attachment %><br>
<%= form.file_field :attachment %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
<%= link_to "Back to resumes", resumes_path %>

文件上传的关键部分是 form.file_field :attachment。这里使用了 form_with(model: @resume, local: true);对于 Active Storage 的直接上传(到云存储),根据设置可能会省略 local: true。

  1. 启动服务器:$ bin/rails server
  2. 访问 http://localhost:3000。
  3. 点击 “Upload New Resume”(上传新简历)。页面将显示一个表单,包含 ‘Name’(名称)和 ‘Attachment’(附件,一个文件输入框)字段。
  4. 输入名称,选择文件,然后点击 “Create Resume”(创建简历,或提交按钮上的任何文本)。
  5. 如果成功,您将被重定向到索引页,您上传的简历将列在其中。文件名将是一个链接,用于查看/下载文件。您还会看到文件大小。
  6. 如果存在验证错误(例如,未提供名称,或者您在模型中添加了文件类型/大小限制),‘new’ 页面将重新渲染并显示错误消息。例如,如果您将名称留空,会看到 “Name can’t be blank”(名称不能为空)。如果您上传的文件过大(如果启用了大小验证),可能会看到 “Attachment file is too large”(附件文件太大)。

Active Storage 提供了一种强大且集成的方式来处理 Rails 中的文件上传,简化了这项常见但复杂的任务。