Статьи

Возиться с Синатрой, часть II

В предыдущей статье нам удалось создать приложение, которое позволило бы пользователям продемонстрировать свои навыки работы с HTML, CSS и JavaScript, создав «загадки» — небольшие фрагменты HTML, CSS и JavaScript. В качестве дополнительного бонуса он также позволил пользователям использовать SCSS для стилизации и Markdown для HTML. В этой части мы собираемся добавить некоторые улучшения в приложение, чтобы оно больше походило на сайты, которые оно клонирует .

плавающие фреймы

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

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

@@show
h1.title== @riddle.title
#riddle
iframe src=«/#{@riddle.id}«

view raw
gistfile1.rb
hosted with ❤ by GitHub

Нам нужен обработчик маршрута для iframe. В атрибуте src iframe мы использовали маршрут «/# enj@riddle.id}», который в основном является идентификатором загадки. Нам это нужно, чтобы мы могли найти загадку в базе данных, вот нужный нам обработчик маршрута:

get ‘/:id’ do
@riddle = Riddle.get(params[:id])
slim :riddle, layout: false
end

view raw
gistfile1.rb
hosted with ❤ by GitHub

Это получает Загадку, которая соответствует идентификатору, данному из базы данных. Затем мы отображаем новый вид под названием «загадка». Обратите внимание, что для макета установлено значение false, в противном случае наш макет будет снова отображаться в iframe, а это не то, что нам нужно. Мы хотим, чтобы у iframe были собственные HTML-заголовок и тело. Они перейдут непосредственно в представление, как вы можете видеть ниже:

@@riddle
doctype html
html lang=«en»
head
title== @riddle.title
meta charset=«utf-8»
style
== scss @riddle.css
script
== @riddle.js
body
== markdown @riddle.html

view raw
gistfile1.rb
hosted with ❤ by GitHub

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

get ‘/css/riddle/:id/styles.css’ do
riddle = Riddle.get(params[:id])
scss «#riddle #{riddle.css}«
end
get ‘/js/riddle/:id/script.js’ do
riddle = Riddle.get(params[:id])
content_type ‘text/javascript’
render :str, riddle.js, :layout => false
end

view raw
gistfile1.rb
hosted with ❤ by GitHub

Нам также нужно удалить следующие строки, которые ссылаются на них в нашем макете:

if @riddle
link rel=«stylesheet» href=«/css/riddle/#{@riddle.id}/styles.css»
script src=«/js/riddle/#{@riddle.id}/script.js»

view raw
gistfile1.rb
hosted with ❤ by GitHub

Если вы протестируете это, оно не будет выглядеть по-другому, но теперь мы помещаем весь код в iframe. Вы можете заметить, что iframe по умолчанию выглядит очень маленьким. Это можно решить, добавив следующую строку в наше представление @@ styles:

iframe {width: 100%; min-height: 600px; border: none; }

view raw
gistfile1.css
hosted with ❤ by GitHub

Редактирование Загадок

Следующая функция, которую нужно добавить, позволяет пользователям редактировать Загадки. Если кто-то видит что-то, что ему нравится, было бы хорошо позволить ему внести некоторые изменения и изменения. Прежде всего, давайте добавим кнопку, которая позволяет пользователям редактировать загадку, когда они находятся на странице загадки. Мы поместим его в макет, прямо рядом с кнопкой «Новая загадка», но мы хотим, чтобы он отображался только в том случае, если есть загадка для редактирования. Этот код должен сделать это:

if @riddle && @riddle.id
a.button href=«/edit/riddle/#{@riddle.id}« Edit this Riddle

view raw
gistfile1.rb
hosted with ❤ by GitHub

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

Проблема в том, что мы не хотим обновлять саму загадку… люди не хотят, чтобы их хорошая работа была изменена кем-то другим! И мы не хотим вмешиваться во все проблемы с пользователями. Решение простое — просто создайте новую загадку, используя атрибуты загадки, которые мы хотим отредактировать. Это можно сделать в DataMapper с помощью следующего кода:

Riddle.new(riddle.attributes.merge(id: nil))

view raw
gistfile1.rb
hosted with ❤ by GitHub

Обратите внимание, что мы хотим установить id nil как это поле с автоинкрементом, которое будет автоматически задано базой данных при сохранении новой загадки, давая ей свой уникальный URL-адрес. Все, что нам нужно сделать, чтобы это произошло, это добавить следующий обработчик маршрута в наш код:

get ‘/edit/riddle/:id’ do
riddle = Riddle.get(params[:id])
@riddle = Riddle.new(riddle.attributes.merge(id: nil))
slim :new
end

view raw
gistfile1.rb
hosted with ❤ by GitHub

Это относится к URL, который мы использовали в ссылке кнопки редактирования. Прежде всего, мы получаем загадку, которую хотим редактировать из базы данных. Затем мы «клонируем» его как новый несохраненный объект и затем отображаем новое представление, которое является просто формой. К сожалению, в данный момент это просто покажет пустые поля, поэтому нам нужно внести небольшое изменение в форму, чтобы поля заполнялись любыми значениями по умолчанию:

@@new
form action=«/riddle» method=«POST»
label for=«title» Title
input#title name=»riddle[title]» value=»#{@riddle.title}»
label for=«html» HTML
textarea#html cols=60 rows=10 name=»riddle[html]»=@riddle.html
label for=«css» CSS
textarea#css cols=60 rows=10 name=»riddle[css]»=@riddle.css
label for=«js» JS
textarea#js cols=60 rows=10 name=»riddle[js]»=@riddle.js
input.button type=«submit» value=«Save»

view raw
gistfile1.rb
hosted with ❤ by GitHub

Начни свои двигатели

В начале первой части я упоминал, что Sinatra использует Tilt для реализации множества различных движков представления. На самом деле мы еще не использовали это. Было бы здорово, если бы мы позволили пользователям выбирать, какой движок они хотели бы использовать для отображения HTML, использовать препроцессор для CSS или использовать CoffeScript вместо JavaScript. Синатра делает это невероятно легким для достижения. Прежде всего нам нужно требовать соответствующие драгоценные камни для всех механизмов представления, которые мы планируем использовать. Добавьте следующее в начало main.rb:

require ‘haml’
require ‘RedCloth’
require «coffee-script»
require «v8»
require «liquid»
require «markaby»
require «less»

view raw
gistfile1.rb
hosted with ❤ by GitHub

RedCloth камень RedCloth используется для обработки текстиля, а гем v8 встраивает движок javascript v8 в Ruby, который требуется для поддержки CoffeeScript. Все остальные, надеюсь, говорят сами за себя.

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

class Riddle
include DataMapper::Resource
property :id, Serial
property :created_at, DateTime
property :updated_at, DateTime
property :title, String
property :html, Text
property :html_engine, String
property :css, Text
property :css_engine, String
property :js, Text
property :js_engine, String
def title=(value)
super(value.empty? ? «Yet another untitled Riddle» : value)
end
end
DataMapper.finalize

view raw
gistfile1.rb
hosted with ❤ by GitHub

Чтобы убедиться, что эти изменения внесены в нашу базу данных, все, что нам нужно сделать, это использовать мощную функцию миграции DataMapper с использованием irb. Введите в терминал следующее:

 $ irb ruby-1.9.2-p180 :001 > require './main' ruby-1.9.2-p180 :001 > Riddle.auto_migrate! 

Важно отметить, что с помощью auto_migrate! Метод уничтожит все загадки, хранящиеся в данный момент в базе данных. Если вы не хотите, чтобы это произошло, вы можете использовать auto_upgrade! метод вместо

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

@@new
form action=«/riddle» method=«POST»
label for=«title» Title
input#title name=»riddle[title]» value=»#{@riddle.title}»
select name=«riddle[html_engine]»
option value=«markdown» HTML
option value=«markdown» MARKDOWN
option value=«textile» TEXTILE
option value=«haml» HAML
option value=«slim» SLIM
option value=«erb» ERB
option value=«liquid» LIQUID
option value=«markaby» MARKABY
textarea#html cols=60 rows=10 name=»riddle[html]»=@riddle.html
select name=«riddle[css_engine]»
option value=«css» CSS
option value=«scss» SCSS
option value=«sass» SASS
option value=«less» LESS
textarea#css cols=60 rows=10 name=»riddle[css]»=@riddle.css
select name=«riddle[js_engine]»
option value=«javascript» JAVASCRIPT
option value=«coffee» COFFEESCRIPT
textarea#js cols=60 rows=10 name=»riddle[js]»=@riddle.js
input.button type=«submit» value=«Save Riddle»

view raw
gistfile1.rb
hosted with ❤ by GitHub

Это немного долго и мучительно запрограммировано, но делает ту работу, которую мы хотим сейчас. Важным является атрибут value с каждой опцией. Это будет значение, которое хранится в базе данных, и оно важно, поскольку в большинстве случаев это то же самое, что и вспомогательный метод, который Sinatra использует для визуализации представления с использованием этого конкретного механизма. Как вы увидите, это делает рендеринг с помощью конкретного движка чрезвычайно простым. Все, что нам нужно сделать сейчас, это изменить код iframe в представлении загадки следующим образом:

@@riddle
doctype html
html lang=«en»
head
title== @riddle.title
meta charset=«utf-8»
style
if @riddle.css_engine ==«css»
== @riddle.css
else
== send(@riddle.css_engine, @riddle.css)
script
if @riddle.js_engine == «javascript»
== @riddle.js
else
== send(@riddle.js_engine, @riddle.js)
body
== send(@riddle.html_engine, @riddle.html)

view raw
gistfile1.rb
hosted with ❤ by GitHub

Давайте подробнее рассмотрим, как мы отображаем HTML. Следующая строка будет использовать свойство html_engine загадки для визуализации HTML-кода с использованием правильного движка:

 == send(@riddle.html_engine, @riddle.html) 

Метод send используется для вызова метода, который хранится в свойстве html_engine. Вот почему имя значений в форме было так важно, поскольку они должны точно соответствовать именам методов. Я также немного обманул, так как нет вспомогательного html метода, но, как я заметил в первой части, уценка в любом случае будет просто отображать сырой HTML, и поэтому, если кто-то выберет опцию HTML, это на самом деле «уценка» это хранится в поле базы данных.

Мы должны сделать немного по-другому для CSS и JavaScript. У Sinatra нет специального помощника для CSS или JavaScript, но, поскольку они уже находятся в тегах <style> и <script> соответственно, мы можем просто обслужить это, если пользователь выберет «css» или «javascript» из меню. В противном случае, если они выбирают препроцессор CSS или CoffeScript, тогда значение, хранящееся в поле базы данных, совпадает с именем помощника Sinatra, поэтому мы можем использовать метод send таким же образом, как и при выборе движка HTML, как вы можете видеть в фрагмент ниже показывает, как CSS-код вставляется в блок стиля:

style
if @riddle.css_engine ==«css»
== @riddle.css
else
== send(@riddle.css_engine, @riddle.css)

view raw
gistfile1.rb
hosted with ❤ by GitHub

Заканчивать

Мы практически там! Все, что нам нужно сделать, это немного привести в порядок и добавить немного стиля, чтобы все выглядело немного лучше. Вот стили, которые я использовал:

@@styles
@import url(http://fonts.googleapis.com/css?family=Pacifico);
$purple:#639;
$green:#396;
body{ font: 13px/1.4 arial, sans-serif; }
header{ overflow: hidden; }
.logo{float:left;overflow: hidden;}
.logo a{ color: $purple; font: 64px/1 pacifico; text-decoration: none; &:hover{color:$green;}}
.title{ color: $green; font: 32px/1 pacifico; }
.button {text-decoration: none; font-weight: bold; padding: 4px 8px; border-radius: 10px; background: $green; color: white; border:none; &:hover{background:$purple;}}
header .button{ float:left; margin: 36px 10px 0;}
form label, input.button {display: block;}
form select {display: block;}
iframe {width: 100%; min-height: 600px; border: none; }

view raw
gistfile1.scss
hosted with ❤ by GitHub

И это в значительной степени это! Это работает довольно хорошо, и у нас все еще намного меньше 160 строк кода (включая представления и CSS)! Вы можете увидеть весь код в репозитории GitHub, и готовое приложение работает на Heroku .

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

  • Включите опцию включения некоторых библиотек JavaScript в Riddle (это можно легко сделать, просто связавшись со ссылкой CDN для каждой соответствующей библиотеки JS).
  • Включите предварительный просмотр загадки, когда пользователь печатает ее (это можно сделать с помощью JQuery и некоторого Ajax)
  • Размещайте загадки на github ( этот драгоценный камень поможет с этим)
  • Возможность делиться и встраивать загадки (использование iframes делает это очень простым)
  • Подсветка кода в текстовых областях
  • … и стиль может быть лучше!

Не стесняйтесь раскошелиться на репозиторий GitHub и добавить любую из этих функций!

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