Статьи

Асинхронная загрузка нескольких файлов с помощью Rails и Dropzone.js

Снимок экрана 2016-08-06 14.36.57

В этом уроке я собираюсь показать вам, как использовать Ruby on Rails 4.2 и Dropzone для загрузки нескольких файлов с помощью перетаскивания с помощью AJAX. Dropzone — это потрясающая библиотека, которая, по мнению автора, стоит на голову выше остальных библиотек, предлагающих аналогичную функциональность.

Обратите внимание, что я собираюсь использовать обычный Ruby on Rails без какой-либо другой библиотеки или гемов, таких как Paperclip или Carrierwave.

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

ОК, ребята, давайте отправимся в путь.

Предпосылки

Я предполагаю, что:

  1. Вы немного знаете об объектно-ориентированном программировании и о том, как три компонента шаблона проектирования MVC связаны друг с другом.
  2. Вы знакомы с запросами javascript, jQuery и AJAX.

Инструменты нам понадобятся

  1. Хорошие инструменты для отладки и анализа браузера. Я использую Firefox в качестве браузера с установленным Firebug для отладки и управления взаимодействиями между JavaScript в браузере и Rails на сервере.
  2. Любая IDE или редактор. Мне нравится Rubymine или Sublime, но не стесняйтесь использовать свои собственные.
  3. Ruby on Rails 4.2. Чтобы установить Rails, посетите (http://installrails.com/). Я собираюсь использовать SQLite в качестве базы данных. Вы также можете использовать PostgreSQL, MySQL или любую другую СУБД.
  4. Сама библиотека Dropzone. Иди и скачай его отсюда .
  5. Twitter Bootstrap .

Приложение

Наше приложение представляет собой модуль интернет-магазина для создания товаров и прикрепления к ним изображений. Он загружает изображения на сервер, возвращает список загруженных файлов, а затем отправляет форму для создания записи о продукте. Между изображениями и продуктом существует связь «один ко многим», что означает, что продукт может иметь много изображений.

Вот схема базы данных:

38329356-1acc-11e6-85ef-39a73b823cb7

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

rails new myapp 

Нам нужно добавить файлы Javascript и CSS для Dropzone и Bootstrap, поэтому перейдите в папку dist из загрузок Dropzone и Bootstrap и найдите следующие файлы:

 bootstrap.css dropzone.css basic.css dropzone.js 

Поместите dropzone.js в myapp / app / assets / javascripts и все CSS-файлы в myapp / app / assests / stylesheets . Откройте application.css и удалите эту строку:

 *= require_tree . 

Эта строка сообщает Rails, что требуется все в этом каталоге и во всех его подкаталогах. Мы не хотим, чтобы это произошло, поэтому избавьтесь от этого. Затем добавьте эту строку:

 *= require bootstrap 

Что означает включить загрузчик для нашего приложения. Закройте и сохраните файл.
Перейдите в myapp / app / assets / javascripts , откройте application.js и удалите следующие строки:

 //= require turbolinks //= require_tree . 

Для получения дополнительной информации о //= require turbolinks пожалуйста, прочитайте эту статью . Мы не собираемся использовать Turbolinks сегодня. Сохраните и закройте файл. Теперь перейдите в app / views / layouts / application.html.erb и создайте шаблон макета для вашего приложения. Ваш окончательный файл должен выглядеть так:

 <!DOCTYPE html> <html> <head> <title>Myapp</title> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> <%= csrf_meta_tags %> <%= yield(:css) %> <!-- We use this section for our CSS files --> </head> <body> <%= yield(:content) %><!-- For our main contents --> <%= yield(:javascript) %><!-- We put our JavaScript tags here --> </body> </html> 

Обратите внимание, что мы получили три раздела: css , content и javascript . Откройте терминал и введите следующую команду:

 rails g controller Product new create 

Это создаст два представления в вашей папке app / views и контроллер в app / controllers . Прежде чем двигаться дальше, перейдите в myapp / config / initializers / assets.rb и добавьте эту строку в файл:

 Rails.application.config.assets.precompile += %w(dropzone.js basic.css dropzone.css) 

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

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

Этот раздел включает в себя файлы CSS, basic.css и dropzone.css, которые будут использоваться для нашей области перетаскивания:

 <%= content_for(:css) do %> <%= stylesheet_link_tag 'dropzone' %> <%= stylesheet_link_tag 'basic' %> <% end %> 

Здесь мы включаем форму для создания продукта и зону перетаскивания, которая позволяет пользователю перетаскивать изображения:

 <%=content_for(:content) do %> <div class="container"> <div class="row"> <p>This is the form for creation of your product.</p> <%= form_for :request, :url => request.base_url+'/product/create', html: {id:'myForm'} do %> <label for="name">Product Name:</label> <input type="text" name="name" id="name" class="form-control"> <label for description> Description</label> <input type="text" name="description" id="description" class="form-control" /><br> <input type=hidden name="files_list" id='fileslist'> <!-- We use this <div> element to initialize our Dropzone --> <div id="mydropzone" class="dropzone"></div> <!-- This <div> elements shows a suitable message after a successful upload. --> <div id="msgBoard"></div> <br> <input type='submit' value="Create your product"> </div> </div> <% end %> <% end %> <%= content_for(:javascript) do %> <!-- include the dropzone library itself. --> <%= javascript_include_tag "dropzone" %> 

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

 <script type="text/javascript"> var AUTH_TOKEN=$('meta[name="csrf-token"]').attr('content'); 

Возьмите токен CSRF с помощью jQuery и сохраните его в переменной AUTH_TOKEN :

  Dropzone.autoDiscover = false; var myDropzone = new Dropzone("div#mydropzone",{ url: "<%= request.base_url %>/uploadfiles", autoProcessQueue: false, uploadMultiple: true, addRemoveLinks:true, parallelUploads:10, params:{ 'authenticity_token': AUTH_TOKEN }, successmultiple: function(data,response){ $('#msgBoard').append(response.message).addClass("alert alert-success"); $('#msgBoard').delay(2000).fadeOut(); $('#fileslist').val(response.filesList); $('#myForm').off('submit').submit(); } }); 

Инициализируйте наш перетаскиватель файлов:

  • url — маршрут, который обрабатывает задачу загрузки изображений на сервер
  • autoProcessQueue: false останавливает скрипт для автоматической загрузки изображений после их перетаскивания
  • uploadMultiple: true позволяет uploadMultiple: true отправлять несколько файлов за один запрос
  • addRemoveLinks: true Если установлено значение true, ссылка «Удалить» будет добавлена ​​для каждого предварительного просмотра файла.
  • parallelUploads: 10 устанавливает количество параллельных загрузок, разрешенное до 10
  • params позволяет нам предоставлять дополнительные данные вместе с запросом. Здесь нам нужно передать токен CSRF. Прежде чем двигаться дальше, давайте обсудим некоторые детали отправки AJAX-запросов в приложении Rails. Как вы, вероятно, знаете, если вы не включите токен CSRF в запросы, отправленные в Rails, вы получите эту ошибку в консоли Firebug:

     ActionController::InvalidAuthenticityToken in ProductController#upload 

На самом деле это выглядит так:

50dbced4-423d-11e6-8694-762691e879e9

Итак, мы передаем токен CSRF здесь.

  • successmultiple: function(data,response) : если процесс загрузки на сервере выполнен успешно, мы обрабатываем данные и ответ с помощью этой функции обратного вызова. Эта функция добавляет сообщение в наш msgBoard , затем msgBoard это сообщение, добавляет загруженные файлы в скрытый ввод в форме, а затем отправляет форму.

После отправки формы этот сценарий проверяет, является ли список файлов в области рабочей зоны пустым. Если есть какие-либо файлы для загрузки, он отправляет запрос POST на сервер и загружает их ( myDropzone.processQueue() ).

 $('#myForm').submit(function(e){ if(myDropzone.getQueuedFiles().length > 0){ e.preventDefault(); myDropzone.processQueue(); } }); </script> <% end %> 

В этом приложении Rails нам нужно в первую очередь запретить отправку формы, пока мы не убедимся, что все файлы в зоне размещения загружены. Как только все файлы были загружены, мы можем отправить форму. Поэтому в функции обратного вызова для successmultiple мы позволяем событию submit происходить методом off из jQuery.

Следующая строка отправляет форму, если скрипт успешно загружает наши изображения и сервер отправляет сообщение об успешном выполнении с сервера:

 `$('#myForm').off('submit').submit();` 

Теперь нам нужно реализовать серверную часть нашего приложения, которая отправляет сообщение об успешном завершении вместе со списком загруженных файлов. Откройте файл config / rout.rb и добавьте следующую строку:

 post 'uploadfiles'=>'product#upload' 

Затем перейдите в myapp / public и создайте каталог загрузки с помощью следующей команды:

 mkdir uploads 

Это каталог, который будет содержать наши загруженные файлы.

В app / controller / product_controller.rb определите метод загрузки следующим образом:

 def upload uploaded_pics = params[:file] # Take the files which are sent by HTTP POST request. time_footprint = Time.now.to_i.to_formatted_s(:number) # Generate a unique number to rename the files to prevent duplication uploaded_pics.each do |pic| # these two following comments are some useful methods to debug # abort pic.class.inspect -> It is similar to var_dump($variable) in PHP. # abort pic.is_a?(Array).inspect -> With "is_a?" method, you can find the type of variable # abort pic[1].original_filename.inspect # The following snippet saves the uploaded content in '#{Rails.root}/public/uploads' with a name which contains a time footprint + the original file # reference: http://guides.rubyonrails.org/form_helpers.html File.open(Rails.root.join('public', 'uploads', pic[1].original_filename), 'wb') do |file| file.write(pic[1].read) File.rename(file, 'public/uploads/' + time_footprint + pic[1].original_filename) end end files_list = Dir['public/uploads/*'].to_json #get a list of all files in the {public/uploads} directory and make a JSON to pass to the server render json: { message: 'You have successfully uploded your images.', files_list: files_list } #return a JSON object amd success message if uploading is successful end 

На стороне сервера мы берем файлы, отправленные через POST-запрос, с хэшем params . Затем сгенерируйте уникальную числовую строку, которая будет использоваться для создания уникальных имен файлов. Тем самым мы предотвращаем дублирование.

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

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

 rails s 

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

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

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

 rails generate model Product name:string description:text 

Во-вторых, создайте модель pic и ее файл миграции, введя следующую команду:

 rails generate model Pic product_id:integer:index name:text 

Перейдите в папку myapp / db / migrate и найдите файл миграции для модели Pic . Откройте его и добавьте внешний ключ с этим фрагментом кода:

 add_foreign_key :pics, :products 

Функция change вашего файла миграции должна выглядеть следующим образом:

 def change create_table :pics do |t| t.integer :product_id, index: true t.text :name t.timestamps null: false end add_foreign_key :pics, :products end 

Время установить связь между моделями Product и Pic . Эта задача невероятно проста в приложениях Rails. В реальном мире, мы бы сказали, что каждый продукт имеет много изображений, и каждая картинка принадлежит продукту. Итак, откройте модель вашего Product и добавьте этот фрагмент кода:

 has_many :pics 

Ваша модель теперь выглядит так:

 class Product < ActiveRecord::Base has_many :pics end 

Поскольку каждое изображение принадлежит продукту, вам необходимо добавить эту строку в модель Pic :

 belongs_to :product 

Вот и все. Время мигрировать с rake :

 rake db:migrate 

Что касается заключительной части, приложение должно создать продукт после загрузки файлов и связать этот продукт с изображениями, которые уже загружены нашей зоной размещения. Эта задача будет обработана методом create в ProductController . Откройте приложение / controllers / product_controller.rb .

Метод create должен создать продукт для нас, установить связь «один ко многим» и переместить загруженный файл в папку, специально созданную для данного продукта. Имя папки — это идентификатор продукта. Кроме того, метод должен создать флэш-сообщение, если операция прошла успешно, и перенаправить пользователя на предыдущую страницу:

 def create files_list = ActiveSupport::JSON.decode(params[:files_list]) product=Product.create(name: params[:name], description: params[:description]) Dir.mkdir("#{Rails.root}/public/"+product.id.to_s) files_list.each do |pic| File.rename( "#{Rails.root}/"+pic, "#{Rails.root}/public/"+product.id.to_s+'/'+File.basename(pic)) product.pics.create(name: pic) end redirect_to product_new_url, notice: "Success! Product is created." end 

Флэш-сообщение будет отображаться в верхней части страницы создания, поэтому нам нужно создать элемент div который покажет пользователю сообщение о создании. Добавьте следующий код в файл app / views / product / new.html.erb .

 <% if flash.notice %> <div class="alert alert-success"><P><%= flash.notice %></p></div> <% end %> 

ОК, мы закончили с этим небольшим модулем для нашего приложения электронной коммерции. Запустите сервер rails ( rails s ) и перейдите на страницу нового продукта в браузере. Если вы заполните форму, перетащите ее изображения и нажмете кнопку «Отправить», тогда приложение загрузит изображения, создаст наш продукт и свяжет загруженные изображения с нашим продуктом.

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

Если я нажму кнопку «Создать свой продукт», она создаст запись и перенаправит меня на ту же страницу с флэш-сообщением следующим образом:

Вывод

Хорошо, это все, что нужно для создания этого небольшого модуля для нашего интернет-магазина. Dropzone.js позволил нам снабдить наше Rails-приложение приятным пользовательским интерфейсом для загрузки изображений без необходимости использовать файл для загрузки файлов.

Я надеюсь, что вы нашли это полезным.