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 reservadas do y end
  • 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.