[~/srcSTW/url_shortener_with_active_records(master)]$ tree -A
.
|-- Gemfile
|-- Gemfile.lock
|-- README
|-- Rakefile
|-- app.rb
|-- db
| |-- config.yml
| `-- migrate
| `-- 20121017115717_shortened_urls.rb
|-- shortened_urls.db
|-- shortened_urls_bak.db
`-- views
|-- index.haml
|-- layout.haml
`-- success.haml
3 directories, 12 files
Una vez instaladas las gemas implicadas:
[~/srcSTW/url_shortener_with_active_records(master)]$ cat Gemfile source 'https://rubygems.org' #gem 'alphadecimal' gem 'sinatra-activerecord' gem 'sqlite3'con
bundle install,
procedemos a añadir objetivos al Rakefile:
[~/srcSTW/url_shortener_with_active_records(master)]$ cat Rakefile
$: << '.' # add current path to the search path
require 'sinatra/activerecord/rake'
require 'app'
desc "Reset the data base to initial state"
task :clean do
sh "mv shortened_urls.db tmp/"
sh "mv db/migrate /tmp/"
end
desc "Create the specific ActiveRecord migration for this app"
task :create_migration do
sh "rake db:create_migration NAME=create_shortened_urls"
end
desc "shows the code you have to have in your db/migrate/#number_shortened_urls.rb file"
task :edit_migration do
source = <<EOS
class ShortenedUrls < ActiveRecord::Migration
def up
create_table :shortened_urls do |t|
t.string :url
end
add_index :shortened_urls, :url
end
def down
drop_table :shortened_urls
end
end
EOS
puts "Edit the migration and insert this code:"
puts source
end
desc "run the url shortener app"
task :run do
sh "ruby app.rb"
end
Este Rakefile tiene los siguientes objetivos:
[~/srcSTW/url_shortener_with_active_records(master)]$ rake -T rake clean # Reset the data base to initial state rake create_migration # Create the specific ActiveRecord migration for this app rake db:create_migration # create an ActiveRecord migration in ./db/migrate rake db:migrate # migrate the database (use version with VERSION=n) rake db:rollback # roll back the migration (use steps with STEP=n) rake edit_migration # shows the code you have to have in your db/migrate/#number_shortened_urls.rb file rake run # run the url shortener app [~/srcSTW/url_shortener_with_active_records(master)]$De ellos los tres que nos importan ahora son
db:create_migration, db:migrate y db:rollback
que son creados por la línea require 'sinatra/activerecord/rake'.
Las migraciones
nos permiten gestionar la evolución de un esquema utilizado por varias bases de datos.
Es una solución al problema habitual de añadir un campo para proveer una nueva funcionalidad en nuestra
base de datos, pero no tener claro como comunicar dicho cambio al resto de los desarrolladores
y al servidor de producción.
Con las migraciones podemos describir las transformaciones mediante clases autocintenidas que pueden
ser añadadidas a nuestro repositorio git y ejecutadas contra una base de datos que puede estar una, dos
o cinco versiones atrás.
Comenzemos configurando el modelo para nuestra aplicación. En el directorio db
creamos el fichero config.yml:
[~/srcSTW/url_shortener_with_active_records(master)]$ cat db/config.yml development: adapter: sqlite3 encoding: utf8 database: shortened_urls_dev.sqlite test: adapter: sqlite3 encoding: utf8 database: shortened_urls_test.sqlite production: adapter: sqlite3 encoding: utf8 database: shortened_urls_live.sqlite
Comenzaremos creando nuestro modelo.
Para ello ejecutamos rake db:create_migration.
Como vemos debemos pasar una opción NAME para nuestra migración. En nuestro caso pasamos
NAME=create_shortened_urls.
Esto crea la carpeta db/migrate que contienen nuestra migración.
[~/srcSTW/url_shortener_with_active_records(master)]$ tree db
db
|-- config.yml
`-- migrate
`-- 20121017115717_shortened_urls.rb
ahora rellenamos los métodos up y el down:
1 directory, 2 files
[~/srcSTW/url_shortener_with_active_records(master)]$ cat db/migrate/20121017115717_shortened_urls.rb
class ShortenedUrls < ActiveRecord::Migration
def up
create_table :shortened_urls do |t|
t.string :url
end
add_index :shortened_urls, :url
end
def down
drop_table :shortened_urls
end
end
cuando ejecutamos rake db:migrate se ejecuta la migración y crea la base de datos
con la tabla shortened_urls.
En nuestro fichero app.rb creamos nuestro modelo ShortenedUrl.
Para ello escribimos:
class ShortenedUrl < ActiveRecord::Base endDespués incluímos algunas validaciones:
class ShortenedUrl < ActiveRecord::Base
# Validates whether the value of the specified attributes are unique across the system.
validates_uniqueness_of :url
# Validates that the specified attributes are not blank
validates_presence_of :url
#validates_format_of :url, :with => /.*/
validates_format_of :url,
:with => %r{^(https?|ftp)://.+}i,
:allow_blank => true,
:message => "The URL must start with http://, https://, or ftp:// ."
end
A continuación escribimos las rutas:
get '/' do end post '/' do endCreamos también una ruta
get para redireccionar la URL
acortada a su destino final:
get '/:shortened' do endSupongamos que usamos el
id de la URL en la base de datos
para acortar la URL.
Entonces lo que tenemos que hacer es
encontrar mediante el método find
la URL:
get '/:shortened' do short_url = ShortenedUrl.find(params[:shortened].to_i(36)) redirect short_url.url endMediante una llamada de la forma
Model.find(primary_key)
obtenemos el objeto correspondiente a la clave primaria específicada.
que casa con las opciones suministradas.
El SQL equivalente es:
SELECT * FROM shortened_urls WHERE (url.id = params[:shortened].to_i(36)) LIMIT 1
Model.find(primary_key)
genera una excepción ActiveRecord::RecordNotFound
si no se encuentra ningún registro.
Veamos una sesión on sqlite3:
[~/srcSTW/url_shortener_with_active_records(master)]$ sqlite3 shortened_urls.db
SQLite version 3.7.7 2011-06-25 16:35:41
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> .schema
CREATE TABLE "schema_migrations" ("version" varchar(255) NOT NULL);
CREATE TABLE "shortened_urls" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "url" varchar(255));
CREATE INDEX "index_shortened_urls_on_url" ON "shortened_urls" ("url");
CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version");
sqlite> select * from shortened_urls;
1|https://mail.google.com/mail/u/0/
2|https://plus.google.com/u/0/
3|http://campusvirtual.ull.es/1213m2/mod/forum/discuss.php?d=5629
4|http://www.sinatrarb.com/intro#Accessing%20Variables%20in%20Templates
sqlite> select * from shortened_urls where (id = 3) limit 1;
3|http://campusvirtual.ull.es/1213m2/mod/forum/discuss.php?d=5629
sqlite> .quit
Este es el código completo de app.rb:
$ cat app.rb
require 'sinatra'
require 'sinatra/activerecord'
require 'haml'
set :database, 'sqlite3:///shortened_urls.db'
#set :address, 'localhost:4567'
set :address, 'exthost.etsii.ull.es:4567'
class ShortenedUrl < ActiveRecord::Base
# Validates whether the value of the specified attributes are unique across the system.
validates_uniqueness_of :url
# Validates that the specified attributes are not blank
validates_presence_of :url
#validates_format_of :url, :with => /.*/
validates_format_of :url,
:with => %r{^(https?|ftp)://.+}i,
:allow_blank => true,
:message => "The URL must start with http://, https://, or ftp:// ."
end
get '/' do
haml :index
end
post '/' do
@short_url = ShortenedUrl.find_or_create_by_url(params[:url])
if @short_url.valid?
haml :success, :locals => { :address => settings.address }
else
haml :index
end
end
get '/:shortened' do
short_url = ShortenedUrl.find(params[:shortened].to_i(36))
redirect short_url.url
end
Las Vistas. Primero
el fichero views/layout.haml:
$ cat views/layout.haml
!!!
%html
%body
=yield
%form(action="/" method="POST")
%label(for="url")
%input(type="text" name="url" id="url" accesskey="s")
%input(type="submit" value="Shorten")
Creamos un formulario que envía con method="POST" a la raíz action="/".
tiene un elemento input para obtener la URL.
El formulario es procesado por la ruta post '/':
post '/' do
@short_url = ShortenedUrl.find_or_create_by_url(params[:url])
if @short_url.valid?
haml :success, :locals => { :address => settings.address }
else
haml :index
end
end
El método find_or_create
encontrará la URL o creará una nueva.
El fichero views/index.haml:
[~/srcSTW/url_shortener_with_active_records(master)]$ cat views/index.haml
- if @short_url.present? && !@short_url.valid?
%p Invalid URL: #{@short_url.url}
El fichero views/success.haml:
[~/srcSTW/url_shortener_with_active_records(master)]$ cat views/success.haml
%p #{params}
%p http://#{address}/#{@short_url.id.to_s(36)}
Puede ir añadiendo extensiones a la práctica:
find puede serle útil:
get '/show' do urls = ShortenedUrl.find(:all) ... haml :show end
http://www.sinatrarb.com/documentation a http://localhost:4567/sindoc
Esto obliga a poner una opción para ello en el formulario:
%form(action="/" method="POST")
%label(for="url") URL
%input(type="text" name="url" id="url" accesskey="s")
%br
%label(for="custom") Custom Shortened URL(optional)
%input(type="text" name="custom" id="custom" accesskey="t")
%br
%input(type="submit" value="Shorten" class="btn btn-primary")
y a comprobar de alguna manera si la opción custom contiene algo.