imagen

Kon’nichi wa, Ruby

No se como llegué aquí, pero a darle! No pretende ser un post denso, ni documentación compleja, solo apuntes básicos de Ruby para quien esta dando sus primeros pasos en el lenguaje.

Variables y constantes

  • constantes: usadas para valores que no queremos que cambien durante la ejecución, ruby permite sobreescribirlas arrojando un silencioso warning :( .
  • variables: Solo basta con hacer la asignación
# Declarando constante
MYCONSTANT = "hello"
=> "hello"

# Declarando variables
y = 10
name = "Jhon"

# Asignación en paralelo
a, b, c, d = 10, 20, 30, 40

Identificando el tipo de la variable

name.kind_of? String
# => true

name = "Jhon"
name.class
# => String

Covirtiendo tipos de variables

score = 20
# => 20

# Integer a punto flotante
score.to_f
# => 20.0

# Integer a string
score.to_s
# => "20"

# Integer a binario
# score.to_s(BASE)
54321.to_s(2)
# => "1101010000110001"

# Integer a HEXAdecimal
54321.to_s(16)
# => "d431"

# Integer a OCTAL
54321.to_s(8)
# => "152061"

Scope de las variables y constantes

El alcance es una propiedad de las variables: se refiere a su visibilidad (aquella región del programa donde la variable puede utilizarse). Los distintos tipos de variables, tienen distintas reglas de alcance. ref

Empieza con Alcance Detalle
[a-z]o _ local Asequible solo dentro del metodo, funcion o bloque donde es declarada
$ Global Asequible desde cualquier parte el programa, se desaconseja su uso dado que pueden ser modificadas desde cualquier parte del progra,a y esto puede provocar comportamientos inesperados
@@ de clase Asequibles desde todas las instancias de la clase
@ De instancia Asequible solo a nivel de instancia
[A-Z] constante Declaradas en una clase o modulo, estarán disponibles en toda la clase o modulo. Constantes declaradas fuera de clases o módulos, son asignadas al scope(alcance) global

Podemos detectar el scope de cada variable ayudándonos de defined?

x = 10
# => 10

defined? x
# => "local-variable"

$x = 10
# => 10

defined? $x
# => "global-variable"

Números

# Integer
score = -7

# Float
score = -7.0

# Racional
rate = Rational(2, 3) # 2/3

rate = Rational(4, -6) # -2/3

Strings

"SIMON".downcase
# => "simon"

"Simon".upcase
# => "SIMON"

"Simon".swapcase # => "sIMON"

"Simon".length # => 5

"    Simon   ".strip # => "Simon"

"Simon".empty? # => false

"".empty? # => true

"Santiago Fernandez".gsub("Santiago","Frank") # => "Frank Fernandez"

"Santiago fernandez".include?('Santiago')    # => true

# String a array
"Juan Lopez".split(" ")
=> ["Juan", "Lopez"]

Símbolos

ref

De forma predeterminada la copia se hacer por valor y no por referencia creando objetos diferentes y por consecuencia, usando espacio de memoria diferente.

El método objectid retorna la identidad de un objeto. Si dos objetos tienen el mismo objectid, son el mismo objeto (apuntan al mismo objeto en la memoria).

Los símbolos nos ayudan a resolver esto, pudiendo "apuntar" diferentes variables a un mismo objeto (espacio de memoria)

nombre_1 = "simon"
nombre_2 = nombre_1

nombre_1.object_id
# 11111111

nombre_2.object_id
# 25374233

# Usando un simbolo (puntero) usamos el mismo espacio de memoria
nombre_3 = :nombre
nombre_4 = :nombre

nombre_3.object_id
# 88888888

nombre_4.object_id
# 88888888

Rangos

ref

Los rangos representan intervalos, un conjunto de valores con un inicio y un final.

1..10    # crea rango desde 1 a 10 inclusive
1...10   # crea rango desde 1 a 9
a..c     # crea rango desde a a c

# Podemos usarlo para crear arrays
(1..10).to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

(1...10).to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9]

('a'..'l').to_a
=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"]

('cab'..'car').to_a
=> ["cab", "cac", "cad", "cae", "caf", "cag", "cah", "cai", "caj", "cak", "cal", "cam", 
"can", "cao", "cap", "caq", "car"]

# verificar si un valor esta incluido en el rango
("a".."z").cover?("c")    #=> true
("a".."z").cover?("5")    #=> false

# iterar sobre el rango
(10..15).each {|n| print n, ' ' }
# prints: 10 11 12 13 14 15

# Primer objeto definido en el rango (no necesariamente contenido)
(1..10).begin    #=> 10

# Último objeto definido en el rango (no necesariamente contenido)
(1..10).end    #=> 10

# Primer elemento contenido 
(10..20).first     #=> 10

# Primeros 3 elementos contenido 
(10..20).first(3)  #=> [10, 11, 12]

# último elemento
(10..20).last      #=> 20
(10...20).last     #=> 20

# Últimos 3 elementos
(10..20).last(3)   #=> [18, 19, 20]
(10...20).last(3)  #=> [17, 18, 19]

# Comparar dos rangos
(0..2).eql?(0..2)    #=> true

Arrays

ref

letras = ["a","b","c"]
=> ["a", "b", "c"]

letras.class
=> Array

letras.size
=> 3

letras[0]
=> "a"

letras[-1]
=> "c"

letras.include? "b"
=> true

# Contar cuanto items cumplen con determinada condición
# cuantos item tienen valor 4
[1,2,3,4,4,5,6,7].count { |item| item == 4 }
=> 2

# cuantos item son pares
[1,2,3,4,4,5,6,7].count { |item| item.even? }
=> 4

# Iterar sobre array y retornar un nuevo array con el resultado del bloque
a = [ 1, 2, 3, 4 ]
b = a.map {|x| x*2 }
=> [2, 4, 6, 8]

# modificar el mismo array con el resultado del bloque
a.map! {|x| x*2 }

# Si necesitamos el indice del item en el bloque
 a.map.with_index {|x, i| x * i}
=> [0, 2, 6, 12]

# Filtrar elementos con determinada condición
# .filter es alias de .select
[1,2,3,4,5].filter {|num| num.even? }
=> [2, 4]

[90,20,3,46,300,5].min
=> 3

[90,20,3,46,300,5].max
=> 300

["Juan", "Lopez"].join(" ")
=> "Juan Lopez"

["z","b","a"].sort
=> ["a", "b", "z"]

# Intersection array
array_a = ["a","b","c"]
array_b = ["z","b","y"]
intersection = array_a & array_b
=> ["b"]

Hash

ref

 grades = { "Jane Doe" => 10, "Jim Doe" => 6 }
=> {"Jane Doe"=>10, "Jim Doe"=>6}

grades.class
=> Hash

# definir nuevo elemento
grades["Lee park"] = 7
=> 7
=> {"Jane Doe"=>10, "Jim Doe"=>6, "Lee park"=>7}

# leer un elemento
grades["Lee park"] = 7
=> 7

# Eliminar un elemento
grades.delete("grade b")

grades.size
=> 3

grades.empty?
=> false

# Saber si alguno de sus elementos tiene un valor determinado
grades.has_value? "Lee park"
=> false

grades.has_value? 7
=> true

# Saber si alguno de sus elementos tiene una llave determinada
grades.has_key? "Lee park"
=> true

# Puedes crear un hash vacio
grades = Hash.new
grades["Dorothy Doe"] = 9

# También soportan symbols
options = { :font_size => 10, :font_family => "Arial" }
options[:font_size]  # => 10

# También pudes usar esta sintaxis
options = { font_size: 10, font_family: "Arial" }

# Soportan valores por defecto, si se intenta acceder a una key que no existe
# este será el valor que se retornará
grades = Hash.new("valor a retornar por default")

# Segunda opción para definir valor por default
grades.default = 0

# Podemos invertir el hash, dejando los values como keys y las keys como values
grades = { "grade a" => 1, "grade b" => 2, "grade 3" => 3 }
=> {"grade a"=>1, "grade b"=>2, "grade 3"=>3}

grades.invert
=> {1=>"grade a", 2=>"grade b", 3=>"grade 3"}

# Podemos aplicar transformaciones a los valores de un hash
wages = { a: 100, b: 200, c: 300 }
wages.transform_values {|value| value + 10 } 
=> {:a=>110, :b=>210, :c=>310}

# Puedes incluir las keys en los bloques
wages.transform_values.with_index {|value, index| value + 10 + index }
=> {:a=>110, :b=>211, :c=>312}

# También puedes transformar las keys
wages.transform_keys {|key| key.to_s } 
=> {"a"=>100, "b"=>200, "c"=>300}

wages.transform_keys.with_index {|key, index| "#{key}.#{index}" }
=> {"a.0"=>100, "b.1"=>200, "c.2"=>300}

# Iterar sobre el hash
wages.map { |key, value| "El sueldo de #{key} es #{value}" }
=> ["El sueldo de a es 100", "El sueldo de b es 200", "El sueldo de c es 300"]

# Podemos convertir un array a hash
[["Elli",400],["Yuan",400],["Rin",400]].to_h
=> {"Elli"=>400, "Yuan"=>400, "Rin"=>400}

# Hash merge
h1 = { "a" => 100, "b" => 200 }
h2 = { "b" => 246, "c" => 300 }
h3 = { "b" => 357, "d" => 400 }
h1.merge          #=> {"a"=>100, "b"=>200}
h1.merge(h2)      #=> {"a"=>100, "b"=>246, "c"=>300}
h1.merge(h2, h3)  #=> {"a"=>100, "b"=>357, "c"=>300, "d"=>400}
h1.merge(h2) {|key, oldval, newval| newval - oldval}
                  #=> {"a"=>100, "b"=>46,  "c"=>300}
h1.merge(h2, h3) {|key, oldval, newval| newval - oldval}
                  #=> {"a"=>100, "b"=>311, "c"=>300, "d"=>400}
h1                #=> {"a"=>100, "b"=>200}

h100 = { ["a",1] => 100, ["b",2] => 200 }
h200 = { ["c",1] => 300, ["b",2] => 200 }

h100.merge(h200)
# {
#    [ "a", 1 ] => 100,
#    [ "b", 2 ] => 200,
#    [ "c", 1 ] => 300
#}

Control de flujo

# Condicional simple
if 10 < 20
    print "10 is less than 20"
end

# Condicional inline
print "10 is less than 20" if 10 < 20

# Condicional doble
if customerName == "Fred"
      print "Hello Fred!"
else 
      print "You're not Fred! Where's Fred?"
end

# Condicional multiple
if customerName == "Fred"
      print "Hello Fred!"
elsif customerName == "John"
      print "Hello John!" 
elsif customername == "Robert"
      print "Hello Bob!"
end

# Operador ternario
customerName == "Fred" ? "Hello Fred" : "Who are you?"

# case (switch)
car = "Patriot"

manufacturer = case car
   when "Focus" then "Ford"
   when "Navigator" then "Lincoln"
   when "Camry" then "Toyota"
   when "Civic" then "Honda"
   when "Patriot" then "Jeep"
   when "Jetta" then "VW"
   when "Ceyene" then "Porsche"
   when "Outback" then "Subaru"
   when "520i" then "BMW"
   when "Tundra" then "Nissan"
   else "Unknown"
end

puts "The " + car  + " is made by "  + manufacturer
     
# Case (switch) con rangos
score = 70

result = case score
   when 0..40 then "Fail"
   when 41..60 then "Pass"
   when 61..70 then "Pass with Merit"
   when 71..100 then "Pass with Distinction"
   else "Invalid Score"
end

puts result

Ciclos

# while
i = 0
while i < 5 do
   puts i
   i += 1
end

# rompiendo un while
i = 0
while i < 5
   puts i
   i += 1
   break if i == 2
end

# loop
x=0
loop do
  puts x
  x += 1
  break if x > 5
end

# until
i = 0
until i == 5
   puts i
   i += 1
end

# until inline
puts i += 1 until i == 5
  
# for
for i in 1..8 do
  puts i
end

for i in 1..8 do puts i end

# Rompiendo el for
for i in 1..5 do
  print i,  " "
  break if i == 2
end
 
# times, se ejecuta 5 veces
5.times { |i| puts i }
  
# upto , se ejecuta 3 veces
5.upto(7) do
   puts "hello"
end

# upto , se ejecuta 4 veces
8.upto(11) { |i| puts i }
8
9
10
11

# downto , se ejecuta 6 veces
15.downto(10) {|i| puts i }
15
14
13
12
11
10

Blocks

Los bloques Ruby son pequeñas funciones anónimas que se pueden pasar como argumento a métodos.

Los bloques están encerrados en una instrucción do/end o entre llaves {}, y pueden tener múltiples argumentos.

Los nombres de los argumentos se definen entre dos "pipe" |arg1, arg2|. ref

# Form 1: recommended for single line blocks
[1, 2, 3].each { |num| puts num }

# Form 2: recommended for multi-line blocks
[1, 2, 3].each do |num|
  puts num
end

Ruby cuenta con la palabra reservada yield (producir) para llamar al bloque que se recibe como argumento, cuando usas la palabra reservada yield, el código dentro del bloque sera ejecutado.

def print_once   
  yield
end
print_once { puts "Block is being run" }
# "Block is being run"

# Yield puede ser llamado multiples veces
def print_twice
  yield
  yield
end

print_twice { puts "Hello" }

# "Hello"
# "Hello"

# Yield puede recibir argumentos
def one_two_three
  yield 1
  yield 2
  yield 3
end

one_two_three { |number| puts number * 10 }
# 10, 20, 30

Bloques implícitos y explícitos

Ya vimos los implícitos, pero también pueden ser definidos de forma explicita, indicando & en el argumento que recibirá el bloque, la forma explicita nos facilitara almacenar el bloque en una variable y pasar esta variable como argumento.

Cuando un bloque es almacenado en una variable, se le llama proc

# Implicito
def print_once   
  yield
end
print_once { puts "Block is being run" }

# Explicito
def explicit_block(&my_block)
  my_block.call # same as yield
end
explicit_block { puts "Explicit block called" }

Saber si recibimos un bloque

Si intentas usar yield sin haber recibido un bloque obtendrás el error no block given (yield). Podemos verificar si un bloque fue recibido como argumento usando block_given?

def do_something_with_block
  return "No block given" unless block_given?
  yield
end

Procs

ref

Un Proc nos permite encapsular un bloque de código que sera almacenado en una variable.

Se pueden declarar como lambdas y como proc regular, las diferencias son:

  • Retornos

    • En lambdas, return y break significa salir de esta lambda
    • En proc regular, return significa salir del método de desde donde fue llamado el proc (y lanzará LocalJumpError si se invoca fuera del método)
    • En proc regular, break significa salir del método para el que se proporcionó el bloque. (y arrojará LocalJumpError si se invoca después de que el método retorne);
  • Argumentos

    • En lambdas, los argumentos se tratan de la misma manera que en los métodos: estrictos, con ArgumentError para el número de argumento no coincidente y sin procesamiento adicional de argumentos;
    • Los proc regulares aceptan argumentos con más generosidad: los argumentos que faltan se rellenan con nil, si se recibe como argumento un array, este se deconstruyen como varios argumentos y no se genera ningún error en los argumentos adicionales.
# Definiendo un nuevo proc
square = Proc.new {|x| x**2 }

# Ejejcutando un Proc
square.call(3)  #=> 9

# shorthands:
square.(3)      #=> 9
square[3]       #=> 9

# Creación usando constructor
proc1 = Proc.new {|x| x**2 }

# Creación kernel#poroc method
proc2 = proc {|x| x**2 }
  
# Creación kernel#lambda
lambda1 = lambda {|x| x**2 }

# Creación de lambda con sintaxis literal
lambda_with_arguments = ->(x) { x**2 }
lambda_without_arguments = -> { puts "This is a lambda" }

Métodos

...

Clases

class Persona
  def initialize(name)
    @name = name
  end
  
  # Metodo de instancia
  def name
    @name
  end
  
  # Metodo de instancia
  def name=(name)
    @name = name
    self
  end
  
  # Método de clase
  def self.suggested_names
    ["Harry","Ron"]
  end
  
end

# Creando instancia
p1 = Persona.new "juan"
puts p1.name

# Llamando a método de la instancia
p1.name = "luis"
puts p1.name

# Llamando a método de clase (no disponible en la instancia)
puts Persona.suggested_names

utilizando attr_accessor no es necesario definir los setter y getters

class Persona
  attr_accessor :name
  
  def initialize(name)
    @name = name
  end
  
  # Método de clase
  def self.suggested_names
    ["Harry","Ron"]
  end
end

Utilizando Struct no es necesario definir los setter, getters ni constructor

class Persona < Struct.new(:name)
  # Método de clase
  def self.suggested_names
    ["Harry","Ron"]
  end
end

https://ruby-doc.org/core-2.7.0/Enumerable.html