загрузка изображений AJAX’ом на Rails 3. CarrierWave+Uploadify
Больше года назад для загрузки изображений я выбрал Paperclip, но на данный момент поддавшись модным тенденциям переходим на аяксовую форму загрузки используя гем CarrierWave и собственно саму аяксовую примочку Uploadify.
Про это уже много где написано, но все как-то разрозненно и вот я решил собрать все вместе.
CarrierWave
установка
Прежде всего надо установить RMagick и ImageMagick (здесь показан вариант для RH-линуксов):
# yum install ImageMagick ruby-RMagick ImageMagick-devel -y |
Теперь вставляем в Gemfile
:
gem 'carrierwave' gem 'rmagick' |
и бандлим:
bundle install |
настройка в приложении
Для начала создадим тестовое приложение (на всякий случай скажу, что бандлили мы уже непосредственно в приложении, здесь же я создаю новое приложение лишь чтобы были понятны мои модели):
$ rails new Testapp
$ cd Testapp
$ rails g scaffold Picture image:string
$ rake db:migrate |
После установки CarrierWave появится новый генератор uploader
, чем и надо воспользоваться чтобы создать сам аплоадер:
$ rails g uploader image |
После этого появиться файл app/uploaders/image_uploader.rb
, где лежат настройки этого аплодера.
Теперь надо смонтировать загрузчик и модель, для этого открываем модель Picture app/models/picture.rb
и вставляем туда:
mount_uploader :image, ImageUploader |
хелперы форм
Форма для Picture будет выглядеть должна выглядеть примерно так:
1 2 3 4 | <%= form_for(@picture,:html => {:multipart =>true}) do |f| %> <%= f.file_field :image%> <%= f.submit %> <%end%> |
Для отображения закачанного изображения используем хелпер:
1 | <%= image_tag task.image_url.to_s %> |
Практически все, что написано выше я собрал из двух статей: http://asci.blog.ru/120804899.html и http://wiki.dgplug.org/index.php/Deploying_of_gitorious_on_fedora, так что их можно также почитать если мой пост не ясен.
настройка загрузчика
Как я уже писал выше настройки загрузчика находяться в app/uploaders/image_uploader.rb
. Там можно сконфигурировать очень многое, например директорию хранения, по умолчанию она:
1 2 3 | def store_dir "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end |
Также очень полезной опцией являеться варианты сохранения изображений. Чтобы их было несколько надо подключить RMagick и собственно составить сами версии картинки, например:
1 2 3 4 5 | include CarrierWave::RMagick process :resize_to_fit => [800, 800] version :thumb do process :resize_to_fill => [200,200] end |
Стоит знать, что :resize_to_fill
и :resize_to_fit
при одинаковом разрешении делает разные вещи, одно обрезает, другое масштабирует.
Подробнее о настройках читаем на офсайте, также можно посмотреть RailsCast.
Uploadify
Uploadify это набор скриптов, которые отсылают через AJAX файл, непосредственно к самим рельсам и руби оно не имеет никакого отношения.
установка
Качаем сам Uploadify и затем распаковываем. Теперь для Rails 3.1.x кидаем файлы jquery.uploadify.v2.1.4.min.js
и swfobject.js
в app/assets/javascripts
, для Rails 3.0.x закидываем их в public/javascripts
. Первый файл надо переименовать в jquery.uploadify.js
.
Файлы uploadify.swf
и cancel.png
кидаем в app/assets/images/
или public/images
, а uploadify.css
в app/assets/stylesheets/
или public/stylesheets
.
Открываем application.js
из app/assets/javascripts
или public/javascripts
для разных версий Rails соответственно, и в конец дописываем:
//= require swfobject //= require jquery.uploadify |
В форму загрузки изображения (у меня app/views/pictures/_form.html.erb) закидываем сначала:
<input id="uploadify" name="uploadify" type="file" /> |
а после:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <script> $(document).ready(function() { var uploadify_script_data = {}; $('#uploadify').uploadify({ uploader : '/assets/uploadify.swf', script : '<%=pictures_path%>', cancelImg : '/images/cancel.png', auto : true, multi : true, removeCompleted : true, scriptData : uploadify_script_data, onComplete : function(event, ID, fileObj, doc, data){ } }); }); </script> |
В принимающем контроллере (здесь app/controllers/pictures_controller.rb) прописываем например:
1 2 3 4 | def create @picture = Picture.new(:image => params[:Filedata]) @picture.save end |
И в самом начале контроллера убираем защиту от CSRF:
1 | protect_from_forgery :except => :create |
Для тех кто не хочет отключать защиту от CSRF может попытаться следовать полной инструкции.
Если все правильно сделали, то должно получиться как на первой демке.
Дополнение от 14.04.2012 (касаемо CSRF). Архиважное.
Итак, чтобы не отключать защиту от CSRF нужно сделать следующее.
1. Бандлим гем flash_cookie_session, он нужен для того, чтобы не прописывать вагон всякой ерунды про поле User-Agent в заголовке запроса, если агентом является Flash, как в нашем случае.
1 2 | echo "gem 'flash_cookie_session'" >> Gemfile bundle install |
2. Прописываем в дополнительные параметры Uploadify authenticity_token
и кое-что еще, тем самым приводя инициализирующий скрипт к виду:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <script> $(document).ready(function() { $('#uploadify').uploadify({ uploader : '/assets/uploadify.swf', script : '<%=uploadify_pictures_path%>', cancelImg : '/assets/cancel.png', auto : true, multi : true, removeCompleted : true, buttonText : 'Select Files', scriptData : { '_http_accept': 'application/javascript', '_method': 'put', '<%=Rails.application.config.session_options[:key] %>': encodeURIComponent('<%= u cookies[session_key_name] %>'), 'authenticity_token': encodeURIComponent('<%= u form_authenticity_token %>') } }); }); </script> |
В результате этой мимикрии мы сможем оставить защиту от CSRF и сохранить сессию при запросе от Uploadify.
Источники дополнения:
http://www.damiangalarza.com/posts/ruby-on-rails/using-uploadify-with-rails-3/
http://erniemiller.org/2010/07/09/uploadify-and-rails-3/
[...] также как и в случае с Uploadify нужно сгенерировать аплодер carrierwave: 1 $ rails g uploader [...]
Важное дополнение!!! Если ответ в контроллере идет в JS (respond_to {|format| format.js}), то чтобы страница исполнила скрипт в JS-вьюхе нужно в инициализатор Uploadify вставить:
onComplete : function(event, queueID, fileObj, response, data){eval(response);}