Статьи

Простая CMS в Синатре, часть II

Screenshot4 В первой части мы установили MongoDB и использовали Mongoid для создания некоторых страниц для нашей простой CMS. Мы также создали веб-интерфейс для добавления и просмотра страниц. В этом уроке мы добавим две другие операции CRUD, которые позволят пользователям редактировать и удалять страницы. Прежде чем добавить функциональность, давайте добавим пару кнопок на каждую страницу, чтобы можно было редактировать или удалять страницу. Отредактируйте файл show.slim так, чтобы он выглядел следующим образом:

 a href="/pages/#{@page.id}/edit" Edit this page, a href="/pages/delete/#{@page.id}" Delete this page 

Редактирование страниц

Нам нужно создать обработчик маршрута, когда пользователь нажимает на URL страницы редактирования. Добавьте следующее в main.rb (но убедитесь, что оно идет до маршрута ‘/ pages /: id’):

 get '/pages/:id/edit' do @page = Page.find(params[:id]) slim :edit end 

Он находит страницу, которая должна быть отредактирована в базе данных, используя идентификатор, указанный в URL, и сохраняет ее в переменной экземпляра @page . Это будет доступно в представлении, которое называется edit.slim . Нам нужно создать это, поэтому давайте сделаем это сейчас. Сохраните следующее как edit.slim в каталоге views :

 h1 Edit Page form action="/pages/#{@page.id}" method="POST" input type="hidden" name="_method" value="PUT" fieldset legend Edit page == slim :form input type="submit" value="Update" 

Обратите внимание, что здесь повторно используется часть form которую мы использовали для new страницы в последнем уроке. Это сохраняет последовательность и делает наш код СУХИМ. Форма ссылается на значения в объекте @page , поэтому некоторые поля должны быть заполнены их текущими значениями. Форма также имеет скрытое поле, которое используется, чтобы сообщить Синатре, что запрос является запросом PUT. Это связано с тем, что большинство браузеров изначально не поддерживают HTTP-глаголы, кроме GET и POST. Решение состоит в том, чтобы использовать метод переопределения Синатры, чтобы запрос был перенаправлен так, как если бы это был запрос PUT. В этом случае мы используем запрос PUT, потому что мы обновляем ресурс.

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

 put '/pages/:id' do page = Page.find(params[:id]) page.update_attributes(params[:page]) redirect to("/pages/#{page.id}") end 

Это находит страницу, которая должна быть обновлена, и обновляет ее, используя метод update_attributes Mongoid. Затем он перенаправляет пользователя на недавно обновленную страницу.

Удаление страниц

Чтобы удалить страницу, мы собираемся создать двухэтапный процесс. Сначала мы показываем страницу подтверждения, чтобы убедиться, что пользователь желает удалить страницу. Вот обработчик маршрута для этой страницы:

 get '/pages/delete/:id' do @page = Page.find(params[:id]) slim :delete end 

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

 h1 Delete Page p Are you sure you want to delete the page called #{@page.title}? form action="/pages/#{@page.id}" method="POST" input type="hidden" name="_method" value="DELETE" input type="submit" value="Delete" a href="/pages" cancel 

Мы должны использовать форму, чтобы сделать это, так как мы будем использовать метод DELETE HTTP в нашем обработчике маршрута, который удалит страницу. Если мы используем ссылку, то мы можем использовать только методы GET. Нам также нужно использовать скрытое поле ввода еще раз, чтобы использовать переопределение метода Синатры, на этот раз указав ему направить запрос как метод DELETE.

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

 delete '/pages/:id' do Page.find(params[:id]).destroy redirect to('/pages') end 

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

Permalinks

До сих пор мы использовали идентификатор объекта Page в качестве URL. MongoDB использует очень большие идентификаторы, так что это означает, что у нас есть URL-адреса, такие как /pages/5173f443a39401776a000001 . Они очень длинные и не очень описательные, поэтому было бы неплохо, если бы мы могли создать «симпатичный URL» на основе заголовка страницы.

Для этого мы должны добавить в нашу модель страницы новое поле с именем permalink . Это можно сделать с помощью следующей строки кода:

 field :permalink, type: String, default: -> { make_permalink } 

Это не будет поле, которое заполняется пользователем в форме. Он будет автоматически создан на основе заголовка. Мы делаем это путем добавления значения по умолчанию, которое устанавливается с помощью лямбды, которая ссылается на метод с именем make_permalink . Этот метод берет заголовок страницы и удаляет все пробелы или знаки препинания с дефисом (‘-‘), используя различные строковые методы. Вот метод, он просто должен идти внутри класса Page :

 def make_permalink title.downcase.gsub(/W/,'-').squeeze('-').chomp('-') if title end 

Мы можем проверить эту функциональность в IRB, используя следующие строки кода:

 $> irb 2.0.0-p0 :001 > require './main' => true 

Теперь найдите первый документ в нашей коллекции, и вы обнаружите, что он удивительным образом уже имеет поле с permalink с соответствующим значением:

 2.0.0-p0 :002 > Page.first => #This is our first page ", permalink: "hello-world"> 

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

 2.0.0p0 :003 > Page.find_by(permalink: "hello-world") Mongoid::Errors::DocumentNotFound: 

Это связано с тем, что объект Page с новым permalink полем необходимо сохранить. Это легко сделать с помощью следующего кода:

 2.0.0p0 :004 > Page.first.save => true 

Теперь мы должны найти страницу, используя ее постоянную ссылку:

 2.0.0p0 :005 > Page.find_by(permalink: "hello-world") => #This is our first page ", permalink: "hello-world"> 

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

 2.0.0p0 :007 > Page.all.each{|page| page.save } 

Кажется, все работает как надо. Теперь нам просто нужно создать обработчик маршрута для красивых URL-адресов. Это просто будет постоянная ссылка и не будет начинаться с /pages . Например, перейдя по http://localhost:4567/hello-world увидите страницу с заголовком «Hello World!». Этот обработчик маршрута будет фактически соответствовать каждому маршруту, поэтому, чтобы другие маршруты могли пройти, мы будем использовать метод pass Sinatra в блоке resuce, который будет вызываться, если страница не найдена в базе данных.

Добавьте следующий код в main.rb :

 get '/:permalink' do begin @page = Page.find_by(permalink: params[:permalink]) rescue pass end slim :show end 

Этот обработчик маршрута попытается найти страницу на основе постоянной ссылки, указанной в URL-адресе, и сохранить ее в @page экземпляра @page прежде чем отобразить страницу, используя представление show которое мы уже создали. Если не удается найти страницу в базе данных, выдается ошибка. Метод rescue перехватывает ошибку и вызывает метод pass , поэтому Sinatra просто перейдет к следующему маршруту, чтобы проверить, совпадает ли он.

Добавление некоторого стиля

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

 get('/styles/main.css'){ scss :styles } 

Затем поместите следующую строку в файл макета:

 link rel="stylesheet" href="/styles/main.css" 

Затем создайте файл с именем styles.scss и сохраните его в каталоге представлений. Это где вы положили все свои стили.

Вот один, который я собрал вместе. Он добавляет немного цвета, делает форму немного приятнее и делает некоторые ссылки и поля ввода похожими на кнопки:

 $blue: #0166FF; body{ margin: 0; padding: 0; font: 13px/1.2 helvetica, arial, sans-serif; } h1,h2,h3,h4,h5,h6{ color: $blue; } .logo { background: #444; margin: 0; padding: 20px 10px; font-size: 44px; a,a:visited{ color: $blue; text-decoration: none;} } .button{ border: none; border-radius: 8px; padding: 8px 12px; margin: 4px; color: #fff; background: $blue; text-decoration: none; font-weight: bold; display: inline-block; width: auto; &:hover{ background: darken($blue,20%); } } label{ display: block; font-weight: bold; font-size: 16px; } form, input, textarea{ width: 100%; } input, textarea { border: #ccc 1px solid; padding: 10px 5px; font-size: 16px; } 

Обратите внимание, что для работы некоторых из этих стилей вам необходимо добавить класс button для соответствующих элементов в ваших представлениях. Например, show.slim теперь выглядит так:

 h1= @page.title - if @page.content == markdown @page.content a.button href="/pages/#{@page.id}/edit" Edit a.button href="/pages/delete/#{@page.id}" Delete 

… и это выглядит намного лучше для этого!

Screenshot4

Это все люди!

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