Aprendiendo Ruby parte 3 - Clases, objetos y demás

Publicado el Lunes, 14 de febrero de 2011
Ruby

Ruby

Siguiendo con la serie sobre Ruby, con este post cubro parte del capítulo 3 del libro Pickaxe. Respecto a la orientación a objetos del lenguaje, todo lo que manipulamos en Ruby es un objeto. Voy a copiar y pegar el código que hice siguiendo el ejemplo del libro. Creo que está bastante simple, y se puede ir entendiendo y viendo cómo maneja las cosas Ruby con solo mirar el código.

El libro explica muchos conceptos básicos de orientación a objetos que voy a omitir, suponiendo que los lectores ya tienen conocimiento del tema. Voy a hacer un resumen de referencia, con algunos detalles, que muestre lo específico del lenguaje. Así que este post es algo así como algo para tener de referencia, pero no voy a detallar demasiado los conceptos.

Veamos la estructura de un objeto básico, con algunas de sus características:

# -*- coding: utf-8 -*-
 
class Libro
  attr_accessor :isbn, :price #Crea ambos (getter y setter)
 
  #Constructor - llamado con Libro.new
  def initialize(isbn, precio)
    #Variables de instancia: @variable
    @isbn = isbn
    @precio = precio
  end
 
  #Método que envía a un objeto cuando quiere mostrarse como String
  #Similar a toString() de Java
  def to_s
    "Libro -  ISBN: #{@isbn} - Precio: #{@precio}"
  end
end

Acá tenemos una clase "Libro", con un constructor que recibe dos parámetros y establece esos valores en las variables de instancia, y un método to_s para mostrar el objeto. Por ahora las variables de instancia son privadas y no tienen forma de accederse desde afuera, o cambiar su valor. Para esto Ruby podemos hacer lo siguiente:

def isbn
  @isbn
end
 
def isbn=(new_isbn)
  @isbn = new_isbn
end

De esta manera creamos métodos para acceder a las variables, al estilo "getter y setter" de Java:

Como los getter y setter son tan comunes, Ruby provee las siguientes atajos:

attr_reader :isbn, :price #Crea el "get" del atributo
attr_writer :isbn, :price #Crea el "set" del atributo
attr_accessor :isbn, :price #Crea ambos (getter y setter)

Bien, a continuación pego el código que sigue el ejemplo del libro. Tenemos tres archivos:
libro.rb - la clase "libro"

# -*- coding: utf-8 -*-
 
class Libro
  attr_accessor :isbn, :price #Crea ambos (getter y setter)
 
  #Constructor - llamado con Libro.new
  def initialize(isbn, precio)
    #Variables de instancia: @variable
    @isbn = isbn
    @precio = precio
  end
 
  #Método que envía a un objeto cuando quiere mostrarse como String
  #Similar a toString() de Java
  def to_s
    "Libro -  ISBN: #{@isbn} - Precio: #{@precio}"
  end
end

csv_reader.rb - El lector de CSV:

require 'csv' #Dependencia externa
require_relative 'videogame' #Dependencia relativa a este archivo
 
class CsvReader
 
  def initialize
    @libros = []
  end
 
  def leer_datos_csv(nombre_archivo_csv)
    CSV.foreach(nombre_archivo_csv, headers: true) do |row|
      @libros << Libro.new(row['isbn'], row['precio'])
    end
  end
 
  def mostrar_todos
    @libros.each do |libro|
      puts libro
    end
  end
 
end

Y por último el app.rb con el código que usa todo esto:

#!/usr/bin/ruby
require_relative 'csv_reader'
 
reader = CsvReader.new
 
ARGV.each do |nombre_archivo|
  STDERR.puts "Procesando #{nombre_archivo}"
  reader.leer_datos_csv(nombre_archivo)
end
 
reader.mostrar_todos

Como ven, se usa ARGV, el array de parámetros recibidos por el script. En mi caso creé un archivo datos.csv con esta info:

"isbn","precio"
"978-1-93435-608-1","49.95"
"978-0136006633","120.74"

Y lo pasé como parámetro al script de arriba:

[fernando@hoth app]$ ./app.rb datos.csv
Procesando datos.csv
Libro -  ISBN: 978-1-93435-608-1 - Precio: 49.95
Libro -  ISBN: 978-0136006633 - Precio: 120.74

Bien, visto esto, pasamos a control de acceso a una clase. En Ruby hay tres niveles de protección: public, protected y private. La diferencia de Ruby con otros lenguajes es que los métodos protected pueden ser llamados de cualquier instancia de las clases que lo definen y sus subclases. Y el acceso de control es determinado dinámicamente, por lo que los errores saltan cuando se ejecuta el código.

Cuando definimos un método sin especificar el acceso, el valor por defecto es public. Y tenemos dos formas de especificar el acceso de los métodos:

class Clase
 
  def metodo
    #por defecto es public
  end
 
  protected
  #A partir de aca, los métodos son protected
 
  def metodo0
    #protected
  end
 
  def metodo1
    #protected
  end
 
  private
  #A partir de este punto, los métodos son privados
 
  def metodo2
    #privado
  end
 
  #otra forma de definir el acceso:
  public     :metodo0
  #protected :metodo
  #private :metodo
 
end

Bien, la última parte del capítulo 3 del libro habla sobre variables. En Ruby las variables son referenicas a objetos. Por lo tanto sucede lo siguiente:

[fernando@hoth app]$ irb
irb(main):001:0> persona = "Fernando"
=> "Fernando"
irb(main):002:0> persona2 = persona
=> "Fernando"
irb(main):003:0> puts persona.object_id
18425820
=> nil
irb(main):004:0>puts persona2.object_id
18425820
=> nil
irb(main):005:0> persona2[8] = "s"
=> "s"
irb(main):006:0> puts persona
Fernandos
=> nil

Esto pasa también en Java, y en Ruby lo que podemos hacer para almacenar una copia del primer objeto en la segunda variable (para crear un objeto nuevo, y por ende referenicar a un objeto distinto) es usar dup. Otro método interesante que podemos usar en Ruby es freeze. Al hacer objeto.freeze, Ruby lanzará un RuntimeError si otra parte de nuestro código intenta modificar objeto.

Para este capítulo, como dije en los posts anteriores, no entré mucho en detalle. Fue todo más que nada una pasada de cosas que tiene Ruby. Es que a esta altura, ya empecé a hacer pruebas, y tengo ganas de programar.Ya estuve mirando algo más de Ruby On Rails, y leí un poco de Sinatra.

En principio ya estuve haciendo una prueba básica que subí en GitHub: RSS Script. Entre otras tantas ideas, tenía una idea para una aplicación en Ruby que necesitaba leer RSS, así que ahí está el resultado. Fue bastante rápido y simple, no es nada del otro mundo el código, pero a lo mejor queda como algo terminado algun día.

También estoy por juntarme con más gente para desarrollar una aplicación simple, pero más completa. Vamos a ver qué sale, ya estaré contando por acá.

7 comentarios en este post

Feed de comentarios
  1. Avatar

    williams 14 febrero. 2011 - 18:24

    Hola te cuento que me vota un error al querer usar require_relative:

    ./app.rb:3: undefined method `require_relative’ for main:Object (NoMethodError)

    la version de ruby q tengo instalado es:

    ruby 1.8.7 (2010-01-10 patchlevel 249) [i586-linux]

    es necesario instalar alguna version o libreria especifica de ruby??

    saludos williams

    • Avatar

      Fernando 14 febrero. 2011 - 18:50

      En ningún momento especifiqué qué versión de Ruby estaba usando, pero debí hacerlo. Más que el libro que estoy usando es específico de una versión.

      Estoy usando Ruby 1.9.2, y por lo que leí, require_relative es nuevo en Ruby 1.9. Te recomiendo actualizar a esta nueva versión.

      ¿Estás siguiendo los posts? ¿Cómo vas con eso?

      ¡Saludos!

    • Avatar

      Fernando 9 junio. 2011 - 20:08

      ¡Puede ser!

      Emacs lo agrega cuando guardo el archivo con extensión .rb. Por lo que leí, es algo del ruby-mode de Emacs. Pero puede que sea algo de Python…

  1. Tweets that mention Aprendiendo Ruby parte 3 - Clases, objetos y demás | Picando Código -- Topsy.com | 14 febrero. 2011 - 16:11

    […] This post was mentioned on Twitter by Pedro Mejias, Fernando. Fernando said: Post: Aprendiendo Ruby parte 3 — Clases, objetos y demás http://ur1.ca/38ki0 […]

Dejar un comentario

Toasty!