不久前,我写了一篇文章“使用Rails和Shrine上传文件 ”,其中介绍了如何借助Shrine gem将文件上传功能引入Rails应用程序。 但是,有很多类似的解决方案可用,而我最喜欢的之一是Dragonfly ,这是Mark Evans为Rails和Rack创建的易于使用的上传解决方案。
去年年初 ,我们讨论了这个库,但与大多数软件一样,它有助于不时查看库,以了解更改的内容以及如何在应用程序中使用它。
在本文中,我将指导您完成蜻蜓的设置并解释如何利用其主要功能。 你将学到如何:
- 将Dragonfly集成到您的应用程序中
- 配置模型以与Dragonfly一起使用
- 介绍基本的上传机制
- 介绍验证
- 生成图像缩略图
- 执行文件处理
- 存储上载文件的元数据
- 准备要部署的应用程序
为了使事情变得更有趣,我们将创建一个小型音乐应用程序。 它将展示可以在网站上管理和播放的专辑和相关歌曲。
GitHub上提供了本文的源代码。 您还可以签出该应用程序的工作演示 。
列出和管理相册
首先,创建一个没有默认测试套件的新Rails应用程序:
rails new UploadingWithDragonfly -T
对于本文,我将使用Rails 5,但是大多数描述的概念也适用于旧版本。
创建模型,控制器和路径
我们的小型音乐网站将包含两个模型: Album
和Song
。 现在,让我们用以下字段创建第一个:
-
title
(string
)-包含相册的标题 -
singer
(string
)—专辑的表演者 -
image_uid
(string
)—用于存储相册的预览图像的特殊字段。 您可以根据自己的喜好命名此字段,但必须按照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来解决。
处理图像
产生缩图
我们的用户上传的图像可能具有非常不同的尺寸,这可能(并且可能会)对网站的设计造成负面影响。 您可能希望将图像缩小到某些固定尺寸,当然可以通过使用width
和height
样式来实现。 但是,这不是最佳方法:浏览器仍然需要下载完整大小的图像,然后将其缩小。
另一个选择(通常更好)是在服务器上生成具有某些预定义尺寸的图像缩略图。 使用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