[~/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 filesUna 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" endEste
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.rbahora 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 endcuando 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:// ." endA 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 endEl 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.