Ruby en 15 Minutos

Este resumen está basado en Ruby Course de Brian Schröder.

'Hola mundo'

puts 'Hola mundo'

salida:

Hola mundo

Asignaciones

# Toda asignación devuelve su valor
a = 4            # 4
 
# Las asignaciones se pueden usar en cadena
a = b = 4        # 4
a + b            # 8
 
# y usados en un test
fichero = File.open('../tutor.txt')     # #<File:../tutor.txt>
cont_linea = 0                          # 0
cont_linea += 1 while (line = fichero.gets)# nil
 
# Atajos
a += 2          # 6
a = a+2         # 8
 
# Asignación en paralelo
a, b = b, a     # [4, 8]
 
# Dividiendo arrays
array = [1, 2]  # [1, 2]
a, b = *array   # [1, 2]

irb y ri

irb: es el intérprete interactivo de ruby, donde se pueden probar al vuelo porciones de código.

ri: herramienta para acceder a la documentación. Por ejemplo, para documentarnos sobre el método tr de la clase String, ponemos en la línea de comandos:

ri String#tr

Sintaxis

variables, constantes, métodos,..

Variables / Métodos: estudiante, i, epsilon, piso_grande
Las variables y los métodos se parecen. Esto es lógico, puesto que una variable se puede sustituír por un método.

Las variables y los métodos deben ser escritos en minúsculas, que empiecen por una letra, y que contengan cualquier carácter alfanumérico o el guión bajo. Por ej: piso_grande. Sin embargo, las clases deben empezar por mayúscula. Además, a diferencia de los métodos/variables, no se usa el guión bajo para nombres compuestos. Por ej: PisoGrande.

Las clases deben empezar por mayúscula. Además, a diferencia de los métodos/variables, no se usa el guión bajo para nombres compuestos.

Constantes: PDF_KEY, R2D2, PERSONA_VIAJE
Las constantes sólo se pueden definir una vez.

Las constantes deben ser escritas en letras mayúsculas.

Variables de objeto: @nombre, @maximo, @hora_comer
Las variables de objeto (o variables de instancia) sólo pueden ser leídas/escritas por el propio objeto.

Variables de clase: objeto, llave_maestra
Las variables de clase no pertenecen a los objetos, si no a la clase. Existen únicamente en la clase, y son compartidas por todos los objetos.

Variables globlales: $global, $1, $contador
El uso de las variables globales ha sido declarado un crimen capital por la escuela del buen diseño.

Símbolos: :nombre, :edad, :Clase
Los símbolos son identificadores únicos que los encontraremos en varios sitios.

funciones, strings interpolados, números

# Las funciones están definidas por la palabra clave "def"
# Los argumentos pueden tener valores por defecto
 
def multi_foo(count = 3)
  'foo'*count
end
 
# Los paréntesis pueden omitirse, si la situación no es ambigua
multi_foo(3)        # "foo foo foo"
puts "hola mundo"   " hola mundo
 
# Para interpolar valores dentro de los strings:
# i. Usar comillas dobles
# ii. Usar #{...}
'Simple #{multi_foo(2)}'    # Simple #{multi_foo(2)}
"Interpolado #{multi_foo}"  # Interpolado foo foo foo
 
# Números
10      # 10
0.5     # 0.5
2e-4    # 0.0002
0xFFFF  # 65535
010     # 8

Definición de funciones

Las funciones se definen por la palabra clave def

def hola(programador)
  puts "Hola #{programador}"
end
 
hola('Pepe')    # Hola Pepe

Clases y objetos

Todo es un objeto…

…acostúmbrate a usar la notación del .método

(5.6).round         # 6
(5.6).class         # Float
(5.6).round.class   # Fixnum
 
'un string'.length       # 9
'un string'.class        # String
'pepe dice'.gsub('p','t')       # 'tete dice'
'abc'.gsub('b','xxx').length    # 5
 
['algunas', 'cosas', 'array'].length    # 3
['algunas', 'cosas', 'array'].reverse   # ['array', 'cosas', 'array']
 
# Al ser un objeto, el método suma es: 
1.+(2)                  # 3
# pero para estos casos hay un azúcar sintáctico:
1 + 2                   # 3

Definición de clases

class Persona
  def initialize(nombre)
    @nombre = nombre
  end
 
  def saludar
    "Hola, mi nombre es #{@nombre}."
  end
end
 
pepe = Person.new('Pepe')
puts pepe.saludar   # Hola, mi nombre es Pepe

Herencia de clases

class Matz < Persona
  def initialize
    super('Yukihiro Matsumoto')
  end
end
 
puts Matz.new.saludar   # Hola, mi nombre es Yukihiro Matusmoto

Accesores

Los accesores son funciones que permiten el acceso a los atributos de un objeto.

class Perro
  def initialize(nombre, raza)
   @nombre = nombre
   @raza = raza
  end
 
  # para leer las propiedades de un objeto
  def nombre
    @nombre
  end
 
  def raza
    @raza
  end
end
 
perro1 = Perro.new('Trampas', 'palleiro')
perro1.nombre   # Trampas
perro1.raza     # palleiro

Esto es equivalente a:

class Perro
  def initialize(nombre, raza)
   @nombre = nombre
   @raza = raza
  end
 
  attr_reader :nombre, :raza
end
 
perro1 = Perro.new('Trampas', 'palleiro')
perro1.nombre   # Trampas
perro1.raza     # palleiro

Esta es la tabla con el tipo de accesores, y su función:

accesor función
attr_reader lectura
attr_writer escritura
attr_accessor lectura y escritura

Clases: extendiendo las clases

En Ruby las clases nunca se consideran cerradas, y se pueden modificar al vuelo, añadiendo métodos, variables,…

Por ejemplo, veamos como añadir una nueva funcionalidad a la clase Integer:

class Integer
  def fac
    raise "Factorización no definida para #{self}" if self < 0
    return (1..self).inject(1) {|result,i| result*i}
  end
end
 
puts(0..5).map{|i| i.fac}.join(', ')    # 1, 1, 3, 6, 24, 120

Excepciones

La captura se hace mediante rescue previo uso de begin:

begin
  # Código que puede dar problemas
rescue ClaseExcepcion1 => exception1
  # Se ejecuta si se lanza una ClaseExcepcion1
rescue ClaseExcepcion2 => exception2
  # Se ejecuta si se lanza una ClaseExcepcion2
rescue
  # Captura cualquier excepción
ensure
  # Código que siempre se ejecuta
end

Para lanzar una exepción, usaremos raise

Módulos

Definición

Los módulos son similares a las clases en que contienen una colección de métodos, constantes y otros módulos y definiciones. Pero a diferencia de las clases, no se pueden crear clases derivadas de los módulos.

# trig.rb  
module Trig  
  PI = 3.1416  
  # métodos 
  def Trig.sin(x)  
    # ...  
  end  
  def Trig.cos(x)  
    # ...  
  end  
end  
 
require 'trig'  
Trig.sin(Trig::PI/4)    # "::" -> PI/4 de la clas Trig

Herencia múltiple

Podemos añadir funcionalidades de distintos módulos:

module D1  
    ...
end  
 
module D2
    ...
end
 
class ClaseCualquiera
  include D1
  include D2
end

Arrays

# Array literal
['Un', 'array', 'con', 5, 'entradas'].join(' ') # Un array con 5 entradas
 
# Nuevo array
a = Array.new               # []
a << 'algunas' << 'cosas'   # algunas cosas
a[2]                        # cosas
a[0] = 3                    # 3
a                           # 3 cosas
 
# Se pueden usar valores por defecto...
Array.new(4, 0)             # [0, 0, 0, 0]
 
# ...pero ten cuidado
a = Array.new(2, 'Silke')   # ['Silke', 'Silke']
a[0] << 'Amberg'            # Silke Amberg
a                           # ['Silke Amberg', 'Silke Amberg']

Arrays: los arrays pueden ser usados como pilas o colas

print 'Array como pila: '
pila = Array.new()
pila.push('a')
pila.push('b')
pila.push('c')
print pila.pop until pila.empty?    # Array como pila: cba
 
print "\n"
print 'Array como cola: '
cola = Array.new()
cola.push('a').push('b').push('c')
print cola.shift until cola.empty?  # Array como cola: abc

Hashes (diccionarios)

# Hash literal
h0 = {'uno' => 1, 'dos' => 2}
h0['uno']           # 1
 
# Rellenando un hash
h1 = Hash.new       #{}
h1['joya'] = 'ruby'
h1['fruta'] = 'banana'
h1                  # {"joya"=>"ruby", "fruta"=> "banana"}
 
# A menudo, los símbolos se usan como llaves
h2 = {:junio => 'perl', :julio => 'ruby'}
h2[:julio]          # ruby

Bloques e iteradores

Bloques: definición

Un bloque es una pieza de código, similar a un función anónima. Una función puede usar un bloque como argumento.

# Un iterador sencillo, llamando al bloque por cada elemento del array
['soy', 'un', 'platano'].each do |elemento| print elemento, ' ' end #soy un platano
 
# Otro iterador muy usado. El bloque modifica el contexto done fue creado
fac = 1
1.upto(5) do |i| fac *= i end
fac             # 120
 
# El resultado del bloque puede ser usado por quién lo usa...
[1,2,3,4,5].map{ |num| num*num}   # [1,4,9,16,25]
 
# ...y puede usarse más de un argumento
(0..100).inject(0){ |resultado, num| result + num}  #5050

Bloques: sintaxis

# Los bloques son encerrados por do || ... end
[1,2,3,4,5].each do |e| puts e end
 
# o por paréntesis {|| ... }
[1,2,3,4,5].map{ |e| e*e}       #[1,4,9,15,25]

Por convención:

  • usa do || … end donde las modificaciones sean importantes
  • y {|| … } donde el valor de retorno sea importante

Iteradores

def f(cont, &bloque)
  valor = 1
  1.upto(cont) do |i|
    valor = valor * i
    block.call(i, valor)
  end
end
 
f(5).do |i, f_i| puts "f(#{i}) = #{f_i}" end
f(1) = 1
f(2) = 2
f(3) = 6
f(4) = 24
f(5) = 120

Bloques: salvando los bloques

class Repetidor
  def initialize(&bloque)
    @bloque = bloque
    @cont = 0
  end
 
  def repetir
    @cont += 1
    @block.call(@cont)
  end
end
 
repetidor = Repetidor.new do |cont| puts " Me has llamado #{cont} veces" end
3.times do repetidor.repetir end
Me has llamado 1 veces
Me has llamado 2 veces
Me has llamado 3 veces

Estructuras de control

case

def saludar(*nombres)   # * indica número indefinido de argumentos
  case nombres.length
    when 0
      "Que triste, nadie ha leido mi tutorial"
    when 1
      "Hola #{nombres}. Por lo menos una persona quiere saber Ruby"
    when 2..5
      "Hola #{nombres.join(', ')}. Gracias por venir"
    when 6..12
      "#{nombres.length} lectores. Bienvenidos a Ruby!"
    else
      "Wow #{nombres.length} lectores. Cuanta gente!"
  end
end
 
puts saludar('Alejandro', 'Luis', 'Pedro', 'Antonio', 'Guido', 'Matz, 'Satish')
# 7 lectores. Bienvendios a Ruby!

condicionales

Ruby tiene todas las estructuras de control estándar. Y además, se pueden anexar a una expresión.

# Estructura de control habitual...
if (1+1 == 2)
  "Me gusta la escuela"
else
  "Menuda sorpresa!"
end
 
#...que podemos anexar a la derecha
"Me gusta la escuela." if (1+1 == 2)    # "Me gusta la escuela"
"Menuda sorpresa!" unless (1+1 == 2)    # nil
 
(1+1 == 2) ? 'Correcto':'Incorrecto'    # Correcto
 
prob_lluvia = rand(100)                 # 64 (por ejemplo)
case prob_lluvia
  when 0...10 then "Probabilidad más baja"
  when 10...50 then "Baja probabilidad"
  when 50...90 then "Alta probabilidad"
  when 90...100 then "Probabilidad más alta"
end                                     # Alta probabilidad

verdadero y falso

Únicamente nil y false son falsos; todo lo demás, es verdadero.

def es_true(valor)
 puts valor ? true : false
end                     # nil
 
es_true(false)          # false
es_true(nil)            # false
es_true(true)           # true
es_true(1)              # true
es_true(0)              # true
es_true([0,1,2])        # true
es_true('a'..'z')       # true
es_true('')             # true
es_true(:un_simbolo)    # true

bucles

Ruby tiene donde elegir en las construcciones de bucles, ¡pero no hay que olvidarse de los bloques!

i = 1       # 1
 
while (i < 10)
  i *=2
end         # nil
puts i           # 16
 
i *= 2 while (i < 100)  # nil
puts i                       # 128
 
begin
  i *= 2
end while (i < 100)     # nil
puts i                       # 256
 
i *=2 until (i >= 1000) # nil
puts i                       # 1024
 
loop do
  break i if (i >= 4000)
  i *= 2
end                     # 4096
puts i                       # 4096
 
4.times do i *= 2 end   # 4
puts i                       # 65536
 
r = []                  # []
for i in 0..7
  next if i % 2 == 0
  r << i
end                     # 0..7
puts r                       # [1, 3, 5, 7]
 
# Los bloques pueden simplificar muchas cosas
(0..7).select{ |i| % 2 != 0}   # [1, 3, 5, 7]

Expresiones regulares

En las expresiones regulares:

  • Se ponen entre / /
  • Cualquier caracter concuerda con si mismo, excepto \/^$|.+*?()[]\{\} que tienen distintas funciones dentro de las expresiones. Para poder buscarlos, hay que usar la secuencia de escape \. P. ej: \\, \/,…
  • ^ busca el principio de una línea, $ busca el final de una línea
  • . busca cualquier carácter
  • Si a,b son expresiones regulares, entonces:
    • ab es también una expresión regular
    • a* es una expresión regular que busca el caparazón de a
    • a+ es equivalente a aa*
    • a|b busca a ò b
    • Las expresiones pueden ser agrupadas por paréntesis. P. ej:
/(a|b)c/        # busca {'ac', 'bc}
/a|bc/          # busca {'a', 'bc'}
  • [caracteres] busca un rango de caracteres.
/[a-zA-Z0-9]/   # busca los caracteres alfanuméricos
  • [^caracteres] busca los caracteres que NO pertenecen al rango.
  • Atajos para los rangos:
atajo rango
\w [0-9A-Za-z]
\W [^0-9A-Za-z]
\s [\t\n\r\f]
\S [^ \t\n\r\f]

Unidades de Testeo

El testeo de unidades es un método para testear el código en pequeños trozos.

Por qué

  • Significa que nunca tendrás el problema de crear un error mientras solucionas otro.
  • Significa que no tendrás que ejecutar tu programa y jugar con él (lo que es lento) para arreglar los errores. El testeo de unidades es mucho más rápido que el "testeo manual".
  • Conociendo cómo usar las unidades de test, abre el mundo al driven development.

Requisitos

  • Cargar la biblioteca 'test/unit'
  • Hacer que la clase a testear sea una subclase de Test::Unit::TestCase
  • Escribir los métodos con el prefijo test_
  • Afirmar (assert) las cosas que decidas que sean ciertas.
  • Ejecutar los tests y corregir los errores hasta que desaparezcan.
require 'test/unit'
 
class MiPrimerTest < Test::Unit::TestCase
  def test_de_verdad 
    assert true
  end
end
Si no se indica lo contrario, el contenido de esta página se ofrece bajo Creative Commons Attribution 3.0 License