Статьи

Загрузка файлов с помощью скрепки

Снимок экрана 2015-07-01 08.53.31

Некоторое время назад я написал статью « Лучшая загрузка файлов с помощью Dragonfly» и « Асинхронная загрузка файлов в Rails» , две статьи, в которых рассказывалось о Dragonfly , прекрасном украшении для различных вложений. В этой статье я собираюсь представить Paperclip от Thoughtbot — возможно, самое популярное и многофункциональное решение для интеграции выгрузки файлов и управления ими в приложение.

Одной из замечательных особенностей Paperclip является его большое и активное сообщество — этот драгоценный камень постоянно обновляется, и это действительно важно. Вы не хотите использовать решение, от которого через год откажутся?

Мы рассмотрим все основные функции Paperclip и создадим довольно простое, но полезное приложение с:

  • Основная загрузка файлов
  • Проверки и обратные вызовы
  • Постобработка (с генерацией миниатюр)
  • Amazon S3

Я дам вам всю необходимую информацию, чтобы начать очень быстро, а также предоставлю ссылки для дальнейшего чтения.

Исходный код демонстрационного приложения можно найти на GitHub .

Рабочий пример демонстрационного приложения можно найти по адресу sitepoint-paperclip-uploader.herokuapp.com .

Некоторые препараты

Мы собираемся создать приложение, которое позволяет пользователям загружать свои собственные фотографии, а также просматривать фотографии, добавленные другими.

Создайте новое приложение Rails под названием SimpleUploader :

 $ rails new SimpleUploader -T 

Я использую здесь Rails 4, но Paperclip совместим с Rails 3.2 и даже Rails 2 (в этом случае просто используйте Papeclip 2.7).

Я собираюсь использовать Bootstrap для базовых стилей, но вы можете пропустить этот шаг:

Gemfile

 [...] gem 'bootstrap-sass' [...] 

Бегать

 $ bundle install 

И обновите необходимые файлы стилей:

application.scss

 @import 'bootstrap-sprockets'; @import 'bootstrap'; 

Теперь настройте макет, включив верхнее меню и стилизовав основной контент:

макеты / application.html.erb

 [...] <nav class="navbar navbar-inverse"> <div class="container"> <div class="navbar-header"> <%= link_to 'Simple Uploader', root_path, class: 'navbar-brand' %> </div> <div id="navbar" class="collapse navbar-collapse"> <ul class="nav navbar-nav"> <li><%= link_to 'Photos', root_path %></li> <li><%= link_to 'Upload Your Photo', new_photo_path %></li> </ul> </div> </div> </nav> <div class="container"> <% flash.each do |key, value| %> <div class="alert alert-<%= key %>"> <%= value %> </div> <% end %> <%= yield %> </div> [...] 

У нас будет только один контроллер:

photos_controller.rb

 class PhotosController < ApplicationController end 

Настройте соответствующие маршруты:

конфиг / routes.rb

 [...] resources :photos, only: [:new, :create, :index, :destroy] root to: 'photos#index' [...] 

Давайте создадим нашу фотомодель. А пока, добавьте только атрибут title … другие мы позаботимся позже:

 $ rails g model Photo title:string $ rake db:migrate 

В основном, приготовления сделаны. Теперь мы можем интегрировать Paperclip и конкретизировать контроллер. На следующий раздел!

Выполните загрузку файла за 60 секунд

Системные Требования

Прежде чем продолжить, потратьте пару минут, чтобы проверить, готова ли ваша система к использованию Paperclip. Несколько вещей должны быть сделаны:

  • Должен быть установлен Ruby 2.0 или выше и Rails 3.2 или 4. Вы все еще можете использовать Ruby 1.8.7 или Rails 2, но для этого придерживайтесь Paperclip 2.7. Эта версия имеет отдельный файл readme, и я не собираюсь описывать его здесь.
  • Пользователи Windows должны установить DevKit .
  • ImageMagick , отличная утилита для работы с изображениями, должна быть установлена. Доступны бинарные выпуски для всех основных платформ.
  • Команда file должна быть доступна. В Windows его нет, но DevKit его предоставляет. В некоторых случаях, однако, вам придется добавить его вручную — подробнее здесь .

Когда все пункты в этом маленьком контрольном списке выполнены, переходите к следующему шагу!

Интеграция скрепки

Вам не нужно быть супергероем, чтобы за одну минуту интегрировать функции загрузки файлов в ваше приложение — просто используйте Rails и Paperclip. Серьезно, это действительно легко начать. Прежде всего, включите драгоценный камень:

Gemfile

 [...] gem "paperclip", "~> 4.2" [...] 

и установить его:

 $ bundle install 

Теперь нам нужно добавить некоторые атрибуты в таблицу photos . Вы можете создать простую миграцию для этого, но у Paperclip есть хороший генератор:

 $ rails generate paperclip photo image 

Вот как выглядит итоговая миграция:

xxx_add_attachment_image_to_photos.rb

 class AddAttachmentImageToPhotos < ActiveRecord::Migration def self.up change_table :photos do |t| t.attachment :image end end def self.down remove_attachment :photos, :image end end 

Как видите, Paperclip даже предлагает свой собственный метод attachment и это действительно удобно. Что на самом деле делает этот метод? Он добавляет следующие столбцы в вашу таблицу:

  • {attachment}_file_name
  • {attachment}_file_size
  • {attachment}_content_type
  • {attachment}_updated_at

Имя поля вложения — это аргумент, предоставленный методу attachment (в нашем случае это изображение). На самом деле, требуется только поле {attachment}_file_name , поэтому, если вы не хотите хранить другую информацию, вместо этого создайте базовую миграцию.

Теперь примените миграцию:

 $ rake db:migrate 

и оснастите вашу модель функциональностью Paperclip:

модели / photo.rb

 [...] has_attached_file :image [...] 

Настройте контроллер:

photos_controller.rb

 class PhotosController < ApplicationController def index @photos = Photo.order('created_at') end def new @photo = Photo.new end def create @photo = Photo.new(photo_params) if @photo.save flash[:success] = "The photo was added!" redirect_to root_path else render 'new' end end private def photo_params params.require(:photo).permit(:image, :title) end end 

Обратите внимание, что вам нужно только разрешить image , а не image_file_name или другие поля. Они используются внутри Paperclip.

Вид на new действие:

просмотров / фото / new.html.erb

 <div class="page-header"><h1>Upload Photo</h1></div> <%= form_for @photo do |f| %> <%= render 'shared/errors', object: @photo %> <div class="form-group"> <%= f.label :title %> <%= f.text_field :title, class: 'form-control' %> </div> <div class="form-group"> <%= f.label :image %> <%= f.file_field :image, class: 'form-control'%> </div> <%= f.submit 'Upload', class: 'btn btn-primary' %> <% end %> 

Я предпочитаю извлекать сообщения об ошибках в отдельный раздел:

просмотров / общий / _errors.html.erb

 <% if object.errors.any? %> <div class="panel panel-warning errors"> <div class="panel-heading"> <h5><i class="glyphicon glyphicon-exclamation-sign"></i> Found errors while saving</h5> </div> <ul class="panel-body"> <% object.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %> 

Итак, как вы можете видеть, форма действительно проста. Просто используйте f.file_field :image для рендеринга соответствующего поля. Для Rails 3 вам также form_for :html => { :multipart => true } для метода form_for .

Однако, если вы попытаетесь загрузить файл сейчас, возникнет исключение. Почему? Потому что Paperclip заботится о безопасности вашего приложения, как никто другой! Начиная с версии 4, в вашей модели должна быть как минимум проверка типа контента, поэтому давайте добавим ее:

модели / photo.rb

 [...] validates_attachment :image, content_type: { content_type: ["image/jpeg", "image/gif", "image/png"] } [...] 

Обратите внимание, что для проверки правильности, ваша таблица должна иметь _content_type колонка.

Но что, если мы опытные разработчики и можем позаботиться о себе? Можем ли мы сказать Paperclip, что наши приложения не должны проверяться? Но конечно:

 do_not_validate_attachment_file_type :avatar 

Однако добавляйте эту строку, только если вы действительно понимаете, что делаете.

После проверки (или после явного отключения) вы можете загрузить свой первый файл. Однако перед этим давайте создадим представление index :

просмотров / фото / index.html.erb

 <div class="page-header"><h1>Photos</h1></div> <%= render @photos %> 

Мы просто @photos массив @photos . Частично _photo.html.erb будет использоваться по умолчанию, поэтому давайте создадим его:

просмотров / фото / _photo.html.erb

 <div class="media"> <div class="media-left"> <%= link_to image_tag(photo.image.url, class: 'media-object'), photo.image.url, target: '_blank' %> </div> <div class="media-body"> <h4 class="media-heading"><%= photo.title %></h4> </div> </div> 

Для отображения изображения мы используем старый image_tag метод image_tag . image.url , как вы уже догадались, возвращает URL к исходному изображению.

Идите и загрузите что-нибудь. Все должно работать нормально, но многое еще предстоит сделать, поэтому давайте перейдем к следующему шагу.

Генерация миниатюр и постобработка

Если вы загрузите большое изображение, оно полностью нарушит ваш макет страницы. Мы могли бы применить несколько простых стилей для уменьшения больших изображений внутри блоков .media , но это не совсем оптимально. Пользователям по-прежнему придется загружать полную версию изображения, размер которого затем будет изменен. Зачем тратить пропускную способность, как это? Давайте сгенерируем миниатюры вместо этого с помощью ImageMagick!

Каждое вложение может иметь набор «стилей», которые определяют различные версии исходного изображения. Давайте добавим один:

модели / photo.rb

 [...] has_attached_file :image, styles: { thumb: ["64x64#", :jpg] } [...] 

Что все это значит? thumb это название стиля. 64x64 , как вы уже догадались, означает, что эта версия изображения будет иметь размер 64x64 пикселей. # означает, что ImageMagick должен обрезать изображение, если оно больше указанных размеров (с центром тяжести).

Если вы просматриваете эту страницу, вы не найдете эту опцию, но Paperclip добавляет ее самостоятельно . Кстати, Стрекоза делает то же самое.

:jpg означает, что исходный файл должен быть преобразован в JPEG. Действительно аккуратный и чистый.

Вы можете заново генерировать эскизы в соответствии с новыми стилями с помощью следующей команды:

 $ rake paperclip:refresh:thumbnails CLASS=User 

Для воссоздания всех изображений (включая оригинальные) используйте:

 $ rake paperclip:refresh CLASS=Photo 

Чтобы создать только отсутствующие изображения:

 $ rake paperclip:refresh:missing_styles 

Теперь обновите частичное:

просмотров / фото / _photo.html.erb

 [...] <%= link_to image_tag(photo.image.url(:thumb), class: 'media-object'), photo.image.url, target: '_blank' %> [...] 

Мы просто предоставляем имя стиля методу url . Проверьте результат!

Давайте добавим еще несколько вариантов постобработки:

модели / photo.rb

 [...] has_attached_file :image, styles: { thumb: ["64x64#", :jpg], original: ['500x500>', :jpg] }, convert_options: { thumb: "-quality 75 -strip", original: "-quality 85 -strip" } [...] 

original означает, что следующие стили должны быть применены к исходному изображению. 500x500> означает, что размер изображения должен быть увеличен до 500 × 500 и уменьшен, если требуется. Если размеры изображения меньше, оно останется без изменений.

С помощью convert_options вы можете передать дополнительные параметры. В этом примере мы говорим, что качество изображения должно быть изменено до 75% (большой палец) и 85% (оригинальный) соответственно, чтобы уменьшить их размер. Опция -strip сообщает ImageMagick об -strip всей мета-информации.

Восстановить все изображения:

 $ rake paperclip:refresh CLASS=Photo 

и наблюдать за результатами.

Вы можете легко определять стили динамически , передавая lambda styles :

 has_attached_file :image, :styles => lambda { |attachment| { :thumb => (attachment.instance.title == 'Special' ? "100x100#" : "64x64#") } } 

В этом довольно наивном примере мы просто проверяем заголовок фотографии и, если он «Специальный», используем другие размеры для стилизации.

Скрепка предоставляет вам удобные обратные вызовы before_post_process и after_post_process для выполнения работы до и после постобработки. Вы можете даже определить специфичные для вложения обратные вызовы:

 before_{attachment}_post_process after_{attachment}_post_process 

Классическим примером использования обратных вызовов является проверка, является ли файл изображением или нет. Документация Paperclip представляет следующий фрагмент:

 before_post_process :skip_for_audio def skip_for_audio ! %w(audio/ogg application/ogg).include?(asset_content_type) end 

Если обратный вызов before возвращает false , постобработка не произойдет. Узнайте больше здесь .

Стоит отметить, что вы можете написать свой собственный процессор и сохранить его в lib / paperclip_processors — Paperclip автоматически загружает все файлы, размещенные здесь. Узнайте больше здесь .

Validations

Давайте вернемся к нашим проверкам и добавим еще пару. Например, я хочу, чтобы на всех фотографиях были установлены изображения и их размеры не превышали 500 КБ. Это легко:

модели / photo.rb

 validates_attachment :image, presence: true, content_type: { content_type: ["image/jpeg", "image/gif", "image/png"] }, size: { in: 0..500.kilobytes } 

Кстати, Paperclip также автоматически предотвращает подделку типов содержимого, то есть вы не можете, например, загрузить файл HTML в формате JPEG. Больше можно найти здесь .

Следует также помнить, что постобработка не начнется, если модель недействительна, то есть если проверка не пройдена.

Хранение изображений

По умолчанию все изображения размещаются в каталоге public / system вашего проекта Rails. Вы, вероятно, не хотите, чтобы пользователи видели этот путь и каким-то образом запутывали его. Скрепка может позаботиться об этом!

модели / photo.rb

 [...] has_attached_file :image, url: "/system/:hash.:extension", hash_secret: "abc123" [...] 

hash_secret должен установить, чтобы это работало.

Другой вопрос, который вы могли бы задать: что происходит с изображениями, когда соответствующая запись уничтожается? Узнайте это, добавив метод destroy в контроллер

photos_controller.rb

 [...] def destroy @photo = Photo.find(params[:id]) @photo.destroy flash[:success] = "The photo was destroyed." redirect_to root_path end [...] 

и изменение частичного:

просмотров / фото / _photo.html.erb

 <div class="media"> <div class="media-left"> <%= link_to image_tag(photo.image.url(:thumb), class: 'media-object'), photo.image.url, target: '_blank' %> </div> <div class="media-body"> <h4 class="media-heading"><%= photo.title %></h4> <%= link_to 'Remove', photo_path(photo), class: 'btn btn-danger', method: :delete, data: {confirm: 'Are you sure?'} %> </div> </div> 

Удалите несколько фотографий — соответствующие изображения также должны быть уничтожены. Это поведение, однако, можно изменить , установив preserve_files в true :

модели / photo.rb

 [...] has_attached_file :image, preserve_files: "true" [...] 

Это удобно, когда используется что-то вроде параноика для мягкого удаления записей.

Сидеть на облаке

Вы можете спросить, можем ли мы хранить изображения в CDN? Да, да, мы можем. В этой демонстрации я собираюсь показать вам, как использовать Amazon S3 в качестве хранилища файлов. Paperclip поддерживает другие варианты хранения, такие как Dropbox.

Загляните в новый драгоценный камень:

Gemfile

 [...] gem 'aws-sdk', '~> 1.6' [...] 

и беги

 $ bundle install 

На момент написания этой статьи Paperclip не поддерживал AWS SDK версии 2, поэтому пока не используйте его! Поддержка этого должна быть добавлена ​​в ближайшее время.

Теперь измените вашу модель (также удалите параметры url и hash_secret ):

модели / photo.rb

 [...] has_attached_file :image, styles: { thumb: ["64x64#", :jpg], original: ['500x500>', :jpg] }, convert_options: { thumb: "-quality 75 -strip", original: "-quality 85 -strip" }, storage: :s3, s3_credentials: {access_key_id: ENV["AWS_KEY"], secret_access_key: ENV["AWS_SECRET"]}, bucket: "YOUR_BUCKET" [...] 

На самом деле s3_credentials может принимать путь к файлу YAML с access_key_id и secret_access_key . Более того, вы можете предоставить различные значения для разных сред, например:

 development: access_key_id: 123... secret_access_key: 123... test: access_key_id: abc... secret_access_key: abc... production: access_key_id: 456... secret_access_key: 456... 

Это было бы излишним в нашем случае. Узнайте больше здесь .

Теперь, где взять эти ключи? Зарегистрируйтесь в AWS и перейдите в раздел «Учетные данные безопасности» (выпадающее меню в правом верхнем углу). Затем откройте вкладку «Пользователи» и нажмите кнопку «Создать новых пользователей». Мы собираемся создать специальную учетную запись пользователя службы, которая будет иметь только разрешения для работы с S3. Перед внедрением этой системы нам пришлось использовать корневой ключ, который не очень безопасен. Каждый, кто получает доступ к этому ключу, имеет полный доступ ко всем вашим сервисам AWS. Корневые ключи все еще поддерживаются Amazon, но я действительно не рекомендую их использовать.

После нажатия кнопки «Создать новых пользователей» введите имя пользователя и установите флажок «Сгенерировать ключ доступа для каждого пользователя». Нажмите Создать.

Теперь нажмите «Показать учетные данные безопасности пользователя», скопируйте и вставьте идентификатор ключа доступа и секретный ключ доступа в вашу модель. Это будет последний раз, когда учетные данные пользователя будут доступны для загрузки, поэтому вы можете также загрузить и сохранить их в безопасном месте. Будьте очень осторожны с этой парой ключей и никогда не открывайте ее (в частности, она не должна быть видна в GitHub). При развертывании в Heroku я использую переменные среды для хранения этой пары ключей.

Когда вы закончите, перейдите на страницу групп и нажмите Создать новую группу. Введите название группы (что-то вроде «доступ к s3»).

Нажмите Далее и введите «S3» в форму поиска на следующей странице. Это страница для выбора политик для группы. Выберите «AmazonS3FullAccess», нажмите «Далее» и «Создать». Кстати, вы можете создать свои собственные политики на соответствующей странице для определения пользовательских разрешений (например, если вы хотите предоставить кому-то доступ к платежной информации — по умолчанию такой политики нет).

Нажмите на вновь созданную группу и нажмите Добавить пользователей в группу. Далее просто выберите нового пользователя из списка и нажмите «Добавить пользователей». Теперь ваш пользователь имеет все необходимые разрешения для работы с S3. Если вы работали с Active Directory раньше, процесс должен быть очень знакомым.

Также не забудьте открыть консоль управления S3 и создать новую корзину для хранения ваших изображений (вы можете думать о корзинах как о простых папках). Укажите имя bucket параметре bucket в файле модели.

В принципе, вы готовы идти. Попробуйте загрузить некоторые файлы, используя новое хранилище!

Обратите внимание, что по умолчанию эти файлы загружаются с разрешениями public_read , что означает, что любой может их просмотреть, просто введя правильный URL-адрес. Используя опцию s3_permissions вы можете изменить это ( Вот информация о разрешениях S3).

Вики Paperclip также представляет хорошее руководство по ограничению доступа к изображениям S3. По сути, он предлагает установить s3_permissions в private и использовать специальное действие download в контроллере с устаревшим URL.

Последнее, что следует отметить, это то, что в целом S3 является платным сервисом. Однако в первый год вы получаете 5 ГБ дискового пространства, 20000 запросов GET и 2000 запросов PUT в месяц бесплатно. Полную информацию о ценах можно найти здесь .

Вывод

В этой статье мы обсудили Paperclip и его различные функции, такие как постобработка, обратные вызовы, обфускация URL-адресов и поддержка различных вариантов хранения. Если вы никогда не использовали Paperclip в своих проектах, я настоятельно рекомендую попробовать, когда возникнет необходимость в поддержке загрузки файлов.

Как всегда, отзывы очень приветствуются и не стесняйтесь задавать свои вопросы. Спасибо за чтение и до скорой встречи!