Billetes para el tren: aprendiendo a programar con Ruby on Rails (1)

A petición de unos amigos, empiezo una serie de posts que intentarán ser una pequeña guía para aprender a usar Ruby on Rails de una forma rápida. El objetivo es que tras leerlos tengáis los detalles importantes de la foto y podáis poneros enseguida a escribir código útil.
En el post de hoy, todavía no empezaremos a ver Rails, si no que haremos una introducción rápida al lenguaje de programación en que están escritas las aplicaciones Ruby on Rails, es decir, Ruby.
Lo primero que debéis hacer es descargar Ruby desde su página inicial. La distribución de ruby instala el intérprete del lenguaje, que se invoca con la orden ruby además de muchas otras utilidades. Entre ellas está la que más nos interesa para esta introducción: IRB, la shell interactiva de Ruby. Con esta utilidad podéis ir probando los ejemplo a medida que los comente, simplemente tecleando el código línea a línea en la shell, y obtener en ese mismo instante el resultado computado.
Por ejemplo:
$irb
irb(main):001:0>test = "hola ruby"
=>"hola ruby"
irb(main):002:0>test
=>"hola ruby"
Un truco muy útil cuando se usa IRB, es utilizar las capacidades dinámicas de Ruby para inspeccionar los métodos y la clase de cualquier objeto que hayáis creado. Siguiendo con el ejemplo anterior:
irb>test =>"hola ruby" irb>test.class #preguntamos cual es la clase del objeto test =>String irb>test.methods.sort #pedimos la lista de métodos a los que responde el #ordenados alfabéticamente =>=> ["%", "*", "+", "<", "<<", "<=", "<=>", "==", "===", "=~", ">", ">=", "[]", "[]=", "__id__", "__send__", "all?", "any?", "between?", "capitalize", "capitalize!", "casecmp", "center", "chomp", "chomp!", "chop", "chop!", "class", "clone", "collect", "concat", "count", "crypt", "delete", "delete!", "detect", "display", "downcase", "downcase!", "dump", "dup", "each", "each_byte", "each_line", "each_with_index", "empty?", "entries", "eql?", "equal?"...,"zip"]
Una vez instalada la distribución de Ruby e iniciado en una shell el IRB, podemos empezar a escribir código.
Ruby es un lenguaje de programación con tres características principales: es interpretado, es orientado a objetos y es dinámico. Además las tres características las lleva, como veréis, hasta el extremo.
Veamos como hacer algunas cosas esenciales:
Creando objetos
En Ruby todo es un objeto, también las clases, así que para crear un nuevo objeto simplemente se llama al método new de la clase en cuestión, ¡nada de palabras reservadas!:
irb>string_one = String.new("hello world")
=>"hello world"
irb>arr = Array.new
=>[]
Definiendo una función
Para construir una nueva función en Ruby se utiliza la palabra reservada def con la siguiente sintaxis:
def nombre_función(param1=val1, ..., paramN=valN,*rest)
...
end
Es decir, el nombre de la función, seguido de una lista de parámetros a los que se puede asignar un valor por defecto, y un argumento opcional como *rest en el ejemplo, que es un array donde se almacenan todos los parámetros que de el usuario partir de del parámetro N. La definición termina con la palabra reservada end
Algunos ejemplos
irb>def saludo(texta="hola",textb="mundo",*other)
puts texta
puts textb
for elem in other
puts elem
end
end
=>nil
irb>saludar
hola
mundo
=>nil
irb>saludar("adios")
adios
mundo
=>nil
irb>saludar("adios","mundo cruel")
adios
mundo cruel
=>nil
irb>saludar("ya","se","saludar","en","ruby")
ya
se
saludar
en
ruby
=>
En Ruby, todas las funciones devuelven algo, concretamente el resultado de la última línea de la función, aunque también se puede especificar el valor de retorno de forma explícita usando la palabra reservada return
Entendiendo la sintaxis de Ruby
Ruby ofrece una gran libertad al programador a la hora de expresar la sintaxis válida con la que escribir sus programas. En el código Ruby hay que tener en cuenta los siguientes aspectos:
- El punto y coma al final de sentencia es opcional
- El uso de paréntesis es opcional en funciones con menos de dos argumentos.
- Los nombres de clases y módulos empiezan con mayúscula y se usa la sintaxis típica de perl y java e.g.
MiClaseRuby - Los nombres de funciones y parámetros se suelen escribir en minúscula separando las palabras del identificador por un guión bajo, e.g.
mi_funcion_ruby. - Los nombres de constantes se escriben con mayúsculas, e.g.
CONSTANTE_DE_EJEMPLO - Las cadenas se pueden encerrar tanto entre comillas dobles com sencillas
- El valor nulo se denomina
nil - Para referenciar un identificador dentro de un módulo se usa el separador
:: - Los bloques de código se delimitan o bien por
{ }o por las palabras reservadasdoyend - Los comentarios empiezan a partir del símbolo
#y terminan en el final de la línea.
A continuación se muestran las construcciones más típicas del lenguaje:
Condicionales
#condicional simple
if(test_condition==true)
...
end
#if ... else
if(test_condition == false)
...
else
...
end
#ejemplo con elsif
if(test_a == true)
...
elsif(test_b == false)
...
end
#condicional multiple
case test_c
when test_c == 1
...
when test_c == 2
...
else
...
end
Bucles
5.times do
...
end
while condition
...
end
for i in 0..100
...
end
Creando nuevas clases
Para definir una nueva clase en Ruby se usa la palabra reservada class seguida del nombre de la clase y, opcionalmente el símbolo < y el nombre de la clase de la que hereda. Por defecto todas las clases heredan de la clase Object. En ruby no se permite la herencia múltiple.
irb> class A
...
end
=>nil
irb> class B < A
...
end
=>nil
irb>b = B.new
=> #<B:0x70134>
El estado de una clase se construye añadiendo variables de instancia a la clase. Para declarar una nueva variable de instancia se asigna en cualquier parte del cuerpo de la clase el valor a un identificador precedido del símbolo @. Para poder acceder a la variable desde fuera del contexto de la clase es necesario ofrecer un acceso a través de métodos. Los otros objetos no pueden acceder directamente al estado de los objetos de la clase:
#prescindiremos de indicar la shell del IRB
class A
def una_var
@una_var
end
def una_var=(nuevo_valor)
@una_var = nuevo_valor
end
end
> a = A.new
=> A#3242
>a.una_var = 33
=>33
a.una_var
=>33
Para inicializar el estado de un objeto se puede construir un método constructor, que recibe siempre el nombre de initialize. Este método es invocado automáticamente cuando se llama al método new para crear una nueva instancia.
class A
def initialize(una_var=0)
@una_var = una_var
end
def una_var
@una_var
end
def una_var=(nuevo_valor)
@una_var = nuevo_valor
end
end
> a = A.new(15)
#A:ee3425
>a.una_var
=>15
Para evitar la tediosa tarea de construir el método get y set de cada variable de instancia, ruby ofrece el atajo de definir accessors funciones que contruyen el método def variable y def variable=(valor) para cada variable de instancia. Este es un ejemplo de las capacidades dinámicas del lenguaje.
La sintaxis de los accessors es attr_tipo_accessor simbolo_nombre_variable
Los posibles accessors son attr_reader,attr_writer y attr_accessor para lectura, escritura y lectura/escritura.
Un símbolo se puede definir como una cadena de texto de la que existe una única instancia en todo el sistema, los símbolos se definen usando : más el texto del símbolo.
De este modo, si quisiésemos definir un accessor de solo lectura para la variable de instancia @test, escribiríamos el código attr_reader :test.
Quedará más claro con un ejemplo:
class Test
#permitir lectura de la variable @contador
attr_reader :contador
#permitir escritura de la variable @buzon
attr_writer :buzon
#permitir lectura y escritura de la variable @sobre
attr_accessor :sobre
def initialize
@contador=0
@buzon = "el buzon esta vacio"
@sobre = 34
end
def consultar
puts "El buzón contiene:"+@buzon
@contador += 1
end
end
> t = Test.new
>t.sobre
=>34
>t.sobre = 12
=>12
>t.sobre
=>12
>t.contador
=>0
>t.contador = 34
=> ERROR! no existe el metodo
>t.buzon
=> ERROR! no existe el metodo
>t.buzon "si puedo escribir"
=>"si puedo escribir"
>t.consultar
=>El buzon contiene:si puedo escribir
t.contador
=>1
En caso de ambiguedad entre una variable y un parámetro, se puede utilizar la referencia self similar al puntero this de otros lenguajes de programación como Java.
Además de definir métodos y variables de instancia, también se pueden definir métodos y variables de clase, compartidas por todas las instancias de la misma. La sintaxis consiste en utilizar dos @@ para las variables y la sintaxis de definición de métodos normal pero antecediendo el nombre del método con el nombre de la clase. Las variables de clase tampoco pueden ser accedidas directamente desde fuera del contexto de la clase.
class Test
@@contador = 0
def Test.saludo_global
puts "hola tests"
@@contador +=1
end
def Test.contador
@@contador
end
end
>Test.saludo_global
=>"hola tests"
>Test.contador
=>1
>Test.contador = 34
=> ERROR! No existe el metodo
>t = Test.new
=>#Test:435
>t.saludo_global
=> ERROR! No existe el metodo
El acceso a los métodos de una clase se puede regular con las palabras reservadas public, protected y private, que tienen el significado habitual que reciben en otros lenguajes de programación. Para usarlos, simplemente se escribe en cualquier lugar de la definición de la clase el indicador, y a partir de ese punto todo el código que se escriba estará sometido a dicha restricción, hasta que se encuentre un nuevo indicador de acceso o se llegue al final de la definición de la clase. Por defecto, las clases en Ruby tienen acceso public.
Módulos
Un módulo en Ruby sirve, por un lado, como un espacio de nombres en el que incluir funciones, variables, constantes y clases dentro de un contexto modular. Luego esos símbolos pueden ser accedidos usando el operado :: y la llamada include
module A
CONSTANTE_A = 34
def saludo_desde_a
puts "hola amigos"
end
end
>A::CONSTANTE_A
=>34
>A::saludo_desde_a
=> ERROR! no existe el método
>include A
=>Object
>saludo_desde_a
=>hola amigos
Lo interesante de los modulos en Ruby es que se pueden incluir mediante el uso de include, dentro de la definición de una clase, lo que se conoce en la jerga del lenguaje como un mixin. De esa forma esos metodos y constantes pasan a ser metodos y constantes de las instancias de la clase donde se ha incluido el módulo. Se puede ver un mixin como algo a medio camino entre la herencia múltiple de C++ y las interfaces de Java.
module Playable
def play
self.new_track
end
end
class Mp3Player < Mp3Engine
include Playable
def new_track
...
end
end
>mi_mp3 = Mp3Player.new
=>#Mp3Player:4312
>mi_mp3.play
=>reproduciendo KaiserChiefs:Ruby
Alternativamente al uso de include, los módulos también se pueden extender, usando para ello la directiva extend. La diferencia radica en que include produce un efecto similar al que se obtendría escribiendo el código del modulo en el punto en el que se llama a include, de tal forma que si es dentro de una clase, en ese punto se introduce todo lo que haya en el módulo que se mezcla (mixin) con los otros métodos disponibles para las instancias de esa clase. Sin embargo, cuando se llama a extend, se obtiene un resultado similar al que se hubiera obtenido si ese objeto hubiese heredado del módulo, de tal forma que si un objeto exitende un modulo mediante extend esa instancia puede llamar al contenido del módulo, pero si se extiende el módulo dentro de la definición de una clase, como hacíamos antes con include, el resultado es que la clase, que también es un objeto, obtiene acceso a los métodos del modulo que se convierten en métodos de clase, pero las instancias de la clase que ha extendido el módulo, no puede acceder a ellos, como sí ocurría a con include
Tipado en ruby
Ruby es un lenguaje dinámico y por lo tanto, no se especifica el tipo de los objetos en el código, sino que este es inferido por el intérprete en tiempo de ejecución. En ruby, el tipo de un objeto no es importante, lo realmente importante es que sepa responder al mensaje (método) que se le envía. De esta manera, dos objetos de dos clases diferentes, pero que tengan definido el mismo método, serían considerados del mismo tipo por un objeto cliente que invocase dicho método sobre ambos objetos.
Esta forma de abordar el problema del tipado es conocido como duck typing y se puede resumir en que lo importante no es que algo sea o no un pato o no, sino camine como un pato, nade como un pato y que haga "cua cua"
Algunas clases implementan métodos que fuerzan un determinado comportamiento en sus objetos, como si fuese una cadena de texto, un entero, etc. Estos métodos por convención se llaman to_inicialdeltipo. Por ejemplo, para hacer que un objeto del tipo Class se comporte como una cadena se llamaría al método to_s. De la misma manera otras clases implementan métodos to_i o to_f, por ejemplo, para que esos objetos se comportasen como un integer y un float respectivamente.
Colecciones e iteradores
Las dos clases más importantes de las clases colección incluidas en la biblioteca estándar de ruby son la clase Array y la clase Hash. Estas clases contienen todos los métodos que se pueden esperar de un tipo array y de un tipo hash, por ejemplo:
>a1 = Array.new
=>[]
>a1.push 1
=>[1]
>a1.push(2,3,4,5)
=>[1,2,3,4,5]
>a1[0]
=>1
>a1.shift
=>1
>a1
=>[2,3,4,5]
>a1.length
=>4
>a1[10]=99
=>99
>a1
=>[2,3,4,5,nil,nil,nil,nil,nil,99]
>a2 = [1,10,100]
=>[1,10,100]
>h1=Hash.new
=>{}
>h1["a"]=1
=>{"a" => 1}
>h1["2"]="casa"
=>{"a" => 1, "2" => "casa"}
>h1.keys
=>["a","2"]
>h1.values
=>[1,"casa"]
Para ver todos los métodos de ambas clases se puede consultar la documentación de ruby o jugar con los objetos usando IRB
Lo realmente interesante sobre las colecciones es la forma en que se suele iterar por objetos colección en Ruby. Esta tarea se lleva a cabo utilizando bloques, es decir, porciones de código que se pasan a un método de la colección y a los que este método se encarga de llamar iterativamente pasando en cada llamada un elemento de la colección como argumento.
Por convención, estos métodos suelen recibir el nombre de each.Veamos un ejemplo
>a = [1,2,3,4]
=>[1,2,3,4]
>a.each{|num|
puts num
}
=>
1
2
3
4
>h = { "hola"=>1, "mundo"=>2}
=>{"hola"=>1,"mundo"=>2}
>h.each_pair{|key,value|
puts "Valor:#{key},#{value}"
}
=>
Valor:hola,1
Valor:mundo,2
>
Como se puede apreciar, el bloque es la porción del código entre {}, aunque también se podría marcar con las palabras reservadasdo y end. Al principio del bloque se puede indicar una serie de parámetros encerrados entre barras verticales:
do |a,b|
puts "Soy un bloque con parametros:#{a} y #{b}"
end
Los bloques en Ruby se pueden tratar como cualquier otro tipo de dato, se pueden asignar a variables y se pueden pasar como parámetros, como hemos hecho al pasarlos a los métodos each y each_pair de las clases Array y Hash respectivamente.
De hecho, cualquier función Ruby puede recibir un bloque como argumento que se pasa como un parámetro oculto. Dentro del método se puede comprobar si se ha pasado un método llamando al método block_given? y se puede invocar al método llamando a la función yield:
def test(a,b)
if block_given?
yield
yield
end
a+b
end
>test(1,2)
=>3
>test(1,2){ puts "Esta el bloque ahi?"}
=>
Esta el bloque ahi?
Esta el bloque ahi?
3
def test_two
if block_given?
yield(1)
yield(2)
else
puts "no me han pasado ningun bloque"
end
end
>test_two()do |arg|
puts "Recibo en el bloque el parametro #{arg}"
end
=>
Recibo en el bloque el parametro 1
Recibo en el bloque el parametro 2
>test_two
=>
no me han pasado ningun bloque
>
class MiArray
def initialize
@array = [1,2,3,4,5]
end
def each
for i in @array
yield i
end
end
end
> ma = MiArray.new
=>#Array:1123
>ma.each{|item|
puts item.to_s
}
=>
1
2
3
4
5
Una última consideración a tener en cuenta de los bloques en Ruby, es que cada bloque guarda junto al código del mismo todo su entorno de ejecución, es decir, que los bloques en Ruby son lo que se conocen en computación como closures. Esto confiere una gran potencia a los bloques como herramientas del lenguaje, que se explota totalmente cuando se utilizan características avanzadas de ruby como las funciones lambda.
En el siguiente ejemplo vemos como en un bloque se almacena el valor de las variables del entorno en el que se declara el bloque y cuyo valor se recupera posteriormente al invocarse el bloque, aunque dicho entorno no sea accesible desde el ámbito en el que se invoca al bloque.
class TestBloque
def intento_acceder
puts solo_visible_fuera
end
def accedo_a_traves_del_bloque
yield
end
end
>solo_visible_fuera="hola"
=>"hola"
>tb= TestBloque.new
=>#TestBloque:4459
>tb.intento_acceder
=>Error no existe el simbolo
>tb.accedo_a_traves_del_bloque{
puts solo_visible_fuera
}
=>hola
Conclusiones
Bueno, aquí acabamos este rápido repaso por las características de Ruby. Aunque sólo hemos rascado la superficie de todas las posibildades y características del lenguaje, esto debería ser suficiente para entender la mayoría del código Ruby de las aplicaciones Ruby on Rails.
En la próxima entrega de esta serie empezaremos a utilizar estos conocimientos para construir nuevas y maravillosas aplicaciones web con nuestro framework favorito.
Ante cualquier duda con el código Ruby, podéis acudir a la documentación del lenguaje o investigar el código usando IRB. Si todavía no encontráis la respuesta a vuestra duda, o queréis conocer todos los secretos de Ruby, no puedo menos que recomendaros robar este maravilloso libro:Programming ruby: The pragmatic programmers' guide.

Carlos dijo
Breve repaso pero enjundioso. La verdad es que el lenguaje es muy ágil. Pasemos a ponerlo sobre raíles.
31 Agosto 2007 | 11:58 PM