Russian (Pусский) translation by Marat Amerov (you can also view the original English article)
Это очередная статья из серии «Загрузка с Rails». Сегодня мы познакомимся с Carrierwave - одним из самых популярных решений для загрузки файлов с Rails. Мне нравится Carrierwave, потому с ним легко начать работу, у него множество функций из коробки, и для него представлены десятки "how to" статей, написанные сообществом.
Из этой статьи вы узнаете, как:
- Интегрировать Carrierwave в Rails приложение
- Добавить валидацию
- Сохранять файлы через запросы
- Удалять файлы
- Генерировать миниатюрные эскизы
- Загружать файлы из удаленных хостов
- Загружать несколько файлов за раз
- Добавить поддержку для облачного хранилища
Исходный код этой статьи доступен на GitHub.
Создание фундамента приложения
Как всегда, начнем с создания нового Rails приложения:
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
) - это поле будет содержать изображение (имя файла, если быть точным), прикрепленное к посту
Создадим и применим новую миграцию:
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
Теперь, давайте создадим представление для index:
views/posts/index.html.erb
<h1>Posts</h1> <%= link_to 'Add post', new_post_path %> <%= render @posts %>
Также соответствующий partial:
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 truncatе
для отображения только первых 150 символов из поста. Прежде чем мы создадим другие представления и partial формы, давайте сначала интегрируем Carrierwave в приложение.
Интеграция Carrierwave
Добавим новый гем в Gemfile:
Gemfile
gem 'carrierwave', '~> 1.0'
Запуск:
bundle install
Carrierwave хранит свою конфигурацию внутри загрузчиков, включенных в модели. Чтобы создать загрузчик, используйте следующую команду:
rails generate uploader Image
Теперь, внутри app/uploaders, вы обнаружите новый файл с именем image_uploader.rb. Обратите внимание, что у него есть несколько полезных комментариев и примеров, соответственно вы можете использовать его для начала работы. В этой демонстрации мы будем использовать ActiveRecord, но Carrierwave также поддерживает Mongoid, Sequel и DataMapper.
Затем нам нужно включить или монтировать этот загрузчик в модель:
models/post.rb
mount_uploader :image, ImageUploader
У загрузчика уже есть оптимальные настройки по умолчанию, но, по крайней мере, нам нужно выбрать, куда будут загружены файлы. На данный момент давайте использовать файловую систему:
uploaders/image_uploader.rb
storage :file
По умолчанию файлы будут загружены в каталог public/uploads, поэтому лучше исключить его из системы контроля версий:
.gitignore
public/uploads
Вы также можете изменить метод store_dir
внутри вашего загрузчика, чтобы выбрать другое местоположение.
На этом этапе мы можем создать новое представление и partial формы, чтобы начать загрузку файлов:
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 %>
Обратите внимание, что PostsController
не нужно модифицировать, поскольку мы уже разрешили атрибут image
.
Наконец, создадим представление для редактирования:
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
. Обратите внимание, что Carrierwave также предоставляет метод image?
для проверки того, присутствует ли вообще вложение (сам метод image
никогда не вернет nil
, даже если файл отсутствует).
Теперь, после перехода к посту, вы должны увидеть изображение, но оно может показаться слишком большим: в конце концов, мы нигде не ограничиваем размеры. Конечно, мы могли бы уменьшить масштаб изображения с помощью некоторых правил CSS, но гораздо лучше создать миниатюру после загрузки файла. Однако это требует некоторых дополнительных шагов.
Генерация эскизов
Чтобы обрезать и масштабировать изображения, нам нужен отдельный инструмент. Из коробки Carrierwave имеет поддержку гемов RMagick и MiniMagick, которые, в свою очередь, используются для управления изображениями с помощью ImageMagick. ImageMagick - это решение с открытым исходным кодом, позволяющее редактировать существующие изображения и создавать новые, поэтому перед продолжением необходимо загрузить и установить его. Затем вы можете выбрать любой из двух гемов. Я буду придерживаться MiniMagick, потому что его гораздо проще установить, и он имеет лучшую поддержку:
Gemfile
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 мегабайта. Для этого нам понадобится дополнительный гем, поддерживающий проверку файлов для ActiveModel:
Gemfile
gem 'file_validators'
Установка:
bundle install
Теперь добавим желаемые проверки (обратите внимание, что я также добавляю проверки атрибутов title
и body
):
models/post.rb
validates :title, presence: true, length: {minimum: 2} validates :body, presence: true validates :image, file_size: { less_than: 1.megabytes }
Следующее, что нужно сделать, это добавить переводы I18n для сообщений об ошибках Carrierwave:
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}"
В настоящее время мы не показываем ошибки валидации , поэтому давайте создадим общий partial:
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 %>
Используйте этот partial внутри формы:
views/posts/_form.html.erb
<%= render 'shared/errors', object: post %>
Теперь попробуем загрузить недопустимые файлы и понаблюдаем результат. Это должно работать, но если выбрать валидный файл и не заполнить заголовок или тело, тогда проверки все равно будут происходить, и будет отображаться ошибка. Однако поле файла будет очищено, и пользователю нужно будет снова выбрать изображение, что не очень удобно. Чтобы исправить это, нам нужно добавить другое поле в форму.
Сохранение файлов между запросами
Сохранение файлов из redisplays на самом деле довольно просто. Все, что вам нужно сделать, это добавить новое скрытое поле и разрешить его внутри контроллера:
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). Я предпочитаю последний вариант, поэтому давайте перейдем к новому адаптеру базы данных. Удалите гем sqlite3 из Gemfile и добавьте вместо него pg:
Gemfile
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.
Теперь подключите загрузчики (обратите внимание на множественную форму!):
model/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? Ну, это тоже довольно простая задача. Carrierwave зависит от fog-aws для реализации этого функционала:
Gemfile
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 для безопасного хранения переменных окружения, но Вы можете выбрать любой другой вариант. Однако убедитесь, что ваша пара ключей S3 недоступна публично, потому что в противном случае любой может загрузить что-нибудь в ваш бакет!
Затем замените строку storage: file
:
uploaders/image_uploader.rb
storage :fog
Помимо S3, Carrierwave поддерживает загрузку в Google Storage и Rackspace. Эти сервисы также легко настраиваются.
Заключение
На сегодня, это все. Мы рассмотрели все основные функции Carrierwave, и теперь Вы можете начать использовать его в своих проектах. Он имеет несколько дополнительных опций, поэтому просмотрите документацию.
Если вы застряли, не стесняйтесь оставлять свои вопросы. Кроме того, может быть полезно заглянуть в wiki- Carrierwave, в котором содержатся полезные «how to» статьи и ответы на многие распространенные вопросы.
Благодарю вас за то, что вы остались со мной, и счастливо покодить!
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Update me weeklyEnvato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post