Статьи

Как загружать файлы с легкостью, используя DragonFly

Загрузка файлов, как правило, сложная область в веб-разработке. В этом уроке мы узнаем, как использовать Dragonfly , мощный гем Ruby, который позволяет легко и эффективно добавлять любые функции загрузки в проект Rails.


В нашем примере приложения будет отображен список пользователей, и для каждого из них мы сможем загрузить аватар и сохранить его. Кроме того, Dragonfly позволит нам:

  • Динамически манипулировать изображениями без сохранения дополнительных копий
  • Использование HTTP-кэширования для оптимизации загрузки нашего приложения

В этом уроке мы будем следовать подходу BDD [Behavior Driven Development], используя Cucumber и RSpec.


Вам нужно будет установить Imagemagick : вы можете обратиться к этой странице для установки двоичных файлов. Поскольку я базируюсь на платформе Mac, использую Homebrew , я могу просто набрать brew install imagemagick .

Вам также нужно будет клонировать базовое приложение Rails, которое мы будем использовать в качестве отправной точки.


Мы начнем с клонирования исходного репозитория и установки наших зависимостей:

1
2
git clone http://[email protected]/cloud8421/tutorial_dragonfly_template.git
cd tutorial_dragonfly_template

Это приложение требует как минимум Ruby 1.9.2 для запуска, однако я рекомендую вам использовать 1.9.3. Версия Rails — 3.2.1. Проект не включает .rvmrc или .rbenv .

Далее мы запускаем:

1
2
bundle install
bundle exec rake db:setup db:test:prepare db:seed

Это позаботится о зависимостях гемов и настройке базы данных (мы будем использовать sqlite, поэтому не нужно беспокоиться о конфигурации базы данных).

Чтобы проверить, что все работает должным образом, мы можем запустить:

1
2
bundle exec rspec
bundle exec cucumber

Вы должны обнаружить, что все тесты пройдены. Давайте рассмотрим вывод огурца:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Feature: managing user profile
  As a user
  In order to manage my data
  I want to access my user profile page
  
  Background:
    Given a user exists with email «[email protected]»
  
  Scenario: viewing my profile
    Given I am on the home page
    When I follow «Profile» for «[email protected]»
    Then I should be on the profile page for «[email protected]»
  
  Scenario: editing my profile
    Given I am on the profile page for «[email protected]»
    When I follow «Edit»
    And I change my email with «[email protected]»
    And I click «Save»
    Then I should be on the profile page for «[email protected]»
    And I should see «User updated»
  
2 scenarios (2 passed)
11 steps (11 passed)
0m0.710s

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

Теперь попробуйте запустить приложение:

1
rails s

Если вы откроете http:://localhost:3000 в браузере, вы найдете список пользователей (мы предварительно заполнили базу 40 случайными записями благодаря жемчужине Faker ).

На данный момент каждый из пользователей будет иметь небольшой аватар размером 16×16 пикселей и большой аватар-заполнитель на странице своего профиля. Если вы отредактируете пользователя, вы сможете изменить его данные (имя, фамилию и пароль), но если вы попытаетесь загрузить аватар, он не будет сохранен.

Не стесняйтесь просматривать кодовую базу: приложение использует Простую форму для генерации представлений формы и Twitter Bootstrap для CSS и макета, поскольку они прекрасно интегрируются и очень помогают в ускорении процесса прототипирования.


Мы начнем с добавления нового сценария в features/managing_profile.feature :

1
2
3
4
5
6
7
8
Scenario: adding an avatar
  Given I am on the profile page for «[email protected]»
  When I follow «Edit»
  And I upload the mustache avatar
  And I click «Save»
  Then I should be on the profile page for «[email protected]»
  And the profile should show «the mustache avatar»

Эта функция довольно features/step_definitions/user_steps.rb , но для добавления в features/step_definitions/user_steps.rb требуется несколько дополнительных шагов:

01
02
03
04
05
06
07
08
09
10
11
12
13
When /^I upload the mustache avatar$/ do
  attach_file ‘user[avatar_image]’, Rails.root + ‘spec/fixtures/mustache_avatar.jpg’
end
  
Then /^the profile should show «([^»]*)»$/ do |image|
  pattern = case image
  when ‘the mustache avatar’
    /mustache_avatar/
  end
  n = Nokogiri::HTML(page.body)
  n.xpath(«.//img[@class=’thumbnail’]»).first[‘src’].should =~ pattern
end

Этот шаг предполагает, что у вас есть изображение mustache_avatar.jpg внутри spec/fixtures . Как вы можете догадаться, это всего лишь пример; это может быть что угодно.

Первый шаг использует Capybara, чтобы найти поле файла user[avatar_image] и загрузить файл. Обратите внимание, что мы предполагаем, что у нас будет атрибут avatar_image в модели User .

На втором этапе используется Nokogiri (мощная библиотека для разбора HTML / XML) и XPath, чтобы проанализировать содержимое страницы профиля и найти первый тег img с помощью класса thumbnail и проверить, что атрибут src содержит mustache_avatar .

Если вы сейчас запустите cucumber , этот сценарий вызовет ошибку, так как отсутствует поле файла с указанным нами именем. Пришло время сосредоточиться на модели User .


Прежде чем интегрировать Dragonfly с моделью User , давайте добавим пару спецификаций в user_spec.rb .

Мы можем добавить новый блок сразу после контекста attributes :

1
2
3
4
5
6
7
context «avatar attributes» do
  
  it { should respond_to(:avatar_image) }
  
  it { should allow_mass_assignment_of(:avatar_image) }
  
end

Мы проверяем, что у пользователя есть атрибут avatar_image и, поскольку мы будем обновлять этот атрибут через форму, он должен быть доступен (вторая спецификация).

Теперь мы можем установить Dragonfly: сделав это, мы получим эти характеристики зелеными.

Давайте добавим следующие строки в Gemfile:

1
2
gem ‘rack-cache’, require: ‘rack/cache’
gem ‘dragonfly’, ‘~>0.9.10’

Далее мы можем запустить bundle install . Rack-cache необходим при разработке, так как это самый простой вариант для HTTP-кеширования. Он также может быть использован в производстве, даже если более надежные решения (такие как Varnish или Squid ) будут лучше.

Нам также нужно добавить инициализатор Dragonfly. Давайте создадим файл config/initializers/dragonfly.rb и добавим следующее:

1
2
3
4
5
6
7
require ‘dragonfly’
  
app = Dragonfly[:images]
app.configure_with(:imagemagick)
app.configure_with(:rails)
  
app.define_macro(ActiveRecord::Base, :image_accessor)

Это ванильная конфигурация Dragonfly: она устанавливает приложение Dragonfly и настраивает его с помощью необходимого модуля. Он также добавляет новый макрос в ActiveRecord который мы сможем использовать для расширения нашей модели User .

Нам нужно обновить config/application.rb и добавить новую директиву в конфигурацию (прямо перед блоком config.generators ):

1
2
3
4
5
6
7
config.middleware.insert 0, ‘Rack::Cache’, {
  verbose: true,
  metastore: URI.encode(«file:#{Rails.root}/tmp/dragonfly/cache/meta»),
  entitystore: URI.encode(«file:#{Rails.root}/tmp/dragonfly/cache/body»)
} unless Rails.env.production?
  
config.middleware.insert_after ‘Rack::Cache’, ‘Dragonfly::Middleware’, :images

Не вдаваясь в подробности, мы настраиваем Rack::Cache (за исключением рабочего, где он включен по умолчанию) и настраиваем Dragonfly для его использования.

Мы будем хранить наши изображения на диске, однако нам нужен способ отслеживать связь с пользователем. Самый простой вариант — добавить два столбца в пользовательскую таблицу с помощью миграции:

1
2
rails g migration add_avatar_to_users avatar_image_uid:string avatar_image_name:string
bundle exec rake db:migrate db:test:prepare

Еще раз, это прямо из документации Dragonfly : нам нужен столбец avatar_image_uid для уникальной идентификации файла аватара и имя avatar_image_name для хранения его исходного имени файла (последний столбец не нужен строго, но он позволяет генерировать URL-адреса изображений, которые заканчиваются с оригинальным именем файла).

Наконец, мы можем обновить модель User :

1
2
3
4
5
6
class User < ActiveRecord::Base
  
  image_accessor :avatar_image
  
  attr_accessible :email, :first_name, :last_name, :avatar_image
  …

Метод image_accessor доступен инициализатору Dragonfly и требует только имени атрибута. Мы также делаем этот атрибут доступным в строке ниже.

Запуск rspec теперь должен показать все спецификации зелеными.



Чтобы протестировать функцию загрузки, мы можем добавить контекст к users_controller_spec.rb в блоке PUT update :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
context «avatar image» do
  
  let!(:image_file) { fixture_file_upload(‘/mustache_avatar.jpg’, ‘image/jpg’) }
  
  context «uploading an avatar» do
  
    before do
      put :update, id: user.id, user: { avatar_image: image_file }
    end
  
    it «should effectively save the image record on the user» do
      user.reload
      user.avatar_image_name.should =~ /mustache_avatar/
    end
  
  end
  
end

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

Так как эта функциональность используется в Dragonfly, нам не нужно писать код для ее прохождения.

Теперь мы должны обновить наши представления, чтобы показать аватар. Давайте начнем со страницы показа пользователя и откроем app/views/users/show.html.erb и обновим ее следующим содержанием:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
<div class=»row»>
<div class=»span4″>
    <% if @user.avatar_image.present?
      <%= image_tag @user.avatar_image.url, class: ‘thumbnail’ %>
    <% else %>
      <img src=»http://placehold.it/400×400&amp;text=Super+cool+avatar» alt=»Super cool avatar» class=»thumbnail»>
    <% end %>
  </div>
  <div class=»span8″>
    <hr /> <h2><%= @user.name %></h2>
    <h4><%= @user.email %></h4>
    <hr />
    <%= link_to ‘Edit’, edit_user_path(@user), class: «btn» %>
  </div>
</div>

Далее мы можем обновить app/views/users/edit.html.erb :

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
<%= simple_form_for @user, multipart: true do |f|
<div class=»row»>
  <div class=»span4″>
    <div class=»thumbnail-wrapper»>
      <% if @user.avatar_image.present?
        <%= image_tag @user.avatar_image.url, class: ‘thumbnail’ %>
      <% else %>
        <img src=»http://placehold.it/400×400&amp;text=Super+cool+avatar» alt=»Super cool avatar» class=»thumbnail»>
      <% end %>
      <div class=»caption form-inline»>
        <%= f.input :avatar_image, as: :file %>
      </div>
    </div>
  </div>
  <div class=»span8″>
    <div class=»well»>
      <%= f.input :first_name %>
      <%= f.input :last_name %>
      <%= f.input :email %>
    </div>
    <div class=»form-actions»>
      <%= f.submit ‘Save’, class: «btn btn-primary» %>
    </div>
  </div>
</div>
<% end %>

Мы можем показать аватар пользователя простым вызовом @user.avatar_image.url . Это вернет URL-адрес неизмененной версии аватара, загруженной пользователем.

Если вы запустите cucumber сейчас, вы увидите зеленую функцию. Не стесняйтесь попробовать это в браузере!

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

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


Начнем с открытия файла user_spec.rb и добавления нового блока спецификации:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
context «avatar attributes» do
  
  %w(avatar_image retained_avatar_image remove_avatar_image).each do |attr|
    it { should respond_to(attr.to_sym) }
  end
  
  %w(avatar_image retained_avatar_image remove_avatar_image).each do |attr|
    it { should allow_mass_assignment_of(attr.to_sym) }
  end
  
  it «should validate the file size of the avatar» do
    user.avatar_image = Rails.root + ‘spec/fixtures/huge_size_avatar.jpg’
    user.should_not be_valid # size is > 100 KB
  end
  
  it «should validate the format of the avatar» do
    user.avatar_image = Rails.root + ‘spec/fixtures/dummy.txt’
    user.should_not be_valid
  end
  
end

Мы проверяем наличие и разрешаем «массовое назначение» для дополнительных атрибутов, которые мы будем использовать для улучшения пользовательской формы ( :retained_avatar_image и :remove_avatar_image ).

Кроме того, мы также проверяем, что наша пользовательская модель не будет принимать большие загрузки (более 200 КБ) и файлы, которые не являются изображениями. Для обоих случаев нам нужно добавить два файла фикстуры (изображение с указанным именем и размером более 200 КБ и текстовый файл с любым содержимым).

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

01
02
03
04
05
06
07
08
09
10
11
12
13
  
attr_accessible :email, :first_name, :last_name, :avatar_image, :retained_avatar_image, :remove_avatar_image
  
  
validates_size_of :avatar_image, maximum: 100.kilobytes
  
validates_property :format, of: :avatar_image, in: [:jpeg, :png, :gif, :jpg]
  
validates_property :mime_type, of: :avatar_image,
                               in: [‘image/jpg’, ‘image/jpeg’, ‘image/png’, ‘image/gif’],
                               case_sensitive: false

Эти правила довольно эффективны: обратите внимание, что в дополнение к проверке формата мы также проверяем тип mime для большей безопасности. Будучи изображением, мы допускаем файлы jpg, png и gif.

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


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

1
2
user.avatar_image.thumb(‘100×100’).url
user.avatar_image.process(:greyscale).url

Эти методы создадут обработанную версию этого изображения с уникальным хешем, и благодаря нашему уровню кэширования ImageMagick будет вызываться только один раз для каждого изображения. После этого изображение будет обслуживаться прямо из кеша.

Вы можете использовать множество встроенных методов или просто создать свой собственный, в документации Dragonfly есть много примеров .

Давайте вернемся к нашей странице edit пользователя и обновим код представления:

1
2
3
4
5
<% if @user.avatar_image.present?
  <%= image_tag @user.avatar_image.thumb(‘400×400#’).url, class: ‘thumbnail’ %>
<% else %>

Мы сделаем то же самое для страницы show пользователя:

1
2
3
4
5
<% if @user.avatar_image.present?
  <%= image_tag @user.avatar_image.thumb(‘400×400#’).url, class: ‘thumbnail’ %>
<% else %>

Мы увеличиваем размер изображения до 400 x 400 пикселей. Параметр # также указывает ImageMagick обрезать изображение, сохраняя центральное положение. Вы можете видеть, что у нас один и тот же код в двух местах, поэтому давайте сменим его на частичный views/users/_avatar_image.html.erb

1
2
3
4
5
<% if @user.avatar_image.present?
  <%= image_tag @user.avatar_image.thumb(‘400×400#’).url, class: ‘thumbnail’ %>
<% else %>
  <img src=»http://placehold.it/400×400&amp;text=Super+cool+avatar» alt=»Super cool avatar» class=»thumbnail»>
<% end %>

Затем мы можем заменить содержимое контейнера .thumbnail простым вызовом:

1
2
3
<div class=»thumbnail»>
  <%= render ‘avatar_image’ %>
</div>

Мы можем сделать еще лучше, переместив аргумент thumb из частичного. Давайте обновим _avatar_image.html.erb :

1
2
3
4
5
<% if user.avatar_image.present?
  <%= image_tag user.avatar_image.thumb(args).url %>
<% else %>
  <img src=»http://placehold.it/<%= args.gsub(/\W/, ») %>&amp;text=Super+cool+avatar» alt=»Super cool avatar»>
<% end %>

Теперь мы можем вызвать наше частичное с двумя аргументами: один для желаемого аспекта и один для пользователя:

1
<%= render ‘avatar_image’, args: ‘400×400#’, user: @user %>

Мы можем использовать приведенный выше фрагмент при edit и show представлений, в то время как мы можем вызывать его следующим образом в views/users/_user_table.html.erb , где мы показываем небольшие эскизы.

1
2
3
4
5
6
7
<td><%= link_to ‘Profile’, user_path(user) %></td>
<td>
  <%= render ‘avatar_image’, args: ’16×16#’, user: user %>
</td>
<td><%= user.first_name %></td>

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



Dragonfly создает два дополнительных атрибута, которые мы можем использовать в форме:

  • retained_avatar_image : хранит загруженное изображение между перезагрузками. Если проверки для другого поля формы (скажем, по электронной почте) не пройдены и страница перезагружена, загруженное изображение все еще доступно без необходимости его повторной загрузки. Мы будем использовать его непосредственно в форме.
  • remove_avatar_image : когда это правда, текущее изображение аватара будет удалено как из пользовательской записи, так и с диска.

Мы можем проверить удаление аватара, добавив дополнительную спецификацию к users_controller_spec.rb в блоке avatar image :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
context «removing an avatar» do
  
  before do
    user.avatar_image = Rails.root + ‘spec/fixtures/mustache_avatar.jpg’
    user.save
  end
  
  it «should remove the avatar from the user» do
    put :update, id: user.id, user: { remove_avatar_image: «1» }
    user.reload
    user.avatar_image_name.should be_nil
  end
  
end

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

Давайте тогда добавим еще одну функцию в managing_profile.feature :

1
2
3
4
5
6
7
8
Scenario: removing an avatar
  Given the user with email «[email protected]» has the mustache avatar
  And I am on the profile page for «[email protected]»
  When I follow «Edit»
  And I check «Remove avatar image»
  And I click «Save»
  Then I should be on the profile page for «[email protected]»
  And the profile should show «the placeholder avatar»

Как обычно, нам нужно добавить несколько шагов в user_steps.rb и обновить один, чтобы добавить user_steps.rb для аватара заполнителя:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
Given /^the user with email «([^»]*)» has the mustache avatar$/ do |email|
  u = User.find_by_email(email)
  u.avatar_image = Rails.root + ‘spec/fixtures/mustache_avatar.jpg’
  u.save
end
  
When /^I check «([^»]*)»$/ do |checkbox|
  check checkbox
end
  
Then /^the profile should show «([^»]*)»$/ do |image|
  pattern = case image
  when ‘the placeholder avatar’
    /placehold.it/
  when ‘the mustache avatar’
    /mustache_avatar/
  end
  n = Nokogiri::HTML(page.body)
  n.xpath(«.//img[@class=’thumbnail’]»).first[‘src’].should =~ pattern
end

Нам также необходимо добавить два дополнительных поля в форму edit :

1
2
3
4
5
6
7
<div class=»caption form-inline»>
  <%= f.input :retained_avatar_image, as: :hidden %>
  <%= f.input :avatar_image, as: :file, label: false %>
  <%= f.input :remove_avatar_image, as: :boolean %>
</div>

Это сделает нашу функцию успешной.


Чтобы не иметь больших и слишком подробных функций, мы можем протестировать те же функции в спецификации запроса.

Давайте создадим новый файл с именем spec/requests/user_flow_spec.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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
require ‘spec_helper’
  
describe «User flow» do
  
  let!(:user) { Factory(:user, email: «[email protected]») }
  
  describe «viewing the profile» do
  
    it «should show the profile for the user» do
      visit ‘/’
      page.find(‘tr’, text: user.email).click_link(«Profile»)
      current_path = URI.parse(current_url).path
      current_path.should == user_path(user)
    end
  
  end
  
  describe «updating profile data» do
  
    it «should save the changes» do
      visit ‘/’
      page.find(‘tr’, text: user.email).click_link(«Profile»)
      click_link ‘Edit’
      fill_in :email, with: «[email protected]»
      click_button ‘Save’
      current_path.should == user_path(user)
      page.should have_content «User updated»
    end
  
  end
  
  describe «managing the avatar» do
  
    it «should save the uploaded avatar» do
      user.avatar_image = Rails.root + ‘spec/fixtures/mustache_avatar.jpg’
      user.save
      visit user_path(user)
      click_link ‘Edit’
      attach_file ‘user[avatar_image]’, Rails.root + ‘spec/fixtures/mustache_avatar.jpg’
      click_button ‘Save’
      current_path.should == user_path(user)
      page.should have_content «User updated»
      n = Nokogiri::HTML(page.body)
      n.xpath(«.//img[@class=’thumbnail’]»).first[‘src’].should =~ /mustache_avatar/
    end
  
    it «should remove the avatar if requested» do
      user.avatar_image = Rails.root + ‘spec/fixtures/mustache_avatar.jpg’
      user.save
      visit user_path(user)
      click_link ‘Edit’
      check «Remove avatar image»
      click_button ‘Save’
      current_path.should == user_path(user)
      page.should have_content «User updated»
      n = Nokogiri::HTML(page.body)
      n.xpath(«.//img[@class=’thumbnail’]»).first[‘src’].should =~ /placehold.it/
    end
  
  end
  
end

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

Теперь мы можем сократить managing_profile.feature :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
Feature: managing user profile
  As a user
  In order to manage my data
  I want to access my user profile page
  
  Background:
    Given a user exists with email «[email protected]»
  
  Scenario: editing my profile
    Given I change the email with «[email protected]» for «[email protected]»
    Then I should see «User updated»
  
  Scenario: adding an avatar
    Given I upload the mustache avatar for «[email protected]»
    Then the profile should show «the mustache avatar»
  
  Scenario: removing an avatar
    Given the user «[email protected]» has the mustache avatar and I remove it
    Then the user «[email protected]» should have «the placeholder avatar»

Обновлен user_steps.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
45
46
47
48
49
50
51
52
53
54
55
56
57
Given /^a user exists with email «([^»]*)»$/ do |email|
  Factory(:user, email: email)
end
  
Given /^the user with email «([^»]*)» has the mustache avatar$/ do |email|
  u = User.find_by_email(email)
  u.avatar_image = Rails.root + ‘spec/fixtures/mustache_avatar.jpg’
  u.save
end
  
Then /^I should see «([^»]*)»$/ do |content|
  page.should have_content(content)
end
  
Then /^the profile should show «([^»]*)»$/ do |image|
  n = Nokogiri::HTML(page.body)
  n.xpath(«.//img[@class=’thumbnail’]»).first[‘src’].should =~ pattern_for(image)
end
  
Given /^I change the email with «([^»]*)» for «([^»]*)»$/ do |new_email, old_email|
  u = User.find_by_email(old_email)
  visit edit_user_path(u)
  fill_in :email, with: new_email
  click_button ‘Save’
end
  
Given /^I upload the mustache avatar for «([^»]*)»$/ do |email|
  u = User.find_by_email(email)
  visit edit_user_path(u)
  attach_file ‘user[avatar_image]’, Rails.root + ‘spec/fixtures/mustache_avatar.jpg’
  click_button ‘Save’
end
  
Given /^the user «([^»]*)» has the mustache avatar and I remove it$/ do |email|
  u = User.find_by_email(email)
  u.avatar_image = Rails.root + ‘spec/fixtures/mustache_avatar.jpg’
  u.save
  visit edit_user_path(u)
  check «Remove avatar image»
  click_button ‘Save’
end
  
Then /^the user «([^»]*)» should have «([^»]*)»$/ do |email, image|
  u = User.find_by_email(email)
  visit user_path(u)
  n = Nokogiri::HTML(page.body)
  n.xpath(«.//img[@class=’thumbnail’]»).first[‘src’].should =~ pattern_for(image)
end
  
def pattern_for(image_name)
  case image_name
  when ‘the placeholder avatar’
    /placehold.it/
  when ‘the mustache avatar’
    /mustache_avatar/
  end
end

В качестве последнего шага мы можем легко добавить поддержку S3 для хранения файлов аватаров. Давайте снова откроем config/initializers/dragonfly.rb и обновим блок конфигурации:

01
02
03
04
05
06
07
08
09
10
11
Dragonfly::App[:images].configure do |c|
  
  c.datastore = Dragonfly::DataStorage::S3DataStore.new
  
  c.datastore.configure do |d|
    d.bucket_name = ‘dragonfly_tutorial’
    d.access_key_id = ‘some_access_key_id’
    d.secret_access_key = ‘some_secret_access_key’
  end
  
end unless %(development test cucumber).include?

Это будет работать «из коробки» и повлияет только на производство (или любую другую среду, которая не указана в файле). Dragonfly по умолчанию будет хранилищем файловой системы для всех остальных случаев.


Я надеюсь, что вы нашли этот урок интересным, и вам удалось найти несколько интересных деталей.

Я рекомендую вам обратиться к странице Dragonfly GitHub для получения обширной документации и других примеров использования — даже вне приложения Rails.