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
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
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
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
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
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
ybreak
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);
- En lambdas,
-
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.
- En lambdas, los argumentos se tratan de la misma manera que en los métodos: estrictos, con
# 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