Это еще одна статья из серии «Загрузка с Rails». Сегодня мы встретимся с Carrierwave — одним из самых популярных решений для загрузки файлов в Rails. Мне нравится Carrierwave, потому что его легко начать, он имеет множество функций из коробки и предоставляет десятки статей «как сделать», написанных членами сообщества, так что вы не потеряете себя.
В этой статье вы узнаете, как:
- Интегрируйте Carrierwave в ваше приложение Rails
- Добавить проверки
- Сохранять файлы в запросах
- Удалить файлы
- Генерировать миниатюры
- Загружать файлы из удаленных мест
- Ввести несколько загрузок файлов
- Добавить поддержку облачного хранилища
Исходный код этой статьи доступен на GitHub . Наслаждайся чтением!
Закладка фундаментов
Как всегда, начните с создания нового приложения Rails:
1
|
rails new UploadingWithCarrierwave -T
|
Для этой демонстрации я буду использовать Rails 5.0.2. Обратите внимание, что Carrierwave 1 поддерживает только Rails 4+ и Ruby 2. Если вы все еще используете Rails 3, подключите Carrierwave версии 0.11.
Чтобы увидеть Carrierwave в действии, мы собираемся создать очень простое приложение для блогов с единственной моделью Post
. Он будет иметь следующие основные атрибуты:
-
title
(string
) -
body
(text
) -
image
(string
) — это поле будет содержать изображение (если быть точным, имя файла), прикрепленное к сообщению
Создайте и примените новую миграцию:
1
2
|
rails g model Post title:string body:text image:string
rails db:migrate
|
Установите несколько маршрутов:
конфиг / routes.rb
1
2
|
resources :posts
root to: ‘posts#index’
|
Также создайте очень простой контроллер:
posts_controller.rb
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
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
|
Теперь давайте создадим индексное представление:
просмотров / сообщений / index.html.erb
1
2
3
4
5
|
<h1>Posts</h1>
<%= link_to ‘Add post’, new_post_path %>
<%= render @posts %>
|
И соответствующий частичный:
просмотров / сообщений / _post.html.erb
1
2
3
4
5
6
|
<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>
|
Здесь я использую метод truncate
Rails для отображения только первых 150 символов из поста. Прежде чем создавать другие представления и частичную форму, давайте сначала интегрируем Carrierwave в приложение.
Интеграция Carrierwave
Вставьте новый драгоценный камень в Gemfile :
Gemfile
1
|
gem ‘carrierwave’, ‘~> 1.0’
|
Бегать:
1
|
bundle install
|
Carrierwave хранит свою конфигурацию внутри загрузчиков , которые входят в ваши модели. Чтобы сгенерировать загрузчик, используйте следующую команду:
1
|
rails generate uploader Image
|
Теперь внутри app / uploaders вы найдете новый файл с именем image_uploader.rb . Обратите внимание, что в нем есть несколько полезных комментариев и примеров, поэтому вы можете использовать его для начала. В этой демонстрации мы будем использовать ActiveRecord, но Carrierwave также поддерживает Mongoid, Sequel и DataMapper.
Далее нам нужно включить или смонтировать этот загрузчик в модель:
модели / post.rb
1
|
mount_uploader :image, ImageUploader
|
Загрузчик уже имеет нормальные настройки по умолчанию, но по крайней мере нам нужно выбрать, где будут сохранены загруженные файлы. А пока давайте используем хранилище файлов:
закачивающие / image_uploader.rb
1
|
storage :file
|
По умолчанию файлы будут помещаться в каталог public / uploads , поэтому лучше всего исключить его из системы контроля версий:
.gitignore
1
|
public/uploads
|
Вы также можете изменить метод store_dir
внутри вашего загрузчика, чтобы выбрать другое местоположение.
На этом этапе мы можем создать новый вид и частичную форму для начала загрузки файлов:
просмотров / сообщений / new.html.erb
1
2
3
|
<h1>Add post</h1>
<%= render ‘form’, post: @post %>
|
просмотров / сообщений / _form.html.erb
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
<%= 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 %>
|
Обратите внимание, что PostsController
не нужно изменять, так как мы уже разрешили атрибут image
.
Наконец, создайте представление редактирования:
просмотров / сообщений / edit.html.erb
1
2
3
|
<h1>Edit post</h1>
<%= render ‘form’, post: @post %>
|
Это оно! Вы можете загрузить сервер и попытаться создать пост с изображением. Проблема в том, что это изображение нигде не видно, поэтому давайте перейдем к следующему разделу и добавим страницу показа!
Отображение изображений
Итак, единственный вид, который мы еще не создали, это шоу . Добавьте это сейчас:
просмотров / сообщений / show.html.erb
1
2
3
4
5
6
7
8
|
<%= 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
. Обратите внимание, что Carrierwave также предоставляет image?
метод для нас, чтобы проверить, присутствует ли вложение вообще (сам метод image
никогда не вернет nil
, даже если файл не присутствует).
Теперь после перехода к сообщению вы должны увидеть изображение, но оно может показаться слишком большим: в конце концов, мы нигде не ограничиваем размеры. Конечно, мы могли бы уменьшить изображение с помощью некоторых правил CSS, но гораздо лучше создать миниатюру после загрузки файла. Это, однако, требует некоторых дополнительных шагов.
Генерация миниатюр
Чтобы обрезать и масштабировать изображения, нам нужен отдельный инструмент. «Carrierwave» имеет встроенную поддержку драгоценных камней RMagick и MiniMagick, которые, в свою очередь, используются для управления изображениями с помощью ImageMagick . ImageMagick — это решение с открытым исходным кодом, позволяющее редактировать существующие изображения и создавать новые, поэтому перед тем, как продолжить, вам необходимо скачать и установить его . Далее вы можете выбрать любой из двух драгоценных камней. Я остановлюсь на MiniMagick, потому что он намного проще в установке и имеет лучшую поддержку:
Gemfile
1
|
gem ‘mini_magick’
|
Бегать:
1
|
bundle install
|
Затем включите MiniMagick в свой загрузчик:
закачивающие / image_uploader.rb
1
|
include CarrierWave::MiniMagick
|
Теперь нам просто нужно представить новую версию для нашего загрузчика. Понятие версий (или стилей) используется во многих библиотеках загрузки файлов; это просто означает, что дополнительные файлы на основе исходного вложения будут созданы, например, с разными размерами или форматами. Введите новую версию под названием thumb
:
закачивающие / image_uploader.rb
1
2
3
|
version :thumb do
process resize_to_fill: [350, 350]
end
|
У вас может быть столько версий, сколько вам нужно, и, более того, версии могут быть построены поверх других:
закачивающие / image_uploader.rb
1
2
3
|
version :small_thumb, from_version: :thumb do
process resize_to_fill: [20, 20]
end
|
Если вы уже загрузили некоторые изображения, у них не будет доступных миниатюр. Это не проблема, так как вы можете воссоздать их из консоли Rails:
1
2
|
rails c
Post.find_each {|post|
|
Наконец, отобразите свой эскиз со ссылкой на исходное изображение:
просмотров / сообщений / show.html.erb
1
|
<%= link_to(image_tag(@post.image.thumb.url, alt: ‘Image’), @post.image.url, target: ‘_blank’) if @post.image?
|
Загрузи сервер и наблюдай за результатом!
Добавление проверок
В настоящее время наша загрузка работает, но мы вообще не проверяем ввод пользователя, что, конечно, плохо. Пока мы хотим работать только с изображениями, давайте добавим в белый список .png, .jpg и .gif расширения:
закачивающие / image_uploader.rb
1
2
3
|
def extension_whitelist
%w(jpg jpeg gif png)
end
|
Вы также можете добавить проверки типов контента, определив метод content_type_whitelist
:
закачивающие / image_uploader.rb
1
2
3
|
def content_type_whitelist
/image\//
end
|
В качестве альтернативы, можно занести в черный список некоторые типы файлов, например исполняемые файлы, определив метод content_type_blacklist
.
Помимо проверки типа и расширения файла, давайте установим его размер менее 1 мегабайта. Для этого нам понадобится дополнительный валидатор, поддерживающий валидацию файлов для ActiveModel:
Gemfile
1
|
gem ‘file_validators’
|
Установите это:
1
|
bundle install
|
Теперь введите нужные проверки (обратите внимание, что я также добавляю проверки для атрибутов title
и body
):
модели / post.rb
1
2
3
|
validates :title, presence: true, length: {minimum: 2}
validates :body, presence: true
validates :image, file_size: { less_than: 1.megabytes }
|
Следующее, что нужно сделать, это добавить переводы I18n для сообщений об ошибках Carrierwave:
конфиг / локали / en.yml
1
2
3
4
5
6
7
8
|
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}»
|
В настоящее время мы нигде не отображаем ошибки проверки, поэтому давайте создадим общую часть:
просмотров / общий / _errors.html.erb
1
2
3
4
5
6
7
8
|
<% if object.errors.any?
<h3>Some errors were found:</h3>
<ul>
<% object.errors.full_messages.each do |message|
<li><%= message %></li>
<% end %>
</ul>
<% end %>
|
Используйте это частичное внутри формы:
просмотров / сообщений / _form.html.erb
1
|
<%= render ‘shared/errors’, object: post %>
|
Теперь попробуйте загрузить несколько недействительных файлов и просмотрите результат. Это должно работать, но если вы выберете правильный файл и не заполните заголовок или текст, проверки все равно не пройдут, и появится сообщение об ошибке. Однако поле файла будет очищено, и пользователю потребуется снова выбрать изображение, что не очень удобно. Чтобы это исправить, нам нужно добавить еще одно поле в форму.
Сохранение файлов в запросах
Сохранение файлов при повторном отображении формы на самом деле довольно просто. Все, что вам нужно сделать, это добавить новое скрытое поле и разрешить его внутри контроллера:
просмотров / общий / _form.html.erb
1
2
3
|
<%= f.label :image %>
<%= f.file_field :image %><br>
<%= f.hidden_field :image_cache %>
|
posts_controller.rb
1
|
params.require(:post).permit(:title, :body, :image, :image_cache)
|
Теперь image_cache
будет заполнен автоматически, и изображение не будет потеряно. Также может быть полезно отобразить миниатюру, чтобы пользователь понял, что изображение было успешно обработано:
просмотров / общий / _form.html.erb
1
2
3
|
<% if post.image?
<%= image_tag post.image.thumb.url %>
<% end %>
|
Удаление изображений
Еще одна очень распространенная функция — это возможность удалять вложенные файлы при редактировании записи. С Carrierwave реализация этой функции не является проблемой. Добавьте новый флажок в форму:
просмотров / общий / _form.html.erb
1
2
3
4
5
6
7
8
9
|
<% 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
1
|
params.require(:post).permit(:title, :body, :image, :remove_image, :image_cache)
|
Это оно! Чтобы удалить изображение вручную, используйте remove_image!
метод:
1
|
@post.remove_image!
|
Загрузка из удаленного местоположения
Carrierwave также предоставляет очень полезную функцию из коробки: возможность загружать файлы из удаленных мест по их URL. Давайте сейчас введем эту возможность, добавив новое поле и разрешив соответствующий атрибут:
просмотров / общий / _form.html.erb
1
2
|
<%= f.text_field :remote_image_url %>
<small>Enter URL to an image</small>
|
posts_controller.rb
1
|
params.require(:post).permit(:title, :body, :image, :remove_image, :image_cache, :remote_image_url)
|
Как это круто? Вам не нужно вносить какие-либо изменения, и вы можете проверить эту функцию прямо сейчас!
Работа с несколькими загрузками
Предположим, мы хотим, чтобы в нашем сообщении было несколько доступных вложений. При текущей настройке это невозможно, но, к счастью, Carrierwave также поддерживает такой сценарий. Чтобы реализовать эту функцию, вам нужно добавить либо сериализованное поле (для SQLite), либо поле JSON (для Postgres или MySQL). Я предпочитаю последний вариант, поэтому давайте теперь перейдем к новому адаптеру базы данных. Удалите драгоценный камень sqlite3 из Gemfile и добавьте вместо него pg:
Gemfile
1
|
gem ‘pg’
|
Установите это:
1
|
bundle install
|
Измените конфигурацию базы данных следующим образом:
конфиг / database.yml
01
02
03
04
05
06
07
08
09
10
11
|
default: &default
adapter: postgresql
pool: 5
timeout: 5000
development:
<<: *default
database: upload_carrier_dev
username: ‘YOUR_USER’
password: ‘YOUR_PASSWORD’
host: localhost
|
Создайте соответствующую базу данных Postgres, а затем сгенерируйте и примените миграцию:
1
2
|
rails g migration add_attachments_to_posts attachments:json
rails db:migrate
|
Если вы предпочитаете придерживаться SQLite, следуйте инструкциям, приведенным в документации Carrierwave.
Теперь смонтируйте загрузчики (обратите внимание на форму множественного числа!):
модель / post.rb
1
|
mount_uploaders :attachments, ImageUploader
|
Я использую тот же загрузчик для вложений, но, конечно, вы можете создать новый с другой конфигурацией.
Добавьте поле с несколькими файлами в форму:
просмотров / общий / _form.html.erb
1
2
3
4
|
<div>
<%= f.label :attachments %>
<%= f.file_field :attachments, multiple: true %>
</div>
|
Пока поле attachments
будет содержать массив, это должно быть разрешено следующим образом:
posts_controller.rb
1
|
params.require(:post).permit(:title, :body, :image, :remove_image, :image_cache, :remote_image_url, attachments: [])
|
Наконец, вы можете перебирать вложения к сообщению и отображать их как обычно:
просмотров / общий / show.html.erb
1
2
3
4
5
6
7
|
<% 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? Ну, это тоже довольно простая задача . Carrierwave зависит от драгоценного камня fog-aws для реализации этой функции:
Gemfile
1
|
gem «fog-aws»
|
Установите это:
1
|
bundle install
|
Давайте создадим инициализатор для Carrierwave и настроим облачное хранилище глобально:
конфиг / Инициализаторы / carrierwave.rb
01
02
03
04
05
06
07
08
09
10
|
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 для безопасной установки переменных окружения, но вы можете выбрать любой другой вариант. Тем не менее, убедитесь, что ваша пара ключей S3 не доступна публично, потому что в противном случае любой может загрузить что-либо в ваше ведро!
Затем замените строку storage :file
на:
закачивающие / image_uploader.rb
1
|
storage :fog
|
Помимо S3, Carrierwave поддерживает загрузку в Google Storage и Rackspace . Эти сервисы также просты в настройке.
Вывод
Это на сегодня! Мы рассмотрели все основные функции Carrierwave, и теперь вы можете начать использовать его в своих проектах. У него есть некоторые дополнительные опции, так что просмотрите документацию .
Если вы застряли, не стесняйтесь оставлять свои вопросы. Кроме того, может быть полезно взглянуть на вики Carrierwave , где размещены полезные статьи «как делать», отвечающие на многие распространенные вопросы.
Поэтому я благодарю вас за то, что вы остались со мной, и счастливого кодирования!