Данная статья рассчитана первым делом на новичков, которые только начинают изучать Ruby. После 3 недель изучения этого замечательного языка, накопились некоторые знания, которыми хочется поделиться.
Sinatra — бесплатный и открытый программный каркас написанный на языке Ruby, предназначенный для разработки веб-приложений. (ru.wikipedia.org)
Redactor — достаточно мощный и в тоже время простой и красивый в использовании wysiwyg редактор (ссылка на хабре)
Итак начнем!
Предположительно у вас уже установлен Ruby и SQLite.
Так как основным моментом статьи является попытка прикрутить Redactor, где для выбора уже загруженных изображений используются превью картинок, то для работы с изображениями установим Rmagick. В OS X установка совершается достаточно просто
Теперь необходимо установить дополнительные gem'ы.
sintra — сам фреймворк
data_mapper — ORM
carrierwave — гем для загрузки изображений (использовался в одной статье на Хабре)
carrierwave/datamapper — для связи DataMapper и CarrierWave
json — для генерации данных в формате JSON
Нужные гемы установлены, теперь скачаем сам редактор redactorjs.com/ru/download
Приступим к написанию кода:
Классика жанра
сохраним к примеру под именем init.rb и запустим приложение shotgun init.rb
перейдем по адресу http://127.0.0.1:9393/

Вы заметили ссылку /posts, сейчас займемся ее работоспособностью.
Первое что мы должны сделать, это добавить установленные нами гемы в приложение
Установим public папку с js скриптами и куда будем загружать изображения (также обычно в эту папку помещаются css файлы, изображения макетов страниц)
И сразу создадим в ней папку js и скопируем туда ранее скаченный редактор

Далее напишем нашу модель Post
Создадим папку db в корне с нашим файлом init.rb
Настроим работу с базой данных
Пропишем роутинг для /posts
*по умолчанию Sinatra использует шаблонизатор erb — Erubis
Теперь создадим папку views в корне с нашим файлом init.rb, опять же по умолчанию Sinatra ищет шаблоны в папке views. Создадим в этой папке index.erb следующего содержания:
Разберем некоторе моменты:
Помните, мы с вами в запросе get '/posts' do задавали переменную @posts = Post.all? Так вот, конструкцией <% @posts.each do |post| %>.......<% end %> мы ищем в массиве все посты, имеющиеся в базе данных.
/posts/<%= post.id %>/edit — ссылка для редактирование поста, обращаться к постам мы будем через их id
<%= post.title %> — выводим в шаблоне Название поста
<%= post.body %> — выводим в шаблоне Тело поста
Сейчас нам необходимо создать общий шаблон для всех страниц, где будем подключать все пользовательские css и js. Создадим файл layout.erb в папке views и забегай немного вперед создадим в этой же директории папку posts и в ней 2 файла edit.erb и new.erb. Теперь структура приложения должна быть такой:

<%= yield %> — вместо этого будут выводится различные шаблоны которые мы создали — index.erb, new.erb, edit.erb
Теперь нам нужно, чтобы заработала ссылка /posts/new
Добавляем в init.rb
И дописываем в views/posts/new.erb
Переходим по ссылке «Создать новую страницу»

Добавляем данные, жмем кнопку «Создать», пост создан.
Теперь добавим немного кода для редактирования и удаления страниц.
Отредактируем views/posts/edit.erb
По сути приложение готово к использованию, теперь о второй части статьи, это прикрутим Redactor к textarea

Сначала п��дключим его. Открываем views/layout.erb, добавляем после
Теперь настроем (более подробно о настройке можно прочитать тут).
$('.redactor_1') — вызываем редактор с именем redactor_1
toolbar: 'default' — устанавливаем стандартный тулбар со всеми кнопками
lang: 'ru' — устанавливаем русский язык
imageUpload: '/upload/image' — так мы будем загружать изображения
imageGetJson: '/uploads/images/imageslist.json' — так мы будем получать превью и список уже загруженных изображений
Теперь вызовем редактор для textarea, для этого откроем views/posts/new.erb и views/posts/edit.erb и добавим класс class=«redactor_1». Для примера new.erb должен выглядеть сейчас так:
И edit.erb так:
Создадим в папке public папку uploads в ней папку images. Структура директорий получится public/uploads/images.
Теперь реализуем загрузку изображений через gem CarrierWave. Настраиваем в init.rb:
Так как Redactor показывает нам уже загруженные изображения через их превью, которые в свою очередь читает из JSON файла, который у нас назвается imageslist.json и лежит в public/uploads/images/. Нам нужно привести этот самый файл к формату, который понимает Redactor.
Отсюда и вытекает, что нам нужно создать модель следующим образом
В самый конец файла init.rb добавляем следущее:

Что происходит? При загрузке изображения удаляется содержимое imageslist.json и генерируется новое. Честно говоря, не знаю насколько это накладно для сервера.
Если статья найдет свой отклик, в следующий раз я постараюсь описать процесс загрузки файлов через этот редактор, а так же интерфейс управления загруженными изображениями.
Исходный код можно посмотреть на github.
P.S.: Прошу не сильно ругать меня, старался изложить все по полкам. Конечно понимаю что мой код далек от идеала, и некоторые моменты можно реализовать более просто, но думаю это неплохая отправная точка для начала изучения Ruby и Sinatra. Спасибо.
P.S.S.: Везде где попадается
Sinatra — бесплатный и открытый программный каркас написанный на языке Ruby, предназначенный для разработки веб-приложений. (ru.wikipedia.org)
Redactor — достаточно мощный и в тоже время простой и красивый в использовании wysiwyg редактор (ссылка на хабре)
Итак начнем!
Предположительно у вас уже установлен Ruby и SQLite.
Так как основным моментом статьи является попытка прикрутить Redactor, где для выбора уже загруженных изображений используются превью картинок, то для работы с изображениями установим Rmagick. В OS X установка совершается достаточно просто
curl https://raw.github.com/maddox/magick-installer/master/magick-installer.sh | sh
Теперь необходимо установить дополнительные gem'ы.
[sudo] gem install sinatra data_mapper carrierwave carrierwave/datamapper json shotgun
sintra — сам фреймворк
data_mapper — ORM
carrierwave — гем для загрузки изображений (использовался в одной статье на Хабре)
carrierwave/datamapper — для связи DataMapper и CarrierWave
json — для генерации данных в формате JSON
Нужные гемы установлены, теперь скачаем сам редактор redactorjs.com/ru/download
Приступим к написанию кода:
Классика жанра
# coding: utf-8 require 'sinatra' get '/' do 'REST приложение на Sinatra <a href="/posts">Перейти к страницам</a>' end
сохраним к примеру под именем init.rb и запустим приложение shotgun init.rb
перейдем по адресу http://127.0.0.1:9393/

Вы заметили ссылку /posts, сейчас займемся ее работоспособностью.
Первое что мы должны сделать, это добавить установленные нами гемы в приложение
#init.rb # coding: utf-8 require 'rubygems' require 'sinatra' require 'data_mapper' require 'carrierwave' require 'carrierwave/datamapper' require 'rmagick' require 'json'
Установим public папку с js скриптами и куда будем загружать изображения (также обычно в эту папку помещаются css файлы, изображения макетов страниц)
set :public_directory, './public'
И сразу создадим в ней папку js и скопируем туда ранее скаченный редактор

Далее напишем нашу модель Post
#init.rb class Post include DataMapper::Resource property :id, Serial #Идентификатор property :title, String #String указывает на то, что поле title будет иметь тип "Строка" и стандартно не более 50 символов property :body, Text #Text указывает на то, что поле body будет иметь тип "Текст" размером 65535 символов end
Создадим папку db в корне с нашим файлом init.rb
Настроим работу с базой данных
#init.rb DataMapper.setup(:default, ENV['DATABASE_URL'] || 'sqlite:./db/base.db') #подключение и путь к бд DataMapper.finalize #Проверка моделей DataMapper.auto_upgrade! #Создает новые таблицы и добавляет новые столбцы в существующие таблицы
Пропишем роутинг для /posts
#init.rb #Список всех постов get '/posts' do #роутинг для ссылки /posts @posts = Post.all #задаем переменную @posts равную выводу всех значений бд из модели Post erb :'index' #выводим все в шаблоне index.erb* end
*по умолчанию Sinatra использует шаблонизатор erb — Erubis
Теперь создадим папку views в корне с нашим файлом init.rb, опять же по умолчанию Sinatra ищет шаблоны в папке views. Создадим в этой папке index.erb следующего содержания:
#index.erb <strong><a href="/posts/new">Создать новую страницу</a></strong> <h2>Список всех страниц</h2> <% @posts.each do |post| %> <strong><a href="/posts/<%= post.id %>/edit"><%= post.title %></a></strong><br> <%= post.body %><br> <% end %>
Разберем некоторе моменты:
Помните, мы с вами в запросе get '/posts' do задавали переменную @posts = Post.all? Так вот, конструкцией <% @posts.each do |post| %>.......<% end %> мы ищем в массиве все посты, имеющиеся в базе данных.
/posts/<%= post.id %>/edit — ссылка для редактирование поста, обращаться к постам мы будем через их id
<%= post.title %> — выводим в шаблоне Название поста
<%= post.body %> — выводим в шаблоне Тело поста
Сейчас нам необходимо создать общий шаблон для всех страниц, где будем подключать все пользовательские css и js. Создадим файл layout.erb в папке views и забегай немного вперед создадим в этой же директории папку posts и в ней 2 файла edit.erb и new.erb. Теперь структура приложения должна быть такой:

#layout.erb <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Redactor.js + Sinatra app</title> </head> <body> <%= yield %> </body> </html>
<%= yield %> — вместо этого будут выводится различные шаблоны которые мы создали — index.erb, new.erb, edit.erb
Теперь нам нужно, чтобы заработала ссылка /posts/new
Добавляем в init.rb
#init.rb #Create new Post get '/posts/new' do #роутинг для ссылки создания страницы erb :'posts/new' #рендерим шаблон /posts/new.erb end post '/posts/new' do params.delete 'submit' @post = Post.create(params) #сохраняем данные, полученные из формы в базу данных модели Post redirect '/posts' #редиректимся на страницу со всеми постами end
И дописываем в views/posts/new.erb
#new.erb <h3>Создать</h3> <form method="post" action="/posts/new"> <label for="title"><strong>Название:</strong></label><br> <input id="title" type="text" name="title" value="" style="width: 250px;"> <br> <label for="body"><strong>Тело страницы:</strong></label><br> <textarea id="body" name="body" style="height: 250px;"></textarea> <br> <input type="submit" name="submit" value="Создать"> </form>
Переходим по ссылке «Создать новую страницу»

Добавляем данные, жмем кнопку «Создать», пост создан.
Теперь добавим немного кода для редактирования и удаления страниц.
#init.rb #Edit post get '/posts/:id/edit' do #роутинг для редактирования поста, доступ к посту получаем по его id @post = Post.get(params[:id]) #в переменной @post получаем данные из бд согласно id поста erb :'posts/edit' #рендерим шаблон /posts/edit.erb 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
Отредактируем views/posts/edit.erb
#edit.erb <h3>Редактировать</h3> <a href="/posts/<%= @post.id %>/delete">Удалить страницу</a><br><br> <form action="/posts/<%= @post.id %>/edit" method="post"> <input name="_method" type="hidden" value="put" /> <label for="title"><strong>Название:</strong></label><br> <input type="text" name="title" id="title" value="<%= @post.title %>"> <br> <label for="body"><strong>Тело страницы:</strong></label><br> <textarea id="body" name="body" style="height: 250px;"><%= @post.body %></textarea> <br> <input id="post_submit" name="commit" type="submit" value="Сохранить"> </form>
По сути приложение готово к использованию, теперь о второй части статьи, это прикрутим Redactor к textarea

Сначала п��дключим его. Открываем views/layout.erb, добавляем после
#layout.erb <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script> <script type="text/javascript" src="/js/redactor/redactor.js"></script> <link rel="stylesheet" href="/js/redactor/css/redactor.css" type="text/css">
Теперь настроем (более подробно о настройке можно прочитать тут).
#layout.erb <script type="text/javascript"> $(document).ready(function() { $('.redactor_1').redactor({toolbar: 'default', lang: 'ru', imageUpload: '/upload/image', imageGetJson: '/uploads/images/imageslist.json'}); }); </script>
$('.redactor_1') — вызываем редактор с именем redactor_1
toolbar: 'default' — устанавливаем стандартный тулбар со всеми кнопками
lang: 'ru' — устанавливаем русский язык
imageUpload: '/upload/image' — так мы будем загружать изображения
imageGetJson: '/uploads/images/imageslist.json' — так мы будем получать превью и список уже загруженных изображений
Теперь вызовем редактор для textarea, для этого откроем views/posts/new.erb и views/posts/edit.erb и добавим класс class=«redactor_1». Для примера new.erb должен выглядеть сейчас так:
#new.erb <h3>Создать</h3> <form method="post" action="/posts/new"> <label for="title"><strong>Название:</strong></label><br> <input id="title" type="text" name="title" value="" style="width: 250px;"> <br> <label for="body"><strong>Тело страницы:</strong></label><br> <textarea id="body" class="redactor_1" name="body" style="height: 250px;"></textarea> <br> <input type="submit" name="submit" value="Создать"> </form>
И edit.erb так:
#edit.erb <h3>Редактировать</h3> <a href="/posts/<%= @post.id %>/delete">Удалить страницу</a><br><br> <form action="/posts/<%= @post.id %>/edit" method="post"> <input name="_method" type="hidden" value="put" /> <label for="title"><strong>Название:</strong></label><br> <input type="text" name="title" id="title" value="<%= @post.title %>"> <br> <label for="body"><strong>Тело страницы:</strong></label><br> <textarea id="body" class="redactor_1" name="body" style="height: 250px;"><%= @post.body %></textarea> <br> <input id="post_submit" name="commit" type="submit" value="Сохранить"> </form>
Создадим в папке public папку uploads в ней папку images. Структура директорий получится public/uploads/images.
Теперь реализуем загрузку изображений через gem CarrierWave. Настраиваем в init.rb:
#init.rb class ImageUploader < CarrierWave::Uploader::Base def store_dir 'uploads/images' #устанавливаем папку для загружаемых изображений end def extension_white_list %w(jpg jpeg gif png bmp) #разрешаем загружать только jpg, jpeg, gif, png, bmp end include CarrierWave::RMagick #подключаем RMagick для создания превью version :thumb do process :resize_to_fill => [100,74] #говорим RMagick'у какой нам нужен размер для превью end storage :file end
Так как Redactor показывает нам уже загруженные изображения через их превью, которые в свою очередь читает из JSON файла, который у нас назвается imageslist.json и лежит в public/uploads/images/. Нам нужно привести этот самый файл к формату, который понимает Redactor.
{ "thumb": "/tests/_images/1_m.jpg", "image": "/tests/_images/1.jpg" },
Отсюда и вытекает, что нам нужно создать модель следующим образом
#init.rb class UploadedImages #создаем модель в бд загруженных изображений include DataMapper::Resource property :id, Serial property :image, String # property :thumb, String # mount_uploader :file, ImageUploader #прикручиваем загрузчик carrierwave end
В самый конец файла init.rb добавляем следущее:
#init.rb 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 #загрузили - сохранили @images = UploadedImages.all #вводим новую переменную @images для вывода всех загруженных изображений и их превью в JSON File.open("public/uploads/images/imageslist.json","w") do |f| #открываем imageslist.json для записи f.write JSON.pretty_generate(@images) #генерируем JSON формат в файл всех загруженных изображений end '<img src="/uploads/images/' + File.join(filename) + '" />'# вставляем ссылку в textarea со свежезагруженным изображением end

Что происходит? При загрузке изображения удаляется содержимое imageslist.json и генерируется новое. Честно говоря, не знаю насколько это накладно для сервера.
Если статья найдет свой отклик, в следующий раз я постараюсь описать процесс загрузки файлов через этот редактор, а так же интерфейс управления загруженными изображениями.
Исходный код можно посмотреть на github.
P.S.: Прошу не сильно ругать меня, старался изложить все по полкам. Конечно понимаю что мой код далек от идеала, и некоторые моменты можно реализовать более просто, но думаю это неплохая отправная точка для начала изучения Ruby и Sinatra. Спасибо.
P.S.S.: Везде где попадается
или<hh user=posts>
просто замените на @posts и на images соответственно.<hh user=images>
