在Ruby on Rails 6中使用活动存储上传文件

在Ruby on Rails 6中使用活动存储上传文件

将文件上传到web应用程序是一个相当常见的功能。随着Rails 5的到来,活动存储作为Rails代码库的一部分被添加。在主动存储之前,文件上传功能通过添加Ruby gems(最著名的是CarrierWave、Silence或曲别针)添加到Rails应用程序中。
我最近花了几天时间,试图让文件上传在带有活动存储的Rails中作为一个完整的CRUD操作来工作。为了了解Active Storage的工作原理,我做了一些教程,并开发了几个小应用程序。和许多教程一样,有几个边缘案例没有被教程涵盖,所以我决定写这篇文章。如果你打算在Rails应用程序中添加文件上传,希望这能对你有所帮助。

将文件上载到Rails中的基本博客帖子
为了演示活动存储的工作原理,让我们构建一个带有附件的简单博客。我们将此示例称为active_博客。让我们创建我们的博客:

$ rails new active_blog
$ cd active_blog
$ rails active_storage:install
$ rails g scaffold Post title:string body:text
$ rails db:migrate
$ rails s

通过上传表单向我们的帖子添加附件
现在,我们有了一个基本的博客文章,通过脚手架,让我们深入到通过主动存储添加上传功能。
首先,让我们将数据库逻辑添加到Post模型中。使用活动存储,我们可以选择每篇文章附加一个文件或每篇文章附加多个文件。根据我们想要的,我们添加到Post模型中的语句略有不同:

在model 新建以下文件

class Post < ApplicationRecord
  has_one_attached :header_image   # Use has_one_attached for only one file allowed
  has_many_attached :files         # Use has_many_attached for multiple files allowed
end

在controller 新建以下文件

class PostsController < ApplicationController
  before_action :set_post, only: %i[ show edit update destroy ]

  # GET /posts or /posts.json
  def index
    @posts = Post.all
  end

  # GET /posts/1 or /posts/1.json
  def show
  end

  # GET /posts/new
  def new
    @post = Post.new
  end

  # GET /posts/1/edit
  def edit
  end

  # POST /posts or /posts.json
  def create
    @post = Post.new(post_params)

    respond_to do |format|
      if @post.save
        format.html { redirect_to post_url(@post), notice: "Post was successfully created." }
        format.json { render :show, status: :created, location: @post }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /posts/1 or /posts/1.json
  def update
    respond_to do |format|
      if @post.update(post_params)
        format.html { redirect_to post_url(@post), notice: "Post was successfully updated." }
        format.json { render :show, status: :ok, location: @post }
      else
        format.html { render :edit, status: :unprocessable_entity }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /posts/1 or /posts/1.json
  def destroy
    @post.destroy

    respond_to do |format|
      format.html { redirect_to posts_url, notice: "Post was successfully destroyed." }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_post
      @post = Post.find(params[:id])
    end

    # Only allow a list of trusted parameters through.
    def post_params
      params.require(:post).permit(:title, :body)
    end

    def post_params
      params.require(:post).permit(:title, :body, :header_image)
    end
    
    # Use a Ruby symbol with brackets (array) for many attachments
    
    def post_params
      params.require(:post).permit(:title, :body, files: [])
    end
end

最后在views 写下

<%= form_with(model: post) do |form| %>
  <% if post.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(post.errors.count, "error") %> prohibited this post from being saved:</h2>

      <ul>
        <% post.errors.each do |error| %>
          <li><%= error.full_message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= form.label :title %>
    <%= form.text_field :title %>
  </div>

  <div class="field">
    <%= form.label :body %>
    <%= form.text_area :body %>
  </div>

  <div class="field">
    <%= form.label :header_image %>
    <%= form.file_field :header_image %>
  </div>

 # use this for many attachments, note we need multiple: true

  <div class="field">
    <%= form.label :files %>
    <%= form.file_field :files, multiple: true %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

显示上传的文件
要查看我们上传的文件,我们需要在Posts show页面上访问它们。在最简单的形式中,我们可以通过迭代文件并链接到它们来简单地链接到文件。这将是我们的许多文件上传:

<% @post.files.each do |file| %>
   <%= link_to file.filename, rails_blob_path(file, disposition: :attachment) %>
 <% end %>

注意:disposition::attachment参数会在单击文件时下载该文件。如果要在浏览器中打开它,请使用disposition::inline语句。
如果我们上传文件、保存帖子并查看帖子显示页面,我们将看到指向文件的链接。在本例中,这些是图像,但可以是任何文件类型

编辑上传的文件
对于我们附加的多个文件,如果编辑它们并上载新文件,默认行为是覆盖现有图像并用新图像替换它们。如果这是你想要做的,这很好。但是,如果你只是想在已经上传的现有文件中添加其他文件,该怎么办?
在默认的活动存储配置下,无法执行此操作。除了现有的文件之外,你还必须选择你想要上传的新文件,这样每次上传都会很麻烦。
这有个解决办法。在config/environments/development下添加以下:

require "active_support/core_ext/integer/time"

Rails.application.configure do
  # Settings specified here will take precedence over those in config/application.rb.

  # In the development environment your application's code is reloaded any time
  # it changes. This slows down response time but is perfect for development
  # since you don't have to restart the web server when you make code changes.
  config.cache_classes = false

  # Do not eager load code on boot.
  config.eager_load = false

  # Show full error reports.
  config.consider_all_requests_local = true

  # Enable/disable caching. By default caching is disabled.
  # Run rails dev:cache to toggle caching.
  if Rails.root.join('tmp', 'caching-dev.txt').exist?
    config.action_controller.perform_caching = true
    config.action_controller.enable_fragment_cache_logging = true

    config.cache_store = :memory_store
    config.public_file_server.headers = {
      'Cache-Control' => "public, max-age=#{2.days.to_i}"
    }
  else
    config.action_controller.perform_caching = false

    config.cache_store = :null_store
  end

  # Store uploaded files on the local file system (see config/storage.yml for options).
  config.active_storage.service = :local
  config.active_storage.replace_on_assign_to_many = false
  # Don't care if the mailer can't send.
  config.action_mailer.raise_delivery_errors = false

  config.action_mailer.perform_caching = false

  # Print deprecation notices to the Rails logger.
  config.active_support.deprecation = :log

  # Raise exceptions for disallowed deprecations.
  config.active_support.disallowed_deprecation = :raise

  # Tell Active Support which deprecation messages to disallow.
  config.active_support.disallowed_deprecation_warnings = []

  # Raise an error on page load if there are pending migrations.
  config.active_record.migration_error = :page_load

  # Highlight code that triggered database queries in logs.
  config.active_record.verbose_query_logs = true

  # Debug mode disables concatenation and preprocessing of assets.
  # This option may cause significant delays in view rendering with a large
  # number of complex assets.
  config.assets.debug = true

  # Suppress logger output for asset requests.
  config.assets.quiet = true

  # Raises error for missing translations.
  # config.i18n.raise_on_missing_translations = true

  # Annotate rendered view with file names.
  # config.action_view.annotate_rendered_view_with_filenames = true

  # Use an evented file watcher to asynchronously detect changes in source code,
  # routes, locales, etc. This feature depends on the listen gem.
  config.file_watcher = ActiveSupport::EventedFileUpdateChecker

  # Uncomment if you wish to allow Action Cable access from any origin.
  # config.action_cable.disable_request_forgery_protection = true
end

删除上传的文件
因为我们每个帖子只有一个标题图片,所以我们永远不需要删除这个图片。我们可以通过编辑标题图像并上传不同的图像来切换标题图像。
对于我们的许多附件,我们可能希望在某个时候删除其中一个甚至几个。因为删除活动存储附件超出了Rails中RESTful操作的默认约定,所以我们需要向posts控制器添加一个新操作,以及向路由添加一个新路。
首先,让我们在destroy方法下面的posts控制器中添加一个新操作,但首先是控制器中的所有私有方法。此操作称为delete_file,包含从数据库中删除活动存储附件所需的清除方法

 def delete_file
   file = ActiveStorage::Attachment.find(params[:id])
   file.purge
   redirect_back(fallback_location: posts_path)
 end
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值