Пишем REST приложение на Sinatra и прикручиваем Redactor. Часть 2

в 8:35, , рубрики: ruby, sinatra, wysiwyg-редактор, метки: , ,

В первой части статьи мы написали REST приложение и на 1/3 настроили Redactor.js. Сегодня мы закончим наше изобретение, написав интерфейс управления загруженными изображениями, и обеспечим загрузку файлов. При загрузке файлов мы не будем использовать CarrierWave, а пойдем обычным путем Ruby.

Начнем с загрузки файлов. Предполагаю, что сейчас у вас такой файл init.rb:

# coding: utf-8
require 'rubygems'
require 'sinatra'
require 'data_mapper'
require 'carrierwave'
require 'carrierwave/datamapper'
require 'rmagick'
require 'json'

set :public_directory, './public'

class ImageUploader < CarrierWave::Uploader::Base
  def store_dir
    'uploads/images'
  end
  def extension_white_list
    %w(jpg jpeg gif png bmp)
  end
  include CarrierWave::RMagick
  version :thumb do
    process :resize_to_fill => [100,74]
  end
  storage :file
end

class Post
  include DataMapper::Resource
  property :id,         Serial
  property :title,      String
  property :body,       Text
end

class UploadedImages
  include DataMapper::Resource
  property :id,    Serial
  property :image, String
  property :thumb, String

  mount_uploader :file, ImageUploader
end

class UploadedFiles
  include DataMapper::Resource
  property :id,    Serial
  property :name,  String
  property :path,  String
end

DataMapper.setup(:default, ENV['DATABASE_URL'] || 'sqlite:./db/base.db')
DataMapper.finalize
DataMapper.auto_upgrade!

get '/' do
  'REST приложение на Sinatra <a href="/posts">Перейти к страницам</a>'
end

#List posts
get '/posts' do
  <hh user=posts> = Post.all
  erb :'index'
end

#Create new Post
get '/posts/new' do
  erb :'posts/new'
end

post '/posts/new' do
  params.delete 'submit'
  <hh user=post> = Post.create(params)
  redirect '/posts'
end

#Edit post
get '/posts/:id/edit' do
  <hh user=post> = Post.get(params[:id])
  erb :'posts/edit'
end

#Update post
put '/posts/:id/edit' do
  post = Post.get(params[:id])
  post.title = (params[:title])
  post.body = (params[:body])
  post.save
  redirect '/posts'
end

#Delete post
get '/posts/:id/delete' do
  Post.get(params[:id]).destroy
  redirect '/posts'
end

post '/upload/image' do
  params[:file]
  filename = params[:file][:filename]
  file = params[:file][:tempfile]
  upload = UploadedImages.new
  upload.file = params[:file]
  upload.image = params[:image] = '/uploads/images/' + File.join(filename)
  upload.thumb = params[:thumb] = '/uploads/images/thumb_' + File.join(filename)
  upload.save
  <hh user=images> = UploadedImages.all
  File.open("public/uploads/images/imageslist.json","w") do |f|
    f.write JSON.pretty_generate(<hh user=images>)
  end
  '<img src="/uploads/images/' + File.join(filename) + '" />'
end

Итак, создадим папку files в директории public/uploads, напишем новую модель UploadedFiles и добавим метод post для загрузки файлов

set :files,  File.join(settings.public_directory, 'uploads/files') #берем из настройки public директории (set :public_directory, './public' почти в самом начале init.rb) ее путь и прибавляем путь, куда будут загружаться файлы

class UploadedFiles
  include DataMapper::Resource
  property :id,    Serial #идентификатор
  property :name,  String #имя загруженного файла
  property :path,  String #путь к загруженному файлу
end

post '/upload/file' do
  params[:file]
  filename = params[:file][:filename]
  file = params[:file][:tempfile]
  ext = File.extname(filename) #устанавливаем переменную для проверки расширения файла, нам же не нужно, чтобы пытались загружать что угодно.
  if ext == ".doc" || ext == ".zip" || ext == ".dmg" #здесь я установил 3 допустимых расширения файла doc, zip, dmg, при желании список разрешенных файлов всегда можно увеличить.
    File.open(File.join(settings.files, filename), 'wb') {|f| f.write file.read } #загрузили - сохранили
    upload_f = UploadedFiles.new #создаем новый UploadedFile в базе данных
    upload_f.name = params[:name] = File.join(filename) #в моделе UploadedFiles поле :name, записываем туда имя загруженного файла 
    upload_f.path = params[:path] = '/uploads/files/' + File.join(filename) #в моделе UploadedFiles поле :path, записываем туда путь к загруженному файлу
    upload_f.save #сохраняем
    '<a href="/uploads/files/' + File.join(filename) + '" />' + File.join(filename) + '</a>' #вставляем в редактор ссылку на файл
  end
end

Теперь осталось сказать редактору как именно загружать файлы, откроем layout.erb найдем строку

$('.redactor_1').redactor({toolbar: 'default', lang: 'ru', imageUpload: '/upload/image', imageGetJson: '/uploads/images/imageslist.json'});

Добавим настройку fileUpload: '/upload/file' чтобы выглядело следующим образом

$('.redactor_1').redactor({toolbar: 'default', lang: 'ru', imageUpload: '/upload/image', fileUpload: '/upload/file', imageGetJson: '/uploads/images/imageslist.json'});

Все необходимые настройки мы сделали: создали модель, настроили путь к загружаемым файлам, собственно реализовали саму загрузку файла, сделали проверку на разрешенные файлы. Вот что хотелось бы отметить, неплохо было бы реализовать сообщение об ошибке, если расширение файл не соответствует разрешенному, но тут есть 2 НО: во-первых, Redactor.js все делает во фрейме и, если сделать сообщение об ошибке, то оно будет писаться в textarea, что, на мой взгляд, не является хорошей идеей, если попытаться реализовать сообщение об ошибке через gem sinatra-flash, то опять не вариант, так как ошибка появится только после перегрузки страницы. Ну может более опытные люди подскажут в какую сторону копать.

Теперь приступим к управлению загруженными изображениями, максимум что мы сделаем, это список всех загруженных изображений и их удаление.
Добавим в init.rb

#init.rb
#List UploadedImages
get '/images' do
  @ uploadedimage = UploadedImages.all #задаем переменную для вывода всех загруженных изображений по адресу /images (убрать пробел между собакой и uploadedimage)
  erb :'images' #рендерим images.erb
end

#Delete UploadedImage
get '/images/:id/delete' do #обращаемся к изображению через его ID
  UploadedImages.get(params[:id]).destroy #удаляем (честно говоря, для меня какое-то волшебство, удаляет строки в бд и файлы!...)
  redirect '/images' #редеректимся на страницу со всеми оставшимися изображениями
end

Создадим в директории views файл images.erb

<strong><a href="/posts">Перейти ко всем post'ам</a></strong>
<h2>Список загруженных файлов</h2>	
<% @ uploadedimage.each do |uploadedimages| %>
<a href="<%= uploadedimages.image %>"><img src="<%= uploadedimages.thumb %>"></a>
 #ссылка на полное изображение при клике на его превью
<a href="/images/<%= uploadedimages.id %>/delete">удалить</a>

<% end %>

Ну вот мы и закончили наше приложение. По аналогии с управлением изображений можно сделать и управление файлами. Думаю получилось неплохо. И это еще не конец, я продолжаю изучать Ruby и Sinatra и будут еще статьи.
P.S.: Везде где попадается

<hh user=posts>

или

<hh user=images>

просто замените на @название_переменной

Автор: aylo


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js