这是“使用Rails上传”系列中的另一篇文章。 今天,我们将认识Carrierwave ,这是Rails最受欢迎的文件上传解决方案之一。 我喜欢Carrierwave,因为它很容易上手,具有很多现成的功能,并且提供了社区成员撰写的数十篇“如何”文章,因此您不会迷路。
在本文中,您将学习如何:
- 将Carrierwave集成到您的Rails应用中
- 添加验证
- 跨请求保留文件
- 删除档案
- 产生缩图
- 从远程位置上传文件
- 介绍多个文件上传
- 添加对云存储的支持
GitHub上提供了本文的源代码。 享受阅读!
奠定基础
与往常一样,首先创建一个新的Rails应用程序:
rails new UploadingWithCarrierwave -T
对于此演示,我将使用Rails 5.0.2。 请注意,Carrierwave 1仅支持Rails 4+和Ruby2。如果仍在使用Rails 3,请连接Carrierwave版本0.11。
为了了解Carrierwave的实际应用,我们将创建一个具有唯一Post
模型的非常简单的博客应用程序。 它将具有以下主要属性:
-
title
(string
) -
body
(text
) -
image
(string
)-此字段将包含附加到帖子的图像(准确地说是文件名)
生成并应用新的迁移:
rails g model Post title:string body:text image:string
rails db:migrate
设置一些路线:
config / routes.rb
resources :posts
root to: 'posts#index'
另外,创建一个非常基本的控制器:
posts_controller.rb
class PostsController < ApplicationController
before_action :set_post, only: [:show, :edit, :update]
def index
@posts = Post.order('created_at DESC')
end
def show
end
def new
@post = Post.new
end
def create
@post = Post.new(post_params)
if @post.save
redirect_to posts_path
else
render :new
end
end
def edit
end
def update
if @post.update_attributes(post_params)
redirect_to post_path(@post)
else
render :edit
end
end
private
def post_params
params.require(:post).permit(:title, :body, :image)
end
def set_post
@post = Post.find(params[:id])
end
end
现在让我们制作索引视图:
views / posts / index.html.erb
<h1>Posts</h1>
<%= link_to 'Add post', new_post_path %>
<%= render @posts %>
并对应部分:
views / posts / _post.html.erb
<h2><%= link_to post.title, post_path(post) %></h2>
<p><%= truncate(post.body, length: 150) %></p>
<p><%= link_to 'Edit', edit_post_path(post) %></p>
<hr>
在这里,我使用Rails truncate
方法仅显示帖子中的前150个符号。 在创建其他视图和部分表单之前,让我们首先将Carrierwave集成到应用程序中。
整合载波
将新的gem放入Gemfile中 :
宝石文件
gem 'carrierwave', '~> 1.0'
跑:
bundle install
Carrierwave将其配置存储在模型中包含的上载器中。 要生成上传器,请使用以下命令:
rails generate uploader Image
现在,在app / uploaders内部,您将找到一个名为image_uploader.rb的新文件。 请注意,它具有一些有用的注释和示例,因此您可以使用它开始。 在此演示中,我们将使用ActiveRecord,但Carrierwave 还支持 Mongoid,Sequel和DataMapper。
接下来,我们需要将此上传器包含或安装到模型中:
模型/ post.rb
mount_uploader :image, ImageUploader
上载器已经具有合理的默认设置,但是至少我们需要选择上载文件的存储位置。 现在,让我们使用文件存储:
uploaders / image_uploader.rb
storage :file
默认情况下,文件将放置在public / uploads目录中,因此最好将其从版本控制系统中排除:
.gitignore
public/uploads
您也可以在上传器内部修改store_dir
方法,以选择其他位置。
此时,我们可以创建一个新视图和一个局部表单以开始上传文件:
views / posts / new.html.erb
<h1>Add post</h1>
<%= render 'form', post: @post %>
views / posts / _form.html.erb
<%= form_for post do |f| %>
<div>
<%= f.label :title %>
<%= f.text_field :title %>
</div>
<div>
<%= f.label :body %>
<%= f.text_area :body %>
</div>
<div>
<%= f.label :image %>
<%= f.file_field :image %>
</div>
<%= f.submit %>
<% end %>
请注意,由于我们已经允许使用image
属性,因此无需修改PostsController
。
最后,创建编辑视图:
views / posts / edit.html.erb
<h1>Edit post</h1>
<%= render 'form', post: @post %>
而已! 您可以引导服务器并尝试创建带有图像的帖子。 问题在于该图像在任何地方都不可见,因此让我们继续下一部分并添加显示页面!
显示影像
因此,我们尚未创建的唯一视图是show 。 立即添加:
views / posts / show.html.erb
<%= link_to 'All posts', posts_path %>
<h1><%= @post.title %></h1>
<%= image_tag(@post.image.url, alt: 'Image') if @post.image? %>
<p><%= @post.body %></p>
<p><%= link_to 'Edit', edit_post_path(@post) %></p>
如您所见,显示附件确实很容易:您所要做的就是说@post.image.url
来获取图像的URL。 要获取文件的路径,请使用current_path
方法。 请注意,载波还提供image?
我们检查附件是否存在的方法(即使文件不存在, image
方法本身也不会返回nil
)。
现在,导航到帖子后,您应该会看到一张图片,但是它可能看起来太大了:毕竟,我们没有在任何地方限制尺寸。 当然,我们可以使用一些CSS规则按比例缩小图像,但是在文件上传后生成缩略图要好得多。 但是,这需要一些其他步骤。
产生缩图
为了裁剪和缩放图像,我们需要一个单独的工具。 开箱即用Carrierwave支持RMagick和MiniMagick宝石,而宝石又可以在ImageMagick的帮助下操纵图像。 ImageMagick是一个开放源代码解决方案,允许您编辑现有图像并生成新图像,因此在继续之前,您需要下载并安装它 。 接下来,您可以自由选择两个宝石之一。 我会坚持使用MiniMagick,因为它更容易安装并且具有更好的支持:
宝石文件
gem 'mini_magick'
跑:
bundle install
然后将MiniMagick包含在您的上传器中:
uploaders / image_uploader.rb
include CarrierWave::MiniMagick
现在,我们只需要向上传器介绍一个新版本。 版本 (或样式)的概念在许多文件上传库中使用。 它只是意味着将基于原始附件创建其他文件,例如具有不同的尺寸或格式。 引入一个称为thumb
的新版本:
uploaders / image_uploader.rb
version :thumb do
process resize_to_fill: [350, 350]
end
您可以根据需要拥有任意多个版本,而且,甚至可以在其他版本之上构建版本:
uploaders / image_uploader.rb
version :small_thumb, from_version: :thumb do
process resize_to_fill: [20, 20]
end
如果您已经上传了一些图像,则它们将没有缩略图。 不过,这不是问题,因为您可以从Rails控制台重新创建它们:
rails c
Post.find_each {|post| post.image.recreate_versions!(:thumb) if post.image?}
最后,显示带有原始图像链接的缩略图:
views / posts / show.html.erb
<%= link_to(image_tag(@post.image.thumb.url, alt: 'Image'), @post.image.url, target: '_blank') if @post.image? %>
引导服务器并观察结果!
添加验证
目前,我们的上传正常,但我们根本没有验证用户的输入,这当然是不好的。 只要我们只想处理图片,就将.png,.jpg和.gif扩展名列入白名单:
uploaders / image_uploader.rb
def extension_whitelist
%w(jpg jpeg gif png)
end
您还可以通过定义content_type_whitelist
方法来添加内容类型检查:
uploaders / image_uploader.rb
def content_type_whitelist
/image\//
end
或者,可以通过定义content_type_blacklist
方法将某些文件类型(例如可执行文件)列入黑名单。
除了检查文件的类型和扩展名之外,我们还强制它小于1兆字节。 为此,我们将需要一个额外的gem支持 ActiveModel的文件验证 :
宝石文件
gem 'file_validators'
安装它:
bundle install
现在介绍所需的验证(注意,我还将添加对title
和body
属性的检查):
模型/ post.rb
validates :title, presence: true, length: {minimum: 2}
validates :body, presence: true
validates :image, file_size: { less_than: 1.megabytes }
接下来要做的是为Carrierwave的错误消息添加I18n转换:
config / locales / en.yml
en:
errors:
messages:
carrierwave_processing_error: "Cannot resize image."
carrierwave_integrity_error: "Not an image."
carrierwave_download_error: "Couldn't download image."
extension_whitelist_error: "You are not allowed to upload %{extension} files, allowed types: %{allowed_types}"
extension_blacklist_error: "You are not allowed to upload %{extension} files, prohibited types: %{prohibited_types}"
当前,我们不会在任何地方显示验证错误,因此让我们创建一个共享的部分:
views / shared / _errors.html.erb
<% if object.errors.any? %>
<h3>Some errors were found:</h3>
<ul>
<% object.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
<% end %>
在表单中使用此部分内容:
views / posts / _form.html.erb
<%= render 'shared/errors', object: post %>
现在尝试上传一些无效文件并观察结果。 它应该可以工作,但是如果您选择一个有效的文件并且不填写标题或正文,则检查仍将失败并显示错误。 但是,文件字段将被清除,用户将需要再次选择图像,这不是很方便。 要解决此问题,我们需要在表单中添加另一个字段。
跨请求保留文件
在表单重新显示中持久保存文件实际上非常容易。 您需要做的就是添加一个新的隐藏字段,并将其允许在控制器内部:
views / shared / _form.html.erb
<%= f.label :image %>
<%= f.file_field :image %><br>
<%= f.hidden_field :image_cache %>
posts_controller.rb
params.require(:post).permit(:title, :body, :image, :image_cache)
现在, image_cache
将自动填充,并且图像不会丢失。 同时显示缩略图可能会有所帮助,以便用户了解图像已成功处理:
views / shared / _form.html.erb
<% if post.image? %>
<%= image_tag post.image.thumb.url %>
<% end %>
移除影像
另一个非常常见的功能是在编辑记录时删除附件的功能 。 使用Carrierwave,实现此功能不是问题。 在表单中添加一个新的复选框:
views / shared / _form.html.erb
<% if post.image? %>
<%= image_tag post.image.thumb.url %>
<div>
<%= label_tag :remove_image do %>
Remove image
<%= f.check_box :remove_image %>
<% end %>
</div>
<% end %>
并允许remove_image
属性:
posts_controller.rb
params.require(:post).permit(:title, :body, :image, :remove_image, :image_cache)
而已! 要手动删除图像,请使用remove_image!
方法:
@post.remove_image!
从远程位置上传
Carrierwave还提供了一个非常酷的功能,即开箱即用:通过URL 从远程位置上传文件的功能。 现在,通过添加一个新字段并允许相应的属性来介绍此功能:
views / shared / _form.html.erb
<%= f.text_field :remote_image_url %>
<small>Enter URL to an image</small>
posts_controller.rb
params.require(:post).permit(:title, :body, :image, :remove_image, :image_cache, :remote_image_url)
多么酷啊? 您根本不需要进行任何更改,并且可以立即测试此功能!
处理多个上传
假设我们希望我们的帖子有多个附件。 在当前设置下,这是不可能的,但幸运的是,Carrierwave也支持这种情况。 要实现此功能,您需要添加序列化字段(对于SQLite)或JSON字段(对于Postgres或MySQL)。 我更喜欢后者,所以现在我们切换到新的数据库适配器。 从Gemfile中删除sqlite3 gem,然后添加pg:
宝石文件
gem 'pg'
安装它:
bundle install
像这样修改数据库配置:
config / database.yml
default: &default
adapter: postgresql
pool: 5
timeout: 5000
development:
<<: *default
database: upload_carrier_dev
username: 'YOUR_USER'
password: 'YOUR_PASSWORD'
host: localhost
创建相应的Postgres数据库,然后生成并应用迁移:
rails g migration add_attachments_to_posts attachments:json
rails db:migrate
如果您喜欢使用SQLite,请按照 Carrierwave文档中列出的说明进行操作。
现在安装上载器(注意复数形式!):
模型/ post.rb
mount_uploaders :attachments, ImageUploader
我为附件使用了相同的上传器,但是您当然可以使用不同的配置生成一个新的上传器。
将多个文件字段添加到您的窗体:
views / shared / _form.html.erb
<div>
<%= f.label :attachments %>
<%= f.file_field :attachments, multiple: true %>
</div>
只要attachments
字段将包含一个数组,就应该通过以下方式允许它:
posts_controller.rb
params.require(:post).permit(:title, :body, :image, :remove_image, :image_cache, :remote_image_url, attachments: [])
最后,您可以遍历帖子的附件并像往常一样显示它们:
views / shared / show.html.erb
<% if @post.attachments? %>
<ul>
<% @post.attachments.each do |attachment| %>
<li><%= link_to(image_tag(attachment.thumb.url, alt: 'Image'), attachment.url, target: '_blank') %></li>
<% end %>
</ul>
<% end %>
请注意,每个附件都将具有在ImageUploader
配置的缩略图。 真好!
使用云存储
坚持文件存储并不总是很方便和/或可能的,例如在Heroku上不可能存储自定义文件。 因此,您可能会问如何将Carrierwave与Amazon S3云存储结合? 嗯,这也是一个非常容易的任务 。 载波依赖于fog-aws gem来实现此功能:
宝石文件
gem "fog-aws"
安装它:
bundle install
让我们为Carrierwave创建一个初始化程序并全局配置云存储:
config / initializers / carrierwave.rb
CarrierWave.configure do |config|
config.fog_provider = 'fog/aws'
config.fog_credentials = {
provider: 'AWS',
aws_access_key_id: ENV['S3_KEY'],
aws_secret_access_key: ENV['S3_SECRET'],
region: ENV['S3_REGION'],
}
config.fog_directory = ENV['S3_BUCKET']
end
还有其他一些可用选项,可以在文档中找到。
我正在使用dotenv-rails gem以安全的方式设置环境变量,但是您可以选择任何其他选项。 但是,请确保您的S3密钥对不公开,否则任何人都可以将任何内容上传到您的存储桶!
接下来,将storage :file
行替换为:
uploaders / image_uploader.rb
storage :fog
除S3外,Carrierwave还支持上传到Google Storage和Rackspace 。 这些服务也很容易设置。
结论
今天就是这样! 我们已经介绍了Carrierwave的所有主要功能,现在您可以在项目中开始使用它了。 它具有一些可用的其他选项,因此请浏览文档 。
如果您陷入困境,请随时发布您的问题。 此外,窥视Carrierwave的Wiki可能很有用,该Wiki包含有用的“如何”文章,回答了许多常见问题。
因此,感谢您与我在一起,并祝您编程愉快!
翻译自: https://code.tutsplus.com/articles/uploading-with-rails-and-carrierwave--cms-28409