Sinatra и DataMapper: пример сервиса сокращения ссылок
В мире Ruby существует несколько ORM библиотек. Самая популярная библиотека ActiveRecord является и самой тяжелой, к тому же ее не особо удобно использовать вне Rails. Для небольших приложений на фреймворке Sinatra наиболее оптимальным вариантом является библиотека DataMapper, которая не уступает по функциональности, а в некоторых моментах опережает своего главного конкурента.
На примере простого сервиса сокращения ссылок рассмотрим работу с DataMapper внутри Sinatra.
Устанавливаем гемы с DataMapper и адаптером Sqlite к нему:
DataMapper состоит из нескольких библиотек, использовать будем только некоторые из них:
dm-core- ядро DataMapper'а.dm-validations- готовые методы для проверки входных данных.dm-timestamps- автоматическое создание и обновление полейcreated_at,updated_at.
В app.rb подключим необходимые библиотеки из DataMapper и настроим соединение с базой данных:
require 'dm-validations'
require 'dm-timestamps'
DataMapper.setup(:default, "sqlite3://#{Dir.pwd}/#{Sinatra::Application.environment}.sqlite")
Настала пора написать класс модели данных:
include DataMapper::Resource
property :id, Serial
property :url, String, :length => 255, :required => true, :index => true
timestamps :at
validates_format :url, :as => :url
end
Каждая модель включает модуль DataMapper::Resource, с помощью метода property определяются поля в базе данных и свойства модели.
Метод timestamps (доступен при подключении dm-timestamps) создает поля created_at и updated_at и обновляет их при добавлении/изменении записи в базе данных.
Также используется метод validates_format из библиотеки dm-validations для того, чтобы перед сохранением происходила проверка на корректность поля url.
Изменение структуры базы данных делаем с помощью rake, для этого создаем Rakefile:
desc 'Migrate DataMapper database'
task :migrate do
DataMapper.auto_migrate!
end
Метод auto_migrate! создает таблицы, которых еще нет, и при необходимости добавляет новые поля в уже существующие.
Теперь можно легко применить изменения структуры базы данных из командной строки:
В коротких ссылках будем использовать поле id, закодированное с помощью библиотеки base62.
Устанавливаем соответствующий gem:
Подключаем библиотеку base62 в app.rb:
Для удобного экранирования при выводе добавим в хелперах алиас к методу escape_html из Rack::Utils, а для генерации ссылки напишем хелпер link_url, использующих метод base62_encode из библиотеки base62:
include Rack::Utils
alias :h :escape_html
def link_url(link)
"http://#{request.host}/#{link.id.base62_encode}"
end
end
Для запроса GET на главную страницу создаем новый объект модели Link и рендерим шаблон index.haml:
@link = Link.new
haml :index
end
При POST запросе получаем из базы данных существующую ссылку по url или создаем и сохраняем новую с помощью метода first_or_create:
@link = Link.first_or_create(:url => params[:url])
haml :index
end
В шаблоне index.haml выводим форму добавления ссылки с ошибкой в случае неправильного url:
%input{:type => :text, :size => 40, :name => :url, :value => @link.url || 'http://'}
%input{:type => :submit, :value => 'Shorten'}
- if @link.errors[:url].any?
.error=h 'may be some error in your url?'
Выводим информацию о созданной короткой ссылке, с помощью метода saved? проверяется, что ссылка уже сохранена в базе данных (то есть произошел сабмит формы и ошибок не было):
%dl
%dt Original:
%dd=h @link.url
%dt Shorted:
%dd
%a{:href => link_url(@link)}=h link_url(@link)
Для перенаправления по коротким ссылкам используем роутинг с регулярным выражением:
@link = Link.get(params[:captures].first.base62_decode)
throw :halt, [404, "Not found"] unless @link
redirect @link.url
end
В массиве params[:captures].first будет содержаться буквы и цифры после / в короткой ссылке, являющиеся закодированным id. Преобразование base62 строки в число делаем с помощью метода base62_decode, а метод get модели позволяет получить запись по первичному ключу (то есть полю id).
Полный исходный код можно взять на github:
Увидеть приложение в работе можно по ссылке http://tourl.heroku.com/.
Related posts: