使用Rails和Dragonfly上传文件

不久前,我写了一篇文章“使用Rails和Shrine上传文件 ”,其中介绍了如何借助Shrine gem将文件上传功能引入Rails应用程序。 但是,有很多类似的解决方案可用,而我最喜欢的之一是Dragonfly ,这是Mark Evans为Rails和Rack创建的易于使用的上传解决方案。

去年年初 ,我们讨论了这个库但与大多数软件一样,它有助于不时查看库,以了解更改的内容以及如何在应用程序中使用它。

在本文中,我将指导您完成蜻蜓的设置并解释如何利用其主要功能。 你将学到如何:

  • 将Dragonfly集成到您的应用程序中
  • 配置模型以与Dragonfly一起使用
  • 介绍基本的上传机制
  • 介绍验证
  • 生成图像缩略图
  • 执行文件处理
  • 存储上载文件的元数据
  • 准备要部署的应用程序

为了使事情变得更有趣,我们将创建一个小型音乐应用程序。 它将展示可以在网站上管理和播放的专辑和相关歌曲。

GitHub上提供了本文的源代码。 您还可以签出该应用程序的工作演示

列出和管理相册

首先,创建一个没有默认测试套件的新Rails应用程序:

rails new UploadingWithDragonfly -T

对于本文,我将使用Rails 5,但是大多数描述的概念也适用于旧版本。

创建模型,控制器和路径

我们的小型音乐网站将包含两个模型: AlbumSong 。 现在,让我们用以下字段创建第一个:

  • titlestring )-包含相册的标题
  • singerstring )—专辑的表演者
  • image_uidstring )—用于存储相册的预览图像的特殊字段。 您可以根据自己的喜好命名此字段,但必须按照Dragonfly文档的说明包含_uid后缀。

创建并应用相应的迁移:

rails g model Album title:string singer:string image_uid:string
rails db:migrate

现在,让我们创建一个非常通用的控制器,以使用所有默认操作来管理相册:

albums_controller.rb
class AlbumsController < ApplicationController
  def index
    @albums = Album.all
  end

  def show
    @album = Album.find(params[:id])
  end

  def new
    @album = Album.new
  end

  def create
    @album = Album.new(album_params)
    if @album.save
      flash[:success] = 'Album added!'
      redirect_to albums_path
    else
      render :new
    end
  end

  def edit
    @album = Album.find(params[:id])
  end

  def update
    @album = Album.find(params[:id])
    if @album.update_attributes(album_params)
      flash[:success] = 'Album updated!'
      redirect_to albums_path
    else
      render :edit
    end
  end

  def destroy
    @album = Album.find(params[:id])
    @album.destroy
    flash[:success] = 'Album removed!'
    redirect_to albums_path
  end

  private

  def album_params
    params.require(:album).permit(:title, :singer)
  end
end

最后,添加路线:

config / routes.rb
resources :albums

整合蜻蜓

是时候蜻蜓成为众人瞩目的时候了。 首先,将gem添加到Gemfile中

宝石文件
gem 'dragonfly'

跑:

bundle install
rails generate dragonfly

后面的命令将使用默认配置创建一个名为Dragonfly.rb的初始化程序。 我们暂时将其搁置一旁,但是您可以在Dragonfly的官方网站上阅读各种选择。

下一件重要的事情是为我们的模型配备Dragonfly方法。 这是通过使用dragonfly_accessor完成的:

型号/album.rb
dragonfly_accessor :image

请注意,这里我说的是:image它直接与我们在上一节中创建的image_uid列有关。 如果,例如,一个名为您的专栏photo_uid ,那么dragonfly_accessor方法将需要接受:photo作为参数。

如果您使用的是Rails 4或5,则另一个重要步骤是在控制器允许的范围内标记:image字段(不是:image_uid !):

albums_controller.rb
params.require(:album).permit(:title, :singer, :image)

差不多了,我们已经准备好创建视图并开始上传文件!

创建视图

从索引视图开始:

views / albums / index.html.erb
<h1>Albums</h1>

<%= link_to 'Add', new_album_path %>

<ul>
  <%= render @albums %>
</ul>

现在部分:

views / albums / _album.html.erb
<li>
  <%= image_tag(album.image.url, alt: album.title) if album.image_stored? %>
  <%= link_to album.title, album_path(album) %> by
  <%= album.singer %>
  | <%= link_to 'Edit', edit_album_path(album) %>
  | <%= link_to 'Remove', album_path(album), method: :delete, data: {confirm: 'Are you sure?'} %>
</li>

这里有两种蜻蜓方法要注意:

  • album.image.url返回图像的路径。
  • album.image_stored? 说明记录中是否有上传的文件。

现在添加新页面和编辑页面:

views / albums / new.html.erb
<h1>Add album</h1>

<%= render 'form' %>
views / albums / edit.html.erb
<h1>Edit <%= @album.title %></h1>

<%= render 'form' %>
views / albums / _form.html.erb
<%= form_for @album do |f| %>
  <div>
    <%= f.label :title %>
    <%= f.text_field :title %>
  </div>

  <div>
    <%= f.label :singer %>
    <%= f.text_field :singer %>
  </div>

  <div>
    <%= f.label :image %>
    <%= f.file_field :image %>
  </div>

  <%= f.submit %>
<% end %>

格式没什么花哨的,但是再次注意,在渲染文件输入时,我们说的是:image ,不是:image_uid

现在您可以启动服务器并测试上传功能!

移除影像

因此,用户能够创建和编辑相册,但是存在一个问题:他们无法删除图像,只能用另一张图像替换。 幸运的是,这很容易通过引入“删除图像”复选框来解决:

views / albums / _form.html.erb
<% if @album.image_thumb_stored? %>
    <%= image_tag(@album.image.url, alt: @album.title)  %>
    <%= f.label :remove_image %>
    <%= f.check_box :remove_image %>
<% end %>

如果相册中有关联的图像,我们将其显示并渲染一个复选框。 如果选中此复选框,则图像将被删除。 请注意,如果您的字段名为photo_uid ,则删除附件的相应方法将是remove_photo 。 很简单,不是吗?

唯一要做的另一件事是允许控制器中的remove_image属性:

albums_controller.rb
params.require(:album).permit(:title, :singer, :image, :remove_image)

添加验证

在这个阶段,一切工作都很好,但是我们根本没有检查用户的输入,这并不是特别好。 因此,让我们为专辑模型添加验证:

型号/album.rb
validates :title, presence: true
validates :singer, presence: true
validates :image, presence: true
validates_property :width, of: :image, in: (0..900)

validates_property是Dragonfly方法,可以检查附件的各个方面:您可以验证文件的扩展名,MIME类型,大小等。

现在,让我们创建一个通用部分以呈现发现的错误:

views / shared / _errors.html.erb
<% if object.errors.any? %>
  <div>
    <h4>The following errors were found:</h4>

    <ul>
      <% object.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
    </ul>
  </div>
<% end %>

在表单中使用此部分内容:

views / albums / _form.html.erb
<%= form_for @album do |f| %>
    <%= render 'shared/errors', object: @album %>
    <%# ... %>
<% end %>

样式化带有错误的字段以直观地描述它们:

样式表/application.scss
.field_with_errors {
  display: inline;
  label {
    color: red;
  }
  input {
    background-color: lightpink;
  }
}

在请求之间保留图像

引入验证后,我们遇到了另一个问题(很典型的情况,是吗?):如果用户在填写表单时犯了错误,则他或她将需要在单击“ 提交”按钮后再次选择文件。

蜻蜓还可以通过使用retained_*隐藏字段来帮助您解决此问题:

views / albums / _form.html.erb
<%= f.hidden_field :retained_image %>

别忘了也允许该字段:

albums_controller.rb
params.require(:album).permit(:title, :singer, :image, :remove_image, :retained_image)

现在,该图像将在请求之间保持不变! 但是,唯一的小问题是文件上传输入仍将显示“选择文件”消息,但是可以通过一些样式和少量JavaScript来解决。

处理图像

产生缩图

我们的用户上传的图像可能具有非常不同的尺寸,这可能(并且可能会)对网站的设计造成负面影响。 您可能希望将图像缩小到某些固定尺寸,当然可以通过使用widthheight样式来实现。 但是,这不是最佳方法:浏览器仍然需要下载完整大小的图像,然后将其缩小。

另一个选择(通常更好)是在服务器上生成具有某些预定义尺寸的图像缩略图。 使用Dragonfly确实很简单:

views / albums / _album.html.erb
<li>
  <%= image_tag(album.image.thumb('250x250#').url, alt: album.title) if album.image_stored? %>
  <%# ... %>
</li>

250x250当然是尺寸,而#是表示“如果需要以中心重心保持长宽比,则调整大小和裁剪”的几何形状。 您可以在Dragonfly网站上找到有关其他几何形状的信息

thumb方法由ImageMagick支持,这是创建和处理图像的绝佳解决方案。 因此,为了在本地查看有效的演示,您需要安装ImageMagick (支持所有主要平台)。

默认情况下,Dragonfly的初始化程序中启用了对ImageMagick的支持:

config / initializers / dragonfly.rb
plugin :imagemagick

现在正在生成缩略图,但是它们不会存储在任何地方。 这意味着用户每次访问相册页面时,都会重新生成缩略图。 有两种方法可以解决此问题:通过在保存记录后生成它们,或通过即时生成。

第一个选项涉及引入新列以存储缩略图并调整dragonfly_accessor方法。 创建并应用新的迁移:

rails g migration add_image_thumb_uid_to_albums image_thumb_uid:string
rails db:migrate

现在修改模型:

型号/album.rb
dragonfly_accessor :image do
    copy_to(:image_thumb){|a| a.thumb('250x250#') }
end

dragonfly_accessor :image_thumb

需要注意的是,现在第一个电话dragonfly_accessor发送实际生成的缩略图,我们和它复制到块image_thumb 。 现在,只需在视图中使用image_thumb方法即可:

views / albums / _album.html.erb
<%= image_tag(album.image_thumb.url, alt: album.title) if album.image_thumb_stored? %>

该解决方案是最简单的,但是官方文档不建议这样做,更糟糕的是,在编写本文时,该解决方案不适用于retained_*字段。

因此,让我向您展示另一个选择:动态生成缩略图。 它涉及创建一个新模型并调整Dragonfly的配置文件。 一,模型:

rails g model Thumb uid:string job:string
rake db:migrate

thumbs表将承载您的缩略图,但他们会来按需生成。 为此,我们需要在Dragonfly初始化程序中重新定义url方法:

config / initializers / dragonfly.rb
Dragonfly.app.configure do
    define_url do |app, job, opts|
        thumb = Thumb.find_by_job(job.signature)
        if thumb
          app.datastore.url_for(thumb.uid, :scheme => 'https')
        else
          app.server.url_for(job)
        end
    end
    
    before_serve do |job, env|
        uid = job.store
        
        Thumb.create!(
            :uid => uid,
            :job => job.signature
        )
    end
    # ...
end

现在添加新相册并访问根页面。 首次执行此操作时,以下输出将被打印到日志中:

DRAGONFLY: shell command: "convert" "some_path/public/system/dragonfly/development/2017/02/08/3z5p5nvbmx_Folder.jpg" "-resize" "250x250^^" "-gravity" "Center" "-crop" "250x250+0+0" "+repage" "some_path/20170208-1692-1xrqzc9.jpg"

这实际上意味着ImageMagick正在为我们生成缩略图。 但是,如果您重新加载页面,则此行将不再显示,这意味着缩略图已被缓存! 您可以在Dragonfly网站上阅读更多有关此功能的信息

更多处理

上传图像后,您几乎可以对其进行任何处理。 这可以在after_assign回调内部完成。 例如,让我们将所有图像转换为90%质量的JPEG格式:

dragonfly_accessor :image do
    after_assign {|a| a.encode!('jpg', '-quality 90') }
end

您还可以执行更多操作:旋转和裁剪图像,使用不同的格式编码,在图像上写文本,与其他图像混合(例如,放置水印)等。要查看其他示例,请参阅蜻蜓网站上的ImageMagick部分

上载和管理歌曲

当然,我们音乐网站的主要部分是歌曲,所以现在就添加它们。 每首歌都有一个标题和一个音乐文件,并且属于一张专辑:

rails g model Song album:belongs_to title:string track_uid:string
rails db:migrate

像在Album模型中一样,连接Dragonfly方法:

型号/song.rb
dragonfly_accessor :track

不要忘记建立has_many关系:

型号/album.rb
has_many :songs, dependent: :destroy

添加新路线。 专辑范围内始终存在一首歌,因此我将嵌套以下路线:

config / routes.rb
resources :albums do
    resources :songs, only: [:new, :create]
end

创建一个非常简单的控制器(再次,请不要忘记允许track字段):

songs_controller.rb
class SongsController < ApplicationController
  def new
    @album = Album.find(params[:album_id])
    @song = @album.songs.build
  end

  def create
    @album = Album.find(params[:album_id])
    @song = @album.songs.build(song_params)
    if @song.save
      flash[:success] = "Song added!"
      redirect_to album_path(@album)
    else
      render :new
    end
  end

  private

  def song_params
    params.require(:song).permit(:title, :track)
  end
end

显示歌曲和添加新歌曲的链接:

views / albums / show.html.erb
<h1><%= @album.title %></h1>
<h2>by <%= @album.singer %></h2>

<%= link_to 'Add song', new_album_song_path(@album) %>

<ol>
  <%= render @album.songs %>
</ol>

编写表格:

views / songs / new.html.erb
<h1>Add song to <%= @album.title %></h1>

<%= form_for [@album, @song] do |f| %>
  <div>
    <%= f.label :title %>
    <%= f.text_field :title %>
  </div>

  <div>
    <%= f.label :track %>
    <%= f.file_field :track %>
  </div>

  <%= f.submit %>
<% end %>

最后,添加_song部分:

views / songs / _song.html.erb
<li>
  <%= audio_tag song.track.url, controls: true %>
  <%= song.title %>
</li>

我在这里使用的是HTML5 audio标签,该标签不适用于较旧的浏览器。 因此,如果您打算支持此类浏览器,请使用polyfill

如您所见,整个过程非常简单。 蜻蜓并不真正在乎您要上传的文件类型。 您所需要做的就是提供一个dragonfly_accessor方法,添加一个适当的字段,允许它,并呈现文件输入标签。

存储元数据

当我打开播放列表时,我希望看到有关每首歌曲的其他信息,例如持续时间或比特率。 当然,默认情况下,此信息不会存储在任何地方,但是我们可以很轻松地解决它。 Dragonfly允许我们提供有关每个上载文件的其他数据,并在以后使用meta方法获取它。

但是,当我们处理音频或视频时,事情要复杂一些,因为要获取它们的元数据,需要特殊的gem streamio-ffmpeg 。 反过来,此gem依赖于FFmpeg ,因此,要继续进行,您需要将其安装在PC上。

streamio-ffmpeg添加到Gemfile中

宝石文件
gem 'streamio-ffmpeg'

安装它:

bundle install

现在,我们可以使用上after_assign已经看到的相同的after_assign回调:

型号/song.rb
dragonfly_accessor :track do
    after_assign do |a|
      song = FFMPEG::Movie.new(a.path)
      mm, ss = song.duration.divmod(60).map {|n| n.to_i.to_s.rjust(2, '0')}
      a.meta['duration'] = "#{mm}:#{ss}"
      a.meta['bitrate'] = song.bitrate ? song.bitrate / 1000 : 0
    end
end

请注意,这里我使用的是path方法,而不是url ,因为此时我们正在使用一个tempfile。 接下来,我们仅提取歌曲的持续时间(将其转换为以零开头的分钟和秒)和比特率(将其转换为每秒的千字节)。

最后,在视图中显示元数据:

views / songs / _song.html.erb
<li>
  <%= audio_tag song.track.url, controls: true %>
  <%= song.title %> (<%= song.track.meta['duration'] %>, <%= song.track.meta['bitrate'] %>Kb/s)
</li>

如果您检查public / system / dragonfly文件夹(托管上传的默认位置)上的内容,您会注意到一些.yml文件-它们以YAML格式存储所有元信息。

部署到Heroku

我们今天将讨论的最后一个主题是如何在部署到Heroku云平台之前准备应用程序。 主要问题是Heroku不允许您存储自定义文件(例如上载),因此我们必须依靠Amazon S3之类的云存储服务。 幸运的是,蜻蜓可以轻松地与其集成。

您需要做的就是在AWS上注册一个新帐户(如果您还没有),创建一个有权访问S3存储桶的用户,并在一个安全的位置写下该用户的密钥对。 您可以使用根密钥对,但是实际上不建议这样做 。 最后,创建一个S3存储桶。

回到我们的Rails应用程序,放入一个新的gem:

宝石文件
group :production do
  gem 'dragonfly-s3_data_store'
end

安装它:

bundle install

然后调整Dragonfly的配置以在生产环境中使用S3:

config / initializers / dragonfly.rb
if Rails.env.production?
    datastore :s3,
              bucket_name: ENV['S3_BUCKET'],
              access_key_id: ENV['S3_KEY'],
              secret_access_key: ENV['S3_SECRET'],
              region: ENV['S3_REGION'],
              url_scheme: 'https'
else
    datastore :file,
        root_path: Rails.root.join('public/system/dragonfly', Rails.env),
        server_root: Rails.root.join('public')
end

要在Heroku上提供ENV变量,请使用以下命令:

heroku config:add SOME_KEY=SOME_VALUE

如果希望在本地测试与S3的集成,则可以使用dotenv-rails之类的gem管理环境变量。 但是请记住,您的AWS密钥对一定不能公开公开

我在部署到Heroku时遇到的另一个小问题是缺少FFmpeg。 问题在于,当创建一个新的Heroku应用程序时,它具有一组常用的服务(例如,默认情况下ImageImageick可用)。 其他服务可以作为Heroku插件或以buildpacks的形式安装。 要添加FFmpeg buildpack,请运行以下命令:

heroku buildpacks:add https://github.com/HYPERHYPER/heroku-buildpack-ffmpeg.git

现在一切就绪,您可以与世界分享您的音乐应用!

结论

这是一段漫长的旅程,不是吗? 今天,我们讨论了蜻蜓-一种在Rails中上传文件的解决方案。 我们已经看到了它的基本设置,一些配置选项,缩略图生成,处理和元数​​据存储。 此外,我们已经将Dragonfly与Amazon S3服务集成在一起,并准备了我们的应用程序以在生产中进行部署。

当然,本文没有讨论Dragonfly的所有方面,因此请确保浏览其官方网站以找到大量的文档和有用的示例。 如果您还有其他问题或遇到一些代码示例,请随时与我联系。

感谢您与我在一起,很快再见!

翻译自: https://code.tutsplus.com/tutorials/uploading-files-with-rails-and-dragonfly--cms-28184

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值