AWD 04: Introducción a Ruby - LPC-Ltda/Ruby-on-Rails GitHub Wiki

Libro recomendado para profundizar Ruby: Programming Ruby [TFH13]

##4.1 Ruby es un lenguaje orientado al objeto

Todo lo que usted manipula en Ruby es un objeto, y el resultado de esas manipulaciones es también un objeto.

Los objetos son creados al llamar a un constructor, un método especial asociado con una clase. El constructor estándar es llamado new(). Dada una clase llamada LineItem, usted puede crear objetos line item como sigue:

line_item_one = LineItem.new
line_item_one.quantity = 1
line_item_one.sku = "AUTO_B00"

Los métodos son invocados enviando un mensaje al objeto. El mensaje contiene el nombre del método, junto con cualquier parámetro que el método necesite.

"dave".length
line_item_one.quantity()
cart.add_line_item(next_purchase)
submit_tag "Add to Cart"

Los paréntesis son opcionales.

Nombres Ruby

Variables locales, parámetros de métodos, y nombres de métodos deben todos comenzar con letras minúsculas o con un underscore: order, line_item, y xr2000 son todos válidos. Las variables de instancias comienzan con un signo @, como en @quantity y product_id. La convención Ruby es usar underscore para separar palabras en un método o nombre de variable multi-palabra (así line_item es preferible a lineItem).

Nombres de clases, nombres de módulos y las constantes deben comenzar con una letra mayúscula. Por convención ellas usan la 'capitalización' más que underscores para distinguir el comienzo de la palabra dentro del nombre. Nombres de clases como Object, PurchaseOrder, y LineItem.

Rails usa símbolos para identificar cosas. En particular, los usa como llaves cuando nombra parámetros de métodos y busca cosas en hashes:

redirect_to :action => "edit", :id => params[:id]

Como puede ver un símbolo es igual a un nombre de variable pero prefijado con dos puntos (:). Ejemplos de símbolos son: :action, :line_item y :id.

Métodos

Escribamos un método:

def say_goodnight(name)
  result = 'Good nigth, ' + name
  return result
end

# Time for bed...
puts say_goodnight('Mary-Ellen') # => 'Good night, Mary-Ellen'
puts say_goodnight('John-Boy') # => 'Good night John-Boy'

Usted no necesita un punto y coma al final de la frase en la medida que usted ponga cada frase en una línea separada. Los comentarios en Ruby comienzan con un # y corren hasta el final de la línea. La indentación no es necesaria pero la indentación de dos caracteres es un estándar de facto en Ruby.

Ruby no usa llaves { } para delimitar el cuerpo de las frases que lo componene o definiciones (como con métodos y clases). A cambio, usted termina el cuerpo con la palabra clave end. La palabra clave return es opcional, si no está presente, el resultado de la última expresión evaluada es retornada.

##4.2 Tipos de datos

Strings

Una forma de crear objetos string es usar strings literales, lo cual es una secuencia de caracteres en comillas simples o dobles. En el caso de las comillas simples, con muy pocas excepciones, lo que usted pone entre las comillas es el valor del string.

En el caso de las comillas dobles. Primero, busca las sustituciones -secuencias que comienzan con un caracter backslash- y los reemplaza con algún valor binario. El más común de ellos es \n, el cual es reemplzado por un caracter de nueva línea.

Segundo, Ruby realiza la interpolación de expresiones. En el string la secuencia #{expresion} es reemplazada por el valor de la expresion.

def say_goodnight(name)
  "Good night, #{name.capitalize}"
end
puts say_goodnight('pa')

Arreglos y hashes

Los arreglos y hashes de Ruby son colecciones indexadas. Ambos almacenan colecciones de objetos, accesibles mediante una llave. Con los arreglos, la llave es un entero, mientras que los hashes soportan cualquier objeto como una llave. Ambos crecen tanto como se necesite para contener nuevos elementos. Es más eficiente acceder a elementos de un arreglo, pero los hashes proveen mayor flexibilidad. Cualquier arreglo o hash particular puede contener objetos de diferentes tipos; usted puede tener un arreglo que contenga un entero, un string y un número de punto flotante.

a = [ 1, 'cat', 3.14 ] # array with three elements
a[0] # access the first element (1)
a[2] = nil # setea el tercer elemento, el arreglo ahora es [ 1, 'cat', nil ]

En Ruby nil no significa "sin objeto" como en otros lenguajes. En Ruby nil es un objeto como cualquier otro que representa nada.

El método <<() es comúnmente usado con los arreglos. Este agrega un valor a su receptor.

ages = []
for person in @people
  ages << person.age
end

Ruby tiene un atajo para crear un arreglo de palabras.

a = [ 'ant', 'bee', 'cat', 'dog', 'elk' ]
# this is the same
a = %w{ ant bee cat dog elk }

Los hashes de Ruby son muy similares a los arreglos. Un hash literal usa llaves { } en lugar de paréntesis cuadrados. Un literal debe suministrar dos objetos para cada entrada, uno para la llave, y el otro para el valor.

inst_section = {
  :cello => 'string',
  :clarinet => 'woodwind',
  :drum => 'percussion',
  :oboe => 'woodwind',
  :trumpet => 'brass',
  :violin => 'string',
}

Las llaves en un hash deben ser únicas. Las llaves y valores en un hash pueden ser objetos arbitrarios. En Rails los hashs tipicamente usan símbolos como llaves.

inst_section = {
  cello: 'string',
  clarinet: 'woodwind',
  drum: 'percussion',
  oboe: 'woodwind',
  trumpet: 'brass',
  violin: 'string',

Usted necesita usar la sintaxis => cuando la llave no es un símbolo.

Los hashes usan ls misma sintaxis de paréntesis cuadrados de los arreglos.

inst_section[:oboe]  # => 'woodwind'
inst_section[:cello]  # => 'string'
inst_section[:bassoon]  # => nil

Un hash retorna nil cuando es indexado por una llave que no contiene, lo cual es conveniente, porque nil significa falso cuando se usa en expresiones condicionales.

Usted puede pasar hashes como parámetros a llamadas de métodos. Ruby le permite omitir las llaves, pero solo si el hash es el último parámetro de la llamada. Rails hace extensivo uso de esta característica.

redirect_to action: 'show', id: product_id

Expresiones regulares

En Ruby usted crea una expresión regular escribiendo /pattern/ o %r{}.

Por ejemplo usted puede escribir una expresión regular que coincida con los textos Perl o Phyton así: /Perl|Phyton/.

Los slashes hacia adelante delimitan el patrón. Usted puede usar paréntesis con los patrones como si se tratara de expresiones aritméticas, así usted puede escribir este patrón así /P(erl|hyton)/. Los programas hacen coincidir strings contra expresiones regulares usando el operador de coincidencia =~.

if line =~ /P(erl|hyton)/
  puts "There seems to be another scripting language here"
end

Usted puede explicitar la repetición dentro del patrón. /ab+c/ coincide con un string que contiene una a seguida de una o más bs, seguida de una c. Cambiamos el + por un * y /ab*c/ cincide con una a seguida de cero o más bs y una c.

Los backslash comienzan secuencias especiales; \d coincide con cualquier dígito, \s con el carácter espacio en blanco y \w (word) coincide con un caracter alfanumérico.

##4.3 Lógica

if count > 10
  puts "Try again"
elsif tries == 3
  puts "You lose"
else
  puts "Enter a number"
end
while weight < 100 and num_pallets <= 30
  pallet = next_pallet()
  weight += pallet.weight
  num_pallets += 1
end

unless es como un if excepto que entra cuando la condición es falsa. Similarmente until es como while excepto que el loop continua hasta que la condición es evaluada verdadera.

Modificadores de frases

puts "Danger, Will Robinson" if radiation > 3000
distance = distance * 1.2 while distance < 100

Bloques e iteradores

Los bloques de código son sólo fragmentos de código entre llaves o entre do...end. Una convención común es que la gente usa llaves para bloques de una sóla línea y do/end para bloques multi-línea.

{ puts "Hello" }

do
  club.enroll(person)
  person.socialize
end

Para pasar un bloque a un método, ponga el bloque después de los parámetros (si los hay).

greet { puts "Hi" }
verbose_greet("Dave", "loyal customer") { puts "Hi" }

Un método puede invocar un bloque asociado una o más veces usando la instrucción Ruby yield. Usted puede pensar que yield hace algo como una llamada de método que llama al bloque asociado al método que contiene el yield.

Usted puede pasar parámetros al bloque dándole parámetros al yield. Dentro del bloque, usted lista los nombres de los argumentos que reciben estos parámetros dentro de barras verticales (|).

Los bloques de código aparecen a través de las aplicaciones Ruby. Frecuentemente son usados en conjunto con iteradores: métodos que retornan elementos sucesivos desde algún tipo de colección, como un arreglo.

animals = %w( ant bee cat dog elk ) # create an array
animals.each { |animal| puts animal } # iterate over the contents

Cada entero N implementa un método times(), el cual invoca un bloque asociado N veces.

3.times { print "Ho! " }  # => Ho! Ho! Ho!

El prefijo operador & le permite a un método capturar un bloque pasado como un parámetro nombrado.

def wrap &b
  print "Santa says: "
  3.times(&b)
  print "\n"
end
wrap { print "Ho! " }

Dentro de un bloque, o un método el control es secuencial, excepto cuando es una excepcion.

Excepciones

Las excepciones son objetos de la clase Exception o sus subclases. El método raise causa que una excepción sea levantada. Esto interrumpe el flujo normal a través del código. A cambio Ruby busca llamadas de código en el stack que digan que pueden manejar esta excepción.

Ambos métodos y bloques de código envueltos en las llaves begin y end interceptan cierta clase de excepciones usando las cláusulas rescue.

begin
  content = load_blog_data(file_name)
rescue BlogDataNotFound
  STDERR.puts "File #{file_name} not found"
rescue BlogDataFormatError
  STDERR.puts "Invalid blog data in #{file_name}"
rescue Exception => Exec
  STDERR.puts "General error loading #{file_name}: #{exec.message]"
end

Las cláusulas rescue pueden ser directamente puestas en el nivel externo de la definición del método sin necesidad de encerrar el contenido en un bloque begin/end.

##4.4 Organizando estructuras

Clases

class Order < ActiveRecord::Base
  has_many :line_items
  def self.find_all_unpaid
    self.where('paid = 0')
  end
  def total
    sum = 0
    line_items.each { |li| sum += li.total }
  end
end

La definición de clases comienza con con la palabra clave class seguida por el nombre de la clase (la cual debe comenzar con mayúsculas). Esta clase Order es una subclase de las clase Base dentro del módulo ActiveRecord.

Rails hace uso extensivo de las declaraciones a nivel de clases. Aquí has_many es un método definido por Active Record. Normalente este tipo de métodos hacen aseveraciones acerca de la clase, así en este libro las llamaremos declaraciones.

Dentro del cuerpo de la clase usted puede definir métodos de clase y métodos de instancia. Al prefijar un nombre de método con self, (como lo hicimos en la línea 3) construimos un método de clase; este puede ser llamado sobre la clase en general. En este caso, podemos hacer la siguiente llamada en nuestra aplicación:

to_collect = Order.find_all_unpaid

Los objetos de una clase depositan sus estados en variables de instancia. Estas variables, cuyos nombres comienzan con @, están disponibles para todos los métodos de instancias de una clase. Cada objeto obtienen su propio conjunto de variables de instancia.

Las variables de instancias no son accequibles directamente desde fuera de la clase. Para hacerlas accequibles escriba métodos que retornen sus valores.

class Greeter
  def initialize(name)
    @name = name
  end
  def name
    @name
  end
  def name=(new_name)
    @name = new_name
  end
end

g = Greeter.new("Barney")
g.name  #=> Barney
g.name = "Betty"
g.name  #=> Betty

Ruby provee métodos convenientes para escribir estos métodos accesors por usted (lo gual es una gran noticia para tipos cansados de escribir todos estos getters y setters).

class Greeter
  attr_accessor :name  # create reader and writer methods
  attr_reader :greeting  # create reader only
  attr_writer :age  # create writer only
end

Un método de instancia de una clase en público por defecto; cualquiera puede llamarlo. Esto se puede cambiar.

class MyClass
  def m1    # this method is public
  end
  protected
  def m2    # this method is protected
  end
  private
  def m3    # this method is private
  end
end

La directiva private es la más estricta; los métodos private pueden ser llamados sólo dentro de la misma instancia. Los métodos protected pueden ser llamados tanto por la misma instancia y por otras instancias de las misma clase y sus subclases.

Modulos

Los módulos son similares a las clases en el sentido que de que ellos contienen una colección de métodos, constantes y otros módulos y definiciones de clases. A diferencia de las clases, usted no puede crear objetos basados en módulos.

Los módulos sirven a dos propósitos. Primero, ellos actúan como un namespace, permitiéndole definir métodos cuyos nombres no colisionarán con otros definidos en otro lugar. Segundo, ellos le permiten compartir funcionalidad entre clases -si una clase combina dentro un módulo, esos métodos de instancias del módulo se hacen disponibles como si ellos hubieran sido definidos dentro de la clase. Múltiples clases pueden combinar dentro el mismo módulo, compartiendo la funcionalidad del módulo sin usar la herencia. Usted puede también combinar múltiples módulos dentro de una única clase.

Los métodos helper son un ejemplo de donde Rails usa módulos. Rails automáticamente combina estos modulos helper dentro del template de vista apropiado. Por ejemplo, si usted busca escribir un método helper que pueda ser llamado desde las vistas invocadas por el controlador store, usted puede definir el siguiente módulo en el archivo store_helper.rb en el directorio app/helpers:

module StoreHelper
  def capitalize_word(string)
    string.split(' ').map { |word| word.capitalize }.join(' ')
  end
end

YAML

YAML es un acrónimo recursivo para "YAML ain't Markup Language". En el contexto de Rails, YAML es usado como una forma conveniente de definir la configuración de cosas como bases de datos, data de prueba, y traducciones. Aquí hay un ejemplo:

development:
  adapter: sqlite3
  database: db/development.sqlite3
  pool: 5
  timeout: 5000

En YAML la indentación es importante.

##4.5 Marshaling objects

Ruby puede tomar un objeto y convertirlo en un streams de bytes que pueden ser almacenados fuera de la aplicación. Este proceso es llamado marshaling. Este objeto guardado puede después ser leído por otra instancia de la aplicación (o por una aplicación totalmente separada), y una copia del objeto grabado originalmente puede ser reconstituida.

Hay dos temas potenciales cuando usted usa marshaling. Primero, algunos objetos no pueden ser vaciados. Si el objeto a vaciar incluye bindings, objetos procedimiento o método, instancias de clases IO, u objetos singleton, o si usted intenta vaciar clases o modulos anónimos, se levantará un TypeError.

Segundo, cuando usted levanta un objeto marshaleado, Ruby necesita conocer la definición de clase de ese objeto (y de todos los objetos que contiene).

Rails usa el marshaling para almacenar data de la sesión. Si usted descansa en Rails para cargar dinámicamente clases, es posible que una clase particular no haya sido definida en el punto en que reconstituye la data de la sesión. Por esta razón, usted usará la declaración model en su controlador para listar todos los modelos que son marshaleados.

##4.6 Poniendo todo junto

class CreateProducts < ActiveRecord::Migration
  def change
    create_table :products do |t|
      t.string :title
      t.text :description
      t.string :image_url
      t.decimal :price, precision: 8, scale: 2

      t.timestamps
    end
  end
end

Este código crea una tabla llamada products. Los campos definidos cuando creamos esta table incluyen title, description, image_url y price así como algunos timestamps.

Una clase llamada CreateProducts es definida, la cual hereda de la clase Migration del módulo ActiveRecord. Se define un método llamado change(). Este método llama al método create_table() (definido en ActiveRecord::Migration, pasándole el nombre de la tabla en forma de símbolo.

La llamada a create_table() también pasa un bloque que es evaluado antes de que la tabla sea creada. Este bloque, cuando es llamado, se pasa un objeto llamado t, el cual es usado para acumular una lisya de campos. Rails define un número de métodos sobre este objeto -métodos cuyos nombres son los nombres de tipos de datos comunes. Estos métodos, cuando son llamados, simplemente agregan una definición de campo al conjunto de nombre acumulado.

La definición de decimal también acepta un número de parámetros opcionales expresados en un hash.

##4.7 Modismos Ruby

Metodos como empty! y empty?

Los nombres de métodos de Ruby pueden terminar con un signo de exclamación (método bang) o un signo de interrogación (método predicate). Los métodos bang normalmente hacen algo destructivo para el receptor. Los métodos predicados retornan true o false dependiendo de alguna condición.

a||b

La expresión a||b evalua a. Si esta no es false o nil, entonces la evaluación se detiene y retorna a, en caso contrario retorna b. Esta es una forma común de retornar un valor por defecto si el primer valor no ha sido seteado.

a||=b

La instrucción de asignación soporta una serie de atajos: a op= b es lo mismo que a = a op b

count += 1
price *= discount
count ||= 0

obj = self.new

Algunas veces un método de clase necesita crear una instancia de esa clase.

class Person < ActiveRecord::Base
  def self.for_dave
    Person.new(name: 'Dave')
  end
end

Esto funciona bien, retornando un nuevo objeto Person, pero alguien puede despues hacer una subclase de nuestra clase

class Employee < Person
  # ..
end
dave = Employee.for_dave  # return a Person

El método for_dave() fue cableado para retornar un objeto Person. Usando self.new retorna un nuevo objeto de la clase receptora Employee.

lambda

El operador lambda convierte un bloque en un objeto del tipo Proc. Una sintaxis alternativa, introducida en Ruby 1.9, es ->.

require File.expand_path('../../config/environment', __FILE__)

El método require de Ruby carga un archivo fuente externo dentro de su aplicación. Esto es usado para incluir código de librería y las clases en las cuales descansa nuestra aplicación. En su uso normal, Ruby encuentra estos archivos buscando en una lista de directorios, del LOAD_PATH.

Algunas veces necesitamos ser específico respecto a cual archivo incluir. Podemos hacer esto entregándole a require una ruta de sistema completa. El problema es que nosotros no sabemos cual será ese path - los usuarios pueden instalar nuestro código en cualquier lugar.

Donde quiera que nuestra aplicación termine instalada, la ruta relativa entre el archivo que hace el require y el archivo objetivo será la misma. Sabiendo esto, podemos construir el path absoluto al objetivo usando el método File.expand_path() pasándole la ruta relativa al archivo objetivo así como la ruta absoluta al archivo que hace el requerimiento (disponible en la variable especial __FILE__.