Escolar Documentos
Profissional Documentos
Cultura Documentos
Nenhuma parte desta publicação pode ser reproduzida sem o consentimento dos autores.
Todas as marcas registradas são de propriedade de seus respectivos donos.
33 Array 59 for..in
61 while e until 102 Tamanho de uma string
63 loop 103 Substrings
64 Controlando o loop 105 Codibcação
66 Usando iteradores 110 Trabalhando com números
Introdução
Ruby é uma linguagem de programação interpretada, com tipagam forte e dinâmica, que tem como foco a simplicidade e produtividade.
Sua sintaxe é extremamente elegante, o que facilita a leitura e escrita de códigos.
Criada pelo japonês Yukihiro “Matz” Matsumoto em meados de 1993, a linguagem só foi lançada publicamente em 1995. Matz
combinou partes de suas linguagens favoritas (Perl, Smalltalk, Eiffel, Ada e Lisp) para criar uma linguagem que fosse, segundo suas
próprias palavras, mais poderosa que Perl e mais orientada a objetos que Python.
Muitas linguagens não tratam números e outros tipos primitivos como objetos, mas no Ruby isso é diferente. No Ruby, tudo é objeto.
Tipos primitivos possuem métodos e podem ter atributos. Classes são objetos.
Hmmm….
Este conteúdo está sendo escrito e estará disponível em breve.
1
CAPÍTULO 1
Sobre o Ruby
Instalação
O Ruby pode ser instalado em todos os sistemas operacionais. Veja abaixo como instalar em sua máquina de desenvolvimento. É
sempre uma boa ideia utilizar a última versão estável do Ruby, a menos que você tenha razões muito fortes para não fazer isso.
Enquanto este livro está sendo escrito, a última versão estável é 1.9.3-p0
1.9.3-p0.
Instalando no Windows
A maneira mais simples de instalar Ruby no Windows é utilizando o Ruby Installer. Acesse o endereço http://rubyinstaller.org/
downloads/ e faça o download do instalador da última versão estável.
Figur
Figuraa 1: Download do Ruby Installer
2
Execute o arquivo baixado, algo como rubyinstaller-1.9.3-p0.exe, e o instalar será iniciado. Aceite os termos de uso. Será exibida,
então, uma janela onde você deve marcar a conbguração “Add Ruby executables to your PATH”. Sem essa opção, o Ruby não bcará
disponível globalmente no seu terminal.
Figur
Figuraa 2: Conbgurando o instalador
Clique em “Install” e, depois, clique em “Finish”. Pronto! Você já tem o Ruby instalado. Para certibcar-se que tudo está funcionando
corretamente, abra o terminal. Para isso, clique no menu “Windows” e execute “powershell” para listar o terminal. Execute o comando
ruby -v para listar a versão do Ruby. Em versões mais antigas do Windows, você pode usar o comando cmd. Se preferir, você também
pode instalar o PowerShell manualmente.
3
Figur
Figuraa 3: Usando o terminal
Se aparecer alguma mensagem muito diferente de ruby 1.9.3-p0 (2011-10-30) [i386-mingw32], a instalação provavelmente não
foi completada com sucesso. Neste caso, reinicie a instalação e siga exatamente os passos descritos acima.
Instalando no Mac OS X
Embora o Mac OS X já venha com o Ruby instalado por padrão, a sua versão é muito antiga.
Hmmm….
Este conteúdo está sendo escrito e estará disponível em breve.
4
Instalando no Linux Ubuntu
No Linux nós também iremos utilizar o RVM para gerenciar as instalações do Ruby. Assim como no Mac OS X, execute o comando
abaixo em seu terminal.
Hmmm….
Este conteúdo está sendo escrito e estará disponível em breve.
Tipo
O Ruby é uma linguagem dinamicamente tipada
tipada, o que signibca dizer que você não precisa debnir o tipo de dado que uma variável irá
armazenar. Além disso, uma mesma variável pode receber tipos diferentes a qualquer momento.
value = 1234
puts value.class
#=> Fixnum
value = "Hello"
puts value.class
#=> String
5
number = 1234
string = "Hello"
number + string
#=> TypeError: String can't be coerced into Fixnum
Ao tentar somar o número 1234 com a string Hello, o Ruby lançará a exceção TypeError. Isso acontece porque a coerção precisa ser
feita explicitamente.
Duck Typing
No Ruby, nós não declaramos o tipo de objetos, nem o tipo do retorno de métodos. Embora isso possa parecer algo muito ruim para
quem está acostumado com linguagens como Java, linguagens dinamicamente tipadas como o Ruby são muito cexíveis, produtivas e,
acredite, seguras. Na maioria das vezes, o medo de não poder contar com o compilador para fazer veribcações de tipos não tem
fundamento.
Desenvolvedores Ruby estão mais acostumados em debnir objetos pelo que eles podem fazer, do que por seu tipo. Esta técnica é
chamada de duck typing.
Se anda como um pato e faz barulho como um pato, então de devve ser um pato. E o interpretador bcará feliz em fazer com que o objeto
seja tratado como um pato. Na prática, isso signibca que em vez de fazer veribcações de tipo de um objeto, você deve se preocupar se
este objeto é capaz de executar o método que você precisa.
Pegue como exemplo strings, arquivos e arrays. As classes Array, File e String implementam o método de instância <<, que quase
sempre signibca append. Você pode se aproveitar desta interface para criar, por exemplo, uma classe de log que não se importa com o
tipo de objeto que irá armazenar esses logs.
class SimpleLogger
def initialize(io)
@io = io
end
6
def log(message)
@io << "#{Time.now} - #{message}\n"
end
end
A classe SimpleLogger consegue enviar os logs para arrays, strings, arquivos e, se quiser, para qualquer outro objeto que implemente
o método <<.
O Ruby realmente abraça o duck typing por toda a linguagem. Diversos protocolos exigem que o objeto apenas implemente um
método to_<protocol>. Muitas operações que envolvem arrays, por exemplo, exigem que o objeto do lado direito da expressão
apenas implemente o método to_ary.
class Numbers
def to_ary
[4, 5, 6]
end
end
[1, 2, 3] + Numbers.new
#=> [1, 2, 3, 4, 5, 6]
A classe Hash, por exemplo, permite que você una dois objetos, desde que o método to_hash seja implementado.
class Configuration
def to_hash
{root: "/etc"}
end
end
config = Configuration.new
7
{name: "Custom config"}.merge(config)
#=> {:name=>"Custom config", :root=>"/etc"}
Não é preciso dizer que este tipo de protocolo por convenção permite criar códigos muito mais cexíveis, com extrema facilidade.
Isso não signibca que saber o tipo de objetos não seja útil. Veja, por exemplo, as operações matemáticas. Qualquer objeto pode ser
convertido em números. Para isso, basta implementar o método coerce, que recebe o objeto que está solicitando a coerção. O
exemplo abaixo mostra como criar uma classe cuja instância pode ser convertida em números inteiros e cutuantes, mas não em
instâncias da classe BigDecimal.
class NumberOne
def coerce(object)
case object
when Integer
[object, 1]
when Float
[object, 1.0]
else
raise TypeError, "#{self.inspect} can't be coerced into #{object.class}"
end
end
end
puts 1 + NumberOne.new
#=> 2
require "bigdecimal"
8
puts BigDecimal.new("1.0") + NumberOne.new
#=> TypeError: FakeNumber can't be coerced into BigDecimal
O duck typing vai além de simples regras; é um estilo de programação. Antes de exigir tipos de objetos, pergunte-se se isso é realmente
necessário. Às vezes, o tipo do objeto é muito importante, mas muitas vezes isso simplesmente não importa.
Variáveis e constantes
Hmmm….
Este conteúdo está sendo escrito e estará disponível em breve.
Métodos
Hmmm….
Este conteúdo está sendo escrito e estará disponível em breve.
Entendendo o self
self será sempre uma referência ao receiver atual e pode ser diferente dependendo do contexto em que você estiver. Por exemplo,
quando estamos no namespace global, nosso self será o objeto main. Já em uma classe, nosso self será a própria classe.
puts self
#=> main
9
class Thing
puts self
end
#=> Thing
Sempre que executar um método, o Ruby irá veribcar se esse método existe no receiver padrão—self— a menos que você especibque-
o explicitamente. E, pelo fato de o receiver padrão ser self, você nem precisa especibcá-lo se não quiser.
class Thing
def do_something
puts "doing something"
end
def do_something_else
do_something
end
end
No método do_something_else poderíamos usar self.do_something, mas isso faria com que nosso código apenas bcasse mais
poluído. No entanto, debnir o receiver é uma coisa muito comum e que, você pode até não se dar conta, mas o faz constantemente
quando escreve código Ruby.
numbers = [3,1,2]
numbers.sort
#=> [1,2,3]
10
Na prática, o receiver é especibcado antes do ponto na chamada de métodos, como em numbers.sort ou text.upcase.
Além de ser o receiver padrão, self também é o responsável por armazenar variáveis de instância de um objeto. Veja o exemplo
abaixo.
class Person
def initialize(name)
@name = name
end
def name
@name
end
end
A instância da classe Person possui uma única variável de instância associada ao seu objeto, self, que é retornada pelo método
Person#name. Analogamente, podemos debnir variáveis de instância em qualquer objeto, como classes.
class Person
def self.count
@count ||= 0
end
def self.count=(increment)
@count = increment
end
11
def initialize(name)
@name = name
self.class.count += 1
end
def name
@name
end
end
O exemplo acima mostra como variáveis de instância podem ser usadas em contextos diferentes. Primeiro, estamos debnindo um
contador de instâncias da classe Person, cujo valor será armazenado em @count. Depois, em nossa própria instância, debnimos o nome
com a variável @name.
Convenções
Os desenvolvedores Ruby seguem uma série de convenções enquanto estão escrevendo seus códigos. Embora você não seja obrigado
à seguir essas convenções, é sempre uma boa ideia garantir que está escrevendo códigos do mesmo jeito que um desenvolvedor mais
experiente.
Conbra neste capítulo quais são as convenções mais utilizadas e evite olhares estranhos.
12
# recomendado
success_message = "You're done!"
# estranho
successMessage = "You're wrong!"
• Rails
• ActiveSupport
• Net
• HTTP
• HTTP::POST
• XML
# recomendado
SUCCESS_MESSAGE = "You're done!"
# estranho
SuccessMessage = "You're wrong!"
Métodos predicados (aqueles que retornam valores booleanos) devem terminar com ? e não precisam de um prebxo is.
# recomendado
def ready?
status == "ready"
13
end
# estranho
def is_ready?
status == "ready"
end
Métodos que modibcam self, lançam exceções ou são potencialmente perigosos/destrutivos devem terminar com uma exclamação.
# recomendado
def save!
save or raise("OMG!")
end
# recomendado: 2 espaços
def hello
puts "Hello!"
end
# estranho: 4 espaços
def hello
puts "Hello!"
end
14
puts "Hello!"
end
# recomendado
sum = 1 + 1
x, y = 1, 2
# estranho
sum = 1+1
x,y = 1,2
# recomendado
hello("John")
numbers = [1, 2, 3]
# estranho
hello( "John" )
numbers = [ 1, 2, 3 ]
As quebras de linha devem seguir o estilo Unix, ou seja, devem ser inseridas como \n. Sempre adicione uma nova linha \n ao bnal do
seu arquivo.
15
Definindo e executando métodos
Quando o método receber argumentos, sempre coloque os parênteses e separe os argumentos corretamente.
# recomendado
def sum(n1, n2)
n1 + n2
end
# estranho
def sum( n1, n2 )
n1 + n2
end
# mais estranho
def sum n1, n2
n1 + n2
end
# recomendado
def message
"Hello"
end
# estranho
def message()
"Hello"
end
16
A mesma regra deve ser aplicada quando você estiver executando métodos.
# recomendado
user.run
# estranho
user.run()
Esta regra possui uma exceção. Quando um método é debnido com o mesmo nome de uma constante, você deve usar os parênteses.
Caso contrário, você estará acessando a própria constante, que normalmente será um módulo ou classe.
def Foo
puts "You called the Foo method"
end
def message
"Hello"
end
puts message
#=> Hello
17
No entanto, se você precisar encerrar o cuxo de execução antes da última linha, deve usar o return.
def message(text)
return "Hello stranger!" unless text
text
end
message(nil)
#=> Hello stranger!
message("Hello there!")
#=> Hello there!
Usando blocos
Métodos podem receber blocos[2]. Quando o seu bloco possuir mais de uma instrução ou precisar encadeado, utilize chaves ({ e }) para
criar blocos inline.
Se o seu bloco possuir mais de uma instrução, você deve utilizar as palavras-chave do..end.
2
Não se preocupe com o que são blocos por enquanto. Você verá mais sobre este assunto mais à frente.
18
file.write " #nice"
end
Esta convenção também é utilizada para acessar a documentação através da linha de comando, como você verá mais à frente.
Atribuição de variáveis
Hmmm….
Este conteúdo está sendo escrito e estará disponível em breve.
19
Conhecendo o IRB
O Ruby vem com um shell REPL[1] chamado Interactive Ruby (IRB). Ele faz exatamente o que o nome sugere: ele lê uma linha, executa
esta linha, exibe o resultado da execução e faz o loop, esperando por uma nova linha. O IRB é a melhor maneira de testar q
$ irb
irb(main):001:0>
Neste shell você pode digitar qualquer código Ruby. Experimente digitar alguma expressão matemática simples.
irb(main):001:0> 1 + 1
=> 2
No Ruby, tudo[3] é objeto. Você pode descobrir qual a classe de um objeto com o método Object#class.
irb(main):002:0> 1234.class
=> Fixnum
irb(main):003:0> "Hello".class
=> String
Métodos são ações que um objeto pode realizar. No exemplo acima, o método Object#class apenas informa qual a classe que
instanciou um determinado objeto. Você também pode acessar a lista de todos os métodos que um objeto possui com o método
Object#methods. Veja, por exemplo, quais são os métodos de uma string:
1
Read-Eval-Print-Loop
3
Na verdade, quase tudo no Ruby é objeto. Algumas coisas não são objetos diretamente, embora você consiga acessá-las de outras
maneiras.
20
irb(main):004:0> "Hello".methods
=> [:<=>, :==, :===, :eql?, :hash, :casecmp, :+, :*, :%, :[], :[]=, :insert,
:length, :size, :bytesize, :empty?, :=~, :match, :succ, :succ!, :next, :next!,
:upto, :index, :rindex, :replace, :clear, :chr, :getbyte, :setbyte, :byteslice,
:to_i, :to_f, :to_s, :to_str, :inspect, :dump, :upcase, :downcase, :capitalize,
:swapcase, :upcase!, :downcase!, :capitalize!, :swapcase!, :hex, :oct, :split,
:lines, :bytes, :chars, :codepoints, :reverse, :reverse!, :concat, :<<, :prepend,
:crypt, :intern, :to_sym, :ord, :include?, :start_with?, :end_with?, :scan,
:ljust, :rjust, :center, :sub, :gsub, :chop, :chomp, :strip, :lstrip, :rstrip,
:sub!, :gsub!, :chop!, :chomp!, :strip!, :lstrip!, :rstrip!, :tr, :tr_s, :delete,
:squeeze, :count, :tr!, :tr_s!, :delete!, :squeeze!, :each_line, :each_byte,
:each_char, :each_codepoint, :sum, :slice, :slice!, :partition, :rpartition,
:encoding, :force_encoding, :valid_encoding?, :ascii_only?, :unpack, :encode,
:encode!, :to_r, :to_c, :>, :>=, :<, :<=, :between?, :nil?, :!~, :class,
:singleton_class, :clone, :dup, :initialize_dup, :initialize_clone, :taint,
:tainted?, :untaint, :untrust, :untrusted?, :trust, :freeze, :frozen?, :methods,
:singleton_methods, :protected_methods, :private_methods, :public_methods,
:instance_variables, :instance_variable_get, :instance_variable_set,
:instance_variable_defined?, :instance_of?, :kind_of?, :is_a?, :tap, :send,
:public_send, :respond_to?, :respond_to_missing?, :extend, :display, :method,
:public_method, :define_singleton_method, :object_id, :to_enum, :enum_for,
:equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__]
Perceba como o shell do IRB muda sua apresentação, de acordo com o nível de indentação do código.
21
O IRB permite testar códigos Ruby interativamente. Escreva outros tipos de expressões para se familiarizar com esta excelente
ferramenta.
puts "Hello!"
puts "Current time: #{Time.now}"
Para executar este código, basta executar o interpretador Ruby, passando o caminho do arquivo como argumento.
$ ruby hello.rb
Hello!
Current time: 2011-12-23 10:39:20 -0200
Em sistemas operacionais Unix, é possível especibcar o shebang, que determina qual o tipo de interpretador que será usado para
executar aquele arquivo.
#!/usr/bin/env ruby
puts "Hello!"
puts "Current time: #{Time.now}"
Agora você pode fazer com que o arquivo seja executável com o comando chmod +x hello.rb. Ao fazer isso, você não mais precisará
executar o interpretador Ruby.
22
$ ./hello.rb
Hello!
Current time: 2011-12-23 10:44:30 -0200
Esta ferramenta ri permite visualizar a documentação de métodos, constantes classes e módulos da Standard Library (que já vem com
o Ruby) e de códigos de desenvolvedores 3rd party.
$ ri GC
= GC
You may obtain information about the operation of the GC through GC::Profiler.
------------------------------------------------------------------------------
= Class methods:
= Instance methods:
23
garbage_collect
Você também pode visualizar a documentação de um método especíbco. Veja, por exemplo, a documentação do método
String#upcase.
$ ri String#upcase
= String#upcase
------------------------------------------------------------------------------
Returns a copy of str with all lowercase letters replaced with their
uppercase counterparts. The operation is locale insensitive---only characters
``a'' to ``z'' are affected. Note: case replacement is effective only in ASCII
region.
$ ri GC.enable
= GC.enable
24
GC.enable -> true or false
------------------------------------------------------------------------------
25
CAPÍTULO 1
hello = "Hello"
ruby = 'Ruby'
A diferença entre os dois delimitadores é que os apóstrofos ignoram caracteres como \n e \t.
Outra diferença é que strings delimitadas por apóstrofos não podem ser interpoladas. Interpolação é o processo de debnir uma
expressão Ruby dentro de uma string, de modo que seu resultado substitua os delimitadores #{} que englobam a expressão.
now = Time.now
puts "Time: #{now}"
#=> Time: 2011-12-21 01:35:30 -0200
26
puts 'Time: #{now}'
#=> Time: #{now}
Você pode debnir strings com múltiplas linhas sem precisar de nenhuma sintaxe especial.
Caracteres podem ser escapados com uma barra invertida antes do caracter.
Você pode debnir strings de outras formas. Uma delas é usando o formato heredoc, que possui duas variações.
text = <<TEXT
This can be a long text with
multiple lines. And I don't
need to escape "quotes".
TEXT
text = <<-TEXT
This can be a long text with
multiple lines. And I don't
27
need to escape "quotes".
TEXT
A string heredoc precisa de um identibcador (escrito em letras maiúsculas) que será usado para iniciar e terminar a string. A diferença
entre as duas variações é que a primeira forma exige que o identibcador de término esteja no começo da linha
linha.
puts <<-TEXT.upcase
This can be a long text with
multiple lines.
TEXT
#=> THIS CAN BE A LONG TEXT WITH
#=> MULTIPLE LINES.
Uma outra característica de strings heredoc é que elas preservam espaços em branco no começo da linha.
puts <<-TEXT
This can be a long text with
multiple lines.
TEXT
#=> This can be a long text with
#=> multiple lines.
28
#=> Time: 2011-12-21 01:40:09 -0200
Perceba nos exemplos acima que foram usados diferentes tipos de delimitadores: %q[], %Q() e %!!. Na prática, você pode usar
qualquer caracter como delimitador. Note que você precisará escapar os caracteres da string que forem iguais ao delimitador.
29
Números
O Ruby possui 8 classes para representar
números. Todos os objetos que representam
números no Ruby são instâncias da classe
Numeric. Números são imutáveis e, por este
motivo, não existem métodos que podem
mudar o valor de um número; se você tentar
fazer isso, receberá a mensagem de erro Can't
change the value of self.
Fixnum
Números inteiros não possuem um tamanho determinado, pois o seu tamanho é debnido pela quantidade de memória disponível.
Quando debnidos nos intervalos entre −230 e 230-1 ou −262 e 262-1, inteiros são debnidos como instâncias da classe Fixnum. Inteiros
fora deste intervalo são automaticamente debnidos como objetos da classe Bignum, em um processo totalmente transparente e
automático.
number = 1000
3.times do
number *= number
puts "#{number.class} => #{number}"
1
A Standard Library é o conjunto de bibliotecas que vem com a instalação do Ruby.
30
end
# Output:
# Fixnum => 1000000
# Fixnum => 1000000000000
# Bignum => 1000000000000000000000000
Você pode debnir números inteiros usando um sinal de + ou - opcional para debnir valores positivos e negativos, um indicador opcional
da base do número, seguidos pela sequência de números especibcados na base escolhida.
Float
Números de ponto cutuante são debnidos pelo . (ponto decimal) após um ou mais números decimais, seguido por mais números
decimais. Você também pode, opcionalmente, utilizar um expoente. Ao contrário dos números inteiros, números com ponto cutuante
não podem ser debnidos em outra base diferente de 10.
31
No Ruby, não é possível debnir números com ponto cutuante sem ter um número antes do ponto decimal. Se você tentar debnir um
número como .1 irá receber uma mensagem de erro como no .<digit> floating literal anymore; put 0 before dot.
Números de ponto cutuante seguem a especibcação IEEE-754, assim como a maioria das linguagens e hardwares do mercado. Dada a
forma como os números de ponto cutuante são tratados, frações como 1/10 e 1/100 não podem ser representadas corretamente. É
muito comum cálculos como o exemplo à seguir causarem espanto, mesmo ele acontecendo em muitas outras linguagens[2].
O Ruby consegue efetuar cálculos deste tipo quando objetos da classe BigNumber são utilizados.
BigDecimal
A classe BigDecimal permite realizar cálculos onde o arredondamento é muito importante, como em cálculos bnanceiros. Números do
tipo BigDecimal são praticamente ilimitados (expoentes acima de 1 bilhão são suportados) e possuem controle preciso dos modos de
arredondamento.
require "bigdecimal"
BigDecimal("0.3") - BigDecimal("0.2") == 0.1
#=> true
Você pode especibcar os modos de arredondamento e quantidade de dígitos decimais que serão computados. Para ver a referência
completa, acesse a documentação.
Complex
Para ver a referência completa, acesse a documentação.
2
O mesmo problema acontece no C, Java, Python e JavaScript.
32
Rational
Para ver a referência completa, acesse a documentação.
Array
O Ruby possui arrays, que são listas que podem guardar qualquer tipo de objeto e não precisam ser criadas com tamanho determinado;
novos ítens podem ser adicionados a qualquer momento.
Para criar um novo array, basta instanciar a classe Array ou utilizar o atalho [].
O método Array#initialize pode ser utilizado de maneiras diferentes. Você pode dizer com quantos ítens o array deve ser iniciado.
Por padrão, o array será iniciado com o valor nil.
items = Array.new(5)
#=> [nil, nil, nil, nil, nil]
Você também pode passar um valor inicial que será usado para popular este array.
Também é possível iniciar um array com um bloco. Neste caso, o valor retornado pelo bloco será usado.
33
items = Array.new(5) {|i| i * 2}
#=> [0, 2, 4, 6, 8]
Se todos os elementos do array forem strings, você pode utilizar a sintaxe %w ou %W. Assim como as strings, você pode utilizar qualquer
delimitador.
words = %w[jan fev mar apr may jun jul aug sep oct nov dec]
#=> ["jan", "fev", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"]
letters = %w(a b c)
#=> ["a", "b", "c"]
Arrays só podem ter índices numéricos. Os índices começam em 0 e também podem ser acessados de forma negativa.
34
numbers = Array.new(10) {|n| n * 2}
#=> [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
numbers[0] #=> 0
numbers.first #=> 0
numbers[4] #=> 8
numbers[-1] #=> 18 - A mesma coisa que numbers[numbers.size - 1]
numbers.last #=> 18
Hash
Um outro tipo de estrutura de dados do Ruby é o Hash. Hashes são como arrays, com a diferença que o índice de um hash pode ser
qualquer objeto. À partir do Ruby 1.9, hashes enumeram seus valores na ordem que as chaves foram inseridas. No Ruby 1.8 esse
comportamento era imprevisível.
Você pode debnir o valor-padrão de uma chave que ainda não existe no array. Para isso, basta passar um argumento na hora que for
instanciar o hash.
options = Hash.new("OMG!!!")
options["invalid key"]
#=> OMG!!!
35
Você também pode debnir o valor-padrão através de um bloco.
Perceba que os valores-padrão que são retornados não são armazenados no hash. É de responsabilidade do bloco fazer esta atribuição.
Você também pode inicializar arrays usando o método Hash.[]. Você pode passar uma lista de argumentos que alternam entre chave e
valor.
O método Hash.[] também aceita um array de arrays de dois elementos que identibcam a chave e o valor.
36
Por último, você pode passar um objeto que pode ser convertido em hash através do método to_hash.
À partir do Ruby 1.9 é possível debnir hashes usando uma sintaxe semelhante a do JavaScript. Uma característica dessa sintaxe é que
as chaves são geradas como símbolos.
user.keys
#=> [:name, :age]
Symbol
Símbolos são objetos que representam nomes no Ruby e são muito utilizados como identibcadores, principalmente como chaves de
hashes. Eles são gerados através da sintaxe :symbol ou :"symbol", além dos métodos String#to_sym e String#intern.
symbol = :john
symbol = :"john doe"
symbol = "john".to_sym
symbol = "john doe".intern
37
symbol = %s[john doe]
symbol.class
#=> Symbol
Símbolos compartilham sempre o mesmo espaço em memória, independente do lugar onde foram criados.
name = :john
name.object_id
#=> 302248
other_name = :john
other_name.object_id
#=> 302248
Boolean
No Ruby os valores booleanos são true e false, que são instâncias das classes TrueClass e FalseClass. Infelizmente, ambas as
classes não possuem uma superclasse comum.
true.class
#=> TrueClass
false.class
#=> FalseClass
Os valores booleanos também podem ser acessados através das constantes TRUE e FALSE.
TRUE.class
#=> TrueClass
38
FALSE.class
#=> FalseClass
Os valores booleanos true e false ocupam sempre o mesmo espaço em memória, através dos ids 2 e 0, respectivamente.
true.object_id
#=> 2
false.object_id
#=> 0
nil
O Ruby debne o tipo nulo através do do objeto nil, que é uma instância da classe NilClass. O nil ocupa sempre o mesmo espaço em
memória com o id 4.
nil.class
#=> NilClass
nil.object_id
#=> 4
Este é o valor de retorno de blocos e métodos que não retornam nada, o que explicitamente usam as palavras-chave return e next
sem nenhum valor.
def hello
end
39
hello.class
#=> NilClass
def hello
return
end
hello.class
#=> NilClass
Range
Para debnir intervalos de números e strings podemos utilizar a classe Range.
numbers = 1..10
numbers.class
#=> Range
letters = "a".."z"
letters.class
#=> Range
numbers = 1...10
numbers.cover?(10)
#=> false
40
letters = "a".."z"
letters.to_a
#=> [
#=> "a", "b", "c", "d", "e", "f", "g", "h", "i", "j",
#=> "k", "l", "m", "n", "o", "p", "q", "r", "s", "t",
#=> "u", "v", "w", "x", "y", "z"
#=> ]
strings = "1a".."1e"
strings.to_a
#=> ["1a", "1b", "1c", "1d", "1e"]
Sempre que precisar descobrir se um valor está incluído em um intervalo, utilize o método Range#cover?. Ele é muito mais rápido que
transformar o intervalo em array e depois veribcar se o ítem está incluído com o método Array#include?.
Expressões regulares
Expressões regulares são padrões (ou patterns) que permitem descrever o conteúdo de uma string. Elas podem ser utilizadas para
veribcar se uma string contém um determinado padrão ou para extrair partes dessa string. Para criar uma expressão regular você deve
debnir o padrão utilizando a sintaxe /pattern/ ou %r(pattern).
41
regex = /hello/
Alguns caracteres precisam ser escapados pois eles tem um signibcado especial em expressões regulares. São os chamados
metacaracteres: (, ), [, ], {, }, ., ?, + e *.
regex = /\Ahttps?:\/\//
Perceba que estamos escapando a /. Este caracter em particular pode continuar sendo utilizado sem precisar ser escapado se
debnirmos a expressão regular com %r(). Note que você pode utilizar qualquer caracter como delimitador.
regex = %r(\Ahttps?://)
regex = %r[\Ahttps?://]
Expressões regulares são extremamente poderosas. Elas permitem veribcar padrões que seriam difíceis (e em alguns casos
impossíveis) de serem feitas de outras maneiras. Se você deseja aprender mais sobre assunto, leia o livro Expressões Regulares: uma
abordagem divertida, escrito por Aurélio Marinho Jargas, e que está disponível gratuitamente para leitura online.
Procs e lambdas
Procs são blocos de código que podem ser associados a uma variável e que funcionam como funções anonônimas. Muitas vezes você
precisa de um método utilitário que irá fazer algumas pequenas computações onde criar um método propriamente dito seria muito
trabalho; é aí que entram as procs.
Para debnir uma nova proc, você pode utilizar o método Proc.new ou o método Kernel#proc.
# alternativa 1
message = Proc.new { "Hello" }
42
# alternativa 2
message = proc { "Hello" }
Para executar essas procs você pode utilizar três métodos diferentes.
message.call
message[]
message.()
A última maneira de execução (sum.(1, 2)) está disponível à partir do Ruby 1.9.
Por convenção, procs que possuem uma única expressão devem ser debnidas por chaves.
E, também por convenção, quando uma proc possuir mais de uma expressão elas devem ser debnidas pelas palavras-chave do..end.
message = proc do
puts "Hello!"
puts "Ruby!"
end
Procs podem receber parâmetros, assim como métodos. Basta delimitar os parâmetros com |. Para receber mais de um parâmetro,
separe-os com vírgula.
43
sum.call(1, 2)
sum[1, 2]
sum.(1, 2)
O valor de retorno de uma proc, assim como métodos, é a última expressão que for executada. Se você quiser encerrar o cuxo de
execução retornando um valor antes da última expressão, deve usar next, em vez do return utilizado por métodos.
n1 / n2
end
divide[3.0, 2]
#=> 1.5
divide[0, 2]
#=> 0
divide[2, 0]
#=> nil
O Ruby 1.9 também introduziu uma nova sintaxe para a debnição de procs.
Esta nova sintaxe também pode aceitar parâmetros, mas faz com que a legibilidade do código seja prejudicada.
44
sum = -> n1, n2 { n1 + n2 }
sum[1, 2]
#=> 3
Procs podem ser convertidas em blocos[3]. Basta adicionar um & na hora que passar o bloco como parâmetro.
sum[1, 2]
#=> 3
Embora o método Kernel#lambda seja semelhante ao método Kernel#proc, eles possui uma diferença muito importante. Lambdas
irão validar a quantidade de parâmetros que foram passados e lançarão a exceção ArgumentError: wrong number of arguments
caso o número de argumentos seja incorreto. Já as procs irão atribuir o valor nil para cada um dos parâmetros.
3
Para saber mais sobre blocos, leia Blocos.
45
proc_message = proc {|message| p message}
lambda_message = lambda {|message| p message}
proc_message.call
#=> nil
lambda_message.call
#=> ArgumentError: wrong number of arguments (0 for 1)
Uma outra diferença é que se um return for debnido em uma proc, o método que executou esta proc também irá encerrar o cuxo de
execução. No caso de lambdas, o cuxo de execução será encerrado apenas no lambda que originou a chamada ao return.
def return_proc
proc { return }.call
puts "return_proc"
end
def return_lambda
lambda { return }.call
puts "return_lambda"
end
return_proc
return_lambda
# Output:
# return_lambda
Para descobrir quantos parâmetros obrigatórios uma proc espera, use o método Proc#arity. Se a proc possui argumentos opcionais, o
valor de retorno será -n - 1, onde n é a quantidade de parâmetros obrigatórios.
46
Proc.new {}.arity #=> 0
Proc.new {||}.arity #=> 0
Proc.new {|a|}.arity #=> 1
Proc.new {|a,b|}.arity #=> 2
Proc.new {|a,b,c|}.arity #=> 3
Proc.new {|*a|}.arity #=> -1
Proc.new {|a,*b|}.arity #=> -2
Proc.new {|a,*b, c|}.arity #=> -3
Para pegar uma representação dos parâmetros que um bloco pode receber, use o método Proc#parameters. Note que a
representação muda quando um lambda é debnido.
Set
Existem situações onde você pode precisar de uma lista com valores únicos. Isso pode ser facilmente resolvido com arrays e o método
Array#include? ou Array#uniq.
items = [1, 2, 3]
47
items << 3
items.uniq
Embora essas técnicas funcionem, elas não são otimizadas. O Ruby possui a classe Set que faz justamente isso: garante que apenas
ítens únicos serão adicionados à lista.
require "set"
items = Set.new([1, 2, 3])
require "set"
[1, 2, 3, 3, 2, 4].to_set
#=> #<Set: {1, 2, 3, 4}>
48
CAPÍTULO 1
Estruturas condicionais
A estrutura de controle mais comum em qualquer linguagem de programação é a condicional. É apenas uma maneira de executar um
trecho de código se alguma condição for satisfeita. Uma condição é uma expressão que quando veribcada retorna um valor diferente
de false e nil.
O Ruby possui diferentes formas de expressões condições, como você pode conferir à seguir.
if
O if é a forma mais direta de se debnir uma expressão condicional. Sua sintaxe é bastante simples.
if expression
# do something
end
O trecho de código debnido dentro de if..end será executado somente se o valor de expression for diferente de false e nil. Note
que não é necessário adicionar parênteses em torno das condições.
x = 1
y = 0
y = if x > 0
y + 1
end
49
puts y
#=> 1
No exemplo acima, estamos atribuindo o valor y + 1 sempre que o valor de x for maior que zero. Embora seja uma construção válida e
muito cexível, nem sempre é a melhor maneira. A mesma expressão poderia ser escrita de um modo muito mais legível.
x = 1
y = 0
if x > 0
y += 1
end
puts y
#=> 1
x = 0
x += 1 if x.zero?
A condição será sempre a primeira a ser executada, mesmo ela sendo escrita por último.
else
O if pode conter uma cláusula else, que será executada quando a condição não for satisfeita. Caso o valor expression seja igual a
false ou nil, então o código associado ao else será executado.
50
if expression
# do something
else
# do something else
end
elsif
Se você precisar adicionar mais cláusulas else que dependem de outras condições, pode usar o elsif.
if expression
# do something
elsif expression2
# do something else
elsif expressionN
# do something else
else
# do something else
end
Cada uma das expressões irá falhar caso o valor de retorno seja false ou nil, até que seja executada a última expressão else. Note
que o else é opcional.
x = 4
name = nil
if x == 1
name = "one"
elsif x == 2
name = "two"
51
elsif x == 3
name = "three"
end
puts name.inspect
#=> nil
No exemplo acima, estamos veribcando se o x possui os valores 1, 2 ou 3. Como o valor de x é 4, nenhuma das condições de nosso if
será satisfeita, o que faz com que o valor original de name não seja alterado. No Ruby o método inspect normalmente retorna uma
representação do self como uma string.
unless
Uma tendência natural quando queremos executar alguma expressão somente se uma condição falhar é adicionar uma exclamação
antes da condição ou, alternativamente, usar a palavra-chave not, que também tem suporte no Ruby.
if !expression
# do something
end
unless expression
# do something
end
O unless também suporta uma cláusula else, mas seu uso é desencorajado; neste caso, seria muito mais simples escrever um if que
coloca a expressão a ser executada se o valor da condição não for false ou nil primeiro!
52
# estranho
unless x
x = 1
else
x += 1
end
# recomendado
if x
x += 1
else
x = 1
end
Assim como o if, o unless também pode ser usado de forma inline.
x = 0
x += 1 unless x.nonzero?
Operador ternário ?:
O operador ?: é o único operador ternário (que possui três operandos) suportado pelo Ruby.
No exemplo à seguir temos um método que irá retornar uma string levando em consideração a quantidade de ítens. Se o count for igual
a 1, uma string que representa o singular é retornada. Caso contrário, uma string que representa o plural é retornada.
53
def pluralize(count, singular, plural)
if count == 1
singular
else
plural
end
end
case
O case é expressão condicional que permite fazer diversos tipos de comparações e que pode ser usada de duas formas diferentes.
A primeira forma que é apenas uma alternativa para if..elsif..else é a mais simples, mas também a menos utilizada.
case
when x == 1 then "one" # if x == 1 then "one"
when x == 2 then "two" # elsif x == 2 then "two"
when x == 3 then "three" # elsif x == 3 then "three"
else "other" # else "other"
end # end
A palavra-chave then só é necessária se você quiser utilizar expressões na mesma linha do comparador. Alternativamente, você pode
utilizar o caracter ;.
54
A segunda forma irá receber um objeto que pode ser comparado com diversos tipos de expressões diferentes. Esse modo é
extremamente poderoso e permite fazer comparações com expressões regulares, classes e intervalos.
O exemplo anterior poderia ser expressado de uma forma um pouco menos repetitiva.
case x
when 1 then "one"
when 2 then "two"
when 3 then "three"
else "other"
end
O case também retorna o valor da expressão que for executada, podendo ser atribuída a uma variável.
number = case x
when 1 then "one"
when 2 then "two"
when 3 then "three"
else "other"
end
case x
when 1, 2, 3 then "one, two, or three"
when 4, 5, 6 then "for, five, or six"
else "other"
end
55
O case usa o operador === para fazer as comparações. Em alguns casos, esse operador é exatamente o mesmo que ==. Mas em outros
casos, como classes e módulos, o comportamento é um pouco diferente. O operador === implementado[1] pelas classes e módulos irá
veribcar se um objeto é uma instância desta classe ou módulo ou de um de seus descendentes.
hello = "hello"
one = 1
Perceba que String === "hello" retorna true, mas o contrário não é verdade. Isso acontece porque a implementação de
String.=== (que na verdade é implementada por Module#===) é diferente de String#===, que compara se o objeto é uma string e se
ela possui o mesmo conteúdo.
Voltando ao case, se a expressão de comparação for uma classe, então ele irá veribcar se a classe daquela instância é a própria classe
ou um de seus descendentes.
value = "Hello"
case value
when String
1
Sim! O operador === é apenas um método e você pode dební-lo em seus próprios objetos.
56
"A String was provided"
else
"Y U MAD BRO?"
end
Já o operador === implementado pelas expressões regulares irá veribcar se um determinado padrão foi satisfeito pela string.
case text
when /\bruby\b/
"You passed a lowercased Ruby"
when /\bRuby\b/
"You passed a capitalized Ruby"
when /\bRUBY\b/
"You passed an uppercased Ruby"
else
"WAT? NO RUBY?"
end
O operador === implementado por intervalos (Range) também é diferente. Ele veribca se um determinado ítem está presente naquele
intervalo.
number = 100
case number
when 0..10
"Between 0 and 10"
when 11..20
"Between 11 and 20"
57
else
"You're outside my limits"
end
Essa convenção de se utilizar o operador === faz com que o case do Ruby seja muito mais poderoso que seu equivalente de outras
linguagens.
58
CAPÍTULO 1
Estruturas de repetição
O Ruby possui três expressões que debnem loops: for..in, while e until. Mas além deles, é possível usar iteradores em objetos
enumeráveis como arrays e hashes, além de outros objetos.
for..in
O loop for..in permite iterar em objetos que são enumeráveis, como é o caso de arrays e hashes. A cada iteração, um elemento será
atribuído para a variável especibcada. Sua sintaxe é bastante simples:
Note que cada valor atribuído à variável pode ser acessado fora da expressão for..in.
numbers = [1,2,3]
59
inside loop: 1
inside loop: 2
inside loop: 3
outside loop: 3
No caso de hashes, você pode debnir duas variáveis que irão receber a chave e o valor, respectivamente.
Caso você forneça apenas uma variável, esta variável irá armazenar um array com dois elementos: a chave e o valor.
# Output:
# one => 1
# two => 2
# three => 3
Embora esse tipo de loop funcione muito bem, não é assim que os desenvolvedores Ruby mais experientes costumam fazer. Mais à
frente você verá como utilizar os iteradores em objetos enumeráveis.
60
while e until
O while e until são as formas mais básicas de looping do Ruby. Eles irão executar algum trecho de código enquanto uma condição for
verdadeira ou até que uma condição se torne verdadeira. Note que primeiro a condição é testada e, então, o código é executado.
x = 3
while x.nonzero?
puts x
x -= 1
end
x = 3
until x.zero?
puts x
x -= 1
end
Também é possível usar o while e until como modibcadores. Eles irão executar alguma expressão até que a condição seja satisfeita.
items = []
items.push(items.size + 1) while items.size < 3
p items
#=> [1, 2, 3]
61
No exemplo acima estamos adicionando um número ao array enquanto seu tamanho for menor que três. O número que é adicionado
será a quantidade de elementos mais um.
items = []
items.push(items.size + 1) until items.size >= 3
p items
#=> [1, 2, 3]
Também é possível debnir blocos begin..end para utilizar estes modibcadores com mais de uma expressão.
items = []
begin
items.push(items.size + 1)
puts "index #{items.size - 1} => #{items.last}"
end while items.size < 3
# Output:
# index 0 => 1
# index 1 => 2
# index 2 => 3
Mas ao contrário das expressões de uma linha, o bloco é executado antes de a condição ser testada
testada. Sendo assim, o bloco begin..end à
seguir sempre exibirá a mensagem OH NOES! THIS WILL BE DISPLAYED ANYWAY!.
begin
puts "OH NOES!"
62
puts "THIS WILL BE DISPLAYED ANYWAY!"
end while false
Embora seja uma construção aceita pela linguagem, o uso de begin..end é desencorajado e pode, inclusive, ser removido em versões
futuras do Ruby. É possível ter um comportamento semelhante sem que você caia nesta “armadilha”: basta delimitar diversas
expressões com parênteses.
(
puts "OH NOES!"
puts "AIN'T GONNA BE DISPLAYED! :("
) while false
loop
Para loops que não devem interrompidos, uma alternativa é usar algo como while true. No entanto, o Ruby possui o loop, que irá
executar indebnidamente um bloco.
loop do
puts Time.now
sleep 1
end
# Output:
# 2011-12-24 15:42:06 -0200
# 2011-12-24 15:42:07 -0200
# 2011-12-24 15:42:08 -0200
# ... continua até que você interrompa a execução do script
63
Controlando o loop
Muitas vezes é necessário controlar o cuxo de execução de um loop. Às vezes é preciso interromper a execução, outras é preciso
passar para o próximo ítem da iteração em alguma condição especíbca. O Ruby possui algumas maneiras de fazer isso.
x = 0
while x < 10
x += 1
puts x
break if x == 3
end
# Output:
# 1
# 2
# 3
x = 0
64
while x < 5
x += 1
next unless x == 4
puts x
end
# Output:
# 4
Reiniciando a iteração
Para reiniciar a iteração, utilize a palavra-chave redo. Isso fará com que a execução seja reiniciada imediatamente.
numbers = [1,2,3,4]
tries = 1
# Output:
# number: 1, tries: 0
# number: 2, tries: 0
# number: 3, tries: 0
# number: 3, tries: 1
65
# number: 3, tries: 2
# number: 3, tries: 3
# number: 4, tries: 3
Usando iteradores
Embora loops como for..in, while/until e loop sejam úteis para algumas situações, é mais provável que você escreva loops
utilizando métodos chamados iteradores. Os iteradores são provavelmente uma das funcionalidades mais importantes do Ruby.
Um dos exemplos mais comuns de iteradores do Ruby pode ser visto à seguir.
O método Integer#times irá executar o bloco que foi fornecido 5 vezes. É esse tipo de construção do Ruby que faz com que a
linguagem seja elegante.
Existem outros iteradores numéricos, que nada mais são que métodos iteradores implementados pela classe Integer, assim como o
método Integer#times.
O método Integer#upto irá executar o bloco especibcado o números de vezes que for debnido pelo inteiro, até atingir o número que
foi passado como argumento. O bloco especibcado irá receber o número da iteração como argumento.
À partir do Ruby 1.9 métodos iteradores não exigem que você passe um bloco para execução; neste caso, ele irá retornar um objeto do
tipo Enumerator.
1.upto(3) do |number|
puts number
end
# Output:
66
# 1
# 2
# 3
O método Integer#downto funciona como o método Integer#upto, mas faz a contagem de modo descrescente.
3.downto(1) do |number|
puts number
end
# Output:
# 3
# 2
# 1
Existe ainda um outro método chamado Integer#step. Este método permite fazer iterações usando números inteiros e de ponto-
cutuante.
# Output:
# 0.0
# 0.25
# 0.5
# 0.75
# 1.0
67
Objetos enumeráveis
Objetos instanciados à partir das classes Array, Hash e Range são enumeráveis. O objeto é considerado enumerável quando
implementa o método each, que deve receber um bloco e fazer o yield daquele bloco.
A classe Array, por exemplo, implementa o iterador Array#each, o que permite passar por cada um dos ítens de um array, assim como
o for..in.
[1,2,3].each do |number|
puts number
end
A maioria dos objetos enumeráveis que implementa o iterador each inclui também o módulo Enumerable. Este módulo adiciona
muitos métodos que agem em cima do método each e que permitem iterar, buscar e ordenar os ítens da coleção.
O método Enumerable#map permite criar um novo array contendo os elementos retornados pelo bloco.
O método Enumerable#select permite criar um novo array contendo os elementos cujo valor retornado pelo bloco sejam diferentes
de false ou nil.
68
O método Enumerable#reject faz exatamente o contrário do método Enumerable#select e irá retornar um array contendo os
elementos cujo valor do bloco sejam false ou nil.
O método Enumerable#find irá retornar o primeiro elemento cujo valor de retorno do bloco seja diferente de false ou nil.
Já o método Enumerable#inject é mais complexo que todos os iteradores que você viu até aqui. Ele permite passar um objeto que
será o acumulador e que irá armazenar o resultado de iterações passadas. O bloco que foi fornecido pode ou não incrementar este
acumulador, dependendo de suas condições. O acumulador de devve ser o valor de retorno do bloco.
Veja, por exemplo, como retornar um único número que será a soma do dobro dos múltiplos de 2.
Alternativamente, você poderia implementar este mesmo acumulador em mais de uma etapa. Um código que faz a mesma coisa, mas
de forma muito menos elegante, pode ser visto à seguir.
sum = 0
(1..10).each do |number|
69
sum += number * 2 if number.even?
end
70
CAPÍTULO 1
Blocos
Os blocos são essenciais no uso de iteradores. Embora tenhamos usado blocos quando falamos sobre objetos enumeráveis, não
dedicamos tempo para explicar o que eles são.
Blocos nunca podem estar sozinhos; eles sempre precisam estar associados a uma execução de método. Todo método que é executado
pode receber um bloco, mas apenas os métodos que esperam este bloco e que façam o yield é que irão de fato executá-los; caso
contrário, o bloco será ignorado silenciosamente. Por baixo dos panos, blocos são apenas procs.
Assim como as procs, blocos seguem a convenção de se usar chaves para blocos com uma única expressão e do..end para blocos com
mais de uma expressão.
# Diversas expressões
[1, 2, 3, 4, 5].inject(0) do |acc, number|
acc += number if number.even?
acc
end
Lembre-se! Como blocos sempre estão associados à execução de métodos, você não deve usar este termo para se referir
a procs ou lambdas.
71
Escopo de variáveis
Blocos introduzem um novo escopo de variáveis. Toda vez que você debne parâmetros que serão recebidos pelo bloco, estas variáveis
serão acessíveis apenas no contexto do bloco.
O exemplo à seguir mostra como a variável i é debnida apenas no escopo local do bloco.
1.upto(5) {|i| }
p defined?(i)
#=> nil
No entanto, blocos também podem referenciar variáveis do contexto externo ao bloco e, nesse caso, podem inclusive modibcar estas
variáveis.
total = 0
1.upto(10) {|i| total += i}
puts total
#=> 55
À partir do Ruby 1.9 os parâmetros esperados pelo bloco não mais modibcam variáveis de mesmo nome que foram debnidas no
contexto externo ao bloco. O exemplo à seguir mostra exatamente isso. Este mesmo exemplo quando executado no Ruby 1.8 irá exibir
o valor da última iteração, ou seja, 10.
i = 0
1.upto(10) {|i| }
puts i
#=> 0
72
Interagindo com blocos
Como foi dito antes, métodos podem receber blocos mesmo quando eles não esperam um. Para interagir com um bloco que foi
passado, você deve usar a palavra-chave yield. Ela irá executar o bloco que foi passado ao método.
def say
puts yield
end
say { "Hello" }
#=> Hello
O bloco será executado toda vez que você usar yield. Então, se em um mesmo método você usar três vezes a palavra-chave yield, o
bloco será executado três vezes.
def hello
yield "Hello!"
yield "Hi!"
yield "Wassup!"
end
# Output:
# Hello!
# Hi!
# Wassup!
Se nenhum bloco for passado para este método say, uma exceção LocalJumpError será lançada. Para evitar que isto aconteça, você
pode veribcar se algum bloco foi passado com o método Kernel#block_given? e tomar as ações necessárias.
73
No exemplo à seguir lançamos uma exceção caso um bloco não seja passado.
def say
raise "Y U MAD BRO? JUST GIMME A BLOCK!" unless block_given?
puts yield
end
say { "Hello" }
begin
say
rescue Exception => error
puts error.message
end
# Output:
#=> Hello
#=> Y U MAD BRO? JUST GIMME A BLOCK!
Para passar parâmetros para o bloco que foi fornecido, basta fazer o yield passando os argumentos.
def first_and_last(list)
yield list.first, list.last
end
74
O yield quase sempre é subciente. Mas às vezes, você quer ter um pouco mais de controle, seja passando o bloco como parâmetro
para outro método ou agindo como proxy de um outro método que também espera um bloco. O Ruby permite que você capture blocos
passados para métodos usando a construção &variavel. A única exigência é que essa variável deve ser sempre a última variável da
lista.
Note que não estamos mais usando o yield; agora, executamos o método Proc#call passando os argumentos. Alternativamente,
poderíamos usar algum outro método que executa procs, como Proc#[].
75
CAPÍTULO 1
Classes
O Ruby, como você pode perceber até agora, é uma linguagem orientada a objetos. Tudo o que manipulamos no Ruby são objetos e
cada objeto é gerado direta ou indiretamente de uma classe. Classes debnem os métodos que objetos podem responder. Elas também
podem estender ou ser subclasses de outras classes.
Criando classes
Para debnir uma classe use a palavra-chave class. Classes são constantes e, por este motivo, devem começar com uma letra maiúscula.
class Page
end
Classes são apenas objetos instanciados à partir da classe Class. Por isso, você pode instanciar classes dinamicamente[1]. Isso faz com
que a linguagem se torne extremamente poderosa e cexível.
class Page
end
AnotherPage = Class.new
Page.class
#=> Class
1
Classes criadas dinamicamente podem ser atribuídas a qualquer tipo de variável, e não apenas a constantes.
76
AnotherPage.class
#=> class
Embora ainda não tenhamos adicionado nenhum método à classe Page, nós já podemos instânciá-la. Para isso você irá usar o método
Page.new.
page = Page.new
Mesmo não tendo debnido atributos e métodos, nós podemos executar alguns métodos fornecidos pelas superclasses da classe Page.
Você pode, por exemplo, “perguntar” que tipo de objeto ele é.
page.class
#=> Page
page.is_a?(Page)
#=> true
As superclasses da classe Page implementam muitos outros métodos. Para saber quais são as superclasses de uma classe, use o
método Class.ancestors. Note que a própria classe será adicionada à lista.
Page.ancestors
#=> [Page, Object, Kernel, BasicObject]
Toda vez que o método Class.new for executado, ele irá iniciar a instância com o método construtor. No Ruby, o método construtor é
Class#initialize. Este método é debnido automaticamente como privado e não pode ser executado diretamente de fora do objeto.
Vamos fazer o método Page#initialize receber dois argumentos que irão debnir o título e conteúdo da página.
77
class Page
def initialize(title, content)
@title = title
@content = content
end
end
Todos os valores que devem ser persistidos em objetos devem ser debnidos como variáveis de instância, identibcados por uma arroba
na frente da variável. Elas pertencem ao objeto self que referencia o próprio objeto instanciado. Cada instância da classe Page terá
sua própria cópia das variáveis @title e @content.
O método Page#initialize debne duas variáveis de instância, que receber os argumentos passados no momento da instanciação. No
entanto, ainda não temos nenhuma maneira de acessar tais valores diretamente.
Definindo métodos
Para acessar as variáveis de instância que debnimos no método Page#initialize, nós iremos debnir dois getters, que são métodos
que apenas retornam valores. Isso precisa ser feito pois variáveis de instância não podem ser acessadas diretamente. Variáveis de
instância são encapsuladas de tal forma que apenas os métodos de um próprio objeto é que podem acessá-las e modibcá-las
diretamente.
Para acessar estas variáveis de instância que foram debnidas no nosso método construtor, você precisa debnir métodos que irão
retorná-las. Embora o nome do método não precise necessariamente recetir o nome da variável, é sempre uma boa ideia dar nomes
que possam identibcar rapidamente o contexto de uso.
78
class Page
def initialize(title, content)
@title = title
@content = content
end
def title
@title
end
def content
@content
end
end
page.title
#=> Ruby
page.content
#=> OMG! I'm learning Ruby!
Ainda não existe nenhuma maneira de debnir essas variáveis de instância sem ser pelo método construtor. Para fazer isso, é preciso
debnir métodos setters. Em outras linguagens, normalmente isso é feito com um método setTitle(title) ou set_title(title), ou
algo parecido. No Ruby, você pode debnir o seu próprio método title=.
79
class Page
def initialize(title, content)
@title = title
@content = content
end
def title
@title
end
def title=(title)
@title = title
end
def content
@content
end
def content=(content)
@content = content
end
end
O Ruby permite usar o operador = para executar métodos como este. Agora, já é possível atribuir valores para as variáveis @title e
@content com os métodos setters.
page.title
#=> Ruby
80
page.title = "Learning Ruby"
page.title
#=> Learning Ruby
Esta debnição de getters e setters em classes é tão comum que o próprio Ruby fornece uma maneira de automatizar esta debnição.
Basta usar os métodos Module#attr_reader e Module#attr_writer[2].
class Page
attr_reader :title
attr_writer :title
attr_reader :content
attr_writer :content
O método Module#attr_reader irá debnir o método de instância de leitura (getter), enquanto o método Module#attr_writer irá
debnir o método de instância de escrita (setter). Para os casos onde você sempre debne tanto o getter quanto o setter, é possível usar o
método Module#attr_accessor, que fará isso de uma vez só!
Com esta alteração, nossa classe pode bcar muito mais simples.
class Page
attr_accessor :title, :content
2
A classe Class possui a classe Module como superclasse. Para saber mais sobre módulos, leia Módulos.
81
def initialize(title, content)
@title = title
@content = content
end
end
Lembre-se que os métodos Module#attr_accessor e companhia permitem criar apenas getters e setters que mapeiam para uma
variável de instância de mesmo nome. Você terá que implementar os seus próprios métodos se eles forem mais complexos (se eles
precisarem computar valores, por exemplo) ou debnirem variáveis de instância de nomes diferentes.
Alternativamente, você pode fazer com que o método construtor use os métodos Page#title= e Page#content=, em vez de atribuir
as variáveis de instância. Um erro muito comum de iniciantes é não debnir o objeto que irá receber o valor, chamado de receiver.
class Page
attr_accessor :title, :content
Para atribuir os atributos Page#title e Page#content corretamente, é preciso explicitamente executar os métodos através do
receiver self.
class Page
attr_accessor :title, :content
82
def initialize(title, content)
self.title = title
self.content = content
end
end
A atribuição direta das variáveis de instância é mais rápida que executar os métodos através do receiver. A menos que você manipule
as variáveis no método setter antes de atribuí-las, prebra sempre debnir as variáveis de instância.
Vamos implementar o método Page.load, que irá ler um arquivo em formato YAML (Yet Another Markup Language) e retornar uma
nova instância da classe Page com os valores já atribuídos.
require "yaml"
class Page
attr_accessor :title, :content
def Page.load(filepath)
attrs = YAML.load_file(filepath)
Page.new(attrs["title"], attrs["content"])
end
83
end
end
No Ruby, você pode ler e gerar a representação de objetos com a classe YAML. Para isso, basta carregar a standard library com o
método Kernel#require. O método YAML.load_file lê um arquivo e converte seu contéudo em objetos Ruby. Neste exemplo, nosso
arquivo deve retornar um hash.
Classes possuem um objeto self, assim como todos os objetos. Dentro da instrução class..end, o self faz referência à própria
classe. Por isso, uma abordagem mais comum usada por desenvolvedores mais experientes é debnir métodos de classe usando def
self.load(file)..end, em vez de usar o nome da própria constante.
require "yaml"
class Page
attr_accessor :title, :content
def self.load(filepath)
attrs = YAML.load_file(filepath)
Page.new(attrs["title"], attrs["content"])
end
Como o nosso método de classe está no contexto da própria classe (lembre-se, o self faz referência a própria classe), nós podemos
fazer mais uma alteração. Em vez de instanciar a classe Page.new, basta executar o método new diretamente.
84
require "yaml"
class Page
attr_accessor :title, :content
def self.load(filepath)
attrs = YAML.load_file(filepath)
new(attrs["title"], attrs["content"])
end
A classe Page ainda não permite salvar sua representação em YAML. Vamos adicionar um método Page#save_to(file) que faz
exatamente isso.
require "yaml"
class Page
attr_accessor :title, :content
def self.load(filepath)
attrs = YAML.load_file(filepath)
new(attrs["title"], attrs["content"])
end
85
@content = content
end
def save_to(filepath)
File.open(filepath, "w") {|file| file.write to_yaml }
end
end
A biblioteca YAML injeta um método Object#to_yaml, que retorna uma string representando aquele objeto. No nosso caso, ele irá
retornar algo como a string abaixo.
--- !ruby/object:Page
title: Ruby
content: OMG! I'm learning Ruby!
Como a representação em YAML inclui a informação sobre qual classe este objeto foi instanciado, não precisamos mais fazer isso
manualmente no método Page#load. Agora, podemos simplesmente retornar o objeto instanciado com o método YAML.load_file.
require "yaml"
class Page
attr_accessor :title, :content
def self.load(filepath)
YAML.load_file(filepath)
end
86
end
def save_to(filepath)
File.open(filepath, "w") {|file| file.write to_yaml }
end
end
• Métodos públicos podem ser executados em qualquer situação. Métodos são públicos por padrão, com uma única exceção: o
método Class#initialize é sempre privado.
• Métodos protegidos podem ser executados por objetos de uma classe e suas subclasses.
• Métodos privados não podem ser executados através de um receiver explícito. O receiver sempre será o objeto atual self.
Para debnir a visibilidade de métodos você utilizar os métodos Module.public, Module.private e Module.protected.
class SomeClass
def method1 # métodos são públicos por padrão
end
87
end
end
Alternativamente você poderia ter debnido a visibilidade dos métodos passando uma lista de nomes de métodos.
class SomeClass
def method1
end
def method2
end
def method3
end
def method4
end
O controle de acesso é determinado dinamicamente. Somente quando o método for executado é que o controle de acesso será
determinado. Se a visibilidade deste método for violada, uma exceção NoMethodError será lançada.
class SomeClass
private
def some_private_method
end
end
88
object = SomeClass.new
object.some_private_method
#=> NoMethodError: private method ‘some_private_method’ called for #<SomeClass:0x007fc8f3853860>
Para garantir que mensagens sejam enviadas apenas para métodos públicos, use o método Object.public_send, introduzido no Ruby
1.9.
object = SomeClass.new
object.public_send :some_private_method
#=> NoMethodError: private method `some_private_method' called for #<SomeClass:0x007f9441897bf0>
Definindo constantes
Muitas classes podem usar constantes para armazenar informações que poderiam bcar espalhadas pelo código, como “números
mágicos”. Vamos alterar a classe Page de modo que ela possa receber também um permalink, uma representação de como essa página
poderia ser referenciada.
class Page
attr_accessor :title, :content
def self.load(filepath)
YAML.load_file(filepath)
end
89
def initialize(title, content, permalink)
@title = title
@content = content
@permalink = permalink
end
def save_to(filepath)
File.open(filepath, "w") {|file| file.write to_yaml }
end
end
Vamos renomear o método Page#save_to para Page#save. Este método irá salvar os arquivos sempre em um mesmo diretório,
usando o atributo permalink como nome do arquivo.
class Page
attr_accessor :title, :content
def self.load(filepath)
YAML.load_file(filepath)
end
def save
File.open("/tmp/#{permalink}.yml", "w") {|file| file.write to_yaml }
end
end
90
Em vez de deixar o diretório onde os arquivos serão salvos no método Page#save, vamos extrair esta informação para uma constante.
Essa alteração permite, dentre outras coisas, expor esta informação na documentação RDoc.
class Page
attr_accessor :title, :content
ROOT = "/tmp"
def self.load(filepath)
YAML.load_file(filepath)
end
def filepath
File.join(ROOT, "#{permalink}.yml")
end
def save
File.open(filepath, "w") {|file| file.write to_yaml }
end
end
91
Entendendo classes Singleton
Todo objeto do Ruby está associado a duas classes: a classe que a instanciou e uma classe anônima, escondida, especíbca do objeto.
Esta classe anônima é chamada de Singleton Class, mas antes de ter um nome obcial também era chamada de anonymous class,
metaclass, eigenclass ou ghost class.
O nome Singleton usado pelo Ruby nada tem a ver com o Singleton Pattern, que também está disponível com a biblioteca Singleton.
A sintaxe mais comum para acessar a classe Singleton é class << object..end, onde object é o objeto cuja classe Singleton você
quer. É muito comum vermos algo como o exemplo à seguir para debnir métodos em uma classe.
class Person
class << self
def count
@count ||= 0
end
end
end
Aqui, estamos debnindo um método na classe Singleton do objeto Person (lembre-se: tudo no Ruby é objeto, inclusive classes). Como
consequência, isso irá debnir o método Person.count. O efeito é exatamente o mesmo que o código abaixo, porém este é mais
objetivo e fácil de entender.
class Person
def self.count
@count ||= 0
end
end
92
No Ruby 1.9, foi adicionado o método Object#singleton_class, que é apenas um atalho para a sintaxe class << self; self;
end. Em versões mais antigas, você pode injetar este método com o código abaixo.
class Object
def singleton_class
class << self; self; end
end unless respond_to?(:singleton_class)
end
Toda vez que injeta métodos em um objeto, eles são adicionados como métodos singleton. O que é realmente importante saber é que
estes métodos pertecem unicamente ao objeto em que foram debnidos, não afetando nenhum outro objeto da hieraquia.
def string.to_yo
self.gsub(/\b(Hi|Hello)( there)\b?!?/, "Yo! Wassup?")
end
string.to_yo
#=> "Yo! Wassup?"
another_string.respond_to?(:to_yo)
#=> false
E para provar que o método to_yo é singleton, podemos utilizar o método Object#singleton_methods.
string.singleton_methods
#=> ["to_yo"]
93
another_string.singleton_methods
#=> []
Você também pode adicionar métodos singleton de outras maneiras. Uma delas é estendendo um objeto com um módulo.
module Extension
def sum
self.reduce(:+)
end
end
numbers = [1,2,3]
numbers.extend Extension
numbers.singleton_methods
#=> ["sum"]
numbers = [1,2,3]
numbers.singleton_methods
#=> ["sum"]
94
numbers = [1,2,3]
numbers.instance_eval do
def sum
self.reduce(:+)
end
end
numbers.singleton_methods
#=> ["sum"]
Quando você cria uma classe Singleton em um objeto, não poderá mais utilizar o método Marshal.dump, já que a biblioteca Marshal[3]
não suporta objetos com classes Singleton (ela irá lançar a exceção TypeError: singleton can't be dumped). A única maneira de
fazer isso e ainda poder utilizar o Marshal é utilizando o método Object#extend.
Agora, sabendo que você pode adicionar métodos em um objeto com uma sintaxe como def object.some_method; end, perceba
que é exatamente isso que fazemos quando debnimos um método em uma classe; a única diferença é que passamos o próprio self.
class Person
def self.say_hello
"Hello there!"
end
end
Person.singleton_methods
#=> ["say_hello"]
3
A biblioteca Marshal permite converter objetos Ruby em uma sequência de bytes que podem ser restaurados por outros scripts, que
podem reconstituir os objetos originais.
95
Com base nesse exemplo, é possível abrmar que métodos de classe não e
existem
xistem no Rub
Rubyy! Pelo menos não no sentido de métodos
estáticos! O que acontece é que estes métodos pertencem a um objeto, que por acaso é uma classe.
96
CAPÍTULO 1
Módulos
Hmmm….
Este conteúdo está sendo escrito e estará disponível em breve.
97
CAPÍTULO 1
98
CAPÍTULO 1
Embora os três métodos atingem o objetivo—concatenar strings—, existe uma diferença muito importante. O método String#+ irá
criar um novo objeto em memória, enquanto os métodos String#<< e String#concat irão realocar a memória previamente utilizada.
hello = "Hello"
ruby = "Ruby"
99
hello << " "
hello << ruby
puts "#{hello}: #{hello.object_id}"
#=> Hello Ruby: 70310699719100
Formatando strings
Você já viu que no Ruby é possível utilizar expressões arbitrárias que podem ser interpoladas.
language = "Ruby"
puts "#{language} is nice!"
#=> Ruby is nice!
O Ruby também suporta outros tipos de formatação de strings que também permitem interpolar expressões. É o caso dos métodos
String#%, Kernel#printf e Kernel#sprintf. Estes métodos permitem ter maior controle no espaçamento e formatação de
números. Além disso, eles permitem desacoplar os valores que devem ser interpolados da string, facilitando, por exemplo, a
internacionalização de strings.
A mesma string que foi interpolada acima pode ser formatada com os métodos mencionados.
language = "Ruby"
100
"%s is %s!" % ["Ruby", "nice"]
#=> Ruby is nice!
Embora seja fácil interpolar múltiplos valores com arrays, seu código pode acabar confuso quando a lista de parâmetros é muito
grande. Neste caso, prebra usar um hash. Note que esta funcionalidade foi introduzida no Ruby 1.9.
Para saber mais sobre os formatos aceitos, acesse a documentação; basta executar o comando ri String#sprintf.
101
text = "ruby is nice!"
puts text.upcase
#=> RUBY IS NICE!
Note que os métodos String#upcase e String#downcase irão retornar uma nova string. Para alterar a string existente, utilize os
métodos String#upcase! e String#downcase!.
text.upcase!
puts text.object_id
#=> 70347156470500
1
Veja mais detalhes sobre codibcação de caracteres em Codibcação.
102
text = "Ruby is nice!"
Substrings
O Ruby permite pegar substrings (trechos de uma determinada string) com o método String#[].
Em versões anteriores ao Ruby 1.9, o método String#[] com um número inteiro retorna a representação numérica
daquela posição, o que nem sempre recete a represenção de caracteres com mais de um byte.
A maneira mais simples é passar um número inteiro que identibca o caracter que será retornado. Se o índice for negativo, a posição
será movida à partir do bm da string.
Você também pode passar como segundo parâmetro, a quantidade de caracteres que a substring deve ter.
103
Às vezes, pode ser mais conveniente passar o índice inicial e bnal. Neste caso, você pode passar um Range. Note que você também pode
utilizar intervalos com valores negativos.
O método String#[] também pode receber expressões regulares. Neste caso, o primeiro resultado será retornado.
Você pode especibcar o número ou nome do grupo quando estiver usando uma expressão regular.
Por último, se você passar uma string qualquer e ela estiver presente, ela é retornada. Caso contrário, nil é retornado.
104
Codificação
Uma das grandes mudanças introduzidas pelo Ruby 1.9 foi a codibcação das strings, que agora são verdadeiramente sequências de
caracteres que não precisam estar contidas na tabela de caracteres ASCII. Caracteres debnidos como UTF-8, por exemplo, que usam
um número variável de bytes para representar os caracteres, não possuem mais uma relação um-para-um de bytes e caracteres.
A classe String foi reescrita no Ruby 1.9 para que pudesse ter suporte a caracteres multibytes. Quando uma string contém caracteres
multibytes, a quantidade de bytes usados para representá-la não será a mesma do número de caracteres.
Perceba que a primeira linha debne qual o tipo de codibcação será usada no arquivo. Esse comentário é chamado de magic comment e
sem ele o Ruby não saberia decidir qual codibcação utilizar. O magic comment pode ser qualquer string que consiga ser casada pela
seguinte expressão regular[2]. Magic comments devem ser a primeira linha do arquivo ou vir após a linha de shebang.
/coding[:=] ?/
# coding: utf-8
# encoding: utf-8
# -*- coding: utf-8 -*-
# vim:fileencoding=utf-8
# vim:set fileencoding=utf-8 :
2
Na verdade, magic comments seguem a PEP-263 debnida pelo Python.
105
A codibcação de uma string é baseada na codibcação do código-fonte, mas não será necessariamente a mesma.
Nem todas as strings são compatíveis entre diferentes codibcações. Toda vez que você tentar fazer a conversão entre codibcações que
não são compatíveis, uma exceção será lançada.
Neste caso você pode forçar a codibcação utilizando o método String#encode, passando a estratégia de conversão e qual string será
usada no lugar dos caracteres que não são reconhecidos.
Você pode forçar a codibcação de uma string com o método String#force_encoding. Este método não faz qualquer veribcação ou
conversão de caracteres; apenas muda a interpretação que o Ruby faz dos caracteres, mas sem alterar os bytes que representam a
string. Para validar se os bytes daquela string são válidos na codibcação escolhida, utilize o método String#valid_encoding?.
106
Para acessar a representação númerica de cada caracter de uma string, utilize o método String#codepoints. Este método espera um
bloco, mas você pode utilizar o método Enumerator#to_a para converter este iterador em um array.
"hello".codepoints.to_a
#=> [104, 101, 108, 108, 111]
"maçã".codepoints.to_a
#=> [109, 97, 231, 227]
"☃".codepoints.to_a
#=> [9731]
Alternativamente, você pode utilizar o método String#ord para pegar a representação númerica de um único caracter.
"☺".ord
#=> 9786
Você também pode acessar os bytes de uma string com o método String#bytes. Assim como o método String#codepoints, você
pode converter este iterador em um array.
"hello".bytes.to_a
#=> [104, 101, 108, 108, 111]
"maçã".bytes.to_a
#=> [109, 97, 195, 167, 195, 163]
"áéíóú".bytes.to_a
#=> [195, 161, 195, 169, 195, 173, 195, 179, 195, 186]
107
Note como a string áéíóú possui apenas 5 caracteres, mas precisa de 10 bytes para ser representada.
Para acessar a lista de codibcações disponíveis no Ruby, utilize o método Encoding.list. Algumas codibcações possuem um alias, cuja
listagem completa pode ser retornada pelo método Encoding.aliases. O código à seguir fará o output das codibcações disponíveis
com seus respectivos aliases.
puts list.sort_by(&:downcase)
O resultado do código acima, quando executado no Ruby 1.9.3-p0, pode ser visto à seguir (os nomes foram listados em colunas para
não ocupar tanto espaço).
108
macRomania stateless-ISO-2022-JP-KDDI UTF-7 (CP65000) Windows-1253 (CP1253)
macThai TIS-620 UTF-8 (CP65001) Windows-1254 (CP1254)
macTurkish US-ASCII (ASCII) UTF8-DoCoMo Windows-1255 (CP1255)
macUkraine UTF-16 UTF8-KDDI Windows-1256 (CP1256)
Shift_JIS UTF-16BE (UCS-2BE) UTF8-MAC (UTF-8-MAC) Windows-1257 (CP1257)
SJIS-DoCoMo UTF-16LE UTF8-SoftBank Windows-1258 (CP1258)
SJIS-KDDI UTF-32 Windows-1250 (CP1250) Windows-31J (CP932)
SJIS-SoftBank UTF-32BE (UCS-4BE) Windows-1251 (CP1251) Windows-874 (CP874)
stateless-ISO-2022-JP UTF-32LE (UCS-4LE) Windows-1252 (CP1252)
109
CAPÍTULO 1
1 + 1 #=> 2
1 + 2.1 #=> 3.1
-1 + -1 #=> -2
2 - 1 #=> 1
2 - 1.3 #=> 0.7
-1 - -2 #=> 1
2 * 2 #=> 4
2 * 1.3 #=> 2.6
-2 * 1.3 #=> -2.6
Para efetuar divisões, utilize o método Numeric#/. Note que o resultado depende da classe do número que será usado como divisor.
No caso de números inteiros, o resultado será um número inteiro.
110
3 / 2 #=> 1
3 / 2.0 #=> 1.5
2 / 1.75 #=> 1.1428571428571428
2 ** 2 #=> 4
2 ** 3 #=> 8
3 ** 2 #=> 9
Números absolutos
Para pegar o valor absoluto de um número, use o método Numeric#abs.
111
Felizmente a classe Integer implementa dois métodos que permitem fazer a mesma veribcação, mas de uma forma muito mais
elegante.
O valor retornado será uma string que pode ser convertida novamente em número com o método String#to_i.
112
"10011010010".to_i(2) #=> 1234
"2322".to_i(8) #=> 1234
"1234".to_i(10) #=> 1234 - base 10 é o padrão
"4d2".to_i(16) #=> 1234
"ya".to_i(36) #=> 1234
Fazendo arredondamentos
O Ruby possui dois métodos que permitem arredondar números de ponto cutuantes para inteiros. O método Float#ceil irá
arredondar para o próximo número inteiro maior ou igual o próprio número.
1.0.ceil #=> 1
1.1.ceil #=> 2
-1.1.ceil #=> -1
Já o método Float#floor irá arredondar para o número inteiro que for menor ou igual ao próprio número.
1.0.floor #=> 1
1.1.floor #=> 1
-1.1.floor #=> -2
O método Float#round irá arredondar números de ponto cutuante para o número de casas decimais que foram especibcadas. Por
padrão, a precisão é zero e pode ser um número negativo (nesse caso, o número retornado será um inteiro).
1.4.round #=> 1
1.5.round #=> 2
1.6.round #=> 2
-1.5.round #=> -2
113
1.234567.round(2) #=> 1.23
1.234567.round(3) #=> 1.235
1.234567.round(4) #=> 1.2346
1.234567.round(5) #=> 1.23457
12345.67.round(-5) #=> 0
12345.67.round(-4) #=> 10000
12345.67.round(-3) #=> 12000
12345.67.round(-2) #=> 12300
12345.67.round(-1) #=> 12350
12345.67.round(0) #=> 12346
114
CAPÍTULO 1
items = [1, 2, 3]
items.fetch(0) #=> 1
items.fetch(4, "not found") #=> not found
items.fetch(4) do |index| #=> index 4 not found
"index #{index} not found"
end
Outra característica interessante é que se nenhum valor padrão for especibcado, uma exceção IndexError será lançada.
items = [1, 2, 3]
items.fetch(4)
#=> IndexError: index 4 outside of array bounds: -3...3
115
items = []
items << 1
items << 2
items.push(3)
p items
#=> [1, 2, 3]
items = [1, 2, 3]
items.unshift(0)
#=> [0, 1, 2, 3]
Você também pode inserir novos elementos em uma posição especíbca com o método Array#add_elements.rb#insert. Note que o
índice pode ser um valor negativo.
items = [1, 2, 3]
1
Este método também é conhecido como shovel.
116
Removendo elementos
Para remover sempre o último elemento de um array, use o método Array#pop. O elemento removido será o valor de retorno do
método.
items = [1, 2, 3]
items.pop
#=> 3
p items
#=> [1, 2]
items = [1, 2, 3, 4, 5]
items.pop(3)
#=> [3, 4, 5]
p items
#=> [1, 2]
Para remover todos os elementos de um determinado valor, use o método Array#delete. Caso este elemento não seja encontrado,
nil será retornado. Você também pode fornecer um bloco, cujo valor será retornado quando o elemento não for encontrado no array.
items.delete("a")
#=> a
117
items.delete("d")
#=> nil
Para remover um elemento em um determinado índice, use o método Array#delete_at. Se o índice especibcado não existir, nil será
retornado.
items = [1, 2, 3]
items.delete_at(1)
#=> 2
items.delete_at(3)
#=> nil
items = [1, 2, 3]
items.shift
#=> 1
Alternativamente, você pode utilizar o método Array#drop com o número de elementos que devem ser removidos. Note que o array
original não é modibcado.
118
items = [1, 2, 3]
items.drop(1)
#=> [2, 3]
items
#=> [1, 2, 3]
items = items.drop(2)
#=> [3]
Por bm, para remover todos os elementos de um array, use o método Array#clear.
items = [1, 2, 3]
items.clear
items
#=> []
Filtrando elementos
Para gerar um novo array com todos os elementos que satisfaçam uma determinada condição, use o método Array#select. Caso o
bloco retorne um valor que seja detectado como true[2], o elemento será adicionado ao novo array. À partir do Ruby 1.9, todos os
métodos iteradores que não recebem um bloco irão retornar um objeto Enumerator.
items = [1, 2, 3, 4, 5]
items.select {|n| n.odd?}
#=> [1, 3, 5]
2
Ou seja, qualquer valor diferente de false e nil.
119
No Ruby 1.9, o exemplo acima pode ser ainda mais simples. À partir desta versão do Ruby, o método Symbol#to_proc permite criar um
bloco que irá executar o método identibcado pelo símbolo que foi passado.
items = [1, 2, 3, 4, 5]
items.select(&:odd?)
#=> [1, 3, 5]
Se você precisa apenas do primeiro elemento que satisfaça a condição, utilize o método Array#find.
items = [1, 2, 3, 4, 5]
# recomendado
items.find(&:odd?)
#=> 1
# estranho
items.select(&:odd?).first
#=> 1
O método Array#select possui uma contra-parte; trata-se do método Array#reject. Caso o bloco retorne false ou nil, o elemento
será adicionado ao novo array.
items = [1, 2, 3, 4, 5]
items.reject(&:odd?)
#=> [2, 4]
Ordenando elementos
Para ordenar os elementos de um array, utilize o método Array#sort. A comparação é feita usando o operador <=>.
120
items = %w[a d c b i k]
items.sort
#=> ["a", "b", "c", "d", "i", "k"]
A classe Array possui muitos métodos que modibcam o array original. É o caso de Array#sort! e Array#sort_by!.
items = %w[a d c b i k]
items.sort!
items
#=> ["a", "b", "c", "d", "i", "k"]
Você também pode especibcar qual o tipo de valor deve ser usado na comparação. Isso pode ser feito com o método
Enumerator#sort_by. O módulo Enumerator é incluído pela classe Array.
Veja, por exemplo, como ordenar um array pelo tamanho das strings.
Para pegar um único elemento aleatóriamente, utilize o método Array#sample, disponível à partir do Ruby 1.9.
121
items = %w[Ruby Python PHP C JavaScript]
items.sample
#=> JavaScript
Buscando elementos
Para veribcar se um elemento existe em um determinado array, use o método Array#include?. A comparação é feita através do
método ==.
items = [1, 2, 3, 1]
items.index(1) #=> 0
items.index(0) #=> nil
Se um bloco for passado para o método Array#index, o índice será cujo bloco retorne true.
items = [1, 2, 3, 1]
items.index {|i| i == 3} #=> 2
Existe ainda o método Array#rindex, que funciona como o método Array#index mas atua nos elementos de forma reversa.
122
items = [1, 2, 3, 1]
items.rindex(1) #=> 3
items.rindex {|i| i == 1} #=> 3
Iterando elementos
A classe Array inclui o módulo Enumerator, que implementa diversos métodos iteradores, mas outros destes métodos são
implementados pela própria classe.
items = [1, 2, 3]
items.each {|item| puts item}
# Output:
# 1
# 2
# 3
# Output:
# 3
# 2
# 1
123
items = %w[Ruby Python PHP]
items.each_index {|i| puts i}
# Output:
# 0
# 1
# 2
Para iterar em cada elemento e, ainda por cima, ter o índice da iteração, use o método Array#each_with_index.
# Output:
# 0 => Ruby
# 1 => Python
# 2 => PHP
124
[1, 2, 3].collect {|i| i * 2}
#=> [2, 4, 6]
O método Array#inject ou Array#reduce permite reduzir um array a um único objeto. Se um valor inicial não for fornecido, o
primeiro elemento do array será utilizado.
items = [1, 2, 3, 4, 5]
items.reduce(:+) #=> 15 - Inicia em 1 e soma todos os elementos
items.reduce(10, :+) #=> 25 - Inicia em 10 e soma todos os elementos
items.reduce(10) do |acc, i| #=> 19 - Inicia em 10 e soma apenas os números ímpares
acc += i if i.odd?
acc
end
Perceba como estamos utilizando o método Symbol#to_proc para executar a soma dos elementos. Esta técnica será útil apenas para
operações mais simples. Para executar operações mais complexas, forneça um bloco, que irá receber o acumulador e o elemento da
iteração.
125
CAPÍTULO 1
A classe Hash possui quatro métodos diferentes para detectar se uma chave foi debnida. Trata-se dos métodos Hash#key?,
Hash#has_key?, Hash#member? e Hash#include?.
126
options.key?("color") #=> false - As chaves são símbolos
options.has_key?(:color) #=> true
options.include?(:width) #=> true
options.member?(:height) #=> true
Para descobrir se um hash possui um determinado valor, use os métodos Hash#value? e Hash#has_value?.
Para pegar a primeira chave que debne um determinado valor, utilize o método Hash#key. No Ruby 1.8, este método se chama
Hash#index.
Acessando o hash
O método mais utilizado para acessar valores de hashes é Hash#[]. Caso uma chave não tenha sido debnida, nil será retornado por
padrão.
127
Você também pode usar o método Hash#fetch para acessar valores em um hash. Ele possui mais opções na hora de lidar com chaves
inexistentes.
Para extrair mais de um valor de um hash, use o método Hash#values_at. Se uma determina chave não existir, nil será retornado.
Filtrando elementos
Para gerar um novo hash com todos os elementos que satisfaçam uma determinada condição, use o método Hash#select. Caso o
bloco retorne um valor que seja detectado como true, o elemento será adicionado ao novo array. No Ruby 1.8, este método retorna
um array contendo as chaves e valores.
items = {:one => 1, :two => 2, :three => 3, :four => 4, :five => 5}
128
Assim como nos arrays, o método Hash#select possui sua contra-parte Hash#reject. Caso o bloco retorne false ou nil, o elemento
será adicionado ao novo hash.
items = {:one => 1, :two => 2, :three => 3, :four => 4, :five => 5}
Para remover diversas chaves caso uma condição não seja satisfeita, use o método Hash#delete_if.
129
options
#=> {:two=>2, :four=>4}
Alternativamente, você pode usar o método Hash#reject! que também irá modibcar o hash original.
options
#=> {:two=>2, :four=>4}
Por bm, para remover todos os elementos de um hash, use o método Hash#clear.
options = {a: 1, b: 2, c: 3}
options.clear
options
#=> {}
130
Hash[options.map {|key, value| [key, value.upcase]}]
#=> {:color=>"RED", :language=>"RUBY"}
Alternativamente, você pode usar o método Hash#inject ou Hash#reduce, passando um hash vazio como valor inicial.
Note que estamos explodindo a chave e valor em duas variáveis diferentes, conforme vimos em Atribuição de variáveis.
Iterando hashes
Para iterar em hashes, utilize o método Hash#each. O bloco irá receber a chave e o valor em cada iteração.
# Output:
# name => John Doe
# age => 32
Você também pode iterar apenas nas chaves com o método Hash#each_key.
131
options = {name: "John Doe", age: 32}
options.each_key {|key| puts "#{key} => #{options[key]}"}
# Output:
# name => John Doe
# age => 32
Por bm, para iterar somente nos valores, use o método Hash#each_value.
# Output:
# John Doe
# 32
132
CAPÍTULO 1
fullpath = "/Users/fnando/ruby/samples.rb"
dir = File.dirname(fullpath) #=> /Users/fnando/ruby
file = File.basename(fullpath) #=> samples.rb
extension = File.extname(file) #=> .rb
Nenhum destes métodos irá veribcar a existência de arquivos e diretórios. Eles apenas permitem compor nomes, sem se importar com
sua existência e tipo (você pode usar o método File.basename em um diretório, por exemplo.).
O método File.expand_path permite expandir caminhos à partir do diretório atual ou de um caminho raíz.
133
File.expand_path("~/ruby") #=> /Users/fnando/ruby
File.expand_path("ruby", "/usr/local/bin") #=> /usr/local/bin/ruby
File.readlink("/usr/bin/ruby")
#=> ../../System/Library/Frameworks/Ruby.framework/Versions/Current/usr/bin/ruby
Manipulando arquivos
O Ruby possui algumas maneiras distintas de abrir arquivos. O modo mais manual que existe exige que você mesmo encerre o uso de
IO deste arquivo. Neste caso, não será possível efetuar nenhuma operação após executar o método File#close e, caso alguma
tentativa de uso seja realizada, a exceção IOError: closed stream será lançada.
Embora seja extremamente cexível, este cuxo está sujeito à falhas. Um desenvolvedor mais descuidado pode não encerrar o uso de IO.
Para evitar que isto aconteça, é recomendado que você use o método File#open com um bloco. Este bloco irá receber o objeto File e,
após a execução do bloco, terá o uso de IO automaticamente encerrado.
File.open(__FILE__) do |file|
content = file.read
end
134
Por padrão, os métodos File.open e File.new irão abrir o arquivo em modo de leitura. Você pode especibcar a cag que será usada na
hora de abrir um arquivo.
path = "/tmp/sample.txt"
puts File.read(__FILE__)
Veja um exemplo que mostra como ler uma URL, salvar seu conteúdo em um arquivo e depois exibir o conteúdo salvo.
require "open-uri"
page = open("http://localhost/").read
path = "/tmp/localhost.html"
puts File.read(path)
135
Note que estamos usando o método File#write para adicionar o conteúdo ao arquivo. Você também poderia ter usado o método
File#<<.
Para modibcar um arquivo existente, você deve se certibcar que está usando a cag a. Caso contrário, todo o conteúdo de seu arquivo
será substituído. O exemplo abaixo mostra como normalizar as quebras de linha de um arquivo para \n.
path = "/tmp/editing.txt"
content = File.read(path)
p File.read(path)
#=> "This is the first line\nThis is the second line\n"
136
# Apaga diversos arquivos. O valor de retorno é a
# quantidade de nomes que foram passados como argumentos.
File.unlink(*paths)
#=> 2
O exemplo abaixo mostra como abrir um arquivo UTF-8 e convertê-lo para ISO-8859 no momento da leitura. Na hora da gravação, a
codibcação será feita no caminho contrário, convertendo o texto de ISO-8859-1 para UTF-8.
# lê o conteúdo do arquivo
content = file.read
137
file.close
Manipulando diretórios
Para listar todos os arquivos e diretórios, você pode usar o método Dir.entries. Note que este método irá adicionar os caminhos . e
.., que fazem referência ao diretório atual e diretório-pai, respectivamente.
Dir.entries(".")
#=> [".", "..", "dir.rb", "editing.rb", "encoding.rb", "expand_path.rb", "filename.rb", "new.rb",
# "open.rb", "open_flags.rb", "read.rb", "save_page.rb", "unlink.rb"]
O método Dir.[] permite listar todas as entradas que atenderem um determinado padrão. Este método é apenas um atalho para
Dir.glob e aceita os mesmos padrões. Para conhecer todos os padrões aceitos, acesse a documentação com o comando ri Dir.glob.
# Arquivos com nomes "encoding" e/ou "open" que tenham a extensão .rb
Dir["{encoding,open}.rb"]
138
# Arquivos que não tenham extensão começadas com a letra r.
Dir["*.[^r]*"]
Para criar um diretório, use o método Dir.mkdir. Se o diretório já existir, a exceção Errno::EEXIST: File exists será lançada.
Dir.mkdir("/tmp/sample")
Para remover um diretório, use o método Dir.delete, Dir.unlink ou Dir.rmdir. O diretório precisa estar vazio. Caso o diretório
não esteja vazio, a exceção Errno::ENOTEMPTY: Directory not empty será lançada.
Dir.delete("/tmp/sample")
Muitas dessas limitações que a classe Dir impõe podem ser removidas se você carregar a biblioteca padrão FileUtils. Ela possui uma
série de métodos que permitem criar e remover diretórios, dentre outros métodos muito úteis.
require "fileutils"
dir = "/tmp/a/b/c/d/e/f"
FileUtils.mkdir_p(dir)
FileUtils.rm_rf(dir)
139
Testando arquivos e diretórios
Para veribcar se um caminho é um arquivo ou diretório, você pode usar os métodos File.file? e File.directory?.
file = __FILE__
dir = File.dirname(file)
path = "/usr/bin/ruby"
Você também pode veribcar se arquivos e diretórios podem ser lidos ou escritos.
path = "/usr/bin/ruby"
140
File.writable?(path) #=> false
File.world_readable?(path) #=> 493
File.world_writable?(path) #=> nil
File.mtime(__FILE__)
#=> 2011-12-31 12:32:11 -0200
File.atime(__FILE__)
#=> 2011-12-31 12:35:30 -0200
141
CAPÍTULO 1
142
CAPÍTULO 1
143
CAPÍTULO 1
Exceções podem ser lançadas com os métodos Kernel#raise e Kernel#fail. Estes métodos podem ser executados com três
argumentos opcionais, mas a assinatura completa do método é raise(exception_class, message, backtrace).
raise ArgumentError
raise ArgumentError, "you've failed"
raise ArgumentError, "you've failed", caller
Quando o método Kernel#raise é chamado sem nenhum argumento, uma exceção do tipo RuntimeError é lançada. As duas
exceções à seguir são equivalentes.
raise
raise RuntimeError
Alternativamente, você pode passar uma string para o método Kernel#raise, que é equivalente a lançar uma exceção com
raise(RuntimeError, message).
Quando uma exceção é lançada, a classe que foi utilizada para debnir a exceção será instanciada. O processo completo pode ser
descrito em quatro etapas:
144
1. Caso uma classe tenha sido passada como argumento, ela é instanciada com o método Exception.exception. Se uma instância
da classe Exception for fornecida, então a exceção é obtida com o método Exception#exception.
2. O backtrace da exceção é debnido.
3. A variável global @$! é debnida com a última exceção “ativa”.
4. A exceção é lançada na pilha de execução.
Capturando exceções
O Ruby permite capturar exceções com a cláusula rescue.
begin
raise "OH NOES!"
rescue
puts "An exception has been raised"
end
Você pode atribuir o objeto de erro a uma variável, ou pode utilizar a variável global $!.
begin
raise "OH NOES!"
rescue => error
puts "error: #{error.message}"
puts "$!: #{$!.message}"
end
Por padrão, apenas as classes que herdam de StandardError serão capturadas. Para capturar outras classes, você pode passar uma
lista de argumentos com as classes de exceção.
145
begin
raise "OH NOES!"
rescue StandardError => error
puts "error: #{error.message}"
end
begin
raise LoadError
rescue Exception, LoadError => error
puts "error: #{error.message}"
end
Muitas exceções nativas do Ruby são lançadas como Exception, em vez de StandardError. É o caso das classes LoadError e
SyntaxError, lançadas quando um arquivo não consegue ser carregado com o método Kernel#require e quando o Ruby encontra
uma sintaxe inválida, respectivamente. Embora seja possível capturar a classe base Exception, isso não é uma boa ideia. Seja
especíbco quanto às eexxceções que vvocê
ocê quer captur
capturar
ar..
Você pode ter diversas cláusulas rescue que tratam exceções diferentes de modos diferentes. Você pode, inclusive, adicionar várias
classes de exceções em uma única cláusula enquanto debne diferentes pontos de captura da exceção.
exceptions.each do |exception_class|
begin
raise exception_class, "just playing with exceptions!"
rescue LoadError => error
puts "#{error.class} => OH NOES! I couldn't load the specified file"
rescue RuntimeError => error
puts "#{error.class} => Dammit! Something went wrong"
rescue ScriptError, SyntaxError => error
puts "#{error.class} => Y U MAD? JUST GIMME SOME VALID RUBY!"
146
end
end
O Ruby também possui uma cláusula chamada ensure, que será sempre executada independente de uma exceção ter sido lançada. Isso
permite efetuar operações que, em outras situações, poderia deixar a sua aplicação em um estado inconsistente.
begin
puts "Starting execution"
raise
puts "Won't be displayed"
rescue
puts "OH NOES! An exception has occurred"
ensure
puts "I'll be always executed"
end
# Starting execution
# OH NOES! An exception has occurred
# I'll be always executed
Uma boa prática é fazer com que a cláusula ensure execute apenas operações simples e seguras, diminuindo a probabilidade de uma
segunda exceção ser lançada, o que pode fazer com que as operações de limpeza não sejam concluídas, além de tornar a depuração do
erro mais complexa.
Em algumas situações, pode fazer sentido executar novamente um determinado bloco begin..end quando uma exceção é lançada. É aí
que entra a cláusula retry. O exemplo abaixo mostra como tentar executar um determinado bloco três vezes antes de “desistir”.
147
tries = 0
begin
tries += 1
puts "Trying ##{tries}"
raise "OH NOES!"
rescue
retry if tries < 3
puts "Sorry! I couldn't make it work!"
end
# Trying #1
# Trying #2
# Trying #3
# Sorry! I couldn't make it work!
Dentro da cláusula rescue pode existir qualquer tipo de código. Você pode, inclusive, lançar uma nova exceção ou, se preferir, a mesma
exceção que foi capturada inicialmente. Isso permite criar, por exemplo, mecanismos que irão rastrear uma exceção lançada.
O exemplo à seguir mostra como enviar para um arquivo de log qualquer tipo de exceção, antes de lançá-la novamente.
require "logger"
LOGGER = Logger.new(STDOUT)
def bogus_method
raise "OMG! Something went wrong!"
end
begin
bogus_method
rescue Exception => error
148
LOGGER.error "#{error.class} => #{error.message}"
LOGGER.error error.backtrace.join("\n")
raise error
end
Lembre-se que as exceções utilizam os métodos Exception#exception e Exception.exception para debnir se uma nova exceção
deve ser instânciada ou se a própria instância é que deve ser utilizada.
Embora exceções possam ser utilizadas para controlar cuxos de aplicações, este não é o mecanismo mais recomendado para a tarefa.
Neste caso, você deve usar os métodos Kernel#throw e Kernel#catch, criados para interromper rapidamente a execução de loops
aninhados e chamadas a métodos sem necessariamente lançar uma exceção.
O método Kernel#throw permite lançar um símbolo que pode ser capturado com o método Kernel#catch. Se você executar o
método Kernel#throw com um argumento que será usado como valor de retorno do método Kernel#catch.
O exemplo abaixo mostra como descobrir quantas vezes um loop teve que ser executado até que o número 42 fosse encontrado.
loop do
tries += 1
throw(:done, tries) if rand(100) == 42
end
end
149
puts "We tried #{count} times until we get 42"
#=> We tried 82 times until we get 42
Um outro uso seria para interromper loops aninhados. Veja, por exemplo, como para a execução dos loops quando a letra n e o número
42 forem encontrados e, ainda assim, ter acesso aos valores encontrados.
# number: 42
# letter: n
150
CAPÍTULO 1
Conhecendo o Rake
Hmmm….
Este conteúdo está sendo escrito e estará disponível em breve.
151
CAPÍTULO 1
Testes ajudam os desenvolvedores a escrever códigos melhores. Quando seu código ainda não foi escrito, testes ajudam a pensar em
um design de código mais desacoplado. Enquanto seu código está sendo escrito, testes ajudam a veribcar que suas alterações não
quebraram outras partes do código. Depois que seu código foi escrito, testes ajudam no processo de refatoração, dando feedback
instântaneo caso alguma coisa deixe de funcionar, além de ajudar novos desenvolvedores que tenham que manter uma base de código.
O Ruby vem com seu próprio framework de testes chamado Test::Unit. No Ruby 1.9 ele foi reescrito em cima de um outro
framework mais simples, que também já vem como uma standard library, chamado MiniTest::Unit. Resumindo, no Ruby 1.9, o
Test::Unit é apenas uma camada de compatibilidade em cima do MiniTest::Unit.
O MiniTest::Unit vem ainda com um outro idioma chamado MiniTest::Spec, que tenta ser algo próximo do RSpec, um framework
de testes mantido por David Chelimsky.
Independente do framework de testes que você venha a utilizar, todos eles seguem mais ou menos o mesmo padrão. Você irá
especibcar um resultado esperado e irá compará-lo com o resultado gerado por seu código. Caso esta comparação falhe, o framework
de testes irá exibir uma mensagem dizendo qual trecho de código falhou. Não é nem preciso dizer que este tipo de abordagem é
inbnitamente melhor que testar todo um cuxo de ações manualmente cada vez que uma alteração for realizada.
Para mostrar como usar o framework de testes Test::Unit, nós iremos implementar um conversor de temperaturas. Antes de
começar, vamos organizar o nosso código de modo que ele possa ser facilmente distribuído depois como uma gem.
152
Organizando o código
Bibliotecas do Ruby seguem mais ou menos o mesmo padrão. O código-fonte normalmente bca em um diretório lib. Caso sua
biblioteca seja composta por mais de um arquivo, um diretório com o mesmo nome da biblioteca deve ser criado no diretório lib.
Arquivos de teste devem ser criados no diretório test. Organize seu diretório de modo que ele seja parecido com a imagem à seguir.
Figur
Figuraa 1: Estrutura inicial da biblioteca Temperature
Para executar os testes nós iremos utilizar uma rake task. Crie o arquivo Rakefile na raíz de seu projeto com o seguinte conteúdo.
require "rake/testtask"
Rake::TestTask.new do |t|
t.libs << "test"
t.test_files = FileList["test/*_test.rb"]
t.verbose = true
end
153
Agora, você poderá executar os seus testes com o comando rake test. Para visualizar todas as tarefas disponíveis, execute o
comando rake -T. Se tudo estiver certo, você verá algo como isto:
$ rake -T
rake test # Run tests
Convertendo temperaturas
Nossa classe Temperature deverá receber dois argumentos: o primeiro identibca a temperatura e o segundo a unidade de
temperatura utilizada. Nossa API será algo como Temperature.new(32, :celsius). Vamos escrever um primeiro teste que irá
garantir que estes atributos estão sendo debnidos corretamente.
Crie o arquivo test/temperature_test.rb. Esse arquivo será responsável por carregar o código de nossa biblioteca, além de debnir
um caso de teste
teste, que é apenas uma classe que herda da classe Test::Unit::TestCase. Todos os testes são métodos que começam
com as letras test, mas o padrão é utilizar test_.
Download temperature/test/temperature_test.rb
require "temperature"
require "test/unit"
154
Todas as veribcações que seu teste fará são chamadas de asserções
asserções. No Test::Unit, você irá usar os métodos assert_* para garantir
que seu código está de acordo com uma determinada condição.
Execute os testes com o comando rake test. Você verá que este teste irá falhar com um erro. Isso acontece porque ainda não
debnimos nossa classe Temperature.
$ rake test
# Running tests:
1) Error:
test_assign_attributes(TemperatureTest):
NameError: uninitialized constant TemperatureTest::Temperature
/Users/fnando/temperature/test/temperature_test.rb:7:in `test_assign_attributes'
Download temperature/lib/temperature.rb
class Temperature
end
Como a ideia é evoluir o seu código em pequenos passos, você deve executar os testes a cada alteração. Faça isso mais uma vez com o
comando rake test. Você que nosso teste continua falhando com um erro, embora o erro seja diferente.
155
1) Error:
test_assign_attributes(TemperatureTest):
ArgumentError: wrong number of arguments(2 for 0)
/Users/fnando/temperature/test/temperature_test.rb:6:in `initialize'
/Users/fnando/temperature/test/temperature_test.rb:6:in `new'
/Users/fnando/temperature/test/temperature_test.rb:6:in `test_assign_attributes'
Desta vez, o erro que temos é: ArgumentError: wrong number of arguments(2 for 0). Isso signibca que nossa classe não espera
receber nenhum argumento, mas estamos passando dois. Crie o método Temperature#initialize de forma que ele receba estes
dois parâmetros.
Download temperature/lib/temperature.rb
class Temperature
def initialize(number, unit)
end
end
Execute os testes mais uma vez. Agora, o teste irá falhar novamente com um erro. O motivo agora é que não debnimos os atributos
number e unit.
1) Error:
test_assign_attributes(TemperatureTest):
NoMethodError: undefined method `number' for #<Temperature:0x007fa1b40d6dc8>
/Users/fnando/temperature/test/temperature_test.rb:8:in `test_assign_attributes'
156
Debna os atributos utilizando o método Module.attr_accessor.
Download temperature/lib/temperature.rb
class Temperature
attr_accessor :number, :unit
Execute os testes mais uma vez. Deste vez, o teste irá falhar, mas sem erros.
1) Failure:
test_assign_attributes(TemperatureTest) [/Users/fnando/temperature/test/temperature_test.rb:8]:
<32> expected but was
<nil>.
Agora, podemos ver que o motivo da falha é exatamente o ponto que queremos testar. O atributo number não está sendo debnido. Para
fazer isso, basta fazer a atribuição dos valores recebidos como argumentos para as variáveis de instância de mesmo nome.
Download temperature/lib/temperature.rb
class Temperature
attr_accessor :number, :unit
157
end
end
Execute os testes mais uma vez e você verá que agora eles irão passar!
Você deve ter percebido que seguimos um cuxo bem debnido. Primeiro, escrevemos um teste que falhou. Depois, fomos corrigindo
cada uma das falhas individualmente, sempre depois de rodar os testes, até que o próprio teste passasse. Esta técnica é chamada de
Test-Driven Development.
Nossa classe Temperature terá três métodos diferentes que irão permitir a conversão entre as diferentes unidades. Estes métodos
serão chamados de Temperature#to_fahrenheit, Temperature#to_celsius e Temperature#to_kelvin.
Primeiro, vamos escrever um teste que garanta que estamos convertendo uma temperatura em Celsius para Fahrenheit. Neste teste,
iremos converter 40ºC, que são equivalentes a 104ºF. Adicione o teste à seguir ao arquivo temperature/test/
temperature_test.rb.
Download temperature/test/temperature_test.rb
def test_convert_celsius_to_fahrenheit
temp = Temperature.new(40, :celsius)
assert_equal 104, temp.to_fahrenheit
end
Execute os testes. Eles devem falhar dizendo que o método Temperature#to_fahrenheit não existe. Adicione este método.
Download temperature/lib/temperature.rb
class Temperature
attr_accessor :number, :unit
158
@unit = unit
end
def to_fahrenheit
end
end
Execute os testes mais uma vez. Ele irá falhar dizendo que o valor esperado era diferente de nil. A fórmula que faz a conversão de
Celsius para Fahrenheit é F = C * (9 / 5) + 32, onde F signibca Fahrenheit e C signibca Celsius.
Download temperature/lib/temperature.rb
class Temperature
attr_accessor :number, :unit
def to_fahrenheit
number * (9 / 5.0) + 32
end
end
Perceba que estamos utilizando 5.0 para garantir que a divisão não seja feita entre dois números inteiros.
Execute os testes mais uma vez. Veja que a conversão está sendo feita de forma correta.
Um problema de nossa implementação é que estamos assumindo que a temperatura inicial será sempre em Celsius. Mas o que
acontece se passarmos uma unidade diferente, que exige a aplicação de uma outra fórmula? Obviamente, o cálculo será feito de forma
incorreta.
159
Para evitar que nossos métodos tenham diversas expressões condicionais que irão fazer o cálculo de acordo com a unidade atual, nós
iremos sempre utilizar o método Temperature#to_celsius como base para os demais cálculos. Dessa forma, teremos que converter
apenas de Celsius para Fahrenheit e de Celsius para Kelvin.
Altere a classe Temperature de modo que o método que o método Temperature#to_fahrenheit utilize o retorno do método
Temperature#to_celsius, em vez de Temperature#number.
Download temperature/lib/temperature.rb
class Temperature
attr_accessor :number, :unit
def to_fahrenheit
to_celsius * (9 / 5.0) + 32
end
end
Ao executar os testes, eles irão falhar dizendo que o método Temperature#to_celsius ainda não existe. Vamos adicionar alguns
testes para garantir que este método converta os valores corretamente. Primeiro, vamos escrever um teste para a conversão de
Celsius para Celsius.
Download temperature/test/temperature_test.rb
def test_convert_celsius_to_celsius
temp = Temperature.new(40, :celsius)
assert_equal 40, temp.to_celsius
end
160
Execute os testes, que continuarão falhando. Altere o método Temperature#to_celsius, adicionando um case.
Download temperature/lib/temperature.rb
class Temperature
attr_accessor :number, :unit
def to_fahrenheit
to_celsius * (9 / 5.0) + 32
end
def to_celsius
case unit
when :celsius then number
end
end
end
Adicione mais um teste para garantir que a conversão de Fahrenheit para Celsius está sendo feita de forma correta.
Download temperature/test/temperature_test.rb
def test_convert_fahrenhet_to_celsius
temp = Temperature.new(104, :fahrenheit)
161
assert_equal 40, temp.to_celsius
end
1) Failure:
test_convert_fahrenhet_to_celsius(TemperatureTest) [/Users/fnando/temperature/test/temperature_test.rb:24]:
<40> expected but was
<nil>.
Adicione mais esta conversão ao método Temperature#to_celsius. A fórmula que faz a conversão de Fahrenheit para Celsius é C =
(F - 32) * (5 / 9).
Download temperature/lib/temperature.rb
class Temperature
attr_accessor :number, :unit
def to_fahrenheit
to_celsius * (9 / 5.0) + 32
end
def to_celsius
case unit
162
when :celsius
number
when :fahrenheit
(number - 32) * (5 / 9.0)
end
end
end
A última conversão que precisa ser feita é de Kelvin para Celsius. Esta fórmula é a mais simples de todas: C = K - 273.15, onde C
signibca Celsius e K signibca Kelvin. Primeiro adicione o teste.
Download temperature/test/temperature_test.rb
def test_convert_kelvin_to_celsius
temp = Temperature.new(323.15, :kelvin)
assert_equal 50, temp.to_celsius
end
Execute os testes, que devem falhar dizendo que o valor esperado é diferente de nil. Agora, podemos implementar a conversão de
Kelvin para Celsius.
Download temperature/lib/temperature.rb
class Temperature
attr_accessor :number, :unit
163
def to_fahrenheit
to_celsius * (9 / 5.0) + 32
end
def to_celsius
case unit
when :celsius
number
when :fahrenheit
(number - 32) * (5 / 9.0)
when :kelvin
number - 273.15
end
end
end
Muito bem! A conversão de temperaturas em Fahrenheit e Kelvin em Celsius já podem ser realizadas, embora o contrário ainda não
seja verdade. Ainda falta implementarmos a conversão para Kelvin. O próximo teste irá garantir que uma temperatura em Celsius seja
convetida em Kelvin.
def test_convert_celsius_to_kelvin
temp = Temperature.new(50, :celsius)
assert_equal 323.15, temp.to_kelvin
end
Ao executar os testes, você verá que ele irá falhar dizendo que o método Temperature#to_kelvin não foi debnido. Você pode, então,
implementar este método. A fórmula de conversão de Celsius para Kelvin é K = C + 273.15.
164
Download temperature/lib/temperature.rb
class Temperature
attr_accessor :number, :unit
def to_fahrenheit
to_celsius * (9 / 5.0) + 32
end
def to_celsius
case unit
when :celsius
number
when :fahrenheit
(number - 32) * (5 / 9.0)
when :kelvin
number - 273.15
end
end
def to_kelvin
to_celsius + 273.15
end
end
Parabéns! Você acabou de escrever uma biblioteca que converte temperaturas entre diferentes unidades com testes. Para deixar o
exemplo completo, sua classe de testes deve se parecer com isto:
165
Download temperature/test/temperature_test.rb
require "temperature"
require "test/unit"
def test_convert_celsius_to_kelvin
temp = Temperature.new(50, :celsius)
assert_equal 323.15, temp.to_kelvin
end
def test_convert_celsius_to_fahrenheit
temp = Temperature.new(40, :celsius)
assert_equal 104, temp.to_fahrenheit
end
def test_convert_celsius_to_celsius
temp = Temperature.new(40, :celsius)
assert_equal 40, temp.to_celsius
end
def test_convert_kelvin_to_celsius
temp = Temperature.new(323.15, :kelvin)
assert_equal 50, temp.to_celsius
end
166
def test_convert_fahrenhet_to_celsius
temp = Temperature.new(104, :fahrenheit)
assert_equal 40, temp.to_celsius
end
end
assert_equal(expected, actual) Passa se expected for igual a actual quando o operador == é usado
assert_in_delta(expected, actual, delta) Passa se o número de ponto cutuante expected for igual a actual dentro do delta
esperado
assert_in_epsilon(expected, actual, epsilon) Calcula o delta com epsilon * min(expected, actual) e então executa
assert_in_delta com este valor
assert_includes(collection, element) Veribca se collection inclui o objeto element com o método collection#include?
167
assert_operator(object1, operator, object2) Veribca se o envio da mensagem operator para object1 com o parâmetro object2
retorna true
assert_raises(Exception, ..., &block) Veribca se o bloco block lança uma das exceções listadas quando executado
assert_raise(Exception, ..., &block)
assert_throws(expected, &block) Veribca se a execução do bloco block lança o símbolo expected com o método throw
assert_not_match(regexp, string) Veribca se a string string não casa a expressão regular regexp
assert_nothing_raised(Exception, ..., &block) Veribca se o bloco é executado sem lançar as exceções listadas
assert_nothing_thrown(expected, &block) Veribca se o bloco é executado sem lançar o símbolo expected com o método throw
168
CAPÍTULO 1
Cada pacote, chamado de gem, pode conter arquivos Ruby que podem ser carregados pelo seu próprio código. Você pode instalar gems
disponibilizadas por outros desenvolvedores e pode criar e distribuir suas próprias gems com muita facilidade. Atualmente existem
mais 30 mil gems cadastradas no site http://rubygems.org/, o site que hospeda os pacotes disponibilizados pela comunidade Ruby.
Os códigos que podem ser carregados bcam no diretório lib. Caso sua gem inclua arquivos executáveis, eles devem bcar no diretório
bin. Uma estrutura muito usada por convenção pode ser vista à seguir:
example
├── Rakefile
├── bin
│ └── example
├── example.gemspec
├── lib
│ ├── example
│ │ └── version.rb
│ └── example.rb
169
└── test
└── example_test.rb
Download temperature/simple_temperature.gemspec
require "./lib/temperature/version"
Gem::Specification.new do |s|
s.name = "simple_temperature"
s.version = Temperature::Version::STRING
s.description = "Convert temperature between different units."
s.summary = s.description
s.author = "Nando Vieira"
s.email = "fnando.vieira@gmail.com"
s.files = Dir["lib/**/*"]
s.test_files = Dir["test/**/*"]
s.homepage = "http://rubygems.org/gems/simple_temperature"
end
1
Como a gem Temperature já existe, vamos usar um outro nome qualquer. Você provavelmente vai querer manter o nome do arquivo
gemspec igual ao nome de sua gem.
170
• uma pequena descrição sobre o que nossa gem faz e que pode ser exibida com o comando gem list simple_temperature.
• os arquivos que compões a gem, que neste exemplo é tudo o que está dentro do diretório lib.
• os arquivos de teste, que neste exemplo é tudo o que está dentro do diretório test.
Download temperature/lib/temperature/version.rb
class Temperature
module Version
MAJOR = 0
MINOR = 1
PATCH = 0
STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
end
end
Sua gem já está pronta para ser empacotada. À partir da raíz do seu projeto, execute o comando gem build
simple_temperature.gemspec. Este comando irá gerar o arquivo simple_temperature.gem, que é o seu pacote, propriamente dito.
Para instalar a sua gem localmente, use o comando gem install simple_temperature --local. Para ver mais detalhes sobre a
instalação, use o parâmetro --verbose.
171
$ gem install simple_temperature --local
Successfully installed simple_temperature-0.1.0
1 gem installed
Agora, vamos nos certibcar que está tudo funcionando. Você pode fazer isso através do IRB.
$ irb
>> require "temperature"
=> true
>> Temperature.new(60, :celsius).to_fahrenheit
=> 140.0
>>
Note que não estamos carregando a gem simple_temperature. Toda vez que você usa require, todos os arquivos das gems que estão
no diretório lib bcam disponíveis no $LOAD_PATH. Por isso podemos simplesmente carregar o arquivo temperature.rb. Em uma
situação normal, onde o nome do arquivo principal da gem recete o nome da própria gem, isso não seria motivo de confusão.
Uma outra maneira é distribuir sua gem publicamente através do site http://rubygems.org/. Para fazer isso, você precisará criar sua
conta. Acesse o endereço https://rubygems.org/users/new, informe seu e-mail, username e senha e você está pronto para continuar.
Execute o comando gem push simple_temperature-0.1.0.gem. Este comando irá solicitar suas credenciais. Informe as mesmas que
foram cadastradas no RubyGems.org. Você só precisará fazer isso desta vez; futuras publicações serão feitas automaticamente.
172
$ gem push simple_temperature-0.1.0.gem
Enter your RubyGems.org credentials.
Don't have an account yet? Create one at http://rubygems.org/sign_up
Email: fnando.vieira@gmail.com
Password:
Pushing gem to https://rubygems.org...
Signed in.
Pushing gem to https://rubygems.org...
Successfully registered gem: simple_temperature (0.1.0)
A publicação da gem pode demorar alguns minutos. Para veribcar se sua gem já está disponível execute o comando gem list
simple_temperature -rd.
simple_temperature (0.1.0)
Author: Nando Vieira
Homepage: http://rubygems.org/gems/simple_temperature
Sua gem também pode ser acessada através do endereço https://rubygems.org/gems/simple_temperature. Aproveite que você acessou
a página de sua gem e atualize as outras informações como endereço de onde as pessoas poderão reportar bugs e visualizar o código-
fonte.
173
Mais sobre RubyGems
Uma gem é basicamente o que você acabou de ver. No entanto, você pode ir além. É possível, por exemplo, criar extensões nativas
usando C.
Você também pode criar o seu próprio servidor de gems, privado, onde só suas aplicações podem acessar. Desta forma, você pode
distribuir bibliotecas entre os diversos aplicativos de sua empresa sem se preocupar com a privacidade e segurança.
Para mais informações sobre como fazer estas e outras coisas, visite o guia publicado pelo site RubyGems.org no endereço
http://guides.rubygems.org/.
174
Copyright © Hellobits & Nando Vieira. Todos os direitos reservados.
Veja outras publicações em http://howtocode.com.br.