Está en la página 1de 357

Lenguaje de programacin orientado a objetos Ruby

(Traduccin Libre de la 2 edicin del libro Programming Ruby. The Pragmatic Programmers Guide de Dave Thomas)

Nombres Ruby
Ruby utiliza una convencin para ayudar a distinguir el uso de un nombre: los primeros caracteres de un nombre indican cmo se utiliza el nombre. Los nombres de las variables locales, de los mtodos y de los parmetros de mtodo deben comenzar con una letra minscula o con un guin bajo. Las variables globales van precedidas de un signo de dlar ($), y las variables de instancia deben comenzar con una arroba (@). Las variables de clase empiezan con dos arrobas (@@). Por ltimo, los nombres genricos, nombres de mdulos, y las constantes debe comenzar con una letra mayscula. Muestras de diferentes nombres se dan en la Tabla 1.1:

Despus de este carcter inicial, un nombre puede tener cualquier combinacin de letras, dgitos y carcter de subrayado (con la salvedad de que el carcter que sigue a un signo @ no puede ser un dgito). Sin embargo, por convencin, los casos de variables de instancia con varias palabras se escriben con caracteres de subrayado entre las palabras y los nombres de clase con varias palabras estn escritos en clase MixedCase (con cada palabra capitalizada).

Arrays y Hashes
Los arrays y hashes de Ruby son colecciones indexadas. Ambas colecciones de objetos almacenados son accesibles mediante una clave. Con las matrices, la clave es un nmero entero, mientras que los hashes soportan cualquier objeto como una clave. Ambos, matrices y hashes pueden crecer segn sea necesario para mantener los nuevos elementos. Es ms eficiente acceder a elementos de la matriz, pero los hashes proporcionan una mayor flexibilidad. Cualquier array o hash particular, puede contener objetos de diferentes tipos. Se puede tener una matriz que contiene un entero, una cadena, y un nmero de punto flotante, como veremos en un minuto. Puede crear e inicializar un nuevo objeto matriz con un array literal --un conjunto de elementos entre corchetes. Dado un objeto matriz, puede acceder a elementos individuales mediante el suministro de un ndice entre corchetes, como muestra el siguiente ejemplo. Tenga en cuenta que los ndices ruby de array comienzan en cero. a = [ 1, cat, 3.14 ] # matriz con tres elementos # acceso al primer elemento a[0] -> 1 # establecer el tercer elemento a[2] = nil # volcado de salida de la matriz a -> [1, cat, nil] Puede haber notado que hemos usado el valor especial nil en este ejemplo. En muchos idiomas, el

concepto de cero (o nulo) significa no hay ningn objeto. En Ruby, este no es el caso, nada es un objeto como cualquier otro, que pasa a representar nada. A veces, la creacin de matrices de palabras puede ser un dolor de cabeza, con todas las comillas y comas. Afortunadamente, Ruby tiene un acceso directo: %w hace exactamente lo que queremos. a = [ ant, bee, cat, dog, elk ] a[0] ant a[3] dog # esto hace lo mismo: a = %w{ ant bee cat dog elk } a[0] ant a[3] dog Los hashes de Ruby son similares a las matrices. Un hash literal utiliza llaves en lugar de corchetes. El literal debe proporcionar dos objetos para cada entrada: uno para la clave, y el otro para el valor. Por ejemplo, es posible que desee asignar instrumentos musicales para sus secciones de orquesta. Usted puede hacer esto con un hash. inst_section = { cello => string, clarinet => woodwind, tambor => percussion, oboe => woodwind, trumpet => brass, violin => string } Lo que hay a la izquierda de la => es la clave y lo que hay a la derecha es el valor correspondiente. Las claves en un hash en particular deben ser nicas, no se puede tener dos entradas para tambor. Las claves y los valores de un hash pueden ser objetos arbitrarios --se pueden tener hash donde los valores son arrays, otros hashes, etc. Los valores hash son indexados usando la misma notacin de corchetes como en las matrices.

inst_section[oboe] woodwind inst_section[cello] string inst_section[bassoon] nil Como muestra el ejemplo anterior, un hash por defecto devuelve nil cuando es indexado por una clave que no contiene. Normalmente esto es til, ya que nil significa falso cuando se utiliza en las expresiones condicionales. A veces querr cambiar este valor predeterminado. Por ejemplo, si est utilizando un hash para contar el nmero de veces que cada tecla tiene lugar, es conveniente tener el valor por defecto de cero. Esto se hace fcilmente mediante la especificacin de un valor por defecto cuando se crea un nuevo hash vaco. histogram = Hash.new(0) histogram[key1] 0 histogram[key1] = histogram[key1] + 1 histogram[key1] 1 Los objetos array y hash tienen un montn de mtodos tiles que se vern en las correspondientes secciones de referencia.

Estructuras de control
Ruby tiene todas las estructuras de control habituales, como las sentencias if y los bucles while. Los programadores de Java, C o Perl pueden quedar atrapados por la falta de llaves alrededor de los cuerpos de estas declaraciones. En su lugar, Ruby utiliza la palabra clave end para indicar el final de un cuerpo.

if count > 10 puts Intentalo otra vez elsif tries == 3 puts Pierdes else puts Escribe un nmero end Del mismo modo, las sentencias while se terminan con end.

while peso < 100 and num_pallets <= 30 pallet = next_pallet() peso += pallet.peso num_pallets += 1 end La mayora de las sentencias en Ruby devuelven un valor, lo que significa que se pueden utilizar como condiciones. Por ejemplo, el mtodo jets devuelve la siguiente lnea del flujo de entrada estndar o nil cuando se alcanza el final del fichero. Debido a que Ruby trata nil como un valor falso en las condiciones, podra escribir lo siguiente para procesar las lneas de un archivo. while linea = gets puts linea.downcase end En este caso, la sentencia de asignacin establece la variable linea ya sea a la siguiente lnea de texto o a nulo, y a continuacin, la sentencia while comprueba el valor de la asignacin, que termina el bucle cuando es nil. Los modificadores de declaracin ruby son un atajo til si el cuerpo de un if o while es una sola expresin. Simplemente escriba la expresin, seguido por if o while y la condicin. Por ejemplo, aqu una simple declaracin if. if radiation > 3000 puts Danger, Will Robinson end Aqu otra vez, reescrita mediante un modificador de declaracin.

puts Danger, Will Robinson if radiation > 3000 Del mismo modo, un bucle while como

square = 2 while square < 1000 square = square*square end se convierte en el ms breve

square = 2 square = square*square while square < 1000 Estos modificadores de declaracin debe ser familiares a los programadores de Perl.

Expresiones Regulares
Muchos de los tipos ruby empotrados (built-in) sern familiares para todos los programadores. La mayora de los idiomas tienen cadenas, enteros, reales, matrices, etc. Sin embargo, el soporte para

expresionesregulares se construye tpicamente slo en lenguajes de programacin como Ruby, Perl y awk. Esto es una pena: las expresiones regulares, aunque crpticas, son una poderosa herramienta para trabajar con texto. Y si estn integradas, en lugar de pegadas a travs de una interfaz de biblioteca, hay una gran diferencia. Libros enteros se han escrito sobre las expresiones regulares (por ejemplo, Mastering Regular Expressions [Fri02]), por lo que no vamos a tratar de cubrir todo en esta breve seccin. En su lugar, vamos a ver algunos ejemplos de expresiones regulares en accin. Se encontrar una cobertura completa de las expresiones regulares ms adelante. Una expresin regular es simplemente una manera de especificar un patrn de caracteres que se ajustaran a una cadena. En Ruby, se suele crear una expresin regular escribiendo un patrn entre los caracteres de barra inclinada (/patrn/). Y en Ruby, como es Ruby, las expresiones regulares son objetos y se pueden manipular como tal. Por ejemplo, podra escribir un patrn que coincida con una cadena que contiene el texto Perl o Python con la siguiente expresin regular. /Perl|Python/ Las barras inclinadas delimitan el patrn, que consiste en las dos cosas que son coincidentes separados por una barra vertical (|). El caracter pipe en este caso, significa o bien la cosa a la derecha o bien la cosa a la izquierda, ya sea Perl o Python. Se pueden utilizar parntesis dentro de los patrones, tal como se puede en las expresiones aritmticas, por lo que tambin se podra haber escrito este patrn /P(erl|ython)/ Tambin puede especificar la repeticin dentro de los patrones. /ab+c/ coincide con una cadena que contiene una a seguida de una o ms b, seguida por una c. Cambiar el signo ms por un asterisco, y /ab*c/ crea una expresin regular que coincide con una a, cero o ms b y una c. Tambin puede coincidir con un elemento de un grupo de caracteres dentro de un patrn. Algunos ejemplos comunes son las clases caracter como \s, que coincide con un espacio en blanco (espacio, tabulador, nueva lnea, etc); \d,que coincide con cualquier dgito, y \w, que coincide con cualquier carcter que pueda aparecer en una palabra tpica. Un punto (.) coincide con (casi) cualquier carcter. Una tabla de estas clases carcter se mostrar ms adelante en su correspondiente seccin. Podemos juntar todo esto para producir algunas expresiones regulares tiles. un momento tal que 12:34:56 Perl, cero o ms caracteres y Python Perl, un espacio y Python Perl, cero o ms espacios y Python Perl, uno o ms espacios y Python Perl, espacio en blanco y Python Ruby, un espacio , y cualquiera de los dos, Perl o Python

/\d\d:\d\d:\d\d/ # /Perl.*Python/ # /Perl Python/ # /Perl *Python/ # /Perl +Python/ # /Perl\s+Python/ # /Ruby (Perl|Python)/ #

Una vez que se haya creado un patrn, es una pena no usarlo. El operador de emparejamiento =~ se pueden utilizar para comparar una cadena con una expresin regular. Si el patrn se encuentra en la cadena, =~ devuelve su posicin inicial y en caso contrario devuelve nil. Esto significa que usted puede utilizar expresiones regulares como condicin en las declaraciones if y while. Por ejemplo, el siguiente fragmento de cdigo escribe un mensaje si una cadena contiene el texto Perl o Python. if line =~ /Perl|Python/ puts Scripting language mentioned: #{line} end La parte de una cadena coincidente con una expresin regular puede ser sustituida por otro texto mediante uno de los mtodos de sustitucin de Ruby.

line.sub (/Perl/, Ruby) # cambiar el primer Perl por Ruby line.gsub (/Python/, Ruby) # cambiar cada Python por Ruby Puede reemplazar todas las apariciones de Perl y Python con Ruby utilizando

line.gsub (/Perl|Python/, Ruby)


Vamos a decir mucho ms acerca de las expresiones regulares a medida que avanzamos a travs del libro.

Bloques e Iteradores
Esta seccin describe brevemente uno de los puntos fuertes de Ruby. Vamos a ver como lucen los bloques de cdigo: trozos de cdigo que se pueden asociar con las llamadas a mtodos casi como si fueran parmetros. Esta es una caracterstica muy poderosa. Uno de nuestros revisores coment en este punto: Esto es muy interesante e importante, y si no le estaba prestando atencin antes, probablemente debera empezar ahora. Tendramos que estar de acuerdo. Se pueden utilizar los bloques de cdigo para implementar rellanadas (son ms simples que las clases annimas internas de Java), para pasar trozos de cdigo (son ms flexibles que los punteros a funciones de C), y para implementar iteradores. Los bloques de cdigo son trozos de cdigo entre llaves o entre doend. # esto es un bloque ### # y as es en este # ###

{ puts Hello } do club.enroll(person) person.socialize end

Por qu hay dos tipos de delimitadores? Es en parte porque a veces uno siente ms natural escribir uno que otro. Es en parte tambin, porque tienen distintas precedencias: las llaves unen con ms fuerza que los pares do/end. En este libro, tratamos de seguir lo que se est convirtiendo en un estndar de Ruby y las llaves se usan para bloques de una sola lnea y do/end para bloques multilinea. Una vez que se haya creado un bloque, puede ser asociado con una llamada a un mtodo. Esto se hace poniendo el inicio del bloque al final de la lnea fuente que contiene la llamada al mtodo. Por ejemplo, en el siguiente cdigo, el bloque que contiene puts Hola est asociado con la llamada al mtodo greet (saludar). greet { puts Hola } Si el mtodo tiene parmetros, aparecen antes del bloque.

verbose_greet(Dave, cliente fiel) { puts Hola } Un mtodo puede invocar un bloque asociado una o ms veces con la declaracin Ruby yield (productividad). Usted puede pensar en yield como algo parecido a una llamada a un mtodo que llama al bloque asociado con el mtodo que contiene yield. El siguiente ejemplo muestra esto en accin. Se define un mtodo que llama a yield dos veces. A continuacin, llama a este mtodo, poniendo un bloque en la misma lnea despus de la llamada (y despus de los argumentos para el mtodo). A algunas personas les gusta pensar en la asociacin de un bloque con un mtodo como una especie de paso de parmetros. Esto funciona en un nivel, pero en realidad no es toda la historia. Usted puede ver mejor al bloque y al mtodo como co-rutinas que transfieren el control de ida y vuelta entre ellos. def call_block puts Start of method yield

yield puts End of method end call_block { puts In the block } produce: Inicio del mtodo En el bloque En el bloque Final del mtodo Observe cmo funciona el cdigo en el bloque ( puts In the bolck) se ejecuta dos veces, una para cada llamada a yield. Usted puede proporcionar parmetros a la llamada a yield: estos sern pasados al bloque. Dentro del mismo, liste los nombres de los argumentos entre barras verticales (|) para recibir estos parmetros.

Los bloques de cdigo se utilizan en la biblioteca Ruby para implementar iteradores: mtodos que devuelven elementos sucesivos de algn tipo de coleccin como una matriz. animals = %w( ant bee cat dog elk ) # crier ulna lista animals.each {|animal| puts animal } # iterate sobre el contenido produce: ant bee cat dog elk Vamos a ver cmo podemos poner en prctica la clase Array each, iterador que usamos en el ejemplo anterior. El iterador each hace un bucle travs de cada elemento de la matriz, llamando a yield por cada uno de ellos. En pseudo-cdigo, esto puede parecerse a esto: # dentro de la clase Array... def each for each elemento # <--not valid Ruby yield(elemento) end end Muchas de las construcciones de bucles que estn integradas en lenguajes como C y Java son simplemente llamadas a mtodos en Ruby con mtodos que invocan al bloque asociado cero o ms veces. [ cat, dog, horse ].each {|name| print name, } 5.times { print * } 3.upto(6) {|i| print i } (a..e).each {|char| print char } produce: cat dog horse *****3456abcde

Aqu le pedimos al objeto 5 llamar a un bloque cinco veces y luego se pide al objeto 3 llamar a un bloque, pasando valores sucesivos hasta llegar a 6. Finalmente, el rango de caracteres desde a hasta e, invoca a un bloque mediante el mtodo each.

Lectura y Escritura
Ruby cuenta con una amplia biblioteca de E/S. Sin embargo, en la mayora de los ejemplos en este libro vamos a seguir unos pocos mtodos sencillos. Ya hemos llegado llegado a dos mtodos para la salida. puts escribe sus argumentos, aadiendo una nueva lnea despus de cada uno. print tambin escribe sus argumentos, pero sin salto de lnea. Ambos pueden ser utilizados para escribir en cualquier objeto de E/S, pero por defecto, escriben en la salida estndar. Otro mtodo de salida que se utiliza mucho es printf, que imprime sus argumentos bajo el control de una cadena de formato (al igual que printf en C o Perl). printf(Number: %5.2f,\nString: %s\n, 1.23, hola) produce: Number: 1.23, String: hola En este ejemplo, la cadena de formato Number: % 5.2f,\nString: %s\n, le dice a printf que sustituya a un nmero de punto flotante (permitiendo cinco caracteres en total, con dos decimales) y a una cadena. Aviso de los saltos de lnea ( \n) incluidos en la cadena, cada uno mueve la salida a la lnea siguiente. Usted tiene muchas maneras de leer la entrada en su programa. Probablemente el ms tradicional es el uso de la rutina gets, que devuelve la siguiente lnea desde el flujo de entrada estndar de su programa. line = gets print line Ruby escapa a su pasado En los viejos tiempos Ruby tom prestado mucho del lenguaje Perl. Una de estas caractersticas es una cierta magia cuando se trata de variables globales, y probablemente en las no globales ms magia con $_. Por ejemplo, el mtodo gets tiene un efecto secundario: as como regresa la lnea que acaba de leer, tambin la almacena en $_. Si llama a print sin ningn argumento, se imprime el contenido de $_. Si usted escribe un if o un while con slo una expresin regular como condicin, sta expresin se compara con $_. Como resultado de toda esta magia, se podra escribir el siguiente programa para buscar todas las lneas de un archivo que contengan el texto Ruby. while gets if /Ruby/ print end end Sin embargo, este estilo de programacin de Ruby est cayendo rpidamente en desuso con los puristas. Como uno de esos puristas pasa a ser Matz, nos vamos a encontrar ahora advertencias de Ruby para muchos de estos usos especiales: esperamos que estas desaparezcan en un futuro. Esto no significa que usted tienga que escribir programas ms detallados. El va Ruby para escribir esto sera el uso de un iterador y el objeto ARGF predefinido, que representa los archivos de entrada del programa. ARGF.each {|line| print line if line =~ /Ruby/ }

Y se puede escribir mucho ms concisamente: print ARGF.grep(/Ruby/) En general hay un alejamiento de algunos de los Perlismos en la comunidad Ruby. Si ejecuta sus programas con la opcin -w para habilitar las advertencias se encontrar en el intrprete de Ruby capturas de la mayora de ellas.

Adelante y hacia arriba


Esto es todo. Hemos terminado nuestro veloz recorrido por algunos de los rasgos bsicos de Ruby. Hemos echado un vistazo a los objetos, mtodos, cadenas, contenedores y expresiones regulares. Hemos visto algunas estructuras de control simple y algunos iteradores ingeniosos. Esperamos que este captulo le haya dado municin suficiente como para ser capaz de atacar el resto de este libro. Ahora toca seguir adelante a un nivel superior. A continuacin, vamos ver las clases y objetos, que son al mismo tiempo tanto las construcciones de ms alto nivel en Ruby como los fundamentos esenciales de la lengua entera.

Clases, Objetos y Variables


De los ejemplos que hemos mostrado hasta ahora, puede que se pregunte acerca de nuestra afirmacin anterior de que Ruby es un lenguaje orientado a objetos. Bueno, este captulo es en donde se justifica esta afirmacin. Vamos a ver cmo crear clases y objetos en Ruby, y algunas de las formas en que Ruby es ms poderoso que la mayora de los lenguajes orientados a objetos. Despus de meses de trabajo, nuestra muy bien pagada gente de investigacin y desarrollo ha determinado que necesitamos un jukebox de canciones. Entonces, nos parece una buena idea empezar por la creacin de una clase Ruby que represente las cosas que son canciones. Sabemos que una cancin de verdad tiene un nombre, un artista y una duracin, por lo que querr asegurarse de que los objetos cancin en nuestro programa tambin. Vamos a empezar por la creacin de la clase bsica Song, que contiene slo un nico mtodo, initialize (como mencionamos anteriormente, los nombres de clases comienzan con una letra mayscula, y normalmente, los nombres de mtodo comienzan con una letra minscula). class def end end Song initialize(name, artist, duration) @name = name @artist = artist @duration = duration

initialize es un mtodo especial en los programas Ruby. Cuando usted llama a Song.new para crear un nuevo objeto cancin, Ruby reserva algo de memoria para contener un objeto no inicializado y luego llama al mtodo initialize de ese objeto, pasandole todos los parmetros que se le pasan a los nuevos. Esto le da la oportunidad de escribir cdigo que configura el estado de su objeto. Para la clase Song, el mtodo initialize toma tres parmetros. Estos parmetros actan como variables locales dentro del mtodo, por lo que siguen la convencin de nombres de variable local que empiezan con una letra minscula. Cada objeto representa su propia cancin, as que necesitamos cada uno de los objetos cancin llevando su propio nombre, el artista y su duracin. Esto significa que tenemos que almacenar estos valores como variables de instancia dentro del objeto. Las variables de instancia son accesibles a todos los mtodos en un objeto y cada objeto tiene su propia copia de sus variables de instancia.

En Ruby, una variable de instancia es simplemente un nombre precedido de una arroba (@). En nuestro ejemplo, el parmetro name se asigna a la variable de instancia @name, artist se asigna a @artist, y duration (la duracin de la cancin en segundos) se asigna a @duration. Vamos a probar nuestra nueva clase:

song = Song.new(Bicylops, Fleck, 260) song.inspect #<Song:0x1c7ca8 @name=Bicylops, @duration=260, @artist=Fleck> Bueno, parece que funciona. De manera predeterminada, el mensaje inspect, que puede ser enviado a cualquier objeto, formatea el ID de objeto y las variables de instancia. Parece que lo hemos configurado correctamente. Nuestra experiencia nos dice que durante el desarrollo vamos a imprimir el contenido de un objeto Song muchas veces, e inspeccionar el formato por defecto deja mucho que desear. Afortunadamente, Ruby tiene un mensaje estndar, to_s, que enva a cualquier objeto que quiere reporducirlo como una cadena. Vamos a intentarlo en nuestra cancin. song = Song.new(Bicylops, Fleck, 260) song.to_s -> #<Song:0x1c7ec4> Que no es demasiado til -- y que acaba de informar del ID de objeto. Por lo tanto, vamos a redefinir to_s en nuestra clase. Al hacer esto, tambin debemos tomar un momento para hablar de cmo estamos mostrando las definiciones de clase en este libro. En Ruby, las clases no estn cerradas: siempre se pueden aadir mtodos a una clase existente. Esto se aplica a las clases que escriba, as como a las clases estndar y las clases built-in. Slo tiene que abrir una definicin de clase de una clase existente, y los nuevos contenidos que especifique se agregarn a lo que hay. Esto es ideal para nuestros propsitos. A medida que avancemos a travs de este captulo, aadiremos caractersticas a las clases y mostraremos slo las definiciones de clase para los nuevos mtodos; los viejos todava estarn ah. Esto nos ahorra tener que repetir cosas redundantes en cada ejemplo. Obviamente, sin embargo, si usted comenzara la creacin de este cdigo desde cero, probablemente incluiria todos los mtodos en una definicin de clase. Volvamos a la adicin del mtodo to_s a nuestra clase Song. Vamos a utilizar el carcter # en la cadena para interpolar el valor de las tres variables de instancia. class Song def to_s Song: #@name--#@artist (#@duration) end end song = Song.new(Bicylops, Fleck, 260) song.to_s -> Song: Bicylops--Fleck (260) Excelente, estamos haciendo progresos. Sin embargo, hemos puesto algo sutil en el mix. Hemos dicho que Ruby soporta to_s para todos los objetos, pero no dijimos cmo. La respuesta tiene que ver con la herencia, subclases y cmo Ruby determina qu mtodo ejecutar cuando se enva un mensaje a un objeto. Este es un tema para una nueva seccin, por lo que ...

Herencia y Mensajes
La herencia permite crear una clase que es un refinamiento o especializacin de otra clase. Por ejemplo, nuestra mquina de discos tiene el concepto de las canciones, que se encapsulan en la clase Song. Luego, el de marketing viene y nos dice que tenemos que apoyar karaoke. Una cancin de karaoke es como cualquier otra (no tiene una pista de voz, pero eso no nos interesa). Sin embargo, tambin tiene

asociado una letra de cancin, junto con information de sincronizacin.Cuando nuestro jukebox toque una cancin de karaoke, las letra debe fluir a travs de la pantalla en la parte frontal de la mquina de discos, sincronizada con la msica. Una aproximacin a este problema consiste en definir una nueva clase, KaraokeSong, que es como Song, pero con una pista con la letra (lyrics). class def end end KaraokeSong < Song initialize(name, artist, duration, lyrics) super(name, artist, duration) @lyrics = lyrics

El < Song en la lnea de definicin de la clase le dice a Ruby que KaraokeSong es una subclase de Song. (No es de extraar, esto significa que Song es una superclase de KaraokeSong La gente tambin habla de relaciones padre-hijo, por lo el padre de KaraokeSong sera Song...) Por el momento, no se preocupe demasiado por el mtodo initialize, vamos a hablar sobre la llamada super ms adelante. Vamos a crear KaraokeSong y comprobar nuestro cdigo de trabajo. (En el sistema final, lyrics se llevar a cabo en un objeto que incluye el texto y la informacin de sincronizacin). Para probar nuestra clase, sin embargo, slo tendremos que utilizar una cadena. Este es otro beneficio de los lenguajes de tipo dinmico --no tiene que definir todo antes de empezar la ejecucin de cdigo. song = KaraokeSong.new(My Way, Sinatra, 225, And now, the...) song.to_s -> Song: My WaySinatra (225) Bueno, se ejecut. Pero por qu el mtodo to_s muestra la letra?

La respuesta tiene que ver con la forma con la que Ruby determina a qu mtodo se debe llamar cuando se enva un mensaje a un objeto. Durante el anlisis inicial del cdigo fuente del programa, cuando Ruby se encuentra con la invocacin de mtodo song.to_s, en realidad no sabe dnde encontrar el mtodo to_s. En cambio, difiere la decisin hasta que se ejecute el programa. En ese momento, le aparece en la clase Song. Si esa clase implementa un mtodo con el mismo nombre que el mensaje, el mtodo se ejecuta. De lo contrario, Ruby busca un mtodo en la clase principal, y luego en la clase abuelo, y as sucesivamente toda la cadena de los ancestros. Si se queda sin padres sin encontrar el mtodo adecuado, se necesita una accin especial que normalmente resulta en un error (de hecho puede interceptar este error, que le permite engaar a los mtodos en tiempo de ejecucin. Esto se describe en Object#method_missing). Volviendo a nuestro ejemplo. Enviamos el mensaje to_s a la cancin, un objeto de la clase KaraokeSon. Ruby ve en KaraokeSong un mtodo llamado to_s pero no lo halla. El intrprete entonces busca en la clase padre Song, y all se encuentra el mtodo to_s que habiamos definido anteriormente. Es por eso que imprime los detalles de la cancin pero no la letra --la clase Song no sabe nada de letras. Vamos a solucionar este problema mediante la implementacin de KaraokeSong#to_s. Hay varias maneras de hacer esto y vamos a empezar con una mala. Vamos a copiar el mtodo to_s de Song y a aadirlo en la letra. class KaraokeSong # ... def to_s KS: #@name--#@artist (#@duration) [#@lyrics] end end song = KaraokeSong.new(My Way, Sinatra, 225, And now, the...) song.to_s KS: My Way--Sinatra (225) [And now, the...] Para mostrar correctamente el valor de la variable de instancia @lyrics, la subclase accede directamente a las variables de instancia de sus ancestros. Por qu esta es una mala manera de poner en

10

prctica to_s? La respuesta tiene que ver con el buen estilo de programacin (y algo llamado disociacin). Al hurgar dentro de la estructura interna de las clases padres y examinado explcitamente sus variables de instancia, nos estamos atando fuertemente a su implementacin. Digamos que se decidi cambiar Song para almacenar la duracin en milisegundos. Inesperadamente, KaraokeSong empieza a presentar valores ridculos. La idea de una versin karaoke My Way, que tiene una duracin de 3.750 minutos es demasiado amdrentadora para considerarla. Cmo evitar este problema haciendo que cada clase maneje sus propios detalles de implementacin? Cuando se llame a KaraokeSong#to_s, vamos a tener que llamar al mtodo to_s de sus ancestros para obtener los detalles de la cancin. A continuacin, aadirlos a la informacin de la letra y retornar el resultado. El truco aqu es la palabra clave Ruby super. Cuando se invoca sin argumentos, Ruby enva un mensaje a los padres del objeto en curso, pidiendo que se invoque un mtodo del mismo nombre que el mtodo de invocacin de super. Pasa a este mtodo los parmetros que se pasan al mtodo invocado originalmente. Ahora podemos implementar nuestra to_s nueva y mejorada. class KaraokeSong < Song # Formato por nosotros mismos como una cadena por adicin # Nuestras letras con el valor #to_s de nuestros padres. def to_s super + [#@lyrics] end end song = KaraokeSong.new(My Way, Sinatra, 225, And now, the...) song.to_s Song: My Way--Sinatra (225) [And now, the...] Ruby nos dice explcitamente que KaraokeSong es una subclase de Song, pero sin especificar una clase padre para Song misma. Si no se especifica un padre en la definicin de una clase, Ruby suministra una clase Objeto por defecto. Esto significa que todos los objetos tienen Objeto, como un ancestro y los mtodos de instancia de la clase Objeto estn disponibles para todos los objetos de Ruby. Atrs mencionamos que to_s est disponible para todos los objetos. Ahora sabemos por qu: to_s es uno de los ms de 35 mtodos de instancia en la clase Object. La lista completa se ver ms adelante. Hasta aqu hemos estado viendo las clases y sus mtodos. Ahora es el momento de pasar a los objetos, como las instancias de la clase Song.

Objetos y Atributos
Los objetos Song que hemos creado hasta ahora tienen un estado interno (como el ttulo de la cancin y el artista). Este estado es privado para los objetos --otro no objeto puede acceder a las variables de instancia de un objeto. En general, esta es bueno. Esto significa que el objeto es el nico responsable de mantener su propia consistencia. Sin embargo, un objeto que es totalmente secreto es bastante intil. Se puede crear, pero no se puede hacer nada con el. Normalmente se van a definir los mtodos que le permiten acceder y manipular el estado de un objeto, permitiendo al mundo exterior interactuar con el objeto. Estas facetas visibles desde el exterior de un objeto se denominan atributos. Para nuestros objetos Song, la primera cosa que puede necesitar, es la capacidad de encontrar el ttulo y el artista (que es lo que podemos mostrar mientras la cancin est sonando) y la duracin (por lo que se puede mostrar algn tipo de barra de progreso). Herencia y Mixins Algunos lenguajes orientados a objetos (tales como C++) admiten la herencia mltiple, donde una clase puede tener ms de un pariente inmediato, heredando la funcionalidad de cada uno. Aunque poderosa, esta tcnica puede ser peligrosa, ya que la jerarqua de herencia puede ser ambigua.

11

Otros lenguajes, como Java y C#, admiten la herencia nica. Aqu, una clase slo puede tener un pariente inmediato. Aunque ms limpia (y ms fcil de implementar), la herencia simple tambin tiene inconvenientes ya que los objetos del mundo real suelen heredar los atributos de mltiples fuentes (una pelota es a la vez una cosa que rebota y una cosa esfrica, por ejemplo). Rub ofrece un compromiso interesante y potente, que le d la sencillez de la herencia simple y la potencia de la herencia mltiple. La clase Ruby tiene solo un padre directo, as que Ruby es un lenguaje de herencia simple. Sin embargo, las clases Ruby pueden incluir la funcionalidad de cualquier nmero de mixins (un mixin es como una definicin de clase parcial). Esto proporciona la capacidad de un control como de herencia mltiple pero con ninguno de sus inconvenientes. Vamos a ver sobre los mixins ms adelante. class Song def name @name end def artist @artist end def duration @duration end end song = Song.new(Bicylops, Fleck, 260) song.artist -> Fleck song.name -> Bicylops song.duration -> 260 Aqu hemos definido tres mtodos de acceso para devolver los valores de las tres variables de instancia. El mtodo name(), por ejemplo, devuelve el valor de la variable de instancia @name. Debido a que esto es un idioma comn, Ruby proporciona un atajo conveniente: attr_reader crea estos mtodos de acceso para usted. class Song attr_reader :name, :artist, :duration end song = Song.new(Bicylops, Fleck, 260) song.artist -> Fleck song.name -> Bicylops song.duration -> 260 En este ejemplo se ha introducido algo nuevo. La construccin :artist es una expresin que devuelve un objeto Symbol correspondiente a artist. Se puede imaginar :artist en el sentido del nombre de la variable artist, y artist llano en el sentido del valor de la variable. En este ejemplo, hemos llamado a los mtodos de acceso name, artist y duration. Las variables de instancia correspondientes, @ name, @artist y @duration se crearn automticamente. Estos mtodos de acceso son idnticos a los escritos antes a mano.

Atributos de Escritura
A veces es necesario ser capaz de establecer un atributo desde fuera del objeto. Por ejemplo, supongamos que la duracin que inicialmente se asocia con una cancin es una estimacin (tal vez se obtuvo a partir de la informacin de un CD o en los datos de MP3). La primera vez que ponemos el tema, llegamos a saber el tiempo que realmente tiene y queremos guardar este nuevo valor en el objeto Song. En lenguajes como C++ y Java, se hara esto con las funciones setter (de ajuste).

12

class JavaSong { // cdigo Java private Duration _duration; public void setDuration(Duration newDuration) { _duration = newDuration; } } s = new Song(....); s.setDuration(length); En Ruby, los atributos de un objeto se pueden acceder como si se tratara de cualquier otra variable. Hemos visto esto anteriormente con frases como song.name. Por lo tanto, parece natural que se pueda asignar a estas variables cuando se quiere establecer el valor de un atributo. En Ruby esto se hace mediante la creacin de un mtodo cuyo nombre termina con un signo igual. Estos mtodos se pueden utilizar como destino de las asignaciones. class Song def duration=(new_duration) @duration = new_duration end end song = Song.new(Bicylops, Fleck, 260) song.duration -> 260 song.duration = 257 # ajuste de atributo con el valor actualizado song.duration -> 257 La asignacin song.duration = 257 invoca al mtodo duration= en el objeto song, pasandole 257 como argumento. De hecho, la definicin de un nombre de mtodo que termina en un signo igual hace que el nombre pueda aparecer en el lado izquierdo de una asignacin. Una vez ms, Ruby proporciona un acceso directo para la creacin de estos sencillos mtodos de ajuste de atributo. class Song attr_writer :duration end song = Song.new(Bicylops, Fleck, 260) song.duration = 257

Atributos virtuales
Estos mtodos de acceso a atributos no tienen que ser simples envoltorios alrededor de las variables de instancia de un objeto. Por ejemplo, es posible que desee acceder a la duracin en minutos y fracciones de un minuto, en lugar de en segundos tal como lo hemos estado haciendo. class Song def duration_in_minutes @duration/60.0 # forzar punto flotante end def duration_in_minutes=(new_duration) @duration = (new_duration*60).to_i end end song = Song.new(Bicylops, Fleck, 260) song.duration_in_minutes -> 4.33333333333333 song.duration_in_minutes = 4.2 song.duration -> 252 Aqu hemos utilizado mtodos de atributos para crear una variable de instancia virtual. Para el mundo exterior, duration_in_minutes parece ser un atributo como cualquier otro. Internamente, sin embargo, no tiene ninguna variable de instancia correspondiente.

13

Esto es ms que una curiosidad. En su libro de referencia en Construccin de Software Orientado a Objetos [Mey97], Bertrand Meyer llama a esto el Principio de Acceso Uniforme. Al ocultar la diferencia entre las variables de instancia y los valores calculados, se blinda al resto del mundo a partir de la implementacin de su clase. Usted es libre de cambiar cmo funcionaran las cosas en el futuro sin afectar a los millones de lneas de cdigo que pueda utilizar la clase. Esto es un gran acierto.

Atributos, Variables de Instancia y Mtodos


Esta descripcin de los atributos puede hacer pensar que no son nada ms que mtodos, por qu tenemos que inventar un nombre aparte para ellos? En cierto modo, eso es absolutamente correcto. Un atributo es slo un mtodo. A veces un atributo simplemente devuelve el valor de una variable de instancia. A veces un atributo devuelve el resultado de un clculo. Y a veces los cobardes mtodos con signos de igualdad al final de sus nombres se utilizan para actualizar el estado de un objeto. Entonces la pregunta es, dnde terminan los atributos y donde comienzan los mtodos regulares? Qu hace que algo sea un atributo y no slo un mtodo simple y llano? En ltima instancia, que es una de esas preguntas sin respuesta clara. Aqu hay una cuestin personal. Al disear una clase, usted decide qu estado interno tiene y tambin decide cmo ese estado va a aparecer al exterior (para los usuarios de la clase). El estado interno se lleva a cabo en las variables de instancia. El estado externo est expuesto a travs de mtodos que llamamos atributos. Y las dems accionesque su clase puede realizar son mtodos comunes y corrientes. Realmente no es una distincin sumamente importante, pero el llamar al estado externo de un objeto como sus atributos, est ayudando a dar a la gente una pista de cmo deben ver la clase que ha escrito.

Variables de Clase y Mtodos de Clase


Hasta ahora, todas las clases que hemos creado contienen variables de instancia y mtodos de instancia: las variables se asocian a una instancia particular de la clase, y los mtodos que trabajan con estas variables. A veces las clases necesitan tener sus propios estados. Aqu es donde las variables de clase tienen lugar.

Variables de clase
Una variable de clase es compartida entre todos los objetos de una clase y es accesible tambin a los mtodos de clase que vamos a describir despus. Slo existe una copia de una variable de clase particular en una clase determinada. Los nombres de variables de clase comienzan con dos arrobas, tal como @@contador . A diferencia de las variables globales y de instancia, las variables de clase debe ser inicializadas antes de ser utilizadas. A menudo, esta inicializacin es una simple asignacin en el cuerpo de la definicin de clase. Por ejemplo, si usted quiere, nuestro jukebox puede registrar cuntas veces se ha puesto cada cancin. Este contador sera probablemente una variable de instancia del objeto Song. Cuando se reproduce una cancin el valor en la instancia se incrementa. Pero dice que tambin quiere saber cuntas canciones se han jugado en total. Podemos hacer esto mediante la bsqueda de todos los objetos Song y la suma de sus contadores, o que podra correr el riesgo de excomunin de la Iglesia del Buen Diseo y hacer uso de una variable global. En su lugar, vamos a utilizar una variable de clase. class Song @@plays = 0 def initialize(name, artist, duration) @name = name @artist = artist @duration = duration @plays = 0 end def play @plays += 1 # lo mismo que @plays = @plays + 1 @@plays += 1

14

This song: #@plays plays. Total #@@plays plays. end end Para la depuracin, hemos arreglado Song#play para retornar una cadena que contiene el nmero de veces que se ha puesto esa cancin junto con el nmero total para todas las canciones. Esto se puede comprobar fcilmente. s1 = Song.new(Song1, Artist1, 234) # test songs.. s2 = Song.new(Song2, Artist2, 345) s1.play ! This song: 1 plays. Total 1 plays. s2.play ! This song: 1 plays. Total 2 plays. s1.play ! This song: 2 plays. Total 3 plays. s1.play ! This song: 3 plays. Total 4 plays. Las variables de clase son privadas de una clase y sus instancias. Si desea que sean accesibles al mundo exterior, tendr que escribir un mtodo de acceso. Este mtodo podra ser un mtodo de instancia o, lo que nos lleva a la siguiente seccin, un mtodo de clase.

Mtodos de clase
A veces una clase debe proporcionar mtodos que funcionen sin estar atados a ningn objeto en particular. Ya nos hemos encontrado con uno de esos mtodos. El mtodo new crea un objeto Song new, pero no se asocia con una cancin en particular. song = Song.new(....) Encontrar mtodos de clase dispersos a lo largo de las bibliotecas Ruby. Por ejemplo, los objetos de la clase File representan archivos abiertos en el sistema de archivos subyacente. Sin embargo, la clase File tambin ofrece varios mtodos de clase para la manipulacin de los archivos que no estn abiertos y por lo tanto no tienen un objeto File. Si se quiere eliminar un archivo, se llama al mtodo de clase File.Delete pasandole el nombre. File.delete(doomed.txt) Los mtodos de clase se distinguen de los mtodos de instancia por su propia definicin. Los mtodos de clase se definen mediante la colocacin del nombre de clase y un punto delante del nombre del mtodo. class Example def instance_method # mtodo de instancia end def Example.class_method # mtodo de clase end end El jukebox cobra por cada cancin reproducida? no, por el momento. Esto hara las canciones cortas ms rentables que las largas. Es posible que desee evitar que las canciones que se hayan puesto mucho estn disponibles en la lista de canciones. Podramos definir un mtodo de clase en SongList que compruebe si una cancin en particular ha superado el lmite. Vamos a establecer este lmite mediante una constante de clase, que es simplemente una constante (recuerda las constantes? Comienzan con una letra mayscula) que es inicializada en el cuerpo de la clase. class SongList MAX_TIME = 5*60 # 5 minutos

def SongList.is_too_long(song) return song.duration > MAX_TIME end end song1 = Song.new(Bicylops, Fleck, 260) SongList.is_too_long(song1) -> false

15

song2 = Song.new(The Calling, Santana, 468) SongList.is_too_long(song2) -> true

Singletons y Otros Constructores


A veces se desea reemplazar el modo por defecto en el que Ruby crea objetos. Como ejemplo, echemos un vistazo a nuestra mquina de discos. Como vamos a tener muchas mquinas de discos, repartidas por todo el pas, queremos hacer el mantenimiento lo ms fcil posible. Una parte que se requiere es registrar todo lo que sucede en la mquina jukebox: las canciones reproducidas, el dinero recibido, los fluidos extraos vertidos en ella y as sucesivamente. Como queremos el ancho de banda de red reservado para la msica, vamos a almacenar los archivos de registro a nivel local. Sin embargo, queremos un slo objeto de registro por jukebox, y queremos que sea un objeto compartido entre los dems objetos para que lo utilicen. Aqu entra el patrn Singleton, documentado en Design Patterns [GHJV95]. Vamos a organizar las cosas para que la nica manera de crear un registro sea por la llamada a MyLogger.create, y nos vamos a asegurar que se crea un slo objeto registro. class MyLogger private_class_method :new @@logger = nil def MyLogger.create @@logger = new unless @@logger @@logger end end Al hacer nuevo mtodo privado MyLogger, evitamos que nadie pueda crear un objeto de registro con el constructor convencional. En su lugar, proporcionamos un mtodo de clase, MyLogger.create. Este mtodo utiliza la variable de clase @@logger, para mantener una referencia a una sola instancia del registro retornando la instancia cada vez que se le llama. Podemos comprobar esto mirando a los identificadores de objeto que retorna el mtodo. MyLogger.create.id MyLogger.create.id -> -> 936550 936550

(La implemetacin de singletons que aqu presentamos no es segura para subprocesos. Si estn corriendo varios hilos, sera posible crear varios objetos del registrador. En lugar de aadir nosotros mismos la seguridad de hilo, probablemente usaramos el mixin Singleton que se suministra con Ruby y que veremos ms adelante). Definiciones de Metodo de Clase Anteriormente hablamos sobre que los mtodos de clase se definen colocando el nombre de clase y un punto delante del nombre del mtodo. En realidad esto es una simplificacin (un objetivo de trabajo siempre). De hecho, usted puede definir los mtodos de clase de varias formas. Para una comprensin objetiva de esas formas de trabajo tendr que esperar hasta ms adelante, por lo menos hasta el captulo Clases y Objetos. Por ahora, slo vamos a mostrar las expresiones que usa la gente, para el caso de que las encuentre en cdigo Ruby. Lo siguiente define los metodos de clase en la clase Demo: class Demo def Demo.meth1 # ... end

16

def self.meth2 # ... end class <<self def meth3 # ... end end end Utilizar mtodos de clase como pseudo-constructores tambin puede hacer la vida ms fcil a los usuarios de su clase. Como un ejemplo trivial, vamos a ver una clase Shape que representa un polgono regular. Instancias de Shape son creadas por el constructor dando el nmero de lados y el permetro total. class Shape def initialize(num_sides, perimeter) # ... end end Sin embargo, un par de aos ms tarde, esta clase se utiliza en una aplicacin diferente, donde los programadores la utilizan para crear formas por su nombre y especificando la longitud de un lado, no el permetro. Basta con aadir algunos mtodos de clase a Shape. class def end def end end Shape Shape.triangle(side_length) Shape.new(3, side_length*3) Shape.square(side_length) Shape.new(4, side_length*4)

Los mtodos de clase tienen muchos usos interesantes y de gran alcance, pero la explorar todos ellos hara que terminasemos nuestro jukebox muy tarde, as que vamos a seguir adelante.

Control de Acceso
En el diseo de una interfaz de clase, es importante tener en cuenta hasta qu punto el acceso a su clase se expone al mundo exterior. Permitiendo demasiado el acceso en su clase, corre el riesgo de aumentar el acoplamiento en la aplicacin. Los usuarios se vern tentados a confiar en los detalles de la implementacin de su clase, en lugar de en su interfaz lgica. La buena noticia es que la nica manera fcil de cambiar el estado de un objeto en Ruby es llamando a uno de sus mtodos. Controlar el acceso a los mtodos es controlar el acceso al objeto. Una buena regla general es no exponer los mtodos que podran dejar al objeto en un estado no vlido. Ruby le ofrece tres niveles de proteccin. Los mtodos pblicos pueden ser llamados por cualquiera --no se aplica control de acceso. Los mtodos son pblicos por defecto (excepto initialize, que es privado). Los mtodos protegidos slo pueden ser invocado por los objetos de la clase que define y sus subclases. El acceso se mantiene dentro de la familia. Los mtodos privados no pueden ser llamados con un receptor explcito --el explcito es siempre uno mismo. Esto significa que a los mtodos privados slo se les puede llamar en el contexto del objeto actual, no se puede invocar mtodos privados de otro objeto. La diferencia entre protegido y privado es bastante sutil y es diferente en Ruby que en la mayora de los lenguajes OO comunes. Si un mtodo est protegido, puede ser llamado por cualquier instancia de

17

la clase que define o sus subclases. Si un mtodo es privado, slo puede ser llamado dentro del contexto de la llamada al objeto --nunca es posible tener acceso a mtodos privados de otro objeto directamente, incluso si el objeto es de la misma clase que el llamador. Ruby se diferencia de otros lenguajes OO en otra cosa importante. El control de acceso se determina de forma dinmica, cuando el programa se ejecuta, no estticamente. Usted recibir una violacin de acceso slo cuando el cdigo intenta ejecutar un mtodo restringido.

Especificacin de Control de Acceso


Se puede especificar los niveles de acceso a los mtodos dentro de las definiciones de clase o mdulo utilizando una o ms de las tres funciones public, protected y private. Puede utilizar las funciones de dos maneras diferentes. Si se utilizan sin argumentos, las tres funciones establecen el control de acceso por defecto de los mtodos definidos posteriormente. Esta es, probablemente el comportamiento familiar si usted es un programador de C++ o Java, donde se utilizan palabras clave como public para lograr el mismo efecto. class MyClass def method1 #... end protected def method2 #... end private def method3 #... end public def method4 #... end end # por defecto public # mtodos posteriores sern protected # ser protected # mtodos posteriores sern private # ser private # mtodos posteriores sern public # y esto ser public

Como alternativa, puede establecer niveles de acceso a los mtodos llamados mediante su inclusin como argumentos a las funciones de control de acceso. class MyClass def method1 end # ... etc public protected private end

:method1, :method4 :method2 :method3

Vamos a ver algn ejemplo. Tal vez estemos haciendo un sistema de contabilidad en el que cada dbito tiene el correspondiente crdito. Cmo queremos asegurarnos de que nadie pueda romper esta regla, vamos a hacer privados los mtodos que hacen los dbitos y los crditos y vamos a definir nuestra interfaz externa en trminos de transacciones. class Accounts def initialize(checking, savings) @checking = checking @savings = savings end private def debit(account, amount)

18

account.balance -= amount end def credit(account, amount) account.balance += amount end public #... def transfer_to_savings(amount) debit(@checking, amount) credit(@savings, amount) end #... end El acceso protegido se utiliza cuando los objetos necesitan acceder al estado interno de otros objetos de la misma clase. Por ejemplo, puede querer permitir a los objetos individuales Account comparar los saldos, pero puede que desee ocultar los saldos al resto del mundo (tal vez porque los presentemos de una forma diferente). class Account attr_reader :balance # acceso al mtodo balance protected :balance # y que sea protegido def greater_balance_than(other) return @balance > other.balance end end Debido a que el atributo balance est protegido, slo est disponible dentro de los objetos Account.

Variables
Ahora que nos hemos tomado la molestia de crear todos estos objetos, vamos a asegurarnos de que no se pierdan. Las variables se utilizan para realizar un seguimiento de los objetos; cada variable tiene una referencia a un objeto. Vamos a confirmar esto con algo de cdigo.

person = Tim person.id -> 936870 person.class -> String person -> Tim En la primera lnea, Ruby crea un nuevo objeto String con el valor Tim. Una referencia a este objeto se coloca en la variable local person. Una revisin rpida muestra que la variable ha seguido de hecho la personalidad de una cadena con un identificador de objeto, una clase y un valor. Por lo tanto, es una variable de un objeto? En Ruby, la respuesta es no. Una variable es simplemente una referencia a un objeto. Los objetos flotan en un gran estanque en algn lugar (el montn, la mayora de las veces) y son sealados por variables. Vamos a hacer el ejemplo un poco ms complicado.

person1 = Tim person2 = person1 person1[0] = J person1 person2 -> -> Jim Jim

19

Qu ha pasado aqu? Hemos cambiado el primer carcter de person1, pero ambos, person1 y person2 han cambiado de Tim a Jim. Todo se reduce al hecho de que las variables contienen referencias a objetos, no los objetos mismos. La asignacin de person1 a persona2 no crea nuevos objetos, sino que es simplemente una copia de la referencia al objeto person1 en person2, por lo que tanto person1 como person2 se refieren al mismo objeto. Esto se muestra en la Figura 2:

La asignacin de alias a objetos, hace posible mltiples variables que hacen referencia al mismo objeto. Pero, puede causar problemas en el cdigo? Puede, pero no tan a menudo como se podra pensar (los objetos en Java, por ejemplo, funcionan exactamente de la misma manera). En el ejemplo de la Figura 2, se puede evitar el aliasing mediante el mtodo dup a String, que crea un nuevo objeto String con idntico contenido. person1 = Tim person2 = person1.dup person1[0] = J person1 -> Jim person2 -> Tim Tambin puede impedir que alguien cambie un objeto en particular por medio de freezing (congelacin --se hablar ms sobre los objetos freezing ms tarde). Al intentar modificar un objeto congelado Ruby provocar una excepcin TypeError. person1 = Tim person2 = person1 person1.freeze person2[0] = J produce: prog.rb:4:in `[]=: cant modify frozen string (TypeError) from prog.rb:4 Con esto concluye esta mirada a las clases y objetos en Ruby. Este material es importante; todo lo que se manipula en Ruby es un objeto. Y una de las cosas ms comunes que hacemos con los objetos es crear colecciones de ellos. Pero ese es el tema de nuestro prximo captulo.

# evitar modificaciones en el objeto

20

Contenedores, Bloques e Iteradores


Un jukebox con una cancin es poco probable que sea popular (excepto quizs en algun bar ttrico), por lo que muy pronto vamos a tener que empezar a pensar en la produccin de un catlogo de canciones disponibles y una lista de canciones a la espera de ser reproducidas. Ambos son contenedores: objetos que contienen referencias a uno o ms objetos. Tanto el catlogo como la lista necesitan un conjunto similar de mtodos: agregar una cancin, eliminar una cancin, ver una lista de canciones y as sucesivamente. La lista de reproduccin puede realizar tareas adicionales, como la insercin de publicidad de vez en cuando o hacer el seguimiento del tiempo de reproduccin acumulado, pero vamos a preocuparnos por estas cuestiones ms tarde. Mientras tanto, parece una buena idea esarrollar algn tipo de clase genrica SongList, que pueda especializarse en los catlogos y listas de reproduccin.

Contenedores
Antes de empezar la implementacin, tendremos que encontrar la manera de almacenar la lista de canciones dentro de un objeto SongList. Tenemos tres opciones obvias. Podemos usar el tipo Array de Ruby, utilizar el tipo hash Ruby, o crear nuestra propia estructura lista. Perezosamente, por ahora vamos a ver los arrays y hashes y elegir uno de estos para nuestra clase.

Arrays
La clase Array contiene una coleccin de referencias a objetos. Cada referencia a un objeto ocupa una posicin en la matriz, identificada por un ndice de enteros no negativos. Usted puede crear matrices mediante el uso de literales o explcitamente por la creacin de un objeto Array. Una matriz literal es simplemente una lista de objetos entre corchetes. a = [ 3.14159, pie, 99 ] a.class -> Array a.length -> 3 a[0] -> 3.14159 a[1] -> pie a[2] -> 99 a[3] -> nil b = Array.new b.class -> Array b.length -> 0 b[0] = second b[1] = array b -> [second, array] Las matrices son indexados usando el operador [ ]. Como con la mayora de los operadores de Ruby, esto es en realidad un mtodo (un mtodo de instancia de la clase Array) y por lo tanto puede ser anulado en las subclases. Como muestra el ejemplo, los ndices de los arrays comienzan con cero. El ndice de una matriz con un nmero entero no negativo, devuelve el objeto en esa posicin o devuelve nil si no hay nada. El ndice de una matriz con un entero negativo cuenta desde el final. a = [ 1, 3, 5, 7, 9 ] a[-1] -> 9 a[-2] -> 7 a[-99] -> nil Este esquema de indexacin se ilustra con ms detalle en la Figura 3 en la pgina siguiente.

21

Tambin puede indexar arrays con un par de nmeros, [ inicio, cuenta ]. Esto devuelve una nueva matriz que consta de referencias para contar objetos a partir de la posicin inicio. a = [ 1, 3, 5, 7, 9 ] a[1, 3] -> [3, 5, 7] a[3, 1] -> [7] a[-3, 2] -> [5, 7] Por ltimo puede indexar matrices usando rangos, en los que las posiciones de inicio y final estn separados por dos o tres puntos. El formato de dos puntos incluye la posicin final mientras que en el de tres puntos no est incluido el lmite final. a = [ 1, 3, 5, 7, a[1..3] -> [3, a[1...3] -> [3, a[3..3] -> [7] a[-3..-1] -> [5, 9 ] 5, 7] 5] 7, 9]

El operador [ ] tiene su correspondiente operador [ ]= operador, que permite establecer elementos de la matriz. Si se utiliza con un ndice de tipo entero, el elemento en esa posicin se sustituye por lo que est en el lado derecho de la asignacin. Las lagunas que dan lugar se llena de nada. Cualquier vaco que resulte se rellena con nil. a = [ 1, 3, 5, 7, 9 ] a[1] = bat a[3] = cat a[3] = [ 9, 8 ] a[6] = 99 -> -> -> -> -> [1, [1, [1, [1, [1, 3, 5, 7, 9] bat, 5, 7, 9] bat, cat, 7, 9] bat, cat, [9, 8], 9] bat, cat, [9, 8], 9, nil, 99]

Si el ndice en [ ] = es de dos nmeros (un comienzo y una longitud) o un rango, entonces los elementos de la matriz original se sustituyen por lo que est en el lado derecho de la asignacin. Si la longitud es cero, el lado derecho de la asignacin se inserta en la matriz antes de la posicin, no se eliminan los elementos. Si el lado derecho es en si una matriz, sus elementos se utilizan en la sustitucin. El tamao de la matriz se ajusta automticamente si el ndice selecciona un nmero diferente de elementos que se encuentran en el lado derecho de la asignacin. a = [ 1, 3, 5, 7, 9 ] a[2, 2] = cat a[2, 0] = dog a[1, 1] = [ 9, 8, 7 ] a[0..3] = [] a[5..6] = 99, 98 -> -> -> -> -> -> [1, 3, 5, 7, 9] [1, 3, cat, 9] [1, 3, dog, cat, 9] [1, 9, 8, 7, dog, cat, 9] [dog, cat, 9] [dog, cat, 9, nil, nil, 99, 98]

Las matrices tienen un gran nmero de mtodos tiles. Usndolas, usted puede tratar las matrices

22

como pilas, conjuntos, colas, o FiFOs. Una lista completa de los mtodos de array se ver ms adelante en su correspondiente documentacin.

Hashes
Los hashes (conocidos a veces como arrays asociativos, mapas o diccionarios) son similares a las matrices en que tambin se indexan las colecciones de referencias de objetos. Sin embargo, mientras que las matrices se indexan con nmeros enteros, puede indexar un hash con objetos de cualquier tipo: cadenas, expresiones regulares, etc. Cuando se almacena un valor en un hash, en realidad se suministran dos objetos, el ndice, normalmente se llamado key, y el valor. Posteriormente se puede recuperar el valor de la indexacin de los hash con la clave misma. Los valores de un hash pueden ser objetos de cualquier tipo. El ejemplo que sigue utiliza literales hash: una lista de pares clave => valor entre llaves.

h = { dog => canine, cat => feline, donkey => asinine } h.length -> 3 h[dog] -> canine h[cow] = bovine h[12] = dodecine h[cat] = 99 h -> {cow=>bovine, cat=>99, 12=>dodecine, donkey=>asinine, dog=>canine} En comparacin con los arrays, los hashes tienen una ventaja significativa: pueden usar cualquier objecto como un ndice. Sin embargo, tambin tienen una importante desventaja: sus elementos no estn ordenados, por lo que no puede utilizar un hash como una pila o una cola. Encontrar que los hashes son una de las estructuras ms comunes de datos en Ruby. Una lista completa de los mtodos implementados por la clase Hash, ms adelante en su correspondiente seccin de clase.

Implementacin del contenedor SongList


Despus de una pequea desviacin en los arrays y hashes, ahora estamos listos para implementar la lista de canciones del jukebox. Vamos a inventar una lista de mtodos que necesitaremos en nuestra SongList. Empezaremos con lo bsico y a medida que avancemos iremos aadiendo ms. append(song) -> list Aadir una cancin dada a la lista. delete_first() -> song Quitar la primera cancin de la lista, retornando esa cancin. delete_last() -> song Quitar la ltima cancin de la lista, devolviendo esa cancin. [index] -> song Traer la cancin del entero index. with_title(title) -> song Traer la cancin con el ttulo dado. Esta lista nos da una pista de la implementacin. La capacidad de aadir canciones al final, y eliminarlas de la parte delantera y final, sugiere un dequeue, una cola de dos extremos, que sabemos que podemos poner en prctica usando un Array. Del mismo modo, la capacidad de devolver una cancin de una posicin de un entero de la lista soportado por las matrices. Sin embargo, tambin hay que ser capaz de recuperar canciones por ttulo, lo que puede sugerir el uso

23

de un hash, con el ttulo como clave y la cancin como valor. Podramos utilizar un hash? Bueno, tal vez, pero esto causara problemas. En primer lugar, un hash no est ordenado, por lo que probablemente tendramos que usar una matriz auxiliar para realizar un seguimiento de la lista. Un segundo problema ms grande, es que el hash no es compatible con mltiples claves con el mismo valor. Esto sera un problema para nuestra lista de reproduccin, donde puede estar la misma cancin en cola para reproducir varias veces. As que por ahora, nos quedamos con una matriz de canciones, buscando por ttulos cuando sea necesario. Si esto se convierte en un cuello de botella para el rendimiento, siempre podemos aadir ms tarde algn tipo de hash basado en bsqueda. Vamos a empezar la clase con un mtodo initialize bsico, que va a crear la matriz que se va a utilizar para contener las canciones y almacena una referencia a ella en la variable de instancia @songs. class SongList def initialize @songs = Array.new end end El mtodo SongList#append aade la cancin en cuestin al final de la matriz @Songs. Tambin retorna self, una referencia al objeto en curso SongList. Esta es una convencin til, ya que nos permite encadenar varias llamadas a anexar. Vamos a ver un ejemplo de esto ms adelante. class SongList def append(song) @songs.push(song) self end end Ahora vamos a aadir los mtodos delete_first y delete_last, implementados trivialmente utilizando Array#shift y Array#pop respectivamente. class SongList def delete_first @songs.shift end def delete_last @songs.pop end end Hasta ahora, todo bien. Nuestro mtodo siguiente es [ ], que accede a los elementos por su ndice. Este tipo de mtodos simples por delegacin se producen con frecuencia en el cdigo Ruby. class SongList def [](index) @songs[index] end end Ahora tenemos que aadir la funcionalidad que nos facilite buscar una cancin por el ttulo. Esto va a implicar la exploracin de las canciones de la lista, comprobando el ttulo de cada una. Para ello, primero tenemos que pasar por un par de pginas que nos muestre una de las mejores caractersticas de Ruby: los iteradores.

Bloques e iteradores
Nuestro siguiente problema con SongList es la aplicacin del mtodo with_title que toma una cadena y busca una cancin con ese ttulo. Esto parece sencillo: tenemos una lista de canciones, as que vamos a ir de elemento en elemento hasta encontrar el que queremos.

24

class SongList def with_title(title) for i in 0...@songs.length return @songs[i] if title == @songs[i].name end return nil end end Esto funciona y se ve cmodamente familiar: un bucle for itera sobre una matriz. Qu podra ser ms natural? Resulta que hay algo ms natural. En cierto modo, nuestro bucle for es un tanto demasiado ntimo con la matriz; pregunta por una longitud y a continuacin recupera los valores sucesivamente, hasta que encuentra una coincidencia. Por qu no pedir simplemente a la matriz aplicar un test a cada uno de sus elementos? Esto es justamente lo que el mtodo find hace en la matriz. class SongList def with_title(title) @songs.find {|song| title == song.name } end end El mtodo find es un iterador : un mtodo que invoca a un bloque de cdigo repetidamente. Los iteradores y los bloques de cdigo son algunas de las caractersticas ms interesantes de Ruby, as que vamos a pasar un buen rato vindolos (y en el proceso, vamos a saber exactamente lo que esa lnea de cdigo de nuestro mtodo with_title, hace en realidad).

Implementar Iteradores
Un iterador Ruby es simplemente un mtodo que puede invocar a un bloque de cdigo. A primera vista, un bloque en Ruby se ve como un bloque en C, Java, C# o Perl. Pero en este caso, las apariencias engaan. Un bloque en Ruby es una forma de agrupacin de declaraciones o sentencias, pero no en la forma convencional. En primer lugar, un bloque slo puede aparecer junto a una llamada a un mtodo. El bloque se escribe a partir de la misma lnea que el ltimo parmetro de la llamada a mtodo (o parntesis de cierre de la lista de parmetros). En segundo lugar, el cdigo en el bloque no se ejecuta en el momento en que se encuentra. En su lugar, Ruby recuerda el contexto en el que aparece el bloque (las variables locales, el objeto actual, etc) y luego entra en el mtodo. Aqu es donde comienza la magia. Dentro del mtodo, un bloque puede ser invocado casi como si se tratara de un mtodo en s mismo, con la sentencia yield. Cada vez que se ejecuta yield, se invoca al cdigo en el bloque. Cuando el bloque termina, se recoge una copia de seguridad inmediatamente despus de yield. Vamos a empezar con un ejemplo trivial. def three_times yield yield yield end three_times { puts Hola } produce: Hola Hola Hola

25

El bloque (el cdigo entre las llaves) se asocia con la llamada al mtodo three_times. Dentro de este mtodo, se llama tres veces a yield. Cada vez, se invoca el cdigo en el bloque y un saludo alegre se imprime. Lo que hace a estos bloques interesante, sin embargo, es que se les puede pasar parmetros y recibir valores de ellos. Por ejemplo, podramos escribir una simple funcin que devuelva los miembros de la serie de Fibonacci hasta un cierto valor (La serie de Fibonacci es una secuencia de nmeros enteros, empezando con dos 1, en la que cada trmino siguiente es la suma de los dos anteriores. La serie se utiliza a veces en algoritmos de ordenacin y en anlisis de fenmenos naturales). def fib_up_to(max) i1, i2 = 1, 1 # asignacin paralela (i1 = 1 y i2 = 1) while i1 <= max yield i1 i1, i2 = i2, i1+i2 end end fib_up_to(1000) {|f| print f, } produce: 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 En este ejemplo, la sentencia yield tiene un parmetro. Este valor se pasa al bloque asociado. En la definicin del bloque, la lista de argumentos aparece entre barras verticales. En este caso, la variable f recibe el valor pasado a yield, por lo que el bloque imprime los sucesivos miembros de la serie (este ejemplo muestra tambin la asignacin paralela que veremos ms adelante). Aunque es comn pasarle un slo valor al bloque, esto no es un requisito porque un bloque puede tener cualquier nmero de argumentos. Si los parmetros de un bloque son las variables locales existentes, estas variables se utilizan como los parmetros del bloque y sus valores pueden ser modificados por la ejecucin del bloque. Lo mismo se aplica a las variables dentro del bloque: si aparecen por primera vez en el bloque, son locales para el bloque. Si en cambio, aparecieron por primera vez fuera del bloque, las variables sern compartidas entre el bloque y el entorno (Aunque en ocasiones es extremadamente til, esta caracterstica puede dar lugar a un comportamiento inesperado y es objeto de acalorados debates en la comunidad Ruby. Es posible que Ruby 2.0 cambie la forma en que los bloques heredan las variables locales). En este ejemplo (inventado), vemos que el bloque hereda las variables a y b del mbito circundante, pero c es local para el bloque (el mtodo defined? retorna nil si su argumento no est definido). a = [1, 2] b = cat a.each {|b| c = b * a[1] } a -> [1, 2] b -> 2 defined?(c) -> nil Un bloque tambin puede retornar un valor al mtodo. El valor de la ltima expresin evaluada en el bloque se pasa al mtodo como el valor de yield. As es como trabaja el mtodo find utilizado por la clase Array (el mtodo find se define en realidad en el mdulo Enumerable, que se mezcla con la clase Array). Su implementacin sera algo as como lo siguiente. class Array def find for i in 0...size valor = self[i] return valor if yield(valor) end return nil end end

26

[1, 3, 5, 7, 9].find {|v| v*v > 30 }

->

Esto pasa sucesivos elementos de la matriz al bloque asociado. Si el bloque devuelve true, el mtodo devuelve el elemento correspondiente. Si no coincide con ningn elemento, el mtodo devuelve nil. El ejemplo muestra el beneficio de este enfoque por iteradores. La clase Array es lo que mejor hace, accede a elementos de una matriz, dejando al cdigo de la aplicacin concentrarse en sus requerimientos particulares (en este caso, encontrar una entrada que cumpla con algunos criterios matemticos). Algunos iteradores son comunes a muchos tipos de colecciones en Ruby. Ya hemos visto find. Otros dos son each y collect. El iterador ms simple es probablemente each que lo nco que hace es dar elementos sucesivos de su coleccin. [ 1, 3, 5, 7, 9 ].each {|i| puts i } produce: 1 3 5 7 9 El iterador each tiene un lugar especial en Ruby. Ms adelante se describe como se usa como la base del lenguaje para los bucles, y an ms adelante veremos cmo definir un mtodo each para agregar mucha ms funcionalidad a una clase. Otro iterador comn es collect, que toma cada elemento de una coleccin y se lo pasa al bloque. Los resultados devueltos por el bloque se utilizan para construir una nueva matriz. Por ejemplo: [H, A, L].collect {|x| x.succ } -> [I, B, M]

Los iteradores no se limitan a acceder a los datos existentes en los arrays y hashes. Como vimos en el ejemplo de Fibonacci, un iterador puede devolver valores derivados. Esta capacidad, Ruby la utiliza en sus clases de entrada/salida que implementan un interfaz iterador que devuelve sucesivas lneas (o bytes) en un flujo de E/S (el siguiente ejemplo utiliza do..end para definir un bloque. La diferencia entre esta notacin y el uso de llaves para definir bloques, es la prioridad: do..end tiene menos prioridad que {...}). f = File.open(testfile) f.each do |line| puts line end f.close produce: This is line one This is line two This is line three etc ... Echemos un vistazo a otro iterador ms til. El (algo oscuramente nombrado) mtodo inject (que se define en el mdulo Enumerable) le permite acumular un valor entre los elementos de una coleccin. Por ejemplo, puede sumar todos los elementos de una matriz, y encontrar su producto, utilizando cdigo como: [1,3,5,7].inject(0) {|sum, element| sum+element} [1,3,5,7].inject(1) {|product, element| product*element} -> -> 16 105

27

inject funciona as: la primera vez que se llama al bloque asociado, la suma se configura para inyectar el parmetro y el elemento se establece en el primer elemento de la coleccin. La segunda y posteriores veces que se llama al bloque, la suma se establece en el valor devuelto por el bloque de la llamada anterior. El valor final a inyectar es el valor devuelto por el bloque en la ltima vez que fue llamado. Hay una cosa final: si se llama a inject sin ningn parmetro, se utiliza el primer elemento de la coleccin como el valor inicial y comienza la iteracin con el segundo valor. Esto significa que podra haber escrito los ejemplos anteriores como: [1,3,5,7].inject {|sum, element| sum+element} [1,3,5,7].inject {|product, element| product*element} -> -> 16 105

Iteradores Internos y externos


Vale la pena gastar un prrafo comparando el enfoque de Ruby con los iteradores al de otros lenguajes como C++ y Java. En Ruby, el iterador es interno a la coleccin --es simplemente un mtodo, idntico a cualquier otro, que pasa a llamar a yield cada vez que genera un nuevo valor. Lo que usa el iterador es slo un bloque de cdigo asociado a este mtodo. En otros lenguajes, las colecciones no contienen sus propios iteradores. En su lugar, generan objetos externos de ayuda (por ejemplo, los basados en la interfaz Iterator de Java) que llevan el estado iterador. En esto, como en muchos otros aspectos, Ruby es un lenguaje transparente. Cuando escribes un programa en Ruby, te concentras en hacer el trabajo, no en la construccin de andamios para apoyar al mismo lenguaje. Tambin vale la pena gastar un prrafo para ver por qu los iteradores internos de Ruby no son siempre la mejor solucin. Un rea donde caen mal es cuando se necesita tratar el iterador como un objeto en s mismo (por ejemplo, pasar el iterador en un mtodo que lo necesita para acceder a cada uno de los valores devueltos por ese mismo iterador). Tambin es dificil iterar sobre dos colecciones en paralelo con el esquema de iterador interno de Ruby. Afortunadamente, Ruby 1.8 viene con la biblioteca Generator (que se describe ms adelante), que implementa iteradores externos para tales ocasiones.

Bloques para Transacciones


Aunque a menudo los bloques son objeto de un iterador, tambin tienen otros usos. Echmosles un vistazo. Puede utilizar los bloques para definir un trozo de cdigo que debe ejecutarse en algn tipo de control de transacciones. Por ejemplo, muchas veces se abre un archivo, se hace algo con su contenido, y se quiere asegurar de que el archivo se cierre cuando se haya terminado. Aunque usted puede hacer esto utilizando cdigo convencional, hay razones para hacer al archivo responsable de cerrarse. Podemos hacer esto con bloques. Una implementacin ingenua (ignorando el manejo de errores) podra ser algo como lo siguiente. class File def File.open_and_process(*args) f = File.open(*args) yield f f.close() end end File.open_and_process(testfile, r) do |file| while line = file.gets puts line end end produce: This is line one

28

This is line two This is line three etc ... open_and_process es un mtodo de clase, se le puede llamar independientemente de cualquier objeto archivo en particular. Queremos que tome los mismos argumentos que el mtodo File.open convencional, pero en realidad no importa lo que son esos argumentos. Para ello, se especifican los argumentos como *args, que significa recoger los parmetros actuales pasados al mtodo en una matriz llamada args. A continuacin, llamamos a File.open, pasndole *args como un parmetro. Esto expande la matriz de nuevo en los parmetros individuales. El resultado neto es que open_and_process pasa de forma trasparente todos los parmetros que recibi a File.open. Una vez que se ha abierto el archivo, open_and_process hace llamadas a yield pasandole el objeto archivo abierto al bloque. Cuando retorna el bloque, se cierra el archivo. De esta manera, la responsabilidad de cerrar el archivo abierto ha pasado desde el usuario del objeto archivo a los propios archivos. La tcnica de hacer que los archivos gestionen su propio ciclo de vida es tan til que la clase File suministrada con Ruby lo soporta directamente. Si File.open tiene un bloque asociado, cuando se invoque al bloque con un objeto archivo, ste se cerrar cuando el bloque termine. Esto es interesante, ya que significa que File.open tiene dos diferentes comportamientos: cuando se invoca con un bloque, ste se ejecuta y se cierra el archivo. Cuando se invoca sin bloque, devuelve el objeto archivo. Esto se hace posible gracias al mtodo Kernel.block_given?, que devuelve true si el bloque est asociado con el mtodo en curso. Usando este mtodo, se puede implementar algo similar al File.open estndar (de nuevo, haciendo caso omiso de la gestin de errores) como lo siguiente: class File def File.my_open(*args) result = file = File.new(*args) # Si hay un bloque, pasar el archivo y cerrar el archivo #cuando retorne if block_given? result = yield file file.close end return result end end Esto tiene un ltimo giro: en los ejemplos anteriores del uso de bloques para el control de los recursos, no hemos abordado el tratamiento de errores. Si quisieramos poner en prctica correctamente estos mtodos, necesitaramos asegurarnos de que cerramos los archivos, incluso si el cdigo de procesamiento del archivo a abortado de alguna manera. Esto lo hacemos con el manejo de excepciones, de lo que hablaremos ms adelante.

Los Bloques pueden ser Cierres


Volvamos a nuestro jukebox. En algn momento vamos a trabajar en el cdigo que se encarga de la interfaz de usuario, con los botones que la gente presiona para seleccionar las canciones y controlar el jukebox. Vamos a tener que asociar acciones con estos botones: pulsar START y comienza la msica. Resulta que los bloques de Ruby ofrecen una conveniente manera para hacer esto. Vamos a asumir que los que hicieron el hardware implementaron una extensin de Ruby que nos d una clase botn bsica. start_button = Button.new(Start) pause_button = Button.new(Pause) # ... Qu sucede cuando el usuario pulsa uno de los botones? En la clase Button, la gente de hardware ha manipulado las cosas para que se invoque un mtodo de devolucin de llamada, button_pressed. La manera obvia de aadir la funcionalidad de estos botones es la creacin de subclases de Button y hacer que cada subclase implemente su propio mtodo button_pressed.

29

class StartButton < Button def initialize super(Start) # invocar initialize Button end def button_pressed # se inician acciones... end end start_button = StartButton.new Esto tiene dos problemas. En primer lugar, esto dar lugar a un gran nmero de subclases. Si la interfaz Button cambia, podra envolvernos en una gran cantidad de mantenimiento. En segundo lugar, las acciones realizadas cuando se pulsa un botn se expresan en el nivel equivocado, no son una caracterstica del botn, sino una caracterstica de la mquina de discos que utiliza los botones. Podemos arreglar estos dos problemas con los bloques. songlist = SongList.new class JukeboxButton < Button def initialize(label, &action) super(label) @action = action end def button_pressed @action.call(self) end end start_button = JukeboxButton.new(Start) { songlist.start } pause_button = JukeboxButton.new(Pause) { songlist.pause } La clave de todo esto es el segundo parmetro a JukeboxButton#initialize. Si el ltimo parmetro en una definicin de mtodo se precede con un signo & (por ejemplo, &action), Ruby busca un bloque de cdigo cada vez que se llama al mtodo. El bloque de cdigo que se convierte en un objeto de la clase Proc y es asignado al parmetro. A continuacin, puede tratar el parmetro como cualquier otra variable. En nuestro ejemplo, se asigna a la variable de instancia @action. Cuando se invoca al mtodo de retorno button_pressed, se utiliza el mtodo Proc#call en ese objeto para invocar el bloque. Entonces, qu es exactamente lo que tenemos cuando se crea un objeto Proc? Lo interesante es que es algo ms que un trozo de cdigo. Asociado a un bloque (y por lo tanto al objeto Proc) es todo el contexto en el que se ha definido al bloque: el valor mismo y los mtodos, las variables y constantes a su alcance. Parte de la magia de Ruby es que el bloque an puede utilizar toda esta informacin del alcance original, incluso si el entorno en el que se define de otra manera ha desaparecido. En otros lenguajes, esta caracterstica se denomina cierre. Veamos un ejemplo inventado en el que se utiliza el mtodo lambda, que convierte un bloque a un objeto Proc. def n_times(thing) return lambda {|n| thing * n } end p1 = n_times(23) p1.call(3) -> 69 p1.call(4) -> 92 p2 = n_times(Hola ) p2.call(3) -> Hola Hola Hola El mtodo n_times devuelve un objeto Proc que hace referencia al parmetro thing. A pesar de que el parmetro est fuera de alcance de momento se llama al bloque, para que el parmetro sea accesible para el bloque.

30

Contenedores por todas partes


Contenedores, bloques e iteradores son conceptos fundamentales en Ruby. Cuanto ms se escribe en Ruby, ms se ve alejado de las construcciones convencionales de bucles. En su lugar, vamos a escribir clases que admiten la iteracin sobre su contenido. Usted encontrar que este cdigo es compacto y fcil de leer y mantener.

Tipos Estndar
Hasta ahora nos hemos divertido con la plicacin de piezas de cdigo a nuestro jukebox. Pero hemos sido negligentes. Hemos visto arrays, hashes y procs, pero no hemos cubierto los otros tipos bsicos de Ruby: nmeros, cadenas, rangos y expresiones regulares. Ahora vamos a pasar unas cuantas pginas con estos bloques de construccin bsicos.

Nmeros
Ruby soporta nmeros enteros y de punto flotante. Los enteros pueden ser de cualquier longitud (hasta un mximo determinado por la cantidad de memoria disponible en su sistema). Enteros en un rango determinado (normalmente -230 a 230-1 o -262 a 262-1) se llevan a cabo internamente de forma binaria y son objetos de la clase Fixnum. Enteros fuera de este rango se almacenan en objetos de la clase Bignum (actualmente implementados como un conjunto de longitud variable de enteros cortos). Este proceso es transparente y Ruby gestiona automticamente la conversin de ida y vuelta. num = 81 6.times do puts #{num.class}: #{num} num *= num end produce: Fixnum: Fixnum: Fixnum: Bignum: Bignum: Bignum: 81 6561 43046721 1853020188851841 3433683820292512484657849089281 11790184577738583171520872861412518665678211592275841109096961

Se pueden escribir nmeros enteros con signo opcional, un indicador de base opcional (0 para octal, 0d para decimal (por defecto), 0x para hexadecimal y 0b para binario) seguido de una cadena de digitos en la base apropiada. Los guiones bajos son ignorados (algunas personas los utilizan en lugar de comas para nmeros grandes). 123456 => 123456 # Fixnum 0d123456 => 123456 # Fixnum 123_456 => 123456 # Fixnum -543 => -543 # Fixnum 0xaabb => 43707 # Fixnum 0377 => 255 # Fixnum -0b10_1010 => -42 # Fixnum 123_456_789_123_456_789 => 123456789123456789 #

guin bajo ignorado nmero negativo hexadecimal octal binario (negativo) Bignum

Los caracteres de control se pueden generar utilizando ?\Cx y ?\cx (el controlde versin de x es x & 0x9f). Metacaracteres (x | 0x80) se puede generar utilizando ?\Mx. La combinacin de meta y control se genera con ?\M\C-x. Se puede obtener el valor entero del caracter barra invertida utilizando la secuencia ?\\.

31

?a => ?\n => ?\C-a => ?\M-a => ?\M-\C-a => ?\C-? =>

97 10 1 225 129 127

# # # # # #

caracter ASCII codigo para nueva lnea (0x0a) control a = ?A & 0x9f = 0x01 meta bit 7 meta y control a delete character

Un literal numrico con un punto decimal y/o un exponente se convierte en un objeto Float, que corresponde al tipo de dato double en la arquitectura nativa. El punto decimal tiene que ir precedido y seguido por un dgito (si se escribe 1.0e3 como 1.e3, Ruby trata de invocar al mtodo e3 en la clase Fixnum). Todos los nmeros son objetos y responden a una variedad de mensajes (una lista completa se ver ms adelante). As, a diferencia (por ejemplo) de C++, el valor absoluto de un nmero se encuentra escribiendo num.abs, no abs(num). Los enteros tambin soportan varios iteradores tiles. Hemos visto ya uno: 6.times en el cdigo de ejemplo anterior. Otros incluidos son upto y downto, para iterar hacia arriba y hacia abajo entre dos enteros. La clase Numeric tambin proporciona el mtodo ms general step, que es ms como un bucle for tradicional. 3.times 1.upto(5) 99.downto(95) 50.step(80, 5) produce: X X X 1 2 3 4 5 99 98 97 96 95 50 55 60 65 70 75 80 Por ltimo, vamos a ofrecer un toque de atencin a los usuarios de Perl. Las cadenas que contienen slo dgitos no se convierten automticamente a nmeros cuando se utilizan en las expresiones. Esto tiende a engaar con ms frecuencia al leer nmeros de un archivo. Por ejemplo, podemos querer hallar la suma de dos nmeros de cada lnea de un archivo, como: 3 4 5 6 7 8 El siguiente cdigo no funciona: # divisin de lnea en espacios { print X {|i| print {|i| print {|i| print } i, } i, } i, }

some_file.each do |line| v1, v2 = line.split print v1 + v2, end produce: 34 56 78

El problema es que la entrada no se lee como nmeros, sino como cadenas. El operador + concatena cadenas, asi que es eso lo que vemos en la salida. Para solucionar esto, utilice el mtodo Integer para convertir la cadena a un entero. some_file.each do |line| v1, v2 = line.split print Integer(v1) + Integer(v2), end produce: 7 11 15

32

Cadenas
Las cadenas en Ruby son simplemente secuencias de bytes de 8 bits. Normalmente tienen caracteres imprimibles, pero no es un requisito, una cadena tambin puede contener datos binarios. Las cadenas son objetos de la clase String. Las cadenas se crean utilizando una literales de cadena --secuencias de caracteres entre delimitadores. Como los datos binarios son difciles de representar de otra forma en el cdigo fuente del programa, puede colocar varias secuencias de escape en un literal de cadena. Cada una se sustituye por el valor binario correspondiente cuando se compila el programa. El tipo de delimitador de cadena determina el grado de sustitucin realizado. Dentro de comillas simples, dos barras invertidas consecutivas se sustituyen por una sola, y una barra invertida seguida de una comilla simple se convierte en una comilla simple. escape using \\ That\s right -> -> escape using \ Thats right

Las cadenas entre comillas dobles soportan un cargamento de secuencias de escape. El ms comn es probablemente, \n, el carcter de nueva lnea. Ms adelante se muestra una tabla con la lista completa. Adems, puede sustituir el valor de cualquier cdigo Ruby en una cadena mediante la secuencia #{ expr } . Si el cdigo es una variable global, una variable de clase, o una variable de instancia, puede omitir las llaves. Seconds/day: #{24*60*60} #{Ho! *3}Merry Christmas! This is line #$. -> -> -> Seconds/day: 86400 Ho! Ho! Ho! Merry Christmas! This is line 3

El cdigo de interpolacin puede ser una o ms declaraciones, no slo una expresin:

puts now is #{ def the(a) the + a end the(time) } for all good coders... produce: now is the time for all good coders... Hay tres formas de construir cadenas literales: %q, %Q, y here documents.

%q y %Q al inicio delimita cadenas entre comillas simples y dobles (se puede imaginar %q como comilla fina y %Q como comilla gruesa ). %q/general singlequoted string/ %Q!general doublequoted string! %Q{Seconds/day: #{24*60*60}} -> -> -> general singlequoted string general doublequoted string Seconds/day: 86400

El carcter que sigue a q a Q es el delimitador. Si se trata de un corchete de apertura [, llave { parntesis ( o signo menor que <, la cadena se lee hasta que se encuentra el smbolo correspondiente de cierre. De lo contrario la cadena se lee hasta la prxima ocurrencia del mismo delimitador. El delimitador puede ser cualquier carcter no alfanumrico o no multibyte. Por ltimo, puede crear una cadena con here document (un documento aqu).

string = <<END_OF_STRING El cuerpo de la cadena son las lneas de entrada hasta que una de ellas termina con el mismo texto que sigui a << END_OF_STRING

33

Un here document se compone de lneas hasta, pero no incluyendo, la cadena de terminacin que se especifica despus de los caracteres <<. Normalmente, este terminador debe comenzar en la primera columna. Sin embargo, si usted pone un signo menos despus de <<, puede sangrar el terminador. print <<-STRING1, <<-STRING2 Concat STRING1 enate STRING2 produce: Concat enate Tenga en cuenta que Ruby no quita espacios iniciales de los contenidos de las cadenas en estos casos.

Trabajar con Cadenas


String es probablemente la clase ms grande construida en Ruby, con ms de 75 metodos estndar. No vamos a tratar todos ellos, la referencia de la biblioteca tiene una lista completa. En su lugar, vamos a ver algunas expresiones comunes de cadenas --las que ms suelen aparecer durante el da a da de la programacin. Volvamos a nuestra mquina de discos. A pesar de que est diseada para conectarse a Internet, tambin tiene copias de algunas canciones populares en un disco duro local. De esta manera, si una ardilla muerde nuestra conexin a la red, todava ser capaz de entretener a los clientes. Por razones histricas (hay alguna otra?), la lista de canciones se almacena como filas en un archivo plano. Cada fila contiene el nombre del archivo que contiene la cancin, la duracin de la cancin, el artista y el ttulo, con barras verticales para separar los campos. Un archivo tpico puede comenzar /jazz/j00132.mp3 | 3:45 | Fats Waller | Aint Misbehavin /jazz/j00319.mp3 | 2:58 | Louis Armstrong | Wonderful World /bgrass/bg0732.mp3| 4:09 | Strength in Numbers | Texas Red : : : : En cuanto a los datos, est claro que vamos a utilizar algunos de los muchos mtodos de la clase String, para extraer y limpiar los campos antes de crear objetos Song basados en ellos. Como mnimo, tendremos que dividir cada lnea en campos, convertir los tiempos de funcionamiento desde mm:ss a segundos, y eliminar los espacios en blanco de los nombres de los artistas.

Nuestra primera tarea es dividir cada lnea en campos, y String#split va hacer el trabajo adecuadamente. En este caso, vamos a pasar a split una expresin regular, /\s*\|\s*/, que divide la lnea en fichas donde split encuentra una barra vertical, opcionalmente rodeada de espacios. Y, debido a que la lnea a leer del archivo tiene un carcter de nueva lnea al final, vamos a utilizar String#chomp para quitarlo justo antes de aplicar la separacin. File.open(songdata) do |song_file| songs = SongList.new song_file.each do |line| file, length, name, title = line.chomp.split(/\s*\|\s*/) songs.append(Song.new(title, name, length)) end puts songs[1] end

34

produce: Song: Wonderful World--Louis Armstrong (2:58)

Desafortunadamente, el que cre el archivo original introdujo los nombres de los artistas en columnas, por lo que algunos contienen espacios adicionales. Estos se ven feos en nuestra alta tecnologa, supertwist, pantalla plana, Day-Glo display. As que mejor quitar estos espacios extra antes de ir ms all. Tenemos muchas maneras de hacer esto, pero probablemente la ms simple sea String#squeeze, que recorta tiradas de caracteres repetidos. Vamos a utilizar la forma squeeze! del mtodo, que altera la cadena. File.open(songdata) do |song_file| songs = SongList.new song_file.each do |line| file, length, name, title = line.chomp.split(/\s*\|\s*/) name.squeeze!( ) songs.append(Song.new(title, name, length)) end puts songs[1] end produce: Song: Wonderful WorldLouis Armstrong (2:58) Por ltimo, tenemos el asunto de menor importancia del formato del tiempo: el archivo dice 2:58 y queremos el nmero en segundos, 178. Se podra utilizar split de nuevo, que dividira el campo del tiempo en torno al carcter de dos puntos. mins, secs = length.split(/:/) En su lugar, vamos a utilizar un mtodo relacionado. String#scan es similar a split en que rompe una cadena en trozos sobre la base de un patrn. Sin embargo, a diferencia de split, con scan se especifica el patrn que se desea que coincida con los trozos. En este caso, queremos que coincida con uno o ms dgitos para ambos componentes, los minutos y los segundos. El patrn para uno o ms dgitos es /\d+/. File.open(songdata) do |song_file| songs = SongList.new song_file.each do |line| file, length, name, title = line.chomp.split(/\s*\|\s*/) name.squeeze!( ) mins, secs = length.scan(/\d+/) songs.append(Song.new(title, name, mins.to_i*60+secs.to_i)) end puts songs[1] end produce: Song: Wonderful WorldLouis Armstrong (178) Nuestro jukebox debe tener capacidad de bsqueda por palabra clave. Dada una palabra de un ttulo de una cancin o el nombre de un artista, aparecer una lista de todas las pistas que coinciden. Escriba fats, y mostrar canciones de Fats Domino, Fats Navarro, y Fats Waller, por ejemplo. Lo vamos a poner en prctica mediante la creacin de una clase de indexacin. Esto ilustra un poco ms los muchos mtodos de la clase String.

35

class WordIndex def initialize @index = {} end def add_to_index(obj, *phrases) phrases.each do |phrase| phrase.scan(/\w[\w]+/) do |word| # extraer cada palabra word.downcase! @index[word] = [] if @index[word].nil? @index[word].push(obj) end end end def lookup(word) @index[word.downcase] end end El mtodo String#scan extrae los elementos de una cadena que coincida con una expresin regular. En este caso, el patrn \w[-\w]+ coincide con cualquier carcter que pueda aparecer en una palabra, seguido de uno o ms de lo que se especifica entre corchetes (un guin, otro caracter de la palabra, o una comilla simple ). Ms adelante hablaremos ms acerca de las expresiones regulares. Para hacer nuestras bsquedas case insensitive, podemos asignar tanto las palabras que se extraen como las palabras usadas como claves en la bsqueda de minsculas. Tenga en cuenta el signo de exclamacin al final del primer nombre del mtodo downcase!. Al igual que con el mtodo squeeze! utilizado anteriormente, esto es una indicacin de que el mtodo va a modificar al receptor en su lugar, en este caso la conversin de la cadena a minsculas (Este ejemplo de cdigo contiene un error menor: la cancin Gone, Gone, Gone tendra la indexacin en tres ocasiones. Se podra arreglar?). Vamos a ampliar nuestra clase SongList indexndole canciones a medida que se agregan y aadirle un mtodo para buscar una cancin dndole una palabra. class def end def end def end end SongList initialize @songs = Array.new @index = WordIndex.new append(song) @songs.push(song) @index.add_to_index(song, song.name, song.artist) self lookup(word) @index.lookup(word)

Por ltimo, vamos a probar todo.

songs = SongList.new song_file.each do |line| file, length, name, title = line.chomp.split(/\s*\|\s*/) name.squeeze!( ) mins, secs = length.scan(/\d+/) songs.append(Song.new(title, name, mins.to_i*60+secs.to_i)) end puts songs.lookup(Fats) puts songs.lookup(aint) puts songs.lookup(RED) puts songs.lookup(WoRlD)

36

produce: Song: Song: Song: Song: Aint Misbehavin--Fats Waller (225) Aint Misbehavin--Fats Waller (225) Texas Red--Strength in Numbers (249) Wonderful World--Louis Armstrong (178)

En el cdigo anterior, el mtodo de bsqueda (lookup) devuelve una matriz de coincidencias. Cuando se pasa un array puts, simplemente escribe cada elemento, a su vez, separado por un salto de lnea. Podramos pasar las prximas 50 pginas viendo todos los mtodos de la clase String. Sin embargo, vamos a seguir adelante para ver un tipo de datos ms simple: el rango.

Rangos
Los rangos se dan en todas partes: de enero a diciembre, 0-9, mal bien hecho, lneas 50 a 67, etc. Si Ruby nos ayuda a modelar la realidad, parece lgico el apoyo de estos rangos. De hecho, Ruby hace algo mejor: realmente utiliza los rangos para la implementacin de tres distintas caractersticas: las secuencias, las condiciones y los intervalos.

Rangos como Secuencias


El primer uso y quizs el ms natural de los rangos es el de expresar una secuencia. Las secuencias tienen un punto de inicio, un punto final, y una manera de producir valores sucesivos de la secuencia. En Ruby, estas secuencias se crean utilizando los operadores de rango .. y .... La forma de dos puntos crea un rango inclusivo, y la forma de tres puntos crea un rango que excluye el valor ms alto especificado. 1..10 a..z my_array = [ 1, 2, 3 ] 0...my_array.length En Ruby, a diferencia de algunas versiones anteriores de Perl, los rangos no se representan internamente como listas: la secuencia de 1..100000 se mantiene como un objeto Range que contiene referencias a dos objetos Fixnum. Si se necesita, se puede convertir un rango a una lista mediante el mtodo to_a. (1..10).to_a (bar..bat).to_a -> -> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] [bar, bas, bat]

Los rangos implementan mtodos que nos permiten iterar sobre ellos y probar su contenido de varias maneras. digits = 0..9 digits.include?(5) digits.min digits.max digits.reject {|i| i < 5 } digits.each {|digit| dial(digit) } -> -> -> -> -> true 0 9 [5, 6, 7, 8, 9] 0..9

Hasta ahora hemos mostrado rangos de nmeros y cadenas. Sin embargo, como era de esperar de un lenguaje orientado a objetos, Ruby puede crear rangos basados en objetos que se pueden definir. La nica restriccin es que los objetos deben responder a succ devolviendo el siguiente objeto en la secuencia, y que los objetos deben ser comparables usando <=>. A veces llamando al operador spaceship, <=> compara dos valores, devolviendo -1, 0 +1 dependiendo de si el primero es menor, igual o mayor que el segundo. A continuacin tenemos una clase simple que representa las filas para los signos #. Es posible que la desee utilizar como una versin basada en texto del control de volumen del jukebox.

37

class VU include Comparable attr :volume def initialize(volume) # 0..9 @volume = volume end def inspect # * @volume end # soporte para rangos def <=>(other) self.volume <=> other.volume end def succ raise(IndexError, Volume too big) if @volume >= 9 VU.new(@volume.succ) end end Como nuestra clase VU implementa succ y <=>, se puede participar en los rangos. [####, #####, ######, #######] false

medium_volume = VU.new(4)..VU.new(7) medium_volume.to_a -> medium_volume.include?(VU.new(3)) ->

Rangos como Condiciones


As como la representacin de secuencias, los rangos tambin se pueden utilizar como expresiones condicionales. En este caso, actan como una especie de conmutador de palanca --que enciende cuando la condicin en la primera parte del rango se cumple, y se apaga cuando la condicin en la segunda parte se cumple. Por ejemplo, el siguiente fragmento de cdigo imprime conjuntos de lneas en la entrada estndar, donde la primera lnea de cada conjunto contiene la palabra start y la ltima lnea contiene la palabra end. while line = gets puts line if line =~ /start/ .. line =~ /end/ end Entre bambalinas, el rango realiza un seguimiento del estado de cada una de las pruebas. Vamos a mostrar algunos ejemplos de esto en la descripcin de los bucles ms adelante En versiones antiguas de Ruby, rangos desnudos se pueden utilizar como condiciones en if, while y sentencias similares. Se hubiera podido, por ejemplo, haber escrito el fragmento de cdigo anterior como while gets print if /start/../end/ end Esto ya no es compatible. Desafortunadamente, no revela ningn error, el test slo tendr xito en cada ocasin.

Rangos como Intevalos


Un uso final de los verstiles rangos es un test de intervalo: ver si algn valor se encuentra dentro del intervalo representado por el rango. Esto se puede hacer utilizando el operador de igualdad de caso ===. (1..10) === 5 (1..10) === 15 (1..10) === 3.14159 -> -> -> true false true

38

(a..j) === c (a..j) === z

-> ->

true false

Ms adelante veremos un ejemplo de una expresin de caso que muestra esta prueba en accin: dado un ao, la determinacin de un estilo de jazz.

Expresiones Regulares
De vuelta a lo visto anteriormente, cuando se crea una lista de canciones a partir de un archivo, se utiliza una expresin regular para que coincida con el delimitador de campo en el archivo de entrada. Hemos afirmado que la expresin line.split (/\s*\|\s*/) coincide con una barra vertical rodeada de espacio en blanco opcional. Vamos a explorar las expresiones regulares con ms detalle para ver por qu esta afirmacin es verdadera. Las expresiones regulares se utilizan para comparar patrones con cadenas. Ruby proporciona soporte interno que hace la comparacin y sustitucin de patrones conveniente y concisa. En esta seccin vamos a trabajar con las principales caractersticas de las expresiones regulares. No se van a cubrir algunos detalles aqu, ms adelante veremos ms. Las expresiones regulares son objetos de tipo Regexp. Pueden ser creados por una llamada al constructor explicitamente o mediante el uso de las formas literales /patrn/ y %r{patron}. a = Regexp.new(^\s*[az]) b = /^\s*[az]/ c = %r{^\s*[az]} -> -> -> /^\s*[az]/ /^\s*[az]/ /^\s*[az]/

Una vez que tiene un objeto de expresin regular, puede compararlo con una cadena con Regexp# match(cadena) o con los operadores de comparacin =~ (comparacin positiva) y !~ (comparacin negativa). Los operadores de comparacin se definen para ambos objetos, Strings y Regexp. Al menos un operando del operador de comparacin debe ser una expresin regular. (En versiones anteriores de Ruby, ambos operandos pueden ser cadenas, en cuyo caso el segundo operando se convierte en una expresin regular entre bambalinas). name = Fats name =~ /a/ name =~ /z/ /a/ =~ name Waller -> 1 -> nil -> 1

Los operadores de comparacin retornan la posicin de carcter en la que la comparacin se produjo. Tambin tienen el efecto secundario de configurar un montn de variables Ruby. $& recibe la parte de la cadena que fue coincidente con el patrn, $` recibe la parte de la cadena que precedi la comparacin, y $ recibe la cadena despus de la comparacin. Se puede usar esto para escribir un mtodo, show_regexp, que muestra donde se coincide con un patrn particular. def show_regexp(a, re) if a =~ re #{$`}<<#{$&}>>#{$} else no match end end show_regexp(very show_regexp(Fats show_regexp(Fats show_regexp(Fats interesting, /t/) Waller, /a/) Waller, /ll/) Waller, /z/) -> -> -> -> very in<<t>>eresting F<<a>>ts Waller Fats Wa<<ll>>er no match

La comparacin tambin configura el hilo de variables globales $~ y $1 a $9. La variable $~ es un objeto MatchData (descrito ms adelante) que contiene todo lo que usted quiera saber sobre la comparacin. $1 y siguientes, mantiene los valores de las partes de la comparacin. Hablaremos de esto ms tarde. Y

39

para las personas que se encogen cuando ven estos nombres de variables como las de Perl, estad atentos. Hay buenas noticias al final del captulo.

Patrones
Cada expresin regular contiene un patrn, que se utiliza para comparar la expresin regular con una cadena. Dentro de un patrn, todos los caracteres excepto ., |, (, ), [, ], {, }, +, \, ^, $, * y ?, coinciden con ellos mismos. show_regexp(kangaroo, /angar/) show_regexp(!@%&_=+, /%&/) -> -> k<<angar>>oo !@<<%&>>_=+

Si se quiere comparar con uno de estos caracteres especiales, literalmente, se preceden con una barra invertida. Esto explica en parte el modelo que utilizamos para dividir la lnea de song, /\s*\|\s*/. El \| significa compara una barra vertical. Sin la barra invertida, el | habra significado alternancia (que describiremos ms adelante). show_regexp(yes | no, /\|/) show_regexp(yes (no), /\(no\)/) show_regexp(are you sure?, /e\?/) -> -> -> yes <<|>> no yes <<(no)>> are you sur<<e?>>

La barra invertida seguida por un carcter alfanumrico se utiliza para introducir una construccin de comparacin especial, que vamos a cubrir ms tarde. Adems, una expresin regular puede contener sustituciones #{...}.

Anclas
Por defecto, una expresin regular va a tratar de encontrar la primera coincidencia del patrn en una cadena. La comparacin de /iss/ con la cadena Mississippi, que encuentra la subcadena iss comenzando en la posicin uno. Pero, cmo forzar que un patrn se comapre slo con el principio o el final de una cadena? Los patrones ^ y $ coinciden con el comienzo y el final de una lnea respectivamente. A menudo, son usados para anclar una coincidencia de patrn: por ejemplo, /^option/ coincide con la palabra option slo si aparece en el comienzo de una lnea. La secuencia \A coincide con el comienzo de una cadena, y \z y \Z coinciden con el final de una cadena. (En realidad, \Z coincide con el final de una cadena a menos que la cadena termine con un \n, que este caso coincide justo antes del n\). show_regexp(this show_regexp(this show_regexp(this show_regexp(this is\nthe is\nthe is\nthe is\nthe time, time, time, time, /^the/) /is$/) /\Athis/) /\Athe/) -> -> -> -> this is\n<<the>> time this <<is>>\nthe time <<this>> is\nthe time no match

Del mismo modo, los patrones \b y \B coinciden con el lmite de palabra (si no aparece dentro de una especificacin de rango) y con el no lmite de palabra, respectivamente. Caracteres de palabra son letras, nmeros y guiones bajos. show_regexp(this is\nthe time, /\bis/) show_regexp(this is\nthe time, /\Bis/) -> -> this <<is>>\nthe time th<<is>> is\nthe time

Clases Carcter
Una clase carcter es un conjunto de caracteres entre corchetes: [caracteres] coincide con cualquier carcter individual entre los corchetes. [aeiou] coincidr con una vocal, [,.:;!?] coincidir con un signo de puntuacin, etc. La importancia de los caracteres especiales de expresiones regulares -- .|() [{+^$*? -- se anula dentro de los corchetes. Sin embargo, la sustitucin normal de cadenas se sigue produciendo, por lo que (por ejemplo) \b representa un carcter de retroceso y \n una nueva lnea. Adems,

40

puede utilizar las abreviaturas mostradas en la Tabla 2 en la siguiente pgina, para ver que (por ejemplo) \s coincide con cualquier carcter de espacio, no slo un espacio literal. Las clases de caracteres POSIX en la segunda mitad de la tabla corresponden a los macros ctype(3) con el mismo nombre.
Tabla 2. Abreviaturas de la clase carcter.

show_regexp(Price show_regexp(Price show_regexp(Price show_regexp(Price show_regexp(Price

$12., $12., $12., $12., $12.,

/[aeiou]/) /[\s]/) /[[:digit:]]/) /[[:space:]]/) /[[:punct:]aeiou]/)

-> -> -> -> ->

Pr<<i>>ce $12. Price<< >>$12. Price $<<1>>2. Price<< >>$12. Pr<<i>>ce $12.

Dentro de los corchetes, la secuencia c1-c2 representa todos los caracteres entre c1 y c2 ambos inclusive. a = see [Design Patternspage 123] show_regexp(a, /[AF]/) -> see [<<D>>esign Patternspage 123] show_regexp(a, /[AFaf]/) -> s<<e>>e [Design Patternspage 123] show_regexp(a, /[09]/) -> see [Design Patternspage <<1>>23] show_regexp(a, /[09][09]/) -> see [Design Patternspage <<12>>3] Si desea incluir los caracteres literales [ y - dentro de una clase carcter, deben aparecer en el inicio. Ponga un ^ inmediatamente despus de un corchete abierto para negar una clase carcter: [^a-z] coincide con cualquier carcter alfabtico que no sea en minsculas. a = see [Design Patternspage 123] show_regexp(a, /[]]/) -> see [Design Patternspage 123<<]>> show_regexp(a, /[]/) -> see [Design Patterns<<>> page 123] show_regexp(a, /[^az]/) -> see<< >>[Design Patternspage 123] show_regexp(a, /[^az\s]/) -> see <<[>>Design Patternspage 123] Algunas clases carcter se utilizan con tanta frecuencia que Ruby proporciona abreviaturas para ellas. Estas abreviaturas se listan en la Tabla 2 en la pgina siguiente --y pueden ser utilizadas tanto entre

41

corchetescomo en el cuerpo de un patrn. show_regexp(It costs $12., /\s/) show_regexp(It costs $12., /\d/) -> -> It<< >>costs $12. It costs $<<1>>2.

Finalmente, un punto (.) que aparezca fuera de los corchetes representa cualquier carcter excepto un salto de lnea (aunque en modo multilnea tambin coincidira con una nueva lnea). a = It costs $12. show_regexp(a, /c.s/) show_regexp(a, /./) show_regexp(a, /\./) -> -> -> It <<cos>>ts $12. <<I>>t costs $12. It costs $12<<.>>

Repeticin
Cuando especificamos el patrn que divide la lnea de lista de canciones, /\s*\|\s*/, dijimos que queramos que coincidiera con una barra vertical rodeado por una cantidad arbitraria de espacios en blanco. Ahora sabemos que las secuencias con la \s coincide con un nico espacio en blanco, por lo que parece probable que los asteriscos de alguna manera signifiquen una cantidad arbitraria. De hecho, el asterisco es uno de una serie de modificadores que permiten la coincidencia con mltiples ocurrencias de un patrn. Si r se encuentra precediendo inmediatamente a la expresin regular dentro de un patrn, entonces r* r+ r? r{m,n} r{m,} r{m} coincide coincide coincide coincide coincide coincide con cero o ms apariciones de r. con uno o ms apariciones de r. con cero o una ocurrencia de r. al menos con m y como mximo n ocurrencias de r. al menos con m apariciones de r. exactamente con m apariciones de r.

Estas construcciones de repeticin tienen alta prioridad, --se ligan slo a la expresin regular inmediatamente anterior en el patrn. /ab+/ coincide con una a seguida de una o ms b, y no una secuencia de ab. Hay que tener cuidado con las construcciones con * --el patrn /a*/ coincide con cualquier cadena, cada cadena tiene cero o ms aes. A estos patrones se les llama codiciosos, ya que por defecto van a coincidir con gran parte de la cadena. Se puede modificar este comportamiento y hacer que coincida con el mnimo, mediante la adicin de un sufijo de signo de interrogacin. a = The moon is made of cheese show_regexp(a, /\w+/) show_regexp(a, /\s.*\s/) show_regexp(a, /\s.*?\s/) show_regexp(a, /[aeiou]{2,99}/) show_regexp(a, /mo?o/) -> -> -> -> -> <<The>> moon is made of cheese The<< moon is made of >>cheese The<< moon >>is made of cheese The m<<oo>>n is made of cheese The <<moo>>n is made of cheese

Alternancia
Sabemos que la barra vertical es especial, porque nuestro patrn de divisin de lnea tuvo que escapar de ella con una barra invertida. Esto se debe a que un barra vertical sin escape (|) coincide con cualquiera de las dos, la expresin regular que lo precede o la expresin regular que se sigue. a = red ball blue sky show_regexp(a, /d|e/) show_regexp(a, /al|lu/) show_regexp(a, /red ball|angry sky/) -> -> -> r<<e>>d ball blue sky red b<<al>>l blue sky <<red ball>> blue sky

Hay una trampa para los incautos aqu y es que | tiene muy baja prioridad. En el ejemplo anterior coincide con red ball o con angry sky, no con red ball sky o red angry sky. Para que coincida con red ball sky 42

o con red angry sky, hay que anular la precedencia predeterminada, usando agrupamiento.

Agrupamiento
Se pueden utilizar parntesis para agrupar trminos en una expresin regular. Todo lo de dentro del grupo es tratado como una nica expresin regular. show_regexp(banana, /an*/) show_regexp(banana, /(an)*/) show_regexp(banana, /(an)+/) a = red ball blue sky show_regexp(a, /blue|red/) show_regexp(a, /(blue|red) \w+/) show_regexp(a, /(red|blue) \w+/) show_regexp(a, /red|blue \w+/) -> -> -> -> -> -> -> b<<an>>ana <<>>banana b<<anan>>a <<red>> ball <<red ball>> <<red ball>> <<red>> ball -> -> blue blue blue blue sky sky sky sky

show_regexp(a, /red (ball|angry) sky/) a = the red angry sky show_regexp(a, /red (ball|angry) sky/)

no match the <<red angry sky>>

Los parntesis tambin recogen los resultados de la coincidencia de patrones. Ruby cuenta los parntesis de apertura, y para cada uno almacena el resultado de la coincidencia parcial entre ste y el parntesis de cierre correspondiente. Se puede utilizar esta coincidencia parcial tanto en el resto del patrn como en el programa de Ruby. Dentro de este esquema, la secuencia \1 se refiere a la coincidencia del primer grupo, \2 a la del segundo grupo, y as sucesivamente. Fuera del patrn, las variables especiales $1, $2 y sucesivas tienen el mismo propsito. 12:50am =~ /(\d\d):(\d\d)(..)/ Hour is #$1, minute #$2 12:50am =~ /((\d\d):(\d\d))(..)/ Time is #$1 Hour is #$2, minute #$3 AM/PM is #$4 -> -> -> -> -> -> 0 Hour is 12, minute 50 0 Time is 12:50 Hour is 12, minute 50 AM/PM is am

La posibilidad de utilizar parte de la actual coincidencia ms tarde, le permite buscar diversas formas de repeticin. # match duplicated letter show_regexp(He said Hello, /(\w)\1/) # match duplicated substrings show_regexp(Mississippi, /(\w+)\1/) -> -> He said He<<ll>>o M<<ississ>>ippi

Tambin se pueden utilizar de nuevo las referencias para que coincidan con delimitadores. He said <<Hello>> He said <<Hello>>

show_regexp(He said Hello, /([]).*?\1/) -> show_regexp(He said Hello, /([]).*?\1/) ->

Patrones de sustitucin

A veces encontrar un patrn en una cadena es la pera. Si un amigo te reta a encontrar una palabra que contiene las letras a, b, c, d, y e en orden, se podra buscar una lista de palabras con el patrn /a.*b*c.*d.*e/ y encontrar abjectedness, absconded, ambuscade y carbacidometer, entre otras. Esto tiene que ser algo que valga la pena. Sin embargo, a veces es necesario cambiar las cosas sobre la base de una coincidencia de patrn. Volvamos a nuestra fichero de lista de canciones. Quin lo cre introdujo todos los nombres de los artistas en minsculas. Al mostrarlos en la pantalla de nuestra mquina de discos, se veran mejor en maysculas y minsculas. Cmo podemos cambiar el primer carcter de cada palabra a maysculas? 43

Los mtodos String#sub y String#gsub buscan la parte de una cadena que coincide con su primer argumento y la reemplazan por su segundo argumento. String#sub realiza un reemplazo, y String#gsub reemplaza todas las ocurrencias de la comparacin. Ambas rutinas devuelven una nueva copia de la cadena con las sustituciones. Las versiones mutadoras String#sub! y String#gsub! modifican la cadena original. a = the quick brown fox a.sub(/[aeiou]/, *) -> a.gsub(/[aeiou]/, *) -> a.sub(/\s\S+/, ) -> a.gsub(/\s\S+/, ) -> th* quick brown fox th* q**ck br*wn f*x the brown fox the

El segundo argumento de las dos funciones puede ser una cadena o un bloque. Si se utiliza un bloque, se pasa la subcadena coincidente y el valor del bloque se sustituye en la cadena original. a = the quick brown foxa.sub(/^./) {|match| match.upcase } -> The quick brown fox a.gsub(/[aeiou]/) {|vowel| vowel.upcase } -> thE qUIck brOwn fOx Por lo tanto, esto parece una respuesta a la conversin de los nombres de nuestros artistas. El patrn que coincide con el primer carcter de una palabra es \b\w --busca un lmite de palabra seguido de un carcter de palabra. Combine esto con gsub y ya puede manipular los nombres de los artistas. def mixed_case(name) name.gsub(/\b\w/) {|first| first.upcase } end mixed_case(fats waller) mixed_case(louis armstrong) mixed_case(strength in numbers) -> -> -> Fats Waller Louis Armstrong Strength In Numbers

Secuencias de Barra Invertida en la Sustitucin


Anteriormente hemos sealado que las secuencias \1, \2, etc, estn disponibles para los patrones, posicionando hasta el ensimo grupo encontrado. Las mismas secuencias se encuentran disponibles en el segundo argumento de sub y gsub. fred:smith.sub(/(\w+):(\w+)/, \2, \1) nercpyitno.gsub(/(.)(.)/, \2\1) -> -> smith, fred encryption

Las secuencias con barra invertida adicional funcionan en las sustitucin de cadenas as: \& (ltima coincidencia), \+ (ltimo grupo coincidente), \` (cadena anterior a la coincidencia), \ (cadena siguiente a la coincidencia) y \\ (barra invertida literal). Se vuelve confuso si se desea incluir una barra invertida en una sustitucin. Lo obvio es escribir:

str.gsub(/\\/, \\\\) Claramente, este cdigo est tratando de reemplazar cada barra invertida en la cadena con dos. El programador duplic las barras invertidas en la sustitucin de texto, a sabiendas de que seran convertidas a \\ en el anlisis de la sintaxis. Sin embargo, cuando se produce la sustitucin, el motor de expresiones regulares realiza otro paso en la cadena, convirtiendo \\ a \, por lo que el efecto neto consiste en reemplazar cada barra individual con otra barra invertida. Se tiene que escribir gsub(/\\/, \\\\\\\\)! str = a\b\c str.gsub(/\\/, \\\\\\\\) -> -> a\b\c a\\b\\c

Sin embargo, con el hecho de que \& sustituye por la cadena coincidente, tambin se puede escribir:

44

str = a\b\c -> a\b\c str.gsub(/\\/, \&\&) -> a\\b\\c Si se utiliza la forma de bloque de gsub, la cadena de sustitucin se analiza slo una vez (durante la fase de sintaxis) y el resultado da lo que pretenda. str = a\b\c str.gsub(/\\/) { \\\\ } -> -> a\b\c a\\b\\c

Finalmente, como ejemplo de la asombrosa expresividad de combinar expresiones regulares con bloques de cdigo, considere el siguiente fragmento de cdigo del mdulo de la biblioteca CGI, escrito por wakou Aoyama. El cdigo tiene una cadena que contiene secuencias de escape HTML y lo convierte en ASCII normal. Este cdigo fue escrito para un pblico japons, donde se utiliza el modificador n en las expresiones regulares que anula el procesamiento de caarcter ancho. def unescapeHTML(string) str = string.dup str.gsub!(/&(.*?);/n) { match = $1.dup case match when /\Aamp\z/ni then & when /\Aquot\z/ni then when /\Agt\z/ni then > when /\Alt\z/ni then < when /\A#(\d+)\z/n then Integer($1).chr when /\A#x([09af]+)\z/ni then $1.hex.chr end } str end puts unescapeHTML(1&lt;2 &amp;&amp; 4&gt;3) puts unescapeHTML(&quot;A&quot; = &#65; = &#x41;) produce: 1<2 && 4>3 A = A = A

Expresiones Regulares Orientadas a Objetos


Tenemos que admitir que, si bien todas estas variables extraas son muy fciles de usar, no son muy orientadas a objetos y son ciertamente crpticas. Y, no decamos que todo en Ruby es un objeto? Qu ha fallado aqu? Nada, en realidad. Slo que cuando Matz dise Ruby, produjo un sistema de manejo de expresiones regulares totalmente orientado a objetos y a continuacin, envolvi todo para que resultara familiar a los programadores de Perl. Los objetos y las clases siguen ah, debajo de la superficie. As que vamos a pasar un rato en la excavacin. Ya hemos encontrado una clase: los literales de expresiones regulares crean instancias de la clase RegExp (documentado ms adelante). re = /cat/ re.class -> Regexp

El mtodo Regexp#match hace comparar una expresin regular con una cadena. En caso de xito, devuelve una instancia de la clase MatchData, (documentado ms adelante). Y ese objeto MatchData le da acceso a toda la informacin disponible sobre la comparacin. Todas esas cosas interesantes que se puede obtener a partir de las variables $ se incluyen en un objeto pequeo y prctico. re = /(\d+):(\d+)/ # match a time hh:mm

45

md = re.match(Time: 12:34am) md.class -> MatchData md[0] # == $& -> 12:34 md[1] # == $1 -> 12 md[2] # == $2 -> 34 md.pre_match # == $` -> Time: md.post_match # == $ -> am Dado que los datos coincidentes se almacenan en su propio objeto, se pueden mantener los resultados de dos o ms coincidencias de patrones disponibles al mismo tiempo, algo que no se puede hacer con las variables $. En el siguiente ejemplo, comparamos el mismo objeto RegExp con dos cadenas. Cada coincidencia devuelve un objeto MatchData nico, que se verifica mediante el examen de los dos campos de sub-patrn. re = /(\d+):(\d+)/ # coincide con una hora hh:mm md1 = re.match(Time: 12:34am) md2 = re.match(Time: 10:30pm) md1[1, 2] -> [12, 34] md2[1, 2] -> [10, 30] Entonces, cmo se ajustan las variables $? Bueno, despus de cada comparacin, Ruby almacena una referencia al resultado (cero o un objeto MatchData) en una variable local de subprocesos (accesible a travs de $~). Todas las dems variables de expresiones regulares se derivan entonces de este objeto. Aunque en realidad no podemos pensar en un uso para el siguiente cdigo, este demuestra que todas las dems variables $ relacionadas con MatchData son de hecho esclavas del valor en $~. re = /(\d+):(\d+)/ md1 = re.match(Time: 12:34am) md2 = re.match(Time: 10:30pm) [ $1, $2 ] # ltima coincidencia con xito $~ = md1 [ $1, $2 ] # anterior coincidencia con xito

-> ->

[10, 30] [12, 34]

Habiendo dicho todo esto, tenemos que hacer una confesin. Utilizamos normalmente las variables $ en lugar de preocuparnos por los objetos MatchData. Para el uso diario acaba siendo ms conveniente. A veces, simplemente no se puede dejar de ser pragmtico.

Ms sobre los Mtodos


Hasta ahora en este libro, hemos ido definiendo y utilizando mtodos sin pensarlo mucho, pero ha llegado el momento de entrar en los detalles.

La Definicin de un Mtodo
Como hemos visto, un mtodo se define con la palabra clave def.Method debe comenzar con una letra minscula (Usted no recibir un error inmediato si utiliza una letra mayscula, pero cuando Ruby vea la llamada al mtodo, primero supondr que es una constante y no una invocacin al mtodo, y como resultado puede analizar la llamada incorrectamente). Los mtodos que actan como consultas se nombran a menudo con un ? final, Como instance_of?. Los mtodos que son peligrosos, o modifican al receptor, son nombrados con un ! final. Por ejemplo, String proporciona chop y chop!. El primero devuelve una cadena modificada y el segundo modifica al receptor en su lugar. Finalmente, los mtodos que se pueden asignar, (una caracterstica que hemos visto anteriormente) termina con un signo igual (=). ?, !, = son los nicos caracteres raros permitidos como sufijos en el nombre del mtodo. Ahora que hemos especificado un nombre para nuestro nuevo mtodo, es posible que tengamos que declarar algunos parmetros. Estos son simplemente una lista de nombres de variables locales entre parntesis. (Los parntesis son opcionales en torno a los argumentos de un mtodo, la convencin es usarlos cuando un mtodo tiene argumentos y se omiten cuando no los tienen). def my_new_method(arg1, arg2, arg3) # 3 argumentos 46

# Aqu, el cdigo del mtodo end def my_other_new_method # Sin argumentos # Aqu, el cdigo del mtodo end Ruby le permite especificar los valores por defecto para los argumentos de un mtodo --los valores que se utilizarn si el invocador no los pasa de forma explcita. Para ello, se utiliza el operador de asignacin. def cool_dude(arg1=Miles, arg2=Coltrane, arg3=Roach) #{arg1}, #{arg2}, #{arg3}. end cool_dude cool_dude(Bart) cool_dude(Bart, Elwood) cool_dude(Bart, Elwood, Linus) -> -> -> -> Miles, Coltrane, Roach. Bart, Coltrane, Roach. Bart, Elwood, Roach. Bart, Elwood, Linus.

El cuerpo de un mtodo contiene expresiones Ruby normales, salvo que no se puede definir una clase no nica o un mdulo dentro de un mtodo. Si se define un mtodo dentro de otro mtodo, el mtodo interno es definido cuando se ejecuta el mtodo externo. El valor de retorno de un mtodo es el valor de la ltima expresin ejecutada o el resultado de una expresin explcita de retorno.

Listas de Argumentos de longitud Variable


Pero, y si lo que desea es pasar un nmero variable de argumentos o capturar mltiples argumentos en un solo parmetro? Colocar un asterisco antes del nombre del parmetro despus de parmetros normales hace justamente eso. def varargs(arg1, *rest) Got #{arg1} and #{rest.join(, )} end varargs(one) varargs(one, two) varargs one, two, three -> -> -> Got one and Got one and two Got one and two, three

En este ejemplo, el primer argumento se asigna como de costumbre al primer parmetro del mtodo. Sin embargo, el siguiente parmetro es prefijado con un asterisco, por lo que todos los argumentos restantes se agrupan en una nueva matriz, que se asigna a ese parmetro.

Mtodos y Bloques
Como ya comentamos en la seccin de bloques e iteradores, cuando se invoca un mtodo, puede ser asociado a un bloque. Normalmente, slo tiene que llamar al bloque desde dentro del mtodo con yield. def take_block(p1) if block_given? yield(p1) else p1 end end take_block(no block) take_block(no block) {|s| s.sub(/no /, ) } -> -> no block block

Sin embargo, si el ltimo parmetro en una definicin de mtodo se precede con un signo &, cualquier bloque asociado se convierte en un objeto Proc, y ese objeto se le asigna al parmetro.

47

class def end def end end

TaxCalculator initialize(name, &block) @name, @block = name, block get_tax(amount) #@name on #{amount} = #{ @block.call(amount) }

tc = TaxCalculator.new(Sales tax) {|amt| amt * 0.075 } tc.get_tax(100) tc.get_tax(250) -> -> Sales tax on 100 = 7.5 Sales tax on 250 = 18.75

Invocacin de un Mtodo
Se llama a un mtodo mediante la especificacin de un receptor, el nombre del mtodo y, opcionalmente, algunos parmetros y un bloque opcional. connection.download_MP3(jitterbug) {|p| show_progress(p) } En este ejemplo, el objeto connection es el receptor, download_MP3 es el nombre del mtodo, jitterbug es el parmetro, y lo que hay entre las llaves es el bloque asociado. Para los mtodos de clase y de mdulo, el receptor ser la clase o el nombre del mdulo. -> -> 66 0.707106781186548

File.size(testfile) Math.sin(Math::PI/4)

Si se omite el receptor, el valor por defecto es self, el objeto actual en curso. -> -> -> -> -> Object false false 967900 967900

self.class self.frozen? frozen? self.id id

Este mecanismo por defecto es cmo Ruby implementa los mtodos privados. Los mtodos privados no pueden ser llamados con un receptor, por lo que deben ser mtodos disponibles en el objeto en curso. Adems, en el ejemplo anterior hemos llamado a self.class, pero no podamos llamar al mtodo class sin un receptor. Esto es as porque class es tambin una palabra clave de Ruby (que introduce las definiciones de clase), por lo que su uso independiente generara un error de sintaxis. Los parmetros opcionales siguen al nombre del mtodo. Si no existe ambigedad, puede omitir los parntesis alrededor de la lista de argumentos cuando se llama a un mtodo (Otra documentacin de Ruby a veces llama comandos a estas llamadas de mtodo sin parntesis). Sin embargo, excepto en los casos ms simples, no recomendamos esto --puede haber algunos problemas sutiles (En particular, se deben utilizar parntesis en una llamada a mtodo que en s un parmetro a otro mtodo invocado --a menos que sea el ltimo parmetro). Nuestra regla es simple: si tienes dudas, utiliza parntesis. a = obj.hash # es lo mismo a = obj.hash() # que esto. obj.some_method Arg1, arg2, arg3 # es lo mismo obj.some_method(Arg1, arg2, arg3) # que con parntesis. Las versiones anteriores de Ruby agravan el problema porque permiten poner espacios entre el nombre del mtodo y el parntesis de apertura. Esto hace difcil analizar: el parntesis es el inicio de los parmetros o el inicio de una expresin? A partir de Ruby 1.8 se obtiene una advertencia si se pone un espacio entre el nombre del mtodo y un parntesis de apertura.

48

Mtodos que retornan valores


Cada mtodo llamado devuelve un valor (aunque no hay regla dice que haya que utilizar ese valor). El valor de un mtodo es el valor de la ltima instruccin ejecutada durante la ejecucin del mtodo. Ruby tiene una sentencia return, que produce una salida desde el mtodo actualmente en ejecucin. El valor de return es el valor de su argumento(s). Se trata de idiomticas Ruby que omitien el return si no es necesario. def meth_one one end meth_one ->

one

def meth_two(arg) case when arg > 0 positive when arg < 0 negative else zero end end meth_two(23) meth_two(0) -> -> positive zero

def meth_three 100.times do |num| square = num*num return num, square if square > 1000 end end meth_three -> [32, 1024] Como ilustra el ltimo caso, si se le da a return varios parmetros, el mtodo devuelve en una matriz. Se puede utilizar la asignacin paralela para recolectar este valor de retorno. num, square = meth_three num -> 32 square -> 1024

Expansin de Arrays en llamadas a Mtodos


Anteriormente, vimos que si se pone un asterisco delante de un parmetro formal de una definicin de mtodo, se incluirn en una matriz mltiples argumentos en la llamada al mtodo. Bueno, lo mismo funciona a la inversa. Cuando se llama a un mtodo, se puede explorar una matriz y cada uno de sus elementos se toma como un parmetro independiente. Para ello, hay que anteponer al argumento matriz (y lo deben seguir todos los argumentos regulares) un asterisco. def cinco(a, b, c, d, e) I was passed #{a} #{b} #{c} #{d} #{e} end cinco(1, 2, 3, 4, 5 ) -> I was passed 1 2 3 4 5 cinco(1, 2, 3, *[a, b]) -> I was passed 1 2 3 a b cinco(*(10..14).to_a) -> I was passed 10 11 12 13 14

49

Haciendo Bloques Ms Dinmicos


Ya hemos visto cmo asociar un bloque con una llamada a mtodo.

list_bones(aardvark) do |bone| # ... end Normalmente, esto es perfectamente suficiente, se asocia un bloque de cdigo con un mtodo de la misma manera que cualquier trozo de cdigo despus de una instruccin if o while. A veces, sin embargo, le gustara ser ms flexible. Por ejemplo, para la enseanza de las matemticas.

Un estudiante puede pedir una tabla de n-plus o de n-veces. Si el estudiante solicta una tabla de 2 veces, nos da la salida 2, 4, 6, 8, y as sucesivamente. (Este cdigo no verifica sus entradas para los errores.) print (t)imes or (p)lus: times = gets print number: number = Integer(gets) if times =~ /^t/ puts((1..10).collect {|n| n*number }.join(, )) else puts((1..10).collect {|n| n+number }.join(, )) end produce: (t)imes or (p)lus: t number: 2 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 Esto funciona pero es feo, con el cdigo prcticamente idntico en cada rama de la instruccin if. Estara mejor si factorizamos el bloque que realiza el clculo. print (t)imes or (p)lus: times = gets print number: number = Integer(gets) if times =~ /^t/ calc = lambda {|n| n*number } else calc = lambda {|n| n+number } end puts((1..10).collect(&calc).join(, )) produce: (t)imes or (p)lus: t number: 2 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 Si el ltimo argumento de un mtodo est precedido por un signo &, Ruby asume que es un objeto Proc. Si se quita de la lista de parmetros, convierte el objeto Proc en un bloque y lo asocia con el mtodo.

Recoger Argumentos Hash


Algunos lenguajes cuentan con argumentos de palabras clave, es decir, en lugar de pasar los argumentos en un orden y cantidad determinados, se pasa el nombre del argumento con su valor en cualquier orden. Ruby 1.8 no tiene argumentos de palabras clave (mentirosos nosotros, porque en la versin anterior

50

de este libro dijimos que habra. Tal vez en Ruby 2.0). Mientras tanto, la gente est utilizando hashes como una forma de lograr el mismo efecto. Por ejemplo, se podra considerar la adicin de un motor de bsqueda de nombres ms potente para nuestra SongList. class SongList def create_search(name, params) # ... end end list.create_search(short jazz songs, { genre => jazz, duration_less_than => 270 }) El primer parmetro es el nombre a buscar, y el segundo es un hash literal que contiene los parmetros de bsqueda. El uso de un hash nos permite simular palabras clave: Buscar canciones con un gnero jazz y una duracin inferior a 4 minutos y medio. Sin embargo, este enfoque es un poco torpe, y lo que hay entre llaves podra ser fcilmente confundido con un bloque asociado con el mtodo. Por lo tanto, Ruby tiene un acceso directo. Usted puede colocar parejas clave => valor en una lista de argumentos, siempre y cuando vayan a continuacin de los argumentos normales y precedan a los argumentos de la matriz y del bloque. Todos estos pares sern recogidos en un hash nico y pasados como un argumento para el mtodo. No son necesarias las llaves. list.create_search(short jazz songs, genre duration_less_than => jazz, => 270)

Por ltimo, el lenguaje Ruby probablemente hara uso de smbolos en lugar de cadenas, ya que stos dejan claro que te refieres al nombre de algo. list.create_search(short jazz songs, :genre :duration_less_than => :jazz, => 270)

Un programa bien escrito de Ruby normalmente contiene muchos mtodos, cada uno muy pequeo, por lo que vale la pena familiarizarse con las opciones disponibles en la definicin y en el uso de los mtodos de Ruby.

Expresiones
Hasta ahora hemos sido muy arrogantes con el uso de expresiones en Ruby. Despus de todo, a = b + c es una cosa bastante estndar. Puedes escribir un montn de cdigo Ruby sin leer nada de este captulo. Pero no sera tan divertido ;-)

Una de las primeras diferencias de Ruby es que todo lo que razonablemente puede devolver un valor es considerado una expresin. Qu significa esto en la prctica? Algunas cosas obvias incluyen la capacidad de encadenar las declaraciones. -> -> 0 [7, 3, 1, 0]

a = b = c = 0 [ 3, 1, 7, 0 ].sort.reverse

Algo quizs menos obvio, las cosas que normalmente son declaraciones en C o Java son expresiones en Ruby. Por ejemplo, las sentencias if y case, ambas retornan el valor de la ltima expresin ejecutada. song_type = if song.mp3_type == MP3::Jazz if song.written < Date.new(1935, 1, 1) Song::TradJazz

51

else Song::Jazz end else Song::Other end rating = case votes_cast when 0...10 when 10...50 else end then Rating::SkipThisOne then Rating::CouldDoBetter Rating::Rave

Ms adelante hablaremos ms sobre if y case.

Operadores
Ruby tiene el conjunto bsico de operadores (+, -, *, /, etc), as como algunas sorpresas. Ms adelante daremos una tabla con la lista completa de los operadores y sus precedencias. En ruby, muchos operadores se implementan en realidad como llamadas a mtodos. Por ejemplo, al escribir a * b + c en realidad ests pidiendo al objeto referenciado por a, ejecutar el mtodo * pasndole el parmetro b. A continuacin, le pides al objeto que con los resultados de dicho clculo ejecute el mtodo + pasndole c como parmetro. Esto es equivalente a escribir: (a.*(b)).+(c) Ya que todo es un objeto y porque se pueden redefinir los mtodos de instancia. Siempre puede redefinir la aritmtica bsica si no le gustan las respuestas que est recibiendo. class Fixnum alias old_plus + # Redefinir suma en Fixnums. Esto # es una MALA IDEA! def +(other) old_plus(other).succ end end 1 a a a + 2 = 3 += 4 + a + a -> -> -> 4 8 26

Ms til es que las clases que escriba puedan participar en las expresiones del operador como si se tratara de objetos integrados. Por ejemplo, podemos querer ser capaces de extraer el nmero de segundos de msica a la mitad de una cancin. Esto podemos hacerlo usando el operador de indexacin [] para especificar la msica que se va a extraer. class Song def [](from_time, to_time) result = Song.new(self.title + [extract], self.artist, to_time - from_time) result.set_start_time(from_time) result end end

52

Este fragmento de cdigo extiende la clase Song con el mtodo [], que toma dos parmetros (una hora de inicio y una hora de finalizacin). Devuelve una nueva cancin, con la msica recortada al intervalo dado. A continuacin, podra poner la introduccin de una cancin con cdigo como: song[0, 15].play

Miscelneo de Expresiones
As como es obvio que el operador en una expresin es una llamada a mtodo y las (tal vez) menos obvias expresiones declaracin (como if y case), Ruby tiene un par de cosas ms que se pueden utilizar en expresiones.

Expansin de Comandos

Si escribe una cadena entre apstrofes (a veces llamados acentos abiertos), o utiliza el prefijo delimitador de formato %x, ser ejecutada (por defecto) como un comando por el sistema operativo subyacente. El valor de la expresin es la salida estndar de ese comando. Los saltos de lnea no se quitan, por lo que es probable que el valor que recupere tenga un retorno final o un carcter de salto de lnea. `date` `ls`.split[34] %x{echo Hello there} -> -> -> Thu Aug 26 22:36:31 CDT 2004\n book.out Hello there\n

Puede utilizar la expansin de expresin y todas las secuencias usuales de escape en la cadena comando: for i in 0..3 status = `dbmanager status id=#{i}` # ... end El estado de salida del comando est disponible en la variable global $?.

Redefinicin de Apstrofes
En la descripcin de la expresin de salida del comando, hemos dicho que una cadena entre apstrofes por defecto se ejecuta como un comando. De hecho, la cadena se pasa al mtodo llamado Kernel. `(Un nico apstrofe). Si lo desea, puede sustituirlo por ste. alias old_backquote ` def `(cmd) result = old_backquote(cmd) if $? != 0 fail Command #{cmd} failed: #$? end result end print `date` print `data` produce: Thu Aug 26 22:36:31 CDT 2004 prog.rb:10: command not found: data prog.rb:5:in ``: Command data failed: 32512 (RuntimeError) from prog.rb:10

53

Asignacin
Casi todos los ejemplos que hemos dado hasta ahora en este libro han contado con la asignacin. Tal vez es hora de decir algo al respecto. Una sentencia de asignacin establece la variable o atributo en su lado izquierdo para referirse al valor de la derecha. A continuacin, devuelve ese valor como el resultado de la expresin de asignacin. Esto significa que puede encadenar asignaciones y que puede realizar asignaciones en lugares inesperados. a = b = 1 + 2 + 3 a -> 6 b -> 6 a = (b = 1 + 2) + 3 a -> 6 b -> 3 File.open(name = gets.chomp) Ruby tiene dos formas bsicas de asignacin. La primera asigna una referencia de objeto a una variable o constante. Esta forma de trabajo est integrada en el lenguaje. instrumento = piano MIDDLE_A = 440 La segunda forma de asignacin consiste en tener un atributo de objeto o referencia a elemento en el lado izquierdo. song.duration = 234 instrument[ano] = ccolo Estas formas son especiales, porque se implementan mediante llamadas a mtodos en los valores de la izquierda, lo que significa que se pueden anular. Ya hemos visto como definir un atributo de un objeto. Basta con definir el nombre del mtodo seguido de un signo de igual. Este mtodo recibe como parmetro el valor de la derecha de la asignacin. class Song def duration=(new_duration) @duration = new_duration end end Estos mtodos de configuracin de atributos no tienen que corresponder con las variables de instancia internas, y no es necesario un lector de atributos para todos los escritores de atributos (o viceversa). class Amplifier def volume=(new_volume) self.left_channel = self.right_channel = new_volume end end En versiones antiguas de Ruby, el resultado de la asignacin es el valor devuelto por el atributo establecido del mtodo. En Ruby 1.8, el valor de la asignacin es siempre el valor del parmetro, el valor de retorno del mtodo se descarta. class def end end Test val=(val) @val = val return 99

54

t = Test.new a = t.val = 2 a -> 2 En versiones anteriores de Ruby, a se establecera a 99 por la asignacin, y en Ruby 1.8 se establece a 2.

Asignacin Paralela
Durante su primera semana en un curso de programacin, podra tener que escribir cdigo para cambiar los valores de dos variables. int a = 1; int b = 2; int temp; temp = a; a = b; b = temp; Se puede hacer esto ms limpiamente en Ruby:

a, b = b, a Las asignaciones de ruby son realizadas efectivamente en paralelo, por lo que los valores asignados no se ven afectados por la asignacin misma. Los valores en el lado derecho se evalan en el orden en que aparecen antes de hacer la asignacin a las variables o atributos a la izquierda. Un ejemplo un tanto artificial ilustra esto. La segunda lnea asigna a las variables a, b, y c los valores de las expresiones x, x += 1 y x += 1, respectivamente. x = 0 a, b, c = x, (x += 1), (x += 1) -> -> 0 [0, 1, 2]

Cuando una asignacin tiene ms de un valor a la izquierda, la expresin de asignacin devuelve una matriz de valores a la derecha. Si una asignacin contiene ms valores a la izquierda que a la derecha, los valores de la izquierda excedentes se establecen a nil. Si una asignacin mltiple contiene ms valores a la derecha que a la izquierda, los valores a la derecha adicionales se omiten. Si una asignacin tiene slo un valor a la izquierda y varios valores a la derecha, los valores de la derecha se convierten en una matriz que es asignada al valor de la izquierda. Utilizar descriptores de acceso en una clase Por qu escribimos self.left_channel en uno de los ejemplos anteriores? Bueno, los atributos de escritura tienen un gotcha oculto (un inesperado o poco intuitivo, pero documentado, comportamiento de un sistema informtico --en oposicin a un fallo). Normalmente, los mtodos internos de una clase pueden invocar otros mtodos de su misma clase y superclases en la forma funcional (es decir, con un receptor implcito de si mismo). Sin embargo, esto no funciona con los escritores de atributos. Ruby ve la asignacin y decide que el nombre de la izquierda debe ser una variable local, no un mtodo de llamada a un escritor de atributos. class BrokenAmplifier attr_accessor :left_channel, :right_channel def volume=(vol) left_channel = self.right_channel = vol end end ba = BrokenAmplifier.new ba.left_channel = ba.right_channel = 99 ba.volume = 5 ba.left_channel -> 99 ba.right_channel -> 5

55

Nos olvidamos de poner self. delante de la asignacin a left_channel, por lo que Ruby almacena el nuevo valor en una variable local del mtodo volume=; el atributo del objeto nunca se actualiza. Esto puede dar lugar a un error difcil de localizar. Se pueden contraer y expandir las matrices con el operador de Ruby de asignacin paralela. Si el ltimo valor de la izquierda est precedido por un asterisco, todos los valores a la derecha restantes sern recolectados y asignados a ese valor de la izquierda como una matriz. Del mismo modo, si el ltimo valor a la derecha es una matriz, se puede prefijar con un asterisco y efectivamente se expande en sus valores constituyentes en su lugar. (Esto no es necesario si el valor derecho es lo nico que hay en este lado derecho --la matriz se expande de forma automtica.) a = [1, 2, 3, 4] b, c = a b, *c = a b, c = 99, a b, *c = 99, a b, c = 99, *a b, *c = 99, *a -> -> -> -> -> -> b b b b b b == == == == == == 1, 1, 99, 99, 99, 99, c c c c c c == == == == == == 2 [2, 3, 4] [1, 2, 3, 4] [[1, 2, 3, 4]] 1 [1, 2, 3, 4]

Asignaciones Anidadas
Las asignaciones en paralelo tienen una caracterstica que vale la pena mencionar. El lado izquierdo de una asignacin puede contener una lista de elementos entre parntesis. Ruby trata estos elementos como si se tratara de una sentencia de asignacin anidada. Se extrae el valor de la derecha que corresponde, asignndole los trminos entre parntesis antes de continuar con la asignacin de ms alto nivel. b, b, b, b, b, (c, d), (c, d), (c, d), (c, d), (c,*d), e e e e e = = = = = 1,2,3,4 [1,2,3,4] 1,[2,3],4 1,[2,3,4],5 1,[2,3,4],5 -> -> -> -> -> b b b b b == == == == == 1, 1, 1, 1, 1, c c c c c == == == == == 2, 2, 2, 2, 2, d d d d d == == == == == nil, nil, 3, 3, [3, 4], e e e e e == == == == == 3 3 4 5 5

Otras Formas de Asignacin


Al igual que muchos otros lenguajes, Ruby tiene un atajo sintctico: a = a + 2 se puede escribir como a += 2. La segunda forma se convierte internamente a la primera. Esto significa como cabra esperar, que los operadores se han definido como mtodos en sus propias clases de trabajo. class def end def end def end end Bowdlerize initialize(string) @value = string.gsub(/[aeiou]/, *) +(other) Bowdlerize.new(self.to_s + other.to_s) to_s @value

a = Bowdlerize.new(damn ) a += shame

-> ->

d*mn d*mn sh*m*

Algo que no se encuentra en Ruby son los operadores de autoincremento ( ++) y (--) de C y Java. Se utilizan en su lugar las formas += y -=.

56

Ejecucin Condicional
Ruby tiene diferentes mecanismos para la ejecucin condicional de cdigo. Con la mayora de ellos debe sentirse familiarizado y muchos tienen algunas peculiaridades muy cuidadas. Antes de entrar en ellos, sin embargo, tenemos que pasar un tiempo viendo las expresiones booleanas.

Expresiones Booleanas
Ruby tiene una definicin sencilla de la verdad. Cualquier valor que no es nil ni la constante false es verdadero. Usted encontrar que las rutinas de biblioteca utilizan esto constantemente. Por ejemplo, IO#gets que devuelve la siguiente lnea de un archivo, retorna nil al final del archivo, lo que le permite escribir bucles como while line = gets # process line end Sin embargo, los programadores de C, C++ y Perl as veces caen en una trampa. El nmero cero no se interpreta como un valor falso y tampoco es una cadena de longitud cero. Esto puede ser un hbito difcil de romper.

Defined?, And, Or y Not


Ruby soporta todos los operadores lgicos estndar y presenta el nuevo operador defined?. Tanto and como && se evalan como true slo si ambos operandos son verdaderos. Evalan el segundo operando slo si el primero es cierto (esto se conoce como evaluacin de cortocircuito). La nica diferencia en las dos formas es la prioridad (and la tiene ms baje que &&). Del mismo modo, tanto or como || evalan como true si alguno de los operandos es cierto. Evalan su segundo operando slo si el primero es falsa. Al igual que con and, la nica diferencia entre or y || es su prioridad. Slo para hacer la vida ms interesante, and y or tienen la misma prioridad, e && tiene prioridad mayor que ||. not y ! retornan el contrario de su operando ( false si el operando es cierto, y true si el operando es falso). Y s, not y ! slo se diferencian en la precedencia. Todas estas reglas de precedencia se resumen en una tabla ms adelante. El operador defined? retorna nil si su argumento (que puede ser una expresin arbitraria) no est definido, de lo contrario retorna una descripcin de ese argumento. Si el argumento es yield, defined? retorna la cadena yield si se asocia un bloque de cdigo con el contexto actual. defined? defined? defined? defined? defined? defined? defined? defined? 1 dummy printf String $_ Math::PI a = 1 42.abs -> -> -> -> -> -> -> -> expression nil method constant global-variable constant assignment method

Adems de los operadores booleanos, los objetos de Ruby soportan comparacin con los mtodos ==, ===, <=> =~, eql? y equal? (vase el cuadro en la pgina siguiente). Todos, pero <=> se define en la clase Object y a menudo son anulados por los descendientes a fin de proporcionar una semntica adecuada. Por ejemplo, la clase Array redefine == para que dos objetos matriz sean iguales si tienen el mismo nmero de elementos y si sus correspondientes elementos son iguales.

57

Operador == ===

Significado

Test de valor igual Utilizado para comparar los elementos de un each con los objetivos en una clasula de una sentencia case <=> Operador de comparacin general. Devuelve -1, 0 o +1, dependiendo de si el receptor es menor, igual o mayor que su argumento. <, <=, >=, > Operadores de comparacin para menor que, menor o igual, mayor o igual y mayor que =~ Expresin regular coincide con el patrn eql? True si el receptor y el argumento tienen ambos el mismo tipo y valor igual. 1 == 1.0 devuelve true, pero 1.eql?(1,0) devuelve false. equal? True si el receptor y el argumento tienen el mismo identificador (ID) de objeto.

== y =~ tienen la forma negada != y !~. Cuando Ruby lee el programa, a != b es equivalente a !(a==b) y a !~ b es lo mismo que !(a=~b). Esto significa que si usted escribe una clase que anula == o =~ obtiene el funcionamiento de != y !~ gratis. Pero por otro lado esto tambin significa que != y !~ no se pueden definir independientemente de == y =~, respectivamente. Se puede utilizar un rango Ruby como exprersin booleana. Un rango como exp1..exp2 se evaluar como false hasta que exp1 se cumple. El rango se evaluar como true hasta que exp2 se cumple. Una vez que esto sucede, se reestablece el rango, listo para actuar de nuevo. Un poco ms adelante mostramos algunos ejemplos. Antes de Ruby 1.8, se poda utilizar una expresin regular cruda como una expresin booleana. Ahora esto est en desuso. Se puede utilizar el operador ~ para comparar $_ frente a un patrn.

El valor de las Expresiones Lgicas


En el texto, que dice cosas como and da como resultado true si ambos operandos son verdaderos. Pero en realidad es un poco ms sutil que eso. Los operadores and, or, && y || retornan el primero de sus argumentos que determinan la verdad o falsedad de la condicin. Suena muy bien. Qu significa? Tomamos la expresin val1 and val2. Si val1 es falso o nulo, entonces sabemos que la expresin no puede ser verdad. En este caso, el valor de val1 determina el valor total de la expresin, por lo que es el valor devuelto. Si val1 tiene otro valor, el valor total de la expresin depende de val2, por lo que se devuelve el valor de este ltimo. nil and true false and true 99 and false 99 and nil 99 and cat -> -> -> -> -> nil false false nil cat

Tenga en cuenta que a pesar de toda esta magia, el valor de verdad total de la expresin es correcto.

La misma evaluacin se lleva a cabo para or (excepto que en una expresin or el valor se conoce antes si val1 es no falso). false or nil -> nil nil or false -> false 99 or false -> 99 Un lenguaje comn como Ruby hace uso de esto: words[key] ||= [] words[key] << word La primera lnea es equivalente a words[key] = words[key] || []. Si la entrada para key en el hash words no est definida (nil) el valor de || ser el segundo operando, una nueva matriz vaca. Por

58

lo tanto, esta lnea de cdigo asignar una matriz a un elemento hash que ya no tiene un valor, dejando intacto lo contrario. A veces se va a ver esto escrito en una lnea como: (words[key] ||= []) << word

Expresiones If y Unless
Una expresin if en Ruby es bastante similar a una sentencia if en otros lenguajes.

if song.artist == Gillespie then handle = Dizzy elsif song.artist == Parker then handle = Bird else handle = unknown end Si la declaracin if se dispone en varias lineas, puede dejar fuera la palabra clave then.

if song.artist == Gillespie handle = Dizzy elsif song.artist == Parker handle = Bird else handle = unknown end Sin embargo, si desea presentar su cdigo de mejor manera, puede separar la expresin lgica de las siguientes sentencias con la palabra clave then. if song.artist == Gillespie then handle = Dizzy elsif song.artist == Parker then handle = Bird else handle = unknown end Se puede hacer incluso ms conciso utilizando los dos puntos (:) en lugar de then.

if song.artist == Gillespie: handle = Dizzy elsif song.artist == Parker: handle = Bird else handle = unknown end Se puede tener cero o ms clusulas elsif y una clusula else opcional.

Como hemos dicho antes, if es una expresin, no una sentencia, que devuelve un valor. No tiene que usar el valor de una expresin if, pero puede ser til. handle = if song.artist == Gillespie then Dizzy elsif song.artist == Parker then Bird else unknown end Ruby tambin tiene una forma de negacin de la sentencia if.

59

unless song.duration > 180 cost = 0.25 else cost = 0.35 end Finalmente, para los aficionados de C que haya por ah, Ruby tambin es compatible con la expresin condicional estilo C: cost = song.duration > 180 ? 0.35 : 0.25 Una expresin condicional devuelve el valor de las expresin antes o despus de los dos puntos, dependiendo de si la expresin lgica antes del signo de interrogacin se evala como verdadera o falsa. En este caso, si la duracin del tema es mayor de tres minutos, la expresin devuelve 0,35 y para canciones ms cortas, devuelve 0,25. Sea cual sea el resultado, se asigna a cost.

Modificadores If y Unless
Ruby comparte una caracterstica interesante con Perl. Los modificadores de instrucciones le permiten cambiar sentencias condicionales al final de una sentencia normal. mon, day, year = $1, $2, $3 if date =~ /(\d\d)(\d\d)(\d\d)/ puts a = #{a} if debug print total unless total.zero? Por un modificador if, la expresin anterior slo se evaluar si la condicin es verdadera. unless trabaja a la inversa. File.foreach(/etc/fstab) do |line| next if line =~ /^#/ # Skip comments parse(line) unless line =~ /^$/ # No parsear lneas vacas end Como if es en s una expresin, se puede volver muy oscura con sentencias tales como if artist == John Coltrane artist = Trane end unless use_nicknames == no Este camino conduce a las puertas de la locura.

Expresiones Case

La expresin Ruby case es una poderosa bestia, y para hacerla an ms potente, viene en dos sabores.

La primera forma es bastante cercana a una serie de sentencias if: le permite enumerar una serie de condiciones y ejecutar la instruccin correspondiente a la primera que se cumple. Por ejemplo, los aos bisiestos deben ser divisibles por 400 o por 4 y no por 100. bisiesto = case when year % 400 == 0: true when year % 100 == 0: false else year % 4 == 0 end

La segunda forma de la sentencia case es probablemente ms comn. Puede especificar un objetivo al principio de la sentencia case, y un cada when en clasulas con una o ms comparaciones. case input_line when debug

60

dump_debug_info dump_symbols when /p\s+(\w+)/ dump_variable($1) when quit, exit exit else print Illegal command: #{input_line} end Al igual que con if, case devuelve el valor de la ltima expresin ejecutada, y se puede utilizar la palabra clave then, si la expresin est en la misma lnea que la condicin. kind = case year when 1850..1889 then Blues when 1890..1909 then Ragtime when 1910..1929 then New Orleans Jazz when 1930..1939 then Swing when 1940..1950 then Bebop else Jazz end Y al igual que con if, puede utilizar los dos puntos (:) en lugar de then.

kind = case year when 1850..1889: Blues when 1890..1909: Ragtime when 1910..1929: New Orleans Jazz when 1930..1939: Swing when 1940..1950: Bebop else Jazz end case opera mediante comparacin del objetivo (la expresin despus de la palabra clave case) con cada una de las expresiones de comparacin despus de la palabra clave when. Esta prueba se realiza mediante comparacin === objetivo. Mientras una clase defina una semntica significativa para === (y todas las clases internas al lenguaje - built-in- lo hacen), los objetos de esa clase se puede utilizar en las expresiones case. Por ejemplo, las expresiones regulares definen === como una simple comparacin de patrn.

case line when /title=(.*)/ puts Title is #$1 when /track=(.*)/ puts Track is #$1 when /artist=(.*)/ puts Artist is #$1 end Las clases de Ruby son instancias de la clase Class, que define === para comprobar si el argumento es una instancia de la clase o una de sus superclases. As que (abandonando los beneficios del polimorfismo y tirando a los dioses de la refactorizacin de las orejas), se puede testear la clase de los objetos. case shape when Square, Rectangle # ... when Circle # ... when Triangle

61

# ... else # ...end

Bucles
No se lo digais a nadie, pero Ruby tiene internamente una construccin de bucles bastante primitiva. El bucle while ejecuta el cuerpo de la sentencia cero o ms veces, siempre y cuando su condicin sea verdadera. Por ejemplo, este cdigo comn lee hasta que se agota la entrada. while line = gets # ... end El bucle until que es todo lo contrario, se ejecuta el cuerpo hasta que la condicin sea verdadera.

until play_list.duration > 60 play_list.add(song_list.pop) end Al igual que con if y unless, se pueden utilizar ambas en los bucles como modificadores de sentencia.

a = 1 a *= 2 while a < 100 a -= 10 until a < 100 a -> 98 En la seccin de expresiones booleanas, dijimos que un rango se puede utilizar como una especie de flip-flop, retornando true cuando ocurre algn evento y permanece true hasta que un segundo evento ocurre. Esta funcionalidad se utiliza normalmente en los bucles. En el ejemplo siguiente, se lee un archivo de texto que contiene los diez primeros nmeros ordinales (primero, segundo, y as sucesivamente), pero solo empieza a imprimir las lneas cuando coincide con tercero y termina cuando coincide con quinto. file = File.open(ordinal) while line = file.gets puts(line) if line =~ /third/ .. line =~ /fifth/ end produce: third fourth fifth Programadores que vienen de Perl pueden escribir el ejemplo anterior de forma ligeramente diferente.

file = File.open(ordinal) while file.gets print if ~/third/ .. ~/fifth/ end produce: third fourth fifth Este sistema utiliza un comportamiento mgico entre bastidores: gets asigna la ltima lnea para la

62

lectura de la variable global $_, el operador ~ hace una comparacin de expresin regular contra $_ y print sin argumentos imprime $_. Este tipo de cdigo est pasado de moda en la comunidad Ruby. Los elementos de un rango utilizados en una expresin booleana pueden ser tambin expresiones. Estos son evaluados cada vez que se evala la expresin lgica en general. Por ejemplo, el siguiente cdigo utiliza el hecho de que la variable $. contiene el nmero de lnea de entrada actual, para mostrar nmeros de lnea del uno al tres y aquellas que coincidan entre la comparacin /eig/ y /nin/. File.foreach(ordinal) do |line| if (($. == 1) || line =~ /eig/) .. (($. == 3) || line =~ /nin/) print line end end produce: first second third eighth ninth Uno se puede arrugar al usar while y until como modificadores de sentencia. Si la declaracin que va a modificar es un bloque de begin/end, el cdigo en el bloque siempre se ejecutar al menos una vez independientemente del valor de la expresin lgica. print Hello\n while false begin print Goodbye\n end while false produce: Goodbye

Iteradores
Si se lee el comienzo de la seccin anterior, es posible que se haya desanimado. Ruby tiene internamente una construccin de bucles bastante primitiva, se dijo. No se desespere, amable lector, ya que tenemos una buena noticia. Ruby no necesita ningn tipo de complejos bucles integrados, porque todas las cosas divertidas se implementan mediante iteradores Ruby. Por ejemplo, Ruby no tiene un bucle for, (por lo menos no del tipo que se encuentran en C, C++ o Java). En cambio, Ruby utiliza mtodos definidos en varias clases integradas para proporcionar equivalentes, pero menos propensos a errores.. Veamos algunos ejemplos.

3.times do print Ho! end produce: Ho! Ho! Ho! Es fcil evitar errores, este ciclo se ejecutar tres veces y punto. Adems de con times, con los nmeros enteros se pueden hacer bucles en intervalos especficos llamando a downto y upto, y para todos los tipos de nmeros se peuede utilizar step. Por ejemplo, un tradicional bucle for que recorre de 0 a 9 (algo as como i = 0; i <10; i++) se escribe como sigue:

63

0.upto(9) do |x| print x, end produce: 0 1 2 3 4 5 6 7 8 9 Un bucle de 0 a 12 cada 3 se puede escribir de la siguiente manera:

0.step(12, 3) {|x| print x, } produce: 0 3 6 9 12 Del mismo modo, iterar sobre matrices y otros contenedores se hace fcil con el mtodo each: [ 1, 1, 2, 3, 5 ].each {|val| print val, } produce: 1 1 2 3 5 Y una vez que una clase soporta each, los otros mtodos estn disponibles en el mdulo Enumerable (documentado ms adelante). Por ejemplo, la clase File proporciona un mtodo each, que devuelve cada lnea de un archivo por turno. Utilizando el mtodo grep en Enumerable, podramos iterar slo aquellas lneas que cumplen una determinada condicin. File.open(ordinal).grep(/d$/) do |line| puts line end produce: second third Por ltimo, est el lazo ms bsico de todos. Ruby proporciona un iterador integrado llamado loop.

loop do # block ... end El iterador loop llama al bloque asociado para siempre (o por lo menos hasta interrumpir del bucle, pero antes tendr que seguir leyendo para saber cmo hacerlo).

For...In
Anteriormente hemos dicho que las nicas primitivas de bucle integradas son while y until. Qu es for, entonces? Bueno, for es casi un terrn de azcar sintctico. Cuando se escribe: for song in songlist song.play end Ruby lo traduce en algo as como:

64

songlist.each do |song| song.play end La nica diferencia entre el bucle for y la forma each es el alcance de las variables locales que se definen en el cuerpo. Esto se discute ahora un poco ms adelante. Se puede utilizar for para iterar sobre cualquier objeto que responde al mtodo each, como un Array o un Range. for i in [fee, fi, fo, fum] print i, end for i in 1..3 print i, end for i in File.open(ordinal).find_all {|line| line =~ /d$/} print i.chomp, end produce: fee fi fo fum 1 2 3 second third Mientras su clase defina un razonable mtodo each, se puede utilizar un bucle for para recorrer sus objetos. class def end end Periods each yield Classical yield Jazz yield Rock

periods = Periods.new for genre in periods print genre, end produce: Classical Jazz Rock

Break, Redo y Next


Las construcciones de control de bucle break, redo y next permiten alterar el flujo normal de un bucle o iterador. break termina el bucle inmediatamente y el control se reanuda en la siguiente instruccin del bloque. redo repite el bucle desde el principio, pero sin volver a evaluar la condicin o ir a buscar el siguiente elemento (en un iterador). next salta al final del bucle a partir de la prxima iteracin. while line = gets next if line =~ /^\s*#/ # saltar comentarios break if line =~ /^END/ # parada al final # sustitucin de cosas en acentos abiertos y volver a intentarlo redo if line.gsub!(/`(.*?)`/) { eval($1) } # process line ... end

65

Estas palabras clave tambin se pueden utilizar con cualquiera de los mecanismos basados en bucle iterador. i=0 loop do i += 1 next if i < 3 print i break if i > 4 end produce: 345 A partir de Ruby 1.8, break y next pueden recibir argumentos. En bucles convencionales, es probable que slo tienga sentido hacer esto con break (ya que cualquier valor dado a next efectivamente se pierde). Si un bucle convencional no ejecuta un break, este valor es nil. result = while line = gets break(line) if line =~ /answer/ end process_answer(result) if result

Retry
La sentencia redo hace un bucle para repetir la iteracin actual. A veces, sin embargo, es necesario darle cuerda al bucle para que vuelva al principio. La sentencia retry es justo el billete. retry reinicia cualquier tipo de bucle iterador. for i in 1..100 print Now at #{i}. Restart? retry if gets =~ /^y/i end La ejecucin de esta forma es interactiva, puede se puede ver: at 1. Restart? n at 2. Restart? y at 1. Restart? n . .

Now Now Now .

retry volver a evaluar los argumentos para el iterador antes de reiniciarlo. He aqu un ejemplo de un bucle until hgalo usted mismo. def do_until(cond) break if cond yield retry end i = 0 do_until(i > 10) do print i, i += 1 end produce: 0 1 2 3 4 5 6 7 8 9 10

66

mbito de las Variables, Bucles y Bloques


Los bucles while, until y for se construyen integrados en el lenguaje y no introducen nuevas posibilidades; los locales ya existentes se pueden utilizar en el bucle y cualquier otros nuevos locales creados estarn disponibles despus. Los bloques utilizados por iteradores (tales como loop y each) son un poco diferentes. Normalmente, las variables locales creadas en estos bloques no son accesibles fuera del bloque. [ 1, 2, 3 ].each do |x| y = x + 1 end [ x, y ] produce: prog.rb:4: undefined local variable or method `x for main:Object (NameError) Sin embargo, si un momento dado en el bloque se ejecuta una variable local que ya existe con el mismo nombre que el de una variable en el bloque, ste utiliza la variable local existente. Su valor por lo tanto, estar disponible despus de terminar el bloque. Como muestra el siguiente ejemplo, esto se aplica tanto a las variables normales como a los parmetros del bloque. x = nil y = nil [ 1, 2, 3 ].each do |x| y = x + 1 end [ x, y ] -> [3, 4] Tenga en cuenta que a la variable no tiene por qu habrsele dado un valor en el mbito externo: el intrprete de Ruby slo tiene que verla. if false a = 1 end 3.times {|i| a = i } a -> 2

Todo este tema del mbito de las variables y los bloques es el que genera debate en la comunidad Ruby. El esquema actual tiene problemas definidos (sobre todo cuando las variables son alias inesperadamente dentro de bloques), pero de momento no se ha logrado llegar a algo que sea mejor y ms aceptable para la comunidad en general. Matz ha prometido cambios en Ruby 2.0, pero mientras tanto, tenemos un par de sugerencias para minimizar los problemas con las interferencias de las variables locales y los bloques. Mantener breves sus mtodos y bloques. Con menos variables, menor es la posibilidad de que vayan a darse una paliza entre s. Tambin es ms fcil de cara el cdigo para comprobar que no tiene nombres en conflicto. Utilizar diferentes esquemas de nomenclatura para las variables locales y los parmetros de bloques. Por ejemplo, es probable que no quiera una variable local llamada i, pero podra ser perfectamente aceptable como un parmetro de bloque. En realidad, este problema no se plantea en la prctica tan a menudo como se pueda pensar.

67

Excepciones, Capturas y Lanzamientos


Hasta ahora hemos estado desarrollando cdigo en Pleasantville, un lugar maravilloso donde nada, nunca, va mal. Cada llamada a librera tiene xito, los usuarios no introducen datos incorrectos y los recursos son abundantes y baratos. Bueno, eso est por cambiar. Bienvenido al mundo real! En el mundo real, los errores ocurren. Los buenos programas (y programadores) se anticipan y hacen arreglos para manejarlos correctamente. Esto no siempre es tan fcil como pueda parecer. A menudo, el cdigo que detecta un error no tiene el contexto para saber qu hacer al respecto. Por ejemplo, intentar abrir un archivo que no existe es aceptable en algunas circunstancias y es un error fatal en otras ocasiones. Cmo v a hacer su mdulo de manejo de archivos? El enfoque tradicional es el uso de cdigos de retorno. El mtodo open devuelve un valor especfico para decir que falla. Este valor se propaga de nuevo a travs de las capas de las llamadas a rutina hasta que alguien quiere asumir la responsabilidad de ello. El problema con este enfoque es que la gestin de todos estos cdigos de error puede ser un dolor de cabeza. Si en una funcin hay llamadas a open, luego leer, finalmente cerrar, y cada una puede devolver una indicacin de error, cmo puede distinguir la funcin estos cdigos de error en el valor que devuelve a su llamador? En gran medida, las excepciones resuelven este problema. Las excepciones le permiten empaquetar informacin sobre un error en un objeto. El objeto de excepcin despus se propaga de vuelta a la pila de llamadas de forma aautomtica, hasta que el sistema de ejecucin se encuentra el cdigo que explcitamente declara que sabe manejar ese tipo de excepcin.

La Clase Exception
El paquete que contiene la informacin sobre una excepcin es un objeto de la clase Exception o uno de los hijos de la misma. Ruby predefine una jerarqua ordenada de excepciones, que se muestra en la Figura 4 de la pgina siguiente. Como veremos ms adelante, esta jerarqua hace el manejo de excepciones mucho ms fcil. Cuando usted necesita poner una excepcin, puede utilizar una de las clases Exception integradas, o puede crear una propia. Si usted crea la suya, puede que quiera que sea una subclase de StandardError o una de sus hijos. Si no lo hace su excepcin no ser capturada por defecto. Cada Excepction tiene asociada una cadena de mensaje y un trazado de pila. Si define sus propias excepciones, puede aadir informacin adicional.

Manejo de Excepciones
Nuestra mquina de discos descarga canciones de Internet a travs de un socket TCP. El cdigo bsico es simple (suponiendo que el nombre del archivo y el socket se han configurado). op_file = File.open(opfile_name, w) while data = socket.read(512) op_file.write(data) end Qu pasa si tenemos un error fatal a mitad de la descarga? Desde luego, no desea almacenar una cancin incompleta en la lista de canciones. I Did It My *click*. Vamos a aadir un cdigo de control de excepciones y ver cmo ayuda. El manejo de excepciones que incluya el cdigo que podra lanzar una excepcin en un bloque begin/end y usar una o ms clusulas rescue que digan a Ruby los tipos de excepciones que queremos manejar. En este caso particular estamos interesados en detectar excepciones SystemCallError (y, por ende, las excepciones que son subclases de SystemCallError), as que sea eso es lo que aparece en la lnea de rescue. En el bloque

68

de control de errores, incluye el informe de error, cerrar y eliminar el archivo de salida, y luego volver a lanzar la excepcin. op_file = File.open(opfile_name, w) begin # Las excepciones planteadas por este cdigo sern # capturadas por la siguiente clusula de rescate while data = socket.read(512) op_file.write(data) end rescue SystemCallError $stderr.print IO failed: + $! op_file.close File.delete(opfile_name) raise end Cuando se dispara una excepcin, e independiente de cualquier posterior manejo de excepciones, Ruby coloca una referencia al objeto Exception asociado a las variable global $!

(el signo de exclamacin, presumiblemente refleja nuestra sorpresa de que nuestro cdigo podra provocar cualquier error). En el ejemplo anterior, hemos utilizado en la variable el formato $! para nuestro mensaje de error. Despus de cerrar y eliminar el archivo, hacemos un llamada a raise sin parmetros, que relanza la excepcin en $!. Esta es una tcnica til, ya que le permite escribir cdigo que filtra excepciones,

69

transmitiendoaquellas que usted no puede manejar a niveles superiores. Es casi como la aplicacin de una jerarqua de herencia para el procesamiento de errores. Se pueden tener varias clusulas rescue en un bloque begin, y cada clusula rescue puede especificar mltiples excepciones a capturar. Al final de cada clusula rescue se le puede dar a Ruby el nombre de una variable local para recibir la excepcin que coincida. Mucha gente encuentra esto ms legible que el uso de $! por todas partes. begin eval string rescue SyntaxError, NameError => boom print String doesnt compile: + boom rescue StandardError => bang print Error running script: + bang end Cmo decide Ruby que clusula rescue ejecutar? Resulta que el proceso es bastante similar a la utilizada por la instruccin case. Para cada clusula rescue en el bloque begin, Ruby compara la excepcin formulada contra cada uno de los parmetros por turno. Si la excepcin coincide con un parmetro, Ruby ejecuta el cuerpo de rescue y deja de buscar. La comparacin se hizo con parmetro===$!. Para la mayora de las excepciones, esto significa que la comparacin tendr xito si la excepcin nombrada en la clusula rescue es del mismo tipo que la excepcin que se ha lanzado actualmente, o es una superclase de esta excepcin (Esta comparacin se debe a que las excepciones son clases, y las clases, a su vez son tipos de mdulo. El mtodo === est definido para los mdulos y devuelve true si el operando es de la misma clase que el receptor o uno de sus ancestros. Si escribes una clusula rescue, sin lista de parmetros, el parmetro por defecto es StandardError. Si no coincide ninguna clusula rescue o si se lanza una excepcin fuera de un bloque begin/end, Ruby se mueve hacia arriba de la pila y busca un manejador de excepcin en el llamador y sino en el que llama al llamador y as sucesivamente. Aunque los parmetros de la clusula rescue suelen ser los nombres de las clases Exception, en realidad pueden ser expresiones arbitrarias (incluyendo llamadas a mtodos) que retornan una clase Exception .

Errores del sistema


Los errores del sistema se producen cuando una llamada al sistema operativo devuelve un cdigo de error. En los sistemas POSIX, estos errores tienen nombres tales como EAGAIN y EPERM. (Si ests en una mquina Unix, puede escribir man errno para obtener una lista de estos errores.) Ruby tiene estos errores y envuelve cada uno en un objeto de excepcin especfico. Cada uno es una subclase de SystemCallError y se define en un mdulo llamado Errno. Esto significa que usted encontrar excepciones con nombres de clase como Errno::EAGAIN, Erno::EIO y Errno::EPERM. Si desea obtener el cdigo de error del sistema subyacente, cada objeto de excepcin Errno tiene una constante de clase llamada (un tanto confusamente) Errno que contiene el valor. Errno::EAGAIN::Errno Errno::EPERM::Errno Errno::EIO::Errno Errno::EWOULDBLOCK::Errno -> -> -> -> 35 1 5 35

Tenga en cuenta que EWOULDBLOCK y EAGAIN tienen el mismo nmero de error. Esta es una caracterstica del sistema operativo de la computadora utilizada para producir este libro --las dos constantes mapeadas con el mismo nmero de error. Para hacer frente a esto, Ruby arregla las cosas para que Errno::EAGAIN y Errno::EWOULDBLOCK sean tratadas de manera idntica en una clusula rescue. Ya sea que usted pida un rescate, ya sea que usted rescate. Para ello, se hace la redefinicin de SystemCallError#=== de modo que si se comparan dos subclases de SystemCallError, la comparacin se haga con su nmero de error y no con su posicin en la jerarqua.

70

Poner en Orden
A veces es necesario garantizar que algn tipo de procesamiento se realiza al final de un bloque de cdigo, independientemente de si se dispar alguna excepcin. Por ejemplo, usted puede tener un archivo abierto en la entrada del bloque, y necesita asegurarse de que se cerrar al salir del bloque. La clusula ensure hace justo esto. ensure va despus de la ltima clusula rescue y contiene un trozo de cdigo que se ejecuta siempre cuando termina el bloque. No importa si se cierra el bloque con normalidad, si se levanta y rescata una excepcin, o si se termina por una excepcin no capturada --ensure ejecuta el bloque. f = File.open(testfile) begin # .. process rescue # .. handle error ensure f.close unless f.nil? end La clusula else tiene similar, aunque menos til, construccin. Si est presente, va despus de las clusulas rescue y antes de ensure. El cuerpo de una clusula else solamente se ejecuta si no hay excepciones disparadas por el cuerpo principal del cdigo. f = File.open(testfile) begin # .. process rescue # .. handle error else puts Congratulations-- no errors! ensure f.close unless f.nil? end

Correr de nuevo
A veces usted puede ser capaz de corregir la causa de una excepcin. En esos casos, puede utilizar la instruccin retry dentro de una clusula rescue para repetir todo el bloque begin/end. Claramente, aqu existe un enorme margen de bucles infinitos, as que esta caracterstica hay que utilizarla con precaucin (y con un dedo ligeramente apoyado sobre la tecla de interrupcin). Como ejemplo de cdigo que vuelve a intentar en las excepciones, echar un vistazo al siguiente, adaptado de la librera net/smtp.rb de Minero Aokis: @esmtp = true begin # Primero trate un inicio de sesin extendido, di no se consigue porque # el servidor no lo admite, recurrir a un inicio de sesin normal if @esmtp then @command.ehlo(helodom) else @command.helo(helodom) end rescue ProtocolError if @esmtp then @esmtp = false retry else raise

71

end end Este cdigo intenta primero conectarse a un servidor SMTP con el comando EHLO, que no est soportado universalmente. Si el intento de conexin falla, el cdigo establece la variable @esmtp en false y vuelve a intentar la conexin. Si no lo consigue por segunda vez, se lanza una excepcin al llamador.

Lanzando Excepciones
Hasta ahora hemos estado a la defensiva, manejando excepciones lanzadas por otros. Es hora de devolver la pelota y pasar a la ofensiva. Puede crear excepciones en su cdigo con el mtodo Kernel.raise (o su sinnimo un tanto crtico, Kernel.fail). raise raise bad mp3 encoding raise InterfaceException, Keyboard failure, caller La primera forma simplemente relanza la excepcin actual (o un RuntimeError si no es la excepcin en curso). Esta se utiliza en los controladores de excepcion que necesitan interceptar una excepcin antes de transmitirla. La segunda forma crea una nueva excepcin RuntimeError, estableciendo su mensaje a la cadena dada. Esta excepcin dispar la pila de llamadas. La tercera forma utiliza el primer argumento para crear una excepcin y establece el mensaje asociado al segundo argumento y el trazado de pila para el tercer argumento. Normalmente, el primer argumento ser el nombre de una clase de la jerarqua Exception o una referencia a un objeto instancia de una de estas clases (Tcnicamente, este argumento puede ser cualquier objeto que responde al mensaje exception mediante la devolucin de un objeto, de tal manaera como object.kind_of?(Exception) es verdadero). El trazado de pila se produce normalmente mediante el mtodo Kernel.caller. Aqu algunos ejemplos tpicos de raise en accin.

raise raise Missing name if name.nil? if i >= names.size raise IndexError, #{i} >= size (#{names.size}) end raise ArgumentError, Name too big, caller En el ltimo ejemplo, se elimina la rutina actual de la traza de pila, que a menudo es til en los mdulos de librera. Podemos ir ms lejos: el siguiente cdigo elimina dos rutinas de la traza, pasando slo un subconjunto de llamadas de la pila a la nueva excepcin. raise ArgumentError, Name too big, caller[1..1]

Aadiendo Informacin a las Excepciones


Puede definir sus propias excepciones para que contengan cualquier informacin que usted necesite pasar desde el lugar del error. Por ejemplo, ciertos tipos de errores de red puede ser transitorios en funcin de las circunstancias. Si se produce un error y las circunstancias lo permiten, se puede establecer un indicador (flag) en la excepcin, para decirle al controlador que puede valer la pena volver a intentar la operacin. class RetryException < RuntimeError attr :ok_to_retry def initialize(ok_to_retry) @ok_to_retry = ok_to_retry

72

end end En algn lugar en lo ms profundo del cdigo, se produce un error transitorio.

def read_data(socket) data = socket.read(512) if data.nil? raise RetryException.new(true), error de lectura transitorio end # .. normal processing end Ms arriba en la pila de llamadas, controlamos la excepcin.

begin stuff = read_data(socket) # .. process stuff rescue RetryException => detail retry if detail.ok_to_retry raise end

Catch y Throw
Mientras que los mecanismos de excepciones raise y rescue son muy buenos para abandonar la ejecucin cuando las cosas van mal, a veces tambin es bueno ser capaz de saltar en una construccin profundamente anidada durante el procesamiento normal. Aqu es donde catch y throw son muy tiles. catch (:done) do while line = gets throw :done unless fields = line.split(/\t/) songlist.add(Song.new(*fields)) end songlist.play end catch define un bloque que se etiqueta con el nombre dado (que puede ser un Symbol o un String). El bloque se ejecuta con normalidad hasta que se encuentra un throw. Cuando Ruby encuentra un throw, tira hacia atrs en la pila de llamadas en busca de un bloque catch con el smbolo correspondiente. Cuando lo encuentra, Ruby despliega la pila a ese punto y termina el bloque. Por lo tanto, en el ejemplo anterior, si la entrada no contiene correctamente el formato de lineas, throw salta al final del correspondiente catch, no slo termina el bucle while, sino tambin salta en el repertorio de la lista de canciones. Si a throw se le llama con el segundo parmetro opcional, este valor se retorna como el valor de catch. El siguiente ejemplo utiliza un throw para terminar la interaccin con el usuario si se escribe ! en respuesta a cualquier aviso (prompt). def prompt_and_get(prompt) print prompt res = readline.chomp throw :quit_requested if res == ! res end catch :quit_requested do name = prompt_and_get(Name: ) age = prompt_and_get(Age: ) sex = prompt_and_get(Sex: )

73

# ... # process information end Como ilustra este ejemplo, throw no tiene que aparecer en el mbito esttico de catch.

Modulos
Los mdulos son una forma de agrupar mtodos, clases y constantes. Los mdulos le darn dos ventajas importantes. 1. Los mdulos proporcionan un espacio de nombres y evitan conflictos de nombre. 2. Los mdulos implementan la facilidad mixim.

Los Espacios de Nombres


Al empezar a escribir programas ms y ms grandes de Ruby, se encontrar, naturalmente, la produccin de trozos de cdigo reutilizables --libreras de rutinas relacionadas que son de aplicacin general. Usted querr separar este cdigo en archivos separados para que los contenidos puedan ser compartidos entre los diferentes programas de Ruby. A menudo, este cdigo se organiza en clases, as que probablemente se quedar con una clase (o un conjunto de clases relacionadas entre s) en un archivo. Sin embargo, hay ocasiones en las que desear agrupar algunas cosas que, naturalmente, no forman una clase. Una primera aproximacin puede ser la de poner todas estas cosas en un archivo y simplemente cargar ese archivo en cualquier programa que lo necesite. Esta es la forma en la que el lenguaje C funciona. Sin embargo, este enfoque tiene un problema. Digamos que hay que escribir un conjunto de funciones de trigonometra sin, cos, etc. Se meten todos en un archivo, trig.rb para las generaciones futuras y a disfrutar. Mientras tanto, Sally est trabajando en una simulacin del bien y del mal y en los cdigos de un conjunto de sus propias rutinas tiles, incluyendo be_good (ser_bueno) y sin (pecado), y las pone en moral.rb. Joe, que quiere escribir un programa para saber cuntos ngeles pueden bailar en la cabeza de un alfiler, carga tanto trig.rb como moral.rb en su programa. Pero ambos definen un mtodo llamado sin. Malas noticias. La respuesta es el mecanismo de mdulo. Los mdulos de definen un espacio de nombres ( namespace ), una caja en la que sus mtodos y constantes pueden correr sin tener que preocuparse de ser pisados por otros mtodos y constantes. Las funciones trigonomtricas pueden ir en un mdulo: module Trig PI = 3.141592654 def Trig.sin(x) # .. end def Trig.cos(x) # .. end end y los mtodos sobre la buena y la mala moral pueden ir en otro.

module Moral VERY_BAD = 0 BAD = 1 def Moral.sin(badness) # ... end end

74

Las constantes de mdulo se designan como constantes de clase, con una letra mayscula inicial. Las definiciones de mtodo es similar: los mtodos del mdulo se definen como mtodos de clase. Si un programs de terceros quiere usar estos mdulos, simplemente puede cargar los dos archivos (utilizando la declaracin Ruby require, que se discute ms adelante) y referenciar a los nombres cualificados. require trig require moral y = Trig.sin(Trig::PI/4) wrongdoing = Moral.sin(Moral::VERY_BAD) Al igual que con los mtodos de clase, llamar a un mtodo de mdulo precediendo su nombre con el nombre del mdulo y un punto, y se hace referencia a una constante con el nombre de mdulo y dos signos de dos puntos.

Mixins
Los mdulos tienen otro uso extraordinario. De un golpe prcticamente eliminan la necesidad de herencia mltiple proporcionando un servicio llamado mixin. En los ejemplos de la seccin anterior, hemos definido los mtodos de mdulo, mtodos cuyos nombres fueron precedidos por el nombre del mdulo. Si esto le hizo pensar en los mtodos de clase, su siguiente pensamiento podra ser qu pasa si yo defino los mtodos de instancia dentro de un mdulo? Buena pregunta. Un mdulo no puede tener instancias, ya que no es una clase. Sin embargo, puede incluir un mdulo dentro de una definicin de clase. Cuando esto sucede, todos los mtodos de instancia del mdulo estn de pronto disponibles como mtodos de la clase tambin. Se mezclan (mixed in). De hecho, el mezclado en los mdulos efectivamente hace que se comporten como superclases. module Debug def who_am_i? #{self.class.name} (\##{self.id}): #{self.to_s} end end class Phonograph include Debug # ... end class EightTrack include Debug # ... end ph = Phonograph.new(West End Blues) et = EightTrack.new(Surrealistic Pillow) ph.who_am_i? et.who_am_i? -> -> Phonograph (#935520): West End Blues EightTrack (#935500): Surrealistic Pillow

Al incluir el mdulo Debug, tanto Phonograph como EightTrack ganan el acceso al mtodo de instancia who_am_i? . Vamos a ver un par de puntos acerca de la instruccin include antes de continuar. En primer lugar, no tiene nada que ver con los archivos. Los programadores de C utilizan la directiva de preprocesador llamada #include para insertar el contenido de un archivo en otro durante la compilacin. El include de Ruby simplemente hace referencia a un nombre de mdulo. Si este mdulo se encuentra en un archivo separado, deber utilizar require (o su primo menos usado, load) para rastrear el archivo antes de utilizar include. En segundo lugar, un include de Ruby no slo tiene que copiar los mtodos del mdulo de instancia en la clase. Por el contrario, hace una referencia desde la clase al mdulo incluido. Si varias clases incluyen el mdulo, todos ellas apuntan al mismo. Si cambia la definicin de un mtodo dentro de

75

un mdulo, incluso cuando el programa se est ejecutando, en todas las clases que incluyan a este mdulo se presentan el nuevo comportamiento (Por supuesto, aqu estamos hablando slo de los mtodos. Las variables de instancia son siempre por objeto, por ejemplo). Los mixins le dan una forma extraordinariamente controlada de aadir funcionalidad a las clases. Sin embargo, su verdadero poder viene cuando el cdigo en el mixin empieza a interactuar con el cdigo de la clase que lo utiliza. Tomemos el mixin estndar de Ruby Comparable como ejemplo. Usted puede utilizar el mixin Comparable para aadir los operadores de comparacin ( <, <=, ==,>=, y >), as como el mtodo between?, en una clase. Para que esto funcione, Comparable asume que cualquier clase que le utilice define el operador <=>. Por lo tanto, como escritor de clase, se define un mtodo <=>, incluyendo Comparable y se obtienen seis funciones de comparacin de forma gratuita. Vamos a probar esto con nuestra clase Song, para hacer las canciones comparables en funcin de su duracin. Todo lo que tenemos que hacer es incluir el mdulo Comparable y poner en prctica el operador de comparacin <=>. class Song include Comparable def initialize(name, artist, duration) @name = name @artist = artist @duration = duration end def <=>(other) self.duration <=> other.duration end end Podemos comprobar que los resultados son sensibles con una prueba de algunas canciones.

song1 = Song.new(My Way, Sinatra, 225) song2 = Song.new(Bicylops, Fleck, 260) song1 song1 song1 song1 <=> song2 < song2 == song1 > song2 -> -> -> -> 1 true true false

Iteradores y el Mdulo Enumerable


Usted probablemente haya notado que las clases de la coleccin de Ruby soportan un gran nmero de operaciones que permiten hacer varias cosas con ella: recorrerla, ordenarla, etc. Usted puede estar pensando: Vaya, seguro que estara bien si mi clase puede soportar todas estas interesantes caractersticas, tambin! (Si usted realmente lo cree, probablemente es tiempo de dejar de ver las repeticiones la televisin de la dcada de 1960). Bueno, las clases pueden soportar todas estas interesantes caractersticas, gracias a la magia de mixins y el mdulo Enumerable. Todo lo que tiene que hacer es escribir un iterador llamando a each, que devuelva los elementos de su coleccin por turno. Mezclar en Enumerable, y de repente su clase admite cosas como mapa, include? y find_all?. Si los objetos de su coleccin implementan semntica significativa ordenando el uso del mtodo <=>, tambin obtendr mtodos como min, max y short.

Composicin de Mdulos
Anteriormente vimos el mtodo inject de Enumerable. Enumerable es otro mixin estndar, que implementa una serie de mtodos en cuanto a la acogida de clase del mtodo each. Debido a esto, podemos usar inject en cualquier clase que incluya el mdulo Enumerable y defina el mtodo each. Muchas clases integradas hacen esto. [ 1, 2, 3, 4, 5 ].inject {|v,n| v+n } ( a..m).inject {|v,n| v+n } -> -> 76 15 abcdefghijklm

Tambin podramos definir nuestra propia clase que mezcle en Enumerable y por lo tanto se soporte inject. class VowelFinder include Enumerable def initialize(string) @string = string end def each @string.scan(/[aeiou]/) do |vowel| yield vowel end end end vf = VowelFinder.new(the quick brown fox jumped) vf.inject {|v,n| v+n } -> euiooue

Observe que hemos usado el mismo patrn en la llamada a inject en estos ejemplos --lo estamos utilizando para realizar una sumatoria. Cuando se aplica a los nmeros, devuelve la suma aritmtica, cuando se aplica a las cadenas las concatena. Tambin se puede utilizar un mdulo para encapsular esta funcionalidad. module Summable def sum inject {|v,n| v+n } end end class Array include Summable end class Range include Summable end class VowelFinder include Summable end [ 1, 2, 3, 4, 5 ].sum ( a..m).sum -> -> 15 abcdefghijklm

vf = VowelFinder.new(the quick brown fox jumped) vf.sum -> euiooue

Variables de Instancia en Mixins


Gente que viene a Ruby de C++ nos preguntan con frecuencia: Qu sucede con las variables de instancia en un mixin? En C++, tengo que saltar a travs de unos aros para controlar cmo se comparten las variables en una jerarqua de herencias mltiples. Cmo maneja esto Ruby? Bueno, para empezar, les decimos que no es una buena pregunta. Recuerda cmo trabajan las variables de instancia en Ruby: la primera mencin a una variable prefijada con @ crea la variable de instancia en el objeto actual, self. Para un mixin, esto significa que el mdulo se mezcla en su clase cliente (el mixee?) pudiendo crear variables de instancia en el objeto cliente y pudiendo utilizar attr_reader y amigos para definir el acceso para estas variables de instancia. Por ejemplo, el mdulo Observable en el ejemplo siguiente agrega una variable de instancia @observer_list a cualquier clase que se incluya.

77

module Observable def observers @observer_list ||= [] end def add_observer(obj) observers << obj end def notify_observers observers.each {|o| o.update } end end Sin embargo, este comportamiento nos expone a un riesgo. Una variable de instancia mixin puede entrar en conflicto con las de la clase de acogida o con las de otros mixins. El ejemplo siguiente muestra una clase que utiliza nuestro mdulo Obsevable, pero que desgraciadamente tambin utiliza una variable de instancia llamada @observer_list. En tiempo de ejecucin, este programa va a salir mal de algunas formas difciles de diagnosticar. class TelescopeScheduler # otras clases pueden registrarse para recibir # notificaciones cuando cambia el horario include Observable def initialize @observer_list = [] # personas con tiempo de telescopio end def add_viewer(viewer) @observer_list << viewer end # ... end En su mayor parte, los mdulos mixin no trata de llevar los datos a su propia instancia --hace uso de mtodos de acceso para recuperar los datos del objeto cliente. Pero si usted necesita crear un mixin que tiene que tener su propio estado, asegrese de que las variables de instancia tienen nombres nicos para distinguirlas de cualquier otro mixin en el sistema (tal vez utilizando el nombre del mdulo como parte del nombre de la variable). Alternativamente, el mdulo podra utilizar un hash a nivel de mdulo, indexado por el identificador (ID) de objeto actual para almacenar datos especficos de la instancia, sin necesidad de utilizar las variables de instancia Ruby. module Test State = {} def state=(value) State[id] = value end def state State[id] end end class Client include Test end c1 = Client.new c2 = Client.new c1.state = cat c2.state = dog c1.state -> cat c2.state -> dog

78

Resolucin de Nombres de Mtodo Ambiguos


Una de las otras preguntas la gente hace acerca de los mixins es, cmo se maneja el mtodo de bsqueda? En particular, qu sucede si mtodos con el mismo nombre se definen en una clase, en la clase del padre de la clase y en un mixin incluido en la clase? La respuesta es que Ruby mira primero en la clase inmediata de un objeto, en los mixins incluidos en esa clase y despus de las superclases y sus mixins. Si una clase tiene varios mdulos mixtos en ella, el ltimo incluido se busca en primer lugar.

Incluyendo Otros Archivos


Debido a que Ruby hace que sea fcil escribir buen cdigo modular, a menudo se encontrar produciendo un pequeo archivo que contiene alguna funcionalidad auto-contenida --una interfaz de x, un algoritmo, etc. Lo normal es que organizar estos archivos como libreras de clase o mdulo. Despus de haber producido estos archivos, usted desear incorporarlos a sus nuevos programas. Ruby tiene dos instrucciones que hacen esto. El mtodo load incluye el llamado archivo cdigo fuente Ruby cada vez que se ejecuta el mtodo. load filename.rb El ms comnmente utilizado mtodo require carga cualquier archivo dado una sola vez.

require filename Esto no es estrictamente cierto. Ruby mantiene una lista de los archivos cargados por require en el array $. Sin embargo, esta lista contiene slo los nombres de los archivos tal como se le da a require. Es posible engaar a Ruby y obtener el mismo archivo cargado dos veces. require /usr/lib/ruby/1.9/English.rb require /usr/lib/ruby/1.9/rdoc/../English.rb $ [/usr/lib/ruby/1.9/English.rb, /usr/lib/ruby/1.9/rdoc/../English.rb] En este caso, ambas declaraciones require terminan sealando el mismo archivo, pero utilizan diferentes caminos para cargarlo. Algunos consideran esto un error y este comportamiento tambin puede cambiar en versiones posteriores. Las variables locales en un archivo cargado o requerido no se propagan con el alcance de quien las carga o requiere. Por ejemplo, aqu hay un archivo llamado included.rb. a = 1 def b 2 end Y esto es lo que sucede cuando la incluimos en otro archivo:

a = cat b = dog require included a b b() -> -> -> cat dog 2

require tiene una funcionalidad adicional: puede cargar las libreras binarias compartidas. Las rutinas aceptan rutas absolutas y relativas. Si se les da una ruta relativa (o slo un nombre comn), van a buscar en todos los directorios en la ruta de carga en curso ($:, se ver ms adelante) para el archivo. 79

Los archivos cargados con load o require pueden, por supuesto, incluir otros archivos, que incluyan otros archivos, y as sucesivamente. Lo que no puede ser obvio es que require es una sentencia ejecutable --que puede estar dentro de una sentencia if, o puede incluir una cadena que acaba de ser construida. La ruta de bsqueda se puede modificar en tiempo de ejecucin. Slo tiene que aadir el directorio que desea para el array $:. Ya que load incluir los fuentes de forma incondicional, se puede utilizar para volver a cargar un archivo fuente que puede haber cambiado desde que comenz el programa. El ejemplo que sigue es (muy) artificial. 5.times do |i| File.open(temp.rb,w) do |f| f.puts module Temp f.puts def Temp.var f.puts #{i} f.puts end f.puts end end load temp.rb puts Temp.var end produce: 0 1 2 3 4 Para un uso menos artificial de esta funcionalidad, considere una aplicacin Web que recarga los componentes mientras est funcionando. Esto le permite actualizarse a s misma sobre la marcha y no necesita ser reiniciada para que la nueva versin del software se integre. Esta es una de las muchas ventajas de usar un lenguaje dinmico como Ruby.

Entrada y Salida Bsica


Ruby proporciona lo que a primera vista se ve como dos grupos separados de rutinas de I/O. La primera es la interfaz sencilla --la hemos estado usando casi exclusivamente hasta el momento. print Enter your name: name = gets Hay todo un conjunto de mtodos relacionados con I/O que se implementan en el mdulo Kernel --gets, open, print, printf, putc, puts, readline, readlines, y test --que hacen que sea sencillo y cmodo escribir sencillos programas en Ruby. Estos mtodos suelen hacer I/O a la entrada y la salida estndares, que los hace tiles para la escritura de filtros. Usted los encontrar documentados ms adelante. La segunda forma, que le da mucho ms control, es el uso de objetos IO.

Qu es un Objeto IO?
Ruby define una clase base nica, IO, para manejar la entrada y salida. Esta clase base es una subclase de las clases File y BasicSocket para proporcionar un comportamiento ms especializado, pero los principios son los mismos. Un objeto IO es un canal bidireccional entre un programa de Ruby y otros recursos externos. Para aquellos que slo tienen que saber los detalles de implementacin, esto significa que un solo objeto IO a veces puede hacer la gestin de ms de un descriptor de fichero del sistema operativo.

80

Por ejemplo,si abre un par de pipes (tubos), un solo objeto IO contiene un pipe de lectura y un pipe de escritura. Un objeto IO puede tener ms de lo que parece a simple vista, pero al final, simplemente escriba en l y lea de l. En este captulo, nos concentraremos en la clase IO y su subclase ms comnmente utilizada, la clase File. Ms detalles sobre el uso de las clases tipo socket para trabajo en red, se vern ms adelante.

Abrir y Cerrar Ficheros


Como cabe esperar, se puede crear un objeto fichero nuevo utilizando File.new.

file = File.new(testfile, r) # ... process the file file.close Usted puede crear un objeto archivo que se abre para lectura, escritura, o ambas, de acuerdo con la cadena de modo. (Aqu abrimos testfile para la lectura con una r. Tambin podra haber usado w para escribir o rw para lectura y escritura. La lista completa de modos permitidos aparece ms adelante). Opcionalmente, cuando se crea un archivo tambin puede especificar los permisos de archivo. Consulte la descripcin de File.new para ms detalles. Despus de abrir el archivo, podemos trabajar con el y escribir y/o leer datos segn sea necesario. Por ltimo, como ciudadanos responsables del software, cerramos el archivo, lo que garantiza que todos los datos en el bfer se han escrito y que todos los recursos relacionados son liberados. Ruby puede hacer la vida un poco ms fcil para usted. El mtodo File.open tambin abre un archivo . En uso normal se comporta como File.new. Sin embargo, si con la llamada est asociado un bloque, open se comporta de diferente manera. En lugar de devolver un nuevo objeto File, se invoca al bloque, pasandole el archivo recin abierto como un parmetro. Cuando se sale del bloque, el archivo se cierra automticamente. File.open(testfile, r) do |file| # ... process the file end Este segundo enfoque tiene un beneficio aadido. En el caso anterior, si al procesar el archivo se lanza una excepcin, no puede llevarse a cabo la llamada a file.close. Una vez que la variable de archivo est fuera de mbito, entonces la recoleccin de basura eventualmente lo cierre, pero esto no puede suceder durante un tiempo. Mientras tanto, los recursos estn abiertos. Esto no sucede con la forma de bloque de File.open. Si se lanza una excepcin dentro del bloque, el archivo se cierra antes de que la excepcin se propague al llamador. El mtodo abierto se parece a lo siguiente. class File def File.open(*args) result = f = File.new(*args) if block_given? begin result = yield f ensure f.close end end return result end end

81

Lectura y Escritura de Ficheros


Los mismos mtodos que se usan para la I/O simple estn disponibles para todos los objetos fichero. Por lo tanto, gets lee una lnea de la entrada estandar (o de cualquier fichero espcificado en la lnea de comandos cuando se invoca el script), y file.gets lee una lnea del objeto fichero file. Por ejemplo, se puede crear un programa llamado copy.rb

while line = gets puts line end Si se ejecuta este programa sin argumentos, lee lneas desde la consola y copia de vuelta a la misma. Tenga en cuenta que se repite cada lnea, una vez que se pulsa la tecla return (en este ejemplo y el siguiente, se muestra la entrada de usuario en cursiva). % ruby copy.rb These are lines These are lines that I am typing that I am typing ^D Tambin puede pasar uno o ms nombres de archivo en la lnea de comandos, en cuyo caso gets leer cada uno por turno. % ruby copy.rb testfile This is line one This is line two This is line three And so on... Finalmente, se puede abrir el archivo y leer de l explcitamente. File.open(testfile) do |file| while line = file.gets puts line end end produce: This is line one This is line two This is line three And so on... As como con gets, los objetos I/O disfrutan de un conjunto adicional de mtodos de acceso, todo con la intencin de hacernos la vida ms fcil.

Iteradores para Lectura


Igual que se usan bucles comunes para leer datos de un flujo IO, tambin se pueden usar varios iteradores Ruby. IO#each_byte invoca un bloque con el siguiente byte de 8 bits de un objeto IO (en este caso, un objeto de tipo File). File.open(testfile) do |file| file.each_byte {|ch| putc ch; print . } end

82

produce: T.h.i.s. .i.s. .l.i.n.e. .o.n.e. .T.h.i.s. .i.s. .l.i.n.e. .t.w.o. .T.h.i.s. .i.s. .l.i.n.e. .t.h.r.e.e. .A.n.d. .s.o. .o.n....... . IO#each_line llama al bloque con cada lnea del archivo. En el siguiente ejemplo, vamos a hacer visibles los saltos de lnea originales usando String#dump, para que se pueda ver que no estamos engaando. File.open(testfile) do |file| file.each_line {|line| puts Got #{line.dump} } end produce: Got Got Got Got This is line one\n This is line two\n This is line three\n And so on...\n

Se puede pasar a each_line cualquier secuencia de caracteres, como por ejemplo un separador de lnea, y separar la entrada en consecuencia, retornando el fin de lnea al final de cada lnea de datos. Es por esto que usted ve los caracteres \n en la salida del ejemplo anterior. En el siguiente ejemplo, vamos a utilizar el carcter e como separador de lnea. File.open(testfile) do |file| file.each_line(e) {|line| puts Got #{ line.dump } } end produce: Got Got Got Got Got Got Got This is line one \nThis is line two\nThis is line thre e \nAnd so on...\n

Si se combina la idea de un iterador con la caracterstica de bloque autoclosing, se obtiene IO.foreach. Este mtodo toma el nombre de una fuente de I/O abrindola para lectura, llama al iterador una vez por cada lnea del archivo y luego cierra automticamente el archivo. IO.foreach(testfile) {|line| puts line } produce: This is line one This is line two This is line three And so on... O si lo prefiere, puede recuperar un archivo entero en una cadena o en una matriz de lneas.

# lectura en una cadena str = IO.read(testfile) str.length -> 66 str[0, 30] -> This is line one\nThis is line

83

# lectura en una matriz arr = IO.readlines(testfile) arr.length -> 4 arr[0] -> This is line one\n No hay que olvidar que las operaciones de I/O nunca son seguras en un mundo incierto. Se lanazarn excepciones en la mayora de errores que puedan ocurrir y usted debe estar dispuesto a rescatarlos y tomar las medidas adecuadas.

Escritura en Ficheros
Hasta ahora, hemos estado llamando a puts y print alegremente, pasndoles cualquier objeto antiguo y confiando en que Ruby va a hacer lo correcto (que por supuesto lo hace). Pero, qu es exactamente lo que hace? La respuesta es bastante simple. Salvo un par de excepciones, todos los objetos que se pasan a puts y print se convierten en una cadena llamando al mtodo to_s de ese objeto. Si por alguna razn, el mtodo to_s no devuelve una cadena vlida, se crea una que contiene el nombre de clase del objeto y el ID, algo as como #<ClassName:0x123456>. Las excepciones son simples, quizs demasiado. El objeto nil que imprime la cadena nil, y una matriz pasada a puts se ecriben como si cada uno de sus elementos por turno fueran pasados separadamente a puts. Qu pasa si usted quiere escribir datos binarios y Ruby no quiere lidiar con ellos? Bueno, por lo general, simplemente se puede utilizar IO#print y pasarle una cadena que contiene los bytes a escribir. Sin embargo, si realmente quiere, puede llegar a las rutinas de entrada y salida de bajo nivel --eche un vistazo a la documentacin de IO#sysread y IO#syswrite. Y cmo obtener los datos binarios en una cadena en primer lugar? Las tres formas ms comunes son usar un literal, empujar byte a byte, o usar Array#pack. str1 str2 str2 [ 1, = \001\002\003 = << 1 << 2 << 3 2, 3 ].pack(c*) -> \001\002\003 -> -> \001\002\003 \001\002\003

Pero echo de menos mi C++ iostream


A veces simplemente no hay nada que decir sobre gustos... Sin embargo, tal como se puede aadir un objeto a una matriz con el operador <<, tambin se puede aadir un objeto a un flujo de salida IO. endl = \n STDOUT << 99 << red balloons << endl produce: 99 red balloons Una vez ms, el mtodo << utiliza to_s para convertir sus argumentos a cadenas antes de enviarlos a su alegre camino. A pesar de que empezamos despectivos sobre el pobre operador <<, en realidad hay algunas buenas razones para usarlo. Debido a que otras clases (como String y Array) tambin implementan un operador << con semntica similar, muy a menudo se puede escribir cdigo que aade a algo, utilizando << sin importar si se aade a una matriz, a un archivo o a una cadena. Esta flexibilidad tambin hace fciles las pruebas de unidad.

84

Hacer I/O con cadenas


A menudo hay momentos en los que se necesita trabajar con el cdigo que se supone que se est leyendo o escribiendo en uno o ms archivos. Pero hay un problema: los datos no se encuentran en los archivos. Quizs estn disponibles a travs de un servicio SOAP, o se les han pasado como parmetros de lnea de comandos. O tal vez se est ejecutando pruebas de unidad y no desea modificar el sistema de archivos real. Introduzca objetos StringIO. Se comportan igual que otros objetos I/O, pero leen y escriben cadenas, no archivos. Si se abre un objeto StringIO para lectura, le proporciona una cadena. Luego, todas las operaciones de lectura en el objeto StringIO leen de esta cadena. Del mismo modo, cuando se quiere escribir en un objeto StringIO, se le pasa una cadena. require stringio ip = StringIO.new(now is\nthe time\nto learn\nRuby!) op = StringIO.new(, w) ip.each_line do |line| op.puts line.reverse end op.string -> \nsi won\n\nemit eht\n\nnrael ot\n!ybuR\n

Hablando con redes


Ruby es fludo en la mayora de los protocolos de Internet, tanto de bajo como de alto nivel. Para quienes gustan deslizarse en torno al nivel de red, Ruby viene con un conjunto de clases en la biblioteca socket (documentada ms delante). Estas clases le dan acceso a TCP, UDP, SOCKS y sockets de dominio Unix, as como culaquier tipo de socket adicional compatible con su arquitectura. La librera tambin ofrece clases de ayuda para hacer la escritura a los servidores ms fcil. He aqu un sencillo programa que obtiene informacin sobre el usuario mysql en nuestra mquina local utilizando el protocolo finger. require socket client = TCPSocket.open(127.0.0.1, finger) client.send(mysql\n, 0) # 0 means standard packet puts client.readlines client.close produce: Login: mysql Name: MySQL Server Directory: /var/empty Shell: /usr/bin/false Never logged in. No Mail. No Plan. En un nivel superior, el conjunto lib/net de los mdulos de librera proporciona los controladores para un conjunto de protocolos de nivel de aplicacin (actualmente FTP, HTTP, POP, SMTP y telnet). Estos estn documentados ms adelante. Por ejemplo, el programa siguiente lista las imgenes que se muestran en la pgina home del Programador Pragmtico. require net/http h = Net::HTTP.new(www.pragmaticprogrammer.com, 80) response = h.get(/index.html, nil) if response.message == OK puts response.body.scan(/<img src=(.*?)/m).uniq end

85

produce: images/title_main.gif images/dot.gif /images/Bookshelf_1.5_in_green.png images/sk_all_small.jpg images/new.jpg Aunque atractivamente simple, este ejemplo se podra mejorar de manera significativa. En particular, no hace mucho en la forma del manejo de errores. En realidad, debe informar de los errores Not Found (en los famosos 404), y debe manejar redirecciones (que ocurren cuando un servidor web da al cliente una direccin alternativa para la pgina solicitada). Podemos llevar esto a un nivel an ms alto. Al traer la librera open-uri a un programa, de repente el mtodo Kernel.open reconoce http:// y ftp:// URLs en el nombre del archivo. No slo eso, sino que tambin maneja las redirecciones automaticamente. require open-uri open(http://www.pragmaticprogrammer.com) do |f| puts f.read.scan(/<img src=(.*?)/m).uniq end produce: images/title_main.gif images/dot.gif /images/Bookshelf_1.5_in_green.png images/sk_all_small.jpg images/new.jpg

Hilos y Procesos
Ruby le ofrece dos formas bsicas para organizar el programa de modo que se puedan ejecutar diferentes partes del mismo al mismo tiempo. Se pueden dividir las tareas de cooperacin dentro del programa utilizando varios hilos, o puede dividir las tareas entre diferentes programas utilizando mltiples procesos. Echemos un vistazo a cada uno de estos.

Multithreading
A menudo, la forma ms sencilla de hacer dos cosas a la vez es mediante el uso de hilos Ruby. Estos son totalmente integrados en proceso, implementados con el intrprete de Ruby, lo que los hace totalmente portables, no basndose en el sistema operativo. Al mismo tiempo, no se obtienen ciertos beneficios de tener soporte nativo para subprocesos. Qu significa esto? Se puede experimentar falta de subprocesos (donde un subproceso de prioridad baja no tiene la oportunidad de correr). Si logra obtener el punto muerto de sus hilos, todo el conjunto de procesos puede paralizarse y si hay algn hilo que hizo una llamada al sistema operativo que lleva mucho tiempo en completarse, todos los hilos se paran hasta que el intrprete toma el control de nuevo. Por ltimo, si su mquina tiene ms de un procesador, los hilos de Ruby no se aprovechan de ello --ya que se ejecutan en un nico proceso y en un nico hilo nativo, y estn obligados a ejecutarse en un procesador a la vez. Todo esto suena horrible. En la prctica, sin embargo, en muchos casos los beneficios de utilizar los hilos son muy superiores a los posibles problemas que puedan ocurrir. Utilizar los hilos Ruby es una manera eficiente y ligera de conseguir el paralelismo en el cdigo. Slo tiene que comprender las cuestiones de aplicacin subyacentes y hacer el diseo en consecuencia.

Crear Subprocesos Ruby


Crear un nuevo subproceso o hilo es bastante sencillo. El cdigo siguiente es un ejemplo sencillo. Es capaz de descargar un conjunto de pginas Web en paralelo. Para cada direccin URL que se le pide que descargue, el cdigo crea un subproceso independiente que se encarga de la transaccin HTTP.

86

require net/http pages = %w( www.rubycentral.com slashdot.org www.google.com ) threads = [] for page_to_fetch in pages threads << Thread.new(page_to_fetch) do |url| h = Net::HTTP.new(url, 80) puts Fetching: #{url} resp = h.get(/, nil ) puts Got #{url}: #{resp.message} end end threads.each {|thr| thr.join } produce: Fetching: www.rubycentral.com Fetching: slashdot.org Fetching: www.google.com Got www.google.com: OK Got www.rubycentral.com: OK Got slashdot.org: OK Echemos un vistazo con ms detalle a este cdigo para ver como ocurren algunas sutiles cosas.

Con la llamada Thread.new se crean nuevos hilos. Se le pasa a un bloque que contiene el cdigo que se ejecutar en un nuevo hilo. En nuestro caso, al bloque que utiliza la librera net/http en busca de la primera pgina de cada uno de los sitios indicados. Nuestra bsqueda muestra claramente que las descargas se llevan a cabo en paralelo. Cuando creamos el hilo, se pasa la direccin URL requerida como un parmetro, y ste, se pasa al bloque como url. Por qu hacemos esto en lugar de simplemente utilizar el valor de la variable page_ to_fetch dentro del bloque? Un hilo comparte todas las instancias globales y las variables locales que se encuentran en existencia en el momento del comienzo del hilo. Como puede decir cualquiera con un hermano pequeo, el compartir no es siempre una buena cosa. En este caso, los tres hilos comparten la variable page_to_fetch. Se inicia el primer hilo y page_to_fetch se ajusta a www.rubycentral.com. Mientras tanto, an est en marcha el bucle para creacin de los hilos. En un segundo momento, page_to_fetch se ajusta a slashdot.org. Si el primer hilo an no ha terminado de usar la variable page_to_fetch, de pronto se comenzar a usar este nuevo valor. Este tipo de errores son difciles de localizar. Sin embargo, las variables locales creadas en un bloque de un hilo son realmente locales para ese hilo, --cada hilo tiene su propia copia de estas variables. En nuestro caso, la URL de la variable se establecer en el momento de crear el hilo y cada hilo tendr su propia copia de la direccin de pgina. Puede pasar cualquier nmero de argumentos en el bloque a travs de Thread.new.

Manipulacin de Hilos
Se produce otra sutileza en la ltima lnea en nuestro programa de descarga. Por qu llamamos a join en cada uno de los hilos que hemos creado? Cuando un programa de Ruby termina, todas los hilos han muertos, independientemente de sus estados. Sin embargo, usted puede esperar a que termine un hilo en particular llamando para el mismo al mtodo Thread#join. La llamada a subproceso se bloquear hasta que el subproceso dado haya terminado. Llamando a join praa cada uno de los hilos, puede asegurarse de que las tres solicitudes se completan antes de terminar el programa principal. Si no se desea bloquear indefinidamente, se le puede dar a join un tiempo de espera como parmetro, --si el tiempo de espera expira antes de que termine el subproceso, la llamada a join retorna nil. Otra variante de join, el mtodo Thread#value, retorna el valor de la ltima instruccin ejecutada por el hilo.

87

Adems de join, se utilizan algunas otras rutinas para manipular los hilos. El flujo en curso siempre es accesible a travs de Thread.current. Puede obtener una lista de todos los hilos con Thread.list, que devuelve una lista con todos los objetos Thread que son ejecutables o que se pueden detener. Para determinar el estado de un hilo en particular, puede utilizar Thread#status y Thread#alive?. Adems, puede ajustar la prioridad de un hilo con Thread#priority=. Los hilos con mayor prioridad se ejecutarn antes que los de menor. Hablaremos ms sobre la programacin de subprocesos y la detencin e inicio de los mismos, en un momento.

Variables de Hilo

Un hilo normalmente puede tener acceso a las variables del mbito cuando se crea el hilo. Las variables locales al bloque que contiene el cdigo del hilo son locales al hilo y no se comparten. Pero lo que si es necesario es que variables de hilos que puedan ser accedidas por otros hilos --incluyendo el hilo main? La clase Thread cuenta con un mecanismo especial que permite que las variables de subproceso local se creen y se accedan por su nombre. Slo tiene que tratar el objeto hilo, como si se tratara de un hash, escribiendo los elementos utilizando []= y leyendo de nuevo con []. En el ejemplo que sigue, cada hilo registra el valor actual de la variable count en una variable de subproceso local con la clave mycount. Para ello, el cdigo utiliza la cadena mycount al indexar los objetos hilo. (En este cdigo existe una condicin de carrera, pero sin embargo, no hemos hablado acerca de la sincronizacin y tendremos que ignorarlo tranquilamente por el momento. Simplemente decir, que se produce una condicin de carrera, cuando dos o ms piezas de cdigo (o hardware) tratan de acceder a algn recurso compartido y cambian los resultados en funcin del orden en que lo hacen. En este ejemplo, es posible para un hilo establecer el valor de su variable mycount a count, pero antes de que llegue la oportunidad de incremento para count, el hilo se desincronice y otro subproceso vuelve a utilizar el mismo valor de count. Estas cuestiones se fijan mediante la sincronizacin del acceso a los recursos compartidos --tal como la variable count). count = 0 threads = [] 10.times do |i| threads[i] = Thread.new do sleep(rand(0.1)) Thread.current[mycount] = count count += 1 end end threads.each {|t| t.join; print t[mycount], , } puts count = #{count} produce: 4, 1, 0, 8, 7, 9, 5, 6, 3, 2, count = 10 El hilo principal espera a que los hilos secundarios terminen y luego imprime el valor de count capturado por cada uno. Slo para hacerlo ms interesante, tenemos a cada hilo esperando un tiempo aleatorio antes de registrar el valor.

Hilos y Excepciones
Qu sucede si un hilo produce una excepcin no controlada? Depende de la configuracin de la bandera abort_on_exception y la de la bandera debug del intrprete (que se documentarn ms adelante). Si abort_on_exception es false y la bandera de depuracin no est habilitada (configuracin por defecto), una excepcin incontrolada simplemente mata a hilo en curso, --todo el resto seguir ejecutndose. De hecho, ni siquiera oir hablar de la excepcin hasta que se emita un join en el hilo que la ha lanzado. 88

En el siguiente ejemplo, el hilo 2 explota y no produce ninguna salida. Sin embargo, todava se puede ver la huella de los dems hilos. threads = [] 4.times do |number| threads << Thread.new(number) do |i| raise Boom! if i == 2 print #{i}\n end end threads.each {|t| t.join } produce: 0 1 3 prog.rb:4: Boom! (RuntimeError) from prog.rb:8:in `join from prog.rb:8 from prog.rb:8:in `each from prog.rb:8 Podemos rescatar la excepcin a la vez que los hilos se unen.

threads = [] 4.times do |number| threads << Thread.new(number) do |i| raise Boom! if i == 2 print #{i}\n end end threads.each do |t| begin t.join rescue RuntimeError => e puts Failed: #{e.message} end end produce: 0 1 3 Failed: Boom! Sin embargo, establecer abort_on_exception en true, o usar -d en su momento en la bandera de depuracin, hace que una excepcin no controlada mate a todos los subprocesos que se ejecutan. Una vez que el subproceso 2 muere, no se produce ms salida. Thread.abort_on_exception = true threads = [] 4.times do |number| threads << Thread.new(number) do |i| raise Boom! if i == 2 print #{i}\n end end threads.each {|t| t.join }

89

produce: 0 1 prog.rb:5: Boom! (RuntimeError) from prog.rb:4:in `initialize from prog.rb:4:in `new from prog.rb:4 from prog.rb:3:in `times from prog.rb:3 Este cdigo tambin ilustra un gotcha. Dentro del bucle, los hilos utilizan print, en lugar de puts, para escribir el nmero. Por qu? Porque entre bastidores, puts divide su labor en dos trozos: escribe su argumento y luego escribe una nueva lnea. Entre estos dos, un hilo podra conseguir tiempo de programa y la salida se intercalara. Llamando a print con una sola cadena que ya contiene la nueva lnea soluciona el problema.

Controlar el Tiempo de Programa de Hilo


Una aplicacin bien diseada normalmente va a dejar que los hilos hagan lo suyo. La construccin de las dependencias de tiempo en una aplicacin multiproceso, est generalmente considerado como de mal gusto. Hace al cdigo mucho ms complejo y tambin impide al planificador de procesos la optimizacin de la ejecucin del programa. Sin embargo, a veces es necesario controlar los subprocesos de manera explcita. Nuestra mquina de discos va a tener un hilo que muestre un espectculo de luces, que se tendr que detener temporalmente cuando la msica se detiene. Se puede tener dos hilos en una relacin clsica de productor-usador, donde el usador tiene que hacer una pausa si se atrasa el productor. La clase Thread proporciona una serie de mtodos para controlar el planificador de hilos. Invocando Thread.stop detiene el subproceso actual e invocacando Thread#run para un hilo particular organiza que este se ejecute. Thread.pass desprograma el subproceso actual, lo que permite que se ejecuten otros, Thread#join y Thread#value suspenden el hilo de llamada hasta que otro hilo determinado concluye. Podemos demostrar estas caractersticas en el siguiente programa, totalmente intil. Se crean dos procesos hijo, T1 y T2, cada uno de ellos ejecuta una instancia de la clase Chaser. El mtodo chase incrementa un contador, pero sin dejar que se supere en dos el contador por el otro hilo. Para hacer esto, se plantea un mtodo Thread.pass, que permite al chase en el otro hilo ponerse al da. Para hacerlo ms interesante (para una definicin menor de interesante), tenemos los hilos inicialmente suspendidos para luego comienzar uno en primer lugar aleatoriamente. class Chaser attr_reader :count def initialize(name) @name = name @count = 0 end def chase(other) while @count < 5 while @count other.count > 1 Thread.pass end @count += 1 print #@name: #{count}\n end end end c1 = Chaser.new(A) c2 = Chaser.new(B)

90

threads = [ Thread.new { Thread.stop; c1.chase(c2) }, Thread.new { Thread.stop; c2.chase(c1) } ] start_index = rand(2) threads[start_index].run threads[1 - start_index].run threads.each {|t| t.join } produce: B: B: A: B: A: B: A: B: A: A: 1 2 1 3 2 4 3 5 4 5

Sin embargo, utilizar estas primitivas para lograr el cdigo de sincronizacin en la vida real no es fcil --las condiciones de carrera siempre estarn a la vuelta de la esquina. Y cuando se trabaja con datos compartidos, las condiciones de carrera te garantizan sesiones de depuracin ms o menos largas y frustrantes. De hecho, el ejemplo anterior tiene justamente como un error: es posible para el contador ser incrementado en un hilo pero antes de que pueda poner la salida de su cuenta, el segundo hilo obtiene tiempo de programa y pone la suya. La salida resultante estar fuera de secuencia. Afortunadamente, los hilos tienen una caracterstica adiciona: la idea de exclusin mutua. Utilizando esto, podemos construir un esquema de sincronizacin seguro.

Exclusin Mutua
El mtodo de ms bajo nivel para bloquear la ejecucin de otros hilos utiliza una condicin global thread-critical. Cuando la condicin est establecida a true (utilizando el mtodo Thread.critical=), el planificador no programa para ejecutar ningn hilo existente. Sin embargo, esto no bloquea la creacin de nuevos hilos para ejecutar. Ciertas operaciones de hilo (como la de detener o matar un hilo, mandar a dormir al hilo en curso o lanzar una excepcin) puede causar la asignacin de tiempo a un hilo, aun estando en una seccin crtica. Utilizar Thread.critical= directamente es ciertamente posible pero no es muy conveniente. De hecho, le recomendamos encarecidamente que no lo use a menos que tenga cinturn negro en multithreading (y aficin por las sesiones de depuracin largas). Afortunadamente, Ruby viene con varias alternativas. Ahora vamos a ver una de estas, la librera Monitor. Tambin se puede consultar la librera Sync, la librera Mutex_m y la clase Queue implementada en la librera Thread (todas se vern ms adelante).

Monitores
Aunque las primitivas para hilos proporcionan sincronizacin bsica, son dificiles de usar. Con los aos, varias personas han llegado a alternativas de ms alto niuvel. Una que funciona especialmente bien en sistemas orientados a objetos es el concepto de monitor. Los monitores envuelven objetos que contienen algn tipo de recursos con funciones de sincronizacin. Para verlos en accin, vamos a ver un simple contador al que se accede desde dos hilos. class Counter attr_reader :count def initialize

91

end def end end

@count = 0 super tick @count += 1

c = Counter.new t1 = Thread.new { 10000.times { c.tick } } t2 = Thread.new { 10000.times { c.tick } } t1.join t2.join c.count -> 11319

Sorprendentemente quizs, el recuento no es igual a 20.000. La razn est en una simple lnea de cdigo:

@count += 1 Esta lnea en realidad es ms compleja de lo que parece. En el intrprete de Ruby podra descomponerse en val = fetch_current(@count) add 1 to val store val back into @count Ahora imagine que dos hilos ejecutan este cdigo al mismo tiempo. La tabla 3 muestra el nmero de los hilos (t1 y t2), el cdigo que se est ejecutando, y el valor del contador (que se inicializa a 0). Aunque nuestro bsico conjunto de instrucciones load/add/store se ejecuta cinco veces, terminamos con el contador a 3. Como el hilo 1 interrumpe la ejecucin del hilo 2 en medio de una secuencia, cuando se reanuda el hilo 2 almacena un valor antiguo al volver a @count.

La solucin es arreglar las cosas para que slo un hilo pueda ejecutar el mtodo de incremento tick en un momento dado. Esto es fcil utilizando monitores. require monitor class Counter < Monitor attr_reader :count

92

def initialize @count = 0 super end def tick synchronize do @count += 1 end end end c = Counter.new t1 = Thread.new { 10000.times { c.tick } } t2 = Thread.new { 10000.times { c.tick } } t1.join; t2.join c.count -> 20000 Al hacer nuestro contador un monitor, gana el acceso al mtodo synchronize. Slo un hilo puede ejecutar cdigo en un bloque de sincronizacin, para un objeto monitor particular en un momento dado. As, ya no tenemos dos hilos almacenando en cach los resultados intermedios al mismo tiempo, y nuestra cuenta tiene el valor esperado. No tenemos que hacer de nuestra clase una subclase de monitor para conseguir estos beneficios. Tambin puede combinar en una variante, MonitorMixin. require monitor class Counter include MonitorMixin . . . end El ejemplo anterior pone la sincronizacin dentro del recurso que se est sincronizado. Esto es apropiado cuando todos los accesos a todos los objetos de la clase requieren sincronizacin. Pero si se quiere controlar el acceso a los objetos que requieren sincronizacin, slo en algunas circunstancias, o si la sincronizacin se reparte entre un grupo de objetos, entonces puede ser mejor usar un monitor externo. require monitor class Counter attr_reader :count def initialize @count = 0 end def tick @count += 1 end end c = Counter.new lock = Monitor.new t1 = Thread.new { 10000.times { lock.synchronize { c.tick } } } t2 = Thread.new { 10000.times { lock.synchronize { c.tick } } } t1.join; t2.join c.count -> 20000

Se pueden incluso hacer objetos especficos en los monitores:

93

require monitor class Counter # como antes ... end c = Counter.new c.extend(MonitorMixin) t1 = Thread.new { 10000.times { c.synchronize { c.tick } } } t2 = Thread.new { 10000.times { c.synchronize { c.tick } } } t1.join; t2.join c.count -> 20000 En este caso, como la clase Counter no sabe lo que es un monitor en el momento en que se define, tenemos que realizar la sincronizacin externa (en este caso, envolviendo las llamadas a c.tick). Esto claramente es un poco peligroso: si algn otro cdigo llama a tick, pero no realiza la sincronizacin requerida, estamos de vuelta en el mismo lo en el que comenzamos.

Colas
Los monitores nos dan la mitad de lo que necesitamos, porque hay un problema. Digamos que tenemos dos hilos accediendo a una cola compartida. Uno para aadir las entradas, y el otro para leer (tal vez la lista de las canciones la espera de ser puestas en nuestro jukebox: aade las selecciones que hacen los clientes, y vaca los registros que terminaron). Ya sabemos lo que necesitamos para sincronizar el acceso, por lo que intentamos algo as como

require monitor playlist = [] playlist.extend(MonitorMixin) # Hilo reproductor Thread.new do record = nil loop do playlist.synchronize do # < < BUG!!! sleep 0.1 while playlist.empty? record = playlist.shift end play(record) end end # Peticin de cancin del cliente Thread.new do loop do req = get_customer_request playlist.synchronize do playlist << req end end end Pero este cdigo tiene un problema. En el interior del hilo reproductor, se accede al monitor y luego el bucle espera que se aada algo a la lista de reproduccin. Pero debido a que en el propio monitor el hilo de clientes no ser capaz de entrar en su bloque sincronizado, nunca agregar nada a la lista de reproduccin. Estamos atascados. Lo que necesitamos es ser capaces de indicar que la lista tiene algo en ella para proporcionar una sincronizacin entre los hilos sobre la base de esta condicin, mantenindonos a la vez dentro de la seguridad de un monitor. En trminos ms generales, tenemos que ser capaces de abandonar temporalmente el uso exclusivo de la regin crtica y al mismo tiempo decirle a los clientes que

94

estamos esperando un recurso. Cuando el recurso est disponible, tenemos que ser capaces de captar y reobtener el bloqueo de la regin crtica, todo en un solo paso. Ah es donde vienen las variables de condicin. Una variable de condicin es una forma controlada de comunicar un evento (o una condicin) entre dos hilos. Un hilo puede esperar la condicin y el otro puede enviarle seales. Por ejemplo, podramos reescribir nuestra mquina de discos utilizando variables de condicin. (A los efectos de este cdigo vamos a escribir mtodos de cdigo auxiliar para recibir las solicitudes de los clientes y para escuchar los discos. Tambin tenemos que aadir una bandera para decirle al reproductor cuando puede cerrar. Normalmente se ejecutara indefinidamente) require monitor SONGS = [ Blue Suede Shoes, Take Five, Bye Bye Love, Rock Around The Clock, Ruby Tuesday ] START_TIME = Time.now def timestamp (Time.now - START_TIME).to_i end # Esperar un mximo de 2 minutos entre las peticiones de los clientes def get_customer_request sleep(120 * rand) song = SONGS.shift puts #{timestamp}: Requesting #{song} if song song end # Canciones toman entre dos y tres minutos def play(song) puts #{timestamp}: Playing #{song} sleep(120 + 60*rand) end ok_to_shutdown = false # y aqu est nuestro cdigo original playlist = [] playlist.extend(MonitorMixin) plays_pending = playlist.new_cond # Peticin de cancin del cliente customer = Thread.new do loop do req = get_customer_request break unless req playlist.synchronize do playlist << req plays_pending.signal end end end # Hilo reproductor player = Thread.new do loop do song = nil playlist.synchronize do

95

break if ok_to_shutdown && playlist.empty? plays_pending.wait_while { playlist.empty? } song = playlist.shift end break unless song play(song) end end customer.join ok_to_shutdown = true player.join produce: 26: Requesting Blue Suede Shoes 28: Playing Blue Suede Shoes 72: Requesting Take Five 188: Requesting Bye Bye Love 214: Playing Take Five 288: Requesting Rock Around The Clock 299: Requesting Ruby Tuesday 396: Playing Bye Bye Love 563: Playing Rock Around The Clock 708: Playing Ruby Tuesday

Ejecucin de Mltiples Procesos


A veces es posible que desee dividir una tarea en procesos de uno u otro tamao --o tal vez se necesita ejecutar un proceso separado que no estaba escrito en Ruby. No hay problema: Ruby tiene una serie de mtodos por los cuales se pueden generar y gestionar procesos separados.

Generar Nuevos Procesos


Hay varias maneras de generar un proceso separado, lo ms fcil es ejecutar algn comando y esperar a que se complete. Puede que uno se encuentre haciendo esto para ejecutar algn comando por separado o recuperar datos del sistema anfitrin. Ruby lo hace por usted con los mtodos system y acento grave (o tilde). system(tar xzf test.tgz) result = `date` result -> -> true Thu Aug 26 22:36:55 CDT 2004\n

El mtodo Kernel.system ejecuta un comando dado en un subproceso. Si se ha encontrado el comando y se ha ejecutado correctamente devuelve true, y false, en caso contrario. En caso de fallo, se encuentra el cdigo de salida del subproceso en la variable global $?. Un problema con system es que la salida del comando, simplemente va al mismo destino que la salida del programa, que puede no ser lo que se pretende. Para capturar la salida estndar de un subproceso, puede utilizar los caracteres de acento grave, al igual que con `date` en el ejemplo anterior. Recuerde que se puede necesitar utilizar String#chomp para eliminar los caracteres de fin de lnea del resultado. OK, esto est bien para los casos simples, podemos ejecutar algn otro proceso y obtener el estado de retorno. Pero muchas veces tenemos que controlar algo ms que eso. Nos gustara mantener una comunicacin con el subproceso, como envarle datos y la posibilidad de conseguir algunos de vuelta. El mtodo IO.popen hace justamente esto. El mtodo popen ejecuta un comando como un subproceso y se conecta a la entrada estndar de subprocesos y a la salida estndar de un objeto IO Rub. Escribe en el objeto IO y el subproceso puede leer en la entrada estndar. Cualquiera cosa que el subproceso escribe est disponible en el programa Ruby con la lectura del objeto IO. Por ejemplo, en nuestros sistemas una de las utilidades ms conveniente es pig, un programa que lee 96

palabras de la entrada estndar y las imprime en pig latn (o igpay atinlay). Podemos utilizar esta opcin cuando nuestros programas Ruby necesita enviarnos una salida que nuestros cinco-aos-de-edad no deberan ser capaz de entender (Pig Latin es un giro del Ingls para personas que quieren hacerse el tonto, o para los nios que no quieren que sus padres sepan lo que estn hablando). pig = IO.popen(/usr/local/bin/pig, w+) pig.puts ice cream after they go to bed pig.close_write puts pig.gets produce: iceway eamcray afterway eythay ogay otay edbay Este ejemplo ilustra la aparente simplicidad y la verdadera complejidad del mundo real en la que participa la conduccin de los subprocesos a travs de las tuberas (pipes). El cdigo parece ciertamente bastante simple: abrir el pipe, escribir una frase, y de vuelta a leer la respuesta. Pero resulta que el programa pig no limpia la salida que escribe. Nuestro primer intento en este ejemplo, que tena un pig.puts seguido de un pig.gets, se qued colgado para siempre. El programa pig procesa nuestra entrada, pero su respuesta no fue escrita en la tubera. Hemos tenido que insertar la lnea pig.close_write. Esto enva un fin de archivo a la entrada estndar de pig y la salida buscada se vaca cuando pig termina. popen tiene una peculiaridad ms. Si el comando que se le pasa es un signo menos (-), popen ser un fork de un nuevo intrprete de Ruby. Tanto ste como el intrprete original se seguirn ejecutando mediante el retorno de popen. El proceso original recibir un nuevo objeto IO y el hijo recibir nil. Esto slo funciona en sistemas operativos que soporten la llamada a fork(2) (y por ahora esto excluye a Windows). pipe = IO.popen(,w+) if pipe pipe.puts Get a job! STDERR.puts Child dice #{pipe.gets.chomp} else STDERR.puts Papa dice #{gets.chomp} puts OK end produce: Papa dice Get a job! Child dice OK Adems del mtodo popen, algunas plataformas soportan los mtodos Kernel.fork, Kernel.exec e IO.pipe. La convencin de nomenclatura de archivos de muchos mtodos IO y Kernel.open tambin genera subprocesos si se pone a | como el primer carcter del nombre de archivo (vase la introduccin a la clase IO para ms detalles). Tenga en cuenta que no se puede crear pipes utilizando File.new ya que es slo para archivos.

Procesos Hijo Independientes


A veces no hace falta ser tan prctico: nos gustara dar al subproceso sus cometidos y luego ir a nuestro negocio. Algn tiempo despus, vamos a comprobar si ha terminado. Por ejemplo, es posible que queramos poner en marcha una clasificacin externa de larga ejecucin. exec(sort testfile > output.txt) if fork.nil? # The sort is now running in a child process # carry on processing in the main program # ... dum di dum ... # then wait for the sort to finish Process.wait

97

La llamada a Kernel.fork devuelve un identificador de proceso en el padre y nil en el hijo, por lo que ste llevar a cabo la llamada a Kernel.exec y se ejecutar sort. Algn tiempo despus, emitimos una llamada a Process.wait, que espera a que sort termina (y devuelve su identificador de proceso). Si prefiere ser notificado cuando un subproceso sale (en vez de esperar la vuelta), puede configurar un controlador de seal con Kernel.trap (que se describe ms adelante). Aqu hemos configurado una trampa en SIGCLD, que es la seal enviada a la muerte del proceso hijo. trap(CLD) do pid = Process.wait puts Child pid #{pid}: terminated end exec(sort testfile > output.txt) if fork.nil? # hacer otras cosas... produce: Child pid 25816: terminated Para ms informacin sobre el uso y control de procesos externos, consulte la documentacin de Kernel.open , IO.popen y la seccin del mdulo Process.

Bloques y Subprocesos
IO.popen trabaja con un bloque ms o menos de la misma forma que File.open. Si se le pasa un comando, como date, el bloque pasa un objeto IO como un parmetro. IO.popen(date) {|f| puts Date is #{f.gets} } produce: Date is Thu Aug 26 22:36:55 CDT 2004 El objeto IO se cerrar automticamente cuando se sale del bloque de cdigo, tal como con File.open. Si asocia un bloque con Kernel.fork, el cdigo del bloque se ejecutar en un subproceso Ruby, y el padre continuar despus del bloque. fork do puts In child, pid = #$$ exit 99 end pid = Process.wait puts Child terminated, pid = #{pid}, status = #{$?.exitstatus} produce: In child, pid = 25823 Child terminated, pid = 25823, status = 99 $? es una variable global que contiene informacin sobre la terminacin de un subproceso. Para ms informacin ver la seccin sobre Process::Status.

Pruebas de Unidad
Las pruebas de unidad (la descripcin en el siguiente recuadro resaltado) es una tcnica que ayuda a los desarrolladores a escribir mejor cdigo. Esto ayuda antes de que se escriba realmente el cdigo, ya

98

que pensar acerca de las pruebas nos lleva de forma natural a crear mejores diseos ms disociados. Ayuda en la reescritura del cdigo, ya que da informacin instantnea sobre la forma exacta del mismo. Y ayuda despus de haber escrito el cdigo, tanto porque le da la posibilidad de comprobar que el cdigo todava funciona como porque ayuda a otros a entender cmo usar el cdigo. Las pruebas de unidad son una cosa buena.

Pero por qu hay un captulo sobre las pruebas de unidad en medio de un libro sobre Ruby? Porque pruebas de unidad y lenguajes como Ruby parecen ir de la mano. La flexibilidad de Ruby hace fcil escribir pruebas, y a stas, ser ms fcil comprobar el cdigo en el que se est trabajando. Una vez que usted entra en su swing, se encontrar escribiendo un poco de cdigo, despus una o dos pruebas comprobando que todo est en orden, y a continuacin, seguir escribiendo un poco ms de cdigo. Las pruebas de la unidad tambin son bastante triviales, --se ejecuta un programa que llama a una parte de cdigo de la aplicacin, se obtienen algunos resultados, y a continuacin, se comprueba que los resultados son los esperados. Digamos que estamos probando una clase para nmeros romanos. Por ahora, el cdigo es bastante simple: slo nos permite crear un objeto que representa un nmero determinado y mostrar el objeto en nmeros romanos. La figura 5 muestra nuestro primer intento de una aplicacin.

Podramos probar este cdigo escribiendo este otro programa:

require roman r = Roman.new(1) fail i expected unless r.to_s == i

r = Roman.new(9) fail ix expected unless r.to_s == ix Sin embargo, como el nmero de pruebas en un proyecto va creciendo, este tipo de enfoque ad-hoc puede empezar a complicarse para su gestin. Con los aos, han surgido diversos marcos (frameworks) para pruebas unitarias para ayudar a estructurar el proceso de prueba. Ruby cuenta con uno pre-instalado, Test::Unit de Nathaniel Talbotts.

99

Qu son las Pruebas de Unidad? Las pruebas de unidad se centra en pequeos trozos (unidades) de cdigo, mtodos tpicamente individuales o lneas dentro de mtodos. Esto contrasta con la mayora de otras formas de prueba, que consideran el sistema como un todo. Por qu centrarse tan estrechamente? Porque al final todo el software se construye en capas: una capa de cdigo se basa en el correcto funcionamiento del cdigo en las capas inferiores. Si este cdigo subyacente resulta contener errores, entonces todas las capas superiores estn potencialmente afectadas. Este es un gran problema. Fred puede escribir el cdigo con un bug una semana, y luego puede terminar llamandole, indirectamente, dos meses despus. Cuando el cdigo comienza a generar resultados incorrectos, a Fred le llevar un tiempo encontrar el problema en el mtodo. Y cuando Fred se pregunte por qu lo escribi de esa manera, la respuesta ser probablemente no me acuerdo. Eso fue hace meses. Si en lugar de esto, Fred hubiera puesto a prueba su cdigo cuando lo escribi, habran sudecido dos cosas. En primer lugar, habra encontrado el error cuando el cdigo todava est fresco en su mente. En segundo lugar, como las pruebas de unidad se hacen con el cdigo que se acababa de escribir, cuando se presenta el bug, slo hay que mirar un puado de lneas para encontrarlo, en lugar de hacer arqueologa en el resto de la base del cdigo.

Framework Test::Unit
El Framework Test::Unit es, bsicamente, tres utilidades envueltas en un paquete ordenado. 1. Te da una manera de expresar las pruebas individuales. 2. Proporciona un marco para la estructuracin de las pruebas. 3. Te da formas flexibles de invocar las pruebas.

Afirmaciones == Resultados Esperados


En lugar de tener que escribir una serie de sentencias if individuales en las pruebas, Test::Unit ofrece una serie de afirmaciones que logran lo mismo. Aunque existe un nmero de diferentes estilos de afirmacin, todas siguen bsicamente el mismo patrn. Cada afirmacin le da una forma de especificar el resultado o la salida deseados y una manera de pasar el resultado actual. Si el actual no es igual al esperado, la afirmacin muestra un bonito mensaje y registra el hecho como un fallo. Por ejemplo, podramos reescribir nuestra prueba anterior de la clase romana en Test::Unit. Por el momento, vamos a ignorar el cdigo de andamiaje con el inicio y el final, y nos bastar con ver el mtodo assert_equal. require roman require test/unit class TestRoman < Test::Unit::TestCase def test_simple assert_equal(i, Roman.new(1).to_s) assert_equal(ix, Roman.new(9).to_s) end end produce: Loaded suite Started . Finished in 0.003655 seconds. 1 tests, 2 assertions, 0 failures, 0 errors

100

La primera afirmacin dice que estamos esperando la representacin de nmeros romanos de una cadena como i, y la segunda que podemos esperar que 9 sea ix. Por suerte para nosotros, las expectativas se cumplen y el informe de seguimiento dice que se ha pasado la prueba. Vamos a aadir unas cuantas pruebas ms.

require roman require test/unit class TestRoman < Test::Unit::TestCase def test_simple assert_equal(i, Roman.new(1).to_s) assert_equal(ii, Roman.new(2).to_s) assert_equal(iii, Roman.new(3).to_s) assert_equal(iv, Roman.new(4).to_s) assert_equal(ix, Roman.new(9).to_s) end end produce: Loaded suite Started F Finished in 0.021877 seconds. 1) Failure:<ii> expected but was <i>. 1 tests, 2 assertions, 1 failures, 0 errors test_simple(TestRoman) [prog.rb:6]: Uh oh! La segunda afirmacin fall. Ver cmo el mensaje de error utiliza el hecho de que las afirmaciones conocen tanto los valores esperados como los reales: se espera obtener ii pero se obtiene i. En cuanto a nuestro cdigo, se puede ver un error evidente en to_s. Si el recuento despus de dividir por el factor es mayor que cero, entonces debemos tenemos en la salida muchos dgitos romanos. El cdigo existente produce slo uno. La solucin es fcil. def to_s value = @value roman = for code, factor in FACTORS count, value = value.divmod(factor) roman << (code * count) end roman end Ahora vamos a ejecutar nuestras pruebas otra vez. Loaded suite Started . Finished in 0.002161 seconds. 1 tests, 5 assertions, 0 failures, 0 errors En buen estado. Ahora podemos ir un paso ms all y eliminar algunas duplicidades.

require roman require test/unit

101

class TestRoman < Test::Unit::TestCase NUMBERS = [ [ 1, i ], [ 2, ii ], [ 3, iii ], [ 4, iv], [ 5, v ], [ 9, ix ] ] def test_simple NUMBERS.each do |arabic, roman| r = Roman.new(arabic) assert_equal(roman, r.to_s) end end end produce: Loaded suite Started . Finished in 0.004026 seconds. 1 tests, 6 assertions, 0 failures, 0 errors Qu otra cosa se puede probar? Bueno, el constructor comprueba que el nmero se le pasa se puede representar como un nmero romano y lanza una excepcin si no se puede. Vamos a probar la excepcin. require roman require test/unit class TestRoman < Test::Unit::TestCase def test_range assert_raise(RuntimeError) { Roman.new(0) } assert_nothing_raised() { Roman.new(1) } assert_nothing_raised() { Roman.new(499) } assert_raise(RuntimeError) { Roman.new(5000) } end end produce: Loaded suite Started . Finished in 0.002898 seconds. 1 tests, 4 assertions, 0 failures, 0 errors Podramos hacer muchas ms pruebas en nuestra clase Roman, pero vamos a pasar a cosas ms grandes y mejores. Antes de seguir, sin embargo, debemos decir que slo hemos araado la superficie del conjunto de afirmaciones disponible dentro de Test::Unit. La figura 6 un poco ms adelante da una lista completa. El parmetro final de cada afirmacin es un mensaje que se emite antes que cualquier mensaje de error. Esto normalmente no es necesario, ya que los mensajes de Test::Unit son normalmente bastante razonables. La nica excepcin es el test assert_not_nil, donde el mensaje <nil> esperado no ser nil no ayuda mucho. En este caso, es posible que desee aadir alguna anotacin por su cuenta. require test/unit class TestsWhichFail < Test::Unit::TestCase def test_reading assert_not_nil(ARGF.read, Read next line of input) end end

102

produce: Loaded suite Started F Finished in 0.033581 seconds. 1) Failure: Read next line of input. <nil> expected to not be nil. 1 tests, 1 assertions, 1 failures, 0 errors test_reading(TestsWhichFail) [prog.rb:4]:

Pruebas de Estructuracin
Antes dijomos hacer caso omiso del andamiaje en torno a nuestras pruebas. Pues ahora es el momento de verlo. En su unidad de prueba se incluye la utilidad Test::Unit con la siguiente lnea:

require test/unit Las pruebas de unidad parecen caer naturalmente bien en grupos de alto nivel, llamados test cases. En grupos de bajo nivel, los mtodos se prueban en s mismos. Los casos de prueba generalmente contienen todas las pruebas relacionadas con una utilidad o caracterstica en particular. Nuestra clase de nmeros romanos es bastante simple, por lo que todas las pruebas para ella probablemente entrarn en un solo caso de prueba. En un caso de prueba, es probable que desee organizar sus afirmaciones en una serie de mtodos de prueba, en la que cada mtodo contiene las afirmaciones de un tipo de prueba: un mtodo que puede comprobar la conversin regular de nmeros, otro podra poner a prueba el manejo de errores, etc. Las clases que representan los casos de prueba deben ser subclases de Test::Unit::TestCase. Los mtodos que llevan las afirmaciones deben tener nombres que empiezan con test. Esto es importante: Test::Unit utiliza el reflejo para encontrar las pruebas a ejecutar y slo los mtodos cuyos nombres empiezan con test son elegibles. Muy a menudo se encontrar todos los mtodos de prueba en un caso de prueba configurados para un escenario particular. Entonces, cada mtodo de prueba sondea algn aspecto de ese escenario. Finalmente, cada mtodo puede ponerse en orden a s mismo. Por ejemplo, podra ser la prueba de una clase que extrae las listas de reproduccin del jukebox de una base de datos. require test/unit require playlist_builder require dbi class TestPlaylistBuilder < Test::Unit::TestCase def test_empty_playlist db = DBI.connect(DBI:mysql:playlists) pb = PlaylistBuilder.new(db) assert_equal([], pb.playlist()) db.disconnect end def test_artist_playlist db = DBI.connect(DBI:mysql:playlists) pb = PlaylistBuilder.new(db) pb.include_artist(krauss) (pb.playlist.size > 0, Playlist shouldnt be empty) pb.playlist.each do |entry| assert_match(/krauss/i, entry.artist) end

103

db.disconnect end def test_title_playlist db = DBI.connect(DBI:mysql:playlists) pb = PlaylistBuilder.new(db) pb.include_title(midnight) assert(pb.playlist.size > 0, Playlist shouldnt be empty) pb.playlist.each do |entry| assert_match(/midnight/i, entry.title) end db.disconnect end # ... end produce: Loaded suite Started ... Finished in 0.004809 seconds. 3 tests, 23 assertions, 0 failures, 0 errors Cada prueba se inicia mediante la conexin a la base de datos y la creacin de un nuevo constructor de lista de reproduccin. Cada prueba termina por desconexin de la base de datos. (La idea de utilizar una base de datos real en las pruebas unitarias es cuestionable, ya que las pruebas de unidad se supone que ejecutan rpido, son independientes del contexto y fcil de configurar, pero ilustra el tema). Podemos extraer de todo este cdigo comn los mtodos de montaje y desmontaje. Dentro de una clase TestCase, se ejecutar un mtodo llamado setup (montaje) antes de cada uno de los mtodos de prueba. Un mtodo llamado teardown (desmontaje) se llevar a cabo despus de que termina cada mtodo de prueba. Hacemos hincapi en que: los mtodos de montaje y desmontaje soportan cada prueba, en lugar de ser ejecutado una vez para el caso de prueba. Nuestra prueba vendra a ser:

require test/unit require playlist_builder require dbi class TestPlaylistBuilder < Test::Unit::TestCase def setup @db = DBI.connect(DBI:mysql:playlists) @pb = PlaylistBuilder.new(@db) end def teardown @db.disconnect end def test_empty_playlist assert_equal([], @pb.playlist()) end def test_artist_playlist @pb.include_artist(krauss) assert(@pb.playlist.size > 0, Playlist no de be estar vaca) @pb.playlist.each do |entry| assert_match(/krauss/i, entry.artist) end end def test_title_playlist @pb.include_title(midnight)

104

assert(@pb.playlist.size > 0, Playlist no debe estar vaca) @pb.playlist.each do |entry| assert_match(/midnight/i, entry.title) end end # ... end produce: Loaded suite Started ... Finished in 0.00691 seconds. 3 tests, 23 assertions, 0 failures, 0 errors

Organizacin y Ejecucin de las Pruebas


Los casos de prueba que hemos demostrado hasta el momento son todos programas ejecutables Test::Unit. Si, por ejemplo, el caso de prueba para la clase Roman estaba en un archivo llamado test_roman.rb, podramos ejecutar las pruebas desde la lnea de comandos con % ruby test_roman.rb Loaded suite test_roman Started ... Finished in 0.039257 seconds. 2 tests, 9 assertions, 0 failures, 0 errors Test::Unit es lo suficientemente funcional como para notar que no existe un programa principal, as que recolecta toda las clases de caso de prueba y ejecuta cada una de ellas. Si queremos, podemos pedir que se ejecute un slo mtodo de prueba en particular.

% ruby test_roman.rb --name test_range Loaded suite test_roman Started . Finished in 0.006445 seconds. 1 tests, 4 assertions, 0 failures, 0 errors

Dnde Colocar Pruebas


Una vez que se entra en las pruebas de unidad, es muy posible encontrar que se genera casi tanto cdigo de prueba como cdigo de produccin. Todas esas pruebas tienen que vivir en alguna parte. El problema es que si se colocan junto a los archivos de produccin regular de cdigo fuente, los directorios comienzan a hincharse --efectivamente se terminar con dos archivos por cada archivo fuente producido. Una solucin comn es tener un directorio test/ donde colocar todos los archivos fuente de prueba. Este directorio se coloca paralelo al directorio que contiene el cdigo que est en desarrollo. Por ejemplo, para nuestra clase de nmeros romanos, podemos tener:

105

roman | |---- lib/ | | | |---- roman.rb | |---- otros ficheros... | |---- test/ | | | |---- test_roman.rb | |---- otros test... | |---- otras cosas Esto funciona bien como forma de organizacin de archivos, pero te deja con un pequeo problema: cmo decir a Ruby dnde encontrar los archivos de librera para probar? Por ejemplo, si nuestro cdigo de prueba TestRoman est en el subdirectorio /test cmo sabe Ruby dnde encontrar el archivo fuente roman.rb que estamos tratando de probar? Una opcin que no funciona correctamente es la construccin de la ruta con la declaracin require y ejecutar las pruebas desde el subdirectorio /test. require test/unit require ../lib/roman class TestRoman < Test::Unit::TestCase # ... end Por qu no funciona? Debido a que el archivo roman.rb puede a su vez necesitar otros archivos fuente en la biblioteca que estamos escribiendo. Que se van a cargar con require (sin el ../lib/), y como no estn en $LOAD_PATH de Ruby, no se encontrarn. Simplemente no se ejecutar nuestra prueba. Un segundo problema, menos inmediato, es que no vamos a ser capaces de usar estas mismas pruebas para nuestras clases una vez instalado en un sistema de destino, ya que luego van a referenciar con require roman. Una solucin mejor es ejecutar las pruebas desde el directorio que contiene la biblioteca que se est probando. Debido a que el directorio actual se encuentra en la ruta de carga, el cdigo de prueba ser capaz de encontrarlo. % ruby ../test/test_roman.rb Sin embargo, este enfoque no sirve si se quiere ser capaz de ejecutar las pruebas en algn otro lugar del sistema. Tal vez su proceso de gestin de procesos (scheduled) ejecuta las pruebas para todo el software de la aplicacin, simplemente buscando archivos llamados test_xxx y ejecutandolos. En este caso, se necesita un poco de magia en la ruta de carga. Al principio de su cdigo de prueba (por ejemplo, en test_roman.rb), se aade la siguiente lnea: $:.unshift File.join(File.dirname(__FILE__), .., lib) require ... Esta magia funciona porque el cdigo de prueba se encuentra en una ubicacin conocida en relacin con el cdigo que se est probando. Se inicia con la elaboracin del nombre del directorio desde el que se ejecuta el archivo de prueba y luego la construccin de la ruta a los archivos bajo prueba. Este directorio es el antepuesto a la ruta de carga (la variable $:). A partir de entonces, cdigo tal como require roman buscar la librera que se prob por primera vez.

Conjuntos de Prueba
Despus de un tiempo, tendr una decente coleccin de casos de prueba para su aplicacin. Puede que encuentre que tienden a agruparse: un grupo de casos de prueba para un determinado conjunto de

106

funciones y otro grupo de pruebas parsa un conjunto diferente de funciones. Puede agrupar los casos de prueba en conjuntos de pruebas, lo que le permite ejecutar todos como un grupo. En Test::Unit esto es fcil de hacer. Todo lo que hay que hacer es crear un archivo de Ruby que requiera test/unit y a continuacin, requierir cada uno de los archivos de los casos de prueba que se desean agrupar. De esta manera, se construye una jerarqua de material de prueba y Puede Puede Puede Puede ejecutar pruebas individuales por su nombre. ejecutar todas las pruebas en un archivo mediante la ejecucin de dicho archivo. agrupar una serie de archivos en un conjunto de prueba y ejecutarlas como una unidad. agrupar conjuntos de prueba en un conjunto de prueba.

Esto le da la capacidad de ejecutar las pruebas de unidad controlando el nivel de granularidad, testear slo un mtodo o toda la aplicacin. En este punto, vale la pena pensar acerca de las convenciones de nombres. Nathaniel Talbott, el autor de Test::Unit, utiliza la convencin de que los casos de prueba se encuentran en archivos con el nombre tc_xxx y los conjuntos (suites) de pruebas se encuentran en archivos con el nombre ts_xxx. # file ts_dbaccess.rb require test/unit require tc_connect require tc_query require tc_update require tc_delete Ahora, si ejecuta Ruby con el archivo ts_dbaccess.rb, se ejecutarn los casos de prueba de los cuatro archivos que ha requerido. Eso es todo lo que hay? No, usted puede hacer que sea ms complicado si lo desea. Puede crear y rellenar manualmente objetos TestSuite, aunque no parece tener mucho sentido en la prctica. Para obtener ms informacin, ri Test::Unit debera ayudar. Test::Unit viene con una serie de lujo de ejecutables GUI de pruebas. Aunque los autnticos programadores utilizan la lnea de comandos, no se describe aqu sin embargo. Una vez ms, consulte la documentacin para ms detalles.

Cuando Atacan los Problemas


Es triste decirlo, pero es posible escribir programas con errores usando Ruby. Se siente. Pero no se preocupe! Ruby tiene varias caractersticas que ayudan a depurar los programas. Pronto nos ocuparemos de estas caractersticas, y le mostraremos algunos errores comunes que se pueden cometer en Ruby y cmo solucionarlos.

El Depurador de Ruby
Ruby cuenta con un depurador que est muy bien integrado en el sistema base. Puede ejecutar el depurador llamando al intrprete con la opcin -r debug, junto con cualquier otra opcin de Ruby y el nombre de su script. ruby -r debug [ debug-options ] [ programfile ] [ program-arguments ] El depurador es compatible con la habitual gama de caractersticas que usted esperara, incluyendo la posibilidad de establecer puntos de interrupcin, entrar en y pasar por encima de las llamadas a mtodos y mostrar los marcos de pila y variables. Tambin se pueden enumerar los mtodos definidos para un objeto o clase en particular y le permite la lista y control de los hilos separados dentro de Ruby. La tabla 4 un poco ms adelante enumera todos los comandos disponibles para el depurador.

107

Figura 6. Afirmaciones Test::Unit

Si la instalacin de Ruby tiene activado soporte para readline, puede utilizar las teclas de cursor para moverse adelante y atrs en la historia de comandos y el uso de edicin de lnea de comandos para modificar la entrada anterior. Para hacerse una idea de como es el depurador de Ruby, aqu va un ejemplo de sesin (con la entrada del usuario en negrita). % ruby -r debug t.rb Debug.rb Emacs support available. t.rb:1:def fact(n) (rdb:1) list 1-9 [1, 10] in t.rb => 1 def fact(n) 2 if n <= 0 3 1 4 else 5 n * fact(n1) 6 end

108

7 end 8 9 p fact(5) (rdb:1) b 2 Set breakpoint 1 at t.rb:2 (rdb:1) c breakpoint 1, fact at t.rb:2 t.rb:2: if n <= 0 (rdb:1) disp n 1: n = 5 (rdb:1) del 1 (rdb:1) watch n==1 Set watchpoint 2 (rdb:1) c watchpoint 2, fact at t.rb:fact t.rb:1:def fact(n) 1: n = 1 (rdb:1) where --> #1 t.rb:1:in `fact #2 t.rb:5:in `fact #3 t.rb:5:in `fact #4 t.rb:5:in `fact #5 t.rb:5:in `fact #6 t.rb:9 (rdb:1) del 2 (rdb:1) c 120

Ruby Interactivo
Si quiere jugar con Ruby, le recomendamos Ruby Interactivo --irb, por sus siglas. irb es esencialmente una shell Ruby, similar en concepto a una shell del sistema operativo (completa, con control de trabajos). Proporciona un entorno donde se puede jugar con el lenguaje en tiempo real. Puede inciar irb en el smbolo del sistema. irb [ irb-options ] [ ruby_script ] [ program-arguments ] irb mostrar el valor de cada expresin a medida que se complete. Por ejemplo:
a=1+

% irb irb(main):001:0> irb(main):002:0* irb(main):003:0* => 2 irb(main):004:0> => 4 irb(main):005:0> irb(main):006:1> irb(main):007:1> => nil irb(main):008:0> Hello, world! => nil irb(main):009:0>

2 * 3 / 4 % 5 2+2 def test puts Hello, world! end test

irb tambin le permite crear subsesiones en las que cada una de las cuales puede tener su propio contexto. Por ejemplo, puede crear una subsesin con el mismo contexto (nivel superior) que la sesin originaria o crear una subsesin en el contexto de una determinada clase o instancia. La sesin de ejemplo que se muestra en la figura 7 en la pgina 111 muestra cmo se pueden crear subsesiones y cambiar entre ellas.

109

Para una descripcin completa de todos los comandos que soporta irb, se muestra la tabla de referencia ms adelante. Al igual que con el depurador, si su versin de Ruby fue construida con soporte para GNU readline, puede utilizar las teclas de flecha (como con Emacs) o combinaciones de teclas estilo vi para modificar las lneas individuales o volver atrs y volver a ejecutar o modificar una lnea anterior --igual que una shell de comandos. irb es una gran herramienta de aprendizaje: es muy til si quiere probar una idea de forma rpida y ver si funciona.

Soporte de Editor
El intrprete de Ruby est diseado para leer un programa de una sola pasada. Esto significa que usted puede canalizar todo un programa por un pipe a la entrada estndar del intrprete y funcionar muy bien. Podemos tomar ventaja de esta caracterstica para ejecutar cdigo Ruby desde el interior de un editor. En Emacs, por ejemplo, puede seleccionar una regin de texto Ruby y utilizar el comando Meta-| para ejecutar Ruby. El intrprete de Ruby usar la regin seleccionada como la entrada estndar, y la salida se destinar a un buffer llamado *Shell Command Output*. Esta caracterstica ha llegado a ser muy til mientras escribamos este libro, --slo tena que seleccionar unas pocas lneas de Ruby en medio de un prrafo y prubelo! Se puede hacer algo similar en el editor vi con :%ruby que sustituye el texto del programa con su salida, o :w !ruby, que muestra la salida sin afectar al buffer. Otros editores tienen caractersticas similares. Ya que estamos en el tema, este probablemente sera un buen momento para mencionar que hay un modo de Ruby para Emacs incluido en la distribucin de cdigo fuente de Ruby como ruby-mode.el en el subdirectorio /misc. Tambin puede encontrar los mdulos de resaltado de sintaxis para vim (una versin mejorada del editor vi), jed, y otros editores en la red. Echar una ojeada a preguntas frecuentes (FAQ) acerca de Ruby (http://rubyhacker.com/clrFAQ.html) para obtener una lista actualizada y referencias a otros recursos.

Pero, no funciona!
Con lo que ha ledo ya en este libro, usted comienza a escribir su programa propio en Ruby, pero, no funciona!. Aqu hay una lista de errores comunes y otros consejos. En primer lugar, ejecutar las secuencias de comandos con las advertencias activadas (la opcin -w de lnea de comandos). Si se olvida de una , en una lista de argumentos, sobre todo para imprimir, puede producir algunos mensajes de error muy extraos. Un error de anlisis en la ltima lnea del fuente a menudo indica que falta una palabra clave end (a veces un poco antes). Un atributo setter que no se est llamando. Dentro de una definicin de clase, Ruby analizar setter= como una asignacin a una variable local, no como una llamada al mtodo. Utilice la forma self.setter= para indicar la llamada al mtodo. class Incorrect attr_accessor :one, :two def initialize one = 1 # incorrect sets local variable self.two = 2 end end

110

Figura 7. Ejemplo de sesin irb.

En esta misma sesin irb, vamos a crear una nueva subsesin en el contexto de la clase VolumeKnob. Podemos usar fg 0 para volver a la sesin principal, mirar todos los trabajos actuales, y ver qu mtodos de instancia define VolumeKnob.

Hacer un objeto VolumeKnob nuevo y crear una nueva subsesin con este objeto segn el contexto.

Volver a la sesin principal, matar las subsesiones y salir.

obj = Incorrect.new obj.one -> nil obj.two -> 2 Objetos que parecen no estar bien configurados puede ser por una escritura incorrecta del mtodo initialize. class Incorrect attr_reader :answer def initialise # < < < spelling error @answer = 42 end end ultimate = Incorrect.new ultimate.answer -> nil

111

Sucede lo mismo si se escribe incorrectamente el nombre de una variable de instancia.

class Incorrect attr_reader :answer def initialize @anwser = 42 # < < < spelling error end end ultimate = Incorrect.new ultimate.answer -> nil Los parmetros de bloque estn en el mismo mbito que las variables locales. Si existe una variable local con el mismo nombre que un parmetro de bloque cuando ste se ejecuta, la variable ser modificada por la llamada al bloque. Esto puede o no puede ser una buena cosa. c = carbon i = iodine elements = [ c, i ] elements.each_with_index do |element, i| # do some chemistry end c i -> -> carbon 1

Tenga cuidado con los problemas de prioridad, especialmente cuando se utiliza {} en lugar de do/end . def one(arg) if block_given? block given to one else arg end end def two if block_given? block given to two end end result1 = one two { three } result2 = one two do three end puts With braces, result = puts With do/end, result = produce: With braces, result = block given to two returns three With do/end, result = block given to one returns three La salida de un terminal puede estar en un buffer. Esto significa que puede no ver de inmediato un mensaje. Adems, si escribe mensajes a $stdout y $stderr, la salida puede no aparecer en el orden que se esperaba. Utilice siempre I/O sin buffer (configuracin sync=true) para los mensajes de depuracin.

returns #{yield}

returns #{yield}

#{result1} #{result2}

112

Si los nmeros no salen bien tal vez son cadenas. El texto ledo de un archivo es un String y Ruby no lo convierte automticamente en un nmero. Una llamada a Integer har maravillas (y producir una excepcin si la entrada no es un entero bien formado). Un error comn que los programadores de Perl hacen es: while line = gets num1, num2 = line.split(/,/) # ... end Puede volver a escribir esto como:

while line = gets num1, num2 = line.split(/,/) num1 = Integer(num1) num2 = Integer(num2) # ... end O puede convertir todas las cadenas utilizando map :

while line = gets num1, num2 = line.split(/,/).map {|val| Integer(val) } # ... end aliasing no intencionado. Si est utilizando un objeto como la clave de un hash, asegrese de que no cambia su valor hash (o los arreglos para llamar Hash#rehash si lo hace). arr = [1, 2] hash = { arr => value } hash[arr] -> value arr[0] = 99 hash[arr] -> nil hash.rehash -> {[99, 2]=>value} hash[arr] -> value Asegrese de que la clase del objeto que est utilizando es lo que usted piensa que es. En caso de duda, utilice puts mi_obj.class. Asegrese de que los nombres de mtodo comienzan con una letra minscula y los nombres de clase y constante comienzan con una letra mayscula. Si las llamadas a mtodos no estn haciendo lo que se espera, asegrese de que ha puesto entre parntesis los argumentos. Asegrese de que el parntesis de apertura de los parmetros de mtodo est pegado al nombre del mismo, sin espacios intermedios. Utilice irb y el depurador.

Utilice Object#freeze. Si usted sospecha que alguna porcin de cdigo desconocido est fijando una variable a un valor falso, trate de congelar la variable. El culpable ser capturado al intentar modificar la variable. Una mayor tcnica hace escribir cdigo Ruby tanto ms fcil y divertido. Desarrolle sus aplicaciones de forma incremental. Escriba unas pocas lneas de cdigo y ejectelo. Utilize Test::Unit y escriba algunas pruebas. Escriba unas pocas lneas ms de cdigo y ejerctelo. Una de las principales ventajas de un lenguaje de tipos dinmicos es que las cosas no tienen por qu estar completas antes de usarlas.

113

Pero es demasiado lento!


Ruby es un lenguaje de alto nivel interpretado, y como tal, no puede llevar un desempeo tan rpido como un lenguaje de bajo nivel como C. En las siguientes secciones, vamos a enumerar algunas cosas bsicas que usted puede hacer para mejorar el rendimiento. Tambin eche un vistazo en el ndice de bajo rendimiento para otras sugerencias. Por lo general, los programas de ejecucin lenta tienen uno o dos cementerios de rendimiento, lugares donde el tiempo de ejecucin va a morir. Bsquelos, mejorelos y de repente su programa entero volver a la vida. El truco es encontrarlos. El mdulo Benchmark y los perfiladores de Ruby pueden ayudar.

Benchmark
Se puede utilizar el mdulo Benchmark, que se describe ms adelante, para ver el desempeo de secciones de cdigo. Por ejemplo, podemos preguntarnos que es ms rpido: un bucle grande utilizando variables locales en el bloque o el uso de variables del mbito circundante. La figura 8 muestra cmo utilizar Benchmark para averiguarlo. Hay que tener cuidado con el benchmarking, porque muchas veces los programas de Ruby pueden funcionar lentamente debido a la sobrecarga de la recoleccin de basura. Debido a que esta recoleccin de basura puede ocurrir que en cualquier momento durante la ejecucin del programa, puede encontrar que la comparacin da resultados engaosos y muestre que una seccin de cdigo se ejecuta lentamente, cuando en realidad la ralentizacin fue causada porque la recoleccin de basura se dispara mientras se ejecuta el cdigo. El mdulo Benchmark tiene el mtodo bmbm que ejecuta las pruebas dos veces, una como un ensayo y otra para medir el desempeo en un intento de minimizar la distorsin introducida por la recoleccin de basura. El proceso de evaluacin comparativa (benchmarking) en s tiene relativamente buenos modales --no ralentiza el programa mucho ms.

El Perfilador
Ruby cuenta con un analizador de cdigo (documentado ms adelante). El perfilador muestra el nmero de veces que se llama en el programa a cada mtodo y el tiempo promedio y acumulativo que Ruby pasa en ellos.

114

Se pueden agregar perfiles al cdigo con la opcin -r profile en la lnea de comandos o desde dentro del cdigo utilizando require profile. Por ejemplo: require profile count = 0 words = File.open(/usr/share/dict/words) while word = words.gets word = word.chomp! if word.length == 12 count += 1 end end puts #{count} twelve-character words La primera vez que se corre esto (sin perfiles) contra de un diccionario de casi 235.000 palabras, tarda varios segundos en completarse. Como parece excesivo, hemos aadido la opcin -r profile en la lnea de comandos ejecutndolo de nuevo. Eventualmente hemos visto que la salida se pareca a lo siguiente. 20460 twelve-character words % cumulative self time seconds seconds calls 7.76 12.01 12.01 234937 7.75 24.00 11.99 234938 7.71 35.94 11.94 234937 7.62 47.74 11.80 234937 0.59 48.66 0.92 20460 0.01 48.68 0.02 1 0.00 48.68 0.00 1 0.00 48.68 0.00 1 0.00 48.68 0.00 1 0.00 48.68 0.00 1 0.00 48.68 0.00 2 0.00 0.00 48.68 0.00 1 0.00 self total ms/call ms/call name 0.05 0.05 String#chomp! 0.05 0.05 IO#gets 0.05 0.05 String#length 0.05 0.05 Fixnum#== 0.04 0.04 Fixnum#+ 20.00 20.00 Profiler__.start_profile 0.00 0.00 File#initialize 0.00 0.00 Fixnum#to_s 0.00 0.00 File#open 0.00 0.00 Kernel.puts 0.00 IO#write 154800.00 #toplevel

Lo primero a notar es que los tiempos se muestran mucho ms lentos que cuando el programa se ejecuta sin el perfilador. Este conlleva una seria sobrecarga, pero asumiendo que se aplica en todos los mbitos, los nmeros relativos son an significativos. Este programa en particular pasa claramente mucho tiempo en el bucle, que ejecuta casi 235.000 veces. Probablemente podramos mejorar el rendimiento si se pueden hacer las cosas en el bucle menos costosas o eliminando el bucle completamente. Una forma de hacer esto ltimo es mediante la lectura de la lista de palabras en una cadena larga y entonces usar un patrn que corresponda para extraer las palabras de doce caracteres. require profile words = File.read(/usr/share/dict/words) count = words.scan(PATT= /^............\n/).size puts #{count} twelve-character words Nuestros nmeros de perfil son ahora mucho mejores (y el programa se ejecuta hasta cinco veces ms rpido cuando se vuelve a tomar el perfil). 20460 twelve-character words % cumulative self self time seconds seconds calls ms/call 96.67 0.29 0.29 1 290.00 6.67 0.31 0.02 1 20.00 0.00 0.31 0.00 1 0.00 0.00 0.31 0.00 1 0.00 0.00 0.31 0.00 2 0.00 total ms/call 290.00 20.00 0.00 0.00 0.00

name String#scan Profiler__.start_profile Array#size Kernel.puts IO#write

115

0.00 0.00 0.00

0.31 0.31 0.31

0.00 1 0.00 1 0.00 1

0.00 0.00 0.00

0.00 300.00 0.00

Fixnum#to_s #toplevel File#read

Recuerde revisar despus el cdigo sin el perfilador, ya que a veces la ralentizacin que el perfilador introduce puede enmascarar otros problemas. Ruby es un lenguaje extraordinariamente transparente y expresivo, pero no exime al programador de la necesidad de aplicar el sentido comn: la creacin de objetos innecesarios realizando trabajos que no son necesarios y la creacin de cdigo hinchado ralentizar sus programas sin importar el lenguaje.

Ruby en su Entorno
Ruby y su Mundo
Es un hecho desafortunado de la vida que nuestras aplicaciones tienen que lidiar con un mundo grande y malo. En este captulo, veremos cmo Ruby interacta con su entrono. Los usuarios de Microsoft Windows tambin puedrn ver las especificaciones de su plataforma ms adelante.

Argumentos de la Lnea de Comandos


En el principio fue la lnea de comandos(Este es el ttulo de un fabuloso ensayo de Neal Stephenson disponible en lnea en http://www.spack.org/index.cgi/InTheBeginningWasTheCommandLine). Independientemente del sistema en el que Ruby es desplegado, ya sea una super estacin de trabajo de grficos cientficos de alta calidad, o, un dispositivo PDA integrado, hay que iniciar el intrprete de Ruby de alguna manera, y sto, nos da la oportunidad de pasar argumentos de lnea de comandos. Una lnea de comandos Ruby consta de tres partes: opciones para el intrprete de Ruby, el nombre de un programa a ejecutar opcionalmente, y, opcionalmente tambin, un conjunto de argumentos para ese programa. ruby [ options ] [ --- ] [ programfile ] [ arguments ] Las opciones de Ruby terminan con la primera palabra en la lnea de comandos que no comienza con un guin, o por la bandera especial -- (dos guiones). Si no hay ningn nombre de archivo en la lnea de comandos, o si el nombre del archivo es un guin (-), Ruby lee el cdigo fuente del programa a partir de la entrada estndar. Los argumentos para el programa van a continuacin del nombre del programa. Por ejemplo:

% ruby -w - Hello World permitir los avisos y leer un programa de la entrada estndar pasndole la cadena entre comillas Hola Mundo como un argumento.

Opciones de la Lnea de Comandos


-0[octal]. La bandera 0 (el dgito cero) especifica el carcter separador de registro (\0, si no dgitos siguientes). --00 indica el modo prrafo: los registros estn separados por dos caracteres separadores de registro sucesivos. 0777 lee todo el archivo a la vez (ya que es un carcter no vlido). Establece $/. -a Modo auto divisin cuando se utiliza con -n o -p, equivalente a ejecutar $F = $ _.split en la parte superior de cada iteracin del bucle. -C directorio. Cambiar el directorio de trabajo al directorio dado antes de ejecutar.

116

Tabla 4. Comandos del Depurador.

-c chequear slo la sintaxis, no se ejecuta el programa.

--copyright. Imprime la nota de copyright y sale. -d, --debug. Establece $DEBUG y $VERBOSE en true. Esto puede ser utilizado por sus programas para permitir un seguimiento adicional.

117

-e comando. Ejecuta el comando como una lnea de cdigo fuente Ruby. Se permiten varias -es, y los comandos son tratados como varias lneas en el mismo programa. Si se omite programfile cuando est presente -e, se detiene la ejecucin despus de que los -e comandos se han ejecutado. Los programas que se ejecutan con -e tienen acceso al antiguo comportamiento de los rangos y las expresiones regulares en condicionales --rangos de nmeros enteros se comparan con el nmero de la entrada de lnea actual y las expresiones regulares contra $_. -F patrn. Especifica el separador de campos de entrada ($;) que se utiliza como valor predeterminado para split() (afecta a la opcin -a). -h, --help. Muestra una pantalla de ayuda.

-I directorios. Especifica los directorios que se anteponen a $LOAD_PATH ($:). Mltiples opciones -I pueden estar presentes y varios directorios pueden aparecer despus de cada -I, separados por dos puntos (:) en sistemas tipo Unix y por un punto y coma (;) en DOS/Windows. -i [extension]. Edita archivos ARGV. Para cada archivo nombrado en ARGV, cualquier cosa que escriba en la salida estndar se guarda en el contenido de ese archivo. Se har una copia de seguridad del archivo si se suministra la extensin. % ruby -pi.bak -e gsub(/Perl/, Ruby) *.txt -K kcodigo. Especifica el conjunto de cdigos que se utilizarn. Esta opcin es til sobre todo cuando Ruby se utiliza para el procesamiento del idioma japons. kcode puede ser uno de: e, E para la EUC; s, S para SJIS; u, U para UTF-8; o bien a, A, n, N para ASCII. -l Permite procesamiento automtico de final de lnea. Establece $\ con el valor de $/ y corta cada lnea de entrada de forma automtica. -n Asume un bucle while ... en torno a su programa. Por ejemplo, se puede implementar un comando grep como % ruby -n -e print if /wombat/ *.txt -p Coloca el cdigo del programa en el bucle while ...; print.

% ruby -p -e $_.downcase! *.txt -r librera. Requiere la biblioteca llamada antes de ejecutar. -S Busca el archivo de programa en RUBYPATH o donde indique la variable de entorno PATH.

-s Cualquier modificador de lnea de comandos encontrado tras el nombre del programa, pero antes cualquier argumento de archivo o de --, se elimina de ARGV y se asigna a una variable global llamada para el cambio. En el siguiente ejemplo, el efecto de esto sera establecer la variable $opt en electric. % ruby -s prog -opt=electric ./mydata -T[nivel]. Ajusta el nivel de seguridad, que entre otras cosas permite pruebas en modo conteminado (ms adelante se ver). Establece $SAFE. -v, --verbose. Establece $VERBOSE en true, lo que permite el modo detallado. Tambin se imprime el nmero de versin. En modo detallado se imprimen las advertencias de compilacin. Si no aparece ningn nombre de archivo de programa en la lnea de comandos, Ruby sale. --version. Muestra el nmero de versin de Ruby y sale.

-w Activa el modo detallado. A diferencia de -v, lee el programa de la entrada estndar si no estn presentes archivos de programa en la lnea de comandos. Se recomienda ejecutar sus programas Ruby con -w.

118

-W nivel. Ajusta el nivel de las alertas emitidas. Con un nivel o dos (o sin especificar nivel), equivalente a -w -- se dan advertencias adicionales. Si el nivel es 1, se ejecuta en el nivel estndar (por defecto). Con -W0 no se se d absolutamente ninguna advertencia ( incluyendo las emitidas utilizando Kernel.warn !). -X directorio. Cambia el directorio de trabajo al directorio dado antes de ejecutar. Igual que -C directorio. -x [directorio] Se despoja del texto antes de la lnea #!ruby y cambia el directorio de trabajo a un directorio dado si se le da. -y, --yydebug. Habilita la depuracin yacc en el analizador (fooooorma demasiada informacin).

ARGV
Los argumentos de lnea de comandos tras el nombre del programa estn disponibles para el programa Ruby en la matriz global ARGV. Por ejemplo, supongamos que test.rb contiene el siguiente programa: ARGV.each {|arg| p arg } Invocndole en la lnea de comandos:

% ruby -w test.rb Hello World a1 1.6180 Genera la salida siguiente:

Hello World a1 1.6180 Hay aqu un gotcha para los programadores de C -- ARGV[0] es el primer argumento del programa, no el nombre del programa. El nombre del programa en curso est disponible en la variable global $0. Tenga en cuenta que todos los valores de ARGV son cadenas. Si el programa intenta leer de la entrada estndar (o utiliza el archivo especial ARGF, que se describe ms adelante), los argumentos del programa en ARGV se tomarn comonombres de archiv, y Ruby leerde estos archivos. Si el programa tiene una mezcla de argumentos y nombres de archivo, asegrese de vaciar los argumentos que no son nombres de archivo del arrray ARGV antes de la lectura.

Terminacin de Programa
El mtodo Kernel#exit termina su programa devolviendo un valor de estado para el sistema operativo. Sin embargo, a diferencia de otros lenguajes, la salida no termina el programa inmediatamente. Kernel#exit primero lanza una excepcin SystemExit, que se puede capturar y a continuacin, realiza una serie de acciones de limpieza incluyendo la ejecucin de cualquiera de los mtodos at_exit y finalizadores de objetos registrados. Se puede ver la referencia de Kernel#exit ms adelante.

Variables de Entorno
Se puede acceder a las variables de entorno del sistema con la variable predefinida ENV. Responde a los mismos mtodos que Hash (ENV en realidad no es un hash, pero si es necesario, se puede convertir en un hash utilizando ENV#to_hash). ENV[SHELL] -> /bin/sh ENV[HOME] -> /Users/dave ENV[USER] -> dave ENV.keys.size -> 34 ENV.keys[0, 7] -> [MANPATH, TERM_PROGRAM, TERM, SHELL, SAVEHIST, HISTSIZE, MAKEFLAGS]

119

Ruby lee los valores de algunas variables de entorno cuando se inicia por primera vez. Estas variables modifican el comportamiento del intrprete como se muestra en la tabla 5. Tabla 5. Variables de entorno utilizadas por Ruby.

Nombre de Variable Descripcin


DLN_LIBRARY_PATH Ruta de bsqueda para los mdulos de carga dinmica. HOME Indicadores al directorio home del usuario. Se utiliza para la expansin de ~ en nombres de archivos y directorios. LOGDIR Puntero de reserva para el directorio home del usuario en caso de que $HOME no est definida. Utilizado slo por Dir.chdir. OPENSSL_CONF Especifica la ubicacin del archivo de configuracin de OpenSSL. RUBYLIB Ruta de bsqueda adicional para los programas Ruby ($SAFE debe ser 0). RUBYLIB_PREFIX (Slo Windows) Mutila la ruta de bsqueda RUBYLIB por la adicin de este prefijo a cada componente. RUBYOPT Opciones adicionales de lnea de comandos para Ruby, examinadas despus de analizar las de lnea de comandos real ($SAFE debe ser 0). RUBYPATH Con la opcin -S, la ruta de bsqueda para los programas Ruby (por defecto PATH). RUBYSHELL Shell a utilizar al lanzar un proceso en Windows. Si no se establece, tambin se comprobar SHELL o COMSPEC. RUBY_TCL_DLL Ignorar el nombre por defecto para la DLL o librera compartida TCL. RUBY_TK_DLL Ignorar el nombre por defecto para la DLL o librera compartida Tk. Tanto sta como RUBY_TCL_DLL se deben establecer si se van a utilizar.

Escribir Variables de Entorno


Un programa Ruby puede escribir en el objeto ENV. En la mayora de los sistemas esto cambia los valores de las variables de entorno correspondientes. Sin embargo, este cambio es local al proceso que lo hace y para cualquier proceso hijo generado posteriormente. Esta herencia de las variables de entorno se ilustra en el cdigo que sigue. Un subproceso cambia una variable de entorno, y se inicia otro proceso que hereda este cambio. Sin embargo, el cambio no es visible para el padre de origen. (Esto slo sirve para demostrar que los padres nunca saben realmente lo que estn haciendo sus hijos.) puts In parent, term = #{ENV[TERM]} fork do puts Start of child 1, term = #{ENV[TERM]} ENV[TERM] = ansi fork do puts Start of child 2, term = #{ENV[TERM]} end Process.wait puts End of child 1, term = #{ENV[TERM]} end Process.wait puts Back in parent, term = #{ENV[TERM]} produce: In parent, term = xtermcolor Start of child 1, term = xtermcolor Start of child 2, term = ansi End of child 1, term = ansi Back in parent, term = xtermcolor

Dnde Encuentra Ruby Sus Mdulos


Se utiliza require o load para llevar un mdulo de librera a su programa Ruby. Algunos de estos mdulos se suministran con Ruby, otros pueden estar instalados fuera de Ruby Application Archive y otros puede haberlos escrito usted mismo. Cmo los encuentra Ruby?

120

Cuando Ruby se construye para su mquina particular, permite predefinir un conjunto de directorios estndar para mantener las cosas de librera. Entonces estos directorios dependen de la mquina en cuestin. Usted puede determinar desde la lnea de comandos con algo como % ruby -e puts $: En una mquina Linux tpica, es probable encontrar algo como lo siguiente. Notar que a partir de Ruby 1.8, el orden de estos directorios se ha cambiado --los directorios de arquitectura especfica ahora siguen a sus homlogos independientemente de la mquina. /usr/local/lib/ruby/site_ruby/1.8 /usr/local/lib/ruby/site_ruby/1.8/i686-linux /usr/local/lib/ruby/site_ruby /usr/local/lib/ruby/1.8 /usr/local/lib/ruby/1.8/i686-linux Los directorios site_ruby estndestinados a contener mdulos y extensiones que haya aadido. Los directorios dependientes de la arquitectura ( i686-linux en este caso) tienen ejecutables y otras cosas especficas para esta mquina en particular. Todos estos directorios se incluyen automticamente en la bsqueda de Ruby para los mdulos. A veces esto no es suficiente. Tal vez usted vuelva a trabajar en un gran proyecto escrito en Ruby, y usted y sus colegas han construido una considerable biblioteca de cdigo Ruby. Entonces quiere que todos los del equipo tengan acceso a todo este cdigo. Usted tiene un par de opciones para lograr esto. Si su programa se ejecuta en un nivel seguro establecido en cero (ms adelante se ver esto), se puede entonces establecer la variable de entorno RUBYLIB a una lista de uno o ms directorios donde buscar (El separador entre las entradas depende de la plataforma. Para Windows, es el punto y coma. Para Unix, son dos puntos, : ). Si su programa no es setuid, puede utilizar el parmetro de lnea de comandos -I para hacer lo mismo. La variable de Ruby $: es una serie de lugares para buscar los archivos cargados. Como hemos visto, esta variable se inicializa en la lista de directorios estndar, adems de cualquier otra adicional que se especifica utilizando RUBYLIB e -I. Siempre se pueden aadir directorios adicionales para esta serie dentro de su programa en ejecucin. Para hacer las cosas ms interesantes, una nueva forma de organizacin de libreras lleg justo a tiempo para este libro. Un poco ms adelante se describe RubyGems, un sistema de paquetes con capacidad de gestin a travs de la red.

Construir el Entorno
Cuando Ruby es compilado para una arquitectura particular, todos los ajustes pertinentes utilizados para su construccin (incluyendo la arquitectura de la mquina en la que se compil, las opciones del compilador, el directorio de cdigo fuente, etc) se escriben en el mdulo Config dentro del archivo de librera rbconfig.rb. Despus de la instalacin, cualquier programa Ruby puede usar este mdulo para obtener detalles sobre cmo se compil Ruby. require rbconfig.rb include Config CONFIG[host] -> CONFIG[libdir] ->

powerpcappledarwin7.5.0 /Users/dave/ruby1.8/lib

Las libreras de extensin utilizan este archivo de configuracin con el fin de compilar y enlazar correctamente en cualquier arquitectura dada. Vase ms adelante el captulo Extensin de Ruby y la referencia para mkmf para ms detalles.

121

Ruby Shell Interactivo


Unas pginas atrs se introdujo irb, un mdulo de Ruby que le permite entrar en los programas Ruby de forma interactiva y ver los resultados inmediatamente. En este captulo se entra en ms detalles sobre el uso y la personalizacin de irb.

De Lnea de Comandos
irb se ejecuta desde la lnea de comandos.

irb [ irb-options ] [ ruby_script ] [ program arguments ] Las opciones de lnea de comandos para irb se enumeran en la tabla 6. Por lo general, se encontrar con irb sin opciones, pero si quieres ejecutar un script y ver la descripcin con puntos y comas de que se ejecuta, puede proporcionar el nombre del script Ruby y las opciones para ese script.

Opcin Descripcin
--back-trace-limit n Mostrar la informacin de backtrace con las ltimas n entradas. El valor predeterminado es 16. -d Establecer $DEBUG en true (lo mismo que ruby -d). -f Suprime la lectura de ~/.irbrc. -I path Especifica el directorio $LOAD_PATH. --inf-ruby-mode Establecer irb para ejecutarse en inf-ruby-mode bajo Emacs. Cambia el prompt y suprime --readline. --inspect Utiliza Object#inspect para el formato de salida. (por defecto, menos en el modo matemtico). --irb_debug n Ajuste el nivel de depuracin interna en n (slo es til para desarrollo irb). -m Modo matemtico (soporte para fracciones y matrices) --noinspect No utilizar inspect para la salida. --noprompt No mostrar un prompt. --noreadline No utilizar el mdulo de extensin Readline. --prompt prompt-mode Cambiar el prompt. Los modos prompt predefinidos son null, default, classic, simple, xmp, e inf-ruby. --promp-tmode Lo mismo que --prompt. -r load-module Lo mismo que ruby -r. --readline Utilizar el mdulo de extensin readline. --simple-prompt Utilizar prompt simple. --tracer Mostrar la traza para la ejecucind e comandos. -v,--version Imprimir la versin de irb. Una vez iniciado, irb muestra un mensaje y espera la entrada. En los ejemplos que siguen vamos a utilizar el prompt irb por defecto, que muestra el smbolo actual, el nivel de anidamiento y el nmero de lnea. En el smbolo del sistema puede escribir cdigo Ruby. irb incluye un analizador de Ruby, por lo que sabe cuando los estados estn incompletos. Cuando esto ocurre, el indicador termina con un asterisco. Puede abandonar irb escribiendo exit o quit o introduciendo un carcter de fin de archivo (a menos que est establecido el modo IGNORE_EOF). % irb irb(main):001:0> => 3 irb(main):002:0> irb(main):003:0* => 7 irb(main):004:0> %

1 + 2 3 + 4 quit 122

Durante una sesin irb, el trabajo que hace se acumula en el espacio de trabajo irb. Las variables que se definen, los mtodos y las clases que se crean son recordados y pueden recordarse posteriormente . irb(main):001:0> irb(main):002:1> irb(main):003:1> irb(main):004:2> irb(main):005:2> irb(main):006:2> irb(main):007:1> => nil irb(main):008:0> 1 1 2 3 => nil

def fib_up_to(n) f1, f2 = 1, 1 while f1 <= n puts f1 f1, f2 = f2, f1+f 2 end end fib_up_to(4)

Observe el valor de retorno nil. Es el resultado de definir el mtodo y luego ejecutarlo. La salida del mtodo son nmeros de Fibonacci, pero luego vuelve a nil. Un gran uso de irb es experimentar con el cdigo que ya ha escrito. Quizs usted desea localizar un error o tal vez slo quier jugar. Si carga el programa en irb, a continuacin puede crear instancias de las clases que defina e invocar sus mtodos. Por ejemplo, el cdigo del archivo /fib_up_to.rb contiene la definicin de mtodo siguiente: def fib_up_to(max) i1, i2 = 1, 1 while i1 <= max yield i1 i1, i2 = i2, i1+i2 end end Podemos cargarlo en irb y jugar con el mtodo.

% irb irb(main):001:0> load code/fib_up_to.rb => true irb(main):002:0> result = [] => [] irb(main):003:0> fib_up_to(20) {|val| result << val} => nil irb(main):004:0> result => [1, 1, 2, 3, 5, 8, 13] En este ejemplo, se utiliza load en lugar de require para incluir el archivo en nuestra sesin. Hacemos esto como una cuestin de prctica: load nos permite cargar el mismo archivo varias veces, as que si encuentra un error y edita el archivo, se podra volver a cargar en la sesin irb.

Autocompletado
Si la instalacin de Ruby tiene soporte para readline, se puede utilizar la caracterstica de autocompletado. Una vez cargado (y vamos s ver como se carga en breve), el autocompletado cambia el significado de la tecla TAB cuando se escriben expresiones en el prompt irb. Cuando se pulsa TAB hasta cierto punto de una palabra, irb va a buscar las terminaciones posibles que tengan sentido en ese momento. Si slo hay una, el IRB la va a rellenar de forma automtica. Si hay ms de una opcin vlida, irb inicialmente no hace nada. Sin embargo, si usted presiona TAB, se mostrar la lista de terminaciones vlidas en ese momento.

123

Por ejemplo, es posible que en medio de una sesin irb, acabe de asignar un objeto string a la variable a. irb(main):002:0> a = cat => cat Ahora quieren probar el mtodo de String#reverse en ese objeto. Empieza por escribir a.re y luego pulsa TAB dos veces. irb(main):003:0> a.re TABTAB a.reject a.replace a.respond_to? a.reverse a.reverse! irb lista de todos los mtodos admitidos por el objeto cuyos nombres empiezan por re. Vemos lo que queramos, reverse, e introducimos el siguiente carcter de su nombre, v, seguido de la tecla TAB. irb(main):003:0> a.rev TAB irb(main):003:0> a.reverse => tac irb(main):004:0> irb responde a la tecla TAB ampliando el nombre tan lejos como pueda ir, en este caso completando la palabra reverse. Si teclea TAB dos veces en este punto, se nos muestran las opciones actuales, reverse y reverse!. Sin embargo, como el que queremos es reverse, presionamos ENTER y se ejecuta la lnea de cdigo. La implementacin del tabulador no se limita slo a los nombres integrados. Si definimos una clase en irb, la implementacin del tabulador funciona cuando tratamos de acogernos a alguno de sus mtodos. irb(main):004:0> class Test irb(main):005:1> def my_method irb(main):006:2> end irb(main):007:1> end => nil irb(main):008:0> t = Test.new => #<Test:0x35b724> irb(main):009:0> t.my TAB irb(main):009:0> t.my_method La implementacin del tabulador se implementa como una librera de extensin, irb/completion. Se puede cargar al invocar irb desde la lnea de comandos. % irb -r irb/completion Tambin puede cargar la libera completion cuando irb se est ejecutando.

irb(main):001:0> require irb/completion => true Si utiliza la implementacin del tabulador todo el tiempo, es probable que sea ms conveniente poner el comando require en su archivo .irbrc. require irb/completion

Subsesiones
irb soporta mltiples sesiones concurrentes. Una de ellos es siempre la actual, las otros permanecen latentes hasta que se activan. Entrando el comando irb dentro de irb se crea un subsesin. Introduciendo el comando jobs se enumeran todas las sesiones e introduciendo fg, se activa una sesin inactiva en particular.

124

Este ejemplo tambin ilustra la opcin -r de lnea de comandos, que carga el archivo dado antes de comenzar irb. % irb -r code/fib_up_to.rb irb(main):001:0> result = [] => [] irb(main):002:0> fib_up_to(10) {|val| result << val } => nil irb(main):003:0> result => [1, 1, 2, 3, 5, 8] irb(main):004:0> # Crear una sesin irb anidada irb(main):005:0* irb irb#1(main):001:0> result = %w{ cat dog horse } => [cat, dog, horse] irb#1(main):002:0> result.map {|val| val.upcase } => [CAT, DOG, HORSE] irb#1(main):003:0> jobs => #0->irb on main (#<Thread:0x331740>: stop) #1->irb#1 on main (#<Thread:0x341694>: running) irb#1(main):004:0> fg 0 irb(main):006:0> result => [1, 1, 2, 3, 5, 8] irb(main):007:0> fg 1 irb#1(main):005:0> result => [cat, dog, horse] Si se especifica un objeto cuando se crea un subsesin, ese objeto se convierte en el valor de s mismo (self) en ese enlace. Esta es una forma cmoda de experimentar con los objetos. En el siguiente ejemplo, creamos un subsesin con la cadena wombat como el objeto por defecto. Los mtodos sin receptor sern ejecutado por este objeto. % irb irb(main):001:0> self => main irb(main):002:0> irb wombat irb#1(wombat):001:0> self => wombat irb#1(wombat):002:0> upcase => WOMBAT irb#1(wombat):003:0> size => 6 irb#1(wombat):004:0> gsub(/[aeiou]/, *) => w*mb*t irb#1(wombat):005:0> irb_exit irb(main):003:0> self => main irb(main):004:0> upcase NameError: undefined local variable or method `upcase for main:Object

Configuracin
irb es muy configurable. Puede configurar las opciones de configuracin con el comando options, dentro de un archivo de inicializacin y mientras estamos dentro del mismo irb.

Archivo de Inicializacin
irb utiliza un archivo de inicializacin en el que puede definir las opciones de uso comn o ejecutar cualquier declaracin Ruby necesaria. Cuando se ejecuta irb, intentar cargar un archivo de inicializacin de los siguientes por este orden: ~/.irbrc, .irbrc, irb.rc, _irbrc y $irbrc. 125

En el archivo de inicializacin puede ejecutar cualquier cdigo Ruby arbitrario. Tambin puede establecer valores de configuracin. La lista de variables de configuracin las veremos ahora un poco ms adelante --los valores que se pueden utilizar en un archivo de inicializacin son los smbolos (comienzan con dos puntos). Se pueden utilizar estos smbolos para establecer los valores en la tabla hash IRB.conf. Por ejemplo, para que el valor por defecto del prompt para todas las sesiones de irb sea SIMPLE, se puede tener lo siguiente en el fichero de inicializacin: IRB.conf[:PROMPT_MODE] = :SIMPLE Como una interesante peculiaridad sobre la configuracin de irb, se puede establecer IRB. conf[:IRB_RC] a un objeto Proc. Este proc se invoca cada vez que el contexto cambia e irb recibir la configuracin de ese contexto como un parmetro. Usted puede utilizar esta funcin para cambiar la configuracin de forma dinmica en funcin del contexto. Por ejemplo, el archivo .irbrc siguiente establece el prompt de modo que slo el prompt main muestra el nivel irb, pero le pide la continuacin y el resultado todava se alinea. IRB.conf[:IRB_RC] = proc do |conf| leader = * conf.irb_name.length conf.prompt_i = #{conf.irb_name} --> conf.prompt_s = leader + \- conf.prompt_c = leader + \-+ conf.return_format = leader + ==> %s\n\n puts Welcome! end Una sesin irb con este archivo .irbrc tiene el siguiente aspecto:

% irb Welcome! irb --> 1 + 2 ==> 3 irb --> 2 + \-+ 6 ==> 8

Extendiendo irb
Como lo que escribe para irb se interpreta como cdigo Ruby, se puede efectivamente extender irb mediante la definicin de nuevos mtodos de nivel superior. Por ejemplo, puede ser capaz de buscar la documentacin de una clase o mtodo mientras est en irb. Si aade lo siguiente a su archivo .irbrc, aadir un mtodo llamado ri que invoca el comando externo ri en sus argumentos: def ri(*names) system(%{ri #{names.map {|name| name.to_s}.join( )}}) end La prxima vez que inicie irb, ser capaz de utilizar este mtodo para obtener la documentacin.
irb(main):001:0> ri Proc --------------------------------------------------------- Class: Proc Proc objects are blocks of code that have been bound to a set of local variables. Once bound, the code may be called in different contexts and still access those variables. and so on... irb(main):002:0> ri :strftime ------------------------------------------------------Time#strftime time.strftime( string ) => string ---------------------------------------------------------------------

126

Formats time according to the directives in the given format string. Any text not listed as a directive will be passed through to the output string. Format %a %A %b %B %c %d and so meaning: The abbreviated weekday name (``Sun) The full weekday name (``Sunday) The abbreviated month name (``Jan) The full month name (``January) The preferred local date and time representation Day of the month (01..31) on...

irb(main):003:0> ri String.each ------------------------------------------------------String#each str.each(separator=$/) |substr| block => str str.each_line(separator=$/) |substr| block => str ------------------------------------------------------------------- Splits str using the supplied parameter as the record separator ($/ by default), passing each substring in turn to the supplied block. If a zero-length record separator is supplied, the string is split on \n characters, except that multiple successive newlines are appended together. print Example one\n hello\nworld.each |s| p s and so on...

Configuracin Interactiva
La mayora de los valores de configuracin estn disponibles mientras se est corriendo irb. La lista que comienza en esta pgina muestra estos valores como conf.xxx. Por ejemplo, para cambiar el prompt de vuelta a DEFAULT, se puede hacer lo siguiente: irb(main):001:0> 1 + irb(main):002:0* 2 => 3 irb(main):003:0> conf.prompt_mode = :SIMPLE => :SIMPLE >> 1 + ?> 2 => 3

Opciones de Configuracin irb


En las descripciones que siguen, una etiqueta de la siguiente forma :XXX representa una clave utilizada en el hash IRB.conf en un archivo de inicializacin, y conf.xxx, representa un valor que se puede establecer de forma interactiva. El valor entre corchetes al final de la descripcin es la opcin por defecto. :AUTO_INDENT / conf.auto_indent_mode Si es true, irb sangra las estructuras anidadas como se escriben. [false] :BACK_TRACE_LIMIT / conf.back_trace_limit Muestra las n lneas iniciales y n finales de la traza de ejecucin. [16] :CONTEXT_MODE Qu binding utilizar para las nuevas reas de trabajo: 0 -> proc en el nivel superior, 1 -> binding en un archivo cargado annimo, 2 -> binding por hilo en un archivo cargado, 3 -> binding en una funcin de nivel superior. [3]. (Un binding es una ligadura o referencia a otro smbolo ms largo y complicado, y que se usa frecuentemente).

127

:DEBUG_LEVEL / conf.debug_level Ajusta a n el nivel de depuracin interna. Es til si se est depuracin irb lexer. [0] :IGNORE_EOF / conf.ignore_eof Especifica el comportamiento de un fin de archivo recibido en la entrada. Si es true, se tendr en cuenta, si es false, irb se cerrar. [false] :IGNORE_SIGINT / conf.ignore_sigint Si es false, ^C (Ctrl+C) har salir de irb. Si es true, ^C durante la entrada cancela la entrada y retorna al nivel superior. Durante la ejecucin, ^C abortar la operacin actual. [true] :INSPECT_MODE / conf.inspect_mode Especifica como se mostrarn los valores: true utiliza inspect, false utiliza to_s y nil utiliza inspect en modo no matemtico y to_s en modo matemtico. [nil] :IRB_RC Se peude establecer en un objeto proc llamado cuando se inicia una sesin (o subsesin) irb. [nil] conf.last_value El ltimo valor de salida de irb. [...] :LOAD_MODULES / conf.load_modules Una lista de mdulos cargados a travs de la opcin -r de lnea de comandos. [[]] :MATH_MODE / conf.math_mode Si true, irb se ejecuta cargado on la librera math. [false] conf.prompt_c El prompt para una declaracin que contina (por ejemplo, inmediatamente despus de un if). [depends ] conf.prompt_i El prompt estndar de nivel superior. [depends ] :PROMPT_MODE / conf.prompt_mode Estilo de prompt a mostrar. [DEFAULT] conf.prompt_s El prompt para una cadena que contina. [depends ] :PROMPT Consultar configuracin del prompt. :RC/ conf.rc Si false no se carga un archivo de inicializacin. [true] conf.return_format Formato a utilizar para mostrar los resultados de las expresiones introducidas de forma interactiva. [depends] :SINGLE_IRB Si true, todas las sesiones irb anidadas comparten el mismo binding, de lo contrario ser creado un nuevo binding de acuerdo con el valor de: CONTEXT_MODE. [nil] conf.thread Una referencia de slo lectura para el objeto Thread que se est ejecutando. [current thread] :USE_LOADER/ conf.use_loader Especifica si es el propio mtodo irb para leer archivos con load/require. [false]

128

:USE_READLINE / conf.use_readline irb utilizar la biblioteca readline si est disponible a menos que esta opcin est establecida en false, en cuyo caso no se utilizar nunca readline, o nil, en el que readline no se utilizar en modo inf-ruby. [depends] :USE_TRACER / conf.use_tracer Si true, traza la ejecucin de las sentencias. [false] :VERBOSE / conf.verbose En teora activa el traceo adicional cuando es true. En la prctica casi no hay resultados adicionales. [true]

Comandos
En el prompt de irb se puede introducir cualquier expresin vlida de Ruby y ver los resultados. Tambin puede utilizar cualquiera de los siguientes comandos para controlar la sesin irb. exit, quit, irb_exit, irb_quit Sale de la sesin o subsesin irb. Si se ha usado cb para cambiar bindings (ver ms abajo), sale de este modo binding. conf, context, irb_context Muestra la configuracin actual. La configuracin se modifica mediante la invocacin de los mtodos conf. La lista de la seccin anterior muestra los ajustes conf disponibles. Por ejemplo, para establecer el valor predeterminado del prompt para algo subordinado, se puede utilizar: irb(main):001:0> conf.prompt_i = Yes, Master? => Yes, Master? Yes, Master? 1 + 2 cb, irb_change_binding ( obj ) Crea y entra en un nuevo enlace (binding) que tiene su propio espacio para las variables locales. Si se da obj, ser utilizado como self en el nuevo enlace. irb ( obj ) Inicia una subsesin irb. Si se d obj, ser utilizado como self. jobs, irb_jobs Lista las subsesiones irb. fg n, irb_fg n Cambia a la subsesin irb especificada. n puede ser cualquiera de: un nmero de subsesin irb, un ID de hilo, un objeto irb o el objeto que era el valor de self cuando se puso en marcha una subsesin. kill n, irb_kill n Mata a una subsesin irb. n puede ser cualquiera de los valores descritos para irb_fg.

Configurando el Prompt
Hay una gran flexibilidad para la configuracin de los smbolos del sistema que utiliza irb. La configuracin de prompts se almacena en el hash prompt IRB.conf[:PROMPT]. Por ejemplo, para establecer un nuevo modo de prompt llamado MY_PROMPT, podra entrar lo siguiente (ya sea directamente en el prompt irb o en el archivo .irbrc) : IRB.conf[:PROMPT][:MY_PROMPT] = { :PROMPT_I => -->, :PROMPT_S => --, :PROMPT_C => --+, :RETURN => ==>%s\n # # # # # name of prompt mode normal prompt prompt for continuing strings prompt for continuing statement format to return value

129

} Una vez que se haya definido un prompt, hay que decirle a irb que lo utilice. Desde la lnea de comandos, puede utilizar la opcin --prompt. (Observe que el nombre del modo prompt se convierte automticamente en mayscula y los guiones normales a guiones bajos). % irb --prompt my-prompt Si se desea utilizar este prompt en todas las sesiones irb futuras, se puede establecer como un valor de configuracin en el archivo .irbrc. IRB.conf[:PROMPT_MODE] = :MY_PROMPT Los smbolos PROMPT_I, PROMPT_S y PROMPT_C especifican el formato de cada una de las cadenas de prompt. En un formato cadena, se expanden algunas secuencias %.

Bandera Descripcin
%N Comando actual. %m to_s al objeto main (self). %M inspect al objeto main (self). %l Tipo de delimitador. En las cadenas que se continan a travs de un salto de lnea, l% mostrar el tipo de delimitador utilizado para iniciar la cadena, para que se sepa cmo ponerle fin. El delimitador ser uno de , , /, ] o . %ni Nivel de sangra. El nmero opcional n se utiliza como una especificacin de amplitud de printf, como printf(nd%). %nn Nmero de lnea actual (n como con el nivel de sangra). %% Un signo de procentaje literal. Por ejemplo, el modo por defecto del sistema se define de la siguiente manera.

IRB.conf[:PROMPT_MODE][:DEFAULT] = { :PROMPT_I => %N(%m):%03n:%i> , :PROMPT_S => %N(%m):%03n:%i%l , :PROMPT_C => %N(%m):%03n:%i* , :RETURN => %s\n }

Restricciones
Debido a la como trabaja irb, tiene cierta incompatibilidad con el intrprete estndar de Ruby. El problema radica en la determinacin de las variables locales. Normalmente, Ruby busca una sentencia de asignacin para determinar si algo es una variable --si un nombre no ha sido asignado, Ruby asume que el nombre es una llamada a mtodo. eval var = 0 var produce: prog.rb:2: undefined local variable or method `var for main:Object (NameError) En este caso la asignacin est ah, pero dentro de una cadena, por lo que Ruby no lo tiene en cuenta. irb por su parte, ejecuta sentencias a medida que se introducen.

irb(main):001:0> eval var = 0 0

130

irb(main):002:0> var 0 En irb, la asignacin se ejecut antes de encontrar la segunda lnea , por lo que var est correctamente identificada como una variable local. Si se necesita que concuerde ms estrechamente con el comportamiento del intrprete Ruby, se pueden colocar estas declaraciones dentro de un par begin/end. irb(main):001:0> begin irb(main):002:1* eval var = 0 irb(main):003:1> var irb(main):004:1> end NameError: undefined local variable or method `var (irb):3:in `irb_binding

rtags y xmp
En el caso de que irb no fuera ya lo suficientemente complejo, vamos a aadir unos cuantas repliegues ms. Junto con el programa principal, el conjunto irb incluye algunos extras. En las siguientes secciones vamos a ver dos: rtags y xmp.

rtags
rtags es un comando que se utiliza para crear un fichero de etiquetas ( TAGS) para su uso con los editores Emacs o vi. rtags [ -vi ] [ files ]... Por defecto, rtags hace un archivo TAGS adecuado para Emacs (ver etags.el). La opcin -vi hace un fichero TAGS para su uso con vi. rtags necesita ser instalado de la misma forma que irb (es decir, es necesario instalar irb en la ruta de librera y hacer un enlace desde irb/rtags.rb a bin/rtags).

xmp
xmp irb es una impresora de ejemplo, --es decir, una bonita impresora que muestra el valor de cada expresin tal como se ejecuta (al igual que el script que escribimos para dar formato a los ejemplos de este libro). Tambin hay otro xmp independiente en los archivos. xmp se puede utilizar de la siguiente manera:

require irb/xmp xmp <<END artist = Doc Severinsen artist.upcase END produce: artist = Doc Severinsen ==> Doc Severinsen artist.upcase ==> DOC SEVERINSEN O xmp se puede utilizar como una instancia de objeto. Usa de esta manera, el objeto mantiene el contexto entre las invocaciones.

131

require irb/xmp x = XMP.new x.puts artist = Louis Prima x.puts artist.upcase produce: artist = Louis Prima ==> Louis Prima artist.upcase ==> LOUIS PRIMA De forma explcita, puede proporcionar un binding con una u otra forma. De lo contrario, xmp utiliza el entorno del llamador. xmp code_string, abinding XMP.new(abinding) Tenga en cuenta que xmp no funciona con mltiples subprocesos.

La Documentacin de Ruby
A partir de la versin 1.8, Ruby viene con RDoc, una herramienta que extracta y formatea documentacin que est integrada en los archivos de cdigo fuente de Ruby. Esta herramienta se utiliza para documentar las clases y mdulos integrados de Ruby. Un nmero creciente de bibliotecas y extensiones tambin estn documentados de esta manera. RDoc tiene dos trabajos. En primer lugar, el anlisis de los archivos fuente en Ruby y C en busca de informacin para documentar (RDoc tambin pueden documentar los programas de Fortran 77). En segundo lugar, toma esta informacin y la convierte en algo legible. Fuera de la caja, RDoc produce dos tipos de salida: HTML y ri. La figura 9.1 en la pgina siguiente muestra algunos resultados de RDoc en formato HTML en una ventana del navegador. Este es el resultado de alimentar RDoc con un archivo de cdigo fuente Ruby sin documentacin adicional --RDoc hace un fidedigno trabajo para producir algo significativo. Si nuestro cdigo fuente contiene comentarios, RDoc los puede utilizar para condimentar los textos que elabore. Por lo general, un comentario antes de un elemento se utiliza para documentar ese elemento, como se muestra en la figura 9.2 ms adelante. RDoc tambin puede ser utilizado para producir documentacin que pueda ser leda por la utilidad de lnea de comandos ri. Por ejemplo, si pedimos a RDoc documentar el cdigo de la figura 9.2, podemos acceder a la documentacin utilizando ri, como se muestra en la figura 9.3 en la pgina 135. Las nuevas distribuciones de Ruby tienen las clases y mdulos integrados (y algunas bibliotecas) documentados esta manera.

Aadiendo RDoc al cdigo Ruby

RDoc analiza los archivos fuente Ruby para extraer los principales elementos (clases, mdulos, mtodos, atributos,etc). Se puede elegir asociar documentacin adicional, simplemente aadiendo un bloque de comentario antes del elemento en el archivo. Los bloques de comentario se pueden escribir de manera bastante natural, ya sea usando # en las lneas sucesivas de la observacin o mediante la inclusin de comentarios en un bloque =begin...=end. Si se utiliza esta ltima forma, la lnea =begin debe estar marcada con una etiqueta rdoc para distinguir el bloque de otros estilos de documentacin. =begin rdoc Calculate the minimal-cost path though the graph using Debrinkskis algorithm, with optimized inverse pruning of isolated leaf nodes. =end

132

def calculate_path . . . end En un comentario de documentacin, los prrafos est en lneas que comparten el margen izquierdo. El texto con sangra ms all de este margen es formato literal palabra por palabra.
Figura 9.1. Navegacin por la salida de RDoc para la clase counter.

Esta figura muestra una salida de RDoc en una ventana del navegador. La caja superpuesta muestra el programa fuente del que se gener esta salida. A pesar de que el fuente no contiene la documentacin interna, RDoc se las arregla para extraer informacin interesante. Tenemos tres paneles en la parte superior de la pantalla que muestran los archivos, las clases y los mtodos para los que tenemos documentacin. Para la la clase counter, RDoc nos muestra los atributos y mtodos (incluyendo las signaturas de mtodo). Y si hace clic en una signatura de mtodo, RDoc abrir una ventana que contiene el cdigo fuente para el mtodo correspondiente. En la figura siguiente (9.2), se observa cmo aparecen ahora los comentarios antes de cada elemento, en la salida de RDoc formateado en HTML. Es obvio como RDoc ha detectado oportunidades de hipervnculo en nuestros comentarios: en el comentario a nivel de clase, la referencia a Counter#inc es un hipervnculo a la descripcin del mtodo, y en el comentario para el mtodo new, el hipervnculo Counter nos referecnia de nuevo a la documentacin de la clase. Esta es una caracterstica clave de RDoc: est diseado para ser no intrusivo en los archivos de cdigo fuente Ruby y para compensar esto trata de ser inteligente cuando se produce la salida. Se puede marcar texto no literal. Para definir palabras individuales en fuentes cursiva, negrita o mquina de escribir, puede usar _palabra_, *palabra* y +palabra+, respectivamente. Si se quiere hacer esto para varias palabras o texto que contienen caracteres no-palabra, puede utilizar <em>varias palabras</em>, <b>ms palabras</b> y <tt>an ms palabras</tt>. Poner una barra invertida antes de la lnea marcada hace que deje de ser interpretada.

133

Figura 9.2. Navegacin por la salida de RDoc cuando el fuente tiene comentarios.

RDoc detiene el procesamiento de los comentarios si encuentra una lnea de comentario que comienza con #--. Esto puede ser usado para separar comentarios externos de internos o apartar un comentario que se asocia a un mtodo, una clase o un mdulo. Se puede volver a conectar iniciando una lnea con #++. # Extract the age and calculate the # date of birth. ##-FIXME: fails if the birthday falls on # February 29th, or if the person # was born before epoch and the installed # Ruby doesnt support negative time_t #++ # The DOB is returned as a Time object. ##-But should probably change to use Date. def get_dob(person) ... end

134

Hipervnculos
Los nombres de clases, de archivos de cdigo fuente y cualquier nombre de mtodo que contienga un guin bajo, o vaya precedido por una almohadilla, son hipervnculados automticamente desde el texto del comentario a su descripcin. Los hipervnculos a la red que son reconocidos comienzan con http:, mailto:, ftp: y www: . Una URL HTTP que hace referencia a un archivo de imagen externo se convierte en una lnea con la etiqueta <IMG...> . Hipervnculos a partir de link: se supone que se refieren a archivos locales, cuyas rutas son relativas al directorio --op donde se almacenan los archivos de salida. Los hipervnculos tambin pueden ser de la forma label[url], en cuyo caso la etiqueta se utiliza en el texto que se muestra y la url se utiliza como el destino. Si la etiqueta contiene varias palabras, hay que encerrarla entre llaves: {dos palabras}[url].

Listas
# # # # # # Las listas se escriben como prrafos sangrados con: un * o un - (para listas), un dgito seguido de un punto para listas numeradas y una letra mayscula o minscula seguida de un punto para listas alfa. Por ejemplo, se podra producir algo as como el texto anterior con: Lists are typed as indented paragraphs with: * a * or -(for bullet lists) * a digit followed by a period for numbered lists * an upper or lower case letter followed by a period for alpha lists.

Observe cmo las siguientes lneas de un elemento de la lista son sangradas al texto de la primera lnea del elemento.

135

Las listas de marcado (a veces llamadas listas de descripcin) se escriben utilizando corchetes para la etiqueta. # [cat] small domestic animal # [+cat+] command to copy standard input # to standard output Las listas de etiqueta tambin pueden ser producidas poniendo un doble dos puntos despus de la etiqueta. Esto establece el resultado en forma tabular, por lo que las descripciones van alineadas. # cat:: small domestic animal # +cat+:: command to copy standard input # to standard output En ambos tipos de listas de etiqueta, si el cuerpo del texto comienza en la misma lnea de la etiqueta, el comienzo de ese texto determina el bloque de sangrado para el resto del cuerpo. El texto tambin puede comenzar en la lnea siguiente en la etiqueta, con una sangra desde el inicio de la etiqueta. Esto a menudo es preferible si la etiqueta es larga. Ambas son vlidas para las entradas de las listas de etiqueta: # # # # # # # <tt>--output</tt> <i>name [, name]</i>:: specify the name of one or more output files. If multiple files are present, the first is used as the index. <tt>--quiet:</tt>:: do not output the names, sizes, byte counts, index areas, or bit ratios of units as they are processed.

Ttulos
Los ttulos se registran en las lneas que comienzan con signos de igualdad. A ms signos de igual, mayor nivel de ttulo. # = Level One Heading # == Level Two Heading # and so on... Se introducen reglas (lneas horizontales) utilizando tres o ms guiones.

# and so it goes... # ----# The next section...

Modificadores de Documentacin
las listas de parmetros de mtodo se extractan y se muestran con la descripcin del mtodo. Si un mtodo llama a yield, entonces tambin se mostrarn los parmetros pasados a yield. Por ejemplo, considere el siguiente cdigo: def fred ... yield line, address V a ser documentado como:

fred() {|line, address| ... } Se puede cambiar esto usando un comentario que contenga :yields: ... la definicin del mtodo. def fred # :yields: index, position ... 136 en la misma lnea que

yield line, address que se documenta como:

fred() {|index, position| ... } :yields: es un ejemplo de un modificador de documentacin. Estos aparecen inmediatamente despus del comienzo del elemento de documento que se est modificando. Otros modificadores include:

:nodoc: [all] No incluir este elemento en la documentacin. Las clases y mdulos, los mtodos, los alias, constantes y atributos dentro de la clase o mdulo afectado directamente, tambin se excluyen de la documentacin. Por defecto sin embargo, sern documentado los mdulos y las clases dentro de esa clase o mdulo. Esta opcin se desactiva, aadiendo el modificador all. Por ejemplo, en el siguiente cdigo, slo ser documentada la clase SM::Imput. module class end end module class end end SM Input #:nodoc:

Markup #:nodoc: all Output

:doc: Obliga a la documentacin de un mtodo o atributo aunque por otra parte no fuera a ser documentado. til si por ejemplo, desea incluir la documentacin de un mtodo privado en particular. :notnew: (Slo se aplica al mtodo de instancia initialize). Normalmente RDoc asume que la documentacin y los parmetros para #initialize son en realidad para el mtodo new de la clase correspondiente y por tanto falsea un mtodo new para la clase. El modificador :notnew: detiene esto. Recuerde que #initialize est protegido, por lo que no se ve la documentacin a menos que utilice la opcin -a de lnea de comandos.

Otras Directivas
Los bloques de comentarios pueden contener otras directivas.

:call-seq: lines. . . El texto hasta la siguiente lnea de comentario en blanco se utiliza como la secuencia de llamada cuando se genera la documentacin (primordial el anlisis de la lista de parmetros del mtodo). Una lnea se considera en blanco, incluso si comienza con un #. Para esta directiva nica, los dos puntos del principio son opcionales. :include: filename Incluir el contenido del archivo llamado en ese momento. El archivo se buscar en los directorios listados por la opcin del --include o en el directorio actual por defecto. El contenido del archivo se desplazar para tener la misma sangra como : al inicio de la directiva :include: . :title: text Establece el ttulo del documento. Equivalente al parmetro --ttle de la lnea de comandos. (El parmetro de lnea de comandos reemplaza cualquier directiva :title: del fuente) :main: name Equivalente al parmetro --main de lnea de comandos, establece la pgina inicial que aparecer en ese documento.

137

:stopdoc: / :startdoc: Detiene y comienza la adicin de nuevos elementos de la documentacin para el contenedor actual. Por ejemplo, si una clase tiene una serie de constantes que no se quieren documentar, poner un:stopdoc: antes de la primera y un :startdoc: despus de la ltima. Si no se especifica un :startdoc: para el final del contenedor, desactiva la documentacin de toda la clase o mdulo. :enddoc: No documenaro nada ms en el nivel lxico actual. La figura 9.4 en la pgina siguiente muestra un ejemplo ms completo de un archivo fuente documentada mediante RDoc.

Aadiendo RDoc a las Extensiones de C


RDoc tambin entiende muchas de las convenciones utilizadas al escribir extensiones en C para Ruby. La mayora de las extensiones de C tienen una funcin Init_Classname. RDoc la toma como la definicin de clase --cualquier de comentario C antes del mtodo Init_ se puede utilizar como documentacin de la clase. La funcin Init_ normalmente se utiliza para asociar funciones C con nombres de mtodos Ruby. Por ejemplo, una extensin Cipher puede definir un mtodo Ruby salt=, implementado por la funcin C salt_set mediante una llamada como rb_define_method(cCipher, salt=, salt_set, 1); RDoc analiza esta llamada aadiendo el mtodo salt= a la documentacin de la clase. A continuacin RDoc busca la fuente de C para la funcin C salt_set. Si esta funcin est precedida por un bloque de comentario, RDoc lo utiliza para la documentacin del mtodo. Este esquema bsico trabaja sin ningn esfuerzo por su parte ms all de escribir los comentarios de las funciones en la documentacin normal. Sin embargo, RDoc no puede discernir la secuencia de llamada para el correspondiente mtodo de Ruby. En este ejemplo, la salida de RDoc mostrar un solo argumento con el (un poco sin sentido) nombre de arg1. Se puede cambiar esto usando la directiva call-seq en el comentario de la funcin. Las siguientes lneas utilizan call-set (hasta una lnea en blanco) para documentar la secuencia de llamada del mtodo. /* * call-seq: * cipher.salt = number * cipher.salt = string * * Sets the salt of this cipher to either a binary +number+ or * bits in +string+. */ static VALUE salt_set(cipher, salt) ... Si un mtodo devuelve un valor significativo, debe ser documentado en el call-seq siguiendo a los caracteres ->. /* * call-seq: * cipher.keylen -> Fixnum or nil */ A pesar de la heurstica RDoc funciona bien para encontrar los comentarios de clase y de mtodo para extensiones simples, pero no siempre funciona para implementaciones ms complejas. En estos casos, puede utilizar las directivas Document-class: y Document-method: para indicar que un comentario C

138

se refiere a una clase o mtodo, respectivamente. Estos modificadores toman el nombre de la clase o del mtodo Ruby que est siendo documentado. /* * Document-method: reset * * Clear the current buffer and prepare to add new * cipher text. Any accumulated output cipher text * is also cleared. */ Finalmente, en el mtodo Init_ se puede asociar un mtodo Ruby con una funcin de un fichero fuente C diferente. RDoc no encontrar esta funcin sin su ayuda: hay que agregar una referencia al archivo que contiene la definicin de la funcin mediante un comentario especial a la llamada rb_define_method. En el siguiente ejemplo se le indica a RDoc que busque en el archivo md5.c la funcin (y el comentario relacionado) correspondiente al mtodo md5. rb_define_method(cCipher, md5, gen_md5, -1); /* in md5.c */

La figura 9.4 en la pgina siguiente muestra un archivo de cdigo fuente en C documentado mediante RDoc. Tenga en cuenta que los cuerpos de varios mtodos internos se han omitido para ahorrar espacio.

Ejecutando RDoc
Se puede ejecutar RDoc desde la lnea de comandos:

% rdoc [options] [filenames...] Escriba rdoc --help para un resumen de las opciones hasta a la fecha.

Los archivos se analizan y se recolecta la informacin que contienen antes de producir cualquier salida. Esto permite resolver las referencias cruzadas entre todos los archivos. Si un nombre es un directorio es traspasado. Si no se especifican nombres, se procesan todos los archivos Ruby en el directorio actual (y subdirectorios). Un uso tpico puede ser la de generar la documentacin para un paquete de cdigo fuente Ruby (como el mismo RDoc). % rdoc Este comando genera documentacin HTML para todos los los archivos fuente en Ruby y C bajo el directorio actual. Estos se almacenan en un rbol de la documentacin a partir del subdirectorio doc/. RDoc utiliza las extensiones de archivo para determinar cmo procesar cada archivo. Ficheros que terminen en .rb y .rbw se supone que son fuentes de Ruby. Archivos con la extensin .c se analizan como archivos de C. Todos los dems archivos se supone que contienen slo el marcado (con o sin los iniciales marcadores de comentario #). Si se pasan los nombres de directorio a RDoc, se escanean de forma recursiva nicamente para los archivos fuente de Ruby y C. Para incluir archivos que no son fuente como los README en el proceso de documentacin, sus nombres deben estar explcitamente en la lnea de comandos. Cuando se escribe una biblioteca Ruby, a menudo tiene algunos archivos de cdigo fuente que implementan la interfaz pblica. La mayora son internos y no son de inters para los lectores de su documentacin. En estos casos, se construye un archivo .document en cada uno de los directorios de su proyecto. Si RDoc entra en un directorio que contiene un archivo .document, procesar en ese directorio slo los archivos cuyo nombre se corresponda con una de las lneas de ese archivo. Cada lnea del archivo puede ser un nombre de archivo, un nombre de directorio o un comodn (un sistema de archivos con el patrn glob ). Por ejemplo, para incluir todos los archivos Ruby cuyos nombres comienzan con main, junto con el archivo constants.rb, se puede utilizar un archivo .document que contiene:

139

140

main*.rb constants.rb Algunos estndares de proyecto piden la documentacin en un archivo README de nivel superior. Puede que le resulte conveniente escribir este archivo en formato RDoc y luego usar la directiva :include: para incorporar este documento en el de la clase principal.

Crear Documentacin para ri


RDoc tambin se utiliza para crear la documentacin que luego se muestra con ri.

Cuando se ejecuta ri, por defecto busca la documentacin en tres lugares (Se puede reemplazar la ubicacin del directorio con la opcin --op de RDoc y, posteriormente, utilizando la opcin --doc-dir con ri): 1. el directorio de documentacin del sistema, que contiene la documentacin distribuida con Ruby, creada por el proceso de instalacin de Ruby, 2. el directorio del sitio, que contiene la documentacin de todo el sitio agregado localmente, y 3. el directorio de documentacin del usuario, almacenada en el directorio home del propio usuario. Usted puede encontrar estos tres directorios en los siguientes lugares.

$datadir/ri/<ver>/system/... $datadir/ri/<ver>/site/... ~/.rdoc/.... La variable $datadir es el directorio de datos configurado para la instalacin Ruby. Se puede encontrar el datadir local utilizando ruby -r rbconfig -e p Config::CONFIG[datadir] Para aadir documentacin a ri, es necesario indicar a RDoc que directorio de salida utilizar. Para su propio uso, lo ms fcil es usar la opcin --ri. % rdoc --ri file1.rb file2.rb Si se desea instalar la documentacin en todo el sitio, hay que utilizar la opcin --ri-site.

% rdoc --ri-site file1.rb file2.rb La opcin --ri-system se utiliza normalmente slo para instalar la documentacin para las clases y liberas Ruby estndar integradas. Puede volver a generar la documentacin de la distribucin desde la distribucin del cdigo fuente Ruby (no de las libreras mismas instaladas).

Mostrando el Uso del Programa


La mayora de los programas de lnea de comandos tienen algn tipo de funcionalidad para describir su uso correcto; si se les dan parmetros no vlidos reportan un corto mensaje de error seguido de un resumen con sus opciones reales. Y, si usted est utilizando RDoc, es probable que haya descrito en un comentario RDoc al inicio del programa principal cmo el programa que se debe utilizar. En lugar de duplicar toda esta informacin en algn lugar con puts, puede utilizar RDoc::usage para extraerla directamente desde el comando y escribirla hacia el usuario.

141

Se puede pasar a RDoc::usage de una serie de parmetros tipo cadena que utiliza para extraer del bloque de comentario slo aquellas secciones nombradas por los parmetros (donde una seccin comienza con un ttulo igual al parmetro, ignorando maysculas y minsculas). Sin parmetros, RDoc::usage muestra todo el comentario. Adems, cierra el programa despus de mostrar el mensaje de uso. Si el primer parmetro en la llamada es un nmero entero, se utiliza como cdigo de salida del programa (de otra forma RDoc::usage sale con un cdigo de error cero). Si no se desea salir del programa despus de mostrar el mensaje, hay que llamar a RDoc::usage_no_exit. En la figura 9.5 se v un programa trivial que muestra la fecha y la hora. Utiliza RDoc::usage para mostrar el bloque de comentarios completo si el usuario solicita ayuda y muestra slo la seccin de uso si el usuario pasa una opcin no vlida. La figura 9.6 muestra la salida generada en respuesta la opcin --help. RDoc::usage rinde homenaje a la variable de entorno RI, que pueden ser utilizada para establecer el ancho de la pantalla y el estilo de salida. El resultado de la figura 9.6 en la pgina 144, se ha generado con la opcin de configuracin RI en -f ansi. Aunque no es demasiado evidente si ests viendo esta figura en el libro en blanco y negro, los encabezados de seccin, el cdigo fuente y el enfatizado de fuente, se muestran en diferentes colores, usando secuencias de escape ANSI.

142

Chad Fowler es una figura destacada en la comunidad Ruby. Est en el consejo de Ruby Central, Inc. Es uno de los organizadores de RubyConf. Y es uno de los escritores de RubyGems. Todo esto lo hace especialmente calificado para escribir este captulo.

Gestin de Paquetes con RubyGems


RubyGems es un marco estandarizado para el empaquetado y la instalacin de bibliotecas y aplicaciones, que hace fcil encontrar, instalar, actualizar y desinstalar paquetes Ruby. Proporciona a los usuarios y desarrolladores cuatro principales utilidades. 1. 2. 3. 4. Un formato de paquete estandarizado, Un repositorio central para el hospedaje de los paquetes en este formato, Instalacin y gestin de mltiples y simultneas versiones de libreras instaladas, Herramientas de usuario final para efectuar consultas, instalar, desinstalar y otras formas de manipulacinde estos paquetes.

Antes de que llegara RubyGems, la instalacin de una nueva biblioteca involucraba la bsqueda en la web, la descarga del paquete y tratar de instalarlo, slo para descubrir que no se cumplan sus dependencias. En cambio ahora, si la biblioteca que desea utilizar est empaquetada en RubyGems, puede decirle simplemente que instale el paquete (y todas sus dependencias) y todo se hace automticamente para usted. En el mundo de RubyGems, los desarrolladores combinan sus aplicaciones y bibliotecas en archivos individuales llamados gemas. Estos archivos se ajustan a un formato estandarizado, y el sistema RubyGems proporciona una herramienta de lnea de comandos, con el apropiado nombre de gem, para la manipulacin de los archivos gema. La mejor manera de comprobar si RubyGems se instal con xito es utilizando el comando ms importante que hay que aprender: % gem help RubyGems is a sophisticated package manager for Ruby. This is a basic help message containing pointers to more information. Usage: gem -h/--help gem -v/--version gem command [arguments...] [options...] Examples: gem install rake gem list --local gem build package.gemspec gem help install Further help: gem help commands list all gem commands gem help examples show some examples of usage gem help <COMMAND> show help on COMMAND (e.g. gem help install) Further information: http://rubygems.rubyforge.org Como la ayuda RubyGems es bastante amplia, no vamos a entrar en detalles sobre cada uno de los comandos y opciones disponibles.

143

Figura 9.6

Instalando Gemas
Vamos a empezar con RubyGems instalando una aplicacin que est escrita en Ruby. Rake de Jim Weirich (http://rake.rubyforge.org) tiene la distincin de ser la primera aplicacin disponible como una gema. No slo eso, en general es una gran herramienta a tener en cuenta, ya que es una herramienta de construccin similar a Make y Ant. De hecho, incluso se puede usar Rake para crear gemas! La localizacin e instalacin de Rake con RubyGems es simple.

% gem install -r rake Attempting remote installation of Rake Successfully installed rake, version 0.4.3 % rake --version rake, version 0.4.3 RubyGems descarga el paquete de Rake y lo instala. Debido a que Rake es una aplicacin, RubyGems descarga tanto las librferas de Rake como el programa de lnea de comandos rake. El programa gem se controla utilizando subcomandos, los cuales, tienen sus propias opciones y pantallas de ayuda. En este ejemplo, utilizamos el subcomando install con la opcin -r, que le dice trabajar de forma remota. (Muchas operaciones con RubyGems se pueden realizar de forma local o remota. Por ejemplo, puede utilizar el comando query bien para mostrar todas las gemas que estn disponibles para instalar de forma remota, o bien para ver una lista de las gemas que ya tiene instaladas. Por esta razn, algunos subcomandos aceptan las opciones -r y -l, que especifican si se tiene la intencin de llevar a cabo las operacines de forma remota o local.) Si por alguna razn -tal vez debido a un potencial problema de compatibilidad-, necesita una versin antigua de Rake, puede utilizar el operador de requerimiento de versin para especificar la versin a instalar. % gem install -rcrake -v < 0.4.3 Attempting remote installation of rake Successfully installed rake, version 0.4.2 % rake --version rake, version 0.4.2 La tabla siguiente muestra los operadores de requerimientos de versin. El argumento -v en el anterior ejemplo requiere la versin ms alta menor que 0.4.3.

144

Operador

Descripcin

= Coincide con la versin exacta. Lanzamientos mayor y menor y nivel de parche deben ser idnticos. != Cualquier versin que no sea la especificada. > Cualquier versin mayor (incluso en el nivel de parche) que la especificada. < Cualquier versin menor que la especificada. >= Cualquier versin mayor o igual que la especificada. <= Cualquier versin menor o igual que la especificada. ~> Operador de versin Boxed. La versin debe ser mayor o igual a la especificada despus de aumentar en uno su nmero de versin minor. Esto es para evitar incompatibilidades API entre versiones de lanzamientos menores.
Tanto el mtodo require_gem como el atributo add_dependency en Gem::Specification aceptan un argumento que especifica una versin de la dependencia. La versiones RubyGems de las dependencias son de la forma operator major.minor.patch_level.

Hay una sutileza a la hora de instalar con RubyGems diferentes versiones de la misma aplicacin. A pesar de que RubyGems mantiene versiones diferentes de los archivos de biblioteca de la aplicacin, no lo hace con la versin del comando real que se utiliza para ejecutar la aplicacin. Como resultado de ello, cada instalacin de una aplicacin sobrescribe de hecho la anterior. Durante la instalacin, tambin se puede aadir la opcin -t para el comando install, que hace que RubyGems ejecute (si se ha creado) la suite de prueba de la gema. Si las pruebas fallan, el instalador le preguntar si mantiene o descarta la gema. Esta es una buena manera de tener cierta confianza en que la gema que se acaba de descargar trabaja en el sistema de la forma para la que el autor la ha escrito. % gem install SomePoorlyTestedProgram -t Attempting local installation of SomePoorlyTestedProgram1.0.1 Successfully installed SomePoorlyTestedProgram, version 1.0.1 23 tests, 22 assertions, 0 failures, 1 errors...keep Gem? [Y/n] n Successfully uninstalled SomePoorlyTestedProgram version 1.0.1 Si hubiramos elegido la opcin predeterminada y hubiramos instalado la gema, podramos haberla inspeccionado para tratar de determinar la causa de que la prueba falle.

Instalacin y Uso de las Libreras Gema


Utilizar RubyGems para instalar una aplicacin completa es una buena manera de tener los pies mojados para empezar su camino de aprendizaje con el comando gem. Sin embargo, en la mayora de los casos, se va a utilizar RubyGems para instalar las bibliotecas Ruby para utilizar en sus propios programas. RubyGems le permite instalar y gestionar mltiples versiones de la misma biblioteca, pero tambin tendr que hacer algunas cosas nuevas especficas de RubyGems en caso de requerir algunas libreras en su cdigo. Tal vez alguien le ha pedido crear un programa que ayude a mantener y publicar un diario. Usted piensa que estara bien publicar el diario en formato HTML, pero est preocupado de que la otra persona no pueda entender todos los pros y contras del cdigo HTML. Por esta razn, usted ha opta por utilizar uno de los muchos excelentes paquetes de plantillas disponibles para Ruby. Despus de algunas investigaciones, usted se decide por BlueCloth de Michael Granger, basndose en su reputacin de ser muy sencillo de utilizar. Primero tiene que encontrar e instalar la gema BlueCloth.

% gem query -rn Blue *** REMOTE GEMS *** BlueCloth (0.0.4, 0.0.3, 0.0.2) BlueCloth is a Ruby implementation of Markdown, a text-to-HTML conversion tool for web writers. Markdown allows you to write using an easy-to-read, easy-to-write plain text format, then convert it to structurally valid XHTML (or HTML).

145

Esta invocacin del comando query utiliza la opcin -n para buscar en el repositorio central de gemas, cualquier gema cuyo nombre coincida con la expresin regular /Bluel/. Los resultados muestran las tres versiones disponibles de BlueCloth que existen (0.0.4, 0.0.3 y 0.0.2). Cuando se quiere instalar la ms reciente, no hay que indicar una versin explcita en el mandato de instalacin, por defecto se descarga la ltima.

Generacin de Documentacin de la API


Siendo que esta es su primera vez con BlueCloth, no est muy seguro de cmo usarlo. Necesita un poco de documentacin de la API para comenzar. Afortunadamente, aadiendo la opcin --rdoc al comando de instalacin, RubyGems generar la documentacin RDoc de la gema que est instalando. % gem install -r BlueCloth --rdoc Attempting remote installation of BlueCloth Successfully installed BlueCloth, version 0.0.4 Installing RDoc documentation for BlueCloth-0.0.4... WARNING: Generating RDoc on .gem that may not have RDoc. bluecloth.rb: cc.............................. Generating HTML... Despus de haber generado toda esta documentacin HTML til, cmo la consulta? Hay al menos dos opciones. La manera difcil (aunque en realidad no lo es tanto) es abrir el directorio documentacin de RubyGems y consultar la documentacin de forma directa. Como con la mayora de las cosas en RubyGems, la documentacin de cada gema se almacena en un central, protegido y especfico lugar. Esto vara segn el sistema y segn por dnde explcitamente se ha optado por instalar las gemas. La manera ms confiable de encontrar los documentos es pedir al comando gem donde se encuentra el directorio RubyGems principal. Por ejemplo: % gem environment gemdir /usr/local/lib/ruby/gems/1.8 RubyGems almacena la documentacin generada en el subdirectorio /doc de este directorio. En este caso /usr/local/lib/ruby/gems/1.8/doc/. Puede abrir el archivo de index.html y ver la documentacin. Si se utiliza este path a menudo, puede crear un acceso directo. La segunda (y fcil) manera de ver la documentacin RDoc es utilizar la utilidad includa en RubyGems, gem_server. Simplemente escriba: % gem_server [2004-07-18 11:28:51] INFO WEBrick 1.3.1 [2004-07-18 11:28:51] INFO ruby 1.8.2 (2004-06-29) [i386mswin32] [2004-07-18 11:28:51] INFO WEBrick::HTTPServer#start: port=8808 gem_server inicia un servidor Web que se ejecutan en cualquier ordenador en que se lance. De manera predeterminada, se iniciar en el puerto 8808 y servir las gemas y su documentacin desde el directorio de instalacin RubyGems por defecto. Tanto el puerto como el directorio de gemas son reemplazables a travs de opciones de lnea de comandos, utilizando -p y -d respectivamente. Una vez que haya iniciado el programa gem_server, si se est ejecutando en el equipo local, puede acceder a la documentacin de sus gemas instaladas escribiendo en la barra de direcciones del navegador web http://localhost:8808. Ah encontrar una lista de las gemas instaladas, con sus descripciones y enlaces a la documentacin RDoc.

Vamos al Cdigo!
Ahora que tenemos BlueCloth instalado y sabemos cmo utilizarlo, estamos listos para escribir cdigo. Despus de haber utilizado RubyGems para descargar la librera, ahora tambin podemos usarlo para cargar los componentes de la biblioteca en nuestra aplicacin. Antes de RubyGems, diramos algo as como

146

require bluecloth Con RubyGems sin embargo, podemos sacar provecho de su empaquetado y compatibilidad de versiones. Para ello, utilizamos require_gem en lugar de require. require rubygems require_gem BlueCloth, >= 0.0.4 doc = BlueCloth::new <<MARKUP This is some sample [text][1]. Just learning to use [BlueCloth][1]. Just a simple test. [1]: http://ruby-lang.org MARKUP puts doc.to_html produce: <p>This is some sample <a href=http://ruby-lang.org>text</a>. Just learning to use <a href=http://ruby-lang.org>BlueCloth</a>. Just a simple test.</p> Las dos primeras lneas es el cdigo especfico de RubyGems. La primera lnea carga las bibliotecas del ncleo RubyGems que vamos a necesitar para trabajar con las gemas instaladas. require rubygems La segunda lnea es donde est la mayor parte de la magia.

require_gem BlueCloth, >= 0.0.4 Esta lnea aade la gema BlueCloth a la variable Ruby $LOAD_PATH y utiliza require para cargar todas las bibliotecas que el creador de la gema especifica que se cargen automticamente. Cada joya es considerada como un conjunto de recursos. Puede contener un archivo de biblioteca o cien. En una biblioteca antigua, no RubyGems, todos estos archivos se copiaban en algn lugar compartido en el rbol de la biblioteca Ruby, un lugar que estaba en la ruta de carga predefinida de Ruby. RubyGems no funciona de esta manera. Por el contrario, mantiene a cada versin de cada gema en su propio rbol de directorios autnomo. Las gemas no se aaden a los directorios de la biblioteca estndar de Ruby. Como resultado, RubyGems tiene que hacer algunas filigranas para que se pueda llegar a estos archivos. Esto se logra mediante la adicin del rbol de directorios de la gema a la ruta de carga de Ruby. Desde el interior de un programa en ejecucin, el efecto es el mismo: slo funciona require. Desde el exterior sin embargo, RubyGems le da mucho ms control sobre lo que est cargado en sus programas Ruby. En el caso de BlueCloth, el cdigo de plantillas se distribuye como un archivo, bluecloth.rb. Este es el archivo que carga require_gem, que adems tiene un segundo argumento opcional, que especifica un requerimiento de versin. En este ejemplo, se ha especificado que la versin 0.0.4 o superior de BlueCloth debe estar instalada para utilizar este cdigo. Si hubiera requerido la versin 0.0.5 o superior, este programa hubiera fallado, porque la versin que acaba de instalar es demasiado baja para cumplir con los requisitos del programa. require rubygems require_gem BlueCloth, >= 0.0.5 produce: /usr/local/lib/ruby/site_ruby/rubygems.rb:30: in `require_gem: (LoadError) RubyGem version error: BlueCloth(0.0.4 not >= 0.0.5) from prog.rb:2

147

Como hemos dicho anteriormente, el argumento de requerimiento de versin es opcional y este ejemplo es obviamente artificial. Sin embargo, es fcil imaginar cmo esta funcin puede ser til cuando diferentes proyectos comienzan a depender de mltiples y potencialmente incompatibles versiones de la misma biblioteca. El cdigo entre bastidores Qu es lo que sucede detrs del escenario cuando se llama al mtodo mgico require_gem?

En primer lugar, la biblioteca de gemas modifica su $LOAD_PATH, incluyendo cualquier directorio que se haya aadido a la require_paths de la especificacin de gema. En segundo lugar, se llama al mtodo require para cualquiera de los archivos especificados en el atributo autorequires de la especificacin de gema (se describe ms adelante). Es as como la modificacin de la conducta de $LOAD_PATH permite a RubyGems gestionar mltiples versiones instaladas de la misma biblioteca.

Dependencias en RubyGems
Los lectores astutos se habrn dado cuenta de que el cdigo que hemos creado hasta ahora depende de que el paquete RubyGems est instalado. A largo plazo es una apuesta bastante segura (nos imaginamos) que RubyGems har su camino en la distribucin principal de Ruby. Por ahora, sin embargo, RubyGems no es parte de la distribucin estndar de Ruby, por lo que si distribuimos cdigo con require rubygems en l, ese cdigo fallar. Se pueden utilizar al menos dos tcnicas para lograr solucionar este problema. En primer lugar, se puede ajustar el cdigo RubyGems especfico en un bloque y utilizar el manejo de excepciones Ruby para rescatar el LoadError resultante durante el require. begin require rubygems require_gem BlueCloth, >= 0.0.4 rescue LoadError require bluecloth end Este cdigo intenta en primer lugar el require de la biblioteca RubyGems. Si esto falla, se invoca la lnea del rescue y el programa intentar cargar BlueCloth con un require convencional. Esto ltimo producir un error si BlueCloth no est instalada, que es el mismo comportamiento que los usuarios vern si no utilizan RubyGems. Por otra parte, RubyGems puede generar e instalar un archivo de cdigo auxiliar durante la instalacin de la gema. Este archivo se inserta en la ubicacin de la biblioteca estndar de Ruby y llevar el nombre del paquete con los contenidos de la gema (de modo que el archivo auxiliar de BlueCloth se llamar bluecloth.rb). Los usuarios que utilicen esta biblioteca pueden simplemente poner require bluecloth Esto es exactamente lo que se habra puesto en los das pre RubyGems. La diferencia ahora es que en lugar de cargar BlueCloth directamente, en su lugar va a cargar el fichero auxiliar que a su vez llama a require_gem para cargar el paquete correcto. Un archivo de cdigo auxiliar para BlueCloth sera algo como esto: require rubygems $.delete(bluecloth.rb) require_gem BlueCloth El archivo auxiliar mantiene todo el cdigo RubyGems especfico en un solo lugar, por lo que las bibliotecas dependientes no necesitan incluir ningn cdigo RubyGems en su fuente. La llamada

148

require_gem carga todo los archivos de biblioteca que mantenedor de la gema ha especificado que cargen automticamente. A partir de RubyGems 0.7.0, la instalacin de los archivos auxiliares est activada por defecto. Durante la instalacin, se puede desactivar con la opcin - no-install-stub. La mayor desventaja de la utilizacin de estos archivos auxiliares es que se pierde la capacidad de RubyGems para gestionar mltiples versiones instaladas de la misma biblioteca. Si necesita una versin especfica de una biblioteca, es mejor utilizar el mtodo LoadError descrito anteriormente.

Crear su Propia Gema

Por ahora, hemos visto lo fcil que RubyGems hace las cosas para los usuarios de una aplicacin o una biblioteca. Probablemente est listo para hacer una gema por su cuenta. Si va a crear cdigo para compartir con la comunidad de cdigo abierto, RubyGems es una forma ideal de cara a los usuarios finales para descubrir, instalar y desinstalar el cdigo. Tambin constituye una forma eficaz de gestionar los proyectos internos de empresa o incluso proyectos personales, ya que hace las actualizaciones y restauraciones tan simples. En ltima instancia, la disponibilidad de ms gemas hace ms fuerte la comunidad Ruby. Estas gemas tienen que venir de algn lugar y ahora vamos a mostrar cmo pueden comenzar a venir de usted. Digamos que ha conseguido por fin terminar la aplicacin que alguien le solicit, el diario en lnea, MiRegistro, y que ha decidido liberarlo bajo una licencia de cdigo abierto. Naturalmente, usted desea liberar MiRegistro como una gema (a la gente le encanta que le den joyas).

Diseo del Paquete


La primera tarea en la creacin de una gema es la organizacin del cdigo en una estructura de directorios que tenga sentido. Las mismas reglas que se utilizan en la creacin de un archivo tar o zip tpicos, se aplican en la organizacin de paquetes. Algunos convenios generales a continuacin. Coloque todos los archivos de cdigo fuente Ruby en un subdirectorio llamado /lib. Ms tarde, le mostraremos cmo asegurarse de que este directorio se aade a $LOAD_PATH cuando los usuarios cargan la gema. Si es apropiado para su proyecto, incluya un archivo en lib/yourproject.rb que realice los necesarios comandos require para cargar la mayor parte de la funcionalidad del proyecto. Antes que la caracterstica RubyGems autorequire, esto hace las cosas ms fciles para que otros puedan usar una biblioteca. Incluso para RubyGems, hace ms fcil que otros puedan explorar su cdigo si se les da un punto de partida obvio. Incluya siempre un archivo README que contenga un resumen del proyecto, informacin de contacto del autor e indicaciones para empezar. Utilice el formato RDoc para que se pueda agregar a la documentacin que se genera durante la instalacin de la gema. Recuerde que debe incluir los derechos de autor y de licencia en el archivo README, ya que muchos usuarios comerciales no van a usar un paquete a menos que los trminos de la licencia sean claras. Las pruebas deben ir en un directorio llamado test/. Muchos desarrolladores utilizan una librera de pruebas de unidad como una gua de uso. Es bueno ponerla en algn lugar predecible, lo que facilita a los dems el poderla encontrar. Cualquier script ejecutables deben ir en un subdirectorio denominado bin/. El cdigo fuente para las extensiones de Ruby debe ir en ext/. Si usted tiene una gran cantidad de documentacin para incluir en su gema, es bueno mantenerla en su propio subdirectorio llamado docs/. Si el archivo README est en el nivel superior del paquete, asegrese de referenciar a los lectores a este lugar. Esta estructura de directorios se ilustra en la figura 10 un poco ms adelante.

149

La especificacin de Gema
Ahora que tiene los archivos establecidos como deseaba, es el momento de llegar al corazn de la creacin de la gema: la especificacin de gema, o gemspec. Un gemspec es una coleccin de metadatos en Ruby o YAML que proporciona informacin clave sobre su gema. El gemspec se utiliza como entrada para el proceso de construccin de la gema. Puede utilizar diferentes mecanismos para crear una gema, pero todos son conceptualmente lo mismo. A continuacin la primera y bsica gema MiRegistro: require rubygems SPEC = Gem::Specification.new do |s| s.name = MiRegistro s.version = 1.0.0 s.author = Jo Programmer s.email = jo@joshost.com s.homepage = http://www.joshost.com/MiRegistro s.platform = Gem::Platform::RUBY s.summary = An online Diary for families candidates = Dir.glob({bin,docs,lib,tests}/**/*) s.files = candidates.delete_if do |item| item.include?(CVS) || item.include?(rdoc) end s.require_path = lib s.autorequire = miregistro s.test_file = tests/ts_miregistro.rb s.has_rdoc = true s.extra_rdoc_files = [README] s.add_dependency(BlueCloth, >= 0.0.4) end Vamos a recorrer rpidamente este ejemplo. Los metadatos de una gema se llevan a cabo en un objeto de clase Gem::Specification. El gemspec puede expresarse en YAML o cdigo Ruby. Aqu vamos a mostrar la versin Ruby, ya que generalmente es ms fcil de construir y ms flexible de utilizar. Los cinco primeros atributos en la especificacin dan informacin bsica como el nombre de la gema, la versin, el nombre del autor, correo electrnico y pgina principal. En este ejemplo, el siguiente atributo es la plataforma sobre la que esta gema se puede ejecutar. En este caso, la gema es una biblioteca pura de Ruby con ningn sistema operativo especfico en los requerimientos, por lo que hemos establecido la plataforma en RUBY. Si esta gema hubiera sido escrita slo para Windows, por ejemplo, la plataforma debera estar listada como Win32. Por ahora, este campo slo es informativo, pero en el futuro ser utilizado por el sistema de gemas para la seleccin inteligente de la extension de gemas precompiladas nativas. El sumario de la gema es la descripcin breve que aparece cuando se ejecuta gem query (como en nuestro ejemplo anterior con BlueCloth). El atributo files es un conjunto de rutas de acceso a los archivos que se incluirn cuando se construye la gema. En este ejemplo, hemos utilizado Dir.glob para generar la lista y filtrar los ficheros CVS y RDoc.

Magia en Tiempo de Ejecucin


Los siguientes dos atributos, require_path y autorequire, le permiten especificar los directorios que se agregarn a $LOAD_PATH cuando require_gem carga la gema, as como cualquier otro archivo que se cargar automticamente usando require. En este ejemplo, lib se refiere a una ruta relativa al directorio MiRegistro, y autorequire har que se requiera lib/miregistro.rb cuando se llama a require_gem MiRegistro. Para cada uno de estos dos atributos, RubyGems ofrece sus correspondientes versiones, require_paths y autorequire, que toman matrices, lo que permite tener muchos archivos cargados automticamente a partir de diferentes directorios, cuando la gema se carga mediante require_gem.

150

Agregar Pruebas y Documentacin


El atributo test_file contiene el nombre de ruta relativa a un nico archivo Ruby incluido en la gema y que debe ser cargado como un Test::Unit (se puede usar la forma plural, test_files, para hacer referencia a una serie de archivos que contiengan las pruebas). Para ms detalles sobre cmo crear un conjunto de pruebas, consulte el captulo sobre las pruebas unitarias. Para terminar con este ejemplo, tenemos dos atributos que controlan la produccin de documentacin local de la gema. El atributo has_rdoc especifica que se han aadido comentarios RDoc al cdigo. Es posible ejecutar RDoc sin absolutamente ningn comentario, proporcionando una vista navegable de sus interfaces, pero obviamente esto es mucho menos valioso que el funcionamiento de RDoc con el cdigo bien comentado. has_rdoc es una forma de decirle al mundo: Si. Vale la pena generar la documentacin de esta gema. RDoc tiene la ventaja de ser muy legible por lo que es una excelente opcin para un archivo README incluido en un paquete. Por defecto, el comando rdoc slo se ejecutar en los archivos de cdigo fuente. El atributo extra_rdoc_file toma una serie de rutas de acceso a los archivos no fuente de la gema que se quisieran incluir en la generacin de documentacin la RDoc.

Aadir Dependencias
Para que su gema funcione correctamente, los usuarios van a necesitar tener instalado BlueCloth. Hemos visto anteriormente cmo establecer una dependencia de versin en tiempo de carga para una librera. Ahora tenemos que inicar a nuestro gemspec esta dependencia, para lo que el instalador se asegure de que est presente durante la instalacin de MiRegistro. Lo hacemos con la adicin de una nica llamada al mtodo de nuestro objeto Gem::Specification. s.add_dependency(BlueCloth, >= 0.0.4) Los argumentos al mtodo add_dependency son idnticos a los de require_gem que hemos explicado antes. Despus de la generacin de esta gema, el intentar instalarla en un sistema limpio sera algo como:

% gem install pkg/MiRegistro-1.0.0.gem Attempting local installation of pkg/MiRegistro-1.0.0.gem /usr/local/lib/ruby/site_ruby/1.8/rubygems.rb:50:in `require_gem: (LoadError) Could not find RubyGem BlueCloth (>= 0.0.4) Debido a que se est realizando una instalacin local desde un archivo, RubyGems no intentar resolver la dependencia. Por el contrario, falla estrepitosamente y le dice que necesita BlueCloth para completar la instalacin. A continuacin, se puede instalar BlueCloth como lo hacamos antes, y las cosas irn bien la prxima vez que se intente instalar la gema MiRegistro. Si haba subido MiRegistro al repositorio central de RubyGems y luego trata de instalarla como gema en un sistema limpio, se le pedir que instale automticamente BlueClot como parte de la instalacin de MiRegistro.

% gem install -r MiRegistro Attempting remote installation of MiRegistro Install required dependency BlueCloth? [Yn] y Successfully installed MiRegistro, version 1.0.0
Ahora tienes ambas instaladas, BlueCloth y MiRegistro y tu amigo puede empezar alegremente la publicacin de su diario. Si se hubiera optado por no instalar BlueCloth, la instalacin habra fallado como lo hizo durante el intento de instalacin local. A medida que se aadan ms funciones a MiRegistro, puede que nos encontremos que necesitemos 151

gemas externas adicionales para apoyar esas caractersticas. Al mtodo add_dependency se le puede llamar varias veces en una sola gemspec, soportando cualesquiera dependencias que se necesiten.

Extensin de Ruby Gems


Hasta ahora, todos los ejemplos que hemos visto han sido cdigo Ruby puro. Sin embargo, muchas libreras Ruby se crean como extensiones nativas. Hay dos formas de empaquetar y distribuir este tipo de libreras como una gema. Se puede distribuir la gema en formato fuente y que el instalador compile el cdigo en tiempo de instalacin. Alternativamente, se pueden precompilar estas extensiones y distribuir una gema por separado para cada plataforma a la que se desee dar soporte. Para gemas fuente, RubyGems provee un atributo de Gem::Specification adicional denominado extensions . Este atributo es una matriz de rutas de acceso a los archivos Ruby que van a generar Makefiles. La forma ms habitual para crear uno de estos programas es usar la librera Ruby mkmf (se vern detalles ms adelante). Estos archivos son llamados convencionalmente extconf.rb, aunque cualquier nombre valdra. Su mam tiene una base de datos computarizada de recetas que es muy querida para ella, ya que ha estado almacenamiento sus recetas durante aos, y le gustara que le diera la posibilidad de publicar estas recetas en la web para sus amigos y familiares. Usted descubre que el programa de recetas, MenuBuilder, tiene una API nativa bastante agradable y decide escribir una extensin de Ruby para envolverlo. Ya que la extensin puede ser til para otras personas que pueden estar usando MiRegistro, usted decide empaquetarla como una gema por separado y agregarla como una dependencia adicional para MiRegistro . Aqu est la gemspec:

require rubygems spec = Gem::Specification.new do |s| s.name = MenuBuilder s.version = 1.0.0 s.author = Jo Programmer s.email = jo@joshost.com s.homepage = http://www.joshost.com/projects/MenuBuilder s.platform = Gem::Platform::RUBY s.summary = A Ruby wrapper for the MenuBuilder recipe database. s.files = [ext/main.c, ext/extconf.rb] s.require_path = . s.autorequire = MenuBuilder s.extensions = [ext/extconf.rb] end if $0 == __FILE__ Gem::manage_gems Gem::Builder.new(spec).build end Tenga en cuenta que usted tiene que incluir los archivos de cdigo fuente en la lista de las especificaciones de archivos, para que estn incluidos en el paquete de la gema para su distribucin. Cuando se instala una gema fuente, RubyGems ejecuta cada uno de sus programas de extensin y luego ejecuta el Makefile resultante.

% gem install MenuBuilder-1.0.0.gem Attempting local installation of MenuBuilder-1.0.0.gem ruby extconf.rb inst MenuBuilder-1.0.0.gem creating Makefile 152

make gcc -fPIC- g -O2 -I. -I/usr/local/lib/ruby/1.8/i686linux \ -I/usr/local/lib/ruby/1.8/i686linux -I. -c main.c gcc -shared -L/usr/local/lib -o MenuBuilder.so main.o \ -ldl -lcrypt -lm -lc make install install -c -p -m 0755 MenuBuilder.so \ /usr/local/lib/ruby/gems/1.8/gems/MenuBuilder-1.0.0/. Successfully installed MenuBuilder, version 1.0.0
RubyGems no tiene la capacidad de detectar las dependencias del sistema de libreras que las gemas fuente puedan conllevar. Si una gema fuente depende de una librera que no est instalada, la instalacin de la gema va a fracasar y se mostrar cualquier salida de error del comando make. La distribucin de las gemas fuente, requiere obviamente, que el usuario de la gema tenga un conjunto de las herramientas del trabajo de desarrollo. Como mnimo, se va a necesitar algn tipo de programa make y un compilador. Especialmente para los usuarios de Windows, estas herramientas pueden no estar presentes. Se puede superar esta limitacin mediante la distribucin de las gemas ya precompiladas. La creacin de gemas precompiladas es simple: aadir a la lista files de la especificacin de gema los archivos compilados de objetos compartidos (archivos DLL en Windows) , y asegurarse de que estos archivos se encuentran en uno de los atributos require_path de la gema. Al igual que con las gemas puras de Ruby, el comando require_gem modificar la variable Ruby $LOAD_PATH y el objeto compartido ser accesible a travs de require. Dado que estas gemas son especficas de la plataforma, tambin puede utilizar el atributo platform (esto lo vimos en el primer ejemplo gemspec) para especificar la plataforma de destino de la gema. La clase Gem::Specification define constantes para Windows, Linux Intel, Macintosh, y Ruby puro. Para las plataformas que noestn en esta lista, se puede utilizar el valor de la variable RUBY_PLATFORM. Este atributo es solamente informativo por ahora, pero es un buen hbito a adquirir. Las futuras versiones de RubyGems utilizaran el atributo platform para seleccionar de forma inteligente las gemas pre-compiladas para la plataforma en la que se est ejecutando el instalador.

La Construccin del Archivo Gema


El archivo de especificacin de MiRegistro que acabamos de crear es ejecutable como un programa Ruby. La invocacin crear un archivo gema, MiRegistro-0.5.0.gem.

% ruby miregistro.gemspec Attempting to build gem spec miregistro.gemspec Successfully built RubyGem Name: MiRegistro Version: 0.5.0 File: MiRegistro-0.5.0.gem
Alternativamente, puede usar el comando gem build para generar el archivo de la gema.

% gem build miregistro.gemspec Attempting to build gem spec miregistro.gemspec Successfully built RubyGem Name: MiRegistro Version: 0.5.0 File: MiRegistro-0.5.0.gem
Ahora que tenemos un archivo gema, podemos distribuirlo como cualquier otro paquete. Se puede poner en un servidor FTP o un sitio Web para descargar o enviar por correo electrnico a los amigos. Una vez que tus amigos tienen este archivo en su ordenador local (descargado desde el servidor FTP si es necesario), se puede instalar la gema (suponiendo que hayan instalado tambin RubyGems) mediante la llamada:

153

% gem install MiRegistro-0.5.0.gem Attempting local installation of MiRegistro-0.5.0.gem Successfully installed MiRegistro, version 0.5.0
Si desea liberar su gema a la comunidad Ruby, la forma ms fcil es usar RubyForge (http://rubyforge. org). RubyForge es un sitio Web de gestin de proyectos open-source. Tambin alberga el repositorio central de gemas. Los lanzamientos de gemas a travs de la funcionalidad de RubyForge son automticamente recogidos y aadidos al repositorio central varias veces al da. La ventaja para los posibles usuarios de su software es que ste estar disponible para consulta e instalacin remota con RubyGems, haciendo todo an ms fcil.

Construccin con Rake


Por ltimo, pero ciertamente no menos importante, podemos utilizar Rake para construir gemas. Rake utiliza un archivo de comandos llamado Rakefile para controlar la construccin. Este define (en la sintaxis de Ruby!) un conjunto de reglas y tareas. Para obtener ms informacin sobre el uso de Rake, ver http://rake.rubyforge.org. Tiene una completa documentacin y siempre est actualizada. Aqu, nos centraremos slo en Rake lo suficiente para construir una gema. De la documentacin de Rake: Las tareas son la unidad principal de trabajo en un Rakefile. Las tareas tienen un nombre (por lo general como un smbolo o una cadena), una lista de prerequisitos (ms smbolos o cadenas) y una lista de acciones (dada como un bloque). Normalmente, puede utilizar Rake con el mtodo integrado task para definir sus propias tareas por nombre en el Rakefile. Para casos especiales, tiene sentido proporcionar el cdigo de ayuda para automatizar algunas tareas repetitivas que puede haber para hacer de otra manera. La creacin de gemas es uno de estos casos especiales. Rake viene con una TaskLib especial, llamado GemPackageTask, que ayuda a integrar la creacin de gemas con el resto de su funcionalidad de construccin automatizada y el proceso de liberacin. Para utilizar GemPackageTask en el Rakefile, hay que crear el gemspec exactamente como lo hicimos anteriormente, pero esta vez colocndolo en el Rakefile. Esta especificacin de gema ahora es para GemPackageTask. require rubygems Gem::manage_gems require rake/gempackagetask spec = Gem::Specification.new do |s| s.name = MiRegistro s.version = 0.5.0 s.author = Jo Programmer s.email = jo@joshost.com s.homepage = http://www.joshost.com/MiRegistro s.platform = Gem::Platform::RUBY s.summary = An online Diary for families s.files = FileList[{bin,tests,lib,docs}/**/*].exclude(rdoc).to_a s.require_path = lib s.autorequire = miregistro s.test_file = tests/ts_miregistro.rb s.has_rdoc = true s.extra_rdoc_files = [README] s.add_dependency(BlueCloth, >= 0.0.4) s.add_dependency(MenuBuilder, >= 1.0.0) end Rake::GemPackageTask.new(spec) do |pkg| pkg.need_tar = true end Tenga en cuenta que tendr que requerir el paquete rubygems en su Rakefile. Tambin note que hemos utilizado la clase de Rake FileList en lugar de Dir.glob para construir la lista de archivos.

154

FileList es ms inteligente que Dir.glob para este propsito, ya que ignora automticamente los archivos comnmente no usados (como el directorio CVS que la herramienta CVS de control de versiones deja por ah). Internamente, GemPackageTask genera un objetivo Rake con el identificador

package_directory/gemname-gemversion.gem En nuestro caso, este identificador es pkg/MiRegistro-0.5.0.gem. Se puede invocar esta tarea desde el mismo directorio en el que se haya puesto el Rakefile.

% rake pkg/MiRegistro-0.5.0.gem (in /home/chad/download/gembook/code/MiRegistro) Successfully built RubyGem Name: MiRegistro Version: 0.5.0 File: MiRegistro-0.5.0.gem
Ahora que tenemos una tarea, se puede utilizar como cualquier otra tarea Rake, aadirle dependencias o agregarla a la lista de dependencias de otra tarea, como el despliegue o la liberacin del paquete.

Mantenimiento de la Gema (y un ltimo Vistazo a MiRegistro)


Ya se ha lanzado MiRegistro y ahora hay nuevos usuarios cada semana. Hemos tenido mucho cuidado de empaquetar la gema de forma limpia y hemos utilizado rake para construirla. Su gema est en tierra salvaje con su informacin de contacto y sabe que es slo cuestin de tiempo el empezar a recibir peticiones de nuevas funcionalidades (y cartas de admirador!) de sus usuarios. Sin embargo, su primera solicitud llega a travs de una llamada telefnica de nada menos que del viejo y querido amigo que le pidi la aplicacin. Acaba de llegar de unas vacaciones en Florida y le pregunta cmo puede incluir las fotos de vacaciones en su diario. No crees que una explicacin de comandos FTP sera una buena idea y como es un amigo de la infancia te pasas la noche en la codificacin de un mdulo de lbum de fotos para MiRegistro. Puesto que se ha aadido una nueva funcionalidad a la aplicacin (en lugar de slo la fijacin de un error), decide aumentar el nmero de versin a MiRegistro de 1.0.0 a 1.1.0. Tambin agrega una serie de pruebas para la nueva funcionalidad y un documento sobre cmo configurar la funcionalidad de subir fotos. La figura 10 en la pgina siguiente muestra la estructura completa de directorios del paquete MiRegistro-1.1.0 (MomLog). La especificacin de gema final (extrado del Rakefile) luce as: spec = Gem::Specification.new do |s| s.name = MiRegistro s.version = 1.1.0 s.author = Jo Programmer s.email = jo@joshost.com s.homepage = http://www.joshost.com/MiRegistro s.platform = Gem::Platform::RUBY s.summary = An online diary, recipe publisher, + and photo album for families. s.files = FileList[{bin,tests,lib,docs}/**/*].exclude(rdoc).to_a s.require_path = lib s.autorequire = miregistro s.test_file = tests/ts_miregistro.rb s.has_rdoc = true s.extra_rdoc_files = [README, docs/DatabaseConfiguration.rdoc, docs/Installing.rdoc, docs/PhotoAlbumSetup.rdoc] s.add_dependency(BlueCloth, >= 0.0.4) s.add_dependency(MenuBuilder, >= 1.0.0) end

155

Ejecuta Rake sobre su Rakefile y ya tiene la gema MiRegistro actualizada. Ya est listo para lanzar la nueva versin. Inicie sesin en su cuenta de RubyForge y cargue su gema en la seccin Files de su proyecto. Mientras espera a que el proceso automatizado de RubyGems libere la gema en el repositorio central de gemas, puede escribir un anuncio publicando el lanzamiento de su proyecto en RubyForge. Dentro de aproximadamente una hora, su amigo puede conectarse al servidor para instalar la nueva versin:

% gem query -rn MiRegistro


*** REMOTE GEMS *** MiRegistro (1.1.0, 1.0.0) An online diary, recipe publisher, and photo album for families. Genial! La consulta indica que hay dos versiones de MiRegistro disponibles. Escribiendo el comando de instalacin sin especificar un argumento de versin, se instala por defecto la versin ms reciente.

% gem install -r MiRegistro Attempting remote installation of MiRegistro Successfully installed MiRegistro, version 1.1.0
No ha cambiado ninguna de las dependencias de MiRegistro, por lo que la instalacin existente de BlueCloth y MenuBuilder cumple con los requisitos para MiRegistro 1.1.0.

156

Ruby y la Web
Ruby no es ajeno a Internet. No slo puede escribir su propio servidor SMTP, demonio FTP o servidor Web en Ruby, sino que tambin puede utilizar Ruby para las tareas ms habituales, como programacin CGI o como un reemplazo para PHP. Hay muchas opciones disponibles en la utilizacin de Ruby para implementar aplicaciones Web, y un solo captulo, no puede contenerlo todo. En su lugar, vamos a tratar algunos puntos y a poner de relieve las bibliotecas y los recursos que pueden ayudar. Vamos a empezar con algunas cosas sencillas: la ejecucin de programas Ruby como Common GatewayInterface (CGI).

Escritura de Scripts CGI


Se puede utilizar Ruby para escribir scripts CGI con bastante facilidad. Para tener un script Ruby que genere cdigo HTML de salida, todo lo que necesita es algo as como #!/usr/bin/ruby print Content-type: text/html\r\n\r\n print <html><body>Hello World! Its #{Time.now}</body></html>\r\n Coloque este script en un directorio CGI marcndolo como ejecutable y podr accederle a travs del navegador (si su servidor web no aade automticamente las cabeceras, tendr que aadir la cabecera de respuesta usted mismo). #!/usr/bin/ruby print HTTP/1.0 200 OK\r\n print Content-type: text/html\r\n\r\n print <html><body>Hello World! Its #{Time.now}</body></html>\r\n Sin embargo, esto est a un nivel bastante bajo. Tendra que escribir su propia solicitud de anlisis, gestin de sesin, manipulacin de cookies, mecanismo de escape y as sucesivamente. Afortunadamente, hay opciones disponibles para hacer esto ms fcil.

Utilizando cgi.rb
La clase CGI proporciona soporte para la escritura de scripts CGI. Con ella, se pueden manipular los formularios, cookies y entorno, mantener sesiones de estado, etc. Es una clase bastante grande, pero aqu vamos a echar un rpido vistazo a sus capacidades.

Escapado
Cuando se trata de URLs y de cdigo HTML hay que tener cuidado de escapar algunos caracteres. Por ejemplo, un carcter de barra (/) tiene un significado especial en una URL, por lo que debe ser escapado si no es parte de la ruta. Es decir, cualquier / en la parte de la URL se convertira en la cadena %2F y debe ser traducida de vuelta a una / para ser usada. El espacio y el smbolo & son tambin caracteres especiales. Para controlar esto, CGI proporciona las rutinas CGI.escape y CGI.unescape. require cgi puts CGI.escape(Nicholas Payton/Trumpet & Flugel Horn) produce: Nicholas+Payton%2FTrumpet+%26+Flugel+Horn Ms frecuentemente, es posible que desee escapar los caracteres especiales HTML.

require cgi

157

puts CGI.escapeHTML(a < 100 && b > 200) produce: a &lt; 100 &amp;&amp; b &gt; 200 Para obtener algo realmente de lujo, puede decidir escapar slo algunos elementos HTML dentro de una cadena. require cgi puts CGI.escapeElement(<hr><a href=/mp3>Click Here</a><br>,A) produce: <hr>&lt;a href=&quot;/mp3&quot;&gt;Click Here&lt;/a&gt;<br> Aqu slo el elemento A fu escapado, los otros elementos se quedan. Cada uno de estos mtodos tiene una versin un- para restaurar la cadena original. require cgi puts CGI.unescapeHTML(a &lt; 100 &amp;&amp; b &gt; 200) produce: a < 100 && b > 200

Parmetros de Consulta
Las peticiones HTTP desde el navegador a su aplicacin pueden contener parmetros, ya sean pasados como parte de la URL o pasados como datos incrustados en el cuerpo de la solicitud. El procesamiento de estos parmetros se complica por el hecho de que un valor con un nombre determinado puede ser devuelto varias veces en la misma peticin. Por ejemplo, supongamos que estamos escribiendo una encuesta para averiguar por qu a la gente le gusta Ruby. El cdigo HTML de nuestro formulario se vera as: <html> <head><title>Test Form</title></head> <body> I like Ruby because: <form target=cgi-bin/survey.rb> <input type=checkbox name=reason value=flexible /> Its flexible<br /> <input type=checkbox name=reason value=transparent /> Its transparent<br /> <input type=checkbox name=reason value=perlish /> Its like Perl<br /> <input type=checkbox name=reason value=fun /> Its fun <p> Your name: <input type=text name=name> </p> <input type=submit/> </form> </body> </html> Cuando alguien rellena este formulario, puede tener varias razones por las que le gusta Ruby (como se muestra en la figura 11 en la pgina siguiente). En este caso, el dato del formulario que corresponde al nombre reason tiene tres valores, correspondientes a las tres casillas marcadas.

158

La clase CGI le permite acceder a los datos del formulario de un par de maneras. En primer lugar, slo podemos tratar al objeto CGI como un hash, indexndole con nombres de campo y obteniendo valores de campo. require cgi cgi = CGI.new cgi[name] cgi[reason]

-> ->

Dave Thomas flexible

Sin embargo, esto no funciona bien con el campo reason: slo vemos uno de los tres valores. Podemos solicitar ver todos usando el mtodo CGI#params. El valor devuelto por params acta como un hash que contiene los parmetros de la solicitud. Se puede leer y escribir en este hash (lo que le permite modificar los datos asociados a una solicitud). Tenga en cuenta que cada uno de los valores del hash es en realidad una matriz.

require cgi cgi = CGI.new cgi.params -> {name=>[Dave Thomas], reason=>[flexible, transparent, fun]} cgi.params[name] -> [Dave Thomas] cgi.params[reason] -> [flexible, transparent, fun] cgi.params[name] = [ cgi[name].upcase ] cgi.params -> {name=>[DAVE THOMAS], reason=>[flexible, transparent, fun]} Se puede determinar si un parmetro en particular est presente en una solicitud utilizando CGI#has_key?. require cgi cgi = CGI.new cgi.has_key?(name) cgi.has_key?(age)

-> ->

true false

159

Generacin de HTML
CGI contiene un gran nmero de mtodos que pueden utilizarse para crear HTML --un mtodo por cada elemento. Para activar estos mtodos, se debe crear un objeto CGI llamando CGI.new, pasndolo al nivel requerido de HTML. En estos ejemplos, vamos a utilizar HTML3. Para hacer ms fcil la anidacin de elementos, estos mtodos toman su contenido como bloques de cdigo. stos deben devolver un String, que se utiliza como contenido del elemento. Para este ejemplo, hemos aadido algunas nuevas lneas gratuitas para hacer que la salida se ajuste a la pgina. require cgi cgi = CGI.new(html3) # add HTML generation methods cgi.out { cgi.html { cgi.head { \n+cgi.title{This Is a Test} } + cgi.body { \n+ cgi.form {\n+ cgi.hr + cgi.h1 { A Form: } + \n+ cgi.textarea(get_text) +\n+ cgi.br + cgi.submit } } } } produce: Content-Type: text/html Content-Length: 302 <!DOCTYPE HTML PUBLIC -//W3C//DTD HTML 3.2 Final//EN><HTML><HEAD> <TITLE>This Is a Test</TITLE></HEAD><BODY> <FORM METHOD=post ENCTYPE=application/x-www-form-urlencoded> <HR><H1>A Form: </H1> <TEXTAREA NAME=get_text ROWS=10 COLS=70></TEXTAREA> <BR><INPUT TYPE=submit></FORM></BODY></HTML> Este cdigo producir un formulario HTML titulado This Is a Test, seguido por una lnea horizontal y un encabezado de primer nivel, un rea de entrada de texto, y finalmente un botn de envo. Cuando se de el envo, tendremos un parmetro CGI llamado get_text que contiene el texto introducido por el usuario. Aunque es interesante, este mtodo de generacin de HTML es bastante laborioso y, probablemente, no se utilice mucho en la prctica. La mayora de la gente no parece que escriba el cdigo HTML directamente, ms bien utiliza un sistema de plantillas, o utiliza un framework de aplicacion, tal como Iowa. Desafortunadamente, no tenemos espacio para hablar de Iowa --puede echar un vistazo a la documentacin en lnea en http://enigo.com/projects/iowa, o mirar en el captulo 6 de The Ruby Developers Guide [FJN02] --aunque ahora vamos a ver el sistema de plantillas.

Sistema de plantillas
Los sistemas de plantillas le permiten separar lo que es la presentacin de lo que es la lgica de su aplicacin. Parece que casi todo el mundo que escribe una aplicacin web con Ruby, en algn momento tambin escribe un sistema de plantillas: en el wiki RubyGarden hay unas cuantas. Por ahora, vamos a ver en tres: las plantillas RDoc, Amrita y erb/eruby.

160

Plantillas RDoc
El sistema de documentacin RDoc (descrito anteriormente) incluye un sistema muy sencillo de plantillas, que utiliza para generar su salida en XML y HTML. Como RDoc se distribuye como parte de la norma Ruby, el sistema de plantillas est disponible donde quiera que Ruby versin 1.8.2 o posterior est instalado. Sin embargo, el sistema de plantillas no usa formatos HTML o XML convencionales (ya que est destinado a ser utilizado para generar la salida en diferentes formatos), por lo que los archivos formateados con plantillas RDoc pueden no ser fciles de editar con las herramientas convencionales de edicin de HTML. require rdoc/template HTML = %{Hello, %name%. <p> The reasons you gave were: <ul> START:reasons <li>%reason_name% (%rank%) END:reasons </ul> } data = { name => Dave Thomas, reasons => [ { reason_name => flexible, rank => 87 }, { reason_name => transparent, rank => 76 }, { reason_name => fun, rank => 94 }, ] } t = TemplatePage.new(HTML) t.write_html_on(STDOUT, data) produce: Hello, Dave Thomas. <p> The reasons you gave were: <ul> <li>flexible (87) <li>transparent (76) <li>fun (94) </ul> Se le pasa una cadena al construcrtor que contiene la plantilla que se va a utilizar. A continuacin, se le pasa al mtodo write_html_on un hash que contiene los nombres y los valores. Si la plantilla contiene la secuencia %xxxx%, se consulta el hash y se sustituye el valor correspondiente al nombre xxx Si la plantilla contiene START:yyy, el valor hash que corresponde a yyy se supone que es un array de hashes. Las lneas de la plantilla entre START:yyy y END:yyy se repiten para cada elemento del array. Las plantillas tambin soportan condicionaless: las lneas entre IF:zzz y ENDIF:zzz se incluyen en la salida slo si el hash tiene una clave zzz.

Amrita
Amrita (http://amrita.sourceforge.jp/index.html) es una librera que genera documentos HTML a partir de una plantilla que s es HTML vlido. Esto hace que Amrita sea fcil de usar con los actuales editores de HTML. Tambin hace que las plantillas de Amrita se muestren correctamente como pginas HTML independientes. Amrita utiliza las etiquetas id de los elementos HTML para determinar los valores a sustituir. Si el valor correspondiente a un nombre dado es nil o false, el elemento HTML no se incluir en el resultado.

161

Si el valor es una matriz, repite el elemento HTML correspondiente. require amrita/template include Amrita HTML = %{<p id=greeting /> <p>The reasons you gave were:</p> <ul> <li id=reasons><span id=reason_name></span>, <span id=rank></span> </ul> } data = { :greeting => Hello, Dave Thomas, :reasons => [ { :reason_name => flexible, :rank => 87 }, { :reason_name => transparent, :rank => 76 }, { :reason_name => fun, :rank => 94 }, ] } t = TemplateText.new(HTML) t.prettyprint = true t.expand(STDOUT, data) produce: <p>Hello, Dave Thomas</p> <p>The reasons you gave were:</p> <ul> <li>flexible, 87 </li> <li>transparent, 76 </li> <li>fun, 94 </li> </ul>

erb y eruby
Hasta ahora hemos visto el uso de Ruby para crear salida HTML, pero se puede cambiar la cuestin de adentro a afuera. Podemos integrar Ruby en un documento HTML. Una serie de paquetes permiten integrar las declaraciones Ruby en algn otro tipo de documento, especialmente en pginas HTML. Generalmente, esto se conoce como eRuby. Especficamente, existen varias implementaciones diferentes de eRuby, incluyendo eruby y erb. eruby, escrito por Shugo Maeda, est disponible para su descarga desde el Archivo de Aplicaciones Ruby. erb, su primo, est escrito en Ruby puro y se incluye con la distribucin estndar. Aqu veremos erb. La integracin de Ruby en HTML es un concepto muy poderoso. Bsicamente, nos da el equivalente de una herramienta como ASP, JSP o PHP, pero con toda la potencia de Ruby.

Usando erb
erb normalmente se utiliza como filtro. El texto en el archivo de entrada pasa a travs tal cul, sin tocar, slo con las siguientes excepciones:

Expresin Descripcin
<% ruby code %> Ejecuta el cdigo Ruby entre los delimitadores. <%= ruby expression %> Evalua la expresin de Ruby y reemplaza la secuencia con el valor de la expresin. <%# ruby code %> El cdigo Ruby entre los delimitadores se tiene en cuenta (para pruebas). % line of ruby code Una lnea que comienza con el signo porcentaje se supone que contiene slo cdigo Ruby.

162

erb se invoca:

erb [ opciones ] [ documento ] Si se omite documento, erb lee desde la entrada estndar. Las opciones de lnea de comandos son las siguientes:

Opcin Descripcin
-d Establecer $DEBUG a true. -Kkcode Especifica un sistema de codificacin alternativo. -n Muestra el resultado de un script Ruby (con nmeros de lnea). -r librera Carga la librera dada. -P No procesar con erb las lneas que comienzan con un %. -S nivel Ajusta el nivel de seguridad. -T modo Establece el modo de ajuste. -v Activa el modo detallado. -x Muestra el resultado de el script Ruby. Veamos algunos ejemplos sencillos. Vamos a correr el ejecutable erb en la siguiente entrada:

% a = 99 <%= a %> botellas de cerveza... La lnea que comienza con el signo de porcentaje, simplemente ejecuta la sentencia Ruby dada. La siguiente lnea contiene la secuencia <% a %>, que sustituye el valor de a. erb f1.erb produce: 99 botellas de cerveza... erb trabaja reescribiendo su entrada como un script Ruby para a continuacin ejecutar el script. Se puede ver el Ruby que genera utilizando las opciones -n o -x. erb -x f1.erb produce: _erbout = ; a = 99 _erbout.concat(( a ).to_s); _erbout.concat botellas de cerveza...\n _erbout Note como erb crea una cadena, _erbout, que contiene tanto las cadenass estticas de la plantilla como los resultados de la ejecucin de las expresiones (en este caso el valor de a). Por supuesto, puede incrustar Ruby dentro de un tipo de documento ms complejo, tal como HTML. La figura 12 muestra un par de bucles en un documento HTML.

163

Instalacin de eruby en Apache


Si desea utilizar erb como el generador de pginas de un sitio web que recibe una cantidad razonable de trfico, es probable que prefiera utilizar eruby, que tiene un mejor rendimiento. Entonces, puede configurar el servidor Web Apache para analizar automticamente documentos integrados de Ruby con eruby, de la misma manera como se hace con PHP. Se pueden crear archivos de Ruby integrados con un sufijo .rhtml y configurar el servidor Web para correr el ejecutable eruby en estos documentos obteniendo as el resultado HTML deseado. Para utilizar eruby con el servidor Web Apache, es necesario realizar los siguientes pasos: 1. Copiar el binario eruby en el directorio cgi-bin. 2. Aadir las siguientes dos lneas a httpd.conf.

AddType application/x-httpd-eruby .rhtml Action application/x-httpd-eruby /cgi-bin/eruby 3. Si se quiere, tambin se puede aadir o reemplazar la directiva DirectoryIndex de tal manera que incluya index.rhtml. Esto le permite utilizar Ruby para crear listados de directorios para los directorios 164

que no contengan un index.html. DirectoryIndex index.html index.shtml index.rhtml DirectoryIndex acepta rutas y tambin nombres de archivo, as que se puede utilizar una ruta URL absoluta donde dejar un script por defecto: DirectoryIndex index.html /cgi-bin/index.rb

Cookies
Las cookies son una forma de hacer que las aplicaciones Web almacenen su estado en la mquina del usuario. Mal vistas por algunos, las cookies son todava una conveniente (si no fiable) manera de recordar la informacin de sesin. La clase Ruby CGI controla la carga y almacenamiento de cookies. Se puede acceder a las cookies asociadas con la solicitud en curso utilizando el mtodo de CGI#cookies. Se pueden configurar de nuevo las cookies en el navegador estableciendo el parmetro cookies de CGI#out referenciando a una nica cookie o un conjunto de cookies. #!/usr/bin/ruby COOKIE_NAME = chocolate chip require cgi cgi = CGI.new values = cgi.cookies[COOKIE_NAME] if values.empty? msg = It looks as if you havent visited recently else msg = You last visited #{values[0]} end cookie = CGI::Cookie.new(COOKIE_NAME, Time.now.to_s) cookie.expires = Time.now + 30*24*3600 # 30 days cgi.out(cookie => cookie ) { msg }

Sesiones
Las cookies por s mismas an necesitan un poco de trabajo para ser tiles. Realmente queremos de la sesin la informacin que persiste entre las peticiones de un navegador web en particular. Las sesiones se manejan con la clase CGI::Session, que utiliza las cookies pero proporciona una abstraccin de alto nivel. Al igual que con las cookies, las sesiones emulan un comportamiento tipo hash, asocindo valores con claves. A diferencia de las cookies, las sesiones almacenan la mayora de sus datos en el servidor, utilizando la cookie del navegador residente simplemente como una forma de identificacin nica de los datos del lado del servidor. Las sesiones tambin tienen opcines respecto a las tcnicas de almacenamiento de estos datos: el alamcenamiento se puede dar en archivos normales, en un PStore (Persistent Object Storage), en la memoria o incluso en un almacenaje personalizado. Despus de su utilizacin, se deben cerrar las sesiones ya que asegura que se guarden los datos. Cuando se haya terminado definitivamente con una sesin, se deben borrar. require cgi require cgi/session cgi = CGI.new(html3) sess = CGI::Session.new(cgi, session_key => rubyweb, prefix => web-session. )

165

if sess[lastaccess] msg = You were last here #{sess[lastaccess]}. else msg = Looks like you havent been here for a while end count = (sess[accesscount] || 0).to_i count += 1 msg << <p>Number of visits: #{count} sess[accesscount] = count sess[lastaccess] = Time.now.to_s sess.close cgi.out { cgi.html { cgi.body { msg } } } El cdigo anterior utiliza el mecanismo de almacenamiento predeterminado para las sesiones: los datos persistentes se almacenan en archivos en el directorio temporal por defecto (ver Dir.tmpdir). Los nombres de archivo emepezarn con web-session. y terminar con una versin hash del nmero de sesin. Ver ri CGI::Session para ms informacin.

Mejorar el Rendimiento
Se puede utilizar Ruby para escribir programas CGI para la Web, pero, al igual que la mayora de los programas de CGI, la configuracin por defecto tiene que empezar una nueva copia de Ruby con cada acceso de pgina cgi-bin. Eso es costoso en trminos de la utilizacin de la mquina y puede ser muy lento para los internautas. El servidor Web Apache resuelve este problema soportando mdulos cargables. Tpicamente, estos mdulos son cargados dinmicamente y se convierten en una parte de los procesos que ejecuta el servidor Web --que no tiene la necesidad de generar otro intrprete una y otra vez para dar servicio a las solicitudes, ya que el servidor Web es el intrprete. Y as llegamos a mod_ruby (disponible en los archivos), un mdulo de Apache que acopla un intrprete Ruby completo en el servidor Apache mismo. El archivo readme incluido con mod_ruby proporciona detalles sobre cmo compilarlo e instalarlo. Una vez instalado y configurado, puede ejecutar scripts Ruby ms o menos como se hace sin mod_ ruby, slo que ahora mucho ms rpido. Tambin puede aprovechar las caractersticas adicionales que ofrece mod_ruby (como la integracin en el manejo de las solicitudes de Apache). Sin embargo, hay que prestar atencin a algunas cosas. Debido a que el intrprete permanece en la memoria entre solicitudes, puede terminar la tramitacin de las solicitudes de varias aplicaciones. Es posible que las bibliotecas de estas aplicaciones entren en conflicto (sobre todo si las diferentes bibliotecas contienen clases con el mismo nombre). Tampoco se puede asumir que el mismo intrprete se encargar de manejar la serie de solicitudes de una sesin de navegador --Apache asignar los procesos controladores utilizando sus algoritmos internos. Algunas de estas cuestiones se resuelven mediante el protocolo FastCGI. Este es un hack interesante, a disposicin de todos los programas estilo CGI, no slo Ruby. Utiliza un programa proxy muy pequeo que generalmente se ejecuta como mdulo de Apache. Cuando se reciben las solicitudes, este proxy las reenva a un proceso concreto de larga ejecucin que acta como un script CGI normal. Los resultados se mandan de vuelta al proxy, y a continuacin de vuelta al navegador. FastCGI tiene las mismas ventajas que correr mod_ruby, ya que el intrprete siempre est ejecutandose en segundo plano. Tambin le da ms control sobre cmo se asignan las solicitudes a los intrpretes. Encontrar ms informacin en http://www.fastcgi.com.

166

Servidores Web Alternativos


Hasta ahora, hemos estado ejecutando scripts de Ruby bajo el control del servidor Web Apache. Sin embargo, a partir de la versin 1.8 Ruby viene con WEBrick, una flexible herramienta escrita en Ruby para dar servicio HTTP. Bsicamente, se trata de un plug-in extensible --basado en framework, que le permite escribir servidores para manejar solicitudes y respuestas HTTP. A continuacin un servidor HTTP bsico que sirve documentos e ndices de directorio: #!/usr/bin/ruby require webrick include WEBrick s = HTTPServer.new( :Port => 2000, :DocumentRoot => File.join(Dir.pwd, /html) ) trap(INT) { s.shutdown } s.start El constructor HTTPServer crea un nuevo servidor web en el puerto 2000. El cdigo establece el directorio raz de documentos en el subdirectorio /html del directorio actual. A continuacin, utiliza Kernel.trap para organizar el cierre ordenado de las interrupciones antes de iniciar la ejecucin del servidor. Si se apunta el navegador a http://localhost:2000, se debera ver una lista del subdirectorio html. WEBrick puede hacer mucho ms que servir contenido esttico. Se puede utilizar como un contenedor de servlets de Java. El siguiente cdigo monta un servlet simple en /hello. Se invoca al mtodo do_GET cunado llegan las solicitudes. Se utiliza el objeto respuesta para mostrar la informacin de agente de usuario y los parmetros de solicitud. #!/usr/bin/ruby require webrick include WEBrick s = HTTPServer.new( :Port => 2000 ) class HelloServlet < HTTPServlet::AbstractServlet def do_GET(req, res) res[Conten-tType] = text/html res.body = %{ <html><body> Hello. Youre calling from a #{req[User-Agent]} <p> I see parameters: #{req.query.keys.join(, )} </body></html> } end end s.mount(/hello, HelloServlet) trap(INT){ s.shutdown } s.start

SOAP y Servicios Web


SOAP -jabn, en ingls-, se puso en su tiempo para Simple Object Access Protocol. Cuando la gente ya no pudo soportar la irona, el acrnimo cay y ahora SOAP es slo un nombre. Ruby viene ahora con una implementacin de SOAP. Esto le permite escribir servidores y clientes utilizando servicios Web. Por su naturaleza, estas aplicaciones pueden funcionar tanto a nivel local como remota a travs de una red. Las aplicaciones SOAP ignoran el lenguaje de implementacin de sus pares en la red, por lo que utilizar SOAP es una forma conveniente de interconectar aplicaciones Ruby con otras escritas en lenguajes como Java, Visual Basic o C++.

167

SOAP bsicamente es un mecanismo que utiliza XML para enviar datos entre dos nodos de una red. Se suele utilizar para implementar las llamadas a procedimiento remoto, RPC, entre procesos de distribuidos. Un servidor SOAP publica una o ms interfaces. Estas interfaces se definen en trminos de tipos de datos y los mtodos que utilizan estos tipos. A continucacin, el cliente SOAP, crea un proxy local que mediante SOAP se conecta a las interfaces en el servidor. En el proxy se pasa una llamada a un mtodo a la interfaz correspondiente en el servidor, y los valores de retorno generados por el mtodo en el servidor, se devuelven al cliente a travs del proxy. Vamos a empezar con un servicio SOAP trivial. Vamos a escribir un objeto que hace los clculos de los intereses. Inicialmente, se ofrece un mtodo nico, compound, que determina el inters compuesto dando uno principal, una tasa de inters, el nmero de veces que el inters se ve combinado por ao y el nmero de aos. Con fines de gestin, tambin vamos a llevar un registro de cuntas veces fue llamado este mtodo y hacer disponible este contador a travs de un descriptor de acceso. Tenga en cuenta que esta clase es slo cdigo Ruby regular --no sabe que se est ejecutando en un entorno SOAP. class InterestCalculator attr_reader :call_count def initialize @call_count = 0 end def compound(principal, rate, freq, years) @call_count += 1 principal*(1.0 + rate/freq)**(freq*years) end end Ahora vamos a hacer un objeto de esta clase que est disponible a travs de un servidor SOAP. Esto permitir a las aplicaciones cliente llamar a los mtodos del objeto a travs de la red. Aqu estamos usando un servidor independiente, lo cual es conveniente cuando se hacen pruebas y se puede utilizar la lnea de comandos. Tambin se pueden ejecutar los servidores SOAP Ruby como scripts CGI o bajo mod_ruby. require soap/rpc/standaloneServer require interestcalc NS = http://pragprog.com/InterestCalc class Server2 < SOAP::RPC::StandaloneServer def on_init calc = InterestCalculator.new add_method(calc, compound, principal, rate, freq, years) add_method(calc, call_count) end end svr = Server2.new(Calc, NS, 0.0.0.0, 12321) trap(INT) { svr.shutdown } svr.start Este cdigo define una clase que implementa un servidor SOAP independiente. Cuando se inicializa, la clase crea un objeto InterestCalculator (una instancia de la clase que acabamos de escribir). A continuacin, utiliza add_method para aadir los dos mtodos utilizados por esta clase, compound y call_count. Finalmente, el cdigo crea y ejecuta una instancia de esta clase servidor. Los parmetros para el constructor son el nombre de la aplicacin, el espacio de nombres por defecto, la direccin de la interfaz a utilizar y el puerto. Entonces necesitamos escribir algn cdigo de cliente para acceder a este servidor. El cliente crea un proxy local para el servicio InterestCalculator en el servidor, agrega los mtodos que desea utilizar y luego los llama. require soap/rpc/driver proxy = SOAP::RPC::Driver.new(http://localhost:12321, http://pragprog.com/InterestCalc) proxy.add_method(compound, principal, rate, freq, years)

168

proxy.add_method(call_count) puts Call count: #{proxy.call_count} puts 5 years, compound annually: #{proxy.compound(100, 0.06, 1, 5)} puts 5 years, compound monthly: #{proxy.compound(100, 0.06, 12, 5)} puts Call count: #{proxy.call_count} Para probar esto, podemos ejecutar el servidor en una ventana de consola (la salida que se muestra aqu ha sido reformateada ligeramente para adaptarse a esta pgina).

% ruby server.rb I, [2004-07-26T10:55:51.629451 #12327] INFO -- Calc: Start of Calc. I, [2004-07-26T10:55:51.633755 #12327] INFO -- Calc: WEBrick 1.3.1 I, [2004-07-26T10:55:51.635146 #12327] INFO -- Calc: ruby 1.8.2 (2004-07-26) [powerpcdarwin] I, [2004-07-26T10:55:51.639347 #12327] INFO -- Calc: WEBrick::HTTPServer#start: pid=12327 port=12321
A continuacin, se ejecuta el cliente en otra ventana.

% ruby client.rb Call count: 0 5 years, compound annually: 133.82255776 5 years, compound monthly: 134.885015254931 Call count: 2
En buen estado! Funciona con xito, llamamos a todos nuestros amigos y lo ejecutamos de nuevo.

% ruby client.rb Call count: 2 5 years, compound annually: 133.82255776 5 years, compound monthly: 134.885015254931 Call count: 4
Observar cmo la segunda vez que se ejecuta el cliente, el nmero de las llamadas se inicia ahora en dos. El servidor crea un solo objeto InterestCalculator para atender las solicitudes de entrada que se vuelve a utilizar para cada solicitud.

SOAP y Google
Es evidente que el beneficio real de SOAP es la forma en que le permite interoperar con otros servicios en la Web. Como ejemplo, vamos a escribir algo de cdigo Ruby para enviar consultas a la API web de Google. Antes de enviar las consultas a Google, se necesita una clave de desarrollador. Este servicio est disponible desde Google, vaya a http://www.google.com/apis y siga las instrucciones en el paso 2, crear una cuenta de Google. Despus de entrar su direccin de correo electrnico y proporcionar una contrasea, Google le enviar una clave de desarrollador. En los siguientes ejemplos, vamos a suponer que usted ha almacenado esta clave en el archivo .google_key en su directorio principal. Vamos a empezar en el nivel ms bsico. En cuanto a la documentacin de la API de Google, el mtodo doGoogleSearch descubrimos que tiene diez (!) parmetros. key La clave de desarrollador. q La cadena de consulta. start El ndice del primer resultado requerido. maxResults El nmero mximo de resultados a devolver por la consulta. filter Si est activado, comprime los resultados para que pginas similares y pginas en el mismo dominio slo se muestren una vez.

169

restrict se restringe la bsqueda a un subconjunto del ndice de Google Web. safeSearch Si est activado, elimina los posibles contenidos para adultos de los resultados. lr Restringe la bsqueda a los documentos en un determinado conjunto de lenguas. ie Ignorado (codificacin de entrada) oe Ignorado (codificacin de salida) Podemos utilizar la llamada add_method para construir un proxy SOAP para el mtodo doGoogleSearch . El siguiente ejemplo hace exactamente eso, imprime la primera entrada devuelta buscando en Google el trmino pragmatic. require soap/rpc/driver require cgi endpoint = http://api.google.com/search/beta2 namespace = urn:GoogleSearch soap = SOAP::RPC::Driver.new(endpoint, namespace) soap.add_method(doGoogleSearch, key, q, start, maxResults, filter, restrict, safeSearch, lr, ie, oe) query = pragmatic key = File.read(File.join(ENV[HOME], .google_key)).chomp result = soap.doGoogleSearch(key, query, 0, 1, false, nil, false, nil, nil, nil) printf Estimated number of results is %d.\n, result.estimatedTotalResultsCount printf Your query took %6f seconds.\n, result.searchTime first = result.resultElements[0] puts first.title puts first.URL puts CGI.unescapeHTML(first.snippet) Al ejecutarlo, se ver algo como lo siguiente (ntese cmo Google ha puesto el trmino de consulta de relieve). Estimated number of results is 550000. Your query took 0.123762 seconds. The <b>Pragmatic</b> Programmers, LLC http://www.pragmaticprogrammer.com/ Home of Andrew Hunt and David Thomass best-selling book The <b>Pragmatic</b> Programmer<br> and The <b>Pragmatic</b> Starter Kit (tm) series. <b>...</b> The <b>Pragmatic</b> Bookshelf TM. <b>...</b> Sin embargo, SOAP permite el descubrimiento dinmico de la interfaz de objetos en el servidor. Esto se hace utilizando WSDL (Web Services Description Language). Un archivo WSDL es un documento XML que describe los tipos, mtodos y mecanismos de acceso para una interfaz de servicios Web. Los clientes SOAP pueden leer los archivos WSDL para crear las interfaces de un servidor de forma automtica. La pgina web http://code.creativecommons.org/svnroot/stats/GoogleSearch.wsdl contiene el WSDL que describe la interfaz de Google. Podemos alterar nuestra aplicacin de bsqueda para leer este WSDL, que elimina la necesidad de agregar el mtodo doGoogleSearch explcitamente. require soap/wsdlDriver require cgi WSDL_URL = http://api.google.com/GoogleSearch.wsdl soap = SOAP::WSDLDriverFactory.new(WSDL_URL).createDriver query = pragmatic key = File.read(File.join(ENV[HOME], .google_key)).chomp result = soap.doGoogleSearch(key, query, 0, 1, false, nil, false, nil, nil, nil) printf Estimated number of results is %d.\n, result.estimatedTotalResultsCount

170

printf Your query took %6f seconds.\n, result.searchTime first = result.resultElements[0] puts first.title puts first.URL puts CGI.unescapeHTML(first.snippet) Por ltimo, podemos ir un paso ms all utilizando la librera Google de Ian Macdonald (disponible en el Ruby Application Archive, en http://raa.ruby-lang.org/project/ruby-google/), que encapsula la API de servicios Web detrs de una interfaz agradable (bueno, si no por otra razn, por lo menos porque elimina la necesidad de todos los parmetros adicionales). La biblioteca tambin cuenta con mtodos para construir rangos de fechas y otras restricciones para una consulta en Google y proporciona interfaces a la cach de Google y a la facilidad de correccin ortogrfica. El siguiente cdigo es nuestra bsqueda de pragmatic utilizando la biblioteca de Ian. require google require cgi key = File.read(File.join(ENV[HOME], .google_key)).chomp google = Google::Search.new(key) result = google.search(pragmatic) printf Estimated number of results is %d.\n, result.estimatedTotalResultsCount printf Your query took %6f seconds.\n, result.searchTime first = result.resultElements[0] puts first.title puts first.url puts CGI.unescapeHTML(first.snippet)

Ms Informacin

La programacin web con Ruby es un gran tema. Para profundizar ms, es posible que desee echar un vistazo al captulo 9 de The Ruby Way [Ful01], donde encontrar muchos ejemplos de red y de programacin Web. En el captulo 6 de The Ruby Developers Guide [FJN02], encontrar algunos buenos ejemplos de la estructuracin de las aplicaciones CGI, junto con algunos ejemplos de cdigo Iowa. Si SOAP puede ser complejo, es posible que desee ver el uso de XML-RPC, que se describe brevemente ms adelante. Hay un nmero de otros marcos de desarrollo Ruby para Web que estn disponibles en la red. Esta es un rea dinmica: nuevos contendientes aparecen constantemente y es difcil para un libro impreso el ser definitivo. Sin embargo, dos frameworks que estn atrayendo el espritu de la comunidad Ruby son Rails (http://www.rubyonrails.org), y CGIKit (http://www.spice-of-life.net/cgikit/index_en.html).

Ruby Tk
El archivo de aplicaciones Ruby contiene varias extensiones que proveen a Ruby de una interfaz grfica de usuario (GUI), incluyendo las extensiones para Fox, GTK y otras. La extensin Tk se incluye en la distribucin y funciona tanto en sistemas Unix como Windows. Para utilizarla, es necesario tener instalado Tk en su sistema. Tk es un gran sistema y se han escrito libros enteros sobre l, as que no vamos a perder el tiempo y los recursos ahondando demasiado en Tk, pero si vamos a concentrarnos en la forma de acceder a las funciones Tk desde Ruby. Usted necesitar uno de estos libros de referencia con el fin de utilizar Ruby con Tk eficazmente. El binding que utilizamos es el ms cercano al binding de Perl, por lo que es probable que desee obtener una copia de Learning Perl/Tk [Wal99] o Perl/Tk Pocket Reference [Lid98]. Tk trabaja segn una composicin de modelo, es decir, empieza por la creacin de un contenedor (como un TkFrame o un TkRoot) y luego crea los widgets (otro nombre para los componentes de la

171

interfazgrfica de usuario) que lo pueblan, como botones o etiquetas. Cuando se est listo para iniciar la interfaz grfica de usuario, se llama a Tk.mainloop. El motor de Tk toma entonces el control del programa, que muestra los widgets y llama al cdigo en respuesta a los eventos de la interfaz grfica de usuario.

Simple aplicacin Tk
Una simple aplicacin Tk en Ruby puede ser algo como esto: require tk root = TkRoot.new { title Ex1 } TkLabel.new(root) do text Hello, World! pack { padx 15 ; pady 15; side left } end Tk.mainloop Veamos el cdigo un poco ms de cerca. Despus de cargar el mdulo de extensin Tk, se crea un marco de nivel raz (root-level) con TkRoot.new. A continuacin hacemos un widget TkLabel como proceso hijo del marco raz, estableciendo varias opciones para la etiqueta. Por ltimo, se empaqueta el marco raz y se entra en el bucle principal de eventos GUI. Es un buen hbito especificar la raiz de forma explcita, aunque se puede dejar de lado (junto con las opciones adicionales) y madurar esto en tres lneas: require tk TkLabel.new { text Hello, World!; pack } Tk.mainloop Esto es todo lo que hay que hacer! Armado con uno de los libros de Perl/Tk de los quehacemos referencia al comienzo de este captulo, se pueden producir todas las sofisticadas interfaces grficas de usuario que usted necesite. Pero, de nuevo, si desea quedarse para algunos detalles ms, aqu vienen.

Widgets
La creacin de widgets es fcil. Se toma el nombre del widget como figura en la documentacin de Tk y se aade Tk al principio de tal nombre. Por ejemplo, los widgets Label, Button y Entry, se convierten en las clases TkLabel, TkButton y TkEntry. Se crea una instancia de un widget utilizando new, al igual que se hara con cualquier otro objeto. Si no se especifica un padre para un determinado widget, se usar por defecto el marco de nivel raz. Por lo general, se especifica el padre del widget junto con muchas otras opciones --el color, tamao, etc. Tambin tenemos que ser capaces de obtener informacin desde nuestros widgets durante la ejecucin de nuestro programa, mediante la creacin de callbacks (rutinas que se invocan cuando ocurren ciertos eventos) y de intercambiar datos.

Opciones de Configuracin para los Widgets


Si nos fijamos en un manual de referencia de Tk (el que est escrito para Perl/Tk, por ejemplo), nos daremos cuenta que las opciones para los widgets normalmente se especifican con un guin --como las opciones de lnea de comandos. En Perl/Tk, las opciones se le pasan a un widget en un Hash. Se puede hacer en Ruby as, pero tambin puede pasar las opciones utilizando un bloque de cdigo. El nombre de la opcin se utiliza como el nombre de un mtodo dentro del bloque y los argumentos de opcin aparecen como argumentos para la llamada al mtodo. Los widgets toman un padre como primer argumento, seguido de un hash de opciones opcional o el bloque de cdigo de opciones. Por lo tanto, las dos formas siguientes son equivalentes. TkLabel.new(parent_widget) do text Hello, World! pack(padx => 5, pady => 5, side => left)

172

end # or TkLabel.new(parent_widget, text => Hello, World!).pack(...) Una pequea precaucin cuando se utiliza la forma del bloque de cdigo: el mbito de las variables no es como se piensa que es. El bloque es evaluado realmente en el contexto del objeto widget, no en el del llamador. Esto significa que las variables de instancia del llamador no estarn disponible en el bloque, pero si las variables locales del mbito que lo contiene y las globales (no es que utilize variables globales, por supuesto). Vamos a mostrar como se pasan las opciones utilizando ambos mtodos, en los ejemplos que siguen. Las distancias (como las opciones padx y pady en estos ejemplos) se supone que son en pxeles, pero puede ser especificado en las diferentes unidades con los sufijos c (cm), i (pulgadas), m (milmetros), o p (puntos). 12p, por ejemplo, es doce puntos.

Obtencin de Datos del Widget


Podemos obtener informacin de vuelta de los widgets mediante retornos de llamada (callbacks) y por variables binding (vinculantes). Los retornos de llamada son muy fciles de configurar. La opcin command (que se muestra en la llamada a TkButton en el ejemplo que sigue) toma un objeto Proc, que ser llamado cuando se dispare el retorno de llamada. Aqu pasamos el proc como un bloque asociado con la llamada al mtodo, pero tambin podramos haber utilizado Kernel.lambda para generar un objeto Proc explcito. require tk TkButton.new do text EXIT command { exit } pack(side=>left, padx=>10, pady=>10) end Tk.mainloop Tambin se puede enlazar una variable Ruby para un valor widget Tk usando un proxy TkVariable. Esto arregla las cosas de manera que cada vez que cambia el valor del widget, la variable Ruby se actualiza automticamente, y cuando cambia la variable, el widget reflejar el nuevo valor. Se muestra esto en el siguiente ejemplo. Observe cmo est configurado TkCheckButton, la documentacin dice que la opcin variable toma una var referencia como argumento. Para ello, creamos una variable Tk de referencia con TkVariable.new. El acceso a mycheck.value devolver la cadena 0 1 dependiendo de si la casilla est marcada. Usted puede utilizar el mismo mecanismo para todo lo que es compatible con una referencia var, como botones radiales y campos de texto. require tk packing = { padx=>5, pady=>5, side => left } checked = TkVariable.new def checked.status value == 1 ? Yes : No end status = TkLabel.new do text checked.status pack(packing) end TkCheckButton.new do variable checked pack(packing) end TkButton.new do

173

text Show status command { status.text(checked.status) } pack(packing) end Tk.mainloop

Configuracin/Obtencin de Opciones Dinmicamente


Adems de establecer las opciones de un widget cuando se crea, puede volver a configurar un widget mientras se est ejecutando. Cada widget soporta el mtodo configure, que toma un hash o un bloque de cdigo de la misma manera como new. Podemos modificar el primer ejemplo para cambiar el texto de la etiqueta en respuesta a un clic de botn. require tk root = TkRoot.new { title Ex3 } top = TkFrame.new(root) { relief raised; border 5 } lbl = TkLabel.new(top) do justify center text Hello, World! pack(padx=>5, pady=>5, side => top) end TkButton.new(top) do text Ok command { exit } pack(side=>left, padx=>10, pady=>10) end TkButton.new(top) do text Cancel command { lbl.configure(text=>Goodbye, Cruel World!) } pack(side=>right, padx=>10, pady=>10) end top.pack(fill=>both, side =>top) Tk.mainloop Ahora, cuando se hace clic en el botn Cancelar, el texto de la etiqueta cambia inmediatamente de Hola, Mundo! a Adis, mundo cruel! Tambin puede consultar los valores de opcin particulares para los widgets con cget.

require tk b = TkButton.new do text OK justify left border 5 end b.cget(text) -> b.cget(justify) -> b.cget(border) ->

OK left 5

Aplicacin de Ejemplo
He aqu un ejemplo un poco ms largo que muestra una aplicacin real --un generador de pig latin. Escriba la frase tal como las reglas de Ruby, y el botn Pig It lo traducir al instante al latn de cerdo. require tk class PigBox def pig(word) leading_cap = word =~ /^[A-Z]/ word.downcase! res = case word

174

when /^[aeiouy]/ word+way when /^([^aeiouy]+)(.*)/ $2+$1+ay else word end leading_cap ? res.capitalize : res end def show_pig @text.value = @text.value.split.collect{|w| pig(w)}.join( ) end def initialize ph = { padx => 10, pady => 10 } # common options root = TkRoot.new { title Pig } top = TkFrame.new(root) { background white } TkLabel.new(top) {text Enter Text: ; pack(ph) } @text = TkVariable.new TkEntry.new(top, textvariable => @text).pack(ph) pig_b = TkButton.new(top) { text Pig It; pack ph} pig_b.command { show_pig } exit_b = TkButton.new(top) {text Exit; pack ph} exit_b.command { exit } top.pack(fill=>both, side =>top) end end PigBox.new Tk.mainloop

Enlazar Eventos

Los widgets estn expuestos al mundo real. En ellos se hace clic, se mueve el ratn sobre ellos, los usuarios escriben en ellos, todas estas cosas y muchas ms, generan eventos que podemos captar. Se puede crear un binding desde un evento en un widget en particular a un bloque de cdigo, usando el mtodo de widget bind. Por ejemplo, supongamos que hemos creado un widget de botn que muestra una imagen. Nos gustara que la imagen cambie cuando el ratn del usuario pasa por encima del botn. require tk image1 = TkPhotoImage.new { file img1.gif } image2 = TkPhotoImage.new { file img2.gif } b = TkButton.new(@root) do image image1 command { exit } pack end b.bind(Enter) { b.configure(image=>image2) } b.bind(Leave) { b.configure(image=>image1) } Tk.mainloop En primer lugar, se crean dos objetos de imagen GIF a partir de archivos en el disco utilizando TkPhotoImage . Despus, se crea un botn (muy atractivo, llamado b), que muestra la imagen image1. A continuacin, se enlaza el evento Enter para que cambie dinmicamente la imagen mostrada a image2 cuando el ratn est sobre el botn, y el evento Leave para volver a image1 cuando el ratn abandona el botn. Este ejemplo muestra los eventos simples Enter y Leave. Sin embargo, el nombre de evento dado como un argumento a bind puede estar compuesto de varias sub-cadenas, separadas por guiones, en

175

disposicin modificador-modificador-tipo-detalle. Los modificadores son mencionados en la referencia de Tk e incluyen Button1, Control, Alt, Shift, etc. Tipo es el nombre del evento (tomado de las convenciones de nombres X11) e incluye eventos como ButtonPress, KeyPress y Expose. Detalle puede ser un nmero del 1 al 5 para los botones o un smbolo clave para la entrada de teclado. Por ejemplo, un binding que tendr lugar a la liberacin del botn 1 del ratn mientras se presiona la tecla control puede especificarse como Control-Button1-ButtonRelease o Control-ButtonRelease-1 El evento en s puede contener ciertos campos, como la hora del evento y las posiciones x e y. bind pueden pasar estos elementos al retorno de llamada, utilizando los cdigos de campos de evento. Estos se utilizan como las especificaciones printf. Por ejemplo, para obtener las coordenadas x e y en un movimiento del ratn, se especifica la llamada a bind con tres parmetros. El segundo parmetro es el Proc para el retorno de llamada, y el tercer parmetro es la cadena de campo de evento. canvas.bind(Motion, lambda {|x, y| do_motion (x, y)}, %x %y)

Canvas
Tk proporciona un widget Canvas con el que se puede dibujar y generar una salida PostScript. La figura 13 en la pgina 178 muestra un pequeo cdigo simple (adaptado de la distribucin) que dibuja lneas rectas. Clikcando y manteniendo pulsado el botn 1 se iniciar una lnea, que ser bandeada en goma segn se mueva el ratn. Cuando suelte el botn 1, la lnea se dibujar en esa posicin. Gestin de la Geometra En el cdigo de ejemplo de este captulo, ver referencias al mtodo widget pack. Es una llamada muy importante --olvdese de ella y usted nunca ver el widget. pack es un comando que le dice al gestor de la geometra como colocar el widget de acuerdo a las limitaciones que se especifican. Los gestores de geometra reconocen tres comandos: Comando Especificacin de Colocacin

pack Flexible, basada en restricciones de colocacin place Posicin Absoluta grid Posicin Tabular (Fila/Columna) Como pack es el comando ms utilizado, es el que usamos en nuestros ejemplos.

Unos pocos clics del ratn y tienes una obra maestra instantnea. Como se suele decir, No se encontr el artista, as que tuvimos que colgar el cuadro. . . .

176

Desplazamiento
A menos que se planee hacer dibujos muy pequeos, el ejemplo anterior puede que no sea til. TkCanvas , TkListbox y TkText pueden ser configurados para usar barras de desplazamiento, y que se pueda trabajar en un subconjunto ms pequeo de una gran imagen. La comunicacin entre una barra de desplazamiento y un widget es bidireccional. El movimiento de la barra de desplazamiento hace que cambier la vista del widget, pero cuando es sta la que cambia por algn otro medio, la barra de desplazamiento tiene que cambiar tambin para reflejar la nueva posicin. Aunque que no hemos hecho mucho con listas, el ejemplo de desplazamiento siguiente utiliza el desplazamiento para una lista de texto. En el fragmento de cdigo, vamos a empezar por la creacin de un simple TkListbox y un TkScrollbar asociados. El callback o retorno de llamada de la barra de desplazamiento (configurado con command) llama al mtodo widget de lista yview, que hace que cambie el valor de la parte visible de la lista, en la direccin y. Despus de configurar el retorno de llamada, hacemos la relacin inversa: cuando la lista tenga la necesidad de desplazarse, vamos a configurar el rango adecuado para scrollbar con TkScrollbar#set. Vamos a utilizar este mismo fragmento en un programa totalmente funcional en la siguiente seccin: list_w = TkListbox.new(frame) do selectmode single pack side => left end list_w.bind(ButtonRelease-1) do busy do filename = list_w.get(*list_w.curselection) tmp_img = TkPhotoImage.new { file filename } scale = tmp_img.height / 100 scale = 1 if scale < 1 image_w.copy(tmp_img, subsample => [scale, scale]) image_w.pack end end scroll_bar = TkScrollbar.new(frame) do command {|*args| list_w.yview *args } pack side => left, fill => y end list_w.yscrollcommand {|first,last| scroll_bar.set(first,last) }

Slo Una Cosa Ms


Podramos seguir hablando de Tk para otros cientos de pginas, pero ese sera otro libro. El siguiente programa es nuestro ltimo ejemplo Tk --un visor simple de imagenes GIF. Se puede seleccionar un nombre de archivo GIF a partir de la lista de desplazamiento y se mostrar una versin en miniatura de la imagen. Vamos a sealar slo alguna cosa ms. Alguna vez ha utilizado una aplicacin que crea un cursor ocupado y luego se olvida de restablecerlo a la normalidad? Hay un buen truco en Ruby para evitar que esto suceda. Recuerda cmo File.new utiliza un bloque para asegurarse de que el archivo se cierra despus de utilizarlo? Podemos hacer algo similar con el mtodo busy, como se muestra en el siguiente ejemplo. Este programa tambin muestra algunas manipulaciones simples con TkListbox --aadir elementos a la lista, crear un retorno de llamada en un botn del ratn (probablemente se requiera a la liberacin del botn, no a la pulsacin, ya que el widget es seleccionado en la pulsacin del botn), y la recuperacin de la seleccin en curso. Hasta ahora, hemos usado TkPhotoImage para mostrar las imgenes directamente, pero tambin se puede hacer zoom, submuestreo y tambin mostrar porciones de las imgenes. Aqu se utiliza la funcin de sub-muestreo para escalar la imagen para su visualizacin.

177

require tk class GifViewer def initialize(filelist) setup_viewer(filelist) end def run Tk.mainloop end

178

def setup_viewer(filelist) @root = TkRoot.new {title Scroll List} frame = TkFrame.new(@root) image_w = TkPhotoImage.new TkLabel.new(frame) do image image_w pack side=>right end list_w = TkListbox.new(frame) do selectmode single pack side => left end list_w.bind(ButtonRelease-1) do busy do filename = list_w.get(*list_w.curselection) tmp_img = TkPhotoImage.new { file filename } scale = tmp_img.height / 100 scale = 1 if scale < 1 image_w.copy(tmp_img, subsample => [scale, scale]) image_w.pack end end filelist.each do |name| list_w.insert(end, name) # Insert each file name into the list end scroll_bar = TkScrollbar.new(frame) do command {|*args| list_w.yview *args } pack side => left, fill => y end list_w.yscrollcommand {|first,last| scroll_bar.set(first,last) } frame.pack end # Run a block with a wait cursor def busy @root.cursor watch # Set a watch cursor yield ensure @root.cursor # Back to original end end viewer = GifViewer.new(Dir[screenshots/gifs/*.gif]) viewer.run

Trasladar desde Documentacin Perl/Tk


Eso es todo, usted cuenta con usted mismo ahora. En su mayor parte, se puede trasladar fcilmente la documentacin entregada para Perl/Tk a Ruby. Hay algunas excepciones, algunos mtodos no se aplican y algunas funcionalidades extra indocumentadas. Hasta que un libro de Ruby/Tk salga, lo mejor es preguntar en el grupo de noticias o leer el cdigo fuente. Pero en general, es bastante fcil ver lo que est pasando. Recuerde que las opciones se pueden dar como un hash o en el estilo bloque de cdigo. Y recuerde el entorno del bloque de cdigo se encuentra dentro del TkWidget que se utiliza, no en la instancia de clase.

Creacin de Objetos
En la asignacin de Perl/Tk, los padres son responsables de crear sus widgets hijos. En Ruby, el padre se pasa como el primer parmetro al constructor de widget.

179

Perl/Tk: $widget = $parent->Widget( [ option => value ] ) Ruby: widget = TkWidget.new(parent, option-hash) widget = TkWidget.new(parent) { code block } Puede que no se necesite salvar el valor devuelto del widget recin creado, pero est ah si se hace. No hay que olvidar empaquetar (pack) el widget (o utilizar una de las otras llamadas de geometra), o no se mostrar.

Opciones
Perl/Tk: -background => color Ruby: background => color { background color } Recuerde que el mbito de un bloque de cdigo es diferente.

Referencias a Variables
Use TkVariable para vincular una variable Ruby al valor de un widget. A continuacin, se puede utilizar el accesor value en TkVariable (TkVariable#value y TkVariable#value=) para afectar directamente al contenido del widget.

Ruby y Microsoft Windows


Ruby se ejecuta en numerosos entornos diferentes. Algunos de estos estn basados en Unix y otros en las diferentes versiones de Microsoft Windows. Ruby lleg de personas que estaban centradas en Unix, pero con los aos se han desarrollado tambin un montn de caractersticas tiles en el mundo de Windows. En este captulo, vamos a ver estas caractersticas y compartir algunos secretos para utilizar Ruby eficazmente en Windows.

Obteniendo Ruby para Windows


Dos versiones de Ruby estn disponibles para el entorno Windows.

La primera es una versin de Ruby que funciona de forma nativa --es decir, es slo otra aplicacin de Windows. La forma ms fcil para esta distribucin es utilizar el programa de instalacin de un solo clic, que carga una distribucin binaria hecho y listo en su compartimento. Siga los enlaces desde http:// rubyinstaller.rubyforge.org/ para obtener la ltima versin. Si se siente ms aventurero, o si necesita compilarlo con las bibliotecas que no se suministran con la distribucin binaria, entonces se puede construir desde el cdigo fuente de Ruby. Usted necesitar el compilador VC++ de Microsoft y sus herramientas asociadas para hacerlo. Descargar el cdigo fuente de Ruby de http://www.ruby-lang.org, o usar CVS para comprobar la ltima versin en desarrollo. A continuacin, lea el archivo win32\README.win32 para obtener instrucciones. Una segunda alternativa utiliza una capa de emulacin llamada Cygwin. Esto proporciona un ambiente a modo Unix en la parte superior de Windows. La versin Cygwin de Ruby es la ms cercana al Ruby que se ejecuta en las plataformas Unix, pero su funcionamiento conlleva que tambin tenga que instalar Cygwin. Si usted quiere seguir esta va, puede descargar la versin Cygwin de Ruby de http://ftp. ruby-lang.org/pub/ruby/binaries/cygwin/. Tambin necesitar Cygwin en s mismo. El enlace de descarga tiene un puntero a la biblioteca de enlace dinmico (DLL) requerida, o se puede ir a http:// www.cygwin.com y descargar el paquete completo (pero atencin: necesita asegurarse de que la versin que obtenga es compatible con la de Ruby que se ha descargado). Qu versin de elegir? Cuando se produjo la primera edicin de este libro, la versin de Cygwin para Ruby era la distribucin a elegir. Esa situacin ha cambiado: la construccin nativa se ha vuelto ms y ms funcional con el tiempo, hasta el punto que sta es ahora nuestra construccin preferida de Ruby.

180

Ejecutando Ruby en Windows


Hay dos archivos ejecutables en la distribucin Windows de Ruby.

ruby.exe est destinado a ser utilizado en un sistema de comandos (un shell de DOS), como en la versin Unix. Para las aplicaciones que leen y escriben en la entrada y salida estndar, esto est muy bien. Pero esto tambin significa que cada vez que se ejecuta ruby.exe obtendr un shell de DOS, incluso si usted no lo quiere --Windows crear una nueva consola de comandos y la mostrar mientras que Ruby se est ejecutando. Este no puede ser el comportamiento adecuado si, por ejemplo, se hace doble clic en un script Ruby que usa una interfaz grfica (como Tk), o si est ejecutando un script Ruby como tarea de fondo o si lo est ejecutando desde el interior de otro programa. En estos casos, usted querr usar rubyw.exe. Es lo mismo que ruby.exe excepto que no proporciona entrada estndar, salida estndar o error estndar y no lanzar un shell de DOS cuando se ejecuta. El programa de instalacin (por defecto) establece las asociaciones de archivos para que los archivos con la extensin .rb utilicen automticamente rubyw.exe. Al hacer esto, puede hacer doble clic en scripts Ruby y que simplemente correran sin desplegar un shell de DOS.

Win32API
Si su plan es hacer programacin Ruby que necesite acceder a algunas de las funciones de la API de Windows 32 directamente, o que tienga que utilizar los puntos de entrada de algunos otros archivos DLL, tenemos buenas noticias para usted --la biblioteca Win32API. Como ejemplo, aqu tenemos un cdigo que es parte de una aplicacin de Windows utilizada por nuestro sistema para libro de cumplimientos, que descarga e imprime las facturas y los recibos. Una aplicacin web genera un archivo PDF que el script Ruby ejecuta en las descargas de Windows a un archivo local. El script utiliza el comando shell print bajo Windows para imprimir este archivo. arg = ids=#{resp.intl_orders.join(,)} fname = /temp/invoices.pdf site = Net::HTTP.new(HOST, PORT) site.use_ssl = true http_resp, = site.get2(/fulfill/receipt.cgi? + arg, Authorization => Basic + [name:passwd].pack(m).strip ) File.open(fname, wb) {|f| f.puts(http_resp.body) } shell = Win32API.new(shell32,ShellExecute, [L,P,P,P,P,L], L ) shell.Call(0, print, fname, 0,0, SW_SHOWNORMAL) Se crea un objeto Win32API que representa una llamada a un punto de entrada DLL en particular, especificando el nombre de la funcin, el nombre del DLL que contiene la funcin, y el armado de la funcin (tipos de argumentos y tipo de retorno). En el ejemplo anterior, la variable shell envuelve la funcin ShellExecute de Windows en el DLL shell22. La funcin toma seis parmetros (un nmero, cuatro punteros de cadena y un nmero) y retorna un nmero. (Este tipo de parmetros se describen en la pgina ms adelante) El objeto resultante se puede utilizar para realizar la llamada a imprimir para el archivo que hemos descargado. Muchos de los argumentos a las funciones DLL son de alguna forma estructuras binarias. Win32API maneja esto mediante el uso de objetos cadena Ruby para pasar los datos binarios de ida y vuelta. Usted tendr que empacar y desempacar estas cadenas cuando sea necesario (ms adelante veremos un ejemplo).

Automatizacin de Windows
Si deslizarse alrededor de la API de bajo nivel de Windows no le interesa, la automatizacin de Windows puede --se puede utilizar Ruby como un cliente de automatizacin de Windows, gracias a una

181

extensin de Ruby llamada WIN32OLE, escrita por Masaki Suketa. Win32OLE es parte de la distribucin estndar de Ruby. La Automatizacin de Windows permite a un controlador de automatizacin (un cliente) el mandar rdenes y hacer consultas a un servidor de automatizacin, como Microsoft Excel, Word, PowerPoint, etc. Podemos ejecutar un mtodo para un servidor de automatizacin para llamar a un mtodo del mismo nombre desde un objeto WIN32OLE. Por ejemplo, puede crear un nuevo cliente WIN32OLE que lanza una copia limpia de Internet Explorer y mandarle visitar la pgina de inicio. ie = WIN32OLE.new(InternetExplorer.Application) ie.visible = true ie.gohome Tambin podra hacer que navegue a una pgina determinada.

ie = WIN32OLE.new(InternetExplorer.Application) ie.visible = true ie.navigate(http://www.pragmaticprogrammer.com) Los mtodos que son desconocidos para WIN32OLE (como visible, gohome, o navigate) se pasan al mtodo WIN32OLE#invoke, el cual enva los comandos apropiados al servidor.

Obtener y Configurar Propiedades


Podemos establecer y obtener propiedades del servidor usando la notacin hash normal de Ruby. Por ejemplo, para establecer la propiedad Rotation en un grfico de Excel, se puede escribir excel = WIN32OLE.new(excel.application) excelchart = excel.Charts.Add() ... excelchart[Rotation] = 45 puts excelchart[Rotation] Los parmetros de un objeto OLE se configuran automticamente como atributos del objeto WIN32OLE. Esto significa que se puede establecer un parmetro mediante la asignacin de un atributo de objeto. excelchart.rotation = 45 r = excelchart.rotation El siguiente ejemplo es una versin modificada del archivo de ejemplo excel2.rb (que se encuentra en el directorio ext/win32/samples). Se inicia Excel, se crea un grfico y luego se gira en la pantalla. Ten cuidado, Pixar! require win32ole # -4100 is the value for the Excel constant xl3DColumn. ChartTypeVal = -4100; excel = WIN32OLE.new(excel.application) # Create and rotate the chart excel[Visible] = TRUE excel.Workbooks.Add() excel.Range(a1)[Value] = 3 excel.Range(a2)[Value] = 2 excel.Range(a3)[Value] = 1 excel.Range(a1:a3).Select() excelchart = excel.Charts.Add() excelchart[Type] = ChartTypeVal 30.step(180, 5) do |rot|

182

excelchart.rotation = rot sleep(0.1) end excel.ActiveWorkbook.Close(0) excel.Quit()

Argumentos con Nombre


Otros lenguajes de cliente de automatizacin, tal como Visual Basic tienen el concepto de argumentos con nombre. Supongamos que tienes una rutina de Visual Basic con la forma Song(artist, title, length): rem Visual Basic

En lugar de llamar con los tres argumentos en el orden especificado, se puede utilizar argumentos con nombre. Song title := Get It On: rem Visual Basic

Esto es equivalente a la llamada Song(nil, Get It On, nil). En Ruby, puede utilizar esta caracterstica pasando un hash con los argumentos con nombre.

Song.new(title => Get It On)

for each
Donde Visual Basic tiene una sentencia for each para iterar sobre una coleccin de elementos en un servidor, un objeto WIN32OLE tiene un mtodo each (que toma un bloque) para lograr la misma cosa. require win32ole excel = WIN32OLE.new(excel.application) excel.Workbooks.Add excel.Range(a1).Value = 10 excel.Range(a2).Value = 20 excel.Range(a3).Value = =a1+a2 excel.Range(a1:a3).each do |cell| p cell.Value end

Eventos
Su cliente de automatizacin escrito en Ruby puede registrarse a s mismo para recibir eventos de otros programas. Esto se hace usando la clase WIN32OLE_EVENT. En este ejemplo (basado en el cdigo de la distribucin Win32OLE 0.1.1) se muestra el uso de un receptor de eventos que registra las direcciones URL por las que un usuario navega cuando utiliza Internet Explorer. require win32ole $urls = [] def navigate(url) $urls << url end def stop_msg_loop puts IE has exited... throw :done end def default_handler(event, *args) case event when BeforeNavigate puts Now Navigating to #{args[0]}...

183

end end ie = WIN32OLE.new(InternetExplorer.Application) ie.visible = TRUE ie.gohome ev = WIN32OLE_EVENT.new(ie, DWebBrowserEvents) ev.on_event {|*args| default_handler(*args)} ev.on_event(NavigateComplete) {|url| navigate(url)} ev.on_event(Quit) {|*args| stop_msg_loop} catch(:done) do loop do WIN32OLE_EVENT.message_loop end end puts You Navigated to the following URLs: $urls.each_with_index do |url, i| puts (#{i+1}) #{url} end

Optimizacin
Como con la mayora (si no todos) los lenguajes de alto nivel, puede ser muy fcil agitar hacia fuera cdigo que es insoportablemente lento, pero que se puede fijar fcilmente con un poco de imaginacin. Con WIN32OLE, es necesario tener cuidado con las bsquedas dinmicas innecesarias. Siempre que sea posible, es mejor asignar un objeto WIN32OLE a una variable y luego referenciar elementos desde ella, en lugar de crear una larga cadena de expresiones . . Por ejemplo, en lugar de escribir = = = = 1 2 4 8

workbook.Worksheets(1).Range(A1).value workbook.Worksheets(1).Range(A2).value workbook.Worksheets(1).Range(A3).value workbook.Worksheets(1).Range(A4).value

podemos eliminar las subexpresiones comunes de ahorro salvando la primera parte de la expresin a una variable temporal y luego hacer las llamadas a partir de esa variable. worksheet = workbook.Worksheets(1) worksheet.Range(A1).value = 1 worksheet.Range(A2).value = 2 worksheet.Range(A3).value = 4 worksheet.Range(A4).value = 8 Tambin se puede crear archivos de resguardo Ruby para una biblioteca de tipos particulares de Windows. Estos archivos envuelven el objeto OLE en una clase de Ruby con un mtodo por punto de entrada. Internamente, el archivo utiliza el nmero del punto de entrada, no el nombre, lo que acelera el acceso. Se genera la clase contenedora con el script olegen.rb en el directorio ext\ win32ole\samples, dndole el nombre de la biblioteca de tiposa reflejar. C:\> ruby olegen.rb NetMeeting 1.1 Type Library >netmeeting.rb Los mtodos y los eventos externos de la biblioteca de tipos se escriben para el archivo dado como los mtodos de Ruby. A continuacin, puede incluirse en sus programas y llamar a los mtodos directamente. Vamos a tratar algunos tiempos. require netmeeting require benchmark include Benchmark

184

bmbm(10) do |test| test.report(Dynamic) do nm = WIN32OLE.new(NetMeeting.App.1) 10000.times { nm.Version } end test.report(Via proxy) do nm = NetMeeting_App_1.new 10000.times { nm.Version } end end produce: Rehearsal --------------------------------------Dynamic 0.600000 0.200000 0.800000 ( 1.623000) Via proxy 0.361000 0.140000 0.501000 ( 0.961000) -------------------------------total: 1.301000sec user system total real Dynamic 0.471000 0.110000 0.581000 ( 1.522000) Via proxy 0.470000 0.130000 0.600000 ( 0.952000) La versin del proxy es ms del 40 por ciento ms rpida que el cdigo que hace la bsqueda dinmica.

Ms Ayuda
Si usted necesita la interfaz de Ruby para Windows NT, 2000 o XP, es posible que desee echar un vistazo al proyecto Win32Utils de Daniel Berger (http://rubyforge.org/projects/win32utils/). All encontrar mdulos para la conexin al portapapeles de Windows, registro de eventos, planificador de procesos, etc. Asimismo, la biblioteca DL (que se describe brevemente ms adelante) permite a los programas Ruby invocar a los mtodos de carga dinmica de objetos compartidos. En Windows, esto significa que el cdigo de Ruby puede cargar y ejecutar los puntos de entrada en un archivo DLL de Windows. Por ejemplo, el siguiente cdigo, tomado a partir del cdigo fuente de la librera DL en la distribucin estndar de Ruby, determina a qu botn el usuario ha hecho clic, cuando aparece un cuadro de mensaje en una mquina Windows. require dl User32 = DL.dlopen(user32) MB_OKCANCEL = 1 message_box = User32[MessageBoxA, ILSSI] r, rs = message_box.call(0, OK?, Please Confirm, MB_OKCANCEL) case r when 1 print(OK!\n) when 2 print(Cancel!\n) end Este cdigo abre el archivo DLL User32. A continuacin, crea un objeto Ruby, message_box, que envuelve el punto de entrada MessageBoxA. El segundo parmetro , ILSSI, declara que el mtodo devuelve un Integer, y toma un Long, dos Strings y un Integer como parmetros. El objeto contenedor se utiliza entonces para llamar al punto de entrada del cuadro de mensaje en el DLL. Los valores de retorno son el resultado (en este caso, el identificador del botn pulsado por el usuario) y una matriz de parmetros que son pasados (que ignoramos).

185

Extendiendo Ruby
Es fcil extender Ruby con nuevas caractersticas escribindolas en cdigo Ruby. Pero de vez en cuando necesita intefaces para conectarse con las cosas a bajo nivel. Una vez que se comienza a aadir cdigo de bajo nivel escrito en C, las posibilidades son infinitas. Dicho esto, la materia de este captulo es bastante avanzada y probablemente, se debiera omitir en una primera lectura rpida del libro. La extensin de Ruby con C es muy fcil. Por ejemplo, supongamos que estamos construyendo una lista a medida de Internet del jukebox para el Sunset Diner and Grill. Se quieren poner archivos de audio MP3 desde un disco duro o CD de audio en el jukebox y queremos ser capaces de controlar el hardware de la mquina de discos desde un programa de Ruby. El proveedor de hardware nos dio un archivo de cabecera C y una biblioteca binaria para utilizar, nuestro trabajo es construir un objeto Ruby que haga las adecuadas llamadas a funciones C. Mucha de la informacin de este captulo se ha tomado del archivo README.EXT que se incluye en la distribucin. Si usted est pensando en escribir una extensin para Ruby, es posible que desee buscar referencias en ese archivo para obtener ms detalles, as como los ltimos cambios.

Su Primera Extensin
Slo para introducir la escritura de extensiones, vamos a escribir una. Esta extensin es simplemente una prueba del proceso --no hace nada que no se pudiera hacer en Ruby puro. Tambin vamos a presentar algunas cosas sin demasiada explicacin --todos los detalles enmaraados se darn ms adelante. La extensin que escribamos tendr la misma funcionalidad que la siguiente clase de Ruby. MyTest initialize @arr = Array.new add(obj) @arr.push(obj)

class def end def end end

Es decir, vamos a escribir una extensin en C que es compatible con esta clase Ruby. El cdigo equivalente en C debera ser algn tanto familiar. #include ruby.h static int id_push; static VALUE t_init(VALUE self) { VALUE arr; arr = rb_ary_new(); rb_iv_set(self, @arr, arr); return self; } static VALUE t_add(VALUE self, VALUE obj) { VALUE arr; arr = rb_iv_get(self, @arr); rb_funcall(arr, id_push, 1, obj); return arr; } VALUE cTest; void Init_my_test() { cTest = rb_define_class(MyTest, rb_cObject); rb_define_method(cTest, initialize, t_init, 0); rb_define_method(cTest, add, t_add, 1); id_push = rb_intern(push);

186

} Vamos a ir en detalle a travs de este ejemplo, ya que ilustra muchos de los conceptos importantes en este captulo. En primer lugar, tenemos que incluir el fichero de cabecera ruby.h para obtener las definiciones Ruby necesarias. Ahora observe la ltima funcin, Init_my_test. Cada extensin define una funcin global C llamada Init_name. Esta funcin es llamada cuando el intrprete carga por primera vez el nombre de extensin (o en el arranque para las extensiones enlazados estticamente). Se utiliza para inicializar la extensin y para infiltrarse en el entorno Ruby (cmo Ruby sabe exactamente que una extensin es llamada por el nombre lo vamos a cubrir ms adelante) En este caso, se define una nueva clase denominada MyTest, que es una subclase de Object (representado por el smbolo externo rb_cObject; ver ruby.h para otros). A continuacin se estableci add e initialize como dos mtodos de instancia para la clase MyTest. Las llamadas a rb_define_method establecen un enlace entre el nombre del mtodo Ruby y la funcin C que lo incorpora. Si el cdigo Ruby llama al mtodo add en uno de nuestros objetos, el intrprete a su vez, llama a la funcin C t_add con un argumento. Del mismo modo, cuando new es llamado para esta clase, Ruby va a construir un objeto bsico y luego llamar a initialize, que aqu hemos definido para llamar a la funcin C t_init sin argumentos (Ruby). Ahora regresa y mira a la definicin de t_init. A pesar de que se dijo que no toma argumentos, tiene aqui un parmetro! Adems de culaquier argumento Ruby, a cada mtodo se le pasa un argumento inicial VALUE que contiene el receptor para este mtodo (el equivalente de self en cdigo Ruby). La primera cosa que haremos en t_init es crear una matriz Ruby y establecer la variable de instancia @arr referenciandola. As como se esperara si estuviera escribiendo cdigo fuente en Ruby, al hacer referencia a una variable de instancia que no existe, se crea. A continuacin, retorna un puntero a si misma. ADVERTENCIA: Todas las funciones C que se pueden llamar desde Ruby deben devolver un valor, incluso si es slo Qnil. De lo contrario, el resultado probable ser un volcado de memoria (o GPF) . Por ltimo, la funcin t_add obtiene la variable de instancia @arr del objeto actual y llama a Array#push para empujar el valor pasado en la matriz. Al acceder a variables de instancia de este modo, el prefijo @ es obligatorio --de lo contrario la variable se crea pero no se puede referenciar desde Ruby. A pesar de la sintaxis extra, torpe, que impone C, est siendo escrito en Ruby --puede manipular objetos mediante todas las llamadas a mtodo que ha llegado a conocer y amar, con la ventaja adicional de ser capaz de crear cdigo ajustado y rpido cuando sea necesario.

Construyendo Nuestra Extensin


Vamos a tener mucho ms que decir sobre la construccin de extensiones despus. Por ahora, sin embargo, todo lo que tenemos que hacer es seguir estos pasos. 1. Cree un archivo denominado extconf.rb en el mismo directorio que nuestro archivo de cdigo fuente en C my_text.c. El archivo extconf.rb debe contener las siguientes dos lneas: require mkmf create_makefile(my_test) 2. Ejecutar extconf.rb. Esto generar un Makefile.

% ruby extconf.rb creating Makefile


3. Utilice make para construir la extensin. Esto es lo que sucede en un sistema OS X.

% make 187

gcc -fno-common -g -O2 -pipe -fno-common -I. -I/usr/lib/ruby/1.9/powerpc-darwin7.4.0 -I/usr/lib/ruby/1.9/powerpcdarwin7.4.0 -I. -c my_test.c cc -dynamic -bundle -undefined suppress -flat_namespace -L/usr/lib -o my_test.bundle my_test.o -ldl -lobjc El resultado de todo esto es la extensin, todo muy bien envuelto en un objeto compartido (un fichero .so, .dll [en OS X] .bundle).

Ejecutando Nuestra Extensin


Podemos utilizar nuestra extensin de Ruby con un simple require de forma dinmica en tiempo de ejecucin (en la mayora de las plataformas). Podemos terminar con esto con una prueba para verificar que todo est funcionando como se espera. require my_test require test/unit class TestTest < Test::Unit::TestCase def test_test t = MyTest.new assert_equal(Object, MyTest.superclass) assert_equal(MyTest, t.class) t.add(1) t.add(2) assert_equal([1,2], t.instance_eval(@arr)) end end produce: Finished in 0.002589 seconds. 1 tests, 3 assertions, 0 failures, 0 errors Una vez que estamos contentos con nuestras obras de ampliacin, se puede instalar a nivel global mediante la ejecucin de make install.

Objetos Ruby en C
Cuando escribimos nuestra primera extensin, hicimos trampa, porque en realidad no se hace nada con los objetos Ruby --no se hacen los clculos basados en cifras Ruby, por ejemplo. Antes de poder hacer esto, tenemos que encontrar la manera de representar y acceder a los datos tipo Ruby dentro de C. Todo en Ruby es un objeto, y todas las variables son referencias a objectos. Cuando vemos los objetos Ruby desde dentro de cdigo en C, la situacin es ms o menos lo mismo. La mayora de los objetos Ruby se representan como punteros C a una zona de memoria que contiene los datos del objeto y otros detalles de implementacin. En el cdigo C, todas estas referencias son a travs de variables de tipo VALUE, por lo que se pase en cuanto a objetos Ruby, se har por envo de valores. Esto tiene una excepcin. Por motivos de rendimiento, Ruby implementa Fixnums, Symbols, true, false y nil como los llamados valores inmediatos. Estos an estn almacenados en variables de tipo VALUE, pero no son punteros. En cambio, su valor se almacena directamente en la variable. As, a veces los valores son punteros y a veces son valores inmediatos. Cmo funciona el intrprete para manejar esta magia? Se basa en el hecho de que todos los punteros apuntan a zonas de memoria alineadas en lmites 4 8 bytes. Esto significa que podemos garantizar que los 2 bits menos significativos en un puntero siempre sern cero. Cuando se quiere almacenar un valor inmediato, se las arregla para tener al menos uno de estos bits establecido, permitiendo al resto del cdigo del intrprete distinguir los valores inmediatos de los punteros. Aunque esto suena complicado, es realmente fcil su uso en la prctica, en gran parte debido a que el intrprete viene con una serie de macros y mtodos que simplifican el trabajo con el sistema de tipos.

188

Esta es la forma en que Ruby implementa cdigo orientado a objetos en C: Un objeto Ruby es una estructura asignada en la memoria que contiene una tabla de variables de instancia y la informacin sobre la clase. La clase en s es otro objeto (una estructura asignada en la memoria) que contiene una tabla de los mtodos definidos para esa clase. Ruby est construido sobre esta base.

Trabajar con Objetos Inmediatos


Como hemos dicho anteriormente, los valores inmediatos no son punteros: Fixnum, Symbol, true, false y nil son almacenados directamente en VALUE. Los valores Fixnum se almacenan como nmeros de 31 bits ( de 63 bits en las arquitecturas ms amplias de CPU) que se forman al desplazar el nmero original un bit a la izquierda y luego establecer el LSB o bit menos significativo (bit 0), a 1. Cuando VALUE se utiliza como un puntero a una estructura especfica Ruby, siempre est garantizado el tener el LSB en cero. Los valores inmediatos tambin tienen el LSB en cero. Por lo tanto, un simple testeo del bit puede indicar si se tiene un Fixnum. Este test est envuelto en una macro, FIXNUM_P. Otros test similares permiten comprobar si hay otros valores inmediatos. FIXNUM_P(value) SYMBOL_P(value) NIL_P(value) RTEST(value) -> -> -> -> nonzero nonzero nonzero nonzero if if if if value value value value is is is is a Fixnum a Symbol nil neither nil nor false

Varias macros tiles de conversin para nmeros, as como otros tipos de datos estndar se muestran en la tabla 5 en la pgina siguiente. Los otros valores inmediatos (true, false y nil) se representan en C como las constantes Qtrue, Qfalse y Qnil, respectivamente. Se pueden testear las variables VALUE frente a estas constantes directamente o utilizar las macros de conversin (que llevan a cabo el casting adecuado).

Trabajando con Cadenas

En C, estamos acostumbrados a trabajar con cadenas de terminacin nula. Las cadenas Ruby, sin embargo, son ms generales y tambin se pueden incluir valores null incrustados. La manera ms segura para trabajar con cadenas Ruby, por lo tanto, es hacer lo que el intrprete hace y utilizando un puntero y una longitud. De hecho, los objetos cadena de Ruby son en realidad referencias a una estructura RString , y esta estructura RString contiene una longitud y un campo de puntero. Puede acceder a la estructura a travs de la macro RSTRING. VALUE str; RSTRING(str)->len RSTRING(str)->ptr -> -> length of the Ruby string pointer to string storage

Sin embargo, la vida es algo ms complicada que esto. En lugar de utilizar directamente el objeto VALUE cuando se necesita un valor de cadena, es probable que se quiera llamar al mtodo StringValue, pasndole el valor original. Esto retorna un objeto que se puede utilizar en RSTRING o lanzar una excepcin si no se puede derivar una cadena a partir del original. Todo esto es parte de la iniciativa duck typing de Ruby 1.8, que se describe con ms detalle ms adelante. El mtodo StringValue comprueba si su operando es un String. Si no, trata de invocar to_str en el objeto y lanza una excepcin TypeError si no se puede. Por lo tanto, si quiere escribir algo de cdigo que itera sobre todos los caracteres en un objeto String, se puede escribir: static VALUE iterate_over(VALUE original_str) { int i; char *p VALUE str = StringValue(original_str); p = RSTRING(str)->ptr; // puede ser null for (i = 0; i < RSTRING(str)->len; i++, p++) { 189

// process *p } return str; } Si no se desea utilizar la longitud y basta con acceder al puntero de cadena subyacente, puede utilizar el mtodo de conveniencia StringValuePtr, que resuelve la referencia a cadena y retorna el puntero C al contenido. Si va a utilizar una cadena para acceder o para el control de algn recurso externo, es probable que desee conectar al mecanismo de corrupcin de Ruby. En este caso vamos a usar el mtodo SafeStringValue , que funciona como StringValue pero produce una excepcin si el argumento est corrupto y el nivel de seguridad es mayor que cero.

Trabajando con Otros Objetos


Cuando los VALUE no son inmediatos, son punteros a una de las estructuras objeto definidas en Ruby --no se puede tener un VALUE que apunta a un rea de memoria arbitraria. Las estructuras para las clases bsicas integradas se definen en ruby.h y se nombran de la forma RClasenombre: RArray, RBignum, RClass, RData, RFile, RFloat, RHash, RObject, RRegexp, RString y RStruct. Se puede chekear para ver qu tipo de estructura se utiliza para un determinado VALUE de varias maneras. El macro TYPE(obj) devuelve una constante que representa el tipo C de un objeto dado: T_OBJECT, T_STRING, etc. Las constantes para las clases integradas se definen en ruby.h. Tenga en cuenta que el tipo al que aqu nos referimos es un detalle de implementacin --no es lo mismo que la clase de un objeto.

190

Si desea asegurarse de que un puntero value apunta a una estructura particular, puede utilizar la macro Check_Type, que provocar una excepcin TypeError si value no es del tipo esperado (una de las constantes T_STRING, T_FLOAT, etc). Check_Type(VALUE value, int type) Una vez ms, tenga en cuenta que estamos hablando de tipo como la estructura C que representa un particular tipo integrado. La clase de un objeto es una bestia completamente diferente. Los objetos clase para las clases integradas se almacenan en las variables C globales llamadas rb_cNombreclase (por ejemplo rb_cObject). Los mdulos se denominan rb_mNombremodulo.

Acceso a Cadenas Anterior a la Versin 1.8 Antes de Ruby 1.8, si se supona que VALUE contiena una cadena, era posible acceder a los campos RSTRING directamente, y eso era todo. En 1.8, sin embargo, la introduccin gradual de duck typing (escritura pato), junto con varias optimizaciones, significa que este enfoque probablemente no funcionar de la manera que se desea. En particular, el campo ptr de un objeto STRING puede ser null para cadenas de longitud cero. Si se utiliza el mtodo 1.8 StringValue, maneja este caso reseteando punteros nulos que referencian una nica, compartida y vaca cadena. As que, cmo se escribe una extensin que funciona tanto con Ruby 1.6 como con 1.8? Cuidadosamente y con macros. Tal vez algo como esto #if !defined(StringValue) # define StringValue(x) (x) #endif #if !defined(StringValuePtr) # define StringValuePtr(x) ((STR2CSTR(x))) #end Este cdigo define las macros 1.8 StringValue y StringValuePtr en trminos de sus homlogos 1.6. Si escribe entonces cdigo en funcin de estas macros, se debe compilar y ejecutar en los dos intrpretes, el antiguo y el nuevo. Si desea que el cdigo tenga el comportamiento duck-typing 1.8, incluso cuando se ejecute en 1.6, es posible que quiera definir StringValue de forma ligeramente diferente. La diferencia entre esta y la anterior implementacin se describe un poco ms adelante. #if !defined(StringValue) # define StringValue(x) do { \ if (TYPE(x) != T_STRING) x = rb_str_to_str(x); \ } while (0) #end

No es conveniente modificar directamente los datos en estas estructuras C --se puede mirar, pero no tocar. En cambio, normalmente vamos a usar las funciones de C suministradas para manipular los datos Ruby (hablaremos ms sobre esto en un momento).

No obstante, en aras de la eficiencia puede ser necesario profundizar en estas estructuras para la obtencin de datos. Para desreferenciar los miembros de estas estructuras C, se tiene que emitir el VALUE genrico para el tipo apropiado de estructura. ruby.h contiene una serie de macros que realizan el reparto adecuado para usted, lo que le permite desreferenciar los miembros de la estructura de forma fcil. Estas macros se llaman RNOMBRECLASE, como RSTRING o RARRAY. Ya hemos visto el uso de RSTRING cuando se trabaja con cadenas. Se puede hacer lo mismo con las matrices.

191

VALUE arr; RARRAY(arr)->len RARRAY(arr)->capa RARRAY(arr)->ptr

-> -> ->

length of the Ruby array capacity of the Ruby array pointer to array storage

Hay accesores similares para los hashes (RHASH), archivos (RFILE), etc. Habiendo dicho todo esto, es necesario tener cuidado sobre la construccin de una excesiva dependencia en el chequeo de tipos en el cdigo de extensin. Vamos a hablar ms sobre las extensiones y el sistema de tipos Ruby un poco ms adelante.

Variables Globales
La mayora de las veces, las extensiones implementan clases y el cdigo Ruby utiliza estas clases. Los datos que se comparten entre el cdigo Ruby y el cdigo C sern pulcramente envueltos dentro de los objetos de la clase. As es como debe ser. A veces, sin embargo, puede que tenga que implementar una variable global accesible tanto por la extensin C como por el cdigo Ruby. La forma ms sencilla de hacer esto es tener la variable como un VALUE (es decir, un objeto Ruby) y entonces, ligar la direccin de esta variable C con el nombre de una variable Ruby. En este caso, el prefijo $ es opcional, pero ayuda a aclarar que es una variable global. Y recordar: hacer global una variable basada en la pila Ruby no va a trabajar (por mucho tiempo). static VALUE hardware_list; static VALUE Init_SysInfo() { rb_define_class(....); hardware_list = rb_ary_new(); rb_define_variable($hardware, &hardware_list); ... rb_ary_push(hardware_list, rb_str_new2(DVD)); rb_ary_push(hardware_list, rb_str_new2(CDPlayer1)); rb_ary_push(hardware_list, rb_str_new2(CDPlayer2)); } Del lado de Ruby se puede acceder a la variable C hardware_list como $hardware. -> [DVD, CDPlayer1, CDPlayer2]

$hardware

A veces, sin embargo, la vida es ms complicada. Tal vez usted desea definir una variable global cuyo valor debe ser calculado cuando se accede a ella. Esto se hace mediante la definicin de las variables de enganche y virtuales. Una variable de enganche es una variable real que se inicia por una funcin nombrada cuando se accede a la correspondiente variable Ruby. Las variables virtuales son similares, pero nunca se almacenan: su valor proviene puramente de la evaluacin de la funcin de enlace. Vea la seccin API que comienza un poco ms adelante para ms detalles. Si se crea un objeto Ruby desde C y se almacena en una variable C global sin exportarlo a Ruby, se debe por lo menos pasar el recolector de basura sobre l, no sea que se recoja inadvertidamente. static VALUE obj; // ... obj = rb_ary_new(); rb_global_variable(obj);

La Extensin Jukebox
Hemos cubierto lo suficiente sobre los fundamentos como para volver ahora a nuestro ejemplo de jukebox --la interfaz de cdigo C con Ruby y el intercambio de datos y comportamiento entre los dos mundos.

192

Envolver Estructuras C
Tenemos la biblioteca del proveedor que controla las unidades de CD de audio del jukebox, y estamos listos para conectarlo a Ruby. El archivo de cabecera del proveedor se parece a esto. typedef struct _cdjb { int statusf; int request; void *data; char pending; int unit_id; void *stats; } CDJukebox; // Allocate a new CDJukebox structure CDJukebox *new_jukebox(void); // Assign the Jukebox to a player void assign_jukebox(CDJukebox *jb, int unit_id); // Deallocate when done (and take offline) void free_jukebox(CDJukebox *jb); // Seek to a disc, track and notify progress void jukebox_seek(CDJukebox *jb, int disc, int track, void (*done)(CDJukebox *jb, int percent)); // ... others... // Report a statistic double get_avg_seek_time(CDJukebox *jb); Este vendedor tiene que organizarse. Aunque es posible que no lo admita, el cdigo est escrito con un sabor orientado a objetos. No sabemos lo que significan todos estos campos dentro de la estructura CDJukeBox, pero OK --lo podemos tratar como un montn opaco de bits. El Vendedor del cdigo sabe qu hacer con l, nosotros slo tenemos que trasladarlo. Cada vez que se tenga una estructura slo en C que se quiera manejar como un objeto Ruby, se debe envolver en una clase especial e internos de Ruby llamada DATA (tipo T_DATA). Dos macros hacen este envoltorio y una macro recupera la estructura de vuelta.

La API: Envolviendo Tipos de Datos C


VALUE Data_Wrap_Struct( VALUE class, void (*mark)(), void (*free)(), void *ptr ) Envuelve el tipo dado de datos C ptr, registros de las dos rutinas de recoleccin de basura (ver abajo), y devuelve un puntero VALUE a un verdadero objeto Ruby. El tipo C del objeto resultante es T_DATA, y su clase de Ruby es la class. VALUE Data_Make_Struct( VALUE class, c-type, void (*mark)(), void (*free)(), c-type * ) Asignacin y configuracin a cero de una estructura del tipo indicado y despus procede como Data_Wrap_Struct. c-type es el nombre del tipo de datos C que est envolviendo, no una variable de ese tipo. Data_Get_Struct( VALUE obj,c-type,c-type * ) Devuelve el puntero original. Esta macro es un contenedor de tipo seguro para toda la macro DATA_PTR(obj), que evala el puntero.

El objeto creado por Data_Wrap_Struct es un objeto normal de Ruby, excepto que tiene un adicional tipo de datos C al que no se puede acceder desde Ruby. Como se puede ver en la figura 14 en la pgina siguiente, este tipo de datos C es independiente de las variables de instancia que contiene el objeto. Pero puesto que es algo separada, cmo deshacerse de l cuando el recolector de basura reclama este

193

objeto? Qu pasa si hay que liberar algunos recursos (cerrar algn archivo, limpiar algn mecanismo de bloqueo o IPC, etc)? Ruby utiliza un esquema de marca y barrido de recoleccin de basura. Durante la fase de marca, Ruby busca punteros a zonas de memoria y marca estas reas como en uso (porque algo est apuntando a ellas). Si esas mismas reas contienen ms punteros, la memoria de estos punteros de referencia tambin son marcados, y as sucesivamente. Al final de la fase de marca, toda la memoria a la que se hace referencia se ha marcado, y las reas hurfanas no tienen marca. En este punto se inicia la fase de barrido, la liberacin de memoria que no est marcada. Para participar en el proceso Ruby de marcar y barrer de la recoleccin de basura, es necesario definir una rutina para liberar su estructura y, posiblemente, una rutina para marcar las referencias de su estructura a otras estructuras. Ambas rutinas reciben un puntero void, una referencia a esa estructura. La rutina de marca ser llamada por el recolector de basura durante su fase de marca. Si su estructura referencia otros objetos Ruby, entonces su funcin de marca tiene que identificar estos objetos utilizando rb_gc_mark(valor). Si la estructura no hace referencia a otros objetos Ruby, simplemente puede pasar 0 como un puntero a funcin. Cuando el objeto tiene que ser eliminado, el recolector de basura llamar a la rutina libre para liberarlo. Si usted tiene asignada alguna memoria para usted mismo (por ejemplo, mediante el uso de Data_Make_ Struct), tendr que pasar una funcin libre --incluso si es justo la rutina free de la biblioteca estndar de C. Para asignaciones de estructuras complejas, es posible que su funcin libre tenga la necesidad de recorrer toda la estructura para liberar toda la memoria asignada. Echemos un vistazo a nuestra interfaz del reproductor de CD. La biblioteca del vendedor pasa la informacin entre sus diversas funciones en una estructura CDJukebox. Esta estructura representa el estado de la mquina de discos y por lo tanto es una buena candidata para envolverla dentro de nuestra clase Ruby. Creamos nuevas instancias de esta estructura mediante la llamada al mtodo de biblioteca CDPlayerNew . A continuacin, querramos envolver la estructura creada dentro de un nuevo objeto Ruby CDPlayer. Un fragmento de cdigo para hacer esto puede tener el siguiente aspecto. (Vamos a hablar del parmetro mgico klass en un minuto.) CDJukebox *jukebox; VALUE obj; // Vendor library creates the Jukebox jukebox = new_jukebox(); // then we wrap it inside a Ruby CDPlayer object obj = Data_Wrap_Struct(klass, 0, cd_free, jukebox); Una vez que se ejecuta este cdigo, obj tendra una referencia a un objeto Ruby CDPlayer recin asignado, envolviendo a una nueva estructura C CDJukebox. Por supuesto, para obtener que este cdigo compile, tendramos que trabajar un poco ms. Tendramos que definir la clase CDPlayer y almacenar una referencia a l en la variable cCDPlayer. Tambin tendramos que definir la funcin para liberar nuestro objeto, cdplayer_free. Esto es fcil: simplemente se llama al mtodo de biblioteca que provee el vendedor. static void cd_free(void *p) { free_jukebox(p); } Sin embargo, los fragmentos de cdigo no hacen un programa. Tenemos que empaquetar todas estas cosas de una manera que se integren en el intrprete. Y para hacer esto, tenemos que ver algunas de las convenciones que utiliza el intrprete.

Creacin de Objetos
Ruby 1.8 ha racionalizado la creacin y la inicializacin de objetos. A pesar de que las formas antiguas todava funcionan, la nueva forma, utilizando las funciones de asignacin, es mucho ms ordenada (y es menos probable que se deje de utilizar en el futuro).

194

La idea bsica es simple. Digamos que usted est creando un objeto de la clase CDPlayer en su programa Ruby:

cd = CDPlayer.new Entre bastidores, el intrprete llama al mtodo de clase new para CDPlayer. Como CDPlayer no tiene definido un mtodo new, Ruby busca en su clase padre, la clase Class. La implementacin de new en la clase Class es bastante simple: se asigna memoria para el nuevo objeto y luego se llama al mtodo de objeto initialize para inicializar esta memoria. Por lo tanto, si nuestra extensin CDPlayer va a ser una buena habitante de Ruby, debe trabajar en este marco. Esto significa que tendremos que implementar una funcin de asignacin y un mtodo initialize .

Funciones de Asignacin
La funcin de asignacin es la responsable de la creacin de la memoria que va a utilizar el objeto. Si el objeto que estamos implementando no utiliza ningn otro dato que las variables de instancia Ruby, entonces no es necesario escribir una funcin de asignacin --el asignador Ruby por defecto funciona bien. Pero si su clase envuelve una estructura C, tendr que asignar espacio para esta estructura con la funcin de asignacin. sta, se pasa a la clase del objeto que viene a ser asignado. En nuestro caso, con toda probabilidad un cCDPlayer, pero vamos a utilizar el parmetro como dado, ya que esto significa que vamos a trabajar correctamente si se subclasifica. static VALUE cd_alloc(VALUE klass) { CDJukebox *jukebox; VALUE obj; // Vendor library creates the Jukebox jukebox = new_jukebox(); // then we wrap it inside a Ruby CDPlayer object obj = Data_Wrap_Struct(klass, 0, cd_free, jukebox); return obj; } A continuacin, deber registrar su funcin de asignacin de cdigo de inicializacin de clase.

void Init_CDPlayer() { cCDPlayer = rb_define_class(CDPlayer, rb_cObject); rb_define_alloc_func(cCDPlayer, cd_alloc); // ... }

195

La mayora de los objetos, probablemente tambin tengan la necesidad de definir un inicializador. La funcin de asignacin crea un objeto vaco, sin inicializar, y tendremos que rellenarlo con los valores especficos. En el caso del reproductor de CD, se llama al constructor con el nmero de unidad del player para ser asociado a este objeto. static VALUE cd_initialize(VALUE self, VALUE unit) { int unit_id; CDJukebox *jb; Data_Get_Struct(self, CDJukebox, jb); unit_id = NUM2INT(unit); assign_jukebox(jb, unit_id); return self; } Una de las razones para este protocolo de varios pasos de creacin de objeto, es que permite al intrprete manejar situaciones donde los objetos tienen que ser creados por el procedimiento de puerta trasera. Un ejemplo es cuando los objetos vienen a deserializarse de su forma de clculo de referencias. En este caso, el intrprete tiene que crear un objeto vaco (llamando al asignador), pero no puede llamar a la inicializacin (ya que no tiene conocimiento de los parmetros para su uso). Otra situacin comn es cuando los objetos estn duplicados o clonados. Aqu se esconde otro tema. Debido a que los usuarios pueden optar por omitir el constructor, es necesario asegurarse de que el cdigo de asignacin deja el objeto retornado en un estado vlido. No contendr toda la informacin que hubiera tenido si se hubiera establecido mediante #initialize, pero al menos tiene que ser utilizable.

196

Asignacin de Objetos Anterior a la Versin 1.8 Antes de Ruby 1.8, si se quera asignar espacio adicional en un objeto, o bien haba que poner el cdigo en el mtodo initialize, o bien haba que definir un nuevo mtodo para su clase. Guy De- coux recomienda el siguiente enfoque hbrido para maximizar la compatibilidad entre las extensiones 1.6 y 1.8. static VALUE cd_alloc(VALUE klass) { // igual que antes } static VALUE cd_new(int argc, VALUE *argv, VALUE klass) { VALUE obj = rb_funcall2(klass, rb_intern(allocate), 0, 0); rb_obj_call_init(obj, argc, argv); return obj; } void init_CDPlayer() { // ... #if HAVE_RB_DEFINE_ALLOC_FUNC // 1.8 allocation rb_define_alloc_func(cCDPlayer, cd_alloc); #else // define manual allocation function for 1.6 rb_define_singleton_method(cCDPlayer, allocate, cd_alloc, 0); #endif rb_define_singleton_method(cCDPlayer, new, cd_new, 1); // ... } Si est escribiendo cdigo que debe ejecutarse en las versiones antiguas y recientes de Ruby, tendr que adoptar un enfoque similar a este. Sin embargo, es probable que tambin necesite manejar la clonacin y la duplicacin, y dems tendr que considerar qu sucede cuando su objeto calcula las referencias.

Clonacion de Objetos
Todos los objetos Ruby pueden ser copiados usando uno de los dos mtodos, dup y clone. Los dos mtodos son similares: ambos producen una nueva instancia de la clase receptora llamando a la funcin de asignacin. Luego se copian las variables de instancia del original. clone va un poco ms all y copia la clase singleton original (si la tiene) y las banderas (como la bandera que indica que un objeto est congelado). Se puede pensar en dup como una copia de los contenidos y en clone como una copia del objeto completo. Sin embargo, el intrprete de Ruby no sabe cmo manejar la copia del estado interno de los objetos que se escriben como extensiones C. Por ejemplo, si su objeto envuelve una estructura C que contiene un descriptor de fichero abierto, depende de la semntica de su implementacin si el descriptor simplemente se deben copiar en el nuevo objeto, o si se debe abrir un nuevo descriptor de fichero. Para manejar esto, el intrprete delega en su cdigo la responsabilidad de copiar el estado interno de los objetos que se implementan. Despus de copiar las variables de instancia del objeto, el intrprete invoca el mtodo initialize_copy del nuevo objeto, pasandole una referencia al objeto original. Depende de usted el implementar la semntica significativa en este mtodo. Para nuestra clase CDPlayer vamos a adoptar un enfoque bastante simple con la cuestin de la clonacin: simplemente se va a copiar la estructura CDJukebox del objeto original.

197

Hay un pedacito de cdigo extrao en este ejemplo. Para probar que el objeto original es de hecho algo que se puede clonar desde el nuevo, el cdigo comprueba que el original 1. tiene un tipo T_DATA (lo que significa que es un objeto no esencial), y 2. tiene una funcin libre con la misma direccin que nuestra funcin libre.

Esta es una forma relativa de alto rendimiento para verificar que el objeto original es compatible con el nuestro (siempre y cuando no se compartan las funciones libres entre clases). Una alternativa, que es ms lenta, sera utilizar rb_obj_is_kind_of y hacer una prueba directa de la clase. static VALUE cd_init_copy(VALUE copy, VALUE orig) { CDJukebox *orig_jb; CDJukebox *copy_jb; if (copy == orig) return copy; // we can initialize the copy from other CDPlayers // or their subclasses only if (TYPE(orig) != T_DATA || RDATA(orig)->dfree != (RUBY_DATA_FUNC)cd_free) { rb_raise(rb_eTypeError, wrong argument type); } // copy all the fields from the original // objects CDJukebox structure to the new object Data_Get_Struct(orig, CDJukebox, orig_jb); Data_Get_Struct(copy, CDJukebox, copy_jb); MEMCPY(copy_jb, orig_jb, CDJukebox, 1); return copy; } Nuestro mtodo de copia no tiene que asignar una estructura envuelta para recibir los objetos originales de la estructura CDJukebox: el mtodo cd_alloc ya se ha ocupado de eso. Tenga en cuenta que en este caso es correcto hacer la comprobacin de tipos basados en las clases: necesitamos que el objeto original tenga una estructura CDJukebox envuelta, y los nicos objetos que tienen una de estas se derivan de la clase CDPlayer.

Ponindolo Todo Junto


OK, finalmente estamos listos para escribir todo el cdigo de nuestra clase CDPlayer.

// Helper function to free a vendor CDJukebox static void cd_free(void *p) { free_jukebox(p); } // Allocate a new CDPlayer object, wrapping // the vendors CDJukebox structure static VALUE cd_alloc(VALUE klass) { CDJukebox *jukebox; VALUE obj; // Vendor library creates the Jukebox jukebox = new_jukebox(); // then we wrap it inside a Ruby CDPlayer object obj = Data_Wrap_Struct(klass, 0, cd_free, jukebox); return obj; } // Assign the newly created CDPLayer to a // particular unit static VALUE cd_initialize(VALUE self, VALUE unit) {

198

int unit_id; CDJukebox *jb; Data_Get_Struct(self, CDJukebox, jb); unit_id = NUM2INT(unit); assign_jukebox(jb, unit_id); return self; } // Copy across state (used by clone and dup). For jukeboxes, we // actually create a new vendor object and set its unit number from // the old static VALUE cd_init_copy(VALUE copy, VALUE orig) { CDJukebox *orig_jb; CDJukebox *copy_jb; if (copy == orig) return copy; // we can initialize the copy from other CDPlayers or their // subclasses only if (TYPE(orig) != T_DATA || RDATA(orig)->dfree != (RUBY_DATA_FUNC)cd_free) { rb_raise(rb_eTypeError, wrong argument type); } // copy all the fields from the original objects CDJukebox // structure to the new object Data_Get_Struct(orig, CDJukebox, orig_jb); Data_Get_Struct(copy, CDJukebox, copy_jb); MEMCPY(copy_jb, orig_jb, CDJukebox, 1); return copy; } // The progress callback yields to the caller the percent complete static void progress(CDJukebox *rec, int percent) { if (rb_block_given_p()) { if (percent > 100) percent = 100; if (percent < 0) percent = 0; rb_yield(INT2FIX(percent)); } } // Seek to a given part of the track, invoking the progress callback // as we go static VALUE cd_seek(VALUE self, VALUE disc, VALUE track) { CDJukebox *jb; Data_Get_Struct(self, CDJukebox, jb); jukebox_seek(jb, NUM2INT(disc), NUM2INT(track), progress); return Qnil; } // Return the average seek time for this unit static VALUE cd_seek_time(VALUE self) { double tm; CDJukebox *jb; Data_Get_Struct(self, CDJukebox, jb); tm = get_avg_seek_time(jb); return rb_float_new(tm); } // Return this players unit number static VALUE

199

cd_unit(VALUE self) { CDJukebox *jb; Data_Get_Struct(self, CDJukebox, jb); return INT2NUM(jb->unit_id);; } void Init_CDPlayer() { cCDPlayer = rb_define_class(CDPlayer, rb_cObject); rb_define_alloc_func(cCDPlayer, cd_alloc); rb_define_method(cCDPlayer, initialize, cd_initialize, 1); rb_define_method(cCDPlayer, initialize_copy, cd_init_copy, 1); rb_define_method(cCDPlayer, seek, cd_seek, 2); rb_define_method(cCDPlayer, seek_time, cd_seek_time, 0); rb_define_method(cCDPlayer, unit, cd_unit, 0); } Ahora podemos controlar nuestra mquina de discos desde Ruby de una forma agradable y orientada a objetos. require CDPlayer p = CDPlayer.new(13) puts Unit is #{p.unit} p.seek(3, 16) {|x| puts #{x}% done } puts Avg. time was #{p.seek_time} seconds p1 = p.dup puts Cloned unit = #{p1.unit} produce: Unit is 13 26% done 79% done 100% done Avg. time was 1.2 seconds Cloned unit = 13 Este ejemplo demuestra la mayor parte de lo que hemos hablado hasta ahora, con una muy buena caracterstica adicional. La biblioteca del vendedor proporciona una rutina de retorno de llamada --un puntero a funcin a la que se llama de vez en cuando mientras que el hardware est trabajando en el camino hacia el siguiente disco. Hemos establecido hasta aqu la ejecucin de un bloque de cdigo que se pasa como argumento a seek. En la funcin progress, comprobamos para ver si hay un iterador en el contexto actual y, si existe, se ejecuta con el porcentaje en curso como un argumento.

Asignacin de Memoria
A veces puede ser necesario asignar memoria a una extensin que no ser utilizada para el almacenamiento de objetos - tal vez usted tiene un mapa de bits gigante para un filtro Bloom, una imagen o un montn de pequeas estructuras que Ruby no utiliza directamente. Para trabajar correctamente con el recolector de basura, se deben utilizar las rutinas de asignacin de memoria siguientes. Estas rutinas hacen un poco ms de trabajo que el estndar malloc. Por ejemplo, si ALLOC_N determina que no puede asignar la cantidad deseada de memoria, se invoca el recolector de basura para tratar de recuperar algo de espacio. Se genera un NoMemError si no se puede o si la cantidad de memoria solicitada no es vlida.

API: Asignacin de Memoria


type * ALLOC_N( c-type, n ) Asigna n objetos c-tipo, donde c-tipo es el nombre literal del tipo C, no una variable de ese tipo. type * ALLOC( c-type )

200

Asigna un y convierte el resultado a un puntero de ese tipo.

REALLOC_N( var, c-type, n ) Reasigna n c-tipos y asigna el resultado a var, un puntero a una variable de tipo c-tipo. type * ALLOCA_N( c-type, n ) Asigna memoria para n objetos c-tipo en la pila --esta memoria se libera automticamente cuando la funcin que invoca ALLOCA_N retorna.

Sistema de Tipos Ruby


En Ruby, se depende menos del tipo (o clase) de un objeto y ms de sus capacidades. Esto se llama duck typing. Se describen con ms detalle ms adelante. Se encontrarn muchos ejemplos de esto si se examina el cdigo fuente del propio intrprete. Por ejemplo, el siguiente cdigo implementa el mtodo Kernel.exec. VALUE rb_f_exec(argc, argv) int argc; VALUE *argv; { VALUE prog = 0; VALUE tmp; if (argc == 0) { rb_raise(rb_eArgError, wrong number of arguments); } tmp = rb_check_array_type(argv[0]); if (!NIL_P(tmp)) { if (RARRAY(tmp)->len != 2) { rb_raise(rb_eArgError, wrong first argument); } prog = RARRAY(tmp)->ptr[0]; SafeStringValue(prog); argv[0] = RARRAY(tmp)->ptr[1]; } if (argc == 1 && prog == 0) { VALUE cmd = argv[0]; SafeStringValue(cmd); rb_proc_exec(RSTRING(cmd)->ptr); } else { proc_exec_n(argc, argv, prog); } rb_sys_fail(RSTRING(argv[0])->ptr); return Qnil; /* dummy */ } El primer parmetro de este mtodo puede ser una cadena, o una matriz que contiene dos cadenas. Sin embargo, el cdigo no hace explcitamente comprobacin del tipo del argumento. En su lugar, primero llama a rb_check_array_type, pasndolo en el argumento. Qu es lo que hace este mtodo? Vamos a ver. VALUE rb_check_array_type(ary) VALUE ary; { return rb_check_convert_type(ary, T_ARRAY, Array, to_ary); } La trama se complica. Vamos a rastrear rb_check_convert_type.

201

VALUE rb_check_convert_type(val, type, tname, method) VALUE val; int type; const char *tname, *method; { VALUE v; /* always convert T_DATA */ if (TYPE(val) == type && type != T_DATA) return val; v = convert_type(val, tname, method, Qfalse); if (NIL_P(v)) return Qnil; if (TYPE(v) != type) { rb_raise(rb_eTypeError, %s#%s should return %s, rb_obj_classname(val), method, tname); } return v; } Ahora estamos llegando a alguna parte. Si el objeto es del tipo correcto ( T_ARRAY en nuestro ejemplo), se retorna el objeto original. De lo contrario..., no nos demos por vencidos todava. En su lugar, llamamos a nuestro objeto original y le pedimos que si se puede representar como una matriz (que llama a su mtodo to_ary). Si se puede, nos alegramos y continuamos. El cdigo est diciendo yo no necesito un Array, slo necesito algo que se pueda representar como una matriz. Esto significa que Kernel.exec lo aceptar como un parmetro matriz cualquiera que implementa un mtodo to_ary. Se discuten estos protocolos de conversin con ms detalle (pero desde la perspectiva de Ruby) ms adelante. Qu significa todo esto para usted como escritor de extensin? Hay dos mensajes. En primer lugar, tratar de evitar la comprobacin de los tipos para parmetros que se le pasan. En su lugar, ver si hay un mtodo rb_check_xxx_type que cnvierte el parmetro en el tipo que se necesita. Si no, buscar si existe una funcin conversora (como rb_Array, rb_Float o rb_Integer) que vaya a hacer el truco para usted. En segundo lugar, si se est escribiendo una extensin que implementa algo que puede ser utilizao significativamente como una cadena o una matriz Ruby, considerar la implementacin de los mtodos to_str o to_ary, permitiendo as la implementacin de los objetos por su extensin para ser utilizado en contextos de cadena o matriz.

La Creacin de una Extensin


Despus de haber escrito el cdigo fuente de una extensin, tenemos que compilarlo para que Ruby pueda utilizarlo. Podemos hacer esto como un objeto compartido que se carga dinmicamente en tiempo de ejecucin, o se vinculando estticamente la extensin en el intrprete principal mismo de Ruby. El procedimiento bsico es el mismo. 1. 2. 3. 4. 5. 6. Crear el archivo o archivos fuente de cdigo C en un directorio determinado. Opcionalmente, crear un archivo Ruby de soporte en un subdirectorio lib. Crear extconf.rb. Ejecutar extconf.rb para crear un Makefile para los archivos C en este directorio. Ejecutar make. Ejecutar make install.

La Creacin de un Makefile con extconf.rb


La figura 15 muestra el flujo de trabajo en la construccin de una extensin. La clave de todo el proceso es el programa extconf.rb que usted, como desarrollador, tiene que crear. En el fichero extconf.rb , se escribe un sencillo programa que determina qu caractersticas estarn disponibles en el sistema de usuario y donde se encuentran estas caractersticas. La ejecucin de extconf.rb construye un Makefile personalizado, adaptado tanto para la aplicacin como para el sistema en el que est siendo compilado. Cuando se ejecuta el comando make contra este Makefile, se construye e instala (opcional) la extensin.

202

Un fichero extconf.rb puede constar en su forma ms sencilla de tan slo dos lneas de cdigo, y para muchas extensiones, esto es suficiente. require mkmf create_makefile(Test) La primera lnea trae el mdulo de librera mkmf (que se describe ms adelante). ste contiene todos los comandos que se van a utilizar. La segunda lnea crea un Makefile para una extensin llamada Test (Tenga en cuenta que Test es el nombre de la extensin, al makefile siempre se le llamar Makefile). Test se construir a partir de todos los archivos de cdigo fuente C en el directorio actual . Cuando se carga el cdigo, Ruby llamar a su mtodo Init_Test. Digamos que ejecutamos este programa extconf.rb en un directorio que contiene un solo archivo fuente, main.c. El resultado es un makefile que construye nuestra extensin. En una mquina Linux, este makefile ejecuta los siguientes comandos. gcc -fPIC -I/usr/local/lib/ruby/1.8/i686-linux -g -O2 \ -c main.c -o main.o gcc -shared -o Test.so main.o -lc El resultado de esta recopilacin es Test.so, que puede vincularse dinmicamente a Ruby en tiempo de ejecucin con require. En Mac OS X, los comandos son diferentes, pero el resultado es el mismo: un objeto compartido se crea (un paquete (bundle) en Mac) . gcc -fno-common -g -O2 -pipe -fno-common \ -I/usr/lib/ruby/1.8/powerpc-darwin \ -I/usr/lib/ruby/1.8/powerpc-darwin -c main.c cc -dynamic -bundle -undefined suppress -flat_namespace \ -L/usr/lib -o Test.bundle main.o -ldl -lobjc Observe cmo los comandos mkmf han situado de forma automtica las bibliotecas especficas de la plataforma y las opciones especficas utilizadas para el compilador local. Impresionante, eh? Aunque este programa extconf.rb bsico funciona para muchas extensiones simples, puede que tenga que trabajar un poco ms, si la extensin necesita los archivos de cabecera o bibliotecas que no 203

estn incluidas en el entorno de compilacin por defecto, o si la compilacin de cdigo condicional est basada en la presencia de bibliotecas o funciones. Un requisito comn es especificar los directorios no estndar donde se puedan encontrar otros ficheros y libreras que se incluyan. Se trata de un proceso en dos pasos. En primer lugar, su fichero extconf.rb debe contener uno o ms comandos dir_config. Esto especifica una etiqueta para un conjunto de directorios. A continuacin, cuando se ejecuta extconf.rb, decirle en mkmf donde se encuentran los directorios fsicos correspondientes del sistema en curso.

Dividir el espacio de nombres Los escritores de extensin estn siendo cada da mejores ciudadanos. En lugar de instalar su directorio de trabajo en uno de los directorios de las libreras de Ruby, estn usando subdirectorios para agrupar sus archivos juntos. Esto es fcil con extconf.rb. Si el parmetro para la llamada create_makefile contiene delante barras diagonales, mkmf asume que todo lo que hay antes de la ltima barra es un nombre de directorio y que el resto es el nombre de extensin. La extensin se instalar en el directorio dado (en relacin con el rbol de directorios Ruby). En el siguiente ejemplo, la exrtensin sigue siendo denominada Test. require mkmf create_makefile(wibble/Test) Sin embargo, en caso de requerirse esta clase en un programa Ruby, hay que escribir

require wibble/Test

Si extconf.rb contiene la lnea dir_config(nombre), entonces dar la ubicacin de los directorios correspondientes con las opciones de lnea de comandos --with-name-include=directory Aadir el directorio/include para el comando de compilacin. --with-name-lib=directory Aadir el directorio/lib al comando de enlazado. Si (como es comn) los directorios de inclusin y de libreras son los subdirectorios llamados include y lib de algn otro directorio, puede tomar un atajo. --with-name-dir=directory Aadir el directorio/lib y el directorio/include a los comandos de enlazado y de compilacin, respectivamente. As como especificar todas estas opciones --with a la hora de ejecutar extconf.rb, tambin se puede utilizar --with con las opciones que se especificaron cuando Ruby se construy para su mquina. Esto significa que usted puede descubrir y utilizar la ubicacin de las bibliotecas que se utilizan por Ruby mismo. Para concretar, digamos que se necesitan utilizar las bibliotecas CDJukebox del proveedor y los ficheros para el reproductor de CD que estamos desarrollando. El fichero extconf.rb puede contener: require mkmf dir_config(cdjukebox) # ... ms cosas create_makefile(CDPlayer) Y entonces, se ejecuta extconf.rb de forma as como

204

% ruby extconf.rb --with-cdjukebox-dir=/usr/local/cdjb


El Makefile generado podra asumir que /usr/local/cdjb/lib contiene las bibliotecas y /usr/ local/cdjb/include los archivos de inclusin. El comando dir_config se aade a la lista de lugares para la bsqueda de bibliotecas y archivos a incluir. Lo que no hace, en cambio, es vincular las bibliotecas a su aplicacin. Para ello, tendr que utilizar uno o ms comandos have_library o find_library. have_library busca un punto de entrada en la biblioteca dada. Si no encuentra el punto de entrada, se aade la biblioteca a la lista de las bibliotecas a utilizar cuando se enlaza la extensin. find_library es similar, pero le permite especificar una lista de directorios de bsqueda para la biblioteca. Estos son los contenidos del fichero extconf.rb que utilizamos para enlazar nuestro reproductor de CD: require mkmf dir_config(cdjukebox) have_library(cdjukebox, new_jukebox) create_makefile(CDPlayer) Una biblioteca particular, puede estar en diferentes lugares en funcin del sistema del host. El sistema X Window, por ejemplo, es notorio que habita en diferentes directorios en diferentes sistemas. El comando find_library buscar en la lista de directorios suministrada, para encontrar el correcto (es diferente de have_library, que slo utiliza la informacin de configuracin para la bsqueda). Por ejemplo, para crear un Makefile que utiliza X Windows y una biblioteca JPEG, extconf.rb puede contener require mkmf if have_library(jpeg,jpeg_mem_init) find_library(X11, XOpenDisplay, /usr/X11/lib, # /usr/X11R6/lib, # /usr/openwin/lib) # then create_makefile(XThing) else puts No X/JPEG support available end and list of directories to check for library

Hemos aadido algunas funcionalidades adicionales a este programa. Todos los comandos mkmf retornan false si fallan. Esto significa que puede escribir un extconf.rb que genera un Makefile slo si todo lo que necesita est presente. La distribucin Ruby hace esto para tratar de compilar slo las extensiones que son compatibles con su sistema. Tambin puede querer que su cdigo de extensin pueda configurar las caractersticas que utiliza en funcin del entorno de destino. Por ejemplo, nuestro reproductor de mquina de discos puede ser capaz de utilizar un decodificador MP3 de alto rendimiento, si el usuario final tiene uno instalado. Se puede comprobar mediante la bsqueda de su archivo de cabecera. require mkmf dir_config(cdjukebox) have_library(cdjb, CDPlayerNew) have_header(hp_mp3.h) create_makefile(CDJukeBox) Tambin puede comprobar para ver si el entorno de destino tiene una funcin particular en cualquiera de las bibliotecas que va a utilizar. Por ejemplo, la llamada setpriority sera til, pero no siempre es t disponible. Se puede comprobar con require mkmf

205

dir_config(cdjukebox) have_func(setpriority) create_makefile(CDJukeBox) Ambos, have_header y have_func definen constantes de preprocesador si encuentran sus objetivos. Los nombres estn formados por la conversin del nombre de destino en maysculas y anteponiendo HAVE_. El cdigo C puede tomar ventaja de esta construccin tal como #if defined(HAVE_HP_MP3_H) # include <hp_mp3.h> #endif #if defined(HAVE_SETPRIORITY) err = setpriority(PRIOR_PROCESS, 0, 10) #endif Si usted tiene requisitos especiales que no se pueden cumplir con todos estos comandos mkmf, su programa puede aadir directamente las variables globales $CFLAGS y $LFLAGS, que se pasan al compilador y enlazador, respectivamente. A veces creamos un fichero extconf.rb y simplemente parece no funcionar. Se le da el nombre de una biblioteca, y jura que no se dispone de la biblioteca como que jams ha existido en todo el planeta. Se ajusta y se hacen cambios, pero mkmf todava no puede encontrar la biblioteca que se necesita. Sera bueno si se pudiera saber exactamente lo que est haciendo entre bambalinas. Bueno, se puede. Cada vez que se ejecuta el script extconf.rb, mkmf genera un archivo de registro que contiene los detalles de lo que hizo. Si nos fijamos en el fichero mkmf.log, podremos ver los pasos que el programa ha utilizado para tratar de encontrar las bibliotecas que se han solicitado. A veces, intentar estos pasos de forma manual ayuda a localizar el problema.

La Instalacin de Destino
El Makefile producido por el fichero extconf.rb incluir una instalacin de destino. Esto copiar la biblioteca compartida objeto en el lugar correcto en su sistema de archivos de usuario (o usuarios) local. El destino est ligado a la ubicacin de la instalacin del intrprete Ruby que se utiliza en primer lugar para ejecutar extconf.rb. Si se tienen varios interpretes Ruby instalados en el sistema, la extensin se instala en el rbol de directorios del que ejecut extconf.rb. Adems de instalar la biblioteca compartida, extconf.rb comprobar la presencia de un subdirectorio /lib. Si encuentra uno, se encargar de todos los archivos Ruby que haya que instalar junto con su objeto compartido. Esto es til si desea dividir el trabajo de escritura de la extensin en cdigo de bajo nivel C y en cdigo de alto nivel Ruby.

Enlazamiento Esttico
Finalmente, si su sistema no admite la vinculacin dinmica, o si usted tiene un mdulo de extensin que desea tener enlazado estticamente a Ruby, hay que editar el archivo ext/Setup de la distribucin y aadir su directorio a la lista de extensiones. En el directorio de su extensin, cree un fichero llamado MANIFEST que contenga una lista de todos los archivos de su extensin (fuentes, extconf.rb, lib/, etc). A continuacin, reconstruya Ruby. Las extensiones que figuren en el fichero Setup sern enlazadas estticamente al archivo ejecutable de Ruby. Si se desea desactivar cualquier enlace dinmico, y enlazar todas las extensiones de forma esttica, hay editar ext/Setup para que contenga la siguiente opcin: option nodynamic

Un Atajo
Si est ampliando una biblioteca ya existente escrita en C o C++, es posible que desee investigar SWIG (http://www.swig.org). SWIG es un generador de interfaz: toma una definicin de biblioteca (por lo general a partir de un archivo de cabecera) y genera automticamente el cdigo de unin necesario para acceder a la biblioteca de otro lenguaje. SWIG soporta Ruby, lo que significa que pueden generar los ar-

206

chivos de cdigo fuente en C que envuelven las libreras externas en las clases Ruby.

Inrtegracin de un intrprete de Ruby


Adems de extender Ruby aadindole cdigo C, tambin se le puede dar la vuelta al problema e incrustar Ruby dentro de una aplicacin. Hay tiene dos maneras de hacer esto. La primera es dejar que el intrprete tome el control llamando a ruby_run. Este es el mtodo ms sencillo, pero tiene un inconveniente importante --el intrprete nunca regresa de una llamada a ruby_run. He aqu un ejemplo: #include ruby.h int main(void) { /* ... las cosas propias de la aplicacin ... */ ruby_init(); ruby_init_loadpath(); ruby_script(embedded); rb_load_file(start.rb); ruby_run(); exit(0); } Para inicializar el intrprete de Ruby, es necesario llamar a ruby_init (). Sin embargo, en algunas plataformas, puede ser necesario tomar medidas especiales antes de esto. #if defined(NT) NtInitialize(&argc, &argv); #endif #if defined(__MACOS__) && defined(__MWERKS__) argc = ccommand(&argv); #endif Ver main.c en la distribucin Ruby para caulquier otra definicin especial o configuracin necesaria para su plataforma. Es necesario incluir los archivos Ruby de inclusin y poner los de librera accesibles para compilar el cdigo incrustado. En mi ordenador (Mac OS X) tengo el intrprete Ruby 1.8 instalado en un directorio privado, por lo que mi Makefile tiene el siguiente aspecto. WHERE=/Users/dave/ruby1.8/lib/ruby/1.8/powerpc-darwin/ CFLAGS=-I$(WHERE) -g LDFLAGS=-L$(WHERE) -lruby -ldl -lobjc embed: embed.o $(CC) -o embed embed.o $(LDFLAGS) La segunda forma de embeber Ruby permite al cdigo Ruby y al cdigo C participar en ms de un dilogo: el cdigo C llama a algn cdigo Ruby, y el cdigo Ruby responde. Para ello, se incializa el intrprete como de costumbre. Entonces, en lugar de entrar en el bucle principal del intrprete, se llama a los mtodos especficos en el cdigo Ruby. Cuando estos mtodos retornan, el cdigo C toma el control de nuevo. Sin embargo, hay una pega. Si el cdigo Ruby genera una excepcin y no es atrapada, el programa C terminar. Para superar esto, se tiene que hacer lo que el intrprete hace y proteger todas las llamadas que podran lanzar una excepcin. Esto puede causar problemas. La llamada al mtodo rb_protect envuelve la llamada a otra funcin C. Esta segunda funcin debe invocar al mtodo Ruby. Sin embargo, el mtodo envuelto por rb_protect est definido para tomar un slo nico parmetro. Para pasar ms implica algn feo lance en C. Veamos un ejemplo. He aqu una simple clase Ruby que implementa un mtodo para devolver la suma de los nmeros desde uno al max.

207

class def end end

Summer sum(max) raise Invalid maximum #{max} if max < 0 (max*max + max)/2

Vamos a escribir un programa en C que llama a una instancia de esta clase varias veces. Para crear el ejemplo, vamos a obtener el objeto de la clase (mediante la bsqueda de una constante de alto nivel cuyo nombre es el nombre de nuestra clase). A continuacin, le pedimos a Ruby crear una instancia de esa clase --rb_class_new_instance es en realidad una llamada a Class.new. (Los dos 0 parmetros iniciales son el recuento de argumentos y un puntero ficticio a los argumentos mismos). Una vez que tengamos este objeto, se puede invocar su mtodo sum con rb_funcall. #include ruby.h static int id_sum; int Values[] = { 5, 10, 15, -1, 20, 0 }; static VALUE wrap_sum(VALUE args) { VALUE *values = (VALUE *)args; VALUE summer = values[0]; VALUE max = values[1]; return rb_funcall(summer, id_sum, 1, max); } static VALUE protected_sum(VALUE summer, VALUE max) { int error; VALUE args[2]; VALUE result; args[0] = summer; args[1] = max; result = rb_protect(wrap_sum, (VALUE)args, &error); return error ? Qnil : result; } int main(void) { int value; int *next = Values; ruby_init(); ruby_init_loadpath(); ruby_script(embedded); rb_require(sum.rb); // get an instance of Summer VALUE summer = rb_class_new_instance(0, 0, rb_const_get(rb_cObject, rb_intern(Summer))); id_sum = rb_intern(sum); while (value = *next++) { VALUE result = protected_sum(summer, INT2NUM(value)); if (NIL_P(result)) printf(Sum to %d doesnt compute!\n, value); else printf(Sum to %d is %d\n, value, NUM2INT(result)); } ruby_finalize(); exit(0); } Una ltima cosa: el intrprete de Ruby no fue escrito originalmente con la incrustacin en mente. Probablemente, el mayor problema es que se mantiene el estado de las variables globales, por lo que no es un subproceso seguro (thread-safe). Se puede integrar Ruby --un solo intrprete por proceso. Un buen recurso para embeber Ruby en C++ se encuentra en http://metaeditor.sourceforge. net/embed/. Esta pgina tambin contiene enlaces a otros ejemplos de incrustacin Ruby.

208

API Ruby Empotrado


void ruby_init( ) Crea e inicializa el intrprete. Esta funcin debe ser llamada antes de cualquier otra relacionada a Ruby. void ruby_init_loadpath( ) Inicializa la variable $: (la ruta de carga), es necesario si el cdigo carga cualquier mdulo de librera. void ruby_options( int argc, char **argv ) Da las opciones de lnea de comandos del intrprete de Ruby. void ruby_script( char *name ) Establece el nombre del script Ruby (y $0) a nombre. void rb_load_file( char *file ) Carga el archivo dado en el intrprete. void ruby_run( ) Ejecuta el intrprete. void ruby_finalize( ) Cierra el intrprete. Para otro ejemplo de incorporacin de un intrprete Ruby dentro de otro programa, vase tambin eruby, que hemos descrito en pginas anteriores.

Puenteando Ruby y Otros Lenguajes


Hasta ahora, hemos hablado de la extensin de Ruby aadiendo rutinas escritas en C. Sin embargo, puede escribir extensiones en casi cualquier lenguaje, siempre y cuando se puedan unir los dos lenguajes con C. Casi todo es posible, incluidos los matrimonios difciles de Ruby y C++, Ruby y Java, etc. Pero se puede ser capaz de lograr lo mismo sin recurrir a cdigo C. Por ejemplo, se puede puentear a otros lenguajes usando middleware tales como SOAP o COM. Ver la seccin sobre SOAP y la seccin sobre inicio de automatizacin de Windows en pginas anteriores para ms detalles.

API Ruby C
Por ltimo, pero no menos importante, aqu estn algunas de las funciones de nivel C que pueden serle de utilidad al escribir una extensin. Algunas funciones requieren un ID: se puede obtener un ID para una cadena mediante rb_intern y reconstruir el nombre de un ID mediante rb_id2name. Como la mayora de estas funciones C tienen equivalentes Ruby que ya se han descrito en detalle en este libro, las descripciones que hagamos aqu, sern breves. La siguiente lista no es completa. Muchas ms funciones estn disponibles --ya que resultara demasiado documentar todas. Si se necesita un mtodo que no se encuentra aqu, mire en ruby.h o intern.h para posibles candidatos. Adems, en o cerca de la parte inferior de cada fichero fuente hay un conjunto de definiciones de mtodos que describen la unin de los mtodos Ruby a las funciones C. Usted puede ser capaz de llamar a la funcin C directamente o buscando una funcin contenedora que llama a la funcin que usted necesita. La siguiente lista, basada en la de README.EXT, muestra los archivos fuente principales del intrprete.

209

Core del Lenguaje Ruby class.c, error.c, eval.c, gc.c, object.c, parse.y, variable.c Funciones de Utilidad dln.c, regex.c, st.c, util.c Intrprete Ruby dmyext.c, inits.c, keywords main.c, ruby.c, version.c Librera Base array.c, bignum.c, compar.c, dir.c, enum.c, file.c, hash.c, io.c, marshal.c, math.c, numeric.c, pack.c, prec.c, process.c, random.c, range.c, re.c, signal.c, sprintf.c, string.c, struct.c, time.c

API: Definicin de Clases


VALUE rb_define_class( char *name, VALUE superclass ) Define una nueva clase en el nivel superior con el nombre y la superclase dados (para la clase Object, utilice rb_cObject). VALUE rb_define_module( char *name ) Define un nuevo mdulo en el nivel superior con el nombre dado. VALUE rb_define_class_under( VALUE under, char *name, VALUE superclass ) Define una clase anidada dentro de la clase o mdulo under. VALUE rb_define_module_under( VALUE under, char *name ) Define un mdulo anidada en la clase o mdulo under. void rb_include_module( VALUE parent, VALUE module ) Incluye el mdulo dado en la clase o padre del mdulo. void rb_extend_object( VALUE obj, VALUE module ) Extender obj con el modulo. VALUE rb_require( const char *name ) Equivalente a require nombre. Retorna Qtrue o Qfalse.

API: Definicin de Estructuras


VALUE rb_struct_define( char *name, char *attribute..., NULL ) Define una nueva estructura con los atributos dados. VALUE rb_struct_new( VALUE sClass, VALUE args..., NULL ) Crea una instancia de sClass con los valores de atributo dados. VALUE rb_struct_aref( VALUE struct, VALUE idx ) Devuelve el elemento nombrado o indexado por idx. VALUE rb_struct_aset( VALUE struct, VALUE idx, VALUE val ) Establece el atributo nombrado o indexado desde idx a val.
... Tambien estn ==> API: Defining Methods, API: Defining Variables and Constants, API: Calling Methods, API: Exceptions, API: Iterators, API: Accessing Variables, API: Object Status y API: Commonly Used Methods. Se pueden ver en el original en ingls de Programming Ruby (2nd edition): The Pragmatic Programmers Guide.

210

Ruby Cristalizado
El Lenguaje Ruby
Este captulo es una mirada de arriba a abajo del lenguaje Ruby. La mayor parte de lo que aparece aqu es la sintaxis y la semntica del lenguaje en s mismo --que en su mayora se ignoran las clases y los mdulos integrados (que se tratan en profundidad en la seccin siguiente). Ruby a veces implementa caraactersticas en sus libreras que en la mayora de los lengiuajes son parte de la sintaxis bsica. Hemos incluido aqu estos mtodos y hemos tratado de marcar sto con Librera en el margen. El contenido de este captulo puede parecer familiar --por una buena razn. Hemos cubierto casi todo esto como tutorial en los captulos anteriores. Considere la posibilidad de ver este captulo como una referencia independiente para el ncleo del lenguaje Ruby.

Diseo de los Fuentes


Los programas Ruby estn escritos en ASCII de 7 bits, Kanji (utilizando EUC o SJIS) o UTF-8. Si se usa un conjunto de cdigos que no sean ASCII de 7 bits, la opcin KCODE debe ser un valor apropiado, como dijimos anteriormente. Ruby es un lenguaje orientado a lnea. Las expresiones y las declaraciones Ruby se terminan al final de una lnea a menos que el analizador pueda determinar que la declaracin est incompleta --por ejemplo, si el ltimo smbolo de una lnea es un operador o una coma. Se puede utilizar un punto y coma para separar las expresiones mltiples en una lnea. Tambin se puede poner una barra invertida al final de una lnea para continuar en la siguiente. Los comentarios comienzan con # y corren hasta el final de la lnea fsica. Los comentarios son ignorados durante el anlisis sintctico. a b d e = 1 = 2; c = 3 = 4 + 5 + 6 + 7 = 8 + 9 \ + 10

# no necesita \ # necesita\

Las lneas fsicas entre una lnea que comienza con =begin y otra lnea que comienza con =end son ignoradas por Ruby y pueden usarse para comentar secciones de cdigo o para incrustar documentacin. Ruby lee la entrada del programa de una sola pasada, por lo que se pueden canalizar los programas al intrprete Ruby a travs de un pipe a la entrada estandar. echo puts Hello | ruby Si Ruby llega a travs de una lnea en cualquier parte del cdigo fuente que contiene slo __END__, sin espacios en blanco iniciales o finales, esa lnea se trata como el final del programa --las lneas subsiguientes no sern tratadas como cdigo del programa. Sin embargo, estas ltimas se pueden leer en el programa que se est ejecutando utilizando el objeto IO global DATA, descrito ms adelante.

Bloques BEGIN / END


Cada archivo de cdigo fuente en Ruby puede declarar bloques de cdigo que se ejecutan como un archivo que se carga (los bloques BEGIN) y tambin despus que el programa ha terminado de ejecutar (los bloques END). BEGIN { begin code } END { end code }

211

Un programa puede incluir varios bloques BEGIN y END. Los bloques BEGIN se ejecutan en el orden en que se encuentran. Los bloques END se ejecutan en orden inverso.

Delimitadores Generales de Entrada


As como el mecanismo normal de entrecomillado, las formas alternativas de cadenas literales, matrices, expresiones regulares y comandos de la shell se especifican mediante una sintaxis de delimitacingeneralizada. Todos estos literales comienzan con un carcter de porcentaje, seguido de un carcter nico que identifica el tipo de literal. Estos carcteres se resumen en: %q %Q, % %w, %W %r %x Cadena entre comillas simples. Cadena entre comillas dobles. Matriz de cadenas. Patrn de expresin regular. Comandos de shell.

Los literales se describen en las secciones correspondientes en este mismo captulo.

Siguiendo al carcter de tipo hay un delimitador, que puede ser cualquier carcter no alfabtico o no multibyte. Si el delimitador es uno de los carcteres (, [, { o <, el literal se compone de los caractereshasta el delimitador de cierre correspondiente, teniendo en cuenta los pares de delimitadores anidados. Para todos los dems delimitadores, el literal comprende los caracteres hasta la siguiente aparicin de carcter delimitador. %q/this is a string/ %q-string%q(a (nested) string) Las cadenas delimitadas pueden continuar en varias lneas; los finales de lnea y todos los espacios al principio de las lneas de continuacin se incluyen en la cadena. meth = %q{def fred(a) a.each {|i| puts i } end}

Los tipos Bsicos


Los tipos bsicos de Ruby son nmeros, cadenas, arrays, hashes, rangos, smbolos y expresiones regulares.

Nmeros Enteros y de Punto Flotante


Los enteros Ruby son objetos de la clase Fixnum o Bignum. Los objetos Fixnum contienen enteros que caben dentro de la palabra nativa de la mquina menos 1 bit. Sin embargo, si un Fixnum supera este rango, se convierte automticamente en un objeto Bignum, cuyo rango est limitado slo por la memoria disponible. Si una operacin con un Bignum tiene un resultado con un valor final que se ajusta a un Fixnum , este resultado ser devuelto como un Fixnum. Los enteros se escriben con un signo opcional, un indicador de base opcional ( 0 para octal, 0d para decimal, 0x para hexadecimal o 0b para binario), seguidos de una cadena de dgitos en la base correspondiente. El caracter guin bajo es ignorado en la cadena de dgitos. 123456 => 0d123456 => 123_456 => -543 => 0xaabb => 0377 => -0b10_1010 => 123456 # 123456 # 123456 # -543 # 43707 # 255 # -42 # Fixnum Fixnum Fixnum Fixnum Fixnum Fixnum Fixnum

underscore ignored negative number hexadecimal octal binary (negated)

212

123_456_789_123_456_789 => 123456789123456789 # Bignum Se puede obtener el valor entero correspondiente a un caracter ASCII precediendo ste por un signo de cierre de interogacin. Se pueden generar caracteres de control utilizando ?\C-x y ?\cx (la versin control de x es x&0x9f). Se pueden generar meta caracteres (x | 0x80) utilizando ?\M-x. La combinacin de meta y control se genera utilizando ?\M-\C-x. Se puede obtener el valor entero de la barra invertida con la secuencia ?\\. ?a => ?\n => ?\C-a => ?\M-a => ?\M-\C-a => ?\C-? => 97 10 1 225 129 127 # # # # # # ASCII character code for a newline (0x0a) control a = ?A & 0x9f = 0x01 meta sets bit 7 meta and control a delete character

Un literal numrico con un punto decimal y/o un exponente se convierte en un objeto Float, que corresponde al tipo de dato double de la arquitectura nativa. Se debe seguir el punto decimal con un dgito, ya que 1.e3 trata de invocar el mtodo e3 de la clase Fixnum. A partir de Ruby 1.8 tambin se debe colocar al menos un dgito antes del punto decimal. 12.34 -> -0.1234e2 -> 1234e-2 -> 12.34 -12.34 12.34

Cadenas
Ruby proporciona una serie de mecanismos para la creacin de cadenas literales. Cada uno genera objetos de tipo String. Estos mecanismos varan en trminos de cmo una cadena est delimitada y que cantidad de sustitucin se realiza en el contenido literal. Literales de cadena entre comillas simples ( cosas y %q/cosas/) se sometan a la sustitucin mnima. La secuencia \\ se convierte en una sola barra invertida y la forma \ en una comilla simple. Todas las otras barras invertidas aparecen en la cadena literalmente. hello a backslash \\\\ %q/simple string/ %q(nesting (really) works) %q no_blanks_here ; -> -> -> -> -> hello a backslash \ simple string nesting (really) works no_blanks_here

Cadenas entre comillas dobles (cosas, %Q/cosas / y %/cosas/) se someten a sustituciones adicionales, que se muestran en la Tabla 6 en la pgina siguiente. a = 123 \123mile Say \Hello\ %Q!I said nuts, I said! %Q{Try #{a + 1}, not #{a - 1}} %<Try #{a + 1}, not #{a - 1}> Try #{a + 1}, not #{a - 1} %{ #{ a = 1; b = 2; a + b } } -> -> -> -> -> -> -> Smile Say Hello I said nuts, I said Try 124, not 122 Try 124, not 122 Try 124, not 122 3

Las cadenas pueden continuar a travs de varias lneas de entrada, en cuyo caso contienen caracteres de nueva lnea. Tambin es posible utilizar documentos para expresar literales de cadena larga. Siempre que Ruby analiza la secuencia <<identificador o <<cadena entrecomillada, se reemplaza con una cadena literal construida de las sucesivas lneas lgicas de entrada. Interrumpe la construccin de la cadena cuando se encuentra una lnea que comienza con el identificador o cadena entrecomillada. Usted puede poner un signo menos inmediatamente despus de los <<caracteres, en cuyo caso el terminador puede

213

ser sangrado o indentado desde el margen izquierdo. Si para especificar el terminador se utiliza una cadena entre comillas, sus reglas de entrecomillado se aplican al documento, de lo contrario, se aplican comillas dobles. print <<HERE Double quoted \ here document. It is #{Time.now} HERE print <<-THERE This is single quoted. The above used #{Time.now} THERE produce: Double quoted here document. It is Thu Aug 26 22:37:12 CDT 2004 This is single quoted. The above used #{Time.now} Cadenas en la entrada adyacentes a comillas simples y dobles se concatenan para formar un nico objeto String. Con cat en ate -> Concatenate Las cadenas se almacenan como secuencias de bytes de 8 bits (para su uso en Japn, la biblioteca jcode soporta un conjunto de operaciones de cadenas escritas con codificacin EUC, SJIS o UTF-8. La cadena subyacente, sin embargo, sigue siendo accedida como una serie de bytes., y cada byte puede contener cualquiera de los 256 valores de 8 bits, incluyendo nulos y saltos de lnea. Las secuencias de sustitucin en la Tabla 6 permiten insertar de forma vonveniente y portable caracteres no imprimibles Cada vez que se utiliza una cadena literal en una asignacin o como un parmetro, se crea un nuevo objeto String. 3.times do print hello.object_id, end produce: 937140 937110 937080 La documentacin para las diferentes clases, entre ellas String, comienza pginas adelante.

Rangos
Fuera del contexto de una expresin condicional, expr..expr y expr...expr construyen objetos Range.

214

La forma de dos puntos es un rango inclusivo y el que tiene tres puntos es un rango que excluye su ltimo elemento. Vase la descripcin de la clase Range ms adelante para ms detalles. Vea tambin la descripcin de las expresiones condicionales para otros usos de los rangos.

Matrices
Los literales de la clase Array se crean mediante la colocacin de una serie de objetos separados por comas referenciados entre corchetes. Una coma al final se ignora. arr = [ fred, 10, 3.14, This is a string, barney(pebbles), ] Se pueden construir matrices de cadenas usando la notacin w% y %W. La forma en minscula se obtiene con los elementos sucesivos de la matriz separados por espacios. No se realiza sustitucin en las cadenas individuales. La forma en mayscula tambin convierte las palabras en una matriz, pero realiza sustituciones normales de cadenas entre comilladas en cada palabra. Se puede poner un espacio ente palabras con una barra invertida. Esta es una forma de entrada delimitada generalizada. arr = %w( fred wilma barney betty great\ gazoo ) arr -> [fred, wilma, barney, betty, great gazoo] arr = %w( Hey!\tIt is now -#{Time.now}- ) arr -> [Hey!\\tIt, is, now, -#{Time.now}-] arr = %W( Hey!\tIt is now -#{Time.now}- ) arr -> [Hey!\tIt, is, now, -Thu Aug 26 22:37:13 CDT 2004-]

Hashes
Un literal hash se crea mediante la colocacin de una lista de pares clave/valor entre llaves, con una coma o la secuencia => entre la clave y el valor. Una coma final se ignora. colors = { red => 0xf00, green => 0x0f0, blue => 0x00f } Las claves y/o valores de un hash particular no necesitan ser del mismo tipo.

Requisitos para una Clave Hash


Las claves hash deben responder al mensajes hash retornando un cdigo hash, y este cdigo hash para una clave determinada no debe cambiar. Las claves utilizadas en los hashes tambin deben ser comparables utilizando eql?. Si eql? retorna true para dos claves, entonces estas claves tambin deben tener el mismo cdigo hash. Esto significa que ciertas clases (como Array y Hash) no pueden ser convenientemente utilizadas como claves, debido a que sus valores pueden cambiar en funcin de su contenido. Si se mantiene una referencia externa a un objeto que se utiliza como una clave, y la utilizacin de esta referencia altera al objeto, cambiando as su cdigo hash, la bsqueda hash a partir de esa clave puede no funcionar. Dado que las cadenas son las claves ms utilizadas, y los contenidos de las cadenas a menudo cambian, Ruby trata las claves cadena de forma especial. Si utiliza un objeto String como clave hash, el hash duplicar la cadena internamente y utilizar la copia como clave. La copia ser congelada. Cualquier cambio realizado en la cadena original no afectar al hash. Si usted escribe sus propias clases y utiliza instancias de ellas como claves hash, necesita asegurarse de que (a) los valores hash de los objetos clave no cambian una vez que los objetos han sido creados o (b) recuerda llamar al mtodo Hash#rehash para reindexar el hash cada vez que una clave hash es cambiada.

215

Smbolos
Un smbolo en Ruby es un identificador que corresponde a una cadena de caracteres, a menudo un nombre. Se construye el smbolo para un nombre precedindole por dos puntos, y se puede construir el smbolo para una cadena arbitraria precediendo la cadena literal por dos puntos. Se producen las sustituciones en las cadenas entre comillas dobles. Un nombre o cadena en particular siempre va a generar el mismo smbolo, independientemente de cmo se usa ese nombre en el programa. :Object :my_variable :Ruby rules a = cat :catsup -> :#{a}sup -> :#{a}sup ->

:catsup :catsup :\#{a}sup

Otros lenguajes llaman a este proceso internado y a los smbolos tomos.

Expresiones Regulares
Las expresiones regulares literales son objetos de tipo RegExp. Se crean de forma explcita mediante una llamada al constructor Regexp.new o implcitamente mediante el uso de las formas literales, /patrn/ y %r{patrn}. La construccin %r es una forma de entrada delimitada generalizada. /pattern/ /pattern/options %r{pattern} %r{pattern}options Regexp.new( pattern [ , options ] )

Opciones de las Expresiones Regulares


Una expresin regular puede incluir una o ms opciones que modifican la forma en que el patrn coincide con cadenas. Si se utilizan literales para crear el objeto RegExp, entonces las opciones son uno o ms caracteres colocados inmediatamente despus del terminador. Si se utiliza Regexp.new, las opciones son constantes que se utilizan como segundo parmetro del constructor. i Case Insensitive. La comparacin de patrn ignora si las letras del patrn y la cadena son maysculas o minsculas. Poner $= para hacer comparaciones case insensitive est ya en obsoleto. o Sustituir una vez. Cualquier sustitucin #... en una particular expresin regular literal se llevar a cabo slo una vez, la primera vez que se evala. De otra manera, se llevarn a cabo las sustituciones cada vez que el literal genera un objeto RegExp. m Modo multilnea. Normalmente, . Coincide con cualquier carcter excepto con el de nueva lnea. Con la opcin /m, . coincide con cualquier carcter. x Modo extendido. Expresiones regulares complejas pueden ser de dificil lectura. La opcin x le permite insertar espacios, nuevas lneas y comentarios en el patrn para que sea ms legible. Otro conjunto de opciones permiten configurar la codificacin de idioma de la expresin regular. Si no se especifica ninguna de estas opciones, se utiliza la codificacin por defecto del intrprete (definido mediante -K o $KCODE). n: no encoding (ASCII) e: EUC s: SJIS u: UTF-8

216

Patrones de Expresin Regular


caracteres regulares Todos los caracteres excepto ., |, (, ), [, \, ^, {, +, $, *, y ? coinciden con ellos mismos . Para coincidir con uno de estos caracteres, hay precederles con una barra invertida. ^ $ \A \z Coincide con el comienzo de una lnea. Coincide con el final de una lnea. Coincide con el comienzo de una cadena. Coincide con el final de una cadena.

\Z Coincide con el final de la cadena a menos que la cadena termina con un \n, en cuyo caso coincide justo antes del \n. \b, \B \G Coincide con los limites de palabra y no palabra respectivamente. La posicin donde una bsqueda repetitiva anterior se complet (pero slo en algunas situaciones). Consulte la informacin adicional justo ahora ms adelante.

[caracteres] Una expresin con corchetes coincide con cualquiera de los caracteres de la lista entre los corchetes. Los caracteres ., |, (, ), [, \, ^, {, +, $, *, y ? que tienen un significado especial en otros casos en los patrones, pierden su significado especial entre corchetes . Las secuencias \nnn \xnn, \cx, \C-x, \M-x y \M-\C-x tienen el significado que se muestra en la tabla 6 tres pginas atrs. Las secuencias \d, \D, \s, \S, \w, y \W son abreviaturas de grupos de caracteres, como se muestra en la Tabla 2 en la seccin clases caracter. La secuencia [:clase:] coincide con una clase de caracteres POSIX y tambin se muestra en la Tabla 2 (Tenga en cuenta que los corchetes de apertura y cierre son parte de la clase, por lo que el patrn /[_[:dgito:]]/ coincidira con un dgito o un guin bajo). La secuencia de c1-c2 representa todos los caracteres entre c1 y c2, inclusives. Caracteres literales ] o - deben aparecer inmediatamente despus del corchete de apertura. Un carcter de intercalacin (^) inmediatamente despus del corchete de apertura niega el sentido de la coincidencia --el patrn coincide con cualquiercarcter que no est en la clase caracter. \d, \s, \w \D, \S, \W Abreviaturas para las clases caracter que coinciden con dgitos, espacios en blanco y caracteres de palabra, respectivamente. Estas abreviaturas se resumen en la Tabla 2. Las formas de negacin de \d, \s, y \w, coincidiendo con los caracteres que no son dgitos, espacios en blanco o caracteres de palabra.

. (punto) Apareciendo fuera de los corchetes, coincide con cualquier carcter excepto una nueva lnea. (Con la opcin /m, coincidira tambin con salto de lnea). re* Coincide con cero o ms apariciones de re.

re+ Coincide con una o ms apariciones de re. re{m,n} re{m,} re{m} re? Coincide al menos con m y como mximo n apariciones de re. Coincide al menos con m apariciones de re. Coincide exactamente con m apariciones de re. Coincide con cero o una ocurrencia de re. Los modificadores *, + y {m, n} son codiciosospor defecto. Se aade un signo de interrogacin para que sean mnimos.

217

re1|re2 Coincide con re1 o re2. La barra | tiene baja prioridad. (...) Los parntesis se utilizan para agrupar expresiones regulares. Por ejemplo, el patrn /abc+/ coincide con una cadena que contiene una a, ab y una o ms c. /(abc)+/ coincide con una o ms secuencias de abc. Los parntesis tambin se utilizan para recoger los resultados de las coincidencias de patrn. Para cada parntesis de apertura, Ruby almacena el resultado de la coincidencia parcial entre ste y el parntesis de cierrecorrespondiente como grupos sucesivos. Dentro del mismo patrn, \1 se refiere a la coincidencia del primer grupo, \2 del segundo grupo y as sucesivamente. Fuera del patrn, las variables especiales $1, $2 y sucesivos, tienen el mismo propsito. El ancla \G trabaja con los mtodos de comparacin repetitiva String#gsub, String#gsub!, String #index y String#scan. En ua comparacin repetitiva, representa la posicin en la cadena donde se ha dado la ltima coincidencia cuando termina la iteracin. \G apunta inicialmente al comienzo de la cadena (o al caracter referenciado por el segundo parmetro de String#index). a01b23c45 d56.scan(/[a-z]\d+/) a01b23c45 d56.scan(/\G[a-z]\d+/) a01b23c45 d56.scan(/\A[a-z]\d+/) -> -> -> [a01, b23, c45, d56] [a01, b23, c45] [a01]

Sustituciones
#{...} Realiza una sustitucin de expresin, como con cadenas. Por defecto, se lleva a cabo la sustitucin cada vez que se evalua una expresin regular literal. Con la opcin /o se realiza slo la primera vez. \0, \1, \2, ... \9, \&, \`, \, \+ Sustituye el valor comparado con la ensima subexpresin agrupada. O por toda la coincidencia, o por la pre- o postcoincidencia, o por el grupo ms alto.

Extensiones de expresiones regulares


Al igual que en Perl y Python, las expresiones regulares en Ruby ofrecen algunas extensiones sobre las expresiones regulares Unix. Todas las extensiones se introducen entre los caractereses (? y ). Los parntesis que encierran estas extensiones son grupos, pero no generan de vuelta referencias: no establecen los valores de \1 y $1, etc. (?# comentario) Inserta un comentario en el patrn. El contenido se ignora durante la comparacin de patrones. (?:re) Convierte re en un grupo sin generar de vuelta referencias. Esto a menudo es til cuando es necesario agrupar un conjunto de construcciones, pero no se quiere que el grupo establecezca el valor de $1 o lo que sea. En el ejemplo que sigue, los patronescoinciden con una fecha, ya sea con dos puntos o ya sea con espacios entreel mes, el da y el ao. La primera forma almacena el carcter separador en $2 y $4, pero el segundo patrn no almacena el separador en una variable externa. date = 12/25/01 date =~ %r{(\d+)(/|:)(\d+)(/|:)(\d+)} [$1,$2,$3,$4,$5] -> [12, /, 25, /, 01] date =~ %r{(\d+)(?:/|:)(\d+)(?:/|:)(\d+)} [$1,$2,$3] -> [12, 25, 01] (?=re) Compara re en esa posicin, pero no lo usa (tambin encantadoramente conocido como zero-width positive lookahead). Esto le permite buacar hacia adelante en el contexto de una comparacin sin afectar a $&. En este ejemplo, el mtodo scan coincide palabras seguidas de una coma, pero las comas no se incluyen en el resultado.

218

str = red, white, and blue str.scan(/[a-z]+(?=,)/) -> [red, white] (?!re) Compara si re no coincide en esa posicin. No consume la comparacin ( zero-width positive lookahead). Por ejemplo, /hot(?!dog)(\w+)/ coincide con cualquier palabra que contenga las letras hot y no continen con dog, retornando el final de la palabra en $1. (?>re) Anida una expresin regular independiente dentro de la primera expresin regular. Esta expresin se ancla en la posicin de coincidencia actual. Si usa caracteres, estos no estarn ya disponibles para la expresin regular de primer nivel. Esta construccinpor lo tanto, inhibe la marcha atrs y puede producir una mejora de rendimiento. Por ejemplo, el patrn /a.*b.*a/ toma un tiempo exponencial cuando se compara con una cadena que contiene una a seguida de una serie de b, pero sin a final. Sin embargo, esto se puede evitar mediante el uso de la expresin regular anidada /a(?>.*b).*a/. De esta forma, la expresin anidada consume toda la cadena de entrada hasta el ltimo carcter b posible. Cuando el chequeo para una a final no ocurre entonces , no hay necesidad de dar marcha atrs, y la coincidencia de patrn falla de inmediato. require benchmark include Benchmark str = a + (b * 5000) bm(8) do |test| test.report(Normal:) { str =~ /a.*b.*a/ } test.report(Nested:) { str =~ /a(?>.*b).*a/ } end produce: Normal: Nested: user system total real 1.090000 0.000000 1.090000 ( 1.245818) 0.000000 0.000000 0.000000 ( 0.001033)

(?imx) Se convierte en la correspondiente opcon. Si se utiliza dentro de un grupo, el efecto se limita a ese grupo. (?-imx) Desactiva la opcin i, m o x. (?imx:re) Activa la opcin i, m o x para re. (?-imx:re) Desactiva la opcin i, m o x para re.

Nombres
Los nombres Ruby se utilizan para referirse a constantes, variables, mtodos, clases y mdulos. El primer carcter de un nombre ayuda a Ruby a distinguir su propsito. Algunos nombres, que figuran en la Tabla 7 en la pgina siguiente, son palabras reservadas y no se deben utilizar como nombres de mtodo, variable, clase o mdulo. Los nombres para los mtodos se describen en la seccin correspondiente un poco ms adelante.

En las siguientes descripciones, letras minsculas son los carcteres desde la a hasta la z, as como el guin bajo (_). Letras maysculas son los carcteres desde la A hasta la Z y dgitos significa desde el 0 hasta el 9. Un nombre es una letra mayscula, minscula o un guin bajo, seguido por carcteres de nombre: cualquier combinacin de letras maysculas, minsculas, guiones bajos y dgitos. Un nombre de variable local se compone de una letra minscula seguida de caracteres de nombre. Lo normal es utilizar guiones bajos en lugar de camelCase (maysculas y minsculas mezcladas) para escribir nombres con varias palabras, pero no es obligatorio.

219

fred anObject _x three_two_one Un nombre de variable de instancia comienza con una arroba (@) seguida de un nombre. Generalmente, es una buena idea usar una letra minscula despus de la @. @name @_ @size Un nombre de clase comienza con dos arrobas (@@) seguidas de un nombre. Un nombre de constante comienza con una letra mayscula seguida de caracteres de nombre. Los nombres de clases y los nombres de mdulos son constantes y siguen las convenciones de nombres de constante. Por convencin, las referencias a objetos constantes normalmente se escriben con letras maysculas y guiones bajos, mientras que en los nombres de clase y de mdulo se usan maysculas y munsculas.

module Math ALMOST_PI = 22.0/7.0 end class BigBlob end Las variables globales y algunas variables especiales del sistema, comienzan con un signo de dlar ($) seguido por carcteres de nombre. Adems, Ruby define un conjunto de variables globales con nombres de dos caracteres los en los que el segundo caracter es un signo de puntuacion. Estas variables predefinidas se enumeran en su seccin un poco ms adelante. Finalmente, un nombre de variable global se puede formar con $- seguido de una sla letra o un guin bajo. Estas ltimas variables suelen reflejar el establecimiento de la correspondiente opcin de lnea de comandos. $params $PROGRAM $! $_ $-a $-K

Ambiguedad Variable/Mtodo
Cuando Ruby ve un nombre tal como a, en una expresin, es necesario determinar si se trata de una referencia a variable local o una llamada sin parmetros a un mtodo. Para decidir cual es el caso, Ruby utiliza un mtodo heurstico. Cuando Ruby analiza un archivo fuente, realiza un seguimiento de los smbolos que han sido asignados. Se supone que estos smbolos son variables. Cuando posteriormente viene un smbolo que pudiera ser una variable o una llamada a mtodo, comprueba si se ha visto una asignacin anterior a este smbolo. Si es as, se trata el smbolo como una variable, de lo contrario lo trata como una llamada a mtodo. Como un caso de esto un tanto patolgico, considere el siguiente fragmento de cdigo, presentado por Clemens Hintze. def a print Function a called\n 99 end for i in 1..2 if i == 2 print a=, a, \n

220

else a = 1 print a=, a, \n end end produce: a=1 Function a called a=99 Durante el anlisis, Ruby ve el uso de a en la primera sentencia print y, como an no haba visto ninguna asignacin para a, supone que se trata de una llamada a mtodo. En el momento en que llega a la segunda sentencia print, sin embargo, ha visto una asignacinn, por lo que la trata como una variable. Tenga en cuenta que la asignacin no tiene que ser ejecutada --Ruby slo tiene que haberla visto. Este programa no generar un error: a = 1 if false; a

Variables y Constantes
Las variables y las constantes Ruby contienen referencias a objetos. Las variables en si mismas no tienen un tipo intrnseco. En su lugar, el tipo de una variable se define nicamente por los mensajes a los que responde el objeto referenciado por la variable (cuando decimos que una variable es no tipada, queremos decir que cualquier variable dada puede tener en diferentes momentos, referencias a objetos de diferentes tipos). Una constante Ruby es tambin una referencia a un objeto. Las constantes se crean cuando se asignan por primera vez (normalmente en una definicin de clase o mdulo). Ruby, a diferencia de lenguajes menos flexible, permite modificar el valor de una constante, aunque genera un mensaje de advertencia. MY_CONST = 1 MY_CONST = 2 produce: prog.rb:2: warning: already initialized constant MY_CONST Tenga en cuenta que pese a que las constantes no se deben cambiar, se puede alterar el estado interno de los objetos a los que hacen referencia. MY_CONST = Tim MY_CONST[0] = J MY_CONST -> Jim # alter string referenced by constant # generates a warning

Potencialemnte se pueden asignar alias a objetos, dando al mismo objeto diferentes nombres.

Ambito de constantes y Variables


Las constantes definidas dentro de una clase o mdulo pueden ser accedidas sin adornos en cualquier lugar dentro de la clase o mdulo. Fuera de la clase o mdulo, se pueden acceder con el operador de mbito :: prefijado por una expresin que retorna el objeto clase o mdulo apropiado. Las constantes definidas fuera de cualquier clase o mdulo se puede acceder sin adornos, o utilizando el operador de mbito :: sin prefijo. No se pueden definir constantes en los mtodos. Las constantes pueden ser aadidos desde el exterior a las clases y mdulos existentes, utilizando el nombre de la clase o el mdulo y el operador de mbito antes del nombre de constante.

221

OUTER_CONST = 99 class Const def get_const CONST end CONST = OUTER_CONST + 1 end Const.new.get_const -> 100 Const::CONST -> 100 ::OUTER_CONST -> 99 Const::NEW_CONST = 123 Las variables globales estn disponibles a travs del programa. Toda referencia a un nombre global en particular devuelve el mismo objeto. La referencia a una variable global no inicializada devuelve nil. Las variables de clase estn disponibles a travs de una clase o cuerpo de mdulo. Las variables de clase deben inicializarse antes de ser utlizadas. Una variable de clase es compartida por todas las instancias de una clase y est disponible dentro de la propia clase. class Song @@count = 0 def initialize @@count += 1 end def Song.get_count @@count end end Las variables de clase pertenecen a la clase o mdulo ms interior en la que estn. Las variables de clase utilizadas en el nivel superior se definen en Object y se comportan como variables globales. Las variables de clase definida dentro de los mtodos singleton pertenecen al nivel superior (aunque este uso est obsoleto y genera una advertencia). En Ruby 1.9, las variables de clase sern de carcter privado a la clase que la define. class Holder @@var = 99 def Holder.var=(val) @@var = val end def var @@var end end @@var = top level variable a = Holder.new a.var -> 99

Holder.var = 123 a.var -> 123 # This references the top-levelobject def a.get_var @@var end a.get_var -> top level variable Las variables de clase son compartidas para los hijos de la clase en que se definieron por primera vez.

222

class Top @@A = 1 def dump puts values end def values #{self.class.name}: A = #@@A end end class MiddleOne < Top @@B = 2 def values super + , B = #@@B end end class MiddleTwo < Top @@B = 3 def values super + , B = #@@B end end class BottomOne < MiddleOne; end class BottomTwo < MiddleTwo; end Top.new.dump MiddleOne.new.dump MiddleTwo.new.dump BottomOne.new.dump BottomTwo.new.dump produce: Top: A = 1 MiddleOne: MiddleTwo: BottomOne: BottomTwo: A A A A = = = = 1, 1, 1, 1, B B B B = = = = 2 3 2 3

Las variables de instancia estn disponibles dentro de los mtodos de instancia a travs del cuerpo de la clase. La referencia a una variable de instancia sin inicializar retorna nil. Cada instancia de una clase tiene un conjunto nico de variables de instancia. Las variables de instancia no estn disponibles para los mtodos de clase (aunque las clases en si mismas puedan tener variables de instancia --se ver ms adelante). Las variables locales son nicas en sus mbitos que son determinados estticamente, aunque su existencia se haya establecido de forma dinmica. Una variable local se crea dinmicamente la primera vez que se le asigna un valor durante la ejecucin del programa. Sin embargo, el alcance de una variable local es estticamente determinado para el bloque ms inmediato en el que est englobada, ya sea definicin de mtodo, definicin de clase, definicin de mdulo o un programa de nivel superior. Referenciar una variable local para el mismo mbito pero que an no se ha creado an, genera una excepcin NameError. Las variables locales con el mismo nombre son variables diferentes si aparecen en mbitos disjuntos. Los parmetros de mtodo se consideran variables locales a ese mtodo. A los parmetros de bloque se les asignan valores cuando se invoca al bloque. # i local to block # assigns to global $i

a = [ 1, 2, 3 ] a.each {|i| puts i } a.each {|$i| puts $i }

223

a.each {|@i| puts @i } a.each {|I| puts I } a.each {|b.meth| } sum = 0 var = nil a.each {|var| sum += var }

# assigns to instance variable @i # generates warning assigning to constant # invokes meth= in object b # uses sum and var from enclosing scope

Si una variable local (incluido un parmetro de bloque) es asignada primero en un bloque, es local al bloque. Si una variable del mismo nombre ya est establecida en el momento en el bloque se ejecuta, el bloque hereda esa variable. Un bloque toma el conjunto de variables locales que existen en el momento en que se crea. Esto forma parte de su ligadura. Tenga en cuenta que aunque la unin de las variables se fija en este punto, el bloque tiene acceso a los valores en curso de estas variables cuando se ejecuta. La unin preserva estas variables, aunque el mbito englobado original se destruye. Los cuerpos while, until y los bucles for son parte del mbito que los contiene; los locales ya existentes se pueden utilizar en el bucle y cualquier otro nuevo local creado estar disponible despus fuera de los cuerpos.

Variables Predefinidas
Las variables a continuacin estn predefinidas en el intrprete de Ruby. En las siguientes descripciones, la notacin [r/o] indica que las variables son de slo lectura. Se produce un error si un programa intenta modificar una variable de slo lectura. Despus de todo, probablemente no se desee cambiar el sentido de true a mitad de un programa (excepto, quizs, si se es un poltico). Las entradas marcadas [hilo] son subprocesos locales. Muchas variables globales son palabrejas como $_, $!, $&, etc. Esto es por razones histricas, ya que la mayora de estos nombres de variables vienen de Perl. Si usted encuentra difcil memorizar toda esta puntuacion, es posible que desee echar un vistazo al archivo de biblioteca llamado English, documentado mss adelante, que da a las variables de uso global ms comunes nombres ms descriptivos. En las tablas de variables y constantes que siguen, se muestra el nombre de la variable, el tipo del objeto referenciado y una descripcin.

Informacin de excepcin
$! Excepcin El objeto de excepcin pas a raise. [hilo]

$@ Array El trazado de pila generado por la ltima excepcin. Ver Kernel#caller ms adelante para ms detalles. [hilo]

Variables de Coincidencia de Patrones


Estas variables (excepto $=) se ponen a cero despus de una comparacin de patrn sin xito.

$& String La cadena coincidente (siguiendo a una comparacin de patrn con xito). Esta variable es local en el mbito actual. [r/o, hilo] $+ String El contenido del grupo numerado ms alto coincidente tras un comparacin de patrn con xito. As, en cat =~/(c|a)(t|z)/, $+ se establece a t. Esta variable es local en el mbito actual. [r/o, hilo] $` String La cadena previa a una comparacin de patrn con xito. Esta variable es local en el mbito actual. [r/o, hilo] $ String La cadena siguiente a una comparacin de patrn con xito. Esta variable es local en el mbito actual. [r/o, hilo]

224

$= Object Obsoleto. Si se establece en cualquier valor, aparte de nil o false, todas las comparaciones de patrn sern case insensitive, en las comparaciones con cadenas y los valores hash de cadena se ignorarn maysculas y minsculas. $1 to $9 String El contenido de los sucesivos grupos coincidentes en una comparacin de patrn con xito. En cat =~/(c|a)(t|z)/, $1 se ajustar a a y $2 a t. Esta variable es local en el mbito actual. [r/o, hilo] $~ Datos coincidentes Un objeto que encapsula los resultados de una comparacin de patrn con xito . Las variables $&, $`, $, y $1 a $9 se derivan de $~. La asignacin de $~ cambia los valores de estas variables derivadas. Esta variable es local en el mbito actual. [hilo]

Variables de Entrada/Salida
$/ String El separador de registros de entrada (salto de lnea por defecto). Este es el valor que las rutinas como Kernel#gets utilizan para determinar los lmites de registro. Si se establece a nil, gets va a leer todo el archivo. $-0 String Sinnimo de $/.

$, String El separador de cadena de salida entre los parmetros a los mtodos tales como Kernel#print y Array#join. Por defecto a nil que no aade el texto. $. Fixnum El nmero de la ltima lnea a leer del archivo de entrada actual. El separador de patrn predeterminado utilizado por String#split. Se puede configurardesde la lnea de comandos utilizando la opcin -F.

$; String

$< Object Un objeto que permite el acceso a la concatenacin de los contenidos de todos los archivosdados como argumentos de lnea de comandos o $stdin (en el caso de que no haya argumentos). $< soporta los mtodos similares a un objeto File: binmode, close, closed?, each, each_byte, each_line, eof, eof?, file, filename , fileno, getc, gets, lineno, lineno=, path, pos, pos=, read , readchar, readline, readlines, rewind, seek, skip, tell, to_a, to_i, to_io, to_s, junto con los mtodos de Enumerable. El mtodo de fichero retorna un objeto File para el archivo que se est leyendo. [r/o] $> IO El destino de salida para Kernel#print y Kernel#printf. El valor predeterminado es $stdout. $_ String La ltima lnea leda por Kernel#gets o Kernel#readline. Muchas de las funciones relacionadas con cadenas en el mdulo kernel operan en $_ por defecto. La variable es local en el mbito actual. [hilo] $defout IO Sinnimo de $>. Obsoleto: utilizar $stdout.

$deferr IO Sinnimo de STDERR. Obsoleto: utilizar $stderr. $-F String IO IO Sinnimo de $;. La salida de error estndar en curso. La entrada estndar en curso.

$stderr $stdin

$stdout IO La salida estndar actual. La asignacin a $stdout est obsoleta: utilizar $stdout.reopen en su lugar.

225

Variables de entorno de ejecucin


$0 String El nombre del programa de nivel superior Ruby que se est ejecutando. Normalmente, este ser el nombre del archivo del programa. En algunos sistemas operativos, la asignacin a esta variable va a cambiar el nombre del proceso reportado (por ejemplo ) por el comando ps(1). $* Array Una matriz de cadenas que contiene las opciones de lnea de comandos de la invocacindel programa. Las opciones utilizadas por el intrprete Ruby se han eliminado . [r/o] $ $$ $? Array Una matriz que contiene los nombres de los mdulos cargados por require. [r/o]

Fixnum El nmero de proceso del programa que se ejecuta. [r/o]

Process::Status El estado de salida del ltimo subproceso al terminar. [r/o, hilo]

$: Array Una matriz de cadenas, donde cada cadena especifica un directorio para buscar scripts de Ruby y extensiones binarias utilizados por los mtodos load y require. El valor inicial es el valor de los argumentos pasados a travs de la opcin -I de lnea de comandos, seguido de una definicin de localizacin de instalacin de la biblioteca estndar, seguido por el directorio actual (.). Esta variable se puede establecer dentro de un programa para modificar la ruta de bsqueda por defecto. Por lo general, los programas utilizan $: << dir para aadir dir a la ruta. [r/o] $-a $-d Object True si se especifica la opcin -a en la lnea de comandos. [r/o] Object Sinnimo de $DEBUG.

$DEBUG Object Establecido a true si se especifica la opcin -d de lnea de comandos. __FILE__ String El nombre del fichero fuente actual. [r/o]

$F Array La matriz que recibe la lnea de entrada dividida si se utiliza la opcin -a de lnea de comandos. $FILENAME String El nombre del fichero de entrada actual. Equivalente a $<.filename. [r/o] $-i String $-I Array Si el modo edicin est activado (tal vez usando la opcin -i de lnea de comandos), $-i tiene la extensin utilizada al crear el archivo de copia de seguridad. Si establece un valor en $-i, activa el modo de edicin. Sinnimo de $:. [r/o]

$-K String Establece el sistema de codificacin multibyte para las cadenas y expresiones regulares . Equivalente a la opcin -K de lnea de comandos. $-l Object Establece a true si la opcin -l (que permite el procesamiento de fin de lnea) est presente en la lnea de comandos. __LINE__ String $LOAD_PATH Array El nmero de lnea actual en el fichero fuente. [r/o] Sinnimo de $:. [r/o]

$-p Object Establece a true si la opcin -p (que pone un bucle while gets ... end implcitoen torno a su programa) est presente en la lnea de comandos. [r/o] $SAFE Fixnum El nivel de seguridad actual. El valor de esta variable nunca puede ser reducido por asignacin. [hilo]

226

$VERBOSE Object Se establece en true si la opcines -v, --version, -W o -w se especifican en la lnea de comandos. Se establece en false si no hay opcin o se da -W1. Se establece a nil si se ha especificado -W0. El establecer esta opcin en true hace que el intrprete y algunas rutinas de biblioteca para proporcionen informacin adicional. Ajuste a nil suprime todas las advertencias (incluyendo la salida de Kernel.warn). $-v Object Sinnimo de $VERBOSE. $-w Object Sinnimo de $VERBOSE.

Objetos Estndar
ARGF Object Sinnimo de $<.

ARGV Array Sinnimo de $*. ENV Object Un objeto hash que contiene las variables de entorno del programa. Una instancia de la clase Object, ENV implementa el conjunto completo de mtodos Hash. Se utiliza para consultar y establecer el valor de una variable de entorno, como en ENV[PATH] y ENV[term]=ansi. false FalseClass Instancia singleton de la clase FalseClass. [r/o]

nil NilClass Instancia singleton de la clase NilClass. El valor de las instancias y las variables globales no inicializadas. [r/o] self true Object El receptor (objeto) del mtodo actual. [r/o]

TrueClass Instancia singleton del la clase TrueClass. [r/o]

Constantes Globales
Las siguientes constantes estn definidas por el intrprete Ruby.

DATA IO Si el el programa principal contiene la directiva __END__, la constante DATA se iniciar de manera que la lectura de la misma retornar la lneas siguientes de __END__ del archivo de fuente. FALSE FalseClass Sinnimo de false.

NIL NilClass Sinnimo de nil. RUBY_PLATFORM String El identificador de la plataforma de ejecucin del programa. Esta cadena est en la misma forma que el identificador de la plataforma utilizada por la utilidad de configuracin de GNU (no es una coincidencia). RUBY_RELEASE_DATE String La fecha de la liberacin actual. RUBY_VERSION String El nmero de versin del intrprete. STDERR STDIN STDOUT IO IO IO El flujo de error estndar actual del programa. El valor inicial para $stderr. El flujo de entrada estndar actual del programa. El valor inicial para $stdin. El flujo de salida estndar actual del programa. El valor inicial para $stdout. 227

SCRIPT_LINES__ Hash Si se define una constante SCRIPT_LINES__ y hace referencia a un Hash, Ruby va a almacenar una entrada que contenga el contenido de cada archivo que analiza, con el nombre del archivo como clave y una matriz de cadenas como el valor. Vea Kernel.require ms adelante para un ejemplo. TOPLEVEL_BINDING Binding Un objeto Binding que representa la unin en el nivel superior de Ruby --el nivel en el que los programas son ejecutados inicialmente. TRUE TrueClass Sinnimo de true.

La constante __FILE__ y la variable $0 se utilizan a menudo en conjunto para ejecutar cdigo slo si aparece en el archivo ejecutado directamente por el usuario. Por ejemplo, los escritores de librera suelen utilizar esto para incluir pruebas en las bibliotecas que se ejecutarn si la fuente de la biblioteca se ejecuta directamente, pero no si la fuente es necesaria en otro programa. # library code # ... if __FILE__ == $0 # tests... end

Expresiones
Cualquiera de los siguientes pueden ser trminos simples en una expresin: Literal. Lliterales de ruby son nmeros, cadenas, arrays, hashes, rangos, smbolos y expresiones regulares. El comando Shell. Un comando shell es una cadena encerrada entre apstrofes o en un delimitador de cadena general que comienza con %x. El valor de la cadena es la salida estndar de ejecucin del comando representado por la cadena del shell estndar del sistema operativo anfitrin. La ejecucin tambin establece la variable $? con el estado de salida del comando. filter = *.c files = `ls #{filter}` files = %x{ls #{filter}} Generador de smbolos. Un objeto Symbol se crea anteponiendole un nombre de operador, cadena, variable, constante, clase o mdulo y dos puntos. El objeto smbolo es nico para cada diferente nombre, pero no har referncia a una particular instancia del nombre, sino que el smbolo (por ejemplo) :fred ser el mismo independientemente del contexto. Un smbolo es similar al concepto de los tomos en otros lenguajes de alto nivel. Referencia a Variable o Constante. Se hace referencia a una variable citando su nombre. Dependiendo de su mbito, se hace referencia a una constante, ya sea citando su nombre o por la cualificacin del nombre, utilizando el nombre de la clase o mdulo que contiene la constante con el operador de mbito (::). barney # variable reference APP_NAMR # constant reference Math::PI # qualified constant reference Invocacin a Mtodo. Hay varias formas para invocar mtodos. Se describirn un poco ms adelante en su correspondiente seccin.

228

Expresiones Operador
Las expresiones pueden combinarse mediante operadores. La tabla 8 muestran los operadores Ruby por orden de precedencia. Los operadores marcados en la columna Mtodo se implementan como mtodos y se pueden obviar.

Ms sobre la Asignacin
El operador de asignacin asigna uno o ms rvalues (la r significa right (derecha), ya que rvalues tienden a aparecer en la parte derecha de la asignacin) a uno o ms lvalues (valores left). Qu significa la asignacin depende de cada individual lvalue. Si un valor a la izquierda es un nombre de variable o constante, dicha variable o constante recibe una referencia al correspondiente valor a la derecha.

a = /regexp/ b, c, d = 1, cat, [ 3, 4, 5 ] Si el valor a la izquierda es un atributo de objeto, se llamar al mtodo de configuracin de atributo correspondiente en el receptor, pasndole como parmetro el valor a la derecha.

229

obj = A.new obj.value = hello # equivalent to obj.value=(hello) Si el valor de la izquierda es una referencia a una matriz de elementos, Ruby llama al operador de asignacin de elementos ([]=) en el receptor, pasndole como parmetros los elementos indexados que aparezcan entre los corchetes seguidos del valor de la derecha. Esto se ilustra en la siguiente tabla: Referencia a Elementos Llamada al Mtodo Actual obj[] = one obj[1] = two obj[a, /^cat/] = three obj.[]=(one) obj.[]=(1, two) obj.[]=(a, /^cat/, three)

El valor de una expresin de asignacin es el valor a la derecha. Esto es cierto incluso si la asignacin es un mtodo de atributo que devuelve algo diferente.

Asignacin Paralela
Una expresin de asignacin puede tener uno o ms valores a la izquierda y uno o ms valores a la derecha. En esta seccin se explica cmo maneja Ruby la asignacin con diferentes combinaciones de argumentos. Si el valor derecho se precede con un asterisco y se implementa to_ary, es reemplazado por los elementos de la matriz, con cada elemento formando su propio valor derecho. Si la asignacin contiene varios valores a la izquierda y un solo valor a la derecha, este se convierte en un un Array y esta matriz se expande en un conjunto de valores a la derecha, tal como se explica en las pginas 55 y 56. Valores sucesivos a la derecha se asignan a valores a la izquierda . Esta asignacin efectivamente sucede en paralelo, de modo que (por ejemplo) a,b = b,a intercambia los valores de a y b. Si hay ms valores a la izquierda que valores a la derecha , el exceso ser asignado a nil. Si hay ms valores a la derecha que a la izquierda , el exceso ser ignorado.

Estas reglas se modifican ligeramente, si el ltimo valor a la izquierda est precedido por un asterisco. Este valor siempre recibir una matriz durante la asignacin. La matriz consistir de cualquier valor a la derecha que normalmente habra sido asignado a este valor izquierdo, seguido por el exceso de valores a la derecha (si hay). Si el valor a la izquierda contiene una lista entre parntesis, la lista es tratada como una sentencia de asignacin anidada, y se hace la asignacin del valor a la derecha correspondiente como se describe en estas reglas.

Expresiones Bloque
begin body end Las expresiones se pueden agrupar en un bloque begin - end. El valor de la expresin de bloque es el valor de la ltima expresin ejecutada. Las expresiones bloque tambin juegan un papel importante en el manejo de excepciones, que se discutir un poco ms adelante.

230

Expresiones Booleanas
Ruby predefine los valores globales false y nil. Ambos son tratados como si fueran falso en un contexto booleano. Todos los dems valores son tratados como true. La constante true est disponible para cuando se necesita un valor verdadero explcito.

And, Or, Not, y Defined?


Los operadores and y && evaluan su primer operando. Si es falso, la expresin devuelve el valor del primer operando, de lo contrario, la expresin devuelve el valor del segundo operando. expr1 and expr2 expr1 && expr2 Los operadores or y || evaluan su primer operando. Si es verdadero, la expresin devuelve el valor de su primer operando, de lo contrario, la expresin devuelve el valor del segundo operando. expr1 or expr2 expr1 || expr2 Los operadores not y ! evaluan su operando. Si es verdadero, la expresin devuelve false. Si es falso, la expresin devuelve true. Por razones histricas, una cadena, expresion regular o rango no puede aparecer como el nico argumento para not o !. La formas de palabra de estos operadores (AND, OR y NOT) tienen una menor prioridad que las formas smbolo correspondiente (&&, || y !). Vase la Tabla 8 dos pginas atrs para ms detalles. El operador defined? devuelve nil si su argumento, que puede ser una expresin arbitraria, no est definido. De lo contrario, devuelve una descripcin de ese argumento.

Operadores de Comparacin
La sintaxis de Ruby define los operadores de comparacin ==, ===, <=>, <, <=, >, >= y =~. Todos estos operadores se implementan como mtodos. Por convencin, el lenguaje tambin utiliza los mtodos estndar eql? y equal?. Aunque los operadores tienen un significado intuitivo, le corresponde a las clases que los implementan el producir una semntica de comparacin significativa. La referencia de biblioteca ms adelante describe la semntica de comparacin de las clases integradas. El mdulo Comparable proporciona soporte para la implementacin de los operadores ==, <, <=, >, >= y el mtodo between? en trminos de <=>. El operador === se utiliza en expresiones case, que se describen un poco ms adelante. Los operadores == y =~ tienen las formas negadas != y !~. Ruby los convierte durante el anlisis sintctico: a != b es mapeado a !(a == b), y a !~ b es mapeado a !(a =~ b). No hay mtodos para != y !~.

Rangos en las Expresiones Booleanas


if expr1 .. expr2 while expr1 ... expr2 Un rango utilizado en una expresin lgica acta como un flip-flop. Tiene dos estados, establecido y no establecido y est no establecido inicialmente. En cada llamada, el rango ejecuta una transicin en la mquina de estados como se muestra en la Figura 16 en la pgina siguiente. La expresin rango retorna true si la mquina de estados est en estado establecido al final de la llamada, y en caso contrario, retorna false. La forma de dos puntos de un rango se comporta de forma ligeramente diferente que la forma de tres puntos. La forma de dos puntos hace primero la transicin de no establecido a establecido, evala de inmediato la condicin final y hace la transicin en consecuencia. Esto significa que si expr1 y expr2 se evalan ambas como true en la misma llamada, la forma de dos puntos termina la llamada en el estado no establecido. No obstante, todava devuelve true para esta llamada.

231

La forma de tres puntos no hace la evaluacin de la condicin final de inmediato al entrar en el estado establecido. La diferencia se ilustra con el siguiente cdigo:

a = (11..20).collect {|i| (i%4 == 0)..(i%3 == 0) ? i : nil} a -> [nil, 12, nil, nil, nil, 16, 17, 18, nil, 20] a = (11..20).collect {|i| (i%4 == 0)...(i%3 == 0) ? i : nil} a -> [nil, 12, 13, 14, 15, 16, 17, 18, nil, 20]

Expresiones Regulares en Expresiones Booleanas


En las versiones de Ruby anteriores de la 1.8, una expresin regular simple en la expresin booleana se comparaba con el valor en curso de la variable $_. Este comportamiento ahora slo se admite si la condicin aparece en el parmetro de lnea de comandos -e. En cdigo regular, el uso de $_ y operandos implcitos est siendo eliminado poco a poco, por lo que es mejor usar una comparacin explcita con una una variable. Si es necesaria una comparacin con $_, utilice if ~/re/ ... o if $_ =~ /re/ ...

Expresiones if y unless
if boolean-expression [ then | : ] body [ elsif boolean-expression [ then | : ] body , ... ] [ else body ] end unless boolean-expression [ then | : ] body [ else body ] end La palabra clave then (o el signo :) separa el cuerpo de la condicin. No es necesario si el cuerpo comienza en una nueva lnea. El valor de una expresin if o unless es el valor de la ltima expresin evaluada en la ejecucin del cuerpo.

Modificadores if y unless
expression if boolean-expression expression unless boolean-expression

232

Evala la expresin booleana slo si la expresin es verdadera (para if) o falsa (para unless).

Operador Ternario
boolean-expression ? expr1 : expr2 retorna expr1 si la expresin booleana es verdadera y expr2 de lo contrario.

Expresin case
Ruby tiene dos formas de declaracin case. La primera permite una serie de condiciones a evaluar, ejecutando de cdigo correspondiente a la primera condicin si esta es verdadera. case when condition [, condition ]... [ then | : ] body when condition [, condition ]... [ then | : ] body ... [ else body ] end La segunda forma de una expresin case toma una expresin de destino despus de la palabra clave case. Se busca una coincidencia, comenzando en la primera comparacin (arriba a la izquierda), realizando : comparison === destino.

case destino when comparison [, comparison ]... [ then | : ] body when comparison [, comparison ]... [ then | : ] body ... [ else body ] end La comparacin puede ser una referencia a matriz precedida por un asterisco, en cuyo caso se expande en los elementos de dicha matriz antes de que se realicen las pruebas en cada uno. Cuando la comparacin devuelve true, se detiene la bsqueda y se realiza el cuerpo asociado con la comparacin (no es necesario un break). case devuelve entonces el valor de la ltima expresin ejecutada. Si no hay coincidencia con la comparacin: si una clusula else est presente, se ejecuta su cuerpo, de lo contrario, silenciosamente case retorna nil. La palabra clave then (o el signo :) separa las comparaciones cuando (when comparison)de los cuerpos y no es necesario si el cuerpo comienza en una nueva lnea.

Bucles
while boolean-expression [ do | : ] body end Ejecuta el cuerpo, cero o ms veces, siempre y cuando la expresin booleana es verdadera.

until boolean-expression [ do | : ] body end

233

Ejecuta el cuerpo, cero o ms veces, siempre y cuando la expresin booleana es falsa.

En ambas formas, el do o dos puntos separan la expresin booleana del cuerpo y se pueden omitir cuando el cuerpo empieza en una lnea nueva. for name [, name ]... in expression [ do | : ] body end El bucle for se ejecuta como si fuera el siguiente bucle each, excepto que las variables locales definidas en el cuerpo del bucle estarn disponible fuera del mismo, mientras que no lo estarn para las definidas dentro de un bloque iterador. expression.each do | name [, name ]... | body end loop, que itera su bloque asociado, no es una construccin del lenguaje --es un mtodo en el mdulo Kernel. loop do print Input: break unless line = gets process(line) end

Modificadores while y until


expression while boolean-expression expression until boolean-expression Si la expresin es otra cosa que un bloque begin/end, se ejecuta la expresin cero o ms veces mientras que la expresin booleana es verdadera (para while) o falsa (para until). Si la expresin es un bloque begin/end, el bloque se ejecutar siempre al menos una vez.

break, redo, next y retry


break, redo, next, y retry alteran el flujo normal a travs de un while, until, for o un iterador que controla el bucle. break termina el bucle inmediatamente englobado --reanuda el control en la siguiente declaracin del bloque. redo repite el bucle desde el principio, pero sin volver a evaluar la condicin o ir al siguiente elemento (en un iterador). La palabra clave next salta al final del bucle, comienza efectivamente en la siguiente iteracin. retry reinicia el bucle, reevaluando la condicin. Opcionalmente, break y next pueden tener uno o ms argumentos. Si se utiliza dentro de un bloque, el o los argumentos dados se retornan como el valor de produccin. Si se usa dentro de un bloque while, until o for, el valor dado a se retorna el valor dado al break como el valor de la declaracin, y el valor dado al next se ignora silenciosamente. Si no se llama nunca al break, o si se le llama sin ningn valor, el bucle retorna nil. match = while line = gets next if line =~ /^#/ break line if line =~ /ruby/ end match = for line in ARGF.readlines next if line =~ /^#/ break line if line =~ /ruby/ end 234

Definicin de Mtodo
def defname [ ( [ arg [ =val ], ... ] [ , *vararg ] [ , &blockarg ] ) ] body end defname es tanto el nombre del mtodo como opcionalmente, el contexto en el que es vlido.

Un nombre-mtodo es otro operador redefinible (vase la tabla 8) o un nombre. Si nombre-mtodo es un nombre, debe comenzar con una letra minscula (o guin bajo), seguido opcionalmente por letras maysculas y minsculas, guiones bajos y dgitos. Opcionalmente un nombre-mtodo puede terminar con un signo de interrogacin (?), signo de exclamacin (!), o signo igual (=). El signo de interrogacin y de exclamacin son simplemente parte del nombre. El signo de igualdad es tambin parte del nombre, pero, adems, seala que este mtodo puede ser utilizado como un valor a la izquierda que tiene un correspondiente valor asignado a la derecha. Una definicin de mtodo utilizando un nombre sin adornos dentro de una definicin de clase o mdulo crea un mtodo de instancia, que slo puede ser invocado mediante el envo de este nombre a un receptor que es una instancia de la clase que lo define (o una de las subclases de esa clase). Fuera de una definicin de clase o mdulo, una definicin con un nombre de mtodo sin adornos, se aade como un mtodo privado a la clase Object, y por lo tanto puede ser llamado en cualquier contexto sin un receptor explcito. Una definicin que utiliza un nombre de mtodo de la forma constante.nombremetodo o la ms general (expr).nombremetodo crea un mtodo asociado con el objeto que es el valor de la constante o de la expresin. A este mtodo slo se le podr llamar suministrandole como receptor el objeto referenciado por la expresin. Este es el estilo de definicin creada por objeto o mtodo singleton. class MyClass def MyClass.method end end MyClass.method obj = Object.new def obj.method end obj.method def (1.class).fred end Fixnum.fred # definicin # llamada # definicin # llamada # el receptor puede ser una expresin # llamada

Las definiciones de mtodos no pueden contener definiciones de clase o mdulo. Pueden contener definiciones de instancia anidada o de mtodos singleton. Se define un mtodo interno cuando se ejecuta el mtodo que lo contiene. El mtodo interno no acta como un cierre en el contexto del mtodo anidado --es autnomo. def toggle def toggle subsequent times end first time end toggle toggle toggle -> -> -> first time subsequent times subsequent times

El cuerpo de un mtodo acta como si se tratara de un bloque de begin/end, ya que puede contener declaraciones de manejo de excepcin (rescue, else y ensure).

235

Argumentos de Mtodo
Una definicin de mtodo puede tener cero o ms argumentos regulares, un argumento matriz opcional y un argumento bloque opcional. Los argumentos estn separados por comas y se puede encerrar entre parntesis una lista de argumentos. Un argumento regular es un nombre de variable local opcionalmente seguido por un signo igual y una expresin que da un valor por defecto. La expresin se evala en el momento que se llama al mtodo. Las expresiones se evalan de izquierda a derecha y una expresin puede hacer referencia a un parmetro que le precede en la lista de argumentos. def options(a=99, [ a, b ] end options -> options 1 -> options 2, 4 -> b=a+1) [99, 100] [1, 2] [2, 4]

El argumento matriz opcional debe seguir a cualquier argumento regular y no pueden haber uno predeterminado. Cuando se invoca al mtodo, Ruby establece el argumento matriz para que referencie a un nuevo objeto de la clase Array. Si la llamada al mtodo especifica algn parmetro de ms en la cuenta de argumentos regulares, todos estos parmetros adicionales se recogen en esta matriz recin creada. def varargs(a, *b) [ a, b ] end varargs 1 -> varargs 1, 2 -> varargs 1, 2, 3 ->

[1, []] [1, [2]] [1, [2, 3]]

Si un argumento matriz sigue a los argumentos con valores por defecto, los primeros parmetros que se pasen se utilizarn para reemplazar a los valores por defecto. El resto ser utilizado para rellenar la matriz. def mixed(a, b=99, *c) [ a, b, c] end mixed 1 -> [1, mixed 1, 2 -> [1, mixed 1, 2, 3 -> [1, mixed 1, 2, 3, 4 -> [1,

99, []] 2, []] 2, [3]] 2, [3, 4]]

El argumento bloque opcional debe ser el ltima en la lista. Cada vez que se llama a un mtodo, Ruby busca un bloque asociado. Si hay un bloque, se convierte en un objeto de la clase Proc y se asigna al argumento bloque. Si no hay ningn bloque, el argumento se establece a nil. def example(&block) puts block.inspect end example example { a block } produce: nil #<Proc:0x001c9940@:-6>

236

Invocacin de un Mtodo
[ receiver. ] name [ parameters ] [ block ] [ receiver:: ] name [ parameters ] [ block ] parameters <-( [ param, ... ] [ , hashlist ] [ *array ] [ &a_proc ] ) { blockbody } do blockbody end

block <-

Se asignan parmetros iniciales a los argumentos reales del mtodo. Lo siguiente a estos parmetros puede ser una lista de pares clave => valor. Estos pares se recogen en un nico objeto Hash nuevo y se pasan como un parmetro nico. Y siguiendo a estos parmetros puede haber un parmetro nico precedido de un asterisco. Si este parmetro es una matriz, Ruby lo reemplaza con cero o ms parmetros correspondientes a los elementos de la matriz. def regular(a, b, *c) # ... end regular 1, 2, 3, 4 regular(1, 2, 3, 4) regular(1, *[2, 3, 4]) Un bloque puede estar asociado con una llamada al mtodo utilizando ya sea un bloque literal (que debe comenzar en la misma lnea fuente que la ltima lnea de la llamada al mtodo), o ya sea un parmetro que contiene una referencia a un objeto Proc o Method prefijado con un signo &. Independientemente de la presencia de un argumento bloque, Ruby se encarga del valor de la funcin global Kernel.block_given? para reflejar la disponibilidad de un bloque asociado con la llamada. a_proc = lambda { 99 } an_array = [ 98, 97, 96 ] def block yield end block { } block do end block(&a_proc) def all(a, b, c, *d, &e) puts a = #{a.inspect} puts b = #{b.inspect} puts c = #{c.inspect} puts d = #{d.inspect} puts block = #{yield(e).inspect} end all(test, 1 => cat, 2 => dog, *an_array, &a_proc) produce: a = test b = {1=>cat, 2=>dog} c = 98 d = [97, 96] block = 99 Se llama a un mtodo pasando su nombre a un receptor. Si no se especifica un recptor, se asume self. El receptor comprueba la definicin del mtodo en su propia clase y luego sus clases antecesoras de forma secuencial. Los mtodos de instancia de mdulos incluidos actan como si estuvieran en

237

superclases annimas de la clase que los incluye. Si no se encuentra el mtodo, Ruby invoca al mtodo method_missing en el receptor. El comportamiento predeterminado que se define en Kernel.method_ missing es para informar de un error y terminar el programa. Cuando se especifica un receptor explcitamente en una invocacin a mtodo, puede ser separado del nombre del mtodo utilizando un punto . o un signo dos puntos doble ::. La nica diferencia entre estas dos formas se produce si el nombre del mtodo comienza con una letra mayscula. En este caso, Ruby asume que esta llamada a mtodo receptor::Loquesea es realmente un intento de acceder a una constante llamada Loquesea en el receptor a menos que la invocacin al mtodo lleve una lista de parmetros entre parntesis. Foo.Bar() Foo.Bar Foo::Bar() Foo::Bar # # # # llamada a mtodo llamada a mtodo llamada a mtodo acceso a constante

El valor de retorno de un mtodo es el valor de la ltima expresin ejecutada.

return [ expr, ... ] Una expresin return sale inmediatamente de un mtodo. Si se llama sin parmetros, el valor de return es nil. Si se llama con un parmetro es el valor de este parmetro y si se llama con ms de un parmetro es una matriz que contiene todos los parmetros.

super
super [ ( [ param, ... ] [ *array ] ) ] [ block ] Dentro del cuerpo de un mtodo, una llamada a super acta igual que una llamada a ese mtodo original, excepto que la bsqueda de un cuerpo de mtodo se inicia en la superclase del objeto que contiene el mtodo original. Si no se pasan parmetros a super (y no hay parntesis), se pasarn los parmetros del mtodo original, de lo contrario, se pasarn los parmetros de super.

Mtodos de Operador
expr1 operator operator expr1 expr1 operator expr2 Si el operador en una expresin de operador corresponde a un mtodo redefinible (vase la tabla 8), Ruby ejecutar la expresin de operador como si se hubiera escrito (expr1).operator(expr2)

Asignacin de Atributos
receptor.nombreattr = rvalue Cuando la forma receptor.nombreattr aparece como un valor a la izquierda, Ruby invoca a un mtodo llamado nombreattr= en el receptor, pasandole el valor a la derecha como un nico parmetro. El valor devuelto por esta asignacin es siempre el valor derecho --se descarta el valor de retorno del mtodo nombreatrr=. Si desea acceder el valor de retorno (en el improbable caso de que ste no sea el valor derecho), enve un mensaje explcito al mtodo. class Demo attr_reader :attr def attr=(val) @attr = val return value

238

end end d = Demo.new # En todos estos casos, d.attr = 99 -> d.attr=(99) -> d.send(:attr=, 99) -> d.attr -> @attr es establecido a 99 99 99 return value 99

Operador de Referencia a Elemento


receptor[ expr [, expr ]... ] receptor[ expr [, expr ]... ] = rvalue Cuando se utiliza como un valor a la derecha, el elemento de referencia invoca el mtodo [] en el receptor, pasando como parmetros las expresiones entre parntesis. Cuando se utiliza como un valor a la izquierda, el elemento de referencia invoca al mtodo []= en el receptor, pasando como parmetros las expresiones entre parntesis, seguido por el valor asignado a la derecha.

Alias
alias new_name old_name Crea un nombre nuevo que se refiere a un mtodo, operador, variable global o referencia a expresin regular ($&, $`, $ y $+) ya existentes. Las variables locales, variables de instancia, variables de clase, y las constantes no pueden ser un alias. Los parmetros para alias pueden ser nombres o smbolos. class Fixnum alias plus + end 1.plus(3) -> alias $prematch $` string =~ /i/ -> $prematch -> alias :cmd :` cmd date ->

4 3 str Thu Aug 26 22:37:16 CDT 2004\n

Cuando se crea un alias de un mtodo, el nombre nuevo se refiere a una copia del cuerpo del mtodo original. Si se redefine el mtodo posteriormente, el alias an invoca a la implementacin original. def meth original method end alias original meth def meth new and improved end meth -> new and improved original -> original method

Definicin de Clase
class [ scope:: ] classname [ < superexpr ] body end

239

class << obj body end Una definicin de clase crea o extiende un objeto de la clase Class mediante la ejecucin del cdigo en el cuerpo. En la primera forma, se crea o extiende una clase llamada class. El Objeto Class resultante se asigna a una constante llamada nombreclase (ver ms abajo las normas de ambito). Este nombre debe comenzar con una letra mayscula. En la segunda forma, una clase annima (singleton) se asocia con el objeto especfico. Si est presente, superexpr debera ser una expresin que se evala como un objeto Class que ser la superclase de la clase que se define. Si se omite, es de la clase Object por defecto. Dentro del cuerpo, la mayora de las expresiones Ruby se ejecutan como la definicin que se lee. Sin embargo: Las definiciones de mtodo registrarn los mtodos en una tabla en la clase del objeto.

Las definiciones de clase y mdulo anidadas se guardarn en constantes dentro de la clase, pero no sonconstantes globales. Estas clases y mdulos anidados se pueden acceder desde fuera de la clase que las define utilizando :: para calificar sus nombres. module NameSpace class Example CONST = 123 end end obj = NameSpace::Example.new a = NameSpace::Example::CONST El mtodo Module#include aade los mdulos nombrados como superclases annimas de la clase que se define. En una definicin de clase, el nombreclase puede ser precedido por los nombres de las clases o mdulos existentes utilizando el operador de mbito ( ::). Esta sintaxis inserta la nueva definicin en el espacio de nombres del mdulo o mdulos, clase o clases prefijados, pero no interpreta la definicin en el mbito de estas clases externas. Un nombreclase con un operador inicial de mbito coloca a la clase o mdulo en el mbito de nivel superior. En el ejemplo siguiente, la clase C se inserta en el espacio de nombres del mdulo A, pero no se interpreta en el contexto de A. Como resultado de ello, la referencia a CONST se resuelve a la constante de nivel superior de ese nombre. Tambin hay que calificar completamente el nombre del mtodo singleton, ya que C por s sola no es una constante conocida en el contexto de A::C. CONST = outer module A CONST = inner # This is A::CONST end module A class B def B.get_const CONST end end end A::B.get_const class A::C -> inner

240

def (A::C).get_const CONST end end A::C.get_const -> outer

Vale la pena destacar que una definicin de clase es el cdigo ejecutable. Muchas de las directrices utilizadas en la definicin de clase (como attr e include) son en realidad simples mtodos privados de instancia de la clase Module (documentada ms adelante). El captulo sobre Clases y Objetos que comienza un poco ms adelante, describe con ms detalle cmo los objetos Class interactuan con el resto del entorno.

Creacin de Objetos de Clases


obj = classexpr.new [ ( [ args, ... ] ) ] La clase de Class define el mtodo de instancia Class#new, que crea un objeto de la clase del receptor (classexpr en el ejemplo de sintaxis). Esto se hace llamando al mtodo classexpr.allocate. Se puede obviar este mtodo, pero su implementacin debe devolver un objeto de la clase correcta. A continuacin se invoca a initialize en el objeto recin creado pasndole a new los argumentos originales. Si una definicin de clase anula el mtodo de clase new sin llamar a super, no se pueden crear los objetos de esa clase y las llamadas a new silenciosamente retornaran nil. Al igual que cualquier otro mtodo, initialize debe llamar a super si se quiere garantizar que las clases padres han sido correctamente inicializadas. Esto no es necesario cuando el padre es Object, ya que la clase Object no hace inicializacin de una especfica instancia.

Declaraciones de Atributos de Clase


Las declaraciones de atributos de clase no forman parte de la sintaxis de Ruby: son simplemente mtodos definidos en la clase Module que crea mtodos accesores de forma automtica. class name attr attribute [ , writable ] attr_reader attribute [, attribute ]... attr_writer attribute [, attribute ]... attr_accessor attribute [, attribute ]... end

Definiciones de Mdulo
module name body end Un mdulo es bsicamente una clase que no puede ser instanciada. Al igual que una clase, su cuerpo se ejecuta durante la definicin y el objeto Module resultante se almacena en una constante. Un mdulo puede contener mtodos de clase y de instancia y puede definir constantes y variables de clase. Al igual que con las clases, los mtodos de mdulo se invoca utilizando el objeto Module como un receptor y las constantes son accesibles usando el operador :: de resolucin de mbito. El nombre de una definicin de mdulo opcionalmente puede ser precedido por los nombres de la clase o clases y/o mdulo o mdulos que lo engloban. CONST = outer module Mod CONST = 1

241

def Mod.method1 # module method CONST + 1 end end module Mod::Inner def (Mod::Inner).method2 CONST + scope end end Mod::CONST -> 1 Mod.method1 -> 2 Mod::Inner::method2 -> outer scope

Mixins --Incluyendo los Mdulos


class|module name include expr end Un mdulo puede ser incluido en la definicin de otro mdulo o clase utilizando el mtodo include. El mdulo o clase que contiene el include gana el acceso a las constantes, variables de clase y los mtodos de instancia del mdulo que incluye. Si un mdulo se incluye dentro de una definicin de clase, las constantes del mdulo, las variables de clase y los mtodos de instancia son efectivamente ligados en un annima (e inaccesible) superclase de esa clase. Los objetos de la clase respondern a los mensajes enviados a los mtodos de instancia del mdulo. Las llamadas a mtodos no definidos en la clase se pasan al mdulo (o mdulos) mezclado en la clase, antes de ser enviados a cualquier clase padre. Un mdulo puede optar por definir un mtodo initialize , que llamar a la creacin de un objeto de una clase mezclada con el mdulo si: (a) la clase no define su mtodo initialize propio, o (b) el mtodo initialize de la clase invoca a super. Un mdulo puede ser incluido tambin en el nivel superior, en cuyo caso las constantes, las variables de clase y los mtodos de instancia del mdulo estarn disponibles en el nivel superior.

Funciones de Mdulo
A pesar de que include es til para proporcionar la funcionalidad de mixin, tambin es una forma de llevar las constantes, variables de clase y los mtodos de instancia de un mdulo, a otro espacio de nombres. Sin embargo, la funcionalidad definida en un mtodo de instancia no estar disponible como un mtodo de mdulo. module Math def sin(x) # end end # La nica forma de acceder a Math.sin es... include Math sin(1) El mtodo Module#module_function resuelve este problema tomando uno o ms mtodos de instancia del mdulo y copiando sus definiciones en los correspondientes mtodos de mdulo. module Math def sin(x) # end module_function :sin end Math.sin(1)

242

include Math sin(1) El mtodo de instancia y el mtodo de mdulo son dos mtodos diferentes: la definicin del mtodo ha sido copiada por module_function, no es un alias.

Control de Acceso
Ruby define tres niveles de proteccin para las constantes y mtodos de clase y de mdulo: Pblico. Accesibles a cualquiera. Protegido. Slo pueden ser invocados por los objetos de la clase que define y sus subclases.

Privado. Slo se pueden llamar en forma funcional (es decir, con un self implcito como receptor). Los mtodos privados por lo tanto, slo se pueden llamar en la clase que define y por los descendientes directos dentro del mismo objeto. Vase la descripcin que comienza en la pgina 17 para ver ejemplos. private [ symbol, ... ] protected [ symbol, ... ] public [ symbol, ... ] Cada funcin se puede utilizar de dos maneras diferentes.

1. Si se utiliza sin argumentos, las tres funciones establecen el control de acceso por defecto de los mtodos definidos posteriormente. 2. Con argumentos, las funciones establecen el control de acceso de los mtodos y constantes nombrados. El control de acceso se aplica cuando se invoca un mtodo.

Bloques, Cierres y Objetos Proc


Un bloque de cdigo es un conjunto de declaraciones y expresiones Ruby entre llaves o entre un par do/end. El bloque puede comenzar con una lista de argumentos entre barras verticales. Un bloque de cdigo slo puede aparecer inmediatamente despus de una invocacin de mtodo. El inicio del bloque (la llave o el do) debe estar en la misma lnea lgica que el final de la invocacin. invocation do | a1, a2, ... | end invocation { | a1, a2, ... | } Las llaves tienen prioridad alta y el do tiene baja prioridad. Si la invocacin del mtodo tiene parmetros que no se estn entre parntesis, la forma de bloque entre llaves se unir al ltimo parmetro, no a la invocacin total. La forma do se unir a toda la invocacin. Dentro del cuerpo del mtodo invocado, se puede llamar al bloque de cdigo con la palabra clave yield. Los parmetros pasados a yield se asignarn a los argumentos en el bloque. Se generar una advertencia si yield pasa varios parmetros a un bloque que tiene slo uno. El valor de retorno de yield es el valor de la ltima expresin evaluada en el bloque o el valor pasado a la sentencia next ejecutada en el bloque. Un bloque es un cierre; recuerda el contexto en el que se define y utiliza ese contexto cada vez que se le llama. El contexto incluye el valor de self, las constantes, las variables de clase, las variables locales y de cualquier bloque capturado.

243

class Holder CONST = 100 def call_block a = 101 @a = 102 @@a = 103 yield end end class Creator CONST = 0 def create_block a = 1 @a = 2 @@a = 3 proc do puts a = #{a} puts @a = #@a puts @@a = #@@a puts yield end end end block = Creator.new.create_block { original } Holder.new.call_block(&block) produce: a = 1 @a = 2 @@a = 3 original

Objetos Proc, break y next


Los bloques de Ruby son trozos de cdigo asociados a un mtodo que operan en el contexto del llamador. Los bloques no son objetos pero pueden ser convertidos en objetos de la clase Proc. Hay tres maneras de convertir un bloque en un objeto Proc. 1. Pasando el bloque a un mtodo cuyo ltimo parmetro se precede con un signo &. Ese parmetro recibe el bloque como un objeto Proc. def meth1(p1, p2, &block) puts block.inspect end meth1(1,2) { a block } meth1(3,4) produce: #<Proc:0x001c9940@-:4> nil 2. Llamando Proc.new, asocindole de nuevo con un bloque

block = Proc.new { a block } block -> #<Proc:0x001c9ae4@-:1> 3. Al llamar al mtodo Kernel.lambda (o el equivalente Kernel.proc) por la asociacin de un

244

bloquecon la llamada. block = lambda { a block } block -> #<Proc:0x001c9b0c@-:1> Los dos primeros estilos de objeto Proc son idnticos en su uso. Vamos a llamar a estos objetos procs crudos. El tercer estilo, generado por lambda, aade algunas funciones adicionales al objeto Proc, como veremos en un minuto. Llamaremos a estos objetos lambdas. Dentro de cada tipo de bloque, la ejecucin de next causa la salida del bloque. El valor del bloque es el valor (o valores) pasados a next o nil si no se pasan valores. def meth res = yield The block returns #{res} end meth { next 99 } -> The block returns 99

pr = Proc.new { next 99 } pr.call -> 99 pr = lambda { next 99 } pr.call -> 99 Dentro de un proc crudo, el break termina el mtodo que invoc el bloque. El valor de retorno del mtodo son los parmetros pasados a break.

Bloques y Retorno
Un retorno desde el interior de un bloque que an est en en un mbito acta como un retorno de ese mbito. El retorno de un bloque cuyo contexto original ya no es vlido lanza una excepcin (LocalJumpError o ThreadError dependiendo del contexto). El siguiente ejemplo ilustra el primer caso. def meth1 (1..10).each do |val| return val end end meth1 -> 1

# returns from method

Este ejemplo muestra un retorno fallido porque el contexto de su bloque ya no existe.

def meth2(&b) b end res = meth2 { return } res.call produce: prog.rb:5: unexpected return (LocalJumpError) from prog.rb:5:in `call from prog.rb:6 Y aqu hay un retorno fallido porque el bloque se crea en un hilo y la llamada en otro.

def meth3 yield end

245

t = Thread.new do meth3 { return } end t.join produce: prog.rb:6: return cant jump across threads (ThreadError) from prog.rb:9:in `join from prog.rb:9 La situacin de los objetos Proc es un poco ms complicada. Si utiliza Proc.new para crear un proc de un bloque, este proc acta como un bloque y se aplican las reglas anteriores. def meth4 p = Proc.new { return 99 } p.call puts Never get here end meth4 -> 99

Si el objeto Proc es creado usando Kernel.proc o Kernel.lambda, se comporta ms como un cuerpo de mtodo independiente: un return simplemente retorna desde el bloque a la llamada del bloque. def meth5 p = lambda { return 99 } res = p.call The block returned #{res} end meth5 -> The block returned 99

Debido a esto, si se utiliza Module#define_method, es probable que se le quiera pasar un proc creado con lambda, no con Proc.new, ya que el retorno funcionar como se espera en el primero y generar un LocalJumpError en el segundo.

Excepciones
Las excepciones Ruby son objetos de la clase Exception y sus descendientes (la lista completa de las excepciones integradas se d ms adelante).

Levantamiento de Excepciones
El mtodo Kernel.raise lanza una excepcin.

raise raise string raise thing [ , string [ stack trace ] ] La primera forma resube la excepcin en $! o una nueva RuntimeError si $! es nil.

La segunda forma crea una nueva excepcin RuntimeError, estableciendo su mensaje en la cadena dada. La tercera forma crea un objeto excepcin invocando al metodo exception en su primer argumento. A continuacin, establece el mensaje de esta excepcin y hace la traza de sus argumentos segundo y tercero.

246

La clase Exception y los objetos de esta clase contienen un mtodo de fbrica denominado exception , por lo que el mismo nombre de clase o instancia se puede utilizar como primer parmetro para levantar una excepcin. Cuando se lanza una excepcin, Ruby coloca una referencia al objeto excepcion en la variable global $!.

Manejo de Excepciones
Las excepciones se pueden manejar: en el mbito de un bloque begin/end,

begin code... code... [ rescue [ parm, ... ] [ => var ] [ then ] error handling code... , ... ] [ else no exception code... ] [ ensure always executed code... ] end dentro del cuerpo de un mtodo

def method and args code... code... [ rescue [ parm, ... ] [ => var ] [ then ] error handling code... , ... ] [ else no exception code... ] [ ensure always executed code... ] end y despus de la ejecucin de una sentencia nica.

statement [ rescue statement, ... ] Un bloque o mtodo puede tener varias clusulas de rescate, y cada clusula de rescate puede especificar cero o ms parmetros de excepcin. Una clusula de rescate sin parmetros se trata como si fuera un parmetro de StandardError. Esto significa que algunas excepciones de menor nivel no sern capturadas por una clase rescue sin parmetros. Si se quiere rescatar todas las excepciones hay que utilizar rescue Exception => e Cuando se lanza una excepcin, Ruby escanea la pila de llamadas hasta que encuentra un bloque begin/end, un cuerpo de mtodo o la declaracin con un modificador de rescate. Para cada clusula de rescate en ese bloque, Ruby compara la excepcin levantada contra cada uno de los parmetros de la clusula de rescate. Cada parmetro se pone a prueba utilizando parmetro===$!. Si la excepcin planteada coincide con un parmetro de rescue, Ruby ejecuta el cuerpo del rescue y deja de buscar. Si una clusula de rescate encontrada termina con => y un nombre de variable, la variable se establece en $!. Aunque los parmetros de la clusula de rescate suelen ser los nombres de las clases Exception, en realidad pueden ser expresiones arbitrarias (incluyendo llamadas a mtodos) que retornan una clase apropiada.

247

Si no se encuentra clusula de rescate coincidente con la excepcin levantada, Ruby se mueve hacia arriba de la pila buscando un bloque begin/end de nivel superior que coincida. Si una excepcin se propaga al nivel superior del hilo principal sin ser rescatada, el programa termina con un mensaje. Si est presente una clusula else, se ejecuta su cuerpo si no se plantearon excepciones en el cdigo. Las excepciones lanzadas durante la ejecucin de la clusula else no son capturadas por clusulas de rescate en el mismo bloque del else. Si est presente una clusula ensure, se ejecuta siempre su cuerpo cuando se abandona el bloque (incluso si hay una excepcin no capturada en proceso de propagarse). Dentro de una clusula rescue, raise sin parmetros relanza la excepcin en $!.

Modificadores de Rescate de Declaracin


Una declaracin puede tener un modificador rescue opcional seguido de otra sentencia (y por extensin un nuevo modificador rescue y as sucesivamente). El modificador rescue no tiene parmetro de excepcin y rescata StandardError y sus subprocesos hijos. Si se lanza una excepcin a la izquierda de un modificador rescue, se abandona la declaracin de la izquierda y el valor de la lnea total es el valor de la declaracin de la derecha. values = [ 1, 2.3, /pattern/ ] result = values.map {|v| Integer(v) rescue Float(v) rescue String(v) } result -> [1, 2.3, (?-mix:pattern)]

Intentando de nuevo un bloque


La declaracin retry se puede utilizar dentro de una clusula rescue para reiniciar el bloque begin/end englobado desde el principio.

Agarrar y Tirar
El mtodo Kernel.catch ejecuta su bloque asociado. catch ( symbol | string ) do block... end El mtodo Kernel.throw interrumpe el proceso normal de una sentencia.

throw( symbol | string [ , obj ] ) Cuando se ejecuta throw, Ruby busca en la pila de llamadas el primer bloque catch con un smbolo o cadena coincidente. Si se encuentra, la bsqueda se detiene y se reanuda la ejecucin ms all del final del bloque de la capturado. Si al throw se le pas un segundo parmetro, se devuelve ese valor como el valor del catch. Ruby hacer honor a las clusulas ensure de cualquier bloque de expresiones que atraviesa en la bsqueda de un correspondiente catch. Si no hay ningn bloque catch que coincide con el throw, Ruby lanza una excepcin NameError en la ubicacin del throw.

Duck Typing
Usted habr notado que en Ruby no se declaran los tipos de variables o de mtodos --todo es simplemente un tipo de objeto.

248

Ahora, parece que la gente reacciona a esto de dos maneras. A algunos les gusta este tipo de flexibilidad y se sienten cmodos escribiendo cdigo con variables y mtodos de tipados dinmicamente. Si eres uno de estos, es posible que desees pasar a la seccin llamada Clases no son Tipos en la pgina siguiente. Algunos sin embargo, se ponen nerviosos cuando piensan en todos los objetos que flotan alrededor sin restricciones. Si usted ha llegado a Ruby a partir de un lenguaje como C# o Java, en los que estamos acostumbrados a dar a todas los mtodos y variables un tipo, puede sentir que Ruby es demasiado descuidado para utilizarlo en la escritura real de aplicaciones. No lo es.

Nos gustara pasar un par de prrafos tratando de convencerle de que la falta de tipos estticos no es un problema cuando se trata de escribir aplicaciones confiables. No estamos tratando de criticar a otros lenguajes aqu. En su lugar, slo dar un contraste de enfoques. La realidad es que los sistemas de tipo esttico en la mayora de los principales lenguajes, no ayudan mucho en trminos de seguridad del programa. Si el sistema de tipos Java fuera fiable, por ejemplo, no sera necesario implementar ClassCastException. La excepcin es necesaria, sin embargo, porque en Java hay incertidumbre de tipo en tiempo de ejecucin (como lo hay en C++, C# y otros). El tipado estticos puede ser bueno para la optimizacin de cdigo, y puede ayudar a los IDEs a hacer cosas inteligentes con herramientas de informacin, pero no hemos visto mucha evidencia de que promueva un cdigo ms seguro. Por otro lado, una vez que se utiliza Ruby por un tiempo, se cae en cuenta de que el tipado dinmico de las variables en realidad agrega productividad de muchas maneras. Tambin se sorprende uno al descubrir que sus temores sobre el caos de tipos eran infundados. Grandes y de larga duracin, los programas Ruby ejecutan aplicaciones importantes y simplemente no arrojan ningn tipo de error relacionado. Por qu es esto? En parte, es una cuestin de sentido comn. Si se ha codificado en Java (Java pre 1.5), todos los contenedores efectivamente no eran tipados: todo en un contenedor era slo un Object, y se convierta al tipo necesario en la extraccin de un elemento. Y sin embargo, no se mostraba un ClassCastException al ejecutar estos programas. La estructura del cdigo simplemente no lo permite: se ponen en objetos Person y ms tarde se toman o se llevan a cabo los objetos Person. Simplemente no se escriben programas que trabajan de otra manera. Bueno, es igual en Ruby. Si se utiliza una variable para algn propsito, hay muchas posibilidades de que se estar usando para la misma finalidad cuando se acceda de nuevo tres lneas ms adelante. El tipo de caos que podra producirse simplemente no ocurre. Adems de eso, mucha de la gente que codifica en Ruby tiende a adoptar un cierto estilo de codificacin. Escriben un montn de mtodos en corto y los prueban sobre la marcha. Mtodos en corto significa que el alcance de la mayora de las variables es limitado: simplemente no hay mucho tiempo para que las cosas vayan mal con su tipo. Y las pruebas capturan los errores tontos cuando suceden: errores ortogrficos y dems simplemente no tienen la oportunidad de propagarse a travs del cdigo. El resultado es que la seguridad en la seguridad de tipos es a menudo ilusoria y que la codificacin en un lenguaje ms dinmico como Ruby es segura y productiva. Por lo tanto, si nos hemos puesto nerviosos por la falta de tipos estticos en Ruby, le sugerimos que trate de poner estas preocupaciones en un segundo plano por un tiempo para darle una oportunidad a Ruby. Creemos que usted se sorprender de cun raramente aparecen errores debido a problemas de tipo, y en lo productivo que ser una vez que comience a explotar el poder del tipado dinmico.

Clases no son Tipos


La cuestin de los tipos es en realidad algo ms profunda que un debate entre los defensores del tipado fuerte y la gente hippie-freak del tipado dinmico. El verdadero problema es la cuestin de que es un tipo en primer lugar? Si usted ha estado programando en lenguajes con tipos convencionales, lo que le ha sido enseado es que el tipo de un objeto es su clase --todos los objetos son instancias de alguna clase y la clase es el tipo

249

de objeto. La clase define las operaciones (mtodos) que el objeto soporta, junto con el estado (variables de instancia) en la que los mtodos funcionan. Vamos a ver algo de cdigo Java. Customer c; c = database.findCustomer(dave); /* Java */

En este fragmento se declara la variable c para ser de tipo Customer, y se establece como referencia el objeto customer para Dave que hemos creado a partir de un registro de base de datos. Por lo que el tipo de objeto en c es Customer, verdad? Tal vez. Sin embargo, incluso en Java, el tema es un poco ms profundo. Java soporta el concepto de interfaces, que son una especie de clase base abstracta mutilada. Una clase Java puede ser declarada como la implementacin de mltiples interfaces. Al utilizar esta caracterstica, es posible que se hayan definido las clases de la siguiente manera: public interface Customer { long getID(); Calendar getDateOfLastContact(); // ... } public class Person implements Customer { public long getID() { ... } public Calendar getDateOfLastContact() { ... } // ... } As que incluso en Java, la clase no siempre es el tipo --a veces el tipo es un subconjunto de la clase, y en ocasiones objetos implementan varios tipos. En Ruby, la clase no es (bueno, casi nunca) el tipo. En cambio, el tipo de un objeto se define ms por lo que ese objeto puede hacer. En Ruby, llamamos a esto duck typing. Si un objeto camina como un pato y habla como un pato, entonces, el intrprete se siente feliz de tratarlo como si fuera un pato. Veamos un ejemplo. Tal vez hemos escrito un mtodo para escribir el nombre de nuestros clientes al final de un archivo abierto. class def end def end end Customer initialize(first_name, last_name) @first_name = first_name @last_name = last_name append_name_to_file(file) file << @first_name << << @last_name

Para ser buenos programadores, vamos a escribir una prueba unitaria para esto. Hay que ser prevenido --es complicado (pero vamos a mejorar en breve). require test/unit require addcust class TestAddCustomer < Test::Unit::TestCase def test_add c = Customer.new(Ima, Customer) f = File.open(tmpfile, w) do |f| c.append_name_to_file(f) end f = File.open(tmpfile) do |f| assert_equal(Ima Customer, f.gets)

250

end ensure File.delete(tmpfile) if File.exist?(tmpfile) end end produce: Finished in 0.003473 seconds. 1 tests, 1 assertions, 0 failures, 0 errors Tenemos que hacer todo este trabajo para crear un archivo para escritura, volverlo a abrir y leer el contenido para verificar que se ha escrito la cadena correcta. Tambin tenemos que eliminar el archivo cuando se haya terminado (pero slo si existe). En su lugar, sin embargo, podemos confiar en el tipado pato. Todo lo que necesitamos es algo que camina como un archivo y habla como un archivo que puede pasar al mtodo bajo prueba. Y todo esto significa que en esta circunstancia necesitamos un objeto que responde al mtodo << aadiendo algo. Tenemos algo que hace esto? Qu tal un humilde String? require test/unit require addcust class TestAddCustomer < Test::Unit::TestCase def test_add c = Customer.new(Ima, Customer) f = c.append_name_to_file(f) assert_equal(Ima Customer, f) end end produce: Finished in 0.001951 seconds. 1 tests, 1 assertions, 0 failures, 0 errors El mtodo bajo prueba piensa que est escribiendo en un archivo, pero en su lugar simplemente est aadiendo a una cadena. Al final, podemos entonces slo prober que el contenido es correcto. No tenemos que usar una cadena --para el objeto que estamos probando aqu, una matriz va a funcionar igual de bien. require test/unit require addcust class TestAddCustomer < Test::Unit::TestCase def test_add c = Customer.new(Ima, Customer) f = [] c.append_name_to_file(f) assert_equal([Ima, , Customer], f) end end produce: Finished in 0.001111 seconds. 1 tests, 1 assertions, 0 failures, 0 errors De hecho, esta forma puede ser ms conveniente si queremos comprobar que las cosas individuales se insertan correctamente.

251

Por tanto, el tipado pato es conveniente para las pruebas, pero, qu pasa en el cuerpo de las propias aplicaciones? Bueno, resulta que lo mismo que hizo fcil las pruebas del ejemplo anterior tambin hace fcil la escritura de cdigo de aplicacin flexible. De hecho, Dave tuvo una experiencia interesante donde el tipado pato le sac (y a un cliente) de un agujero. Haba escrito una gran aplicacin Ruby basada en Web que (entre otras cosas) mantiene una tabla de base de datos completa de los detalles de los participantes en una competicin. El sistema proporciona valores separados por comas (CSV) con la capacidad de poderlos descargar, lo que permite a los administradores importar esta informacin en sus hojas de clculo de forma local. Justo antes de la competicin, el telfono empieza a sonar. La descarga, que haba estado trabajando muy bien hasta ese momento, estaba tomando tanto tiempo que las solicitudes agotaban el tiempo de espera. La presin fue intensa, ya que los administradores tenan que usar esa informacin para hacer planificaciones y enviar correos. Un poco de experimentacin demostr que el problema estaba en la rutina que tomaba los resultados de la consulta de la base de datos y generaba la descarga CSV. El cdigo se vea algo as como def csv_from_row(op, row) res = until row.empty? entry = row.shift.to_s if /[,]/ =~ entry entry = entry.gsub(//, ) res << << entry << else res << entry end res << , unless row.empty? end op << res << CRLF end result = query.each_row {|row| csv_from_row(result, row)} http.write result Cuando este cdigo corre con conjuntos de datos de tamao moderado, desempea bien. Pero a un tamao de entrada determinado, se desacelera de repente y se viene abajo. El culpable? La recoleccin de basura. El enfoque estaba generando miles de cadenas intermedias y la construccin de una gran cadena de resultado, una lnea a la vez. Como esta gran cadena crece, se necesita ms espacio y se invoca la recoleccin de basura, que requiere el escaneo y eliminacin de todas las cadenas itermedias. La respuesta era simple y sorprendentemente efectiva. En lugar de construir la cadena de resultado a medida que avanzaba, el cdigo se cambi para almacenar cada fila CSV como un elemento de una matriz. Esto significaba que las lneas intermedias seguian siendo todava referenciadas y por lo tanto ya no eran basura. Tambin significaba que ya no se contrua una gran cadena cada vez mayor que obligaba a la recoleccin de basura. Gracias al tipado pato, el cambio fue trivial. def csv_from_row(op, row) # como antes end result = [] query.each_row {|row| csv_from_row(result, row)} http.write result.join Lo nico que cambia es que pasamos una matriz en el mtodo csv_from_row. Debido a que (implcitamente) utilizamos tipado pato, el mtodo en s no fu modificado: continua aadiendo los datos que genera a su parmetro, sin importarle el tipo de parmetro que es. Despus que el mtodo devuelve el resultado, se unen todas las lneas individuales en una gran cadena. Este cambio trajo una reduccin del tiempo de ejecucin desde ms de 3 minutos a unos pocos segundos.

252

Codificando Como un Pato


Si desea escribir sus programas con la filosofa tipado pato, slo se necesita recordar una cosa: un tipo de objeto est determinado por lo que puede hacer, no por su clase. (De hecho, Ruby 1.8 ahora desaprueba el mtodo Objeto#type a favor de Objeto#class por esta razn: el mtodo devuelve la clase del receptor, por lo que la palabra type es engaosa). Qu significa esto en la prctica? En un nivel, simplemente significa que a menudo hay poco valor para probar la clase de un objeto. Por ejemplo, es posible que vaya a escribir una rutina para aadir informacin de una cancin en una cadena. Si usted viene de C# o Java, puede estar tentado a escribir: def append_song(result, song) # test para probar que se dan los parmetros correctos unless result.kind_of?(String) fail TypeError.new(String expected) end unless song.kind_of?(Song) fail TypeError.new(Song expected) end result << song.title << ( << song.artist << ) end result = append_song(result, song) -> I Got Rhythm (Gene Kelly)

Adoptanto el tipado pato de Ruby se escribe cdigo mucho ms simple.

def append_song(result, song) result << song.title << ( << song.artist << ) end result = append_song(result, song) -> I Got Rhythm (Gene Kelly)

No es necesario comprobar el tipo de los argumentos. Si soportan << (en el caso de result) o title y artist (en el caso de song), todo va a funcionar. Si no, el mtodo dar una excepcin de todos modos (como lo habra hecho si hubiera revisado los tipos). Pero sin la comprobacin de tipos, su mtodo de repente es mucho ms flexible: se puede pasar una matriz, una cadena, un archivo o cualquier otro objeto usando << y v a funcionar. Ahora, a veces usted puede querer utilizar ms este estilo de programacin de laissez-faire. Puede haber buenas razones para comprobar que un parmetro puede hacer lo que se necesita. Ser expulsado fuera del club de tipado pato si comprueba el parmetro con una clase? No (El club tipado pato no comprueba para ver si usted es miembro...). Sin embargo, es posible que prefiera considerar la comprobacin sobre la base de las capacidades del objeto, en lugar de su clase. def append_song(result, song) # test para probar que se dan los parmetros correctos unless result.respond_to?(:<<) fail TypeError.new(result needs `<< capability) end unless song.respond_to?(:artist) && song.respond_to?(:title) fail TypeError.new(song needs artist and title) end result << song.title << ( << song.artist << ) end

253

result = append_song(result, song)

->

I Got Rhythm (Gene Kelly)

Sin embargo, antes de ir por este camino, asegrese de que est obteniendo un beneficio real --ya que hay que escribir y mantener una gran cantidad de cdigo extra.

Sobre Protocolos y Coerciones Estndares


Aunque tcnicamente no es parte del lenguaje, el intrprete y la biblioteca estndar utilizan varios protocolos para manejar los asuntos en los que se ocupan otros lenguajes para el uso de tipos. Algunos objetos tienen ms de una representacin natural. Por ejemplo, usted puede querer escribir una clase para representar los nmeros romanos ( I, II, III, IV, V, etc). Esta clase no es necesariamente una subclase de Integer, debido a que sus objetos son representaciones de nmeros, no nmeros en s mismos. Al mismo tiempo, ellos tienen una calidad similar a entero. Sera bueno poder utilizar los objetos de nuestra clase nmero Romano donde Ruby espera ver un entero. Para ello, Ruby tiene el concepto de protocolos de conversin --un objeto puede optar por convertirse en un objeto de otra clase. Ruby tiene tres formas estndar de hacer esto. Ya hemos encontrado la primera. Mtodos tales como to_s y to_i convierten su receptor en cadenas y enteros. Estos mtodos de conversin no son muy estrictos: si un objeto tiene algn tipo de representacin decente como una cadena, por ejemplo, es probable que tenga un mtodo to_s. Nuestra clase romana, probablemente implementara to_s con el fin de devolver la representacin de cadena de un nmero (VII, por ejemplo). La segunda forma de la funcin de conversin utiliza mtodos con nombres como to_str y to_int. Estas son las funciones estrictas de conversin: slo se pueden poner en prctica si el objeto, de forma natural, se puede utilizar en todos los lugares donde una cadena o un entero podran ser utilizados. Por ejemplo, nuestros objetos de nmeros romanos tienen una clara representacin de nmero entero y por lo tanto deben implementar to_int. Cuando se trata de la cadeneidad, sin embargo, la cosa es ms dificil. Nmeros romanos claramente tienen una representacin de cadena, pero son cadenas? debemos utilizar una cadena siempre que podamos utilizar una cadena en s misma? No, probablemente no. Lgicamente, son una representacin de un nmero. Se pueden representar como cadenas pero no son conectables a las cadenas. Por esta razn, un nmero romano no ejecutar to_str --en realidad no es una cadena. Para volver al principio: Los nmeros romanos se pueden convertir en cadenas utilizando to_s, pero no son inherentemente cadenas, por lo que no implementan to_str. Para ver cmo funciona esto en la prctica, echemos un vistazo a la apertura de un archivo. El primer parmetro de File.new puede ser un desciptor de fichero existente (representado por un nmero entero) o un nombre de archivo para abrirlo. Sin embargo, Ruby no se limita a mirar el primer parmetro y comprobar si su tipo es Fixnum o String. Por el contrario, da al objeto pasado la oportunidad de presentarse como un nmero o una cadena. Escribirlo en Ruby, puede parecer algo as como class File def File.new(file, *args) if file.respond_to?(:to_int) IO.new(file.to_int, *args) else name = file.to_str # llamada al sistema operativo para abrir el archivo name end end end As que vamos a ver qu pasa si queremos pasar a File.new un entero descriptor de archivo almacenado como un nmero romano. Debido a que nuestra clase implementa to_int, el primer test respond_to? tendr xito. Vamos a pasar una representacin de entero de nuestro nmero a IO.open y ser devuelto el descriptor de fichero, todo ello envuelto en un nuevo objeto IO.

254

La librera estndar tiene incorporadas un pequeo nmero de funciones de conversin estricta:

to_ary Array Se usa cuando el intrprete necesita convertir un objeto en una matriz para el paso de parmetros o la asignacin mltiple. class OneTwo def to_ary [ 1, 2 ] end end ot = OneTwo.new a, b = ot puts a = #{a}, b = #{b} printf(%d -- %d\n, *ot) produce: a = 1, b = 2 1 -- 2 to_hash Hash Se utiliza cuando el intrprete espera ver un Hash. (El nico uso conocido es el segundo parmetro de Hash#replace). to_int Integer Se utiliza cuando el intrprete espera ver un valor entero (tal como un descriptor de fichero o un parmetro para Kernel.Integer). to_io IO Se utiliza cuando el intrprete est esperando objetos IO (tales como parmetros para IO#reopen o IO.select). to_proc Proc Se utiliza para convertir un objeto precedido de un signo & en una llamada a mtodo. class OneTwo def to_proc proc { one-two } end end def silly yield end ot = OneTwo.new silly(&ot) -> one-two to_str String Se utiliza en casi cualquier lugar cuando el intrprete busca un valor String. class OneTwo def to_str one-two end end ot = OneTwo.new puts(count: + ot) File.open(ot) rescue puts $!.message

255

produce: count: one-two No such file or directory - one-two Ntese, sin embargo, que el uso de to_str no es universal - algunos mtodos que quieren argumentos de cadena no llaman a to_str. File.join(/user, ot) -> /user/#<OneTwo:0x1c974c>

to_sym Symbol Expresan el receptor como un smbolo. No utilizado por el intrprete para las conversiones y probablemente no sea til en el cdigo de usuario. Un ltimo punto: las clases como Integer y Fixnum implementan el mtodo to_int y la clase String implementa to_str. De esa manera se pueden llamar a las funciones de conversin estricta polimrficamente: # No importa si obj es un Fixnum o un nmero Romano, # la conversin todava tiene xito num = obj.to_int

Coercin Numrica
Un par de pginas atras se dijo que el intrprete realiza tres tipos de conversin. Cubrimos la conversin flexible y la estricta. La tercera es la coercin numrica. He aqu el problema. Cuando se escribe 1 + 2, Ruby sabe llamar al + en el objeto 1 (un Fixnum), pasndole el Fixnum 2 como parmetro. Sin embargo, cuando se escribe 1+2.3, el mtodo + recibe ahora un parmetro Float. Cmo puede saber qu hacer (en particular, como comprueba las clases de sus parmetros si va en contra del espritu de tipado pato)? La respuesta est en el protocolo de coercin de Ruby, basado en el mtodo coerce. El funcionamiento bsico de coerce es simple. Necesitan dos nmeros (uno como su receptor, el otro como un parmetro). Devuelve una matriz de dos elementos que contiene representaciones de estos dos nmeros (pero con el parmetro primero, seguido por el receptor). El mtodo coerce garantiza que estos dos objetos tienen la misma clase y por tanto, que se pueden sumar (o multiplicar, o comparar o lo que sea). 1.coerce(2) 1.coerce(2.3) (4.5).coerce(2.3) (4.5).coerce(2) -> -> -> -> [2, 1] [2.3, 1.0] [2.3, 4.5] [2.0, 4.5]

El truco es que el receptor llama al mtodo coerce de su parmetro para generar esta matriz. Esta tcnica, llamada double dispatch, permite a un mtodo cambiar el comportamiento basado no slo en su clase, sino tambin en la clase de su parmetro. En este caso, estamos dejando que el parmetro decida exactamente qu clases de objetos se suman (o multiplican, dividen, etc.) Digamos que estamos escribiendo una nueva clase con la intencin de tomar parte en la aritmtica. Para participar en la coercin, es necesario implementar el mtodo coerce. ste toma algn otro tipo de nmero como parmetro y devuelve una matriz que contiene dos objetos de la misma clase cuyos valores son equivalentes. Para nuestra clase nmero Romano es bastante fcil. Internamente, cada objeto nmero Romano tiene su valor real como un Fixnum en una variable de instancia @value. El mtodo coerce comprueba si la clase de su parmetro es tambin un Integer. Si es as, retorna su parmetro y su valor interno. Si no es as, convierte primero a ambos en coma flotante. class Roman def initialize(value)

256

@value = value end def coerce(other) if Integer === other [ other, @value ] else [ Float(other), Float(@value) ] end end # .. otras cosas de Roman end iv = Roman.new(4) xi = Roman.new(11) 3 * iv 1.1 * xi -> -> 12 12.1

Por supuesto, la clase Roman tal como se implementa no sabe que hacer sumas de si misma: no se podra haber escrito xi + 3 en el ejemplo anterior, ya que Roman no tiene un mtodo mas. Y as es probablemente como debe ser. Pero ahora vamos a implementar la suma para nmeros romanos. class Roman MAX_ROMAN = 4999 attr_reader :value protected :value def initialize(value) if value <= 0 || value > MAX_ROMAN fail Los valores romanos deben ser > 0 y <= #{MAX_ROMAN} end @value = value end def coerce(other) if Integer === other [ other, @value ] else [ Float(other), Float(@value) ] end end def +(other) if Roman === other other = other.value end if Fixnum === other && (other + @value) < MAX_ROMAN Roman.new(@value + other) else x, y = other.coerce(@value) x + y end end FACTORS = [[m, 1000], [cm, 900], [d, 500], [cd, 400], [c, 100], [xc, 90], [l, 50], [xl, 40], [x, 10], [ix, 9], [v, 5], [iv, 4], [i, 1]] def to_s value = @value roman = for code, factor in FACTORS count, value = value.divmod(factor)

257

roman << (code * count) end roman end end iv = Roman.new(4) xi = Roman.new(11) iv iv iv xi xi + + + + + 3 3 + 4 3.14159 4900 4990 -> -> -> -> -> vii xi 7.14159 mmmmcmxi 5001

Por ltimo, tenga cuidado con coerce --siempre tratar de coercionar a un tipo ms general, o se puede terminar generando bucles de coercin, donde A intenta coercionar a B, y B trata de coercionar de vuelta a A.

Walk the Walk, Talk the Talk (Recorrer el Camino, Predicar con el Ejemplo)

Duck typing puede generar controversia. De vez en cuando se un hilo arde en las listas de correo o algn blog con alguien favor o en contra del concepto. Muchos colaboradores en estas discusiones tienen algunas posiciones bastante extremas. En ltima instancia, sin embargo, duck typing no es un conjunto de reglas, slo es un estilo de programacin. Disee sus programas para equilibrar la paranoia y la flexibilidad. Si usted siente la necesidad de restringir los tipos de los objetos que los usuarios de un mtodo pasan, pregntese por qu. Trate de determinar qu podra salir mal si usted esperaba una cadena y en su lugar obtiene una matriz. A veces, la diferencia es de vital importancia. A menudo, sin embargo, no lo es. Trate de errar en el lado ms permisivo por un tiempo y observe si suceden cosas malas. Si no, tal vez duck typing no sea slo para las aves.

Clases y Objetos
Las clases y los objetos son, obviamente, el centro de Ruby, pero a primera vista puede parecer un poco confuso. Parece que hay un montn de conceptos: clases, objetos, objetos de clase, mtodos de instancia, mtodos de clase, clases singleton y clases virtuales. En realidad, sin embargo, Ruby tiene slo una sola estructura de clase y objeto subyacente, de la que hablaremos en este captulo. De hecho, el modelo bsico es muy simple, podemos describirlo en un solo prrafo. Un objeto en Ruby tiene tres componentes: un conjunto de banderas, algunas variables de instancia y una clase asociada. Una clase en Ruby es un objeto de la clase Class, que contiene todas las cosas de un objeto adems de una lista de mtodos y una referencia a una superclase (que a su vez es otra clase). Todas las llamadas a mtodos en Ruby nombran a un receptor (que por propio defecto es self, el objeto en curso). Ruby encuentra el mtodo a invocar buscando en la lista de mtodos de la clase del receptor. Si no encuentra el mtodo all, mira en todos los mdulos incluidos, luego en su superclase, los mdulos de la superclase, luego en la superclase de la superclase y as sucesivamente. Si el mtodo no se puede encontrar en la clase del receptor o de cualquiera de sus ascendientes, entonces invoca el mtodo method_missing en el receptor original. Y eso es todo, la explicacin completa. Siguiente captulo.

Pero espere, grita usted: Gast un buen dinero en este captulo. Qu pasa con todas esas otras cosas --las clases virtuales, los mtodos de clase, etc. Cmo funcionan? Buena pregunta.

Cmo Interactan las Clases y los Objetos


Todas las interacciones clase/objeto se explican utilizando el modelo simple dado anteriormente: objetos referencian clases, y las clases referencian cero o ms superclases. Sin embargo, conseguir los detalles de implementacin puede ser un poco complicado.

258

Hemos encontrado que la forma ms sencilla de visualizar todo esto es dibujando las estructuras reales que implementa Ruby. Por lo tanto, en las pginas siguientes vamos a ver todas las posibles combinaciones de clases y objetos. Tenga en cuenta que estos no son diagramas de clases en sentido UML, estamos mostrando las estructuras en memoria y los punteros entre ellas.

Su Objeto Bsico, de uso diario


Vamos a empezar viendo un objeto creado a partir de una clase simple. La figura 17 muestra un objeto referenciado por una variable, lucille, la clase del objeto, Guitar y la superclase de la clase, Object. Observe como la referencia a la clase del objeto, klass, apunta a la clase de objeto y cmo el puntero super hace referencia a la clase padre. Si invocamos el mtodo lucille.play(), Ruby va al receptor, Lucille, y sigue la referencia klass al objeto de clase Guitar. Busca en la tabla el mtodo, encuentra play y lo invoca. Si en su lugar se llama a lucille.display(), Ruby comienza de la misma manera, pero no puede encontrar display en la tabla de mtodos de la clase de Guitar. A continuacin sigue la referencia super a la superclase de Guitar, Object, donde encuentra el mtodo y lo ejecuta.

Metaclases

Los lectores astutos (s, pensamos que son todos ustedes) se habran dado cuenta de que los miembros klass de objetos class no apuntan a nada significativo en la figura 17. Ahora tenemos toda la informacin que necesitamos para resolver a lo que debe hacer referencia. Cuando usted dice lucille.play(), Ruby sigue puntero klass para encontrar un objeto de clase en el que buscar mtodos. Entonces, qu sucede cuando se invoca un mtodo de clase, como Guitar.strings(...)? Aqu, el receptor es el objeto mismo de la clase, Guitar. Por lo tanto, para que haya consistencia, es necesario atenerse a los mtodos de otra clase, referenciada por el puntero klass de Guitar. Esta nueva clase contendr todos los mtodos de clase de Guitar. Aunque la terminologa es un poco dudosa, vamos a llamar a esto una metaclase (vea el recuadro en la pgina siguiente). Vamos a denotar la metaclase de Guitar como Guitar. Pero esto no es toda la historia. Debido a que Guitar es una subclase de Object, su metaclase Guitarser una subclase de la metaclase de Object, 259

Object. En la figura 18, se muestran estas metaclases adicionales. Cuando Ruby ejecuta Guitar.strings(), se sigue el mismo proceso que antes: se va al receptor, la clase Guitar, se sigue la referencia klass para la clase Guitar y encuentra el mtodo. Por ltimo, sealar que un V se ha colado en las banderas de la clase Guitar. Las clases que Ruby crea automticamente se marcan internamente como clases virtuales. estas clases virtuales se tratan de forma ligeramente diferente en Ruby. La diferencia ms obvia desde el exterior es que son efectivamente invisibles: nunca aparecern en una lista de objetos retornados desde mtodos tales como Module#ancestors o ObjectSpace.each_object, y no se pueden crear instancias de ellos utilizando new.

Objetos Especficos de Clase


Ruby le permite crear una clase ligada a un objeto particular. En el siguiente ejemplo, creamos dos objetos String. A continuacin, asociamos una clase annima con uno de ellos, anulando uno de los mtodos de la clase base del objeto y aadindole un nuevo mtodo. a = hello b = a.dup class <<a def to_s The value is #{self} end

260

def two_times self + self end end a.to_s a.two_times b.to_s -> -> -> The value is hello hellohello hello

Metaclases y Clases Singleton Durante la revisin de este libro, el uso del trmino metaclase gener una gran discusin, ya que las metaclases Ruby son diferentes a las de lenguajes como Smalltalk. Pasado un tiempo, Matz intervino con lo siguiente: Se le puede llamar metaclase, pero, a diferencia de Smalltalk, no es una clase de una clase, es una clase singleton de una clase. Todos los objetos en Ruby tienen sus propios atributos (mtodos, constantes, etc) que en otros idiomas estn en manos de las clases. Es como que cada objeto tiene su propia clase. Para manejar atributos por objeto, Ruby proporciona algo similar a una clase para cada objeto que se llama a veces clase singleton. En la implementacin actual, las clases son objetos de la clase singleton especialmente sealada entre los objetos y su clase. Estas pueden ser clases virtuales si lo decide el implementador del lenguaje. Las clases Singleton se comportan para las clases como metaclases de Smalltalk. En este ejemplo se utiliza la notacin class <<obj, que bsicamente dice aconstruir una nueva clase slo para el objeto obj. Tambin se podra haber escrito como a = hello b = a.dup def a.to_s The value is #{self} end def a.two_times self + self end a.to_s -> The value is hello a.two_times -> hellohello b.to_s -> hello El efecto es el mismo en ambos casos: una clase se aade al objeto a. Esto nos da un fuerte indicio sobre la implementacin Ruby: se crea una clase virtual y se inserta como clase directa de a. La clase original de a, String, se hace superclase esta clase virtual. La imagen del antes y el despus se muestra en la figura 19 en la pgina siguiente: Hay que recordar que las clases en Ruby nunca se cierran. Siempre se puede abrir una clase y aadir nuevos mtodos a la misma. Lo mismo se aplica a las clases virtuales. Si la referencia de un objeto klass ya apunta a una clase virtual, no se crear una nueva. Esto significa que la primera de las dos definiciones de mtodo en el ejemplo anterior crea una clase virtual, pero el segundo simplemente aadir un mtodo a la misma. El mtodo Object#extend aade los mtodos en su parmetro a su receptor, por lo que si es necesario, tambin crea una clase virtual. obj.extend(Mod) es bsicamente equivalente a:

261

class <<obj include Mod end

Mdulos Mixin
Cuando una clase incluye un mdulo, los mtodos de instancia del mdulo estn disponibles como mtodos de instancia de la clase. Es casi como si el mdulo se conviertiera en una superclase de la clase que lo utiliza. No es de extraar que sea as cmo funciona. Cuando se incluye un mdulo, Ruby crea una clase proxy1 annima que hace referencia a ese mdulo y se inserta el proxy como la superclase directa de la clase que incluy al mdulo. La clase proxy contiene referencias a las variables de instancia y los mtodos del mdulo. Esto es importante: el mismo mdulo puede ser incluido en muchas diferentes clases y va a aparecer en muchas cadenas de herencia diferentes. Sin embargo, gracias a la clase proxy, seguir habiendo un solo mdulo de fondo: modificar una definicin de mtodo en ese mdulo, har que se modifique en todas las clases que incluyen el mdulo, tanto en el pasado como en el futuro. module SillyModule def hello
1 Proxy: programa o dispositivo que realiza una accin en representacin de otro.

262

Hello. end end class SillyClass include SillyModule end s = SillyClass.new s.hello -> Hello. module SillyModule def hello Hi, there! end end s.hello -> Hi, there!

La relacin entre las clases y los mdulos mixin que incluyen se muestra en la figura 20 en la pgina siguiente. Si se incluyen varios mdulos se aaden a la cadena en orden. Si un mdulo en si mismo incluye otros mdulos, se va a aadir una cadena de clases proxy a cualquier clase que incluya este mdulo. Un proxy para cada mdulo que est directa o indirectamente incluido.

Extender Objetos
As como se puede definir una clase annima para un objeto mediante class <<obj, se puede mezclar un mdulo en un objeto utilizando object#extend. Por ejemplo: module Humor def tickle hee, hee! end end a = Grouchy a.extend Humor a.tickle -> hee, hee! Hay un truco interesante con extend. Si lo usa en una definicin de clase, los mtodos del mdulo se convierten en los mtodos de clase. Esto se debe a que llamar a extend es equivalente a self.extend, por lo que los mtodos se aaden a self, que en una definicin de clase es la clase misma. He aqu un ejemplo de cmo agregar mtodos de un mdulo a nivel de clase.

module Humor def tickle hee, hee! end end class Grouchy include Humor extend Humor end Grouchy.tickle a = Grouchy.new a.tickle -> -> hee, hee! hee, hee!

263

Definiciones de Clase y Mdulo


Despus de haber agotado las combinaciones de clases y objetos, podemos (afortunadamente) volver a la programacin para mirar las tuercas y tornillos de las definiciones de clases y mdulos. En lenguajes como C++ y Java, las definiciones de clase se procesan en tiempo de compilacin: el compilador crea las tablas de smbolos, trabaja sobre laq asignacin de almacenamiento, construye tablas de distribucin y hace todas esas cosas oscuras sobre las que preferimos no pensar demasiado.

Ruby es diferente. En Ruby, las definiciones de clase y de mdulo son cdigo ejecutable. Aunque se analizan en tiempo de compilacin, las clases y los mdulos son creados en tiempo de ejecucin, segn se encuentra la definicin. (Lo mismo es cierto para las definiciones de mtodo). Esto le permite estructurar sus programas mucho ms dinmicamente que la mayora de los lenguajes convencionales. Se pueden tomar decisiones una vez, cuando se define la clase, en lugar de cada vez que se utilizan los objetos de la clase. La clase en el siguiente ejemplo, decide a medida que se define la versin a crear de una rutina de descifrado. module Tracing # ... end class MediaPlayer include Tracing if $DEBUG if ::EXPORT_VERSION def decrypt(stream) raise Decryption not available

264

end else def decrypt(stream) # ... end end end Si las definiciones de clase son cdigo ejecutable, esto implica que se ejecutan en el contexto de un objeto: self debe hacer referencia a algo. Vamos a ver lo que es. class Test puts Class of self = #{self.class} puts Name of self = #{self.name} end produce: Class of self = Class Name of self = Test Esto significa que una definicin de clase se ejecuta con esa clase como el objeto actual. Volviendo a la seccin sobre metaclases unas pginas atrs, podemos ver que esto significa que los mtodos en la metaclase y sus superclases estarn disponibles durante la ejecucin de la definicin del mtodo. Podemos comprobar esto. class Test def Test.say_hello puts Hello from #{name} end say_hello end produce: Hello from Test En este ejemplo se define un mtodo de clase, Test.say_hello, y luego es llamado en el cuerpo de la definicin de clase. Dentro de say_hello, llamamos a name, un mtodo de instancia de la clase Module . Debido a que Module es un ancestro de Class, sus mtodos de instancia se pueden llamar sin un receptor explcito dentro de una definicin de clase.

Variables de Instancia de Clase


Si se ejecuta una definicin de clase en el contexto de algn objeto, esto implica que una clase puede tener variables de instancia. class Test @cls_var = 123 def Test.inc @cls_var += 1 end end Test.inc Test.inc -> -> 124 125

Si las clases tienen sus propias variables de instancia, podemos utilizar attr_reader y amigos para acceder a ellos? Podemos, pero tenemos que ejecutar estos mtodos en el lugar correcto. Para las variables de instancia normales, los accesores de atributo se definen a nivel de clase. Para las variables de

265

instancia de clase, tenemos que definir los mtodos de acceso en la metaclase. class Test @cls_var = 123 class <<self attr_reader :cls_var end end Test.cls_var -> 123

Esto nos lleva a un punto interesante. Muchas de las directivas que se utilizan para definir una clase o mdulo, cosas como alias_method, attr y public, son simplemente mtodos en la clase Module. Esto crea algunas posibilidades intrigantes --se puede ampliar la funcionalidad de las definiciones de clase y mdulo escribiendo cdigo Ruby. Echemos un vistazo a un par de ejemplos. Vamos a ver un ejemplo de adicin de una lnea de documentacin bsica a los mdulos y las clases. Esto nos permite asociar una cadena a mdulos y clases que escribimos y que es accesible cuando el programa est en funcionamiento. Vamos a elegir una sintaxis sencilla. class Example doc This is a sample documentation string # .. rest of class end Tenemos que hacer doc disponible para cualquier mdulo o clase, por lo que necesitamos hacerlo un mtodo de instancia de la clase Module. class Module @@docs = {} # invocado en las definiciones de clase def doc(str) @@docs[self.name] = self.name + :\n + str.gsub(/^\s+/, ) end # invocado para obtener la documentacin def Module::doc(aClass) # Si nos pasa una clase o mdulo, se convierte en cadena # (<= Para las clases de comprobacin de la misma clase o subtipo) aClass = aClass.name if aClass.class <= Module @@docs[aClass] || No documentation for #{aClass} end end class Example doc This is a sample documentation string # .. rest of class end module Another doc <<-edoc And this is a documentation string in a module edoc # rest of module end puts Module::doc(Example) puts Module::doc(Another) produce:

266

Example: This is a sample documentation string Another: And this is a documentation string in a module El segundo ejemplo es una mejora de rendimiento basada en el mdulo date de Tadayoshi Funaba (que se ver ms adelante). Digamos que tenemos una clase que representa una cierta cuantificacin subyacente (en este caso, una fecha). La clase puede tener muchos atributos que presentan la misma fecha subyacente de diversas maneras: como un nmero de da Juliano, como una cadena, como un triple [ao, mes, da] , etc. Cada valor representa la misma fecha y su derivacin puede implicar un clculo bastante complejo. Quisiramos por tanto, tener el clculo de cada atributo de una sola vez, la primera que es accedido. La forma manual sera aadir una prueba para cada accesor. class ExampleDate def initialize(day_number) @day_number = day_number end def as_day_number @day_number end def as_string unless @string # complex calculation @string = result end @string end def as_YMD unless @ymd # another calculation @ymd = [ y, m, d ] end @ymd end # ... end Esta es una tcnica torpe --Vamos a ver si podemos llegar a algo ms sexy. Lo que estamos buscando es una directiva que indique que el cuerpo de un mtodo en particular debe ser invocado slo una vez. El valor devuelto por la primera llamada debe ser almacenado en cach. A partir de entonces, la llamada a ese mismo mtodo debe devolver el valor almacenado en cach sin volver a evaluar el cuerpo del mtodo de nuevo. Esto es similar al modificador de Eiffel once para las rutinas. Nos gustara ser capaces de escribir algo como: class ExampleDate def as_day_number @day_number end def as_string # complex calculation end def as_YMD # another calculation [ y, m, d ] end once :as_string, :as_YMD end

267

Podemos utilizar once como una directiva escribindola como un mtodo de clase de ExampleDate, pero, como tiene que verse internamente? El truco est en reescribir los mtodos cuyos nombres se pasan. Para cada mtodo, se crea un alias para el cdigo original, y luego se crea un nuevo mtodo con el mismo nombre. Aqu est el cdigo de Tadayoshi Funaba, ligeramente reordenado. def once(*ids) # :nodoc: for id in ids module_eval <<-end; alias_method :__#{id.to_i}__, :#{id.to_s} private :__#{id.to_i}__ def #{id.to_s}(*args, &block) (@__#{id.to_i}__ ||= [__#{id.to_i}__(*args, &block)])[0] end end; end end Este cdigo utiliza module_eval para ejecutar un bloque de cdigo en el contexto del mdulo llamador (o, en este caso, la clase llamadora). El mtodo original se renombra con __nnn__, donde la parte nnn es la representacin de nmero entero del smbolo ID del nombre del mtodo. El cdigo utiliza el mismo nombre para la variable de instancia de cach. Entonces se define un mtodo con el nombre original. Si la variable de instancia de cach tiene un valor, se devuelve ese valor, de lo contrario se llama al mtodo original y se devuelve su valor de retorno en cach. Si se entiende este cdigo se estar bien encaminado a cierto dominio de Ruby.

Sin embargo, podemos ir ms lejos. Observando el mdulo date se ver el mtodo once escrito ligeramente diferente. class Date class << self def once(*ids) # ... end end # ... end Lo interesante aqu es la definicin de la clase interna << self. Se define una clase basada en el objeto self, y self pasa a ser el objeto de la clase Date. El resultado? Todos los mtodos dentro de la definicin de la clase interna son automticamente mtodos de clase de Date. La caracterstica once es aplicable en general --debera funcionar para cualquier clase. Si se toma once y se convierte en un mtodo privado de instancia de la clase Module, estar disponible para en cualquier clase Ruby. (Y por supuesto se puede hacer esto, ya que la clase Module es abierta y usted es libre de aadir mtodos en ella.)

Los Nombres de Clase son Constantes


Hemos dicho que cuando se llama a un mtodo de clase, todo lo que estamos haciendo es enviar un mensaje al objeto Class en s. Cuando usted dice algo como String.new(Gumby), est enviando el mensaje new al objeto que es la clase String. Pero, cmo hace Ruby para saber como hacer esto? Despus de todo, el receptor de un mensaje debe ser una referencia de objeto, lo que implica que debe haber una constante llamada String en alguna parte que contiene una referencia al objeto String2. Y de hecho, eso es exactamente lo que sucede. Todas las clases integradas, junto con las clases que usted defina, tienen una correspondiente constante global con el mismo nombre que la clase. Esto es directo y sutil. La sutileza viene del hecho de que las dos cosas son nombradas (por ejemplo) String en el sistema. Hay una constante que referencia la clase String (un objeto de la clase Class), y est el objeto (la clase) en s mismo. 2 Ser una constante, no una variable, ya que String comienza con una letra mayscula. 268

El hecho de que los nombres de clase son constantes significa que se puede tratar a las clases como a cualquier otro objeto Ruby: pueden copiarse, pasarse a mtodos y utilizarse en expresiones. def factory(klass, *args) klass.new(*args) end factory(String, Hello) factory(Dir, .) -> -> Hello #<Dir:0x1c90e4> -> -> [1, 2, 3, 4] {1=>2, 3=>4}

flag = true (flag ? Array : Hash)[1, 2, 3, 4] flag = false (flag ? Array : Hash)[1, 2, 3, 4]

Esto tiene otra faceta: si una clase que no tiene nombre se asigna a una constante, Ruby le da a la clase el nombre de la constante. var = Class.new var.name -> Wibble = var var.name -> Wibble

Entrono de Ejecucin de Nivel Superior


Muchas veces en este libro hemos afirmado que todo en Ruby es un objeto. Sin embargo, hemos utilizado una y otra vez lo que parece contradecir esto --el entorno de ejecucin de alto nivel de Ruby. puts Hola, Mundo No hay un objeto a la vista. Bien podramos haberlo escrito en alguna variante de Fortran o BASIC. Sin embargo, si se cava ms profundo, se encontrararn objetos y clases que acechan incluso en el ms simple cdigo. Sabemos que el literal Hola, Mundo genera un String de Ruby, por lo que es un objeto. Tambin sabemos que la llamada al mtodo pelado puts al e es efectivamente la misma que self.puts. Pero, qu es self? self.class --> Object

En el nivel superior, estamos ejecutando el cdigo en el contexto de un objeto predeterminado. Cuando definimos los mtodos, en realidad estamos creando mtodos de instancia (privados) para la clase Object . Esto es bastante sutil, ya que como estn en la clase Object, estos mtodos estn disponibles en todas partes. Y ya que estamos en el contexto de Object, podemos utilizar todos los mtodos de Object (incluidos los mixtos de Kernel) en forma de funcin. Esto explica por qu podemos llamar a los mtodos de Kernel, como puts en el nivel superior (y de hecho a lo largo de todo Ruby): estos mtodos son parte de cada objeto. Las variables de instancia de alto nivel tambin pertenecen a este objeto de nivel superior.

Herencia y Visibilidad
La ltima arruga en la herencia de clases es bastante oscura.

Dentro de una definicin de clase, se puede cambiar la visibilidad de un mtodo en una clase antecesora. Por ejemplo, usted puede hacer algo como class Base def aMethod

269

puts Got here end private :aMethod end class Derived1 < Base public :aMethod end class Derived2 < Base end En este ejemplo, se debe ser capaz de invocar aMethod en instancias de la clase Derived1, pero no a travs de las instancias de Base o Derived2. Entonces, cmo logra Ruby esta hazaa de tener un mtodo con dos visibilidades diferentes? En pocas palabras: se engaa. Si una subclase cambia la visibilidad de un mtodo en una clase padre, Ruby inserta efectivamente un mtodo proxy oculto en la subclase que invoca el mtodo original utilizando super. A continuacin, establece la visibilidad de ese proxy para lo que se ha pedido. Esto significa que el cdigo class Derived1 < Base public :aMethod end es efectivamente lo mismo que

class Derived1 < Base def aMethod(*args) super end public :aMethod end La llamada a super puede acceder a mtodos de los padres, independientemente de su visibilidad, por lo que la reescritura permite a la subclase reemplazar las reglas de visibilidad de su clase padre. D miedo, eh?

Congelacin de Objetos
A veces usted ha trabajado duro para hacer de su objeto exactamente correcto y, que le aspen si usted permite que nadie lo vaya a cambiar. Tal vez tiene que pasar algn tipo de objeto opaco entre dos de sus clases a travs de algunos objeto de terceros y desea asegurarse de que llega sin modificaciones. Tal vez desee utilizar un objeto como una clave hash y necesitee asegurarse de que nadie lo modifique mientras se est utilizando. Tal vez algo est haciendo corrupto uno de sus objetos y le gustara que Ruby provoque una excepcin tan pronto como se produzca el cambio. Ruby proporciona un mecanismo muy simple para ayudar en esto. Cualquier objeto puede ser congelado por la invocacin de Object#freeze. Un objeto congelado no puede ser modificado: no puede cambiar sus variables de instancia (directa o indirectamente), no se le puede asociar mtodos singleton, y, si se trata de una clase o mdulo, no se les puede aadir, eliminar o modificar sus mtodos. Una vez congelado, un objeto se mantiene congelado: no hay ningn Object#deshielo. Usted puede probar si un objeto est congelado mediante Object#frozen?. Qu sucede cuando se copia un objeto congelado? Eso depende del mtodo que se utilice. Si se llama al mtodo de un objeto clone, el estado del objeto entero (incluyendo si est congelado) se copia en el nuevo objeto. Por otro lado, dup normalmente slo copia el contenido del objeto --la nueva copia no heredar el estado congelado. str1 = hello str1.freeze -> hello

270

str1.frozen? -> str2 = str1.clone str2.frozen? -> str3 = str1.dup str3.frozen? ->

true true false

Aunque al principio la congelacin de objetos puede parecer una buena idea, es posible que prefiera esperar a hacerlo hasta que llegue una necesidad real. La congelacin es una de esas ideas que se ven esenciales en el papel pero que no se utilizan mucho en la prctica.

Bloqueo de Seguridad de Ruby


Walter WebCoder tiene una gran idea para un sitio de portal: la Pgina Web de la Aritmtica. Rodeado de todo tipo de fros enlaces matemticos y anuncios de banner que lo har rico, es un sencillo formulario web que contiene un campo de texto y un botn. Los usuarios escriben una expresin aritmtica en el campo, hacen clic en el botn, y se muestra la respuesta. Todas las calculadoras del mundo se vuelven obsoletos de la noche a la maana, Walter hace caja y se retira para dedicar la vida a su coleccin de nmeros de matrculas de coches. La aplicacin de la calculadora es fcil, piensa Walter. Accede a los contenidos del campo del formulario utilizando la biblioteca CGI de Ruby y utiliza el mtodo eval para evaluar la cadena como una expresin . require cgi cgi = CGI.new(html4) # Recuperar el valor del campo del formulario expression expr = cgi[expression].to_s begin result = eval(expr) rescue Exception => detail # manejar expresiones malas end # mostrar el resultado de vuelta al usuario... Aproximadamente siete segundos despus de que Walter pone la aplicacin en lnea, una nia de doce aos de edad, desde Waxahachie, con problemas glandulares y sin vida real, tipea system(rm *) en el formulario y, al igual que los archivos de su ordenador, los sueos de Walter se vienen abajo. Walter aprendi una importante leccin: Toda la informacin externa es peligrosa. No deje cerca a las interfaces que pueden modificar su sistema. En este caso, el contenido del campo de formulario fu el de datos externos y la llamada a eval fue la violacin de la seguridad. Afortunadamente, Ruby proporciona soporte para la reduccin de este riesgo. Toda la informacin del mundo exterior puede ser marcada como contaminada. Cuando se ejecuta en un modo seguro, los mtodos potencialmente peligrosos lanzarn un SecurityError si pasa un objeto contaminado.

Los Niveles de Seguridad


La variable $SAFE determina el nivel Ruby de paranoia. La Tabla 25.1, dos pginas adelante, proporciona ms detalles de las comprobaciones realizadas en cada nivel de seguridad. El valor por defecto de $SAFE es cero en la mayora de las circunstancias. Sin embargo, si un script Ruby se ejecuta en modo setuid o setgid3, o si se ejecuta bajo mod_ruby, se establece su nivel de seguridad automticamente en 1. El nivel de seguridad tambin se pueden ajustar mediante el uso de la opcin -T de lnea de comandos y mediante la asignacin de $SAFE dentro del programa. No es posible reducir el valor de $SAFE por asignacin.

Un script de Unix puede ser marcado para ejecutarse con el ID de usuario o grupo al que pertenece. Esto permite a otro usuario ejecutar con script los privilegios que no tiene y poder acceder a los recursos que de otro modo tendra prohibidos. Estos scripts se llaman setuid o setgid.

271

El valor en curso de $SAFE se hereda cuando se crean hilos nuevos. Sin embargo, dentro de cada hilo, se puede cambiar el valor de $SAFE sin afectar al valor en otros hilos. Esta caracterstica se puede utilizar para implementar cajas seguras. reas en las que el cdigo externo se puede ejecutar sin peligro para el resto de la aplicacin o sistema. Para ello, se envuelve el cdigo que se va a cargar desde un archivo, en su propio mdulo annimo. Esto proteger el espacio de nombres del programa de cualquier alteracin involuntaria. f=open(filename,w) f.print ... # escribir en un fichero el programa no fiable f.close Thread.start do $SAFE = 4 load(filename, true) end

$SAFE

Limitaciones

0 No se comprueba el suminstro externo de datos (contaminados). Modo por defecto. 1 No se permite el uso de datos contaminados para operaciones potencialmente peligrosas. 2 Se prohbe la carga de archivos de programa desde ubicaciones globales para escritura. 3 Todos los objetos de nueva creacin se consideran contaminados. 4 Ruby particiona eficazmente la ejecucin del programa en dos. Objetos no contaminados no pueden ser modificados. Con un nivel $SAFE de 4, slo se pueden cargar archivos envueltos. Vase la descripcin de Kernel.load ms adelante para ms detalles. El nivel de seguridad en vigor cuando se crea un objeto Proc se almacena con ese objeto. Un Proc no se puede pasar a un mtodo si est contaminado y el nivel de seguridad actual es mayor que el que estaba en vigor cuando se cre el bloque.

Objetos Contaminados
Cualquier objeto Ruby obtenido de una fuente externa (por ejemplo, una cadena leda desde un archivo o una variable de entorno) es automticamente marcado como contaminado. Si el programa utiliza un objeto contaminado para obtener un nuevo objeto, ese nuevo objeto tambin estar contaminado, como se muestra en el cdigo de abajo. Cualquier objeto con datos externos de algn momento de su pasado va a estar contaminado. Este proceso de contaminado se lleva a cabo independientemente del nivel de seguridad en curso. Puede ver si un objeto est contaminado utilizando Object#tainted?. # internal data # ============= x1 = a string x1.tainted? x2 = x1[2, 4] x2.tainted? x1 =~ /([a-z])/ $1.tainted? # external data # =============

-> -> -> ->

false false 0 false

y1 = ENV[HOME] y1.tainted? y2 = y1[2, 4] y2.tainted? y1 =~ /([a-z])/ $1.tainted?

-> -> -> ->

true true 2 true

Se puede forzar la contaminacin de un objeto invocando al mtodo taint. Si el nivel de seguridad es inferior a 3, se puede descontaminar un objeto mediante la invocacin de untaint. Esto no es como para hacerlo a la ligera (tambin se pueden utilizar algunos trucos tortuosos para hacer esto sin usar untaint, pero vamos a dejar que su lado ms oscuro los encuentre). Est claro que Walter ha ejecutado el script CGI en el nivel seguro 1. Esto hizo que se lanzara una

272

excepcin cuando el programa trat de pasar datos del formulario a eval. Una vez que sucede esto, Walter tiene una serie de opciones. Podra haber optado por aplicar un programa de anlisis de expresiones adecuadas, evitando los riesgos inherentes al uso de eval. Por lo menos, la pereza!, probablemente tendra que haber realizado alguna comprobacin simple de seguridad en los datos del formulario para descontaminarlos y que pasen slo los inocuos. require cgi; $SAFE = 1 cgi = CGI.new(html4) expr = cgi[expression].to_s if expr =~ %r{\A[-+*/\d\seE.()]*\z} expr.untaint result = eval(expr) # display result back to user... else # display error message... end Personalmente, creemos que Walter sigue corriendo riesgos indebidos. Probablemente preferiramos ver un verdadero analizador, pero la implementacin de uno aqu, no tendra nada que ensearnos acerca de los objetos contaminados, as que vamos a pasar a otros temas.

Reflexin, Espacio de Objeto y Ruby Distribudo


Una de las muchas ventajas de los lenguajes dinmicos como Ruby es la capacidad de introspeccin --el examinar los aspectos del programa desde el propio programa. Java, por ejemplo, llama a esta caracterstica reflection, pero las capacidades de Ruby van ms all de Java. La palabra reflexin evoca la imagen de mirarse en el espejo --tal vez la investigacin de la propagacin implacable de la calva en la parte superior de la cabeza. Es una analoga muy apropiada: se utiliza la reflexin para examinar las partes de nuestros programas que normalmente no son visibles desde donde estamos. En este estado de nimo profundamente introspectivo, mientras que estamos contemplando nuestro ombligo y quemando incienso (teniendo cuidado de no intercambiar las dos tareas), qu podemos aprender de nuestro programa? Pues, podramos descubrir: Qu objetos contiene, La jerarqua de clases, los atributos y mtodos de los objetos y informacin sobre los mtodos.

Armados con esta informacin, podemos mirar a los objetos particulares y decidir cules de sus mtodos llamar en tiempo de ejecucin --incluso si no exista la clase del objeto cuando se escribi por primera vez el cdigo. Tambin podemos empezar a hacer cosas inteligentes, tal vez modificar el programa segn se est ejecutando. Causa temor? No tiene por qu. De hecho, con estas capacidades de reflexin vamos a hacer algunas cosas muy tiles. Ms adelante en este captulo vamos a ver Ruby distribuido y el clculo de referencias, dos tecnologas basadas en la reflexin que nos permite enviar objetos en todo el mundo y a travs del tiempo.

Mirando Objetos
Alguna vez ha anhelado la posibilidad de recorrer todos los objetos que viven en su programa? Podemos! Ruby le permite realizar este truco con ObjectSpace.each_object. Podemos usarlo para hacer todo tipo de truquitos.

273

Pgina 383 del original en ingls.

274

Por ejemplo, para iterar sobre todos los objetos de tipo Numeric, se podra escribir lo siguiente.

a = 102.7 b = 95.1 ObjectSpace.each_object(Numeric) {|x| p x } produce: 95.1 102.7 2.71828182845905 3.14159265358979 2.22044604925031e-16 1.79769313486232e+308 2.2250738585072e-308 Hey, de dnde salen todos esos nmeros extra? No los definimos en nuestro programa. Ms adelante, veremos que la clase Float define las constantes para el float mximio y mnimo, as como Epsilon, la ms pequea diferencia distinguible entre dos floats. El mdulo Math define constantes para e y . Puesto que estamos examinando todos los objetos que viven en el sistema, estos a su vez aparecen tambin. Vamos a probar el mismo ejemplo con nmeros diferentes.

a = 102 b = 95 ObjectSpace.each_object(Numeric) {|x| p x } produce: 2.71828182845905 3.14159265358979 2.22044604925031e-16 1.79769313486232e+308 2.2250738585072e-308 Ninguno de los objetos Fixnum que hemos creado apareci. Esto es porque ObjectSpace no sabe acerca de los objetos con valores inmediatos: Fixnum, Symbol, true, false y nil.

Mirando Dentro de los Objetos


Una vez que haya encontrado un objeto interesante, puede tener la tentacin de averiguar lo que puede hacer. A diferencia de los lenguajes estticos, donde el tipo de una variable determina su clase y, por lo tanto, los mtodos que soporta, Ruby soporta objetos liberados. Usted realmente no puede decir exactamente lo que un objeto puede hacer hasta que mira debajo de su capota (o bajo su cap, para los objetos creados al este del Atlntico). Hablamos de esto en el captulo de Duck Typing, anteriormente. Por ejemplo, podemos obtener una lista de todos los mtodos a los que un objeto va a responder.

r = 1..10 # Crear un objeto Range list = r.methods list.length -> 68 list[0..3] -> [collect, to_a, instance_eval, all?] O bien, podemos comprobar si un objeto admite un mtodo en particular. -> -> -> true false true

r.respond_to?(frozen?) r.respond_to?(:has_key?) me.respond_to?(==)

275

Podemos determinar la clase de nuestro objeto y su identificador de objeto nico (ID) y poner a prueba su relacin con otras clases. num = 1 num.id num.class num.kind_of? Fixnum num.kind_of? Numeric num.instance_of? Fixnum num.instance_of? Numeric -> -> -> -> -> -> 3 Fixnum true true true false

Mirando Clases
Saber acerca de los objetos es una parte de la reflexin, pero para obtener el cuadro completo, usted tambin necesita ser capaz de mirar a las clases --los mtodos y constantes que contienen. En cuanto a la jerarqua de clases es fcil. Puede obtener la clase padre de cualquier clase en particular con Class#superclass. Para clases y mdulos, Module#ancestors lista tanto superclases como mdulos mixed-in. klass = Fixnum begin print klass klass = klass.superclass print < if klass end while klass puts p Fixnum.ancestors produce: Fixnum < Integer < Numeric < Object [Fixnum, Integer, Precision, Numeric, Comparable, Object, Kernel] Si se quiere construir una jerarqua de clases completa, basta con ejecutar este cdigo para cada clase en el sistema. Podemos utilizar ObjectSpace para iterar sobre todos los objetos Class. ObjectSpace.each_object(Class) do |klass| # ... end

Mirando Dentro de las Clases


Podemos buscar un poco ms en los mtodos y las constantes de un objeto en particular. En lugar de slo comprobar si el objeto responde a un mensaje dado, podemos preguntarle a los mtodos por el nivel de acceso e inquirir slo por los mtodos singleton. Tambin podemos echar un vistazo a las constantes, locales y variables de instancia del objeto. class Demo @@var = 99 CONST = 1.23 private def private_method end protected def protected_method end public def public_method

276

@inst = 1 i = 1 j = 2 local_variables end def Demo.class_method end end Demo.private_instance_methods(false) Demo.protected_instance_methods(false) Demo.public_instance_methods(false) Demo.singleton_methods(false) Demo.class_variables Demo.constants Demo. superclass.constants -> -> -> -> -> -> [private_method] [protected_method] [public_method] [class_method] [@@var] [CONST]

demo = Demo.new demo.instance_variables -> [] # Obtener public_method para retornar sus variables locales # y establecer una variable de instancia demo.public_method -> [i, j] demo.instance_variables -> [@inst] Module.constants devuelve todas las constantes disponibles a travs de un mdulo, incluyendo las constantes de las superclases del mdulo. No estamos interesados en estas justo en este momento, as que las restamos de nuestra lista. Usted puede preguntarse acerca de los parmetros false del cdigo anterior. A partir de Ruby 1.8, estos mtodos de reflexin, por defecto recorre las clases padres y las clases padres de stas y as sucesivamente hasta recorrer toda la cadena de los antepasados. Pasando false se detiene este tipo de fisgoneo. Dada una lista de nombres de mtodos, ahora puede verse tentados a tratar de llamalos. Afortunadamente, esto es fcil con Ruby.

Llamar a Mtodos de Forma Dinmica


Los programadores de C y Java a menudo se encuentran escribiendo una especie de mesa de despacho: funciones que se invocan sobre la base de un comando. Piense en el tpico lenguaje C donde se tiene que traducir una cadena a un puntero a funcin. typedef struct { char *name; void (*fptr)(); } Tuple; Tuple list[]= { { play, fptr_play }, { stop, fptr_stop }, { record, fptr_record }, { 0, 0 }, }; ... void dispatch(char *cmd) { int i = 0; for (; list[i].name; i++) { if (strncmp(list[i].name,cmd,strlen(cmd)) == 0) { list[i].fptr(); return; }

277

} /* not found */ } En Ruby, usted puede hacer todo esto en una sola lnea. Ponga todas las funciones de comando en una clase, cree una instancia de esa clase (llamamandola commands), y haga que el objeto ejecute un mtodo llamado con el mismo nombre que la cadena de comando. commands.send(command_string) Ah, y por cierto, hace mucho ms que la versin C --ya que es de forma dinmica. La versin Ruby encuentra nuevos mtodos aadidos en tiempo de ejecucin con la misma facilidad. No se tienen que escribir las clases especiales de comando para send: funciona en cualquier objeto. -> -> 13 M. Davis

John Coltrane.send(:length) Miles Davis.send(sub, /iles/, .)

Otra forma de invocar mtodos de forma dinmica es utilizando objetos Method. Un objeto Method es como un objeto Proc: representa un trozo de cdigo y un contexto en el que se ejecuta. En este caso, el cdigo es el cuerpo del mtodo y el contexto es el objeto que cre el mtodo. Una vez que tenemos nuestro objeto Method, se puede ejecutar en algn momento ms adelante mediante el envo del mensaje call. trane = John Coltrane.method(:length) miles = Miles Davis.method(sub) trane.call miles.call(/iles/, .) -> -> 13 M. Davis

Puede pasar el objeto Method como lo hara con cualquier otro objeto y cuando se invoca Method#call, se ejecuta el mtodo como si se hubiera invocado en el objeto original. Es como tener un puntero a funcin de estilo C, pero de forma totalmente orientada a objetos. Tambin puede utilizar los objetos Method con iteradores.

def double(a) 2*a end mObj = method(:double) [ 1, 3, 5, 7 ].collect(&mObj) -> [2, 6, 10, 14]

Los objetos Method estn destinados a un objeto en particular. Se puede crear mtodos no consolidados (de la clase UnboundMethod) y, posteriormente, unirse a uno o ms objetos. La unin (binding) crea un nuevo objeto Method. Al igual que con los alias, los mtodos no ligados son referencias a la definicin del mtodo en el momento de su creacin. unbound_length = String.instance_method(:length) class String def length 99 end end str = cat str.length -> 99 bound_length = unbound_length.bind(str) bound_length.call -> 3 Como las cosas buenas vienen de tres en tres, esta es otra manera de llamar a los mtodos de forma dinmica. El mtodo eval (y sus variantes, tales como class_eval, module_eval y instance_eval)

278

analizar y ejecutar una cadena arbitraria de cdigo fuente legtimo Ruby. trane = %q{John Coltrane.length} miles = %q{Miles Davis.sub(/iles/, .)} eval trane eval miles -> -> 13 M. Davis

Cuando se utiliza eval, puede ser til de manera explcita el contexto en el que la expresin debe ser evaluada, en lugar de utilizar el contexto actual. Se puede obtener un contexto llamando a Kernel#binding en el punto deseado. def get_a_binding val = 123 binding end val = cat the_binding = get_a_binding eval(val, the_binding) -> eval(val) -> 123 cat

La primera eval evala val en el contexto de la unin, como era cuando el mtodo get_a_binding se estaba ejecutando. En esta unin, la variable val tiene un valor de 123. El segundo eval evala val en el nivel superior de unin, donde se tiene el valor cat.

Consideraciones sobre el rendimiento


Como hemos visto en esta seccin, Ruby nos ofrece varias formas de invocar un mtodo arbitrario de algn objeto: Object#send, Method#call y las diferentes versiones de eval. Es posible que se prefiera utilizar cualquiera de estas tcnicas en funcin de las necesidades, pero hay que teneren cuenta que eval es significativamente ms lento que los otros (o, para los lectores optimistas, send y call son significativamente ms rpidos que eval). require benchmark include Benchmark test = Stormy Weather m = test.method(:length) n = 100000 bm(12) {|x| x.report(call) { n.times { m.call } } x.report(send) { n.times { test.send(:length) } } x.report(eval) { n.times { eval test.length } } } produce: user system total real call 0.250000 0.000000 0.250000 (0.340967) send 0.210000 0.000000 0.210000 (0.254237) eval 1.410000 0.000000 1.410000 (1.656809)

Sistema de Ganchos
Un gancho es una tcnica que le permite atrapar algn evento Ruby, tal como la creacin de objetos. La tcnica de gancho ms simple en Ruby es interceptar las llamadas a mtodos en las clases del sistema. Tal vez se quiere registrar todos los comandos del sistema operativo que ejecuta el programa. Basta con cambiar el nombre del mtodo Kernel.system y sustituirlo por uno a elegir que registre tanto los 279

comandos como las llamadas al mtodo Kernel original. module Kernel alias_method :old_system, :system def system(*args) result = old_system(*args) puts system(#{args.join(, )}) returned #{result} result end end system(date) system(kangaroo, -hop 10, skippy) produce: Thu Aug 26 22:37:22 CDT 2004 system(date) returned true system(kangaroo, -hop 10, skippy) returned false Un gancho potente es la captura de los objetos que se crean. Si se puede estar presente en cada objeto que nace, se puede hacer todo tipo de cosas interesantes: se pueden envolver, aadirles y eliminarles mtodos, aadirles a contenedores que implementan persistencia, lo que sea. Vamos a mostrar un ejemplo sencillo: vamos a aadir una marca de tiempo a cada objeto que se crea. Primero, aadiremos un atributo timestamp a cada objeto del sistema. Podemos hacer esto hackeando la mismsima clase Object. class Object attr_accessor :timestamp end Despus tenemos que enganchar la creacin de objetos para agregarles esta marca de tiempo. Una manera de hacer esto es mediante nuestro truco de cambiar el nombre de mtodo en Class#new, el mtodo al que se llama para reservar espacio para un nuevo objeto. La tcnica no es perfecta, algunos objetos integrados, tales como cadenas literales, se construyen sin llamar a new --pero va a funcionar bien para los objetos que escribamos. class Class alias_method :old_new, :new def new(*args) result = old_new(*args) result.timestamp = Time.now result end end Por ltimo, podemos realizar una prueba. Vamos a crear un par de objetos con unos pocos milisegundos de diferencia y a comprobar sus marcas de tiempo. class Test end obj1 = Test.new sleep(0.002) obj2 = Test.new obj1.timestamp.to_f obj2.timestamp.to_f -< -> 1093577843.1312 1093577843.14144

Todo esto cambiar el nombre de mtodo est muy bien y realmente funciona, pero hay que tener en cuenta que puede causar problemas. Si una subclase hace lo mismo, y cambia el nombre de los mtodos utilizando los mismos nombres, se terminar con un bucle infinito. Se puede evitar esto con alias de los

280

mtodos a un nombre de smbolo nico o mediante el uso de una nomenclatura coherente. Hay otras formas ms refinadas de obtener el corazn de un programa en ejecucin. Ruby ofrece varios mtodos de retorno de llamada que permiten atrapar ciertos eventos de una manera controlada.

Retornos de Llamada en Tiempo de Ejecucin (Runtime Callbacks)


Usted puede ser notificado cada vez que ocurre uno de los siguientes eventos:

Evento Mtodo de Retorno de llamada


Adicin de un mtodo de instancia Module#method_added Eliminacin de un mtodo de instancia Module#method_removed Mtodo de instancia no definido Module#method_undefined Adicin de un mtodo singleton Kernel.singleton_method_added Eliminacin de un mtodo singleton Kernel.singleton_method_removed Mtodo singleton no definido Kernel.singleton_method_undefineded Se crea una subclase Class#inherited Se crea un mtodo mixin Module#extend_object Por defecto, estos mtodos no hacen nada. Si se define un mtodo de retorno en una clase, va a ser invocado de forma automtica. La secuencia real de llamada se ilustra en las descripciones de librera para cada mtodo de retorno de llamada. Hacer un seguimiento del mtodo de creacin y la utilizacin de clases y mdulos permite crear una imagen exacta del estado dinmico del programa. Esto puede ser importante. Por ejemplo, puede haber se escrito cdigo que envuelva todos los mtodos de una clase, tal vez para aadir soporte transaccional o para implementar alguna forma de delegacin. Esto slo sera la mitad del trabajo: la naturaleza dinmica de Ruby hace que los usuarios de esta clase pudieran aadirle nuevos mtodos en cualquier momento. Utilizando estas devoluciones de llamada, se puede escribir cdigo que envuelva estos nuevos mtodos a medida que se crean.

Seguimiento de la Ejecucin del Programa

Mientras nos estamos divirtiendo con este tema sobre la reflexin de los objetos y las clases en nuestros programas, no nos olvidemos de las humildes declaraciones que hacen que nuestro cdigo en realidad funcione eficazmente. Resulta que Ruby nos permite tambin mirar en detalle estas declaraciones. En primer lugar, se puede ver al intrprete como ejecuta el cdigo. set_trace_func ejecuta un Proc con todo tipo de jugosa informacin de depuracin siempre que se ejecuta una lnea fuente nueva, llamadas a mtodos, objetos creados, etc. Se encontrar una descripcin completa ms adelante pero aqu vamos a ver una muestra. class Test def test a = 1 b = 2 end end set_trace_func proc {|event, file, line, id, binding, classname| printf %8s %s:%-2d %10s %8s\n, event, file, line, id, classname } t = Test.new t.test produce: line prog.rb:11 false c-call prog.rb:11 new Class c-call prog.rb:11 initialize Object 281

c-return prog.rb:11 initialize Object c-return prog.rb:11 new Class line prog.rb:12 false call prog.rb:2 test Test line prog.rb:3 test Test line prog.rb:4 test Test return prog.rb:4 test Test El mtodo trace_var le permite agregar un gancho a una variable global. Cada vez que se haga una asignacin a nivel global, se invoca el objeto Proc.

Cmo Llegamos Hasta Aqu?


Una buena pregunta, y uno se la hace con frecuencia. Lapsos mentales a un lado, en Ruby al menos se puede saber exactamente cmo se lleg all con el mtodo caller, que devuelve un array de objetos String que representa la pila de llamadas en curso. def cat_a puts caller.join(\n) end def cat_b cat_a end def cat_c cat_b end cat_c produce: prog.rb:5:in `cat_b prog.rb:8:in `cat_c prog.rb:10 Una vez que haya averiguado cmo lleg all, el dnde ir ahora depende de usted.

Cdigo Fuente
Ruby ejecuta programas desde antiguos ficheros planos. Se pueden ver estos archivos para examinar el cdigo fuente que compone el programa con una de una serie de tcnicas. La variable especial __FILE__ contiene el nombre del archivo fuente actual. Esto lleva a un muy corto (si hacer trampa) Quine --un programa que produce su propio cdigo fuente como salida nica. print File.read(__FILE__) El mtodo Kernel.caller devuelve la pila de llamadas --la lista de la pila existente en el momento en que se llam al mtodo. Cada entrada de esta lista comienza con un nombre de archivo, dos puntos y un nmero de lnea en ese archivo. Se puede analizar esta informacin para buscar en el cdigo fuente. En el siguiente ejemplo, tenemos un programa principal, main.rb, que llama a un mtodo en un archivo separado, sub.rb. Este mtodo invoca a su vez un bloque, donde se recorre la pila de llamadas y escribe las lneas fuente en cuestin. Ntese el uso de un hash del contenido del archivo, indexado por el nombre de archivo. Aqu est el cdigo que hace el volcado de la pila de llamadas, incluyendo informacin de la fuente.

def dump_call_stack file_contents = {} puts File Line Source Line puts -------------------------------------------------------+------+-------------------- 282

caller.each do |position| next unless position =~ /\A(.*?):(\d+)/ file = $1 line = Integer($2) file_contents[file] ||= File.readlines(file) printf(%-25s:%3d - %s, file, line, file_contents[file][line-1].lstrip) end end El (trivial) archivo sub.rb contiene un solo mtodo.

def sub_method(v1, v2) main_method(v1*3, v2*6) end Y aqu est el programa principal, que invoca el volcado de pila despus de haber sido llamado por el submtodo. require sub require stack_dumper def main_method(arg1, arg2) dump_call_stack end sub_method(123, cat) produce: File Line Source Line ----------------------------------+------+-------------------code/caller/main.rb : 5 - dump_call_stack ./code/caller/sub.rb : 2 - main_method(v1*3, v2*6) code/caller/main.rb : 8 - sub_method(123, cat) La constante SCRIPT_LINES__ est estrechamente relacionada con esta tcnica. Si un programa inicializa una constante llamada SCRIPT_LINES__ con un hash, este hash recibe el cdigo fuente de todos los archivos cargados posteriormente en el intrprete a utilizar require o load. Vea ms adelante Kernel.require para un ejemplo.

Ruby Formateado (marshaling) y Distribudo


Java ofrece la posibilidad de serializar objetos, lo que permite guardarlos en algn lugar y reconstruirlos cuando sea necesario. Se puede utilizar esta funcin, por ejemplo, para salvar un rbol de objetos que representan una parte del estado de la aplicacin --un documento, un dibujo en CAD, una pieza de msica, etc. Ruby llama a este tipo de serializacin marshaling (hay que pensar en una estacin de clasificacin de ferrocarril, donde los automviles se montan secuencialmente en un tren completo, que luego es enviado a alguna parte). Salvar un objeto y todos o algunos de sus componentes se realiza mediante el mtodo Marshal.dump. Por lo general, se vuelca un rbol de objetos completo a partir de un objeto dado. Ms tarde, se puede reconstituir el objeto utilizando Marshal.load. He aqu un breve ejemplo. Contamos con una clase Chord que contiene una coleccin de notas musicales. Nos gustara salvar fuera un acorde particularmente fabuloso para poderlo enviar por correo electrnico a un par de cientos de nuestros amigos ms cercanos. As pueden cargarlo en su copia de Ruby y disfrutarlo tambin. Vamos a empezar con las clases Note y Chord. Note = Struct.new(:value) class Note def to_s value.to_s

283

end end class def end def end end

Chord initialize(arr) @arr = arr play @arr.join(-)

Ahora vamos a crear nuestra obra maestra y a utilizar Marshal.dump para salvarla a una versin serializada en el disco. c = Chord.new( [ Note.new(G), Note.new(Bb), Note.new(Db), Note.new(E) ] ) File.open(posterity, w+) do |f| Marshal.dump(c, f) end Finalmente, para que nuestros nietos lo lean y se transporten por la grandiosidad de nuestra creacin.

File.open(posterity) do |f| chord = Marshal.load(f) end chord.play -> G-Bb-Db-E

Estrategia de Serializacin Personalizada


No todos los objetos pueden ser objeto de volcado: bindings, objetos de procedimiento, instancias de la clase IO y objetos singleton no se pueden salvar fuera del entorno de ejecucin Ruby (se produce un TypeError si se intenta). Incluso si su objeto no contiene uno de estos objetos problemticos, es posible que desee tomar el control de la serializacin de objetos por s mismo. Marshal ofrece los ganchos que se necesitan. En los objetos que requieran serializacin personalizada, simplemente hay aplicar dos mtodos de instancia: marshal_dump, que escribe el objeto a una cadena y marshal_load, que lee una cadena que se habr creado previamente y la utiliza para inicializar un objeto recin asignado. (En versiones anteriores de Ruby tendr que utilizar los mtodos llamados _dump y _Load, pero las nuevas versiones desempean mejor con el nuevo esquema de asignacin de Ruby 1.8). El mtodo de instancia Marshal_dump debe devolver un objeto que representa el estado de volcado. Cuando el objeto es reconstituido posteriormente utilizando Marshal.load que llama a este objeto, lo utilizar para establecer el estado de su receptor --que se llevar a cabo en el contexto de una asignacin pero no inicializa el objeto de la clase que se est cargando. Por ejemplo, aqu hay una clase de ejemplo que define su propia serializacin. Por las razones que sean, en Special no se quiere salvar uno de sus miembros de datos interno, @volatile. El autor ha decidido serializar las otras dos variables de instancia en una matriz. class def end def end def Special initialize(valuable, volatile, precious) @valuable = valuable @volatile = volatile @precious = precious marshal_dump [ @valuable, @precious ] marshal_load(variables)

284

@valuable = variables[0] @precious = variables[1] @volatile = unknown end def to_s #@valuable #@volatile #@precious end end obj = Special.new(Hello, there, World) puts Before: obj = #{obj} data = Marshal.dump(obj) obj = Marshal.load(data) puts After: obj = #{obj} produce: Before: obj = Hello there World After: obj = Hello unknown World Para ms detalles, consulte la seccin de referencia Marshal ms adelante.

YAML para el Formateado


El mdulo Marshal est integrado en el intrprete y utiliza un formato binario para almacenar los objetos externos. Aunque rpido, este formato binario tiene una gran desventaja: si el intrprete cambia de manera significativa, el formato marshal binario tambin puede cambiar, y los antiguos archivos volcados ya no se podrn cargar. Una alternativa es utilizar un formato externo menos exigente, preferentemente uno de texto en lugar de archivos binarios. Una opcin suministrada como librera estndar de Ruby 1.8 es YAML (http://www. yaml.org. YAML es sinnimo de YAML no es el lenguaje de marcado --YAML Aint Markup Language--, pero esto no parece importante. Podemos adaptar nuestro ejemplo marshal anterior para utilizar YAML. En lugar de aplicar mtodos especficos de carga y volcado para controlar el proceso marshal, simplemente definimos el mtodo to_yaml_properties, que devuelve una lista de variables de instancia para ser salvadas. require yaml class Special def initialize(valuable, volatile, precious) @valuable = valuable @volatile = volatile @precious = precious end def to_yaml_properties %w{ @precious @valuable } end def to_s #@valuable #@volatile #@precious end end obj = Special.new(Hello, there, World) puts Before: obj = #{obj} data = YAML.dump(obj) obj = YAML.load(data) puts After: obj = #{obj} produce: Before: obj = Hello there World

285

After: obj = Hello

World

Podemos echar un vistazo a lo que crea YAML como forma serializada del objeto --es bastante simple.

obj = Special.new(Hello, there, World) puts YAML.dump(obj) produce: --- !ruby/object:Special precious: World valuable: Hello

Ruby Distribudo
Ya que puede serializar un objeto o un conjunto de objetos en una forma adecuada para almacenarlos fuera de proceso, podemos utilizar esta capacidad para la transmisin de objetos de un proceso a otro. Unimos esto con la poderosa capacidad de las redes y, voil: Se tiene un sistema de objetos distribuidos. Para ahorrarle la molestia de tener que escribir el cdigo, le sugerimos que utilice la librera de Ruby distribuido (Distributed Ruby library -drb-) de Masatoshi Seki, que ya est disponible como librera estndar de Ruby. Utilizando drb, un proceso Ruby puede actuar como un servidor, como un cliente o como ambos. Un servidor drb acta como fuente de objetos, mientras que un cliente es un usuario de esos objetos. Para el cliente, los objetos aparecen como locales, pero en realidad el cdigo est siendo ejecutado de forma remota. Un servidor inicia un servicio mediante la asociacin de un objeto con un puerto determinado. Entonces se crean hilos internamente para manejar las peticiones entrantes en ese puerto, a fin de recordar la unin al hilo drb antes de salir del programa. require drb class TestServer def add(*args) args.inject {|n,v| n + v} end end server = TestServer.new DRb.start_service(druby://localhost:9000, server) DRb.thread.join # No salir todava! Un cliente drb sencillo, simplemente crea un objeto drb local y la asocia con el objeto en el servidor remoto. El objeto local es un proxy. require drb DRb.start_service() obj = DRbObject.new(nil, druby://localhost:9000) # Ahora utilizamos obj puts Sum is: #{obj.add(1, 2, 3)} El cliente se conecta al servidor y llama al mtodo add, que utiliza la magia de inyectar a la suma sus argumentos. Se devuelve el resultado, que imprime el cliente. Sum is: 6 El argumento inicial nil a DRbObject indica que se desea adjuntar un nuevo objeto distribuido. Tambin se puede utilizar un objeto existente. Ho Hum, dice usted. Esto suena como RMI de Java, o CORBA, o lo que sea. S, se trata de un mecanismo de objetos distribuidos funcional--pero est escrito en tan slo unos pocos cientos de lneas de

286

cdigo Ruby. Nada de C, nada lujoso, simple y viejo cdigo plano Ruby. Por supuesto, no tiene servicio de nombres u operador de servicios, ni nada parecido a lo que vemos en CORBA, pero es sencillo y razonablemente rpido. En un sistema Powerbook de 1GHz, este cdigo de ejemplo ejecuta alrededor de unas 500 llamadas de mensajes remotos por segundo. Y, si le gusta el aspecto de JavaSpaces de Sun, la base de la arquitectura JINI, le interesar saber que el drb se distribuye con un breve mdulo que realiza el mismo tipo de cosas. JavaSpaces se basa en una tecnologa llamada Linda. Para probar que su autor japons tiene sentido del humor, la versin de Ruby de Linda se conoce como Rinda. Si le gusta la mensajera remota gruesa, tonta e interoperable, tambin podra buscar en las bibliotecas de SOAP distribuidas con Ruby (SOAP hace mucho tiempo abandon la parte simple de sus siglas. La implementacin Ruby de SOAP es una maravillosa pieza de trabajo).

Tiempo de Compilacin? Tiempo de Ejecucin? En Cualquier Momento!


Lo importante que hay recordar acerca de Ruby es que no hay una gran diferencia entre tiempo de compilacin y tiempo de ejecucin. Es todo lo mismo. Puede agregar cdigo a un proceso en ejecucin. Puede volver a definir los mtodos sobre la marcha, cambiar su mbito de public a private, etc. Usted puede incluso alterar los tipos bsicos, como Class y Object. Una vez que uno se acostumbra a esta flexibilidad, es difcil volver a un lenguaje esttico como C++ o incluso a un lenguaje medio esttico como Java. Pero, por qu querra uno hacer eso?

Referencia de Librera Ruby


Clases y Mdulos Integrados
Este captulo documenta las clases y mdulos integrados en el estndar del lenguaje Ruby. Estn disponibles para todos los programas de Ruby automticamente sin necesidad de require. Esta seccin no contiene las diversas variables y constantes predefinidas, las cuales se enumeraron anteriormente. Ms adelante se muestran las invocaciones de ejemplo para cada mtodo.

new String.new( some_string ) new_string


Esta descripcin muestra un mtodo de clase que se invoca como String.new. El parmetro en cursiva indica que se pasa una sola cadena, y la flecha indica que se devuelve otra cadena desde el mtodo. Como este valor de retorno tiene un nombre diferente que el del parmetro, representa un objeto diferente. En la ilustracin de los mtodos de instancia, se muestra un ejemplo de llamada con un nombre de objeto ficticio en cursiva, como el receptor.

each

str.each( sep=$/ ) {| record | block } str

El parmetro de String#each ha demostrado tener un valor por defecto. Si se llama a each sin parmetros, se utilizar el valor de $/ . Este mtodo es un iterador, por lo que la llamada es seguida por un bloque. String#each devuelve su receptor, por lo que el nombre del mismo (str en este caso) aparece de nuevo despus de la flecha. Algunos mtodos tienen parmetros opcionales. Se muestran estos parmetros entre parntesis angulares, < xxx >. (Adems, se usa la notacin < xxx > * para indicar cero o ms ocurrencias de xxx, y se usa < xxx > + para indicar una o ms ocurrencias de xxx).

287

index

self.index( str < , offset > ) pos o nil

Por ltimo, para mtodos que se pueden llamar de varias formas diferentes, se lista cada forma en una lnea separada. Clase

Array < Object


Las matrices son colecciones ordenadas de cualquier objeto indexadas con enteros. L ndexacin de un array empieza en 0, como en C o Java. Un ndice negativo se asume que es relativo al final de la matriz, es decir, un ndice de -1 indica el ltimo elemento de la matriz, -2 es el siguiente al ltimo elemento de la matriz y as sucesivamente. Se mezcla en

Enumerable:

all?, any?, collect, detect, each_with_index, entries, find, find_all, grep, include?, inject, map, max, member? , min, partition, reject, select, sort, sort_ by, to_a, zip Mtodos de Clase

[ ]

Array[ < obj >* ] una_matriz

Devuelve una matriz nueva llenada con los objetos dados. Equivalente a la forma del operador Array. [](...) Array.[]( 1, a, /^A/ ) Array[ 1, a, /^A/ ] [ 1, a, /^A/ ] -> -> -> [1, a, /^A/] [1, a, /^A/] [1, a, /^A/]

new

Array.new Array.new ( size=0, obj=nil ) Array.new( array ) Array.new( size ) {| i | block }

una_matriz una_matriz una_matriz una_matriz

Devuelve una matriz nueva. En la primera forma, la nueva matriz est vaca. En el segunda, se crea con copias tamao size de obj (es decir, las referencias de tamao al mismo obj). En la tercera forma se crea una copia de la matriz pasada como parmetro (la matriz se genera llamando a to_ary en el parmetro). En la ltima forma, se crea una matriz del tamao dado. Cada elemento de este vector se calcula pasando el ndice del elemento al bloque dado y almacenando el valor de retorno. Array.new Array.new(2) Array.new(5, A) -> -> -> [] [nil, nil] [A, A, A, A, A]

# slo se creauna instancia del objeto por defecto a = Array.new(2, Hash.new) a[0][cat] = feline a -> [{cat=>feline}, {cat=>feline}] a[1][cat] = Felix a -> [{cat=>Felix}, {cat=>Felix}] a = Array.new(2) { Hash.new } # varias instancias a[0][cat] = feline

288

->

[{cat=>feline}, {}]

squares = Array.new(5) {|i| i*i} squares -> [0, 1, 4, 9, 16] copy = Array.new(squares) # inicializado por copia squares[5] = 25 squares -> [0, 1, 4, 9, 16, 25] copy -> [0, 1, 4, 9, 16] Mtodos de Instancia

&

arr & otra_matriz una_matriz

Interseccin de conjuntos --Devuelve una nueva matriz que contiene elementos comunes a las dos matrices, sin duplicados. Las reglas para la comparacin de los elementos son los mismas que para las claves hash. Si se necesita un comportamiento similar a los conjuntos, ver la librera de la clase Set. [ 1, 1, 3, 5 ] & [ 1, 2, 3 ] -> [1, 3]

* arr * str una_cadena

arr * int una_matriz

Repeticin --Con un argumento responde a to_str, equivalente a arr.join (str). De otra manera, devuelve una nueva matriz construida mediante la concatenacin de int copias a arr. [ 1, 2, 3 ] * 3 [ 1, 2, 3 ] * -- -> -> [1, 2, 3, 1, 2, 3, 1, 2, 3] 1--2--3

+ arr + otra_matriz una_matriz


Concatenacin --Devuelve una nueva matriz construida mediante la concatenacin de las dos matrices para producir una tercera matriz. [ 1, 2, 3 ] + [ 4, 5 ] -> [1, 2, 3, 4, 5]

arr - otra_matriz una_matriz


Diferencia --Devuelve una nueva matriz que es una copia de la matriz original, eliminando todos los elementos que tambin aparecen en otra_matriz. Si se necesita un comportamiento similar a los conjuntos, ver la librera de la clase Set. [ 1, 1, 2, 2, 3, 3, 4, 5 ] - [1, 2, 4 ] -> [3, 3, 5]

<< arr << obj arr


Adicin --Empuja el objeto dado al final de la matriz. Esta expresin devuelve la propia matriz, por lo varias adiciones pueden ser encadenadas juntos. Vase tambin Array#push. [ 1, 2 ] << c << d << [ 3, 4 ] -> [1, 2, c, d, [3, 4]]

<=>

arr <=> otra_matriz 1, 0, +1

Comparacin --Devuelve un entero -1, 0 o +1 si la matriz es menor, igual o mayor que otra_matriz. Cada objeto en cada matriz es comparado (utilizando <=>). Si algn valor no es igual, entonces esta desigualdad es el valor de retorno. Si todos los valores encontrados son iguales, entonces el retorno se basa en una comparacin de las longitudes de matriz. De este modo, dos matrices son iguales, de acuerdo a Array#<=> si y slo si tienen la misma longitud y el valor de cada elemento es igual al valor del correspondiente elemento de la otra matriz. 289

[ a, a, c ] <=> [ a, b, c ] [ 1, 2, 3, 4, 5, 6 ] <=> [ 1, 2 ]

-> ->

-1 1

==

arr == obj true o false

Igualdad --Dos matrices son iguales si contienen el mismo nmero de elementos y si cada elemento es igual a (de acuerdo a Object#==) el correspondiente elemento de la otra matriz. Si obj no es una matriz, trata de convertirlo utilizando to_ary y retorna obj==arr. [ a, c ] == [ a, c, 7 ] [ a, c, 7 ] == [ a, c, 7 ] [ a, c, 7 ] == [ a, d, f ] -> -> -> false true false

[ ]

arr[int] obj o nil arr[start, length] una_matriz o nil arr[range] una_matriz o nil

Referencia a Elemento --Devuelve el elemento situado int en el ndice int, devuelve una submatriz comenzando en el ndice start y continuando hasta length elementos, o devuelve una submatriz especificado por range. Los ndices negativos cuentan hacia atrs desde el final de la matriz (-1 es el ltimo elemento). Devuelve nil si el ndice del primer elemento seleccionado es mayor que el tamao de la matriz. Si el ndice de inicio es igual al tamao de la matriz y se d un parmetro length o range, se devuelve una matriz vaca. Equivalente a Array#slice. a = [ a, b, c, a[2] + a[0] + a[1] a[6] a[1, 2] a[1..3] a[4..7] a[6..10] a[-3, 3] # casos especiales a[5] -> nil a[5, 1] -> [] a[5..10] -> [] d, -> -> -> -> -> -> -> e ] cab nil [b, c] [b, c, d] [e] nil [c, d, e]

[ ]=

arr[int] = obj obj arr[start, length] = obj obj arr[range] = obj obj

Asignacin de elementos --Establece el elemento en el ndice int, sustituye a una submatriz comenzando en el ndice start y continuando hasta length elementos, o reemplaza a una submatriz especificada por range. Si int es mayor que la capacidad actual de la matriz, la matriz crece de forma automtica. Un int negativo cuenta hacia atrs desde el final de la matriz. Inserta los elementos, si la longitud es igual a cero. Si obj es nil, borra elementos de arr. Si obj es una matriz, la forma con ndice nico insertar esta matriz en arr y las formas con una longitud o con un rango reemplazar los elementos dados en arr con el contenido de la matriz. Se lanza un IndexError si los ndices negativos van ms all del principio de la matriz. Vase tambin Array#push y Array#unshift. a = Array.new a[4] = 4; a a[0] = [ 1, 2, 3 ]; a a[0, 3] = [ a, b, c ]; a a[1..2] = [ 1, 2 ]; a a[0, 2] = ?; a a[0..2] = A; a -> -> -> -> -> -> -> [] [nil, nil, nil, nil, 4] [[1, 2, 3], nil, nil, nil, 4] [a, b, c, nil, 4] [a, 1, 2, nil, 4] [?, 2, nil, 4] [A, 4]

290

a[-1] = Z; a[1..-1] = nil;

a a

-> ->

[A, Z] [A]

arr | otra_matriz una_matriz

Unin --Devuelve una matriz nueva de la unin de una matriz con otra_matriz, eliminando duplicados. Las reglas para la comparacin de los elementos son las mismas que para las claves hash. Si se necesita un comportamiento similar a los conjuntos, ver la librera de la clase Set. [ a, b, c ] | [ c, d, a ] -> [a, b, c, d]

assoc

arr.assoc( obj ) una_matriz o nil

Busca en una matriz cuyos elementos son tambin matrices, comparando obj con el primer elemento de cada matriz contenida utiliznado obj.==. Devuelve la primera matriz contenida que coincida (es decir, la primera matriz associada) o nil si no hay coincidencia. Vase tambin Array#rassoc. s1 = [ colors, red, blue, green ] s2 = [ letters, a, b, c ] s3 = foo a = [ s1, s2, s3 ] a.assoc(letters) -> [letters, a, b, c] a.assoc(foo) -> nil

at

arr.at( int ) obj o nil

Devuelve el elemento en el ndice int. Un ndice negativo cuenta desde el final de arr. Devuelve nil si el ndice est fuera de rango. Vase tambin Array#[]. (Array#at es ligeramente ms rpido que el Array#[], ya que no acepta rangos, etc.) a = [ a, b, c, d, e ] a.at(0) -> a a.at(-1) -> e

clear
Elimina todos los elementos de arr. a = [ a, b, c, d, e ] a.clear -> []

arr.clear arr

collect!

arr.collect! {| obj | block } arr

Invoca block una vez para cada elemento de arr, sustituyendo el elemento con el valor devuelto por el bloque. Vase tambin Enumerable#collect. a = [ a, b, c, d ] a.collect! {|x| x + ! } -> a -> [a!, b!, c!, d!] [a!, b!, c!, d!]

compact
[ a, nil, b, nil, c, nil ].compact ->

arr.compact una_matriz
[a, b, c]

Devuelve una copia de arr con todos los elementos nil eliminados.

compact!

arr.compact! arr o nil


291

Elimina los elementos nil de arr. Retorna nil si no se hicieron cambios.

[ a, nil, b, nil, c ].compact! [ a, b, c ].compact!

-> ->

[a, b, c] nil

concat
Aade los elementos de otra_matriz en arr. -> [ a, b ].concat( [c, d] )

arr.concat( otra_matriz ) arr


[a, b, c, d]

delete

arr.delete( obj ) obj o nil arr.delete( obj ) { block } obj o nil

Elimina elementos de arr que son iguales a obj. Si no se encuentra el elemento, devuelve nil. Si se d el bloque de cdigo opcional, devuelve el resultado del bloque si no se encuentra el elemento. a = [ a, b, b, b, c ] a.delete(b) -> b a -> [a, c] a.delete(z) -> nil a.delete(z) { not found } -> not found

delete_at

arr.delete_at( index ) obj o nil

Elimina el elemento en el ndice especificado, devolviendo ese elemento, o nil si el ndice est fuera de rango. Vase tambin Array#slice!. a = %w( ant bat cat dog ) a.delete_at(2) -> cat a -> [ant, bat, dog] a.delete_at(99) -> nil

delete_if
a = [ a, b, c ] a.delete_if {|x| x >= b }

arr.delete_if {| item | block } arr

Elimina todos los elementos de arr para los cules block evala true. -> [a]

each
a = [ a, b, c ] a.each {|x| print x, -- } produce: a -- b -- c

arr.each {| item | block } arr

Llamadas a block una vez por cada elemento de arr, pasando ese elemento como un parmetro.

each_index

arr.each_index {| index | block } arr

Lo mismo que Array#each uno, pero pasa el ndice del elemento en lugar del propio elemento.

a = [ a, b, c ] a.each_index {|x| print x, -- } produce:

292

1 -- 2 -- 3

empty?
Devuelve true si la matriz arr no contiene elementos. [].empty? [ 1, 2, 3 ].empty? -> -> true false

arr.empty? true o false

eql?

arr.eql?( otro ) true o false

Devuelve true si arr y otro son el mismo objeto o si otro es un objeto de la clase Array con la misma longitud y contenido que arr. Los elementos de cada matriz se comparan utilizando Object#eql?.Vase tambin Array#<=>. [ a, b, c ].eql?([a, b, c]) [ a, b, c ].eql?([a, b]) [ a, b, c ].eql?([b, c, d]) -> -> -> true false false

fetch arr.fetch( index ) obj arr.fetch( index, default ) obj arr.fetch( index ) {| i | block } obj
Intenta devolver el elemento en la posicin index. Si el ndice est fuera de la matriz, la primera forma produce una excepcin IndexError, la segunda forma devuelve default, y la tercera forma devuelve el valor de la invocacin del bloque, al que se le pasa el ndice. Los valores negativos del ndice cuentan desde el final de la matriz. a = [ 11, 22, 33, 44 ] a.fetch(1) a.fetch(-1) a.fetch(-1, cat) a.fetch(4, cat) a.fetch(4) {|i| i*i } -> -> -> -> -> 22 44 44 cat 16

fill

arr.fill( obj arr.fill( obj, start < , length > arr.fill( obj, range arr.fill {| i | block arr.fill( start < , length > ) {| i | block arr.fill( range ) {| i | block

) ) ) } } }

arr arr arr arr arr arr

Las tres primeras formas establecen los elementos seleccionados de arr (que puede ser toda la matriz) para obj. Un start nil es equivalente a cero. Una longitud nil es equivalente a arr.length. Las tres ltimas formas llenan la matriz con el valor del bloque, al que se le pasa el ndice absoluto de cada elemento por cubrir. a = [ a, b, c, d ] a.fill(x) -> [x, x, x, x] a.fill(z, 2, 2) -> [x, x, z, z] a.fill(y, 0..1) -> [y, y, z, z] a.fill {|i| i*i} -> [0, 1, 4, 9] a.fill(-3) {|i| i+100} -> [0, 101, 102, 103]

first

arr.first obj o nil arr.first( count ) una_matriz


293

Devuelve el primer elemento o los primeros elementos del recuento de arr. Si la matriz est vaca, la

primera forma retorna nil y la segunda devuelve una matriz vaca. a = [ q, r, s, t ] a.first -> q a.first(1) -> [q] a.first(3) -> [q, r, s]

flatten

arr.flatten una_matriz

Devuelve una nueva matriz que es un aplanamiento de una dimensin de esta matriz (recursivamente). Es decir, para cada elemento que es una matriz, extrae sus elementos en la nueva matriz. s = [ 1, 2, 3 ] t = [ 4, 5, 6, [7, 8] ] a = [ s, t, 9, 10 ] a.flatten -> -> -> -> [1, 2, 3] [4, 5, 6, [7, 8]] [[1, 2, 3], [4, 5, 6, [7, 8]], 9, 10] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

flatten!

arr.flatten! arr o nil

Igual que el Array#flatten, pero modifica el receptor en su lugar. Retorna nil si no se realizaron modificaciones (es decir, arr no contiene submatrices). a = [ 1, 2, a.flatten! a.flatten! a [3, -> -> -> [4, 5] ] ] [1, 2, 3, 4, 5] nil [1, 2, 3, 4, 5]

include?

arr.include?( obj ) true o false

Devuelve true si el objeto dado est presente en arr (es decir, si cualquier objeto == obj), false en caso contrario. a = [ a, b, c ] a.include?(b) -> true a.include?(z) -> false

indexes arr.indexes( i1, i2, ... iN ) una_matriz


Obsoleto, utilizar Array#values_at.

indices arr.indices( i1, i2, ... iN ) una_matriz


Obsoleto, utilizar Array#values_at

insert

arr.insert( index, < obj >+ ) arr

Si el ndice no es negativo, se inserta el valor dado antes del elemento con el ndice dado. Si el ndice es -1, aade los valores a arr. De lo contrario inserta los valores despus del elemento con el ndice dado. a = %w{ a b c d } a.insert(2, 99) a.insert(-2, 1, 2, 3) a.insert(-1, e) -> -> -> [a, b, 99, c, d] [a, b, 99, c, 1, 2, 3, d] [a, b, 99, c, 1, 2, 3, d, e]

join

arr.join( separator=$, ) str

Devuelve una cadena creada mediante la concatenacin de cada elemento de la matriz separando cada uno por el separador.

294

[ a, b, c ].join [ a, b, c ].join(-)

-> ->

abc a-b-c

last

Devuelve el ltimo elemento o elementos ltimos del recuento de arr. Si la matriz est vaca, la primera forma devuelve nil, la segunda una matriz vaca. [ w, x, y, z ].last [ w, x, y, z ].last(1) [ w, x, y, z ].last(3) -> -> -> z [z] [x, y, z]

arr.last obj o nil arr.last( count ) una_matriz

length
Devuelve el nmero de elementos de arr. Vase tambin Array#nitems. -> 5 [ 1, nil, 3, nil, 5 ].length

arr.length int

map!
Sinnimo de Array#collect!.

arr.map! {| obj | block } arr arr.nitems int


-> 3

nitems
[ 1, nil, 3, nil, 5 ].nitems

Devuelve el nmero de elementos no nil de arr. Vease tambin Array#length.

pack

arr.pack ( template ) binary_string

Empaqueta los contenidos de arr en una secuencia binaria de acuerdo a las directivas en template (ver la Tabla 9 en la pgina siguiente). Las directivas A, a y Z puede ser seguido por un recuento, que da el ancho del campo resultante. El resto de directivas tambin pueden tomar un conteo, indicando el nmero de elementos a convertir de la matriz. Si el contador es un asterisco (*), se convertirn todos los elementos restantes de la matriz. Cualquiera de las directivas sSiIlL pueden ser seguida por un guin bajo (_) para utilizar el tamao nativo de la plataforma subyacente para el tipo especificado. De otra manera, utilizar una plataforma independiente del tamao. Los espacios son ignorados en la cadena de plantilla. Los comentarios comenzando con # al siguiente salto de lnea o al final de la cadena tambin se ignoran. Vase tambin String#unpack. a = [ a, b, c ] n = [ 65, 66, 67 ] a.pack(A3A3A3) -> a.pack(a3a3a3) -> n.pack(ccc) ->

abc a\000\000b\000\000c\000\000 ABC

pop
a = [ a, m, z ] a.pop -> z a -> [a, m]

arr.pop obj o nil

Elimina el ltimo elemento de arr y lo devuelve, o retorna nil si la matriz est vaca.

295

1 Los octetos de un entero BER-comprimido representan un entero sin signo en base de 128, con el dgito ms significativo en primer lugar y con tan pocos dgitos como sea posible. Se establece el octavo bit (el bit alto) en cada byte, excepto el ltimo (Self-Describing Binary Data Representation, MacLeod)

push
Aade el argumento o argumentos dados a arr. -> a = [ a, b, c ] a.push(d, e, f)

arr.push( < obj >* ) arr

[a, b, c, d, e, f]

rassoc

arr.rassoc( key ) una_matriz o nil

Busca en la matriz elementos que son tambin matrices. Compara key con el segundo elemento de cada matriz contenida con ==. Devuelve la primera matriz contenida que coincide. Vase tambin Array#assoc.

296

a = [ [ 1, one], [2, two], [3, three], [ii, two] ] a.rassoc(two) -> [2, two] a.rassoc(four) -> nil

reject!

arr.reject! { block } item arr o nil

Equivalente a Array#delete_if, pero retorna nil si no se hicieron cambios. Ver tambin Enumerable#reject.

replace
a = [ a, b, c, d, e ] a.replace([ x, y, z ]) -> a ->

arr.replace( otra_matriz ) arr

Reemplaza el contenido de arr con los contenidos de otra_matriz, truncando o ampliando si es necesario. [x, y, z] [x, y, z]

reverse
[ a, b, c ].reverse [ 1 ].reverse -> -> [c, b, a] [1]

arr.reverse una_matriz

Devuelve una nueva matriz con los elementos de arr en orden inverso.

reverse!
Invierte arr. a = [ a, b, a.reverse! a [ 1 ].reverse! c ] -> [c, b, a] -> [c, b, a] -> [1]

arr.reverse! arr

reverse_each

arr.reverse_each {| item | block } arr

Lo mismo que Array#each, pero atraviesa arr en orden inverso.

a = [ a, b, c ] a.reverse_each {|x| print x, } produce: c b a

rindex
a = [ a, b, b, b, c ] a.rindex(b) -> 3 a.rindex(z) -> nil

arr.rindex( obj ) int o nil

Devuelve el ndice del ltimo objeto en arr, tal que objeto == obj. Devuelve nil si no hay coincidencia.

shift

arr.shift obj o nil

Devuelve el primer elemento de arr y lo elimina (desplazando una posicin atrs todos los dems elementos). Devuelve nil si la matriz est vaca. args = [ -m, -q, filename ] args.shift -> m args -> [q, filename]

297

size
Sinnimo de Array#length.

arr.size int arr.slice( int ) obj arr.slice( start, length ) una_matriz arr.slice( range ) una_matriz

slice
Sinnimo de Array#[ ].

a = [ a, b, c, d, e ] a.slice(2) + a.slice(0) + a.slice(1) a.slice(6) a.slice(1, 2) a.slice(1..3) a.slice(4..7) a.slice(6..10) a.slice(-3, 3) # casos especiales a.slice(5) a.slice(5, 1) a.slice(5..10)

-> -> -> -> -> -> -> -> -> ->

cab nil [b, c] [b, c, d] [e] nil [c, d, e] nil [] []

slice!

arr.slice!( int ) obj o nil arr.slice!( start, length ) una_matriz o nil arr.slice!( range ) una_matriz o nil

Elimina el elemento o elementos dados con un ndice (de manera opcional con una longitud) o con un rango. Devuelve el objeto eliminado, submatriz o nil si el ndice est fuera de rango. Equivalente a def slice!(*args) result = self[*args] self[*args] = nil result end a = [ a, b, c ] a.slice!(1) -> b a -> [a, c] a.slice!(-1) -> c a -> [a] a.slice!(100) -> nil a -> [a]

sort arr.sort una_matriz arr.sort {| a,b | block } una_matriz


Devuelve una matriz nueva creada por la ordenacin de arr. Las comparaciones de la ordenacin se llevarn a cabo utilizando el operador <=> operador o utilizando un bloque de cdigo opcional. El bloque implementa una comparacin entre a y b, devolviendo -1, 0 o +1. Vase tambin Enumerable#sort_by. a = [ d, a, e, c, b ] a.sort -> [a, b, c, d, e] a.sort {|x,y| y <=> x } -> [e, d, c, b, a]

sort!

298

arr.sort! arr arr.sort! {| a,b | block } arr

Ordena arr, que queda congelada mientras una ordenacin est en curso. a = [ d, a, e, c, b ] a.sort! -> [a, b, c, d, e] a -> [a, b, c, d, e]

to_a

arr.to_a arr array_subclass.to_a array

Si arr es una matriz, devuelve arr. Si arr es una subclase de Array, invoca to_ary, y usa el resultado para crear un nuevo objeto matriz.

to_ary
Devuelve arr.

arr.to_ary arr

to_s
Devuelve arr.join. [ a, e, i, o ].to_s -> aeio

arr.to_s str

transpose
a = [[1,2], [3,4], [5,6]] a.transpose -> [[1, 3, 5], [2, 4, 6]]

arr.transpose una_matriz

Asume que arr es una matriz de matrices y transpone las filas y columnas.

uniq

arr.uniq una_matriz

Devuelve una nueva matriz mediante la eliminacin de los valores duplicados en arr, donde los duplicados se detectan por comparacin utilizando eql?. a = [ a, a, b, b, c ] a.uniq -> [a, b, c]

uniq!

arr.uniq! una_matriz o nil

Igual que el Array#uniq, pero modifica el receptor en su lugar. Devuelve nil si no se realizan cambios (es decir, si no se encuentran duplicados). a = [ a, a, b, b, c ] a.uniq! -> [a, b, c] b = [ a, b, c ] b.uniq! -> nil

unshift
Antepone el objeto u objetos a arr. a = [ b, c, d ] a.unshift(a) -> a.unshift(1, 2) ->

arr.unshift( < obj >+ ) arr

[a, b, c, d] [1, 2, a, b, c, d]

299

values_at

arr.values_at( < selector >* ) una_matriz

Devuelve una matriz que contiene los elementos de arr correspondientes al selector o selectores dados. Los selectores pueden ser ndices enteros o rangos. a = %w{ a b c d e f } a.values_at(1, 3, 5) a.values_at(1, 3, 5, 7) a.values_at(-1, -3, -5, -7) a.values_at(1..3, 2...5) Clase -> -> -> -> [b, [b, [f, [b, d, d, d, c, f] f, nil] b, nil] d, c, d, e]

Bignum < Integer


Los objetos Bignum contienen enteros fuera del rango de Fixnum. Los objetos Bignum se crean automticamente cuando de otro modo, los clculos de enteros daran un desbordamiento de Fixnum. Cuando un clculo en relacin a objetos Bignum devuelve un resultado que se ajusta a un Fixnum, el resultado se convierte automticamente. A los efectos de las operaciones bit a bit y para [], un Bignum se trata como si fuera una cadena de bits de longitud infinita con la representacin de complemento a 2. Los valores Fixnum son inmediatos, mientras que los objetos Bignum no --las asignaciones y paso de parmetros trabajan con referencias a objetos, no con los objetos mismos. (...) Clase

Binding < Objeto


Los Objetos de la clase Binding encapsulan el contexto de ejecucin en algn lugar en particular en el cdigo y conservan este contexto para su futuro uso. Las variables, los mtodos, el valor de self y posiblemente un bloque iterador se pueden acceder en este contexto ya que estn todos retenidos. Los objetos Binding pueden ser creados usando Kernel#binding y estn a disposicin de la devolucin de llamada de kernel#set_trace_func. Estos objetos de enlace se pueden pasar como el segundo argumento del mtodo Kernel#eval, estableciendo un entorno para la evaluacin. class Demo def initialize(n) @secret = n end def get_binding return binding() end end k1 b1 k2 b2 = = = = Demo.new(99) k1.get_binding Demo.new(-3) k2.get_binding -> -> -> 99 3 nil

eval(@secret, b1) eval(@secret, b2) eval(@secret)

Los Objetos binding no tienen mtodos especficos de clase.

(...)

300

[En el original en ingls, contina con las dems clases: Bignum, Binding, Class, Compa-

rable, etc, por orden alfabtico. Consultar el mismo, ya que como se v, viene de forma esquemtica y con ejemplos por lo que es de fcil comprensin. Para terminar se vern algunos otros temas interesantes (aunque no en su totalidad) y nos remitimos a la versin original en ingls o a la tercera edicin en ingls, en la que ya se expone la versin 1.9 de Ruby. Comentar que est previsto que para el 2012 salga la versin 2.0.]
Mdulo

Comparable
Basado en: <=> El mixin Comparable es utilizado por las clases cuyos objetos pueden ser ordenados. La clase debe definir el operador <=>, que compara el receptor con otro objeto, devolviendo -1, 0 o +1 dependiendo de si el receptor es menor, igual o mayor que el otro objeto. Comparable utiliza <=> para implementar los operadores de comparacin convencionales (<, <=, ==, >= y >) y tambin utiliza el mtodo between?. class CompareOnSize include Comparable attr :str def <=>(other) str.length <=> other.str.length end def initialize(str) @str = str end end s1 = CompareOnSize.new(Z) s2 = CompareOnSize.new([1,2]) s3 = CompareOnSize.new(XXX) s1 < s2 s2.between?(s1, s3) s3.between?(s1, s2) [ s3, s2, s1 ].sort Mtodo de Instancia -> -> -> -> true true false [Z, [1, 2], XXX]

between?

obj.between?( min, max ) true o false

Devuelve false si obj <=> min es menor que cero o si obj <=> max es mayor que cero, true si lo contrario. 3.between?(1, 5) 6.between?(1, 5) cat.between?(ant, dog) gnu.between?(ant, dog) (...) Mdulo -> -> -> -> true false true false

Enumerable
Basado en: each, <=> El mixin Enumerable ofrece clases de coleccin con varios mtodos de recorrido y bsqueda y con la capacidad de ordenar. La clase debe proporcionar un mtodo each, que da los miembros sucesivos de una coleccin. Si se utiliza Enumerable#max, #min, #sort, o #sort_by, los objetos de la coleccin

301

tambin deben implementar el operador <=>, ya que estos mtodos estn basados en una determinada ordenacin entre los miembros de la coleccin. Mtodos de instancia

all?

enum.all? < {| obj | block } > true o false

Pasa cada elemento de la coleccin al bloque dado. El mtodo devuelve true si el bloque nunca retorna false o nil. Si no se da el bloque, Ruby aade un bloque implcito {|obj| obj} (donde all? devolver true slo si ninguno de los miembros de la coleccin es false o nil). %w{ ant bear cat}.all? {|word| word.length >= 3} %w{ ant bear cat}.all? {|word| word.length >= 4} [ nil, true, 99 ].all? -> -> -> true false false

any?

enum.any? < {| obj | block } > true o false

Pasa cada elemento de la coleccin para el bloque dado. El mtodo devuelve true si el bloque siempre retorna un valor que no sea false o nil. Si no se da el bloque, Ruby aade un bloque implcito {|obj| obj} (donde any? devolver true si al menos uno de los miembros de la coleccin no es false o nil). %w{ ant bear cat}.any? {|word| word.length >= 3} %w{ ant bear cat}.any? {|word| word.length >= 4} [ nil, true, 99 ].any? -> -> -> true true true

collect

enum.collect {| obj | block } array

Devuelve una nueva matriz que contiene los resultados de la ejecucin del bloque una vez por cada elemento en enum. (1..4).collect {|i| i*i } (1..4).collect { cat } -> -> [1, 4, 9, 16] [cat, cat, cat, cat]

detect

enum.detect( ifnone = nil ) {| obj | block } obj o nil

Pasa cada entrada de enum al bloque. Retorna la primera para la que el bloque es no falso. Devuelve nil si no coincide con objeto, a menos que se de el ifnone proc, en cuyo caso se llama y se retorna su resultado. (1..10).detect {|i| i % 5 == 0 and i % 7 == 0 } (1..100).detect {|i| i % 5 == 0 and i % 7 == 0 } sorry = lambda { not found } (1..10).detect(sorry) {|i| i > 50} -> -> -> nil 35 not found

each_with_index

enum.each_with_index {| obj, i | block } enum

Llama al bloque con dos argumentos, el elemento y su ndice, para cada elemento de enum.

hash = Hash.new %w(cat dog wombat).each_with_index do |item, index| hash[item] = index end hash -> {cat=>0, wombat=>2, dog=>1}

entries enum.entries array


Sinnimo de Enumerable#to_a.

find

Sinnimo de Enumerable#detect.

enum.find( ifnone = nil ) {| obj | block } obj o nil


302

find_all

enum.find_all {| obj | block } array

Devuelve una matriz que contiene todos los elementos de enum para los que el bloque es no falso (ver tambin Enumerable#reject). (1..10).find_all {|i| i % 3 == 0 } -> [3, 6, 9]

grep

enum.grep( pattern ) array enum.grep( pattern ) {| obj | block } array

Devuelve una matriz de todos los elementos de enum para los que patrn === elemento. Si se suministra el bloque opcional, se le pasa cada elemento coincidente y el resultado del bloque se almacena en la matriz de salida. (1..100).grep 38..44 -> [38, 39, 40, 41, 42, 43, 44] c = IO.constants c.grep(/SEEK/) -> [SEEK_CUR, SEEK_SET, SEEK_END] res = c.grep(/SEEK/) {|v| IO.const_get(v) } res -> [1, 0, 2]

include?

enum.include?( obj ) true o false


-> -> true false

Devuelve true si algn miembro de enum es igual a obj. Se prueba la igualdad usando ==. IO.constants.include? SEEK_SET IO.constants.include? SEEK_NO_FURTHER

inject

enum.inject(initial) {|memo, obj | block } obj enum.inject {|memo, obj | block } obj

Combina los elementos de enum mediante la aplicacin del bloque a un valor acumulador ( memo) y a cada elemento por separado. En cada paso, memo se establece en el valor devuelto por el bloque. La primera forma permite proporcionar un valor inicial para memo. La segunda forma utiliza el primer elemento de la coleccin como el valor inicial (y se salta ese elemento, mientras itera). # Suma algunos nmeros (5..10).inject {|sum, n| sum + n } # Multiplica algunos nmeros (5..10).inject(1) {|product, n| product * n } -> -> 45 151200

# buscar la palabra ms larga longest = %w{ cat sheep bear }.inject do |memo, word| memo.length > word.length ? memo : word end longest -> sheep # buscar la longitud de la palabra ms larga longest = %w{ cat sheep bear }.inject(0) do |memo, word| memo >= word.length ? memo : word.length end longest -> 5

map
Sinnimo de Enumerable#collect.

enum.map {| obj | block } array enum.max obj enum.max {| a,b | block } obj
303

max

Devuelve el objeto en enum con el valor mximo. La primera supone que todos los objetos implementan <=>, y la segunda utiliza el bloque para devolver a <=> b. a = %w(albatross dog horse) a.max a.max {|a,b| a.length <=> b.length } -> -> horse albatross

member?
Sinnimo de Enumerable#include?.

enum.member?( obj ) true o false

min enum.min obj enum.min {| a,b | block } obj


Devuelve el objeto de enum con el valor mnimo. La primera forma supone que todos los objetos implementan Comparable, y la segunda utiliza el bloque para devolver a <=> b. a = %w(albatross dog horse) a.min a.min {|a,b| a.length <=> b.length } -> -> albatross dog

partition

enum.partition {| obj | block }[ true_array, false_array ]

Devuelve dos matrices, la primera contiene los elementos de enum para los que el bloque se evala como verdadero, la segunda contiene el resto. (1..6).partition {|i| (i&1).zero?} -> [[2, 4, 6], [1, 3, 5]]

reject enum.reject {| obj | block } array


Devuelve una matriz que contiene los elementos de enum para los que el bloque es falso (vase tambin Enumerable#find_all). (1..10).reject {|i| i % 3 == 0 } -> [1, 2, 4, 5, 7, 8, 10]

select enum.select {| obj | block } array


Sinnimo de Enumerable#find_all.

sort

enum.sort array enum.sort {| a, b | block } array

Devuelve una matriz que contiene los elementos de enum ordenados, ya sea de acuerdo a su propio mtodo <=> o mediante el uso de los resultados del bloque suministrado. El bloque debe devolver -1, 0 o +1 en funcin de la comparacin entre a y b. A partir de Ruby 1.8, el mtodo Enumerable#sort_by implementa una Transformada Schwartzian integrada, til cuando el clculo o la comparacin de clave son costosos. %w(rhea kea flea).sort (1..10).sort {|a,b| b <=> a} -> -> [flea, kea, rhea] [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

sort_by

enum.sort_by {| obj | block } array

Ordena enum, utilizando claves generadas por asignacin a los valores de enum, mediante el bloque dado y utilizando el resultado del bloque para la comparacin de elementos. sorted = %w{ apple pear fig }.sort_by {|word| word.length}

304

sorted

->

[fig, pear, apple]

Internamente, sort_by genera una matriz de tuplas que contiene la coleccin original de elementos y el valor asignado. Esto hace de sort_by bastante costoso cuando los conjuntos de claves son simples. require benchmark include Benchmark a = (1..100000).map {rand(100000)} bm(10) do |b| b.report(Sort) { a.sort } b.report(Sort by) { a.sort_by {|a| a} } end produce: user system total real Sort 0.070000 0.010000 0.080000 ( 0.085860) Sort by 1.580000 0.010000 1.590000 ( 1.811626) Sin embargo, hay que tener en cuenta el caso de cuando la comparacin de claves es una operacin no trivial. El cdigo siguiente ordena algunos archivos por tiempo de modificacin utilizando el mtodo bsico sort. files = Dir[*] sorted = files.sort {|a,b| File.new(a).mtime <=> File.new(b).mtime} sorted -> [mon, tues, wed, thurs]

Esta ordenacin es ineficiente: genera dos nuevos objetos File en cada comparacin. Una tcnica algo mejor es utilizar el mtodo Kernel#test para generar los tiempos de modificacin directamente. files = Dir[*] sorted = files.sort do |a,b| test(?M, a) <=> test(?M, b) end sorted -> [mon, tues, wed, thurs]

Esto todava genera muchos objetos Time innecesarios. Una tcnica ms eficiente es almacenar en cach las claves de ordenacin (tiempos de modificacin en este caso) antes de la ordenacin. Los usuarios de Perl a menudo llaman a este mtodo una transformacin Schwartzian, llamado as por Randal Schwartz. Construimos una matriz temporal, donde cada elemento es una matriz que contiene la clave de ordenacin, junto con el nombre de archivo. Ordenamos esta matriz y luego se extrae el nombre del archivo a partir del resultado. sorted = Dir[*].collect {|f| [test(?M, f), f] }.sort.collect {|f| f[1] } sorted -> [mon, tues, wed, thurs]

Esto es exactamente lo que sort_by hace internamente.

sorted = Dir[*].sort_by {|f| test(?M, f)} sorted -> [mon, tues, wed, thurs]

sort_by tambin puede ser til para ordenaciones multinivel. Un truco, que se basa en el hecho de que las matrices se comparan elemento por elemento, es hacer que el bloque de devuelva una matriz con

305

cada una de las claves de comparacin. Por ejemplo, para ordenar una lista de palabras primero por su longitud y luego por orden alfabtico, se podra escribir: words = %w{ puma cat bass ant aardvark gnu fish } sorted = words.sort_by {|w| [w.length, w] } sorted -> [ant, cat, gnu, bass, fish, puma, aardvark]

to_a
Devuelve una matriz que contiene los elementos de enum. -> -> (1..7).to_a { a=>1, b=>2, c=>3 }.to_a

enum.to_a array
[1, 2, 3, 4, 5, 6, 7] [[a, 1], [b, 2], [c, 3]]

zip

enum.zip( < arg >+ ) {| arr | block } nil

enum.zip( < arg >+ ) array

Convierte los argumentos a matrices y luego fusiona los elementos de enum con los correspondientes elementos de cada argumento. El resultado es una matriz que contiene el mismo nmero de elementos que enum. Cada elemento es una matriz de n elementos, donde n es uno o ms del recuento de los argumentos. Si el tamao de los argumentos es menor que el nmero de elementos en enum, se suministran valores nil. Si se da un bloque, se invoca para cada matriz de salida, de otra manera se devuelve una matriz de matrices. a = [ 4, 5, 6 ] b = [ 7, 8, 9 ] (1..3).zip(a, b) cat\ndog.zip([1]) (1..3).zip Mdulo -> -> -> [[1, 4, 7], [2, 5, 8], [3, 6, 9]] [[cat\n, 1], [dog, nil]] [[1], [2], [3]]

Errno
Los objetos excepcin de Ruby son subclases de Exception. Sin embargo, los sistemas operativos suelen reportar errores usando simples enteros. El mdulo Errno se crea de forma dinmica para asignar estos errores del sistema operativo a las clases Ruby, con cada nmero de error generando su propia subclase de SystemCallError. Como la subclase se crea en el mdulo Errno, su nombre comenzar con Errno::. Exception StandardError SystemCallError Errno::xxx Los nombres de las clases Errno:: dependern del entorno en el que se ejecute Ruby. En una plataforma tpica de Unix o Windows, se encuentra que tiene clases Errno como Errno::EACCES, Errno::EAGAIN, Errno::EINTR, etc. El nmero entero de error del sistema operativo correspondiente a un particular error est disponible en la constante de clase Errno::error::Errno. Errno::EACCES::Errno Errno::EAGAIN::Errno Errno::EINTR::Errno -> -> -> 13 35 4

La lista completa de errores del sistema operativo en su plataforma est disponible como las constantes

306

de Errno. Cualquier definicin de usuario de excepcin en este mdulo (incluyendo las subclases de las excepciones existentes) tambin deben definir una constante Errno. Errno.constants -> E2BIG, EACCES, EADDRINUSE, EADDRNOTAVAIL, EAFNOSUPPORT, EAGAIN, EALREADY, ... A partir de Ruby 1.8, las excepciones son comparadas en las clusulas rescue usando Module#===. El mtodo === se anula para SystemCallError de la clase a fin de comparar en funcin del valor Errno. As, si dos clases Errno distintas tienen el mismo valor Errno subyacente, sern tratadas como la misma excepcin por una clusula rescue. Clase

Exception < Object


Los descendientes de la clase Exception se utilizan para la comunicacin entre los mtodos raise y las declaraciones rescue en los bloques begin/end. Los objetos Exception contienen informacin acerca de la excepcin --el tipo (nombre de la clase de la excepcin), una cadena descriptiva opcional e informacin de rastreo opcional. La librera estndar define las excepciones que se muestran en la Figura 21 en la pgina siguiente. Vase tambin la descripcin de Errno en la pgina anterior. Mtodos de Clase

exception

Exception.exception( < message > ) exc Exception.new( < message > ) exc

Crea y devuelve un nuevo objeto excepcin, opcionalmente se puede establecer un mensaje en mesagge.

new
Mtodos de Instancia

Crea y devuelve un nuevo objeto excepcin, opcionalmente se puede establecer un mensaje en mesagge.

backtrace

exc.backtrace array

Devuelve cualquier traza asociada a la excepcin. La traza es una matriz de cadenas, cada una conteniendo ya sea filename:linea: en metodo o filename:linea. def a raise boom end def b a() end begin b() rescue => detail print detail.backtrace.join(\n) end produce: prog.rb:2:in `a prog.rb:6:in `b prog.rb:10

exception

exc.exception( < message > ) exc o exception


307

Sin argumento, devuelve el receptor. De lo contrario, crea un nuevo objeto excepcin de la misma clase que el receptor pero con un mensaje diferente.

message
Devuelve el mensaje asociado a esa excepcin.

exc.message msg

set_backtrace

exc.set_backtrace( array ) array

Establece la informacin de backtrace asociada con exc. El argumento debe ser un array de objetos String con el formato descrito en Exception#backtrace.

status

exc.status status

(SystemExit solamente) Devuelve el estado de salida asociado a esa excepcin SystemExit. Normalmente , esta situacin se configura con Kernel#exit. begin exit(99) rescue SystemExit => e puts Exit status is: #{e.status} end produce:

308

Exit status is: 99

success?
begin exit(99) rescue SystemExit => e print This program if e.success? print did else print did not end puts succeed end produce: This program did not succeed

exc.success? true o false

(SystemExit solamente) Devuelve true si el estado de salida es nil o cero.

to_s

exc.to_s msg

Devuleve el mensaje asociado con esa excepcin (o el nombre de la excepcin si no tiene mensaje establecido). begin raise The message rescue Exception => e puts e.to_s end produce: The message

to_str

exc.to_str msg

Devuleve el mensaje asociado con esa excepcin (o el nombre de la excepcin si no tiene mensaje establecido). Implementar to_str da a las excepciones funcionamiento de cadena. Clase

File < IO
Un File es una abstraccin de cualquier objeto fichero accesible en el programa y est estrechamente relacionada con la clase IO. File incluye los mtodos del mdulo FileTest como mtodos de clase, lo que permite escribir (por ejemplo) File.exist?(foo). Los bits de permiso son un conjunto de bits, especfico de la plataforma, que indican los permisos de un archivo. En sistemas basados en Unix, los permisos son vistos como un conjunto de tres octetos, para el propietario, para el grupo y para el resto del mundo. Para cada una de estas entidades, los permisos se pueden configurar para leer, escribir o ejecutar el archivo.

309

Los bits de permiso en 0644 (en octal), podran interpretarse como lectura / escritura para el propietario y de slo lectura para el grupo y otros. Los bits superiores tambin pueden ser utilizados para indicar el tipo de archivo (normal, directorio, pipe, socket, etc) y varias otras caractersticas especiales. Si los permisos son para un directorio, el significado del bit de ejecucin cambia y cuando se establece, se puede buscar en el directorio. Cada archivo tiene tres tiempos asociados. El atime es el momento del ltimo acceso. El ctime es el momento en que el estado del archivo (no necesariamente el contenido del archivo) se modific por ltima vez. Por ltimo, el mtime es el momento en que los datos del archivo se modificaron por ltima vez. En Ruby, todos estos tiempos se devuelven como objetos Time. En los sistemas operativos no POSIX, slo hay la posibilidad de hacer un archivo de slo lectura, o de lectura / escritura. En este caso, los bits de permisos restantes sern sintetizados para parecerse a los valores tpicos. Por ejemplo, en Windows los bits de permisos por defecto son 0644, lo que significa que son de lectura / escritura para el propietario y de slo lectura para todos los dems. El nico cambio que se puede hacer es pasar el archivo a slo lectura, que se presenta como 0444. Vase tambin Pathname.

Mdulo

FileTest
FileTest implementa las operaciones de prueba de archivo similares a las que se utilizan en File::Stat. Los mtodos de FileTest estn duplicados en la clase File. Aqu slo vamos a listar los nombres de los mtodos. FileTest parece un mdulo un poco rudimentario. Los mtodos de FileTest son:

blockdev?, chardev?, directory?, executable?, executable_real?, exist?, exists?, file?, grpowned?, owned?, pipe?, readable?, readable_real?, setgid?, setuid?, size, size?, socket?, sticky?, symlink?, world_readable?, world_writable?, writable?, writable_real? y zero? Clase

IO < Objeto
Subclases: File La clase IO es la base para todas las entradas y salidas en Ruby. Un flujo de E/S puede ser dplex (es decir, bidireccional) y por lo tanto puede usar ms de un flujo del sistema operativo nativo. La nica subclase estndar de IO es File. Las dos clases estn estrechamente relacionadas.

La clase IO utiliza la abstraccin Unix de descriptores de fichero (fd), enteros pequeos que representan los archivos abiertos. Convencionalmente, la entrada estndar tiene un fd de 0, la salida estndar un fd de 1 y el error estndar un fd de 2. Ruby convierte si es posible los nombres de las rutas entre las diferentes convenciones de los sistemas operativos. Por ejemplo, en un sistema Windows el nombre de archivo /gumby/ruby/test.rb se abrir como \gumby\ruby\test.rb. Cuando se especifica un nombre de archivo al estilo Windows en una cadena Ruby entre comillas dobles, hay que acordarse de escapar las barras invertidas. c:\\gumby\\ruby\\test.rb Se puede utilizar File::SEPARATOR para obtener el carcter separador de la plataforma especfica.

Los puertos de E/S pueden ser abierto en cualquiera de varios modos diferentes, que se muestran en esta seccin como una cadena de modo. Esta cadena de modo debe ser uno de los valores listados en la tabla siguiente:

310

Modo

Significado

r Slo lectura. Se inicia al comienzo del archivo (modo predeterminado). r+ Lectura / escritura. Se inicia al comienzo del archivo. w Slo escritura. Trunca un archivo existente con longitud cero o crea un nuevo archivo para escritura. a Slo escritura. Se inicia al final del archivo si este existe, o de otro modo crea un nuevo archivo para escritura. a+ Lectura / escritura. Se inicia al final del archivo si este existe, o de otro modo crea un nuevo archivo para lectura / escritura. b (slo DOS / Windows) Modo fichero binario (puede aparecer con cualquiera de las letras clave listadas arriba. Mdulo

Marshal
La biblioteca de serializacin (marshaling library) convierte colecciones de objetos Ruby en un flujo de bytes, lo que les permite ser almacenados fuera del script que est activo. Estos datos posteriormente pueden ser ledos y reconstituir los objetos originales. Vase tambin la biblioteca YAML. Los datos serializados tienen un nmero de versin mayor y menor almacenados junto con la informacin del objeto. En uso normal, el serializado puede cargar datos nicamente escritos con el mismo nmero de versin mayor y un nmero de versin menor igual o inferior. Si la bandera Ruby verbose est activa (normalmente con -d, -v, -w o --verbose), los nmeros mayores y menores deben coincidir exactamente. La versin de serializacin es independiente de los nmeros de versin de Ruby. Puede extraer la versin por la lectura de los dos primeros bytes de los datos serializados. str = Marshal.dump(thing) RUBY_VERSION -> 1.8.2 str[0] -> 4 str[1] -> 8 Algunos objetos no pueden ser objeto de volcado: si los objetos a ser objeto de volcado incluyen enlaces, objetos de procedimiento o mtodo, instancias de la clase IO, objetos singleton, o si se trata de volcar clases o mdulos annimos, se lanzar un TypeError. Si su clase tiene necesidades especiales de serializacin (por ejemplo, si se desea serializar en un formato especfico), o si contiene objetos que de otro modo no son serializables, puede implementar su propia estrategia de serializacin. Antes de Ruby 1.8, se definen los mtodos _dump y _load. Ruby 1.8 incluye una interfaz ms flexible para la serializacin personalizada con los mtodos de instancia marshal_dump y marshal_load: Si un objeto que se va a serializar responde a marshal_ dump, se llama a este mtodo en lugar de a _dump. marshal_dump puede devolver un objeto de cualquier clase (no slo String). Una clase que implementa marshal_dump tambin deben implementar marshal_load, que es llamado como un mtodo de instancia de un objeto recin asignado y se le pasa el objeto original creado por marshal_dump. El siguiente cdigo utiliza este nuevo marco para almacenar un objeto Time en la versin serializada de un objeto. Cuando est cargado, este objeto se pasa a marshal_load, que convierte este time a una forma imprimible, almacenando el resultado en una variable de instancia. class TimedDump attr_reader :when_dumped def marshal_dump Time.now end def marshal_load(when_dumped) @when_dumped = when_dumped.strftime(%I:%M%p) end end

311

t = TimedDump.new t.when_dumped

->

nil

str = Marshal.dump(t) newt = Marshal.load(str) newt.when_dumped -> 10:38PM

Constantes de Mdulo MAJOR_VERSION MINOR_VERSION Mtodos de Mdulo

dump

dump( obj < , io > , limit=1 ) io

Serializa obj y todos los objetos descendientes. Si se especifica io, los datos serializados se escribirn en l, de lo contrario los datos se devuelven como un String. Si se especifica limit, el recorrido de subobjetosse limitar a esa profundidad. Si el lmite es negativo, no hay comprobacin de profundidad. class def end def end end Klass initialize(str) @str = str say_hello @str

o = Klass.new(hello\n) data = Marshal.dump(o) obj = Marshal.load(data) obj.say_hello -> hello\n

load

load( from < , proc > ) obj

Devuelve el resultado de la conversin de los datos serializados en from en un objeto Ruby (posiblemente asociados con los objetos subordinados). from puede ser una instancia de IO o un objeto que responde a to_str. Si se especifica proc, es pasado a cada objeto a medida que se deserializa.

restore
Sinnimo de Marshal.load.

restore( from < , proc > ) obj

Mdulo

ObjectSpace
El mdulo ObjectSpace contiene una serie de rutinas que interactan con la utilidad de recoleccin de basura y que permiten recorrer con un iteradortodos los objetos que habitan. ObjectSpace tambin proporciona soporte para los finalizadores de objetos. Estos son procs que se llaman cuando un objeto especfico est a punto de ser destruido por el recolector de basura. include ObjectSpace

312

a, b, c = puts as puts bs puts cs

A, B, C id is #{a.object_id} id is #{b.object_id} id is #{c.object_id}

define_finalizer(a, lambda {|id| puts Finalizer one on #{id} }) define_finalizer(b, lambda {|id| puts Finalizer two on #{id} }) define_finalizer(c, lambda {|id| puts Finalizer three on #{id} }) produce: as id is bs id is cs id is Finalizer Finalizer Finalizer 936150 936140 936130 three on 936130 two on 936140 one on 936150

Mtodos de Mdulo

_id2ref

ObjectSpace._id2ref( object_id ) obj

Convierte un identificador de objeto a una referencia al objeto. No puede ser llamado en un ID de objeto pasado como parmetro a un finalizador. s = I am a string oid = s.object_id r = ObjectSpace._id2ref(oid) r r.equal?(s) -> -> -> -> -> I am a string 936550 I am a string I am a string true

define_finalizer
each_object

ObjectSpace.define_finalizer( obj, a_proc=proc() )

Aade a_proc como un finalizador, llamado cuando obj est a punto de ser destruido.

ObjectSpace.each_object( < class_or_mod > ) {| obj | block } fixnum

Llama al bloque una vez por cada viviente y no inmediato objeto en ese proceso Ruby. Si se especifica class_or_mod, se llama al bloque slo para las clases o mdulos que coincidan (o sean una subclase) con class_or_mod. Devuelve el nmero de objetos encontrados. Objetos inmediatos ( Fixnums, Symbols, true, false y nil) nunca se devuelven. En el siguiente ejemplo, each_object devuelve tanto los nmeros que se han definido como varias constantes definidas en el mdulo Math. a = 102.7 b = 95 # Fixnum: No se retorna c = 12345678987654321 count = ObjectSpace.each_object(Numeric) {|x| p x } puts Total count: #{count} produce: 12345678987654321 102.7 2.71828182845905 3.14159265358979 2.22044604925031e16 1.79769313486232e+308 2.2250738585072e308 Total count: 7

313

garbage_collect
Inicia la recoleccin de basura.

ObjectSpace.garbage_collect nil ObjectSpace.undefine_finalizer( obj )

undefine_finalizer
Elimina todos los finalizadores para obj.

Clase

Proc < Objeto


Los objetos Proc son bloques de cdigo que se han ligado a un conjunto de variables locales. Una vez ligado, el cdigo puede ser llamado en diferentes contextos y acceder an a esas variables. def gen_times(factor) return Proc.new {|n| n*factor } end times3 = gen_times(3) times5 = gen_times(5) times3.call(12) times5.call(5) times3.call(times5.call(4)) -> -> -> 36 25 60

Mtodos de Clase

new

Proc.new { block } un_proc Proc.new un_proc

Crea un objeto Proc nuevo, vinculado al contexto actual. Proc.new puede ser llamado sin un bloque, slo dentro de un mtodo con un bloque adjunto, en cuyo caso el bloque se convierte en el objeto Proc. def proc_from Proc.new end proc = proc_from { hello } proc.call -> hello

Mtodos de Instancia

[ ]
Sinnimo de Proc.call.

prc[ < params >* ] obj prc== other true o false prc.arity integer

==
devuelve true si prc es lo mismo que other.

arity

Devuelve el nmero de argumentos requeridos por el bloque. Si el bloque se declara para no tomar argumentos, devuelve 0. Si el bloque es conocido por tener exactamente n argumentos, devuelve n. Si el bloque tiene argumentos opcionales, devuelve -(n +1), donde n es el nmero de argumentos obligatorios. Un proc sin declaraciones de argumento tambin devuelve -1, ya que puede aceptar (e ignorar) un nmero arbitrario de parmetros.

314

Proc.new Proc.new Proc.new Proc.new Proc.new Proc.new Proc.new

{}.arity {||}.arity {|a|}.arity {|a,b|}.arity {|a,b,c|}.arity {|*a|}.arity {|a,*b|}.arity

-> -> -> -> -> -> ->

1 0 1 2 3 1 2

En Ruby 1.9, arity se define como el nmero de parmetros que no se puede ignorar. En 1.8, Proc.new{}.arity devuelve -1, y en 1,9 devuelve 0.

binding

prc.binding binding

Devuelve el enlace asociado con prc. Hay que tener en cuenta que del Kernel#eval acepta o un Proc o un objeto Binding como segundo parmetro. def fred(param) lambda {} end b = fred(99) eval(param, b.binding) eval(param, b) -> -> 99 99

call

prc.call( < params >* ) obj

Invoca el bloque, estableciendo los parmetros del bloque a los valores en params utilizando alguna semntica como de llamada a mtodo. Devuelve el valor de la ltima expresin evaluada en el bloque. a_proc = Proc.new {|a, *b| b.collect {|i| i*a }} a_proc.call(9, 1, 2, 3) -> [9, 18, 27] a_proc[9, 1, 2, 3] -> [9, 18, 27] Si el bloque es llamado explcitamente acepta un solo parmetro. call emite un mensaje de advertencia, a menos que se le d exactamente un parmetro. De otra manera, acepta encantado lo que se le d, haciendo caso omiso de los parmetros excedentes que se le pasan y establece los parmetros sin establecer del bloque a nil. a_proc = Proc.new {|a| a} a_proc.call(1,2,3) produce: prog.rb:1: warning: multiple values for a block parameter (3 for 1) from prog.rb:2 Si se quiere un bloque para recibir un nmero arbitrario de argumentos, hay que definirlo para aceptar *args. a_proc = Proc.new {|*a| a} a_proc.call(1,2,3) -> [1, 2, 3] Los bloques creados utilizando Kernel.lambda comprueban que se les llama exactamente con el nmero correcto de parmetros. p_proc = Proc.new {|a,b| puts Sum is: #{a + b} } p_proc.call(1,2,3) p_proc = lambda {|a,b| puts Sum is: #{a + b} }

315

p_proc.call(1,2,3) produce: Sum is: 3 prog.rb:3: wrong number of arguments (3 for 2) (ArgumentError) from prog.rb:3:in `call from prog.rb:4

to_proc

prc.to_proc prc

Parte del protocolo para la conversin de objetos a objetos Proc. Las instancias de la clase Proc simplemente lo que devuelven es a si mismas.

to_s
def create_proc Proc.new end my_proc = create_proc { hello } my_proc.to_s -> #<Proc:0x001c7abc@prog.rb:5> Clase

prc.to_s string

Devuelve una descripcin de proc, incluyendo informacin sobre donde se defini.

Regexp < Objeto


Un Regexp tiene una expresin regular, que se utiliza para comparar un patrn con cadenas. Expresiones regulares son creados usando literales /.../ y %r... y con el constructor Regexp.new. Esta seccin documenta expresiones regulares Ruby 1.8. Versiones posteriores de Ruby utilizan un motor para expresiones regulares diferente.

Constantes de Clase
EXTENDED IGNORECASE MULTILINE Ignora espacios y saltos de lnea en expresiones regulares. Las comparaciones son case insensitive, es decir, ignora si son maysculas o minsculas. Nueva lnea es tratada como cualquier otro carcter.

Mtodos de Clase

compile

Regexp.compile( pattern < , options < , lang > > ) rxp

Sinnimo de Regexp.new.

escape

Regexp.escape( string ) escaped_string

Escapa cualquier carcter que tenga un significado especial en una expresin regular. Para cualquier cadena, Regexp.escape(str)=~str ser verdadero. Regexp.escape(\\[]*?{}.) -> \\\[\]\*\?\{\}\.

last_match Regexp.last_match match Regexp.last_match( int ) string


La primera forma devuelve el objeto MatchData generado por la comparacin exitosa de patrn anterior. Esto es equivalente a la lectura de la variable global $~. La segunda forma devuelve el ensimo campo de ese objeto MatchData.

316

/c(.)t/ =~ cat Regexp.last_match Regexp.last_match(0) Regexp.last_match(1) Regexp.last_match(2)

-> -> -> -> ->

0 #<MatchData:0x1c9468> cat a nil

new Regexp.new( string < , options < , lang > > ) rxp Regexp.new( regexp ) new_regexp
Construye una nueva expresin regular desde pattern, que puede ser un String o un Regexp. En este ltimo caso las opciones de expresin regular se propagan, y las nuevas opciones no pueden ser especificadas (cambia a partir de Ruby 1.8). Si options es un Fixnum, deben ser uno o ms de Regexp::EXTENDED, Regexp::IGNORECASE y Regexp::POSIXLINE, or-ed conjuntamente. De otra manera, si options no es nil, la expresin regular ser case insensitive. El parmetro lang habilita el soporte multibyte para la expresin regular: n, N, o nil = none, e, E = EUC, s, S = SJIS, u, U = UTF-8. r1 r2 r3 r4 = = = = Regexp.new(^[a-z]+:\\s+\w+) Regexp.new(cat, true) Regexp.new(dog, Regexp::EXTENDED) Regexp.new(r2) -> -> -> -> /^[a-z]+:\s+\w+/ /cat/i /dog/x /cat/i

quote
Sinnimo de Regexp.escape.

Regexp.quote( string ) escaped_string

Mtodos de Instancia

==

rxp == other_regexp true o false

Igualdad --Dos expresiones regulares son iguales si sus patrones son idnticos, tienen el mismo cdigo de conjunto de carcteres, y sus valores casefold? son los mismos. /abc/ == /abc/x /abc/ == /abc/i /abc/u == /abc/n -> -> -> false false false

===
a = HELLO case a when /^[a-z]*$/; when /^[A-Z]*$/; else end produce: Upper case

rxp === string true o false

Igualdad de caso --Sinnimo de REGEXP#=~ utilizado en las sentencias case.

print Lower case\n print Upper case\n print Mixed case\n

=~

rxp =~ string int o nil

Comparacin --Compara rxp con string, devolviendo la posicin de la coincidencia respecto al inicio de la cadena o nil si la comparacin falla. Establece $~ al correspondiente MatchData o nil. /SIT/ =~ insensitive /SIT/i =~ insensitive -> -> nil 5

317

~ rxp int o nil

Comparacin --Compara rxp con el contenido de $_. Equivalente a rxp =~ $ _.

$_ = input data ~ /at/ -> 7

casefold?
Devuelve el valor de la bandera case-insensitive.

rxp.casefold? true o false rxp.inspect string


-> -> /cat/mi (?mi-x:cat)

inspect
Devuelve una versin legible de rxp. /cat/mi.inspect /cat/mi.to_s

kcode
Devuelve el cdigo de caracteres de la expresin regular. -> -> nil sjis /cat/.kcode /cat/s.kcode

rxp.kcode string

match

rxp.match(string) match o nil

Devuelve un objeto MatchData describiendo la comparacin, es decir, retornando, o nil si no hay coincidencia. Esto equivale a recuperar el valor de la variable especial $~ despus de una comparacin normal. /(.)(.)(.)/.match(abc)[2] -> b

options

rxp.options int

Devuelve el conjunto de bits correspondiente a las opciones utilizadas cuando se crea esa Regexp (ver Regexp.new para ms detalles). Hay que tener en cuenta que se pueden configurar bits adicionales en las opciones retornadas: estos son utilizados internamente por el cdigo de la expresin regular. Estos bits extras se ignoran si se pasan las opciones a Regexp.new. # Vamos a ver cules son los valores ... Regexp::IGNORECASE -> 1 Regexp::EXTENDED -> 2 Regexp::MULTILINE -> 4 /cat/.options /cat/ix.options Regexp.new(cat, true).options Regexp.new(cat, 0, s).options r = /cat/ix Regexp.new(r.source, r.options) -> -> -> -> -> 0 3 1 48 /cat/ix

source
Devuelve la cadena original del patrn. -> ab+c /ab+c/ix.source

rxp.source string

to_s

318

rxp.to_s string

Devuelve una cadena que contiene la expresin regular y sus opciones (utilizando la notacin (?xx: yyy)). Esta cadena puede ser alimentada de nuevo a Regexp.new a una expresin regular con la misma semntica que la original. (Sin embargo, Regexp#== No se puede devolver verdadero cuando se comparan las dos, ya que la fuente de la propia expresin regular puede ser diferente, como muestra el ejemplo). Regexp#inspect produce una versin generalmente ms legible de rxp. r1 = /ab+c/ix s1 = r1.to_s r2 = Regexp.new(s1) r1 == r2 r1.source r2.source Mdulo -> -> -> -> -> -> /ab+c/ix (?ix-m:ab+c) /(?ix-m:ab+c)/ false ab+c (?ix-m:ab+c)

Signal
Muchos sistemas operativos permiten enviar seales a los procesos en ejecucin. Algunas seales tienen un efecto definido sobre el proceso, y otras pueden ser atrapadas a nivel de cdigo y actuar en consecuencia. Por ejemplo, un proceso puede atrapar la seal USR1 y utilizarla para cambiar la depuracin, y puede utilizar TERM para iniciar un apagado controlado. pid = fork do Signal.trap(USR1) do $debug = !$debug puts Debug now: #$debug end Signal.trap(TERM) do puts Terminating... shutdown() end # . . . do some work . . . end Process.detach(pid) # Controlling program: Process.kill(USR1, pid) # ... Process.kill(USR1, pid) # ... Process.kill(TERM, pid) produce: Debug now: true Debug now: false Terminating... La lista de los nombres de las seales disponibles y su interpretacin depende del sistema. La semntica de envo de seales tambin puede variar entre sistemas. El envo de una seal en particular no siempre es fiable.

Mtodos de Mdulo

list

Signal.list hash

Devuelve una lista de los nombres de las seales con los correspondientes nmeros de seal subyacentes asignados. Signal.list -> {BUS=>10, SEGV=>11, KILL=>9, EMT=>7, SYS=>12, TERM=>15, IOT=>6, HUP=>1, STOP=>17, TRAP=>5, INT=>2, INFO=>29, PROF=>27, XCPU=>24, CLD=>20,

319

PIPE=>13, TTIN=>21, USR1=>30, XFSZ=>25,

FPE=>8, VTALRM=>26, IO=>23, WINCH=>28, TSTP=>18, ABRT=>6, TTOU=>22, QUIT=>3, CHLD=>20, CONT=>19, ILL=>4, USR2=>31, URG=>16, ALRM=>14, EXIT=>0}

trap Signal.trap( signal, proc ) obj Signal.trap( signal ) { block } obj


Especifica el tratamiento de las seales. El primer parmetro es un nombre de seal (una cadena como SIGALRM, SIGUSR1, etc) o un nmero de seal. Los caracteres SIG se pueden omitir en el nombre de la seal. El comando o bloque especifica el cdigo que se v a ejecutar cuando se lanza la seal. Si el comando es la cadena IGNORE o SIG_IGN, la seal ser ignorada. Si el comando es DEFAULT o SIG_DFL, ser invocado el controlador por defecto del sistema operativo. Si el comando es EXIT, el script ser terminado por la seal. De otra manera, se ejecutar la orden o el bloque dado. La seal especial EXIT o la seal nmero cero se invoca justo antes de la finalizacin del programa. trap devuelve el controlador anterior de la seal dada.

Signal.trap(0, lambda { puts Terminating: #{$$} }) Signal.trap(CLD) { puts Child died } fork && Process.wait produce: Terminating: 27913 Child died Terminating: 27912 Clase

Symbol < Objeto


Los objetos Symbol representan nombres en el intrprete Ruby. stos se generan utilizando la sntaxis literal :name y mediante los diferentes mtodos to_sym. El objeto smbolo mismo crear una cadena para el nombre dado por la duracin de la ejecucin de un programa, independientemente del contexto o el significado de ese nombre. Por lo tanto, si Fred es una constante en un contexto, un mtodo en otro, y una clase en un tercero, el Symbol :Fred ser el mismo objeto en los tres contextos. module One class Fred end $f1 = :Fred end module Two Fred = 1 $f2 = :Fred end def Fred() end $f3 = :Fred $f1.id -> $f2.id -> $f3.id ->

2526478 2526478 2526478

Mtodos de Clase

all_symbols

Symbol.all_symbols array
320

Devuelve una matriz de todos los smbolos actuales en la tabla de smbolos Ruby.

Symbol.all_symbols.size -> 913 Symbol.all_symbols[1,20] -> [:floor, :ARGV, :Binding, :symlink, :chown, :EOFError, :$;, :String, :LOCK_SH, :setuid?, :$<, :default_proc, :compact, :extend, :Tms, :getwd, :$=, :ThreadGroup, :success?, :wait2]

Mtodos de Instancia

id2name

sym.id2name string

Devuelve la representacin de cadena de sym. Antes de Ruby 1.8, los smbolos representaban tpicamente nombres, ahora pueden ser cadenas arbitrarias. :fred.id2name :99 red balloons!.id2name -> -> fred 99 red balloons!

inspect
Devuelve la representacin de sym como un smbolo literal. -> -> :fred.inspect :99 red balloons!.inspect

sym.inspect string
:fred :99 red balloons!

to_i
:fred.to_i fred.to_sym.to_i -> -> 9857 9857

sym.to_i fixnum

Devuelve un entero que es nico para cada smbolo dentro de una ejecucin particular de un programa.

to_int

sym.to_int fixnum sym.to_s string sym.to_sym sym

Sinnimo de Symbol#to_i. Permite que los smbolos el tener un comportamiento similar al entero.

to_s
Sinnimo de Symbol#id2name.

to_sym
Los smbolos se comportan como smbolos!

Clase

UnboundMethod < Object


Ruby soporta dos tipos de mtodos objetivados. La clase Method se utiliza para representar los mtodos que estn asociados con un objeto en particular: estos objetos mtodo estn obligados a ese objeto. Se pueden crear objetos mtodo enlazados a un objeto utilizando Object#method. Rub tambin soporta mtodos no consolidados, que son objetos mtodo que no estn asociados con un objeto particular. Estos pueden ser creados ya sea llamando a unbind en un objeto mtodo vinculado o ya sea llamando a Module#instance_method. Slo se puede llamar a mtodos sin consolidar despus de haber sido asociados a un objeto. Ese objeto debe ser una clase kind_of? original del mtodo.

321

class def end def end end

Square area @side * @side initialize(side) @side = side

area_unbound = Square.instance_method(:area) s = Square.new(12) area = area_unbound.bind(s) area.call -> 144 Los mtodos no consolidados son una referencia al mtodo en el momento en que es objetivizado: los cambios posteriores a la clase base no afectar al mtodo no consolidado. class Test def test :original end end um = Test.instance_method(:test) class Test def test :modified end end t = Test.new t.test um.bind(t).call -> -> :modified :original

Mtodos de Instancia

arity

umeth.arity fixnum

Ver Method#arity (que devuelve una indicacin del nmero de argumentos aceptados por un mtodo).

bind

umeth.bind( obj ) method

Enlaza umeth a obj. Si Klass era la clase de la cual se obtuvo originalmente umeth, obj.kind_of?(Klass) debe ser verdadero. class A def test puts In test, class = #{self.class} end end class B < A end class C < B end um = B.instance_method(:test) bm = um.bind(C.new) bm.call bm = um.bind(B.new)

322

bm.call bm = um.bind(A.new) bm.call produce: In test, class = C In test, class = B prog.rb:16:in `bind: bind argument must be an instance of B (TypeError) from prog.rb:16

Librera Estndar
El intrprete Ruby viene con un gran nmero de clases, mdulos y mtodos integrados --que estn disponibles como parte del programa en ejecucin. Cuando se necesita una utilidad que no es parte del repertorio integrado, a menudo se encuentra en una biblioteca que puede dar lugar a un require en el programa. Un gran nmero de bibliotecas de Ruby estn disponibles en Internet. Sitios tales como Ruby Application Archive (http://raa.rubylang.org) y RubyForge (http://rubyforge.org) tienen grandes ndices y una gran cantidad de cdigo. Sin embargo, Ruby tambin viene de serie con un gran nmero de bibliotecas. Algunas de stas se han escrito en Ruby puro y estarn disponible en todas las plataformas de Ruby. Otras son extensiones de Ruby, y algunas de ellas estarn presentes slo si su sistema es compatible con los recursos que necesitan. Todos se pueden incluir en su programa Ruby utilizando require. Y, a diferencia de las bibliotecas que se encuentran en Internet, se puede casi garantizar que todos los usuarios de Ruby tienen estas bibliotecas ya instaladas en sus mquinas. Usted no encontrar aqu descripciones detalladas de las libreras: esto es para que consulte la documentacin propia de la biblioteca. Esto que se sugiere de consulte la documentacin propia de la biblioteca est muy bien, pero, dnde podemos encontrarla? La respuesta es depende. Algunas bibliotecas ya han sido documentadas mediante RDoc. Eso significa que se puede usar el comando ri para obtener su documentacin. Por ejemplo, a partir de la lnea de comandos, se debe ser capaz de ver la siguiente documentacin del mtodo decode64 en la biblioteca estndar Base64.

% ri Base64.decode64 --------------------------------------------------------------Base64#decode64 decode64(str) ---------------------------------------------------------------------------- Returns the Base64-decoded version of str.


require base64 str = VGhpcyBpcyBsaW5lIG9uZQpUaGlzIG + lzIGxpbmUgdHdvClRoaXMgaXMgbGlu + ZSB0aHJlZQpBbmQgc28gb24uLi4K puts Base64.decode64(str) Generates: This is line one This is line two This is line three And so on... Si no hay documentacin RDoc disponible, el siguiente lugar para buscar es en la propia biblioteca. Si usted tiene una distribucin de cdigo fuente de Ruby, esto se encuentra en los subdirectorios ext/ y lib/.

323

En cambio, si tiene una instalacin slo binaria, todava se puede encontrar la fuente Ruby pura de los mdulos de biblioteca (normalmente en el directorio lib/ruby/1.8/ de la instalacin de Ruby). A menudo, los directorios de los fuentes de las libreras contienen la documentacin que el autor an no ha convertido al formato RDoc. Si usted todava no puede encontrar la documentacin, es el turno de Google. Muchas de las libreras estndar de Ruby tambin se alojan en proyectos externos. Hay autores que desarrollan de forma independiente y luego peridicamente, integran el cdigo en la distribucin estndar de Ruby. Por ejemplo, si se desea informacin detallada sobre la API de la biblioteca YAML, hay que buscar en Google yaml ruby que puede llevar a http://yaml4r.sourceforge.net/doc/. Despus de admirar la obra de arte why the lucky stiff s, un clic y se tiene acceso a sus ms de 40 pginas de manual de referencia. El prximo puerto de escala es la lista de correo ruby-talk. Haga una pregunta (educada) all, y lo ms probable es que obtenga una respuesta erudita en cuestin de horas. Consulte la seccin Mailing Lists, en http://www.rubylang.org/ para ms detalles sobre como unirse a una lista de correo. Y si usted todava no puede encontrar la documentacin, siempre se puede seguir el consejo de Obi Wan y hacer lo que se hace cuando se documenta Ruby --utilizar la fuente. Se sorprender de lo fcil que es leer los fuentes reales de las bibliotecas Ruby y trabajar en los detalles de su utilizacin. Como ejemplo vamos a ver aqu las libreras CGI y CGI::Session.

Librera

CGI

La clase CGI proporciona soporte para programas utilizados como scripts CGI (Common Gateway Interface) en un servidor Web. Los objetos CGI se inicializan con los datos del entorno y desde una solicitud HTTP, y proporcionan accesores convenientes a los datos de formulario y a cookies. Tambin se puede administrar sesiones usando una variedad de mecanismos de almacenamiento. La clase CGI proporciona tambin utilidades bsicas para la generacin de HTML y mtodos de clase de escape y desesescape de solicitudes y HTML. Nota: La implementacin 1.8 de CGI introduce un cambio en la manera de acceder a los datos de formulario. Consulte la documentacin ri de CGI#[] y CGI#params para ms detalles. Ver tambin: CGI:: Session.

Escapa y desescapa caracteres especiales en direcciones URL y HTML. Si la variable $KCODE est establecida en u (por UTF8), la biblioteca se convertir desde HTML a Unicode a UTF-8 interno. require cgi CGI.escape(c:\My Files) CGI.unescape(c%3a%5cMy+Files) CGI::escapeHTML(a<b & c) -> -> -> c%3A%5CMy+Files c:\My Files &quot;a&quot;&lt;b &amp; c -> -> -> a<=>b AA r2

$KCODE = u # Usar UTF8 CGI.unescapeHTML(&quot;a&quot;&lt;=&gt;b) CGI.unescapeHTML(&#65;&#x41;) CGI.unescapeHTML(&#x3c0;r&#178;) Accede a la informacin de la solicitud entrante.

require cgi c = CGI.new c.auth_type c.user_agent

-> ->

basic Mozscape Explorari V5.6

Accede a los campos del formulario de una solicitud entrante. Suponiendo que el siguiente script se instala como test.cgi y el usuario enlaza a l mediante http://mydomain.com/test. cgi?fred=10&barney=cat.

324

require cgi c = CGI.new c[fred] -> c.keys -> c.params ->

10 [barney, fred] {barney=>[cat], fred=>[10]}

Si un formulario contiene varios campos con el mismo nombre, los correspondientes valores se retornarn al script como una matriz. El accesor [ ] retorna slo el primero de estos valores --indexa el resultado del mtodo params para obtener todos ellos. En el siguiente ejemplo asuminos que el formulario tiene tres campos llamados name. require cgi c = CGI.new c[name] c.params[name] c.keys c.params

-> -> -> ->

fred [fred, wilma, barney] [name] {name=>[fred, wilma, barney]}

Envia una respuesta al navegador. (No hay mucha gente que utilice esta forma de generacin de HTML. Considerar una de las bibliotecas de plantillas). require cgi cgi = CGI.new(html4Tr) cgi.header(type => text/html, expires => Time.now + 30) cgi.out do cgi.html do cgi.head{ cgi.title{Hello World!} } + cgi.body do cgi.pre do CGI::escapeHTML( params: + cgi.params.inspect + \n + cookies: + cgi.cookies.inspect + \n) end end end end Almacenar una cookie en el navegador del cliente.

require cgi cgi = CGI.new(html4) cookie = CGI::Cookie.new(name => mycookie, value => chocolate chip, expires => Time.now + 3600) cgi.out(cookie => cookie) do cgi.head + cgi.body { Cookie stored } end Recuperar una cookie almacenada previamente.

require cgi cgi = CGI.new(html4) cookie = cgi.cookies[mycookie] cgi.out(cookie => cookie) do cgi.head + cgi.body { Flavor: + cookie[0] } end

325

Librera

CGI::Session
CGI::Session mantiene un estado persistente de los usuarios Web en un entorno CGI. Las sesiones pueden ser residentes en memoria o se pueden almacenar en disco. Ver ri CGI::Session para ms informacin. require cgi require cgi/session cgi = CGI.new(html3) sess = CGI::Session.new(cgi, session_key => rubyweb, prefix => web-session. ) if sess[lastaccess] msg = Los ltimos que estuvieron aqu #{sess[lastaccess]}. else msg = Parece que no han estado aqu por un tiempo end count = (sess[accesscount] || 0).to_i count += 1 msg << <p>Numero de visitas: #{count} sess[accesscount] = count sess[lastaccess] = Time.now.to_s sess.close cgi.out { cgi.html { cgi.body { msg } } } ---------------------------------------------

cin, en la que se documenta la versin 1.9 de este extraordinario lenguaje de programacin.

Por ltimo y como colofn a esta traduccin libre, un extracto muy interesante de la 3 edi-

Metaprogramacin
El telar de Jacquard, inventado hace ms de 200 aos, fue el primer dispositivo controlado mediante tarjetas perforadas --filas de agujeros en cada tarjeta utilizadas para controlar el patrn a tejer en la tela. Pero imaginemos que si en vez de producir tela, el telar pudiera perforar ms tarjetas, y stas podran alimentar de nuevo el mecanismo. La mquina podra ser utilizada para crear nuevos programas que podran ejecutarse. Y eso sera la metaprogramacin --la escritura de cdigo que escribe cdigo. La programacin es todo lo que trata acerca de la construccin de capas de abstraccin. Para resolver ste problema est la construccin de puentes entre el implacable y mecnico mundo de silicio y el ms ambiguo y fluido mundo que habitamos. Algunos lenguajes de programacin --como C-- son cercanos a la mquina. La distancia desde el cdigo C hasta el dominio de aplicacin puede ser grande. Otros lenguajes --Ruby, tal vez-- proporcionan abstracciones de alto nivel y por lo tanto permiten empezar a escribir cdigo ms cercano al dominio de destino. Por esta razn, la mayora de la gente considera que un lenguaje de alto nivel puede ser un mejor lugar de partida para el desarrollo de aplicaciones (aunque van a seguir discutiendo sobre que lenguaje elegir). Pero cuando se metaprograma, ya no se est limitado a la serie de abstracciones integradas en su lenguaje de programacin. De otro modo, se pueden crear nuevas abstracciones que se integran en el lenguaje de acogida. En efecto, se est creando un nuevo y especfico dominio de lenguaje de programacin 326

--diseado para que se puedan expresar los conceptos que se necesitan para resolver problemas particulares. Ruby hace fcil la metaprogramacin. Como resultado, los programadores de Ruby ms avanzados utilizan tcnicas de metaprogramacin para simplificar su cdigo. En este captulo se muestra cmo lo hacen. No pretende ser un estudio exhaustivo de las tcnicas de metaprogramacin. En su lugar, vamos a ver la los principios subyacentes de Ruby para hacer posible la metaprogramacin. Desde ah, podr inventar su propio lenguaje de metaprogramacin.

Objetos y clases
Las clases y los objetos son, obviamente, el centro de Ruby. Pero a primera vista puede ser un poco confuso. Parece que hay un montn de conceptos: clases, objetos, objetos de clase, mtodos de instancia, mtodos de clase, clases singleton y clases virtuales. En realidad, sin embargo, Ruby tiene solo una nica clase base y estructura de objeto. Un objeto Ruby tiene tres componentes: un conjunto de banderas, algunas variables de instancia y una clase asociada. Una clase Ruby es un objeto de la clase Class. Contiene todas las cosas que tiene un objeto y un conjunto de definiciones de mtodos y una referencia a una superclase (que a su vez es otra clase). Y, fundamentalmente, eso es todo. A partir de aqu, se puede trabajar en los detalles de metaprogramacin para uno mismo. Pero como siempre, el diablo se esconde en los detalles, as que vamos a profundizar un poco ms.

self y Llamada a Mtodo


Ruby tiene el concepto de objeto actual (o en curso). Este objeto actual hace referencia a la variable integrada self de slo lectura. self tiene dos funciones importantes en un programa Ruby en ejecucin. En primer lugar, self controla cmo Ruby encuentra las variables de instancia. Ya hemos dicho que todos los objetos llevan alrededor un conjunto de variables de instancia. Cuando usted accede a una variable de instancia, Ruby busca en el objeto referenciado por self. En segundo lugar, self juega un papel vital en la llamada a mtodo. En Ruby, cada llamada a mtodo se realiza en algn objeto. Este objeto se llama el receptor de la llamada. Cuando se hace una llamada a un mtodo como items.size, el objeto referenciado por la variable items es el receptor y size es el mtodo a invocar. Si se hace una llamada a mtodo, como puts hola, no hay receptor explcito. En este caso, Ruby utiliza el objeto actual, self, como el receptor. Va a la clase self y busca el mtodo (en este caso, puts). Si no puede encontrar el mtodo en la clase, busca en la superclase de la clase y luego en la superclase de esa clase, detenindose cuando se queda sin superclases (que suceder despus de buscar en BasicObject)4. Cuando se hace una llamada a mtodo con un receptor explcito (por ejemplo, invocar items.size), el proceso es sorprendentemente similar. El nico cambio - pero de vital importancia - es el hecho de que self se cambia por la duracin de la llamada. Antes de iniciar el proceso de bsqueda de mtodos, Ruby establece self para el receptor (el objeto referenciado por items en este caso). Entonces, despus de que la llamada retorna, Ruby restablece el valor que self tena antes de la llamada. Vamos a ver cmo funciona esto en la prctica. He aqu un programa simple: Test one @var = 99 two

class def

Si no puede encontrar el mtodo despus de haber agotado la jerarqua de clase del objeto, Ruby busca un mtodo llamado method_missing en el receptor original, empezando desde la clase de self y luego buscando en la cadena de superclase. 327

end def two puts @var end end t = Test.new t.one produce: 99 La llamada a Test.new en la primera de las dos ltimas lneas, crea un nuevo objeto de la clase Test, asignando ese objeto a la variable t. Luego, en la lnea siguiente, llamamos al mtodo t.one. Para ejecutar esta llamada, Ruby establece self a t y luego busca en la clase de t el mtodo one. Ruby encuentra el mtodo definido en la lnea 2 y lo llama. Dentro del mtodo, se establece la variable de instancia @var a 99. Esta variable de instancia se asociar con el objeto actual. Cul es ese objeto? Bueno, la llamada a t.one establece self en t, por lo que en el mtodo one, self ser esa instancia particular de la de la clase Test. En la siguiente lnea, one llama a two. Como no hay receptor explcito, self no se cambia. Cuando Ruby busca el mtodo two, lo ve en Test, la clase de t. El mtodo two hace referencia a una variable de instancia @var. Una vez ms, Ruby busca esta variable en el objeto actual y encuentra la misma variable que fu establecida por el mtodo one. La llamada a puts al final de two funciona de la misma manera. Una vez ms, como no hay receptor explcito, self no se ve afectada. Ruby busca el mtodo puts en la clase del objeto actual, pero no lo encuentra. A continuacin, busca en la superclase de Test, la clase Object y de nuevo, no encuentra puts. Sin embargo, Object se mezcla en el mdulo Kernel y los mdulos mixtos o mezclados actuan como si se trataran de superclases. El mdulo Kernel hace definicin de puts, por lo que el mtodo es encontrado y ejecutado. Despus de que one y two retornan, Ruby restablece self al valor que tena antes de la llamada original a t.one. Esta explicacin puede parecer pesada, pero la comprensin es vital para el dominio de la metaprogramacin en Ruby.

Self y Definicin de Clase


Hemos visto que llamar a un mtodo con un receptor explcito cambia self. Tal vez sorprendentemente, tambin cambia self por una definicin de clase. Esto es una consecuencia del hecho de que las definiciones de clases son en realidad cdigo ejecutable en Ruby --si podemos ejecutar cdigo, necesitamos tener un objeto actual. Una prueba simple muestra lo que este objeto es: class puts puts puts end Test In the definition of class Test self = #{self} Class of self = #{self.class}

produce: In the definition of class Test self = Test Class of self = Class

328

Dentro de una definicin de clase, self se establece en el objeto de clase de la clase que se define. Esto significa que las variables de instancia establecidas en una definicin de clase estarn disponible para los mtodos de la clase (porque self ser el mismo cuando las variables se definen y cuando los mtodos se ejecutan): class Test @var = 99 def self.value_of_var @var end end puts Test.value_of_var produce: 99

Singletons
Ruby permite definir los mtodos que son especficos de un objeto en particular. Estos se denominan mtodos singleton. Vamos a empezar con un simple objeto cadena: animal = cat puts animal.upcase produce: CAT Esto se traduce en la estructura de objeto que se muestra en la Figura. La variable animal apunta a un objeto que contiene (entre otras cosas) el valor de la cadena ( cat) y un puntero a la clase del objeto String. Cuando se llama a animal.upcase, Ruby va al objeto referenciado por la variable animal y luego busca el mtodo upcase en la clase del objeto referenciada desde el objeto animal. Nuestro animal es una cadena y por lo tanto tiene los mtodos de la clase String disponibles.

Ahora vamos a hacerlo ms interesante. Vamos a definir un mtodo singleton en la cadena referenciada por animal: animal = cat def animal.speak puts The #{self} says miaow end

329

animal.speak puts animal.upcase produce: The cat says miaow CAT Ya vemos cmo la llamada a animal.speak funciona cuando nos fijamos en cmo se invocan los mtodos. Ruby establece self al objeto cadena cat referenciado por animal y luego busca un mtodo speak en la clase de ese objeto. Sorprendentemente, se encuentra. Es en principio sorprendente, porque la clase de cat es String, y String no tiene un mtodo speak. As que, hace Ruby algn tipo de magia para los casos especiales de estos mtodos que se definen en objetos individuales? Afortunadamente, la respuesta es no. El modelo objeto de Ruby es notablemente consistente. Cuando se define el mtodo singleton para el objeto cat, Ruby crea una nueva clase annima y define el mtodo speak en esa clase. Esta clase annima a veces es llamada una clase singleton y otras veces una eigenclass (clase-propia). Preferimos la primera, porque enlaza con la idea de los mtodos singleton.

Ruby hace de esta clase singleton la clase del objeto cat y hace de String (que era la clase original de cat) la superclase de la clase singleton. Esto se muestra en la Figura siguiente: Ahora vamos a seguir la llamada a animal.speak. Ruby va al objeto referenciado por animal y luego busca en su clase el mtodo speak. La clase del objeto animal es la clase singleton recin creada y contiene el mtodo que necesitamos. Qu pasa si en su lugar se llama a animal.upcase? El tratamiento se inicia de la misma manera: Ruby busca el mtodo upcase en la clase singleton, pero no lo encuentra all. A continuacin, sigue las reglas normales de proceso y comienza a buscar en la cadena de superclases. La superclase de singleton es String, y Ruby encuentra el mtodo upcase all. Hay que tener en cuenta que aqu no hay un procesamiento de caso especial --el mtodo Ruby de llamada siempre funciona de la misma manera.

Singletons y Clases
Antes mencionamos que dentro de una definicin de clase, self se establece al objeto de la clase que se est definiendo. Resulta que esta es la base para uno de los aspectos ms elegantes del modelo de objetos de Ruby. Recordemos que podemos definir los mtodos de clase en Ruby utilizando cualquiera de estas dos formas: def self.xxx o def ClassName.xxx.

330

class def end def end end

Dave self.class_method_one puts Class method one Dave.class_method_two puts Class method two

Dave.class_method_one Dave.class_method_two produce: Class method one Class method two Ahora sabemos por qu las dos formas son idnticas: dentro de la definicin de clase, self se establece a Dave. Pero ahora que hemos visto los mtodos singleton, tambin sabemos que, en realidad, no hay tal cosa como mtodos de clase en Ruby. Ambas definiciones anteriores definen mtodos singleton del objeto de la clase. Al igual que todos los otros mtodos singleton, se les puede llamar a travs del objeto (en este caso, la clase Dave). Antes de crear los dos mtodos singleton en la clase Dave, el puntero de clase en el objeto de clase apunta a la clase Class (sta es una frase confusa. Otra forma de decirlo es Dave es una clase, por lo que la clase de Dave es la clase Class, aunque esto es bastante confuso tambin). La situacin se parece a la Figura siguiente: El diagrama de objetos para la clase Dave despus de definir los mtodos se muestra en la Figura de la pgina siguiente. Se ve cmo se crea la clase Singleton, como se vi para el ejemplo animal? La clase se inserta como la clase de Dave, y la clase original de Dave se hace padre de esta nueva clase. Ahora podemos unir juntos los dos usos de self, el objeto actual. Hablamos de cmo las variables de instancia se buscan en self, y hablamos de cmo los mtodos singleton definidos en self se convierten en mtodos de clase. Vamos a utilizar estos hechos para acceder a variables de instancia de los objetos de clase: class Test @var = 99 def self.var @var end def self.var=(value) @var = value end end puts Original value = #{Test.var} Test.var = cat puts New value = #{Test.var} produce: Original value = 99 New value = cat Los recin llegados a Ruby comnmente cometen el error de establecer las variables de instancia en lnea en la definicin de clase (como lo hicimos con @var en el cdigo anterior) para luego tratar de acceder a estas variables desde los mtodos de instancia. Como el cdigo ilustra, esto no funcionar, ya que las variables de instancia definidas en el cuerpo de la clase, estn asociadas con el objeto de clase y no con las instancias de la clase.

331

Otra Forma de Acceder a la Clase Singleton


Hemos visto cmo se pueden crear mtodos en la clase singleton de un objeto mediante aadiendo la referencia al objeto a la definicin del mtodo utilizando algo as como def animal.speak. Se puede hacer lo mismo utilizando la notacin de Ruby class << an_objec

animal = dog class << animal def speak puts The #{self} says WOOF! end

332

end animal.speak produce: The dog says WOOF! Dentro de este tipo de definicin de clase, self se establece a la clase singleton para el objeto dado (animal, en este caso). Como las definiciones de clase devuelven el valor de la ltima instruccin ejecutada en el cuerpo de la clase, podemos usar este hecho para obtener el objeto de la clase singleton: animal = dog def animal.speak puts The #{self} says WOOF! end singleton = class << animal def lie puts The #{self} lies down end self # << retorna objeto de la clase singleton end animal.speak animal.lie puts Singleton class object is #{singleton} puts It defines methods #{singleton.instance_methods - cat.methods} produce: The dog says WOOF! The dog lies down Singleton class object is #<Class:#<String:0x0000010086acf8>> It defines methods [:speak, :lie] Hay que tener en cuenta la notacin que Ruby utiliza para denotar una clase singleton: #<Class:#<String:...>>. Ruby le va a decir cuando algn problema impide el uso de clases singleton fuera del contexto de su objeto original. Por ejemplo, no se puede crear una nueva instancia de una clase singleton: singleton = class << cat; self; end singleton.new produce: prog.rb:2:in new: cant create instance of singleton class (TypeError) from /tmp/prog.rb:2:in <main> Vamos a juntar lo que sabemos sobre las variables de instancia, self y las clases singleton. Anteriormente hemos dicho que con mtodos accesores a nivel de clase vamos a obtener y establecer el valor de una variable de instancia definida en un objeto de clase. Sin embargo, Ruby ya tiene a attr_accessor, que define los mtodos getter y setter (obtenedor y establecedor). Normalmente, sin embargo, estos se definen como mtodos de instancia y por lo tanto tendrn acceso a los valores almacenados en las instancias de una clase. Para que funcionen con las variables de instancia a nivel de clase, tenemos que invocar a attr_accessor en la clase singleton: class Test @var = 99

333

class << self attr_accessor :var end end puts Original value = #{Test.var} Test.var = cat puts New value = #{Test.var} produce: Original value = 99 New value = cat

Herencia y Visibilidad
Hay una arruga oscura cuando se trata la definicin de mtodo y la herencia de clase. Dentro de una definicin de clase, puede cambiar la visibilidad de un mtodo en una clase antecesora. Por ejemplo, se puede hacer algo como esto:

class Base def a_method puts Got here end private :a_method end class Derived1 < Base public :a_method end class Derived2 < Base end En este ejemplo, usted sera capaz de invocar un_metodo en instancias de la clase Derived1, pero en cambio, no en instancias de Base o Derived2. Por tanto, cmo logra Ruby esta hazaa de tener un mtodo con dos visibilidades diferentes? En pocas palabras, se engaa. Si una subclase cambia la visibilidad de un mtodo en uno de los antecesores, Ruby efectivamente inserta un mtodo proxy oculto en la subclase que invoca al mtodo original utilizando super. A continuacin, establece la visibilidad de ese proxy para lo que se solicit. Esto significa que el siguiente cdigo: class Derived1 < Base public :a_method end es efectivamente el mismo que este:

class Derived1 < Base def a_method(*) super end public :a_method end La llamada a super puede acceder a mtodos de los padres independientemente de su visibilidad, por lo que la reescritura permite a la subclase reemplazar las reglas de visibilidad su antecesor. Atemoriza, eh?

334

Mdulos y Mixins
Sabemos que cuando se incluye un mdulo en una clase Ruby, los mtodos de instancia de ese mdulo estn disponibles como mtodos de instancia de la clase. module Logger def log(msg) STDERR.puts Time.now.strftime(%H:%M:%S: ) + #{self} (#{msg}) end end class Song include Logger end class Album include Logger end s = Song.new s.log(created) produce: 11:53:26: #<Song:0x0000010086aaa0> (created) La implementacin Ruby de include es muy simple: el mdulo que se incluye es efectivamente aadido como una superclase de la clase que se define. Es como si el mdulo fuera el padre de la clase en la que se mezcla. Y eso sera el final de la descripcin a excepcin de una pequea pega. Debido a que el mdulo es inyectado en la cadena de superclases, se debe mantener un vnculo con la clase padre original. Si no, no habra manera de atravesar la cadena de superclases para buscar mtodos. Sin embargo, usted puede mezclar el mismo mdulo en diferentes clases, y estas clases podran tener cadenas de superclase totalmente diferentes. Si hubiera slo un objeto mdulo mezclado en todas estas clases, no habra manera de hacer el seguimiento de las diferentes superclases para cada una. Para evitar esto, Ruby utiliza un truco ingenioso. Cuando se incluye un mdulo en la clase Ejemplo, Ruby crea un nuevo objeto de clase, hacindole la superclase de Ejemplo, y luego establece la superclase de la nueva clase a la superclase original de Ejemplo. A continuacin, referencia al mdulo desde este nuevo objeto de clase de tal manera que cuando se busca un mtodo en esta clase, en realidad se busca en el mdulo, como se muestra en la figura en la pgina siguiente. Un bonito efecto secundario de este arreglo es que si usted cambia un mdulo despus de su inclusin en una clase, los cambios se reflejan en la clase (y en los objetos de la clase). De esta manera, los mdulos se comportan igual que las clases. module Mod def greeting Hello end end class Example include Mod end ex = Example.new puts Before change, greeting is #{ex.greeting} module Mod def greeting Hi

335

end end puts After change, greeting is #{ex.greeting} produce: Before change, greeting is Hello After change, greeting is Hi

Si un mismo mdulo incluye otros mdulos, una cadena de clases proxy se aadiran a cualquier clase que incluya a este mdulo, un proxy para cada mdulo que est directa o indirectamente incluidos. Por ltimo, Ruby incluir un mdulo slo una vez en una cadena de herencia --incluir un mdulo que ya est incluido en una de sus superclases no tiene ningn efecto.

extend
El mtodo include aade efectivamente un mdulo como una superclase de self. Se utiliza dentro de una definicin de clase para que los mtodos de instancia en el mdulo estn disponibles para las instancias de la clase. Sin embargo, a veces es conveniente aadir los mtodos de instancia a un objeto particular. Esto se hace con Object#extend. He aqu un ejemplo: module Humor def tickle #{self} says hee, hee! end

336

end obj = Grouchy obj.extend Humor puts obj.tickle produce: Grouchy says hee, hee! Vamos a parar un segundo para pensar cmo puede implementarse esto.

Cuando Ruby ejecuta obj.tickle en este ejemplo de cdigo, se hace el truco habitual de buscar en la clase de obj un mtodo llamado tickle (cosquillas). Para que extend funcione, tiene que aadir los mtodos de instancia del mdulo Humor en la cadena de superclases para la clase de obj. Por lo tanto, al igual que con las definiciones de mtodos singleton, Ruby crea una clase singleton para obj y luego incluye el mdulo Humor en esa clase. De hecho, slo para demostrar que esto es todo lo que sucede, aqu est la implementacin en C de extend en el actual intrprete de Ruby 1.9: void rb_extend_object(VALUE obj, VALUE module) { rb_include_module(rb_singleton_class(obj), module); } Hay un truco interesante con extend. Si lo usa en una definicin de clase, los mtodos de mdulo se convierten en mtodos de clase. Esto se debe a que la llamada a extend es equivalente a self.extend, por lo que los mtodos se aaden a self, que en una definicin de clase es la clase misma. He aqu un ejemplo de cmo agregar mtodos de mdulo a nivel de clase:

module Humor def tickle #{self} says hee, hee! end end class Grouchy extend Humor end puts Grouchy.tickle produce: Grouchy says hee, hee! Ms adelante, vamos a ver cmo utilizar extend para aadir mtodos de estilo macro a una clase.

Metaprogramacin de Macros a Nivel de Clase


Si se ha utilizado Ruby para cualquier toma de tiempo, hay muchas posibilidades de haber utilizado attr_accessor, el mtodo que define los mtodos para leer y escribir las variables de instancia: class Song attr_accessor :duration end Si se ha escrito una aplicacin en Ruby on Rails, probablemente se ha utilizado has_many:

class Album < ActiveRecord::Base has_many :tracks end

337

Ambos son ejemplos de mtodos a nivel de clase que generan cdigo entre bastidores. Debido a la forma en que se expanden en algo ms grande, la gente a veces les llama mtodos macro. Vamos a crear un ejemplo trivial y luego construirlo en algo realista. Vamos a empezar por la implementacin de un simple mtodo que aade capacidades de registro a instancias de una clase. Anteriormente lo hicimos con un mdulo --esta vez lo haremos utilizando un mtodo a nivel de clase. Esta es la primera iteracin: class Example def self.add_logging def log(msg) STDERR.puts Time.now.strftime(%H:%M:%S: ) + #{self} (#{msg}) end end add_logging end ex = Example.new ex.log(hello) produce: 11:53:26: #<Example:0x0000010086b018> (hello) Claramente esta es una pieza tonta de cdigo. Tengan paciencia conmigo --voy a mejorar. Pero an podemos aprender algunas cosas de l. En primer lugar, la notificacin add_logging es un mtodo de clase --se define en la clase singleton del objeto clase. Eso significa que podemos llamarlo ms tarde en la definicin de clase sin un receptor explcito, porque self se establece en el objeto clase dentro de una definicin de clase. Luego hay que observar que este mtodo add_logging contiene una definicin de mtodo anidada. Esta definicin interna se v a ejecutar slo cuando se llama al mtodo add_logging. El resultado es que log es definido como un mtodo de instancia de la clase Example. Vamos a dar un paso ms. Podemos definir el mtodo add_logging en una clase y luego usarlo en una subclase. Esto funciona porque la jerarqua de la clase singleton es paralela a la jerarqua de clases regular. Como resultado, los mtodos de clase en una clase padre estn disponibles en la clase hija, tal como muestra el siguiente ejemplo. class Logger def self.add_logging def log(msg) STDERR.puts Time.now.strftime(%H:%M:%S: ) + #{self} (#{msg}) end end end class Example < Logger add_logging end ex = Example.new ex.log(hello) produce: 11:53:26: #<Example:0x0000010086ac58> (hello) Piense de nuevo en los dos ejemplos al inicio de esta seccin. Ambos funcionan de esta manera. attr_accessor es un mtodo de clase definido en la clase Module y as est disponible en todas las definiciones de mdulo y clase. has_many es un mtodo de clase definido en la clase Base dentro

338

del mdulo ActiveRecord de Rails y por lo tanto disponible para todas las clases de esa subclase ActiveRecord::Base. Este ejemplo, todava no es muy convincente. Sera ms fcil an aadir directamente el mtodo log como un mtodo de instancia de nuestra clase Logger. Pero, qu pasa si queremos construir una versin diferente del mtodo log para cada clase que se utiliza? Por ejemplo, vamos a agregar la posibilidad de aadir una breve clase especfica de identificacin de cadena al inicio de cada mensaje de registro. Queremos ser capaces de decir algo como esto: class Song < Logger add_logging Song end class Album < Logger add_logging CD end Para ello, vamos a definir el mtodo log sobre la marcha. Ya no podemos utilizar una sencilla definicin estilo def...end. En su lugar, usaremos define_method, uno de los pilares de la metaprogramacin. define_method toma el nombre de un mtodo y un bloque, definiendo un mtodo con el nombre dado y con el bloque como el cuerpo del mtodo. Cualquier argumento en la definicin del bloque se convierte en parmetro para el mtodo que se est definiendo. class Logger def self.add_logging(id_string) define_method(:log) do |msg| now = Time.now.strftime(%H:%M:%S) STDERR.puts #{now}-#{id_string}: #{self} (#{msg}) end end end class Song < Logger add_logging Tune end class Album < Logger add_logging CD end song = Song.new song.log(rock on) produce: 11:53:26-Tune: #<Song:0x0000010086a230> (rock on) Hay una sutileza importante en este cdigo. El cuerpo del mtodo log contiene la siguiente lnea:

STDERR.puts #{now}-#{id_string}: #{self} (#{msg}) El valor now es una variable local, y msg es el parmetro para el bloque. Pero id_string es el parmetro para el mtodo add_logging que lo contiene. Es accesible en el interior del bloque debido a que las definiciones de bloque crean cierres, permitiendo que el contexto en el que se define el bloque sea trasladado y utilizado cuando se utiliza el bloque. En este caso, estamos dando un valor a partir de un mtodo a nivel de clase y utilizndolo en un mtodo de instancia que estamos definiendo. Este es un patrn comn para crear este tipo de macros de nivel de clase. As como se pasan parmetros desde un mtodo de clase al cuerpo de un mtodo que se define, tambin se puede utilizar el parmetro para determinar el nombre del mtodo o mtodos a crear. He aqu un

339

ejemplo que crea un nuevo tipo de attr_accessor que registra todas las asignaciones a una variable de instancia dada: class AttrLogger def self.attr_logger(name) attr_reader name define_method(#{name}=) do |val| puts Assigning #{val.inspect} to #{name} instance_variable_set(@#{name}, val) end end end class Example < AttrLogger attr_logger :value end ex = Example.new ex.value = 123 puts Value is #{ex.value} ex.value = cat puts Value is now #{ex.value} produce: Assigning 123 to value Value is 123 Assigning cat to value Value is now cat Una vez ms, se utiliza el hecho de que el bloque que define el cuerpo del mtodo es un cierre, accediendo al nombre del atributo en la cadena de mensaje de registro. Hay que notar que tambin se hace uso del hecho de que attr_reader es simplemente un mtodo de clase --que se puede llamar dentro de nuestro mtodo de clase para definir el mtodo de lectura de nuestros atributos. Hay que tener en cuenta otro detalle comn de metaprogramacin --usamos instance_variable_set para establecer el valor de una variable de instancia (duh). Hay un correspondiente mtodo _get que obtiene el valor de una variable de instancia por su nombre.

Macros y Mdulos de Clase


A veces, es perfectamente aceptable definir macros de clase en una clase y luego utilizar estos mtodos macro en las subclases de esa clase. Otras veces sin embargo, no es apropiado utilizarlos en subclases, bien porque ya tenemos la subclase de alguna otra clase o bien porque nuestro esttico diseo se rebela contra algo as como una cancin de una subclase de un registrador. En estos casos, se puede utilizar un mdulo que contenga la implementacin de metaprogramacin. Como hemos visto, utilizando extend dentro de una definicin de clase se aaden mtodos en un mdulo como mtodos de clase para la clase que se define: module AttrLogger def attr_logger(name) attr_reader name define_method(#{name}=) do |val| puts Assigning #{val.inspect} to #{name} instance_variable_set(@#{name}, val) end end end class Example

340

extend AttrLogger attr_logger :value end ex = Example.new ex.value = 123 puts Value is #{ex.value} ex.value = cat puts Value is now #{ex.value} produce: Assigning 123 to value Value is 123 Assigning cat to value Value is now cat Las cosas se ponen un poco ms complicadas si se quieren aadir tanto los mtodos de clase como los mtodos de instancia en la clase que se define. Aqu hay una tcnica, muy utilizada en la implementacin del marco de trabajo de Rails. Se hace uso de un mtodo gancho de Ruby, included, el cual es llamado automticamente por Ruby cuando se incluye un mdulo en una clase. Se le pasa el objeto clase de la clase que se define. module GeneralLogger # Mtodo de instancia que se aade a cualquier clase que lo incluya def log(msg) puts Time.now.strftime(%H:%M: ) + msg end # mdulo que contiene los mtodos de clase que se aadirn module ClassMethods def attr_logger(name) attr_reader name define_method(#{name}=) do |val| log Assigning #{val.inspect} to #{name} instance_variable_set(@#{name}, val) end end end # extender la clase afitriona con los mtodos de clase cuando se incluyen def self.included(host_class) host_class.extend(ClassMethods) end end class Example include GeneralLogger attr_logger :value end ex = Example.new ex.log(New example created) ex.value = 123 puts Value is #{ex.value} ex.value = cat puts Value is #{ex.value} produce:

341

11:53: New example created 11:53: Assigning 123 to value Value is 123 11:53: Assigning cat to value Value is cat Observar cmo la devolucin de llamada incluida se utiliza para extender la clase anfitriona con los mtodos definidos en el mdulo ClassMethods interior. Ahora, como un ejercicio, tendr que ejecutar el ejemplo anterior de su cabeza. Para cada lnea de cdigo, calcular el valor de self. Dominando esto se domina prcticamente este tipo de metaprogramacin en Ruby.

Otras Dos Formas de Definicin de Clases


En el caso de que pensaramos que habamos agotado las formas de definir las clases en Ruby, vamos a echar un vistazo a otras dos opciones.

Expresiones de Subclases
La primera forma no es realmente nada nuevo --no es ms que una generalizacin de la sintaxis de definicin de clase regular. Sabemos que podemos escribir lo siguiente: class Parent ... end class Child < Parent ... end Lo que quizs no sabamos es que la cosa a la derecha de < no tiene por qu ser slo un nombre de clase, sino que puede ser cualquier expresin que devuelva un objeto de clase. En este ejemplo de cdigo, tenemos la constante Parent. Una constante es una simple forma de expresin, y en este caso la constante Parent contiene el objeto clase de la primera clase que hemos definido. Ruby cuenta con una clase llamada Struct, que permite definir clases que contienen slo atributos de datos. Por ejemplo, podramos escribir lo siguiente: Person = Struct.new(:name, :address, :likes) dave = Person.new(Dave, TX) dave.likes = Programming Languages puts dave produce: #<struct Person name=Dave, address=TX, likes=Programming Languages> El valor de retorno de Struct.new(...) es un objeto de clase. Al asignarlo a la constante Person, se puede utilizar Person a partir de entonces como si se tratara de cualquier otra clase. Pero queremos cambiar el mtodo to_s de nuestra estructura. Podramos hacerlo abriendo la clase y escribiendo el siguiente mtodo: Person = Struct.new(:name, :address, :likes) class Person def to_s #{self.name} lives in #{self.address} and likes #{self.likes} end end 342

Sin embargo, podemos hacerlo ms elegante (aunque a costa de un objeto de clase adicional) escribiendo lo siguiente: class Person < Struct.new(:name, :address, :likes) def to_s #{self.name} lives in #{self.address} and likes #{self.likes} end end dave = Person.new(Dave, Texas) dave.likes = Programming Languages puts dave produce: Dave lives in Texas and likes Programming Languages

Creacin de Clases Singleton


Vamos a ver algo de cdigo Ruby:

class Example end ex = Example.new Cuando llamamos a Example.new, estamos invocando el mtodo new en el objeto clase Example. Esto es slo una llamada regular a mtodo --Ruby busca el mtodo new en la clase del objeto (la clase de Example es Class) y lo invoca. Resulta que tambin podemos invocar Class#new directamente: some_class = Class.new puts some_class.class produce: Class Si se le pasa a Class.new un bloque, este se utiliza como el cuerpo de la clase:

some_class = Class.new do def self.class_method puts In class method end def instance_method puts In instance method end end some_class.class_method obj = some_class.new obj.instance_method produce: In class method In instance method Por defecto, estas clases sern descendientes directos de Object. Se les puede dar un padre diferente pasndoles la clase del padre como un parmetro:

343

some_class = Class.new(String) do def vowel_movement tr aeiou, * end end obj = some_class.new(now is the time) puts obj.vowel_movement produce: n*w *s th* t*m* Como Toman las Clases sus Nombres Usted puede haber notado que las clases creadas por Class.new no tienen nombre. Sin embargo, no todo est perdido. Si se asigna el objeto clase para una clase sin nombre a una constante, Ruby automticamente nombra la clase despus de la constante: some_class = Class.new obj = some_class.new puts Initial name is #{some_class.name} SomeClass = some_class puts Then the name is #{some_class.name} puts also works via the object: #{obj.class.name} produce: Initial name is Then the name is SomeClass also works via the object: SomeClass Podemos utilizar estas clases construidas dinmicamente para extender Ruby de maneras interesantes. Por ejemplo, aqu tenemos una reimplementacin simple de la clase Ruby Struct: def MyStruct(*keys) Class.new do attr_accessor *keys def initialize(hash) hash.each do |key, value| instance_variable_set(@#{key}, value) end end end end Person = MyStruct :name, :address, :likes dave = Person.new(name: dave, address: TX, likes: Stilton) chad = Person.new(name: chad, likes: Jazz) chad.address = CO puts Daves name is #{dave.name} puts Chad lives in #{chad.address} produce: Daves name is dave Chad lives in CO

344

instance_eval y class_eval
Los mtodos Object#instance_eval, Module#class_eval y Module#module_eval permiten establecer self en un objeto arbitrario, evaluar el cdigo en un bloque y luego reajustar self: cat.instance_eval do puts Upper case = #{upcase} puts Length is #{self.length} end produce: Upper case = CAT Length is 3 Estas formas tambin toman una cadena (pero vase el recuadro resaltado con algunas notas sobre los peligros de la evaluacin de cadenas): cat.instance_eval(puts Upper=#{upcase}, length=#{self.length}) produce: Upper=CAT, length=3

eval es del ao pasado


Usted puede haber notado que hemos estado haciendo una buena cantidad de metaprogramacin --acceder a variables de instancia, definicin de mtodos y la creacin de clases-- y todava no hemos utilizado eval. Esto es deliberado. En los viejos tiempos de Ruby, el lenguaje careca de muchas de estas funcionalidades de metaprogramacin y eval era la nica manera de lograr estos efectos. Pero eval viene con un par de inconvenientes. En primer lugar, es lento --la llamada a eval efectivamente compila el cdigo en la cadena antes de ejecutarlo. Pero, peor an, eval puede ser peligroso. Si hay alguna posibilidad de datos externos --cosas que vienen de fuera de la aplicacin-- pueden terminar dentro de los parmetros a eval. Entonces tendramos un agujero de seguridad, debido a que los datos externos pueden terminar conteniendo cdigo arbitrario que la aplicacin ejecutar a ciegas. eval ahora se considera un mtodo de ltimo recurso. Tanto class_eval como instance_eval establecen self para la duracin del bloque. Sin embargo, difieren en la manera de configurar el entorno para la definicin del mtodo. class_eval prepara las cosas como si estuviera en el cuerpo de una definicin de clase, por lo que las definiciones de mtodo definen mtodos de instancia: class MyClass end MyClass.class_eval do def instance_method puts In an instance method end end obj = MyClass.new obj.instance_method produce:

345

In an instance method En cambio, la llamada a instance_eval en una clase, acta como si estuviera funcionando dentro de la clase singleton de self. Por lo tanto, cualquier mtodo que se defina se convertir en mtodo de clase. class MyClass end MyClass.instance_eval do def class_method puts In a class method end end MyClass.class_method produce: In a class method Puede ser til recordar que, a la hora de definir los mtodos, class_eval e instance_eval tienen exactamente los nombres equivocados: class_eval define los mtodos de instancia e instance_eval define los mtodos de clase. Que sabemos...! Ruby 1.9 introduce variantes de estos mtodos. Object#instance_exec, Module#class_exec, y Module#module_exec se comportan de forma idntica a sus contrapartes _eval pero toman slo un bloque (es decir, no toman una cadena). Cualquier argumento dado a los mtodos se pasan como parmetros de los bloques. Esta es una caracterstica importante. Anteriormente, era imposible pasar una variable de instancia dentro de un bloque dado a uno de los mtodos _eval --como self cambia por la llamada, estas variables salen del mbito. Con la forma _exec, ahora se puede pasar: @animal = cat dog.instance_exec(@animal) do |other| puts #{other} and #{self} end produce: cat and dog

instance_eval y Constantes
Ruby 1.9 cambi la forma Ruby de buscar constantes cuando se ejecuta un bloque mediante instance_eval y class_eval. Ruby 1.9.2 luego reverti el cambio. En Ruby 1.8 y Ruby 1.9.2, las constantes se buscan en el mbito lxico en el que se les hace referencia. En Ruby 1.9.0, se buscan en el mbito en el que instance_eval es llamado. En este ejemplo (artificial) se muestra el comportamiento en el momento de la ltima vez en que se hizo este libro --que bien podra haber cambiado de nuevo en el momento en que usted lo ejecute... module One CONST = Defined in One def self.eval_block(&block) instance_eval(&block) end end module Two CONST = Defined in Two def self.call_eval_block One.eval_block do CONST end end

346

end Two.call_eval_block # => Defined in Two

En Ruby 1.9.0, este mismo cdigo se evaluar a Defined in One.

instance_eval y Lenguajes de Dominio Especfico


Resulta que instance_eval tiene un papel fundamental que desempear en un determinado tipo de dominio especfico del lenguaje (domain-specific language o DSL). Por ejemplo, podramos escribir un simple DSL para grficos de tortuga5. Para dibujar un conjunto de tres cuadrados de 5x5, podramos escribir lo siguiente6: 3.times do forward(8) pen_down 4.times do forward(4) left end pen_up end Claramente, pen_down, forward, left y pen_up se pueden implementar como mtodos Ruby. Sin embargo, para llamarles sin un receptor de esta manera, o tenemos que estar dentro de una clase que los define (o es un hijo de esa clase) o tenemos que hacer los mtodos globales. instance_eval al rescate. Podemos definir una clase Turtle que defina los distintos mtodos que necesitamos como mtodos de instancia. Tambin vamos a definir un mtodo walk (recorrido) que ejecutar nuestra tortuga DSL, y un mtodo draw para dibujar la imagen resultante: class def def def def def def def end Turtle left; ... end right; ... end forward(n); ... end pen_up; .. end pen_down; ... end walk(...); end draw; ... end

Si implementamos walk correctamente, podemos escribir lo siguiente:

turtle = Turtle.new turtle.walk do 3.times do forward(8) pen_down 4.times do forward(4) left end pen_up end end turtle.draw 5 En los sistemas de grficos de tortuga, puede imaginar que tiene una tortuga a la que puede ordenar avanzar n casillas, girando a la izquierda y girando a la derecha. Tambin puede hacer que la tortuga suba y baje una pluma retrctil. Si la pluma baja, una lnea dibujar el seguimiento de los movimientos posteriores de la tortuga. Muy pocas de estas tortugas existen en la naturaleza, por lo que se tienden a simular en los ordenadores. 6 S, el forward(4) es correcto en este cdigo. El punto inicial se dibuja siempre. 347

As que, cul es la implementacin correcta de walk? Bueno, es evidente que tiene que utilizar instance_eval, porque queremos que los comandos DSL en el bloque, llamen a los mtodos en el objeto tortuga. Tambin tenemos que organizarlo para pasar el bloque dado al mtodo walk para ser evaluado por la llamada instance_eval. Nuestra implementacin es la siguiente: def walk(&block) instance_eval(&block) end Observar como capturamos el bloque en una variable y luego expandimos de nuevo la variable en un bloque en la llamada a instance_eval. Daremos un listado completo del programa tortuga al final.

Es este un buen uso de instance_eval? Depende de las circunstancias. La ventaja es que el cdigo dentro del bloque parece simple --no se tiene que hacer explcito el receptor: 4.times do turtle.forward(4) turtle.left end Hay un inconveniente, sin embargo. Dentro del bloque, el mbito no es el que se cree que es, por lo que este cdigo no funcionara: @size = 4 turtle.walk do 4.times do turtle.forward(@size) turtle.left end end Las variables de instancia se buscan en self, y self en el bloque no es lo mismo que self en el cdigo, que establece la variable de instancia @size. Debido a esto, la mayora de las personas se estn alejando de este estilo de bloque instance_evaled.

Mtodos Gancho
En la seccin anterior de Macros y Mdulos de Clase, se define un mtodo llamado included en nuestro mdulo GeneralLogger. Cuando este mdulo se incluye en una clase, Ruby invoca automticamente a este mtodo included, permitiendo a nuestro mdulo agregar mtodos de clase a la clase anfitriona. included es un ejemplo de un mtodo gancho (a veces llamado devolucin de llamada). Un mtodo gancho es un mtodo que escribimos, pero que Ruby llama desde dentro del intrprete cuando ocurre algn evento determinado. El intrprete busca estos mtodos por su nombre --si se define un mtodo en el contexto adecuado con un nombre apropiado, Ruby lo va a llamar cuando suceda el evento correspondiente. Los mtodos que se pueden invocar desde dentro del intrprete se muestran en la Figura 36 en la pgina siguiente. No vamos a discutirlos aqu --en su lugar, vamos a mostrar algunos ejemplos de uso. En la seccin de referencia de este libro se describen los mtodos individuales, y el captulo Duck Typing, se describen los mtodos de coercin con ms detalle.

El Gancho Heredado
Si una clase define un mtodo de clase llamado inherited, Ruby lo va a llamar siempre que la clase sea una subclase (es decir, siempre que una clase hereda de la original).

348

Este gancho se usa a menudo en situaciones en que una clase base debe llevar un seguimiento de sus hijos. Por ejemplo, una almacenaje en lnea puede ofrecer una variedad de opciones de envo. Cada una podra estar representada por una clase separada, y cada una de estas clases puede ser una subclase de una clase de envo nica (la clase Shipping por ejemplo). Esta clase padre puede hacer un seguimiento de todas las opciones de envo diferentes mediante un registro de cada clase de estas subclases. Cuando llega el momento de mostrar las opciones de envo para el usuario, la aplicacin puede llamar a la clase base para pedirle una lista de las hijas: class Shipping # Clase Base @children = [] # esta variable se encuentra en la clase, no en instancias def self.inherited(child) @children << child end def self.shipping_options(weight, international) @children.select {|child| child.can_ship(weight, international)} end end class def end end class def end end MediaMail < Shipping self.can_ship(weight, international) !international FlatRatePriorityEnvelope < Shipping self.can_ship(weight, international) weight < 64 && !international

class InternationalFlatRateBox < Shipping def self.can_ship(weight, international) weight < 9*16 && international end end puts Shipping 16oz domestic puts Shipping.shipping_options(16, false) puts \nShipping 90oz domestic puts Shipping.shipping_options(90, false)

349

puts \nShipping 16oz international puts Shipping.shipping_options(16, true) produce: Shipping 16oz domestic MediaMail FlatRatePriorityEnvelope Shipping 90oz domestic MediaMail Shipping 16oz international InternationalFlatRateBox Los intrpretes de comandos suelen utilizar este modelo: la clase base mantiene un registro de los comandos disponibles, cada uno de los cuales se implementa en una subclase.

El Gancho method_missing
Anteriormente, hemos visto cmo Ruby ejecuta una llamada a mtodo mediante la bsqueda del mtodo. Primero busca en la clase del objeto, despus en su superclase, luego en la superclase de esa clase y as sucesivamente. Si la llamada al mtodo tiene un receptor explcito, los mtodos privados se omiten en esta bsqueda. Si el mtodo no se encuentra en el momento en que nos quedemos sin superclases (debido a que BasicObject no tiene superclase), Ruby trata de invocar en el objeto original el mtodo gancho method_missing. Una vez ms, se sigue el mismo proceso --Ruby busca primero en la clase del objeto, entonces en su superclase, y as sucesivamente. Sin embargo, Ruby predefine su propia versin de method_missing en la clase BasicObject y por lo general, la bsqueda se detiene ah. El mtodo integrado method_missing bsicamente plantea una excepcin, ya sea un NoMethodError o un NameError dependiendo de las circunstancias. La clave aqu es que method_missing es simplemente un mtodo Ruby. Podemos reemplazar en nuestras propias clases el manejo de las llamadas a mtodos de otra manera indefinida en una forma especfica de aplicacin. method_missing tiene una armadura simple, pero muchas personas se equivocan:

def method_missing(name, *args, &block) # ... El argumento name recibe el nombre del mtodo que no se pudo encontrar y se pasa como un smbolo. El argumento args es una matriz con los argumentos que se pasaron en la llamada original. Y el argumento a veces olvidado block, recibe cualquier bloque que se pas al mtodo original. def method_missing(name, *args, &block) puts Called #{name} with #{args.inspect} and #{block} end wibble wobble 1, 2 wurble(3, 4) { stuff } produce: Called wibble with [] and Called wobble with [1, 2] and Called wurble with [3, 4] and #<Proc:0x0000010086b1d0@/tmp/prog.rb:7> Antes de profundizar demasiado en los detalles, vamos a ofrecer un consejo acerca de la etiqueta. Hay dos principales maneras en que la gente usa method_missing. La primera intercepta cada uso de un mtodo indefinido y lo maneja. La segunda es ms sutil. Intercepta todas las llamadas pero slo se ocupa

350

de algunas de ellas. En este ltimo caso, es importante transmitir la llamada a una superclase, si se decide el no manejar en la implementacin de method_missing: class MyClass < OtherClass def method_missing(name, *args, &block) if <some condition> # manejar llamada else super # de lo contrario pasarlo end end end Si usted falla en pasar en las llamadas no manejadas, la aplicacin ignorar las llamadas a mtodos desconocidos en su clase. Vamos a mostrar un par de usos de method_missing.

method_missing para Simular Accesores


La clase OpenStruct se distribuye con Ruby. Esto permite escribir objetos con atributos que se crean dinmicamente por asignacin. Por ejemplo, se podra escribir lo siguiente: require ostruct obj = OpenStruct.new(name: Dave) obj.address = Texas obj.likes = Programming puts #{obj.name} lives in #{obj.address} and likes #{obj.likes} produce: Dave lives in Texas and likes Programming Vamos a utilizar method_missing para escribir nuestra propia versin de OpenStruct:

class MyOpenStruct < BasicObject def initialize(initial_values = {}) @values = initial_values end def _singleton_class class << self self end end def method_missing(name, *args, &block) if name[-1] == = base_name = name[0..-2].intern _singleton_class.instance_exec(name) do |name| define_method(name) do |value| @values[base_name] = value end end @values[base_name] = args[0] else _singleton_class.instance_exec(name) do |name| define_method(name) do

351

@values[name] end end @values[name] end end end obj = MyOpenStruct.new(name: Dave) obj.address = Texas obj.likes = Programming puts #{obj.name} lives in #{obj.address} and likes #{obj.likes} produce: Dave lives in Texas and likes Programming Observe la forma de base de nuestra clase en BasicObject, una clase introducida en Ruby 1.9. BasicObject es la raz de la jerarqua de objetos de Ruby y contiene slo un nmero mnimo de mtodos: p BasicObject.instance_methods produce: [:==, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__] Esto est bien, porque significa que nuestra clase MyOpenStruct ser capaz de tener atributos tales como display o clase. Si en su lugar hubiramos basado MyOpenStruct en la clase Object, entoncesestos nombres, junto con cuarenta y nueve ms, habran sido predefinidos y por lo tanto no dara lugar a method_missing. Observe tambin otro patrn comn dentro de method_missing. La primera vez que referencia o asignar a un atributo de nuestro objeto, podemos acceder o actualizar los @valores hash adecuadamente. Pero tambin define el mtodo que la llamada est tratando de acceder. Esto significa que la prxima vez que se utilice este atributo, se utilizar el mtodo y no se invocar method_missing. Esto puede o no puede valer la pena dependiendo de los patrones de acceso a su objeto. Tambin hay que notar cmo hay que saltar a travs de unos aros para definir el mtodo. Queremos definir el mtodo slo para el objeto actual. Esto significa que tenemos que poner el mtodo en la clase singleton del objeto. Podemos hacer esto utilizando instance_exec y define_method. Pero eso significa que tenemos que utilizar el truco class << self para obtener la clase singleton del objeto. A travs de una sutil implementacin interesante, define_method siempre define un mtodo de instancia, independientemente de si se invoca a travs de instance_exec o class_exec. Sin embargo, este cdigo revela la parte ms oscura de la utilizacin de method_missing y BasicObject. Considere lo siguiente: obj = MyOpenStruct.new(name: Dave) obj.address = Texas o1 = obj.dup o1.name = Mike o1.address = Colorado produce: prog.rb:37:in <main>: undefined method name= for nil:NilClass (NoMethodError)

352

El mtodo dup no es definido por BasicObject, sino que aparece en la clase Object. As que cuando se llama a dup, fue recogido por nuestro manejador method_missing, y justo retorna nil (porque no tenemos todava un atributo llamado dup). Podramos solucionar este problema para que al menos informe de error: def method_missing(name, *args, &block) if name[-1] == = # como antes... else super unless @values.has_key? name # como antes... end end Esta clase ahora informa de un error si llamamos en ella a dup (o a cualquier otro mtodo). Sin embargo, todava no podemos hacer dup o clone (o inspect, o convertir en una cadena, etc). Aunque BasicObject parece algo natural para method_missing, puede ser ms problemtico de lo que parece .

method_missing como Filtro


Como muestra el ejemplo anterior, method_missing tiene algunos inconvenientes si se utiliza para interceptar todas las llamadas. Probablemente es mejor usarlo para reconocer ciertos patrones de llamada, pasando aquellas que no reconoce a la clase padre para su manejo. Un ejemplo de ello es la funcionalidad de buscador dinmico del mdulo ActiveRecord de Ruby on Rails. ActiveRecord es la biblioteca de objeto-relacional en Rails --que permite acceder a bases de datos relacionales como si fueran depsitos de objetos. Una caracterstica particular le permite buscar las filas que cumplen los criterios de ciertos valores dados en determinadas columnas. Por ejemplo, si a una clase Active Record denominada Book se la asigna una tabla relacional llamada books y esta incluye columnas llamadas title y author, se podra escribir lo siguiente: pickaxe = Book.find_by_title(Programming Ruby) daves_books = Book.find_all_by_author(Dave Thomas) Active Record no predefine todos estos mtodos de bsqueda posibles. En su lugar, utiliza nuestro viejo amigo method_missing. Dentro de este mtodo, busca las llamadas a mtodos no definidos que coinciden con el patrn /^find_(all_)?by_(.*)/7. Si el mtodo que se invoca no coincide con este modelo o si los campos en el nombre del mtodo no corresponden a las columnas en la tabla de la base de datos, Active Record llama a super para generar un verdadero informe method_missing.

Un ltimo Ejemplo
Vamos a reunir todos los temas de metaprogramacin que hemos discutido en un ltimo ejemplo, escribiendo un mdulo que nos permita rastrear o trazar la ejecucin de los mtodos de cualquier clase mezclada en el mdulo. Podramos escribir lo siguiente: require_relative trace_calls class Example def one(arg) puts One called with #{arg} end end ex1 = Example.new ex1.one(Hello) class Example include TraceCalls # no trazar para esta llamada

Tambin busca para /^find_or_(initialize|create)_by_(.*)/.

353

def two(arg1, arg2) arg1 + arg2 end end ex1.one(Goodbye) puts ex1.two(4, 5) produce: One ==> One <== ==> <== 9 called with Hello calling one with [Goodbye] called with Goodbye one returned nil calling two with [4, 5] two returned 9 # pero veamos el traceo de estas dos

Podemos ver de inmediato que hay una sutileza aqu. Cuando mezclamos el mdulo TraceCalls en una clase, tiene que aadir el traceo para cualquier mtodo de instancia existente en esa clase. Tambin tiene que hacer arreglos para aadir el traceo de los mtodos que se aadan posteriormente. Vamos a empezar con el listado completo del mdulo TraceCalls:

module TraceCalls def self.included(klass) klass.instance_methods(false).each do |existing_method| wrap(klass, existing_method) end def klass.method_added(method) # note: definicin anidada unless @trace_calls_internal @trace_calls_internal = true TraceCalls.wrap(self, method) @trace_calls_internal = false end end end def self.wrap(klass, method) klass.instance_eval do method_object = instance_method(method) define_method(method) do |*args, &block| puts ==> calling #{method} with #{args.inspect} result = method_object.bind(self).call(*args, &block) puts <== #{method} returned #{result.inspect} result end end end end Al incluir este mdulo en una clase, se invoca al mtodo gancho included. En primer lugar, utiliza el mtodo de reflexin instance_methods para encontrar todos los mtodos de instancia existentes en la clase anfitriona (el parmetro false lmita la lista a los mtodos en la propia clase, y no en sus superclases). Para cada mtodo existente, el mdulo llama a un mtodo de ayuda, wrap, para aadirle algo de cdigo de traceo. Vamos a hablar de wrap en breve. A continuacin, el mtodo included utiliza otro gancho, method_added. Este es llamado por Ruby

354

cuando se define un mtodo en el receptor. Hay que tener en cuenta que se define este mtodo en la clase que se pasa al mtodo included. Esto significa que el mtodo ser llamado cuando los mtodos son aadidos a esta clase anfitriona y no en el mdulo. Esto es lo que nos permite incluir TraceCalls al principio de una clase y luego aadir los mtodos de esa clase --todas las definiciones mtodo sern manejadas por method_added. Ahora observe el cdigo dentro del mtodo method_added. Tenemos que lidiar con un potencial problema. Como se ve cuando nos fijamos en el mtodo wrap, se aade el seguimiento de un mtodo creando una nueva versin del mtodo que llama al antiguo. Dentro de method_added, se llama a la funcin wrap para aadir este trazado. Pero dentro de wrap, vamos a definir un nuevo mtodo para manejar este envoltorio y la definicin va a invocar al method_added otra vez, y entonces se llama de nuevo a wrap y as sucesivamente, hasta que la pila se agote. Para evitar esto, se utiliza una variable de instancia y se hace el envoltorio slo si no lo hemos echo ya. El mtodo wrap toma un objeto de clase y el nombre de un mtodo a envolver. Busca la definicin original de este mtodo (con instance_method) y lo guarda. A continuacin, redefine este mtodo. Este nuevo mtodo produce salidas de trazado y luego llama al original, pasandole los parmetros y el bloque desde el envoltorio8. Fjese en cmo se llama al mtodo vinculando el mtodo objeto a la instancia actual y luego invocando ese mtodo vinculado. La clave para entender este cdigo y la mayora de cdigo de metaprogramacin, es seguir los principios bsicos que hemos elaborado al comienzo de este captulo --como self cambia como se llama a los mtodos y se definen las clases y cmo se llama a los mtodos mediante su bsqueda en la clase del receptor. Si se queda atascado, haga lo que hacemos nosotros y dibuje pequeas cajas y flechas. Creemos que es til seguir con la convencin que se utiliza en este captulo: los vnculos de clase van a la derecha y los vnculos de superclase arriba. Dado un objeto, una llamada a mtodo es entonces una cuestin de encontrar el objeto receptor. Se va a la derecha una vez y luego siguiendo la cadena de superclase hacia arriba es por dnde tiene que ir.

Entorno de Ejecucin de Nivel Superior


Por ltimo, hay un pequeo detalle que tenemos que cubrir para completar el entorno de la metaprogramacin. Muchas veces en este libro hemos afirmado que todo en Ruby es un objeto. Sin embargo, hemos utilizado una y otra vez lo que parece contradecir esto --el entorno de ejecucin de alto nivel de Ruby: puts Hola, Mundo No hay objeto a la vista. Nosotros, bien podramos haber escrito alguna variante de Fortran o Basic. Sin embargo, cavando ms profundo, nos econtraremos con objetos y clases que acechan en el ms simple cdigo. Sabemos que el literal Hola, Mundo genera un String de Ruby, por lo que es un objeto. Tambin sabemos que la pelada llamada al mtodo puts es efectivamente lo mismo que self.puts. Pero, qu es self? self.class # => Object En el nivel superior, estamos ejecutando el cdigo en el contexto de algn objeto predeterminado. Cuando definimos los mtodos, en realidad estamos creando (privadamente) mtodos de instancia para la clase Object. Esto es bastante sutil, ya que como estn en la clase Object, estos mtodos estn disponibles en todas partes. Y ya que estamos en el contexto de Object, podemos utilizar todos los mtodos de Object (incluidos los mixtos desde Kernel) en forma de funcin. Esto explica por qu podemos llamar a mtodos Kernel tales como puts en el nivel superior (y de hecho a lo largo de todo Ruby), y es porque estos mtodos son parte de cada objeto. Las variables de instancia de nivel superior tambin incumben a este objeto de nivel superior. La metaprogramacin es una de los ms agudas herramientas de Ruby. No hay que tener miedo de usarla para elevar el nivel en que se programa. Pero al mismo tiempo, hay que usarla slo cuando sea necesario --demasiadas aplicaciones metaprogramadas pueden llegar a ser bastante oscuras rpidamente. 8 La capacidad de un bloque de tomar un parmetro de bloque fue introducido en Ruby 1.9. 355

El Progama de Grficos Tortuga


#--# Excerpted from Programming Ruby 1.9, # published by The Pragmatic Bookshelf. # Copyrights apply to this code. It may not be used to create training material, # courses, books, articles, and the like. Contact us if you are in doubt. # We make no guarantees that this code is fit for any purpose. # Visit http://www.pragmaticprogrammer.com/titles/ruby3 for more book information. #--class Turtle # directions: 0 = E, 1 = S, 2 = W, 3 = N # axis: 0 = x, 1 = y def initialize @board = Hash.new( ) @x = @y = 0 @direction = 0 pen_up end def pen_up @pen_down = false end def pen_down @pen_down = true mark_current_location end def forward(n=1) n.times { move } end def left @direction -= 1 @direction = 3 if @direction < 0 end def right @direction += 1 @direction = 0 if @direction > 3 end def walk(&block) instance_eval(&block) end def draw min_x, max_x = @board.keys.map{|x,y| x}.minmax min_y, max_y = @board.keys.map{|x,y| y}.minmax min_y.upto(max_y) do |y| min_x.upto(max_x) do |x| print @board[[x,y]] end puts end end private

356

def move increment = @direction > 1 ? -1 : 1 if @direction.even? @x += increment else @y += increment end mark_current_location end def mark_current_location @board[[@x,@y]] = # if @pen_down end end turtle = Turtle.new turtle.walk do 3.times do forward(8) pen_down 4.times do forward(4) left end pen_up end end turtle.draw produce: ##### # # # # # # ##### ##### # # # # # # ##### ##### # # # # # # #####

357

También podría gustarte