Billetes para el tren: aprendiendo a programar con Ruby on Rails (3)
Bueno, ha llegado el momento (ahora sí, de verdad) de empezar a picar el código de nuestra aplicación.
Una aplicación rails se construye alrededor del patrón de arquitectura más usado en la breve historia de la ingeniería del software: el patrón modelo-vista-controlador (MVC para abreviar).
La idea es muy sencilla, una aplicación informática no es más que un montón de código que permite a un usuario manipular información. Esa información debe ser persistente, es decir, debe seguir existiendo entre dos usos de la aplicación por parte de los usuarios, reflejando los cambios que sobre ella realicen estos. Esta información normalmente se almacena en una base de datos relacional, en la forma de tuplas dentro de las tablas de la BBDD. Esta capa de la aplicación es la que compone la 'M' que hace referencia al modelo del patrón MVC.
La capa de la vista se encarga de visualizar los datos de una forma que el usuario pueda acceder a ellos y manipularlos de una forma sencilla como, por ejemplo, las celdas de una hoja de cálculo, un formulario de modificación de datos, etc.
Entre modelo y vista está el controlador, que encierra la lógica de la aplicación y se encarga de trasportar los datos desde el modelo a la vista así como de recoger la interacción del usuario con la vista para modificar, acorde con ella, el modelo.
Aquí podéis ver un ejemplo gráfico del patrón MVC de otra excelente implementación del mismo (el framework Cocoa de Apple) pero que sirve para ilustrar la arquitectura de Rails.
En Rails, el modelo se construye a partir de la clase ActiveRecord , y el esquema de la base de datos se define usando lo que en la jerga de rails se conoce como migraciones.
Imaginemos que en nuestra aplicación de prueba necesitamos almacenar datos sobre los usuarios de nuestra web. Queremos almacenar su nombre, su apellido además de un login y un password para que puedan acceder a la misma. Deberemos por lo tanto crear una tabla en nuestra base de datos para almacenar esta información. La clase que representará a los usuarios en nuestro modelo de datos se llamará, por ejemplo User, esto quiere decir que nuestra tabla para los usuarios se deberá llamar obligatoriamente users. Esta obligatoriedad en el nombre de la tabla es un ejemplo muy característico del enfoque que sigue rails para llevar a cabo la configuración de la aplicación. Rails prefiere siempre usar convenciones antes que tener que llenar ficheros XML y ficheros XML con datos de configuración sobre la aplicación, algo que en plataformas como java puede llegar a ser realmente doloroso y fuente de numerosos y frustrantes errores. Por lo tanto, el nombre de la tabla que guarda la información sobre una clase del modelo es siempre el plural de esa tabla. Rails es capaz incluso capaz de deducir que la clase Person del modelo se almacenará en la tabla people. En realidad esta convención no es siempre obligatoria, pero es la forma preferida de trabajar con rails.
Bien, sabemos que necesitamos crear la tabla users en la base datos y un fichero con la clase User, pero en vez de hacerlo manualmente, dejemos que rails se encargue de hacer todo eso por nosotros.
Abrimos una shell en el directorio raíz de nuestra aplicación y tecleamos lo siguiente:
tanguillo:~/Desarrollo/Rails/test_app antonio$ script/generate model user
create app/models/
create test/unit/
create test/fixtures/
create app/models/user.rb
create test/unit/user_test.rb
create test/fixtures/users.yml
create db/migrate
create db/migrate/001_create_users.rb
tanguillo:~/Desarrollo/Rails/test_app antonio$
Con el script anterior le hemos indicado a rails que genere todo lo que necesitamos para definir, usar y testear un nuevo objeto del modelo de nuestra aplicación web.
Veamos que ficheros se han generado:
app/model/user.rbEl fichero con la clase del modelo, como vemos su definición es bastante sencilla:class User < ActiveRecord::Base endSimplemente el nombre de la clase y la signatura de la herencia de la clase base de
ActiveRecord. Sin embargo este código hace mucho más de lo que pudiera parecer a simple vista.db/migrate/001_create_users.rbLa migración a la que nos referíamos con anterioridad.
En vez de escribir el código sql para crear la tabla de la base de datos, las migraciones son porciones de código en ruby que crearán la tabla por nosotros.
Una migración tiene dos métodos básicos up y down. Además, todos los ficheros de migración van numerados, desde el 001_*.rb, que acabamos de crear, hasta el n_*.rb. Rails mantiene en la base de datos el número de versión del esquema de la base de datos, en nuestro caso el 0, porque todavía no la hemos modificado, de tal forma que si invocamos la orden $rake db:migrate desde el directorio raíz de la aplicación, rails irá ejecutando los scripts de las migraciones que encuentre en orden secuencial desde el 001_*.rb hasta el n_*.rb, invocando al método up de cada objeto Migration en ellos contenidos y modificando de esta forma la base de datos según nuestras indicaciones. De la misma manera, si estamos en una versión determinada de la base de datos, por ejemplo la 5, y queremos pasar a una versión anterior, por ejemplo la 3, se puede invocar la orden rake db:migrate VERSION=3 Con lo que rake irá invocando a los métodos down de los objetos Migration en los ficheros 005_*.rb y 004_*.rb para dejar la BBDD en el estado especificado en el fichero 003_*.rb. Por lo tanto, en el método down deberemos indicar como deshacer los cambios que en la BBDD especifiquemos en el método up. La idea es que la llamada sucesiva a los métodos up y down de una migración sea idempotente para el estado de la base de datos.
Creemos la migración para el modelo de usuarios de nuestra aplicación, abrimos el fichero 001_create_users.rb:
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.column :name, :string, :null => false
t.column :surname, :string, :null => false
t.column :login, :string, :null => false
t.column :password, :string, :null => false
end
end
def self.down
drop_table :users
end
end
Como vemos, rails ya había indicado las sentencias para crear y destruir la tabla users, solamente hemos tenido que indicar en el bloque do ... end las llamadas a los métodos para añadir las columnas de la tabla con sus tipos. También hemos indicado que no se permite la creación de las tuplas con columnas nulas. Una aclaración importante es que rails, al ejecutar la migración, ya se encarga de introducir la columna para la clave primaria en la tabla por nosotros, así que no es necesario que nosotros la introduzcamos. Esta tabla por convención se llamará id y será de tipo integer. Una migración permite realizar prácticamente todas las cosas que se pueden hacer con sql: modificaciones, índices, etc. Se puede consultar la documentación de la case ActiveRecord::Migration para un buen repaso con ejemplos a todas ellas.
Cuando hayamos terminado de editar el fichero, estamos listos para ejecutar la migración, tecleamos lo siguiente:
~/Desarrollo/Rails/test_app $ rake db:migrate
(in /Users/antonio/Desarrollo/Rails/test_app)
== CreateUsers: migrating =====================================================
-- create_table(:users)
-> 0.6603s
== CreateUsers: migrated (0.6605s) ============================================
~/Desarrollo/Rails/test_app $
Ahora si examinamos nuestra base de datos del entorno de desarrollo, test_app_development, deberemos ver la tabla users recién creada y vacía.
Como ya comentamos, para deshacer los cambios, bastaría con indicar a rake el número 0 de versión y ejecutaría el método down destruyendo la tabla.
La orden rake db:migrate realiza las modificaciones sobre la base de datos del entorno seleccionado por defecto, en este caso development, se podría indicar los otros entornos (production y test) para que ejecutase las migraciones sobre sus respectivas bases de datos, pero existe un mecanismo mucho más simple. utilizar la orden rake db:test:clone que por ejemplo copiaría el esquema actual de desarrollo al entorno de test. Como vamos a tratar el tema de los test y necesitamos tener actualizada la BBDD de test, ejecutamos la orden anterior:
~/Desarrollo/Rails/test_app $ rake db:test:clone
(in /Users/antonio/Desarrollo/Rails/test_app)
~/Desarrollo/Rails/test_app $
Si ahora examinamos la base de datos test_app_test deberíamos ver que tiene su tabla users creada.
test/unit/user_test.rb El fichero para realizar las pruebas unitarias de nuestro modelo de usuarios. Los tests son parte íntegra de test y forman una de las prácticas más importantes de la ingeniería del software ágil que rails favorece. Los tests hacen que el software sea más fiable, permite asegurar que un nuevo método no estropea algo que programaste hace dos meses, además de servir de documentación sobre como funciona nuestra aplicación. El ideal último que muchos programadores rails perseguimos se llama test driven development que prescribe la escritura de los tests antes de escribir el código que van a testear, como una forma de indicar el contrato que ese código debe cumplir, de una forma muy similar a la práctica de ingeniería que defendía Bertrand Meyer con su diseño por contrato. Si eres un desarrollador que piensa que nunca hay tiempo para escribir tests, piensas que a lo mejor de lo que no tienes tiempo es de no escribirlos.Como buenos programadores rails, vamos a escribir unos tests para nuestra recién creada clase User del modelo lo que de paso nos servirá para aprender a escribir tests y ver que podemos hacer con esa, aparentemente no muy útil clase que generamos hace un rato.
En rails existen diferentes tipos de tests: unitarios, funcionales y de integración. Los tests del modelo se llaman, en la jerga de rails, tests unitarios, y viven en el directorio test/unit Por convención, todos los ficheros de test que escribamos deben terminar con el sufjio test. Abramos el fichero de test para la clase User que nos generó el script de rails (create test/unit/user_test.rb) y escribamos algunos tests para la creación, modificación y borrado de usuarios:
require File.dirname(__FILE__) + '/../test_helper'
class UserTest < Test::Unit::TestCase
fixtures :users
def test_creation
number_of_users = User.count
user_one = User.new(){ |user|
user.name = 'John'
user.surname = 'Smith'
user.login = 'john_smith'
user.password = 'debaser'
}
assert_nothing_raised() {
user_one.save!
}
assert_equal(number_of_users+1, User.count)
end
def test_update
user = User.find(:first, :conditions => ["name = 'test'"])
assert_not_nil(user)
user.surname = 'changed'
assert_nothing_raised() {
user.save!
}
user = User.find(:first, :conditions => ["name = 'test'"])
assert_equal('changed',user.surname)
end
def test_deletion
number_of_users = User.count
user = User.find(:first, :conditions => ["name = 'test'"])
assert_not_nil(user)
user.destroy
assert_equal(number_of_users-1,User.count)
end
end
Un test case o caso de prueba en rails es una clase que termina con el nombre Test y que hereda de la clase Test::Unit::TestCase. Los tests son los métodos de esa clase que empiezan con el nombre test. Cuando ejecutamos el script que prueba los test, este irá buscando esos métodos y ejecutándolos secuencialmente. Un test consiste en una serie de aserciones (modeladas como funciones) sobre como debe ejecutarse el código de la aplicación, en el ejemplo anterior hay algunas: aserciones de que algo es igual a otra cosa (assert_equal), que algo no es nulo (assert_not_nil) o que una serie de sentencias no generan ninguna excepción. En la documentación de ruby se puede encontrar una lista de las posibles aserciones.
¿Y qué hemos probado en estos tests? pues algunas de las funcionalidades básicas que las clases del modelo tienen en ruby on rails. Como se puede apreciar en el código anterior, una vez que hemos creado un nuevo objeto del modelo con el método new podemos acceder a los valores de las columnas que hemos descrito en el esquema de la base de datos, a través de métodos que recubren atributos con el mismo nombre. ActiveRecord se encarga de ello por nosotros.
Una vez asignados valorse a esos atributos, podemos almacenar la nueva tupla en la base de datos llamando al método save save! (la única diferencia es que el último lanza una excepción si no se han podido almacenar los datos en la base de datos. El método de clase count nos devuelve el número de tuplas (objetos del modelo) hay almacenadas en la tabla de la base de datos para esa clase del modelo.
Para buscar objetos de entre todos los almacenados en la base de datos se puede utilizar el método de clase find. find admite varios argumentos: un entero que interpreta como el identificador de esa tupla, el primero objeto que cumple una serie de condiciones similiares a las que se especifica en una consulta SELECT de SQL o todos los registros que cumplan una condición. La documentación del método ofrece numerosos ejemplos de su uso.
Pero para poder buscar algo en la BBDD a través del método find, primero necesitamos tener algo en la base de datos. En el entorno de producción tendremos los datos de nuestros usuarios, pero para los tests, necesitamos datos específicos sólo para los tests. Antes de ejecutar cada test, Rails elimina todos los datos de la BBDD y la vuelve a llenar con los datos específicos del test. Los datos que debe introducir se indican a través de una fixture un fichero de texto que describen los datos a insertar en un formato conocido como YAML muy usado en rails, terriblemente sencillo. Por ejemplo, para nuestros tests de usuarios, debemos editar el fichero test/fixtures/users.yml
one:
id: 1
name: test
surname: test
login: test
password: test
En este fichero describimos un nuevo usuario llamado one con todos los valores que deben tener las columnas de esa instancia en la BBDD. De forma que luego podamos encontrar esos datos o borrarlos en nuestros tests. La única precaución que debemos tener a la hora de editar el fichero de una fixture es que no se debe utilizar tabuladores para identar los datos, sino espacios en blanco.
En el último test podemos ver como se elimina un objeto del modelo mediante el uso del método destroy
Ahora que entendemos que es lo que hace ActiveRecord para dar vida al modelo de datos en Ruby on Rails, sólo nos queda ejecutar los tests para tener la seguridad que nuestra capa de datos funciona correctamente.
Para ello ejecutamos el siguiente script:
tanguillo:~/Desarrollo/Rails/test_app antonio$ rake test
(in /Users/antonio/Desarrollo/Rails/test_app)
/opt/local/bin/ruby -Ilib:test "/opt/local/lib/ruby/gems/1.8/gems/rake-0.7.3/lib/rake/rake_test_loader.rb" "test/unit/user_test.rb"
Loaded suite /opt/local/lib/ruby/gems/1.8/gems/rake-0.7.3/lib/rake/rake_test_loader
Started
...
Finished in 0.096402 seconds.
3 tests, 7 assertions, 0 failures, 0 errors
Como podemos comprobar nuestro código ha pasado todos los tests, podemos tener cierta seguridad de la corrección de nuestro modelo de datos.
En el siguiente posts, terminaremos de ver las posibilidades más avanzadas que ofrece ActiveRecord en especial, como especificar relaciones entre tablas de la base datos de cardinalidad 1:1, 1:N y N:N.

dsafdsafd dijo
hi , just loof buy cheap fake rolex and our best replica watches are very cheap fake swiss watches
3 Enero 2010 | 01:26 PM