Статьи

Загрузка с помощью направляющих и скрепки

Это последняя статья в серии «Загрузка с Rails». В последние пару месяцев мы уже обсуждали драгоценности Shrine, Dragonfly и Carrierwave. Сегодняшний гость – Paperclip от Thoughtbot, компании, которая управляет такими драгоценными камнями, как FactoryGirl и Bourbon.

Paperclip, вероятно, является самым популярным решением для управления вложениями для Rails (более 13 миллионов загрузок), и на то есть веская причина: в нем много функций, большое сообщество и подробная документация. Надеюсь, вы хотите узнать больше об этом драгоценном камне!

В этой статье вы узнаете, как:

  • Подготовьтесь к установке скрепки
  • Интеграция Paperclip в приложение Rails
  • Добавить вложения проверки
  • Генерация миниатюр и обработка изображений
  • Запутывать URL
  • Хранить вложения на Amazon S3
  • Безопасные файлы в облаке с помощью логики авторизации

Исходный код этой статьи доступен на GitHub .

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

  • Последняя версия Paperclip поддерживает Rails 4.2+ и Ruby 2.1+. Этот драгоценный камень также можно использовать без Rails.
  • ImageMagick должен быть установлен на вашем компьютере (он доступен для всех основных платформ), и Paperclip должен иметь к нему доступ.
  • Команда file должна быть доступна из командной строки. Для Windows он доступен через Development Kit, поэтому следуйте этим инструкциям, если у вас еще не установлен DevKit.

Когда вы будете готовы, продолжайте и создайте новое приложение Rails (я буду использовать Rails 5.0.2) без набора тестов по умолчанию:

1
rails new UploadingWithPaperclip -T

Вставьте в скрепку драгоценный камень:

1
gem “paperclip”, “~> 5.1”

Установите это:

1
bundle install

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

1
2
rails g model Book title:string description:text image:attachment author:string
rails db:migrate

Обратите внимание на тип attachment который представлен для нас Paperclip. Под капотом он собирается создать для нас четыре поля:

  • image_file_name
  • image_file_size
  • image_content_type
  • image_updated_at

В отличие от драгоценных камней Shrine и Carrierwave, Paperclip не имеет отдельного файла с конфигурациями. Все настройки определяются внутри самой модели с has_attached_file метода has_attached_file , поэтому добавьте его сейчас:

1
has_attached_file :image

Прежде чем перейти к основной части, давайте также создадим контроллер вместе с некоторыми видами и маршрутами.

Наш контроллер будет очень простым:

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
class BooksController < ApplicationController
  before_action :set_book, only: [:show, :download]
 
  def index
    @books = Book.order(‘created_at DESC’)
  end
 
  def new
    @book = Book.new
  end
 
  def show
  end
 
  def create
    @book = Book.new(book_params)
    if @book.save
      redirect_to books_path
    else
      render :new
    end
  end
 
  private
 
  def book_params
    params.require(:book).permit(:title, :description, :image, :author)
  end
 
  def set_book
    @book = Book.find(params[:id])
  end
end

Вот индексное представление и частичное:

1
2
3
4
5
6
<h1>Bookshelf</h1>
 
<%= link_to ‘Add book’, new_book_path %>
<ul>
  <%= render @books %>
</ul>
1
2
3
<li>
  <strong><%= link_to book.title, book_path(book) %></strong> by <%= book.author %>
</li>

Теперь маршруты:

1
2
3
4
Rails.application.routes.draw do
  resources :books
  root to: ‘books#index’
end

Ницца! Теперь перейдем к основному разделу и закодируем новое действие и форму.

В общем, загружать с помощью Paperclip легко. Вам нужно только разрешить соответствующий атрибут (в нашем случае это атрибут image , и мы уже разрешили его) и представить поле формы в вашей форме. Давай сделаем это сейчас:

1
2
3
<h1>Add book</h1>
 
<%= render ‘form’, book: @book %>
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<%= form_for book do |f|
  <div>
    <%= f.label :title %>
    <%= f.text_field :title %>
  </div>
 
  <div>
    <%= f.label :author %>
    <%= f.text_field :author %>
  </div>
 
  <div>
    <%= f.label :description %>
    <%= f.text_area :description %>
  </div>
 
  <div>
    <%= f.label :image %>
    <%= f.file_field :image %>
  </div>
 
  <%= f.submit %>
<% end %>

С этой настройкой вы уже можете начинать выполнять загрузку, но также неплохо бы ввести некоторые проверки.

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

1
2
3
validates_attachment :image,
                      content_type: { content_type: /\Aimage\/.*\z/ },
                      size: { less_than: 1.megabyte }

Код действительно прост, как вы можете видеть. Мы требуем, чтобы файл был размером менее 1 мегабайта. Обратите внимание, что если проверка не пройдена, никакая постобработка не будет выполнена. В Paperclip уже есть некоторые сообщения об ошибках, заданные для английского языка, но если вы хотите поддерживать другие языки, включите гем paperclip-i18n в свой Gemfile .

Еще одна важная вещь, которую стоит упомянуть, это то, что Paperclip требует от вас проверить тип содержимого или имя файла всех вложений, в противном случае это вызовет ошибку. Если вы на 100% уверены, что вам не нужны такие проверки (что является редким случаем), используйте do_not_validate_attachment_file_type чтобы явно do_not_validate_attachment_file_type , какие поля проверять не следует.

Добавив проверки, давайте также отобразим сообщения об ошибках в нашей форме:

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 %>
1
<%= render ‘shared/errors’, object: book %>

Итак, теперь загруженные изображения должны отображаться как-то. Это делается с помощью помощника image_tag и метода url . Создать представление шоу :

1
2
3
4
5
<h1><%= @book.title %> by <%= @book.author %></h1>
 
<%= image_tag(@book.image.url) if @book.image.exists?
 
<p><%= @book.description %></p>

Мы отображаем изображение, только если оно действительно существует на диске. Более того, если вы используете облачное хранилище, то Paperclip выполнит сетевой запрос и проверит наличие файла. Конечно, эта операция может занять некоторое время, так что вы могли бы использовать present? или file? вместо этого: они просто image_file_name заполнено ли поле image_file_name каким-либо содержимым.

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

1
public/system

Тем не менее, отображение полного URI для файла не всегда может быть хорошей идеей, и вам может потребоваться каким-то образом его скрыть. Самый простой способ включить запутывание – предоставить два параметра has_attached_file method :

1
2
url: “/system/:hash.:extension”,
hash_secret: “longSecretString”

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

1
rails secret

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

Предположим, что мы хотим, чтобы исходное изображение и его эскиз были преобразованы в формат JPEG. Миниатюра должна быть обрезана до 300x300px:

1
2
3
4
5
has_attached_file :image,
                   styles: {
                       thumb: [“300×300#”, :jpeg],
                       original: [:jpeg]
                   }

# – настройка геометрии, означающая: «Обрезать при необходимости, сохраняя соотношение сторон».

Мы также можем предоставить дополнительные параметры преобразования для каждого стиля. Например, давайте обеспечим 70% качества больших пальцев при удалении всех метаданных и 90% качества исходного изображения, чтобы сделать его немного меньше:

1
2
3
4
5
6
7
8
9
has_attached_file :image,
                   styles: {
                       thumb: [“300×300#”, :jpeg],
                       original: [:jpeg]
                   },
                   convert_options: {
                       thumb: “-quality 70 -strip”,
                       original: “-quality 90”
                   }

Ницца! Отобразите эскиз и укажите ссылку на исходное изображение:

1
<%= link_to(image_tag(@book.image.url(:thumb)), @book.image.url, target: ‘_blank’) if @book.image.exists?

Обратите внимание, что в отличие от Carrierwave, например, Paperclip не позволяет писать @book.image.thumb.url .

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

  • rake paperclip:refresh:thumbnails CLASS=Book
  • rake paperclip:refresh:missing_styles CLASS=Book
  • rake paperclip:refresh CLASS=Book

Как и все аналогичные решения, Paperclip позволяет загружать файлы в облако. Он имеет встроенную поддержку адаптеров S3 и Fog, но есть также сторонние гемы для Azure и Dropbox. В этом разделе я покажу вам, как интегрировать Paperclip с Amazon S3. Во-первых, добавьте гем aws-sdk :

1
gem ‘aws-sdk’

Установите это:

1
bundle install

Затем предоставьте новый набор параметров для метода has_attached_file :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
has_attached_file :image,
                   styles: {
                       thumb: [“300×300#”, :jpeg],
                       original: [:jpeg]
                   },
                   convert_options: {
                       thumb: “-quality 70 -strip”,
                       original: “-quality 90”
                   },
                   storage: :s3,
                   s3_credentials: {
                       access_key_id: ENV[“S3_KEY”],
                       secret_access_key: ENV[“S3_SECRET”],
                       bucket: ENV[“S3_BUCKET”]
                   },
                   s3_region: ENV[“S3_REGION”]

Здесь я придерживаюсь драгоценного камня dotenv-rails для установки переменных окружения. Вы можете предоставить все значения непосредственно внутри модели, но не делать ее общедоступной.

Что интересно, s3_credentials также принимает путь к файлу YAML, содержащему ваши ключи и имя s3_credentials . Кроме того, вы можете установить разные значения для разных сред, например:

1
2
3
4
5
6
development:
  access_key_id: key1
  secret_access_key: secret1
production:
  access_key_id: key2
  secret_access_key: secret2

Это оно! Все загружаемые вами файлы теперь будут находиться в вашей корзине S3.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
has_attached_file :image,
                   styles: {
                       thumb: [“300×300#”, :jpeg],
                       original: [:jpeg]
                   },
                   convert_options: {
                       thumb: “-quality 70 -strip”,
                       original: “-quality 90”
                   },
                   storage: :s3,
                   s3_credentials: {
                       access_key_id: ENV[“S3_KEY”],
                       secret_access_key: ENV[“S3_SECRET”],
                       bucket: ENV[“S3_BUCKET”]
                   },
                   s3_region: ENV[“S3_REGION”],
                   s3_permissions: :private

Однако теперь никто кроме вас не сможет увидеть файлы. Поэтому давайте создадим новое действие download для BooksController :

1
2
3
def download
   redirect_to @book.image.expiring_url
 end

Это действие просто перенаправит пользователей на изображение по истекающей ссылке. Используя этот подход, вы можете теперь представить любую логику авторизации, используя такие гемы, как CanCanCan или Pundit .

Не забудьте установить маршрут участника:

1
2
3
4
5
resources :books do
   member do
     get ‘download’
   end
 end

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

1
link_to(‘View image’, download_book_path(@book), target: ‘_blank’)

Мы подошли к концу этой статьи! Сегодня мы увидели Paperclip, решение для управления вложениями для Rails, и обсудили его основные концепции. У этого драгоценного камня гораздо больше, поэтому обязательно ознакомьтесь с его документацией .

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

Спасибо, что остаетесь со мной, и до скорой встречи!