Explorar E-books
Categorias
Explorar Audiolivros
Categorias
Explorar Revistas
Categorias
Explorar Documentos
Categorias
Copyright Hellobits & Nando Vieira. Todos os direitos reservados. Nenhuma parte desta publicao pode ser reproduzida sem o consentimento dos autores. Todas as marcas registradas so de propriedade de seus respectivos donos. Conhecendo Ruby, Nando Vieira, 1a verso
Contedo
1 2 2 5 9 9 9
Introduo Sobre o Rub Ruby y Instalao Tipo Variveis e constantes Mtodos Entendendo o self
35 Hash 37 Symbol 38 Boolean 39 nil 40 Range 41 Expresses regulares 42 Procs e lambdas 47 Set 49
Estrutur Estruturas as condicionais
12 Convenes 19 Atribuio de variveis 19 Constantes e variveis globais 20 Conhecendo o IRB 22 Executando cdigos Ruby 23 Acessando a documentao do Ruby 26
Rub Ruby y Core Classes
59 for..in
110 Operadores matemticos 111 Nmeros absolutos 111 Veribcando nmeros pares e mpares 112 Veribcando zeros e no-zeros 112 Converso entre diferentes bases 113 Fazendo arredondamentos 115
Trabalhando com arr arra ays
76 Criando classes 78 Debnindo mtodos 83 Debnindo mtodos de classe 89 Debnindo constantes 92 Entendendo classes Singleton 97 98 99
Mdulos Trabalhando com o load path Trabalhando com strings
115 Acessando os elementos 115 Adicionando novos elementos 117 Removendo elementos 119 Filtrando elementos 120 Ordenando elementos 122 Buscando elementos 123 Iterando elementos 124 Compondo novos arrays
126
152
126 Lista de chaves e valores 126 Veribcando a existncia de chaves e valores 127 Acessando o hash 128 Filtrando elementos 129 Removendo valores de um hash 130 Compondo novos hashes 131 Iterando hashes 133
Trabalhando com arquiv arquivos os e diretrios
153 Organizando o cdigo 154 Convertendo temperaturas 167 Lista de mtodos de assero 169
Criando e distribuindo gems
169 A estrutura de uma gem 170 Criando sua prpria gem 172 Distribuindo sua gem 174 Mais sobre RubyGems
133 Manipulando nomes de arquivos 134 Manipulando arquivos 138 Manipulando diretrios 140 Testando arquivos e diretrios 141 Datas de modibcao e acesso de arquivos 142 143 144
Trabalhando com data e hor hora a URLs e requisies HTTP Lidando com e ex xcees
CAPTULO 1
Introduo
Ruby uma linguagem de programao interpretada, com tipagam forte e dinmica, que tem como foco a simplicidade e produtividade. Sua sintaxe extremamente elegante, o que facilita a leitura e escrita de cdigos. Criada pelo japons Yukihiro Matz Matsumoto em meados de 1993, a linguagem s foi lanada publicamente em 1995. Matz combinou partes de suas linguagens favoritas (Perl, Smalltalk, Eiffel, Ada e Lisp) para criar uma linguagem que fosse, segundo suas prprias palavras, mais poderosa que Perl e mais orientada a objetos que Python. Muitas linguagens no tratam nmeros e outros tipos primitivos como objetos, mas no Ruby isso diferente. No Ruby, tudo objeto. Tipos primitivos possuem mtodos e podem ter atributos. Classes so objetos. O Ruby uma linguagem extremamente cexvel. Hmmm. Este contedo est sendo escrito e estar disponvel em breve. Nando Vieira, Janeiro de 2012
CAPTULO 1
Sobre o Ruby
Instalao
O Ruby pode ser instalado em todos os sistemas operacionais. Veja abaixo como instalar em sua mquina de desenvolvimento. sempre uma boa ideia utilizar a ltima verso estvel do Ruby, a menos que voc tenha razes muito fortes para no fazer isso. Enquanto este livro est sendo escrito, a ltima verso estvel 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 endereo http://rubyinstaller.org/ downloads/ e faa o download do instalador da ltima verso estvel.
Execute o arquivo baixado, algo como rubyinstaller-1.9.3-p0.exe, e o instalar ser iniciado. Aceite os termos de uso. Ser exibida, ento, uma janela onde voc deve marcar a conbgurao Add Ruby executables to your PATH. Sem essa opo, o Ruby no bcar disponvel globalmente no seu terminal.
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 verso do Ruby. Em verses mais antigas do Windows, voc pode usar o comando cmd. Se preferir, voc tambm pode instalar o PowerShell manualmente.
Se aparecer alguma mensagem muito diferente de ruby 1.9.3-p0 (2011-10-30) [i386-mingw32], a instalao provavelmente no foi completada com sucesso. Neste caso, reinicie a instalao e siga exatamente os passos descritos acima.
Instalando no Mac OS X
Embora o Mac OS X j venha com o Ruby instalado por padro, a sua verso muito antiga. Hmmm. Este contedo est sendo escrito e estar disponvel em breve.
Isso ir baixar e instalar o RVM. Hmmm. Este contedo est sendo escrito e estar disponvel em breve.
Tipo
O Ruby uma linguagem dinamicamente tipada tipada, o que signibca dizer que voc no precisa debnir o tipo de dado que uma varivel ir armazenar. Alm disso, uma mesma varivel pode receber tipos diferentes a qualquer momento.
value = 1234 puts value.class #=> Fixnum value = "Hello" puts value.class #=> String
O Ruby tambm uma linguagem fortemente tipada tipada, j que o tipo do objeto veribcado antes de efetuar determinadas operaes.
number = 1234 string = "Hello" number + string #=> TypeError: String can't be coerced into Fixnum
Ao tentar somar o nmero 1234 com a string Hello, o Ruby lanar a exceo TypeError. Isso acontece porque a coero precisa ser feita explicitamente.
Duck Typing
No Ruby, ns no declaramos o tipo de objetos, nem o tipo do retorno de mtodos. Embora isso possa parecer algo muito ruim para quem est acostumado com linguagens como Java, linguagens dinamicamente tipadas como o Ruby so muito cexveis, produtivas e, acredite, seguras. Na maioria das vezes, o medo de no poder contar com o compilador para fazer veribcaes de tipos no tem fundamento. Desenvolvedores Ruby esto mais acostumados em debnir objetos pelo que eles podem fazer, do que por seu tipo. Esta tcnica chamada de duck typing. Se anda como um pato e faz barulho como um pato, ento de dev ve ser um pato. E o interpretador bcar feliz em fazer com que o objeto seja tratado como um pato. Na prtica, isso signibca que em vez de fazer veribcaes de tipo de um objeto, voc deve se preocupar se este objeto capaz de executar o mtodo que voc precisa. Pegue como exemplo strings, arquivos e arrays. As classes Array, File e String implementam o mtodo de instncia <<, que quase sempre signibca append. Voc pode se aproveitar desta interface para criar, por exemplo, uma classe de log que no se importa com o tipo de objeto que ir armazenar esses logs.
class SimpleLogger def initialize(io) @io = io end
A classe SimpleLogger consegue enviar os logs para arrays, strings, arquivos e, se quiser, para qualquer outro objeto que implemente o mtodo <<. O Ruby realmente abraa o duck typing por toda a linguagem. Diversos protocolos exigem que o objeto apenas implemente um mtodo to_<protocol>. Muitas operaes que envolvem arrays, por exemplo, exigem que o objeto do lado direito da expresso apenas implemente o mtodo 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 mtodo to_hash seja implementado.
class Configuration def to_hash {root: "/etc"} end end config = Configuration.new
No preciso dizer que este tipo de protocolo por conveno permite criar cdigos muito mais cexveis, com extrema facilidade. Isso no signibca que saber o tipo de objetos no seja til. Veja, por exemplo, as operaes matemticas. Qualquer objeto pode ser convertido em nmeros. Para isso, basta implementar o mtodo coerce, que recebe o objeto que est solicitando a coero. O exemplo abaixo mostra como criar uma classe cuja instncia pode ser convertida em nmeros inteiros e cutuantes, mas no em instncias 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 puts 1.0 + NumberOne.new #=> 2.0 require "bigdecimal"
puts BigDecimal.new("1.0") + NumberOne.new #=> TypeError: FakeNumber can't be coerced into BigDecimal
O duck typing vai alm de simples regras; um estilo de programao. Antes de exigir tipos de objetos, pergunte-se se isso realmente necessrio. s vezes, o tipo do objeto muito importante, mas muitas vezes isso simplesmente no importa.
Variveis e constantes
Hmmm. Este contedo est sendo escrito e estar disponvel em breve.
Mtodos
Hmmm. Este contedo est sendo escrito e estar disponvel em breve.
Entendendo o self
self ser sempre uma referncia 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 prpria classe.
puts self #=> main
Sempre que executar um mtodo, o Ruby ir veribcar se esse mtodo existe no receiver padroself a menos que voc especibqueo explicitamente. E, pelo fato de o receiver padro ser self, voc nem precisa especibc-lo se no quiser.
class Thing def do_something puts "doing something" end def do_something_else do_something end end
No mtodo do_something_else poderamos usar self.do_something, mas isso faria com que nosso cdigo apenas bcasse mais poludo. No entanto, debnir o receiver uma coisa muito comum e que, voc pode at no se dar conta, mas o faz constantemente quando escreve cdigo Ruby.
numbers = [3,1,2] numbers.sort #=> [1,2,3] text = "some string" text.upcase #=> "SOME STRING"
10
Na prtica, o receiver especibcado antes do ponto na chamada de mtodos, como em numbers.sort ou text.upcase. Alm de ser o receiver padro, self tambm o responsvel por armazenar variveis de instncia de um objeto. Veja o exemplo abaixo.
class Person def initialize(name) @name = name end def name @name end end john = Person.new("John Doe") john.name #=> "John Doe"
A instncia da classe Person possui uma nica varivel de instncia associada ao seu objeto, self, que retornada pelo mtodo Person#name. Analogamente, podemos debnir variveis de instncia 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 john = Person.new("John Doe") Person.count #=> 1
O exemplo acima mostra como variveis de instncia podem ser usadas em contextos diferentes. Primeiro, estamos debnindo um contador de instncias da classe Person, cujo valor ser armazenado em @count. Depois, em nossa prpria instncia, debnimos o nome com a varivel @name.
Convenes
Os desenvolvedores Ruby seguem uma srie de convenes enquanto esto escrevendo seus cdigos. Embora voc no seja obrigado seguir essas convenes, sempre uma boa ideia garantir que est escrevendo cdigos do mesmo jeito que um desenvolvedor mais experiente. Conbra neste captulo quais so as convenes mais utilizadas e evite olhares estranhos.
12
Classes e mdulos so nomeados em camel case. Rails ActiveSupport Net Se sua classe ou mdulo for um acrnimo, mantenha todas as letras em maisculas. HTTP HTTP::POST XML Outras constantes devem usar o formato SNAKE_CASE.
# recomendado SUCCESS_MESSAGE = "You're done!" # estranho SuccessMessage = "You're wrong!"
Mtodos predicados (aqueles que retornam valores booleanos) devem terminar com ? e no precisam de um prebxo is.
# recomendado def ready? status == "ready"
13
Mtodos que modibcam self, lanam excees ou so potencialmente perigosos/destrutivos devem terminar com uma exclamao.
# recomendado def save! save or raise("OMG!") end
14
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. Evite deixar espaamentos ao bnal da linha (trailing spaces).
15
16
A mesma regra deve ser aplicada quando voc estiver executando mtodos.
# recomendado user.run # estranho user.run()
Esta regra possui uma exceo. Quando um mtodo debnido com o mesmo nome de uma constante, voc deve usar os parnteses. Caso contrrio, voc estar acessando a prpria constante, que normalmente ser um mdulo ou classe.
class Foo; end def Foo puts "You called the Foo method" end Foo.new Foo() #=> Instancia a classe Foo #=> Executa o mtodo Foo()
17
No entanto, se voc precisar encerrar o cuxo de execuo 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
Mtodos podem receber blocos[2]. Quando o seu bloco possuir mais de uma instruo ou precisar encadeado, utilize chaves ({ e }) para criar blocos inline.
contents = File.open("file.txt") {|file| file.read} #=> Ruby #nice numbers = [1,2,3].map {|n| n * 2}.reject {|n| n % 2 == 0} #=> [2]
Se o seu bloco possuir mais de uma instruo, voc deve utilizar as palavras-chave do..end.
File.open("file.txt", "w+") do |file| file.write "Ruby"
No se preocupe com o que so blocos por enquanto. Voc ver mais sobre este assunto mais frente. 18
Atribuio de variveis
Hmmm. Este contedo est sendo escrito e estar disponvel 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 execuo e faz o loop, esperando por uma nova linha. O IRB a melhor maneira de testar q Para iniciar o IRB, execute o comando irb.
$ irb irb(main):001:0>
Neste shell voc pode digitar qualquer cdigo Ruby. Experimente digitar alguma expresso matemtica simples.
irb(main):001:0> 1 + 1 => 2
No Ruby, tudo[3] objeto. Voc pode descobrir qual a classe de um objeto com o mtodo Object#class.
irb(main):002:0> 1234.class => Fixnum irb(main):003:0> "Hello".class => String
Mtodos so aes que um objeto pode realizar. No exemplo acima, o mtodo Object#class apenas informa qual a classe que instanciou um determinado objeto. Voc tambm pode acessar a lista de todos os mtodos que um objeto possui com o mtodo Object#methods. Veja, por exemplo, quais so os mtodos de uma string:
1 3
Read-Eval-Print-Loop
Na verdade, quase tudo no Ruby objeto. Algumas coisas no so 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 apresentao, de acordo com o nvel de indentao do cdigo.
irb(main):005:0> def sum(n1, n2) irb(main):006:1> n1 + n2 irb(main):007:1> end => nil irb(main):008:0> sum(3, 2) => 5 irb(main):009:0>
21
O IRB permite testar cdigos Ruby interativamente. Escreva outros tipos de expresses para se familiarizar com esta excelente ferramenta.
Para executar este cdigo, 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, possvel 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 executvel com o comando chmod +x hello.rb. Ao fazer isso, voc no mais precisar executar o interpretador Ruby.
22
23
garbage_collect
Voc tambm pode visualizar a documentao de um mtodo especbco. Veja, por exemplo, a documentao do mtodo String#upcase.
$ ri String#upcase = String#upcase (from ruby core) -----------------------------------------------------------------------------str.upcase -> new_str
-----------------------------------------------------------------------------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. "hEllO".upcase #=> "HELLO"
24
GC.enable
-----------------------------------------------------------------------------Enables garbage collection, returning true if garbage collection was previously disabled. GC.disable GC.enable GC.enable #=> false #=> true #=> false
25
CAPTULO 1
A diferena entre os dois delimitadores que os apstrofos ignoram caracteres como \n e \t.
puts "Ruby is really\nnice language." #=> Ruby is really #=> nice language. puts 'Ruby is really\nbeautiful.' #=> Ruby is really\nbeautiful.
Outra diferena que strings delimitadas por apstrofos no podem ser interpoladas. Interpolao o processo de debnir uma expresso Ruby dentro de uma string, de modo que seu resultado substitua os delimitadores #{} que englobam a expresso.
now = Time.now puts "Time: #{now}" #=> Time: 2011-12-21 01:35:30 -0200
26
Voc pode debnir strings com mltiplas linhas sem precisar de nenhuma sintaxe especial.
text = "This can be a long text with multiple lines." text = 'This can be a long text with multiple lines.'
Caracteres podem ser escapados com uma barra invertida antes do caracter.
puts "Ruby for \"rubyists\"." #=> Ruby for "rubyists". puts "Ruby for\\nrubyists." #=> Ruby for\nrubyists.
Voc pode debnir strings de outras formas. Uma delas usando o formato heredoc, que possui duas variaes.
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
A string heredoc precisa de um identibcador (escrito em letras maisculas) que ser usado para iniciar e terminar a string. A diferena entre as duas variaes que a primeira forma exige que o identibcador de trmino esteja no comeo da linha linha. Voc pode executar mtodos em strings heredoc.
puts <<-TEXT.upcase This can be a long text with multiple lines. TEXT #=> THIS CAN BE A LONG TEXT WITH #=> MULTIPLE LINES.
Uma outra caracterstica de strings heredoc que elas preservam espaos em branco no comeo da linha.
puts <<-TEXT This can be a long text with multiple lines. TEXT #=> This can be a long text with #=> multiple lines.
#=> Time: 2011-12-21 01:40:09 -0200 puts %!Time: #{Time.now}! #=> Time: 2011-12-21 01:40:09 -0200
Perceba nos exemplos acima que foram usados diferentes tipos de delimitadores: %q[], %Q() e %!!. Na prtica, voc pode usar qualquer caracter como delimitador. Note que voc precisar escapar os caracteres da string que forem iguais ao delimitador.
puts %q~Time: #{Time.now}~ #=> Time.now #{Time.now} puts %Q/Time: #{Time.now}/ #=> Time: 2011-12-21 01:40:09 -0200 puts %<Time: #{Time.now}> #=> Time: 2011-12-21 01:40:09 -0200 puts %:Time\: #{Time.now}: #=> Time: 2011-12-21 01:40:09 -0200
29
Nmeros
O Ruby possui 8 classes para representar nmeros. Todos os objetos que representam nmeros no Ruby so instncias da classe Numeric. Nmeros so imutveis e, por este motivo, no existem mtodos que podem mudar o valor de um nmero; se voc tentar fazer isso, receber a mensagem de erro Can't change the value of self. Em verses mais antigas do Ruby, as classes Complex e Rational no so nativas do Ruby, embora sejam distribudas como parte da Standard Library[1].
Fixnum
Nmeros inteiros no possuem um tamanho determinado, pois o seu tamanho debnido pela quantidade de memria disponvel. Quando debnidos nos intervalos entre 230 e 230-1 ou 262 e 262-1, inteiros so debnidos como instncias da classe Fixnum. Inteiros fora deste intervalo so automaticamente debnidos como objetos da classe Bignum, em um processo totalmente transparente e automtico.
number = 1000 3.times do number *= number puts "#{number.class} => #{number}"
1
end # # # # Output: Fixnum => 1000000 Fixnum => 1000000000000 Bignum => 1000000000000000000000000
Voc pode debnir nmeros inteiros usando um sinal de + ou - opcional para debnir valores positivos e negativos, um indicador opcional da base do nmero, seguidos pela sequncia de nmeros especibcados na base escolhida.
1234 0d1234 1_234 -1234 0x4d2 02322 0b10011010010 #=> #=> #=> #=> #=> #=> #=> 1234 1234 1234 -1234 1234 1234 1234
Float
Nmeros de ponto cutuante so debnidos pelo . (ponto decimal) aps um ou mais nmeros decimais, seguido por mais nmeros decimais. Voc tambm pode, opcionalmente, utilizar um expoente. Ao contrrio dos nmeros inteiros, nmeros com ponto cutuante no podem ser debnidos em outra base diferente de 10.
puts puts puts puts puts puts 1.234 -1.234 1_234.0 12e2 12.3e2 12.3E2 #=> #=> #=> #=> #=> #=> 1.234 -1.234 1234.0 1200.0 1230.0 1230.0
31
No Ruby, no possvel debnir nmeros com ponto cutuante sem ter um nmero antes do ponto decimal. Se voc tentar debnir um nmero como .1 ir receber uma mensagem de erro como no .<digit> floating literal anymore; put 0 before dot. Nmeros de ponto cutuante seguem a especibcao IEEE-754, assim como a maioria das linguagens e hardwares do mercado. Dada a forma como os nmeros de ponto cutuante so tratados, fraes como 1/10 e 1/100 no podem ser representadas corretamente. muito comum clculos como o exemplo seguir causarem espanto, mesmo ele acontecendo em muitas outras linguagens[2].
0.3 - 0.2 == 0.1 #=> false
O Ruby consegue efetuar clculos deste tipo quando objetos da classe BigNumber so utilizados.
BigDecimal
A classe BigDecimal permite realizar clculos onde o arredondamento muito importante, como em clculos bnanceiros. Nmeros do tipo BigDecimal so praticamente ilimitados (expoentes acima de 1 bilho so 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 dgitos decimais que sero computados. Para ver a referncia completa, acesse a documentao.
Complex
Para ver a referncia completa, acesse a documentao.
Rational
Para ver a referncia completa, acesse a documentao.
Array
O Ruby possui arrays, que so listas que podem guardar qualquer tipo de objeto e no 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 [].
items = [10, "Hello", 3.5] items = Array.new items = Array.new([1, 2, 3])
O mtodo Array#initialize pode ser utilizado de maneiras diferentes. Voc pode dizer com quantos tens o array deve ser iniciado. Por padro, o array ser iniciado com o valor nil.
items = Array.new(5) #=> [nil, nil, nil, nil, nil]
Voc tambm pode passar um valor inicial que ser usado para popular este array.
items = Array.new(5, "hello") #=> ["hello", "hello", "hello", "hello", "hello"]
Tambm possvel iniciar um array com um bloco. Neste caso, o valor retornado pelo bloco ser usado.
33
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 numricos. Os ndices comeam em 0 e tambm 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] numbers.first numbers[4] numbers[-1] numbers.last #=> #=> #=> #=> #=> 0 0 8 18 - A mesma coisa que numbers[numbers.size - 1] 18
Hash
Um outro tipo de estrutura de dados do Ruby o Hash. Hashes so como arrays, com a diferena 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 imprevisvel.
user = {"name" => "John Doe", "age" => 32}
Voc pode debnir o valor-padro de uma chave que ainda no 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
Perceba que os valores-padro que so retornados no so armazenados no hash. de responsabilidade do bloco fazer esta atribuio.
options = Hash.new {|hash, key| "OMG!!!"} options["invalid key"] #=> retorna "OMG!!!" options.keys #=> [] options = Hash.new {|hash, key| hash[key] = "OMG!!!"} options["invalid key"] #=> retorna "OMG!!!" options.keys #=> ["invalid key"]
Voc tambm pode inicializar arrays usando o mtodo Hash.[]. Voc pode passar uma lista de argumentos que alternam entre chave e valor.
user = Hash["name", "John Doe", "age", 32] #=> {"name" => "John Doe", "age" => 32}
O mtodo Hash.[] tambm aceita um array de arrays de dois elementos que identibcam a chave e o valor.
user = Hash[[["name", "John Doe"], ["age", 32]]] #=> {"name" => "John Doe", "age" => 32}
36
Por ltimo, voc pode passar um objeto que pode ser convertido em hash atravs do mtodo to_hash.
user = {"name" => "John Doe", "age" => 32} Hash[user] #=> {"name" => "John Doe", "age" => 32}
partir do Ruby 1.9 possvel debnir hashes usando uma sintaxe semelhante a do JavaScript. Uma caracterstica dessa sintaxe que as chaves so geradas como smbolos.
user = {name: "John Doe", age: 32} user.keys #=> [:name, :age]
Symbol
Smbolos so objetos que representam nomes no Ruby e so muito utilizados como identibcadores, principalmente como chaves de hashes. Eles so gerados atravs da sintaxe :symbol ou :"symbol", alm dos mtodos String#to_sym e String#intern.
symbol symbol symbol symbol = = = = :john :"john doe" "john".to_sym "john doe".intern
37
Smbolos compartilham sempre o mesmo espao em memria, 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 so true e false, que so instncias das classes TrueClass e FalseClass. Infelizmente, ambas as classes no possuem uma superclasse comum.
true.class #=> TrueClass false.class #=> FalseClass
Os valores booleanos tambm podem ser acessados atravs das constantes TRUE e FALSE.
TRUE.class #=> TrueClass
38
Os valores booleanos true e false ocupam sempre o mesmo espao em memria, atravs dos ids 2 e 0, respectivamente.
true.object_id #=> 2 false.object_id #=> 0
nil
O Ruby debne o tipo nulo atravs do do objeto nil, que uma instncia da classe NilClass. O nil ocupa sempre o mesmo espao em memria com o id 4.
nil.class #=> NilClass nil.object_id #=> 4
Este o valor de retorno de blocos e mtodos que no 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 nmeros e strings podemos utilizar a classe Range.
numbers = 1..10 numbers.class #=> Range letters = "a".."z" letters.class #=> Range
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" #=> ]
Sempre que precisar descobrir se um valor est includo em um intervalo, utilize o mtodo Range#cover?. Ele muito mais rpido que transformar o intervalo em array e depois veribcar se o tem est includo com o mtodo Array#include?.
("aaa".."zzz").cover?("def") #=> bom #=> true ("aaa".."zzz").to_a.include?("def") #=> ruim #=> true
Expresses regulares
Expresses regulares so padres (ou patterns) que permitem descrever o contedo de uma string. Elas podem ser utilizadas para veribcar se uma string contm um determinado padro ou para extrair partes dessa string. Para criar uma expresso regular voc deve debnir o padro utilizando a sintaxe /pattern/ ou %r(pattern).
41
regex = /hello/
Alguns caracteres precisam ser escapados pois eles tem um signibcado especial em expresses regulares. So 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 expresso regular com %r(). Note que voc pode utilizar qualquer caracter como delimitador.
regex = %r(\Ahttps?://) regex = %r[\Ahttps?://]
Expresses regulares so extremamente poderosas. Elas permitem veribcar padres que seriam difceis (e em alguns casos impossveis) de serem feitas de outras maneiras. Se voc deseja aprender mais sobre assunto, leia o livro Expresses Regulares: uma abordagem divertida, escrito por Aurlio Marinho Jargas, e que est disponvel gratuitamente para leitura online.
Procs e lambdas
Procs so blocos de cdigo que podem ser associados a uma varivel e que funcionam como funes anonnimas. Muitas vezes voc precisa de um mtodo utilitrio que ir fazer algumas pequenas computaes onde criar um mtodo propriamente dito seria muito trabalho; a que entram as procs. Para debnir uma nova proc, voc pode utilizar o mtodo Proc.new ou o mtodo Kernel#proc.
# alternativa 1 message = Proc.new { "Hello" }
42
Para executar essas procs voc pode utilizar trs mtodos diferentes.
message = proc { "Hello" } message.call message[] message.()
A ltima maneira de execuo (sum.(1, 2)) est disponvel partir do Ruby 1.9. Por conveno, procs que possuem uma nica expresso devem ser debnidas por chaves.
message = proc { "One-line proc" }
E, tambm por conveno, quando uma proc possuir mais de uma expresso elas devem ser debnidas pelas palavras-chave do..end.
message = proc do puts "Hello!" puts "Ruby!" end
Procs podem receber parmetros, assim como mtodos. Basta delimitar os parmetros com |. Para receber mais de um parmetro, separe-os com vrgula.
sum = proc {|n1, n2| n1 + n2}
43
O valor de retorno de uma proc, assim como mtodos, a ltima expresso que for executada. Se voc quiser encerrar o cuxo de execuo retornando um valor antes da ltima expresso, deve usar next, em vez do return utilizado por mtodos.
divide = proc do |n1, n2| next 0 if n1.zero? next nil if n2.zero? n1 / n2 end divide[3.0, 2] #=> 1.5 divide[0, 2] #=> 0 divide[2, 0] #=> nil
O Ruby 1.9 tambm introduziu uma nova sintaxe para a debnio de procs.
message = -> { "Hello" } message.call #=> Hello
Esta nova sintaxe tambm pode aceitar parmetros, mas faz com que a legibilidade do cdigo seja prejudicada.
44
Procs podem ser convertidas em blocos[3]. Basta adicionar um & na hora que passar o bloco como parmetro.
def sum(n1, n2, &block) block[n1 + n2] end # Passando um bloco explicitamente sum(1, 2) {|result| puts result} # Convertendo uma proc em bloco output = proc {|result| puts result} sum(1, 2, &output)
Embora o mtodo Kernel#lambda seja semelhante ao mtodo Kernel#proc, eles possui uma diferena muito importante. Lambdas iro validar a quantidade de parmetros que foram passados e lanaro a exceo ArgumentError: wrong number of arguments caso o nmero de argumentos seja incorreto. J as procs iro atribuir o valor nil para cada um dos parmetros.
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 diferena que se um return for debnido em uma proc, o mtodo que executou esta proc tambm ir encerrar o cuxo de execuo. No caso de lambdas, o cuxo de execuo 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 parmetros obrigatrios uma proc espera, use o mtodo Proc#arity. Se a proc possui argumentos opcionais, o valor de retorno ser -n - 1, onde n a quantidade de parmetros obrigatrios.
46
Para pegar uma representao dos parmetros que um bloco pode receber, use o mtodo Proc#parameters. Note que a representao muda quando um lambda debnido.
proc {|a, b = 42, *args|}.parameters #=> [[:opt, :a], [:opt, :b], [:rest, :args]] lambda {|a, b = 42, *args|}.parameters #=> [[:req, :a], [:opt, :b], [:rest, :args]]
Set
Existem situaes onde voc pode precisar de uma lista com valores nicos. Isso pode ser facilmente resolvido com arrays e o mtodo Array#include? ou Array#uniq.
items = [1, 2, 3] # alternativa 1: verificar se o tem existe antes de adicion-lo items << 3 unless items.include?(3) # alternativa 2: adicione o tem e depois pegue os elementos nicos
47
Embora essas tcnicas funcionem, elas no so otimizadas. O Ruby possui a classe Set que faz justamente isso: garante que apenas tens nicos sero adicionados lista.
require "set" items = Set.new([1, 2, 3]) # adiciona novamente o nmero 2 items << 2 # converte o set em array items.entries #=> [1, 2, 3] items.to_a #=> [1, 2, 3]
48
CAPTULO 1
Estruturas condicionais
A estrutura de controle mais comum em qualquer linguagem de programao a condicional. apenas uma maneira de executar um trecho de cdigo se alguma condio for satisfeita. Uma condio uma expresso que quando veribcada retorna um valor diferente de false e nil. O Ruby possui diferentes formas de expresses condies, como voc pode conferir seguir.
if
O if a forma mais direta de se debnir uma expresso condicional. Sua sintaxe bastante simples.
if expression # do something end
O trecho de cdigo debnido dentro de if..end ser executado somente se o valor de expression for diferente de false e nil. Note que no necessrio adicionar parnteses em torno das condies. O resultado de um if pode ser atribudo a uma varivel.
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 construo vlida e muito cexvel, nem sempre a melhor maneira. A mesma expresso poderia ser escrita de um modo muito mais legvel.
x = 1 y = 0 if x > 0 y += 1 end puts y #=> 1
A condio ser sempre a primeira a ser executada, mesmo ela sendo escrita por ltimo.
else
O if pode conter uma clusula else, que ser executada quando a condio no for satisfeita. Caso o valor expression seja igual a false ou nil, ento o cdigo associado ao else ser executado.
50
elsif
Se voc precisar adicionar mais clusulas else que dependem de outras condies, 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 expresses ir falhar caso o valor de retorno seja false ou nil, at que seja executada a ltima expresso else. Note que o else opcional.
x = 4 name = nil if x == 1 name = "one" elsif x == 2 name = "two"
51
No exemplo acima, estamos veribcando se o x possui os valores 1, 2 ou 3. Como o valor de x 4, nenhuma das condies de nosso if ser satisfeita, o que faz com que o valor original de name no seja alterado. No Ruby o mtodo inspect normalmente retorna uma representao do self como uma string.
unless
Uma tendncia natural quando queremos executar alguma expresso somente se uma condio falhar adicionar uma exclamao antes da condio ou, alternativamente, usar a palavra-chave not, que tambm tem suporte no Ruby.
if !expression # do something end
O unless tambm suporta uma clusula else, mas seu uso desencorajado; neste caso, seria muito mais simples escrever um if que coloca a expresso a ser executada se o valor da condio no for false ou nil primeiro!
52
Assim como o if, o unless tambm pode ser usado de forma inline.
x = 0 x += 1 unless x.nonzero?
Operador ternrio ?:
O operador ?: o nico operador ternrio (que possui trs operandos) suportado pelo Ruby. O primeiro operando que vem antes da interrogao a condio. O segundo operando que vem antes dos dois-pontos a expresso que ser executada caso a condio seja diferente de false ou nil. O terceiro e ltimo operando que vem depois dos dois-pontos a expresso que ser executada caso a condio falhe. No exemplo seguir temos um mtodo que ir retornar uma string levando em considerao a quantidade de tens. Se o count for igual a 1, uma string que representa o singular retornada. Caso contrrio, uma string que representa o plural retornada.
53
def pluralize(count, singular, plural) if count == 1 singular else plural end end
case
O case expresso condicional que permite fazer diversos tipos de comparaes e que pode ser usada de duas formas diferentes. A primeira forma que apenas uma alternativa para if..elsif..else a mais simples, mas tambm a menos utilizada.
case when when when else end
# # # # #
if x == 1 then "one" elsif x == 2 then "two" elsif x == 3 then "three" else "other" end
A palavra-chave then s necessria se voc quiser utilizar expresses 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 expresses diferentes. Esse modo extremamente poderoso e permite fazer comparaes com expresses regulares, classes e intervalos. O exemplo anterior poderia ser expressado de uma forma um pouco menos repetitiva.
case when when when else end x 1 then "one" 2 then "two" 3 then "three" "other"
O case tambm retorna o valor da expresso que for executada, podendo ser atribuda a uma varivel.
number = case when when when else end x 1 then "one" 2 then "two" 3 then "three" "other"
55
O case usa o operador === para fazer as comparaes. Em alguns casos, esse operador exatamente o mesmo que ==. Mas em outros casos, como classes e mdulos, o comportamento um pouco diferente. O operador === implementado[1] pelas classes e mdulos ir veribcar se um objeto uma instncia desta classe ou mdulo ou de um de seus descendentes. Vamos ver na prtica como funcionam os operadores String.=== e String#===.
hello = "hello" one = 1 String === hello #=> true hello === String #=> false String === one #=> false
Perceba que String === "hello" retorna true, mas o contrrio no verdade. Isso acontece porque a implementao de String.=== (que na verdade implementada por Module#===) diferente de String#===, que compara se o objeto uma string e se ela possui o mesmo contedo. Voltando ao case, se a expresso de comparao for uma classe, ento ele ir veribcar se a classe daquela instncia a prpria classe ou um de seus descendentes.
value = "Hello" case value when String
1
Sim! O operador === apenas um mtodo e voc pode debn-lo em seus prprios objetos. 56
J o operador === implementado pelas expresses regulares ir veribcar se um determinado padro foi satisfeito pela string.
text = "Hello Ruby!" 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) tambm 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
Essa conveno de se utilizar o operador === faz com que o case do Ruby seja muito mais poderoso que seu equivalente de outras linguagens.
58
CAPTULO 1
Estruturas de repetio
O Ruby possui trs expresses que debnem loops: for..in, while e until. Mas alm deles, possvel usar iteradores em objetos enumerveis como arrays e hashes, alm de outros objetos.
for..in
O loop for..in permite iterar em objetos que so enumerveis, como o caso de arrays e hashes. A cada iterao, um elemento ser atribudo para a varivel especibcada. Sua sintaxe bastante simples:
for item in collection # do something end
Note que cada valor atribudo varivel pode ser acessado fora da expresso for..in.
numbers = [1,2,3] for number in numbers puts "inside loop: #{number}" end puts "outside loop: #{number}"
59
No caso de hashes, voc pode debnir duas variveis que iro receber a chave e o valor, respectivamente.
numbers = {one: 1, two: 2, three: 3} for key, value in numbers puts "#{key} => #{value}" end
Caso voc fornea apenas uma varivel, esta varivel ir armazenar um array com dois elementos: a chave e o valor.
numbers = {one: 1, two: 2, three: 3} for array in numbers puts "#{array.first} => #{array.last}" end # # # # Output: one => 1 two => 2 three => 3
Embora esse tipo de loop funcione muito bem, no assim que os desenvolvedores Ruby mais experientes costumam fazer. Mais frente voc ver como utilizar os iteradores em objetos enumerveis.
60
while e until
O while e until so as formas mais bsicas de looping do Ruby. Eles iro executar algum trecho de cdigo enquanto uma condio for verdadeira ou at que uma condio se torne verdadeira. Note que primeiro a condio testada e, ento, o cdigo executado.
x = 3 while x.nonzero? puts x x -= 1 end
Tambm possvel usar o while e until como modibcadores. Eles iro executar alguma expresso at que a condio seja satisfeita.
items = [] items.push(items.size + 1) while items.size < 3 p items #=> [1, 2, 3]
61
No exemplo acima estamos adicionando um nmero ao array enquanto seu tamanho for menor que trs. O nmero que adicionado ser a quantidade de elementos mais um. O mesmo exemplo poderia ser escrito com o until.
items = [] items.push(items.size + 1) until items.size >= 3 p items #=> [1, 2, 3]
Tambm possvel debnir blocos begin..end para utilizar estes modibcadores com mais de uma expresso.
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 contrrio das expresses de uma linha, o bloco executado antes de a condio 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
Embora seja uma construo aceita pela linguagem, o uso de begin..end desencorajado e pode, inclusive, ser removido em verses futuras do Ruby. possvel ter um comportamento semelhante sem que voc caia nesta armadilha: basta delimitar diversas expresses com parnteses.
( puts "OH NOES!" puts "AIN'T GONNA BE DISPLAYED! :(" ) while false
loop
Para loops que no 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 execuo do script
63
Controlando o loop
Muitas vezes necessrio controlar o cuxo de execuo de um loop. s vezes preciso interromper a execuo, outras preciso passar para o prximo tem da iterao em alguma condio especbca. O Ruby possui algumas maneiras de fazer isso.
64
Reiniciando a iterao
Para reiniciar a iterao, utilize a palavra-chave redo. Isso far com que a execuo seja reiniciada imediatamente. O exemplo seguir ir executar 3 vezes o output do elemento 3.
numbers = [1,2,3,4] tries = 1 for number in numbers puts "number: #{number}, tries: #{tries}" if tries < 3 && number == numbers[-2] tries += 1 redo end end # # # # # Output: number: number: number: number:
1, 2, 3, 3,
0 0 0 1
65
Usando iteradores
Embora loops como for..in, while/until e loop sejam teis para algumas situaes, mais provvel que voc escreva loops utilizando mtodos chamados iteradores. Os iteradores so provavelmente uma das funcionalidades mais importantes do Ruby. Um dos exemplos mais comuns de iteradores do Ruby pode ser visto seguir.
5.times { puts "Ruby!" }
O mtodo Integer#times ir executar o bloco que foi fornecido 5 vezes. esse tipo de construo do Ruby que faz com que a linguagem seja elegante. Existem outros iteradores numricos, que nada mais so que mtodos iteradores implementados pela classe Integer, assim como o mtodo Integer#times. O mtodo Integer#upto ir executar o bloco especibcado o nmeros de vezes que for debnido pelo inteiro, at atingir o nmero que foi passado como argumento. O bloco especibcado ir receber o nmero da iterao como argumento. partir do Ruby 1.9 mtodos iteradores no exigem que voc passe um bloco para execuo; neste caso, ele ir retornar um objeto do tipo Enumerator.
1.upto(3) do |number| puts number end # Output:
66
# 1 # 2 # 3
O mtodo Integer#downto funciona como o mtodo 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 mtodo chamado Integer#step. Este mtodo permite fazer iteraes usando nmeros inteiros e de pontocutuante. O exemplo seguir ir iterar de 0 a 1, com passos de 0.25.
0.step(1, 0.25) do |number| puts number end # # # # # # Output: 0.0 0.25 0.5 0.75 1.0
67
Objetos enumerveis
Objetos instanciados partir das classes Array, Hash e Range so enumerveis. O objeto considerado enumervel quando implementa o mtodo 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 enumerveis que implementa o iterador each inclui tambm o mdulo Enumerable. Este mdulo adiciona muitos mtodos que agem em cima do mtodo each e que permitem iterar, buscar e ordenar os tens da coleo. O mdulo Enumerable, por exemplo, inclui os mtodos Enumerable#map, Enumerable#select, Enumerable#reject, Enumerable#find e Enumerable#inject, s para citar alguns. O mtodo Enumerable#map permite criar um novo array contendo os elementos retornados pelo bloco.
[1,2,3].map {|number| number * 2} #=> [2, 4, 6]
O mtodo Enumerable#select permite criar um novo array contendo os elementos cujo valor retornado pelo bloco sejam diferentes de false ou nil.
(1..10).select {|number| number.even?} #=> [2, 4, 6, 8, 10]
68
O mtodo Enumerable#reject faz exatamente o contrrio do mtodo Enumerable#select e ir retornar um array contendo os elementos cujo valor do bloco sejam false ou nil.
(1..10).reject {|number| number.odd?} #=> [2, 4, 6, 8, 10]
O mtodo Enumerable#find ir retornar o primeiro elemento cujo valor de retorno do bloco seja diferente de false ou nil.
(1..10).find {|number| number == 3} #=> 3
J o mtodo 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 iteraes passadas. O bloco que foi fornecido pode ou no incrementar este acumulador, dependendo de suas condies. O acumulador de dev ve ser o valor de retorno do bloco. Veja, por exemplo, como retornar um nico nmero que ser a soma do dobro dos mltiplos de 2.
sum = (1..10).inject(0) do |acc, number| acc += number * 2 if number.even? acc end
Alternativamente, voc poderia implementar este mesmo acumulador em mais de uma etapa. Um cdigo que faz a mesma coisa, mas de forma muito menos elegante, pode ser visto seguir.
sum = 0 (1..10).each do |number|
69
70
CAPTULO 1
Blocos
Os blocos so essenciais no uso de iteradores. Embora tenhamos usado blocos quando falamos sobre objetos enumerveis, no dedicamos tempo para explicar o que eles so. Blocos nunca podem estar sozinhos; eles sempre precisam estar associados a uma execuo de mtodo. Todo mtodo que executado pode receber um bloco, mas apenas os mtodos que esperam este bloco e que faam o yield que iro de fato execut-los; caso contrrio, o bloco ser ignorado silenciosamente. Por baixo dos panos, blocos so apenas procs. Assim como as procs, blocos seguem a conveno de se usar chaves para blocos com uma nica expresso e do..end para blocos com mais de uma expresso.
# Apenas uma expresso [1, 2, 3].map {|number| number * 2} # Diversas expresses [1, 2, 3, 4, 5].inject(0) do |acc, number| acc += number if number.even? acc end
Lembre-se! Como blocos sempre esto associados execuo de mtodos, voc no deve usar este termo para se referir a procs ou lambdas.
71
Escopo de variveis
Blocos introduzem um novo escopo de variveis. Toda vez que voc debne parmetros que sero recebidos pelo bloco, estas variveis sero acessveis apenas no contexto do bloco. O exemplo seguir mostra como a varivel i debnida apenas no escopo local do bloco.
1.upto(5) {|i| } p defined?(i) #=> nil
No entanto, blocos tambm podem referenciar variveis do contexto externo ao bloco e, nesse caso, podem inclusive modibcar estas variveis.
total = 0 1.upto(10) {|i| total += i} puts total #=> 55
partir do Ruby 1.9 os parmetros esperados pelo bloco no mais modibcam variveis 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 iterao, ou seja, 10.
i = 0 1.upto(10) {|i| } puts i #=> 0
72
O bloco ser executado toda vez que voc usar yield. Ento, se em um mesmo mtodo voc usar trs vezes a palavra-chave yield, o bloco ser executado trs vezes.
def hello yield "Hello!" yield "Hi!" yield "Wassup!" end hello {|message| puts message} # # # # Output: Hello! Hi! Wassup!
Se nenhum bloco for passado para este mtodo say, uma exceo LocalJumpError ser lanada. Para evitar que isto acontea, voc pode veribcar se algum bloco foi passado com o mtodo Kernel#block_given? e tomar as aes necessrias.
73
begin say rescue Exception => error puts error.message end # Output: #=> Hello #=> Y U MAD BRO? JUST GIMME A BLOCK!
Para passar parmetros para o bloco que foi fornecido, basta fazer o yield passando os argumentos.
def first_and_last(list) yield list.first, list.last end first_and_last([1,2,3]) do |first, last| puts "First item: #{first}" puts "Last item: #{last}" end
74
O yield quase sempre subciente. Mas s vezes, voc quer ter um pouco mais de controle, seja passando o bloco como parmetro para outro mtodo ou agindo como proxy de um outro mtodo que tambm espera um bloco. O Ruby permite que voc capture blocos passados para mtodos usando a construo &variavel. A nica exigncia que essa varivel deve ser sempre a ltima varivel da lista. O exemplo anterior poderia ser reescrito usando essa construo.
def first_and_last(list, &block) block.call(list.first, list.last) end first_and_last([1,2,3]) do |first, last| puts "First item: #{first}" puts "Last item: #{last}" end
Note que no estamos mais usando o yield; agora, executamos o mtodo Proc#call passando os argumentos. Alternativamente, poderamos usar algum outro mtodo que executa procs, como Proc#[].
75
CAPTULO 1
Classes
O Ruby, como voc pode perceber at agora, uma linguagem orientada a objetos. Tudo o que manipulamos no Ruby so objetos e cada objeto gerado direta ou indiretamente de uma classe. Classes debnem os mtodos que objetos podem responder. Elas tambm podem estender ou ser subclasses de outras classes.
Criando classes
Para debnir uma classe use a palavra-chave class. Classes so constantes e, por este motivo, devem comear com uma letra maiscula.
class Page end
Classes so 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 cexvel.
class Page end AnotherPage = Class.new Page.class #=> Class
Classes criadas dinamicamente podem ser atribudas a qualquer tipo de varivel, e no apenas a constantes. 76
Embora ainda no tenhamos adicionado nenhum mtodo classe Page, ns j podemos instnci-la. Para isso voc ir usar o mtodo Page.new.
page = Page.new
Mesmo no tendo debnido atributos e mtodos, ns podemos executar alguns mtodos 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 mtodos. Para saber quais so as superclasses de uma classe, use o mtodo Class.ancestors. Note que a prpria classe ser adicionada lista.
Page.ancestors #=> [Page, Object, Kernel, BasicObject]
Toda vez que o mtodo Class.new for executado, ele ir iniciar a instncia com o mtodo construtor. No Ruby, o mtodo construtor Class#initialize. Este mtodo debnido automaticamente como privado e no pode ser executado diretamente de fora do objeto. Vamos fazer o mtodo Page#initialize receber dois argumentos que iro debnir o ttulo e contedo da pgina.
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 variveis de instncia, identibcados por uma arroba na frente da varivel. Elas pertencem ao objeto self que referencia o prprio objeto instanciado. Cada instncia da classe Page ter sua prpria cpia das variveis @title e @content. O mtodo Page#initialize debne duas variveis de instncia, que receber os argumentos passados no momento da instanciao. No entanto, ainda no temos nenhuma maneira de acessar tais valores diretamente.
Definindo mtodos
Para acessar as variveis de instncia que debnimos no mtodo Page#initialize, ns iremos debnir dois getters, que so mtodos que apenas retornam valores. Isso precisa ser feito pois variveis de instncia no podem ser acessadas diretamente. Variveis de instncia so encapsuladas de tal forma que apenas os mtodos de um prprio objeto que podem acess-las e modibc-las diretamente.
page = Page.new("Ruby", "OMG! Ruby is awesome!") page.title #=> NoMethodError: undefined method title for #<Page:0x007f88e3061648>
Para acessar estas variveis de instncia que foram debnidas no nosso mtodo construtor, voc precisa debnir mtodos que iro retorn-las. Embora o nome do mtodo no precise necessariamente recetir o nome da varivel, 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
Ainda no existe nenhuma maneira de debnir essas variveis de instncia sem ser pelo mtodo construtor. Para fazer isso, preciso debnir mtodos setters. Em outras linguagens, normalmente isso feito com um mtodo setTitle(title) ou set_title(title), ou algo parecido. No Ruby, voc pode debnir o seu prprio mtodo 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 mtodos como este. Agora, j possvel atribuir valores para as variveis @title e @content com os mtodos setters.
page = Page.new("Ruby", "OMG! I'm learning Ruby!") page.title #=> Ruby
80
Esta debnio de getters e setters em classes to comum que o prprio Ruby fornece uma maneira de automatizar esta debnio. Basta usar os mtodos Module#attr_reader e Module#attr_writer[2].
class Page attr_reader :title attr_writer :title attr_reader :content attr_writer :content def initialize(title, content) @title = title @content = content end end
O mtodo Module#attr_reader ir debnir o mtodo de instncia de leitura (getter), enquanto o mtodo Module#attr_writer ir debnir o mtodo de instncia de escrita (setter). Para os casos onde voc sempre debne tanto o getter quanto o setter, possvel usar o mtodo Module#attr_accessor, que far isso de uma vez s! Com esta alterao, nossa classe pode bcar muito mais simples.
class Page attr_accessor :title, :content
A classe Class possui a classe Module como superclasse. Para saber mais sobre mdulos, leia Mdulos. 81
Lembre-se que os mtodos Module#attr_accessor e companhia permitem criar apenas getters e setters que mapeiam para uma varivel de instncia de mesmo nome. Voc ter que implementar os seus prprios mtodos se eles forem mais complexos (se eles precisarem computar valores, por exemplo) ou debnirem variveis de instncia de nomes diferentes. Alternativamente, voc pode fazer com que o mtodo construtor use os mtodos Page#title= e Page#content=, em vez de atribuir as variveis de instncia. Um erro muito comum de iniciantes no debnir o objeto que ir receber o valor, chamado de receiver. O exemplo seguir debne variveis locais, em vez de executar os mtodos setters.
class Page attr_accessor :title, :content def initialize(_title, _content) title = _title content = _content end end
Para atribuir os atributos Page#title e Page#content corretamente, preciso explicitamente executar os mtodos atravs do receiver self.
class Page attr_accessor :title, :content
82
A atribuio direta das variveis de instncia mais rpida que executar os mtodos atravs do receiver. A menos que voc manipule as variveis no mtodo setter antes de atribu-las, prebra sempre debnir as variveis de instncia.
83
end end
No Ruby, voc pode ler e gerar a representao de objetos com a classe YAML. Para isso, basta carregar a standard library com o mtodo Kernel#require. O mtodo YAML.load_file l um arquivo e converte seu contudo em objetos Ruby. Neste exemplo, nosso arquivo deve retornar um hash. Classes possuem um objeto self, assim como todos os objetos. Dentro da instruo class..end, o self faz referncia prpria classe. Por isso, uma abordagem mais comum usada por desenvolvedores mais experientes debnir mtodos de classe usando def self.load(file)..end, em vez de usar o nome da prpria 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 def initialize(title, content) @title = title @content = content end end
Como o nosso mtodo de classe est no contexto da prpria classe (lembre-se, o self faz referncia a prpria classe), ns podemos fazer mais uma alterao. Em vez de instanciar a classe Page.new, basta executar o mtodo 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 def initialize(title, content) @title = title @content = content end end
A classe Page ainda no permite salvar sua representao em YAML. Vamos adicionar um mtodo 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 def initialize(title, content) @title = title
85
@content = content end def save_to(filepath) File.open(filepath, "w") {|file| file.write to_yaml } end end
A biblioteca YAML injeta um mtodo 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 representao em YAML inclui a informao sobre qual classe este objeto foi instanciado, no precisamos mais fazer isso manualmente no mtodo Page#load. Agora, podemos simplesmente retornar o objeto instanciado com o mtodo YAML.load_file.
require "yaml" class Page attr_accessor :title, :content def self.load(filepath) YAML.load_file(filepath) end def initialize(title, content) @title = title @content = content
86
end def save_to(filepath) File.open(filepath, "w") {|file| file.write to_yaml } end end
87
end end
Alternativamente voc poderia ter debnido a visibilidade dos mtodos passando uma lista de nomes de mtodos.
class SomeClass def method1 end def method2 end def method3 end def method4 end public :method1, :method4 private :method2 protected :method3 end
O controle de acesso determinado dinamicamente. Somente quando o mtodo for executado que o controle de acesso ser determinado. Se a visibilidade deste mtodo for violada, uma exceo NoMethodError ser lanada.
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 mtodos pblicos, use o mtodo 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 informaes que poderiam bcar espalhadas pelo cdigo, como nmeros mgicos. Vamos alterar a classe Page de modo que ela possa receber tambm um permalink, uma representao de como essa pgina 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 mtodo Page#save_to para Page#save. Este mtodo ir salvar os arquivos sempre em um mesmo diretrio, usando o atributo permalink como nome do arquivo.
class Page attr_accessor :title, :content def self.load(filepath) YAML.load_file(filepath) end def initialize(title, content, permalink) @title = title @content = content @permalink = permalink end def save File.open("/tmp/#{permalink}.yml", "w") {|file| file.write to_yaml } end end
90
Em vez de deixar o diretrio onde os arquivos sero salvos no mtodo Page#save, vamos extrair esta informao para uma constante. Essa alterao permite, dentre outras coisas, expor esta informao na documentao RDoc.
class Page attr_accessor :title, :content ROOT = "/tmp" def self.load(filepath) YAML.load_file(filepath) end def initialize(title, content, permalink) @title = title @content = content @permalink = permalink end def filepath File.join(ROOT, "#{permalink}.yml") end def save File.open(filepath, "w") {|file| file.write to_yaml } end end
91
Aqui, estamos debnindo um mtodo na classe Singleton do objeto Person (lembre-se: tudo no Ruby objeto, inclusive classes). Como consequncia, isso ir debnir o mtodo Person.count. O efeito exatamente o mesmo que o cdigo abaixo, porm este mais objetivo e fcil de entender.
class Person def self.count @count ||= 0 end end
92
No Ruby 1.9, foi adicionado o mtodo Object#singleton_class, que apenas um atalho para a sintaxe class << self; self; end. Em verses mais antigas, voc pode injetar este mtodo com o cdigo abaixo.
class Object def singleton_class class << self; self; end end unless respond_to?(:singleton_class) end
Toda vez que injeta mtodos em um objeto, eles so adicionados como mtodos singleton. O que realmente importante saber que estes mtodos pertecem unicamente ao objeto em que foram debnidos, no afetando nenhum outro objeto da hieraquia.
string = "Hi there!" another_string = "Hi there!" 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 mtodo to_yo singleton, podemos utilizar o mtodo Object#singleton_methods.
string.singleton_methods #=> ["to_yo"]
93
another_string.singleton_methods #=> []
Voc tambm pode adicionar mtodos singleton de outras maneiras. Uma delas estendendo um objeto com um mdulo.
module Extension def sum self.reduce(:+) end end numbers = [1,2,3] numbers.extend Extension 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, no poder mais utilizar o mtodo Marshal.dump, j que a biblioteca Marshal[3] no suporta objetos com classes Singleton (ela ir lanar a exceo TypeError: singleton can't be dumped). A nica maneira de fazer isso e ainda poder utilizar o Marshal utilizando o mtodo Object#extend. Agora, sabendo que voc pode adicionar mtodos em um objeto com uma sintaxe como def object.some_method; end, perceba que exatamente isso que fazemos quando debnimos um mtodo em uma classe; a nica diferena que passamos o prprio self.
class Person def self.say_hello "Hello there!" end end Person.singleton_methods #=> ["say_hello"]
A biblioteca Marshal permite converter objetos Ruby em uma sequncia de bytes que podem ser restaurados por outros scripts, que podem reconstituir os objetos originais. 95
Com base nesse exemplo, possvel abrmar que mtodos de classe no e existem xistem no Rub Ruby y! Pelo menos no no sentido de mtodos estticos! O que acontece que estes mtodos pertencem a um objeto, que por acaso uma classe.
96
CAPTULO 1
Mdulos
Hmmm. Este contedo est sendo escrito e estar disponvel em breve.
97
CAPTULO 1
98
CAPTULO 1
Embora os trs mtodos atingem o objetivoconcatenar strings, existe uma diferena muito importante. O mtodo String#+ ir criar um novo objeto em memria, enquanto os mtodos String#<< e String#concat iro realocar a memria previamente utilizada.
hello = "Hello" ruby = "Ruby" hello2 = hello + " " + ruby puts "#{hello2}: #{hello2.object_id}" #=> Hello Ruby: 70310699719020 puts "#{hello}: #{hello.object_id}" #=> Hello: 70310699719100
99
hello << " " hello << ruby puts "#{hello}: #{hello.object_id}" #=> Hello Ruby: 70310699719100
Formatando strings
Voc j viu que no Ruby possvel utilizar expresses arbitrrias que podem ser interpoladas.
language = "Ruby" puts "#{language} is nice!" #=> Ruby is nice!
O Ruby tambm suporta outros tipos de formatao de strings que tambm permitem interpolar expresses. o caso dos mtodos String#%, Kernel#printf e Kernel#sprintf. Estes mtodos permitem ter maior controle no espaamento e formatao de nmeros. Alm disso, eles permitem desacoplar os valores que devem ser interpolados da string, facilitando, por exemplo, a internacionalizao de strings. A mesma string que foi interpolada acima pode ser formatada com os mtodos mencionados.
language = "Ruby" "%s is nice!" % language #=> retorna a string "Ruby is nice!" sprintf("%s is nice!", language) #=> retorna a string "Ruby is nice!" printf("%s is nice!\n", language) #=> exibe a mensagem "Ruby is nice!" e retorna nil
100
Embora seja fcil interpolar mltiplos valores com arrays, seu cdigo pode acabar confuso quando a lista de parmetros muito grande. Neste caso, prebra usar um hash. Note que esta funcionalidade foi introduzida no Ruby 1.9.
"language isadjective!" % {language: "Ruby", adjective: "nice"} #=> Ruby is nice!
Para saber mais sobre os formatos aceitos, acesse a documentao; basta executar o comando ri String#sprintf.
101
Note que os mtodos String#upcase e String#downcase iro retornar uma nova string. Para alterar a string existente, utilize os mtodos String#upcase! e String#downcase!.
text = "Ruby is nice!" puts text.object_id #=> 70347156470500 text.upcase! puts text.object_id #=> 70347156470500
Substrings
O Ruby permite pegar substrings (trechos de uma determinada string) com o mtodo String#[]. Em verses anteriores ao Ruby 1.9, o mtodo String#[] com um nmero inteiro retorna a representao numrica daquela posio, o que nem sempre recete a represeno de caracteres com mais de um byte. A maneira mais simples passar um nmero inteiro que identibca o caracter que ser retornado. Se o ndice for negativo, a posio ser movida partir do bm da string.
text = "Ruby is nice!" text[8] #=> n text[-1] #=> !
No Ruby, ndices de array e strings iniciam em zero. Voc tambm pode passar como segundo parmetro, a quantidade de caracteres que a substring deve ter.
text = "Ruby is nice!" text[8,4] #=> nice text[-5, 4] #=> nice
103
s vezes, pode ser mais conveniente passar o ndice inicial e bnal. Neste caso, voc pode passar um Range. Note que voc tambm pode utilizar intervalos com valores negativos.
text = "Ruby is nice!" text[0..3] #=> Ruby text[-5..-2] #=> nice
O mtodo String#[] tambm pode receber expresses regulares. Neste caso, o primeiro resultado ser retornado.
text = "Ruby is nice!" text[/ruby/i] #=> Ruby
Voc pode especibcar o nmero ou nome do grupo quando estiver usando uma expresso regular.
text = "ruby RUBY rUbY Ruby" text[/(ruby) (ruby) (ruby) (ruby)/i, 2] text[/(ruby) (?<upcase>ruby) (ruby) (ruby)/i, :upcase]
Por ltimo, se voc passar uma string qualquer e ela estiver presente, ela retornada. Caso contrrio, nil retornado.
text = "Ruby is nice!" text["Ruby"] #=> Ruby text["ruby"] #=> nil
104
Codificao
Uma das grandes mudanas introduzidas pelo Ruby 1.9 foi a codibcao das strings, que agora so verdadeiramente sequncias de caracteres que no precisam estar contidas na tabela de caracteres ASCII. Caracteres debnidos como UTF-8, por exemplo, que usam um nmero varivel de bytes para representar os caracteres, no possuem mais uma relao 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 contm caracteres multibytes, a quantidade de bytes usados para represent-la no ser a mesma do nmero de caracteres.
# -*- encoding: utf-8 -*text = "ruby legal!" puts text.size #=> 13 puts text.bytesize #=> 14
Perceba que a primeira linha debne qual o tipo de codibcao ser usada no arquivo. Esse comentrio chamado de magic comment e sem ele o Ruby no saberia decidir qual codibcao utilizar. O magic comment pode ser qualquer string que consiga ser casada pela seguinte expresso regular[2]. Magic comments devem ser a primeira linha do arquivo ou vir aps a linha de shebang.
/coding[:=] ?/
A codibcao de uma string baseada na codibcao do cdigo-fonte, mas no ser necessariamente a mesma.
# -*- encoding: utf-8 -*text = "hello".encode("iso-8859-1") puts text.encoding #=> ISO-8859-1
Nem todas as strings so compatveis entre diferentes codibcaes. Toda vez que voc tentar fazer a converso entre codibcaes que no so compatveis, uma exceo ser lanada.
# -*- encoding: utf-8 -*text = "".encode("iso-8859-1") #=> Encoding::UndefinedConversionError: U+221A from UTF-8 to ISO-8859-1
Neste caso voc pode forar a codibcao utilizando o mtodo String#encode, passando a estratgia de converso e qual string ser usada no lugar dos caracteres que no so reconhecidos.
# -*- encoding: utf-8 -*text = "Learn Ruby ".encode("iso-8859-1", :undef => :replace, :replace => "[DONE]") puts text #=> Learn Ruby [DONE]
Voc pode forar a codibcao de uma string com o mtodo String#force_encoding. Este mtodo no faz qualquer veribcao ou converso de caracteres; apenas muda a interpretao que o Ruby faz dos caracteres, mas sem alterar os bytes que representam a string. Para validar se os bytes daquela string so vlidos na codibcao escolhida, utilize o mtodo String#valid_encoding?.
text = "\xF4".force_encoding("utf-8") #=> Fora um valor binrio em UTF-8 puts text.valid_encoding? #=> false
106
Para acessar a representao nmerica de cada caracter de uma string, utilize o mtodo String#codepoints. Este mtodo espera um bloco, mas voc pode utilizar o mtodo 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 mtodo String#ord para pegar a representao nmerica de um nico caracter.
"".ord #=> 9786
Voc tambm pode acessar os bytes de uma string com o mtodo String#bytes. Assim como o mtodo 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 codibcaes disponveis no Ruby, utilize o mtodo Encoding.list. Algumas codibcaes possuem um alias, cuja listagem completa pode ser retornada pelo mtodo Encoding.aliases. O cdigo seguir far o output das codibcaes disponveis com seus respectivos aliases.
list = Encoding.list.collect do |encoding| info = encoding.name alias_name = Encoding.aliases.key(encoding.name) info << " (#{alias_name})" if alias_name info end puts list.sort_by(&:downcase)
O resultado do cdigo acima, quando executado no Ruby 1.9.3-p0, pode ser visto seguir (os nomes foram listados em colunas para no ocupar tanto espao).
ASCII-8BIT (BINARY) Big5 Big5-HKSCS (Big5-HKSCS:2008) Big5-UAO CP50220 CP50221 CP51932 CP850 (IBM850) CP852 CP855 CP949 CP950 CP951 Emacs-Mule EUC-JP (eucJP) EUC-KR (eucKR) EUC-TW (eucTW) eucJP-ms (euc-jp-ms) GB12345 GB18030 GB1988 GB2312 (EUC-CN) GBK (CP936) IBM437 (CP437) IBM737 (CP737) IBM775 (CP775) IBM852 IBM855 IBM857 (CP857) IBM860 (CP860) IBM861 (CP861) IBM862 (CP862) IBM863 (CP863) IBM864 (CP864) IBM865 (CP865) IBM866 (CP866) IBM869 (CP869) ISO-2022-JP (ISO2022-JP) ISO-2022-JP-2 (ISO2022-JP2) ISO-2022-JP-KDDI ISO-8859-1 (ISO8859-1) ISO-8859-10 (ISO8859-10) ISO-8859-11 (ISO8859-11) ISO-8859-13 (ISO8859-13) ISO-8859-14 (ISO8859-14) ISO-8859-15 (ISO8859-15) ISO-8859-16 (ISO8859-16) ISO-8859-2 (ISO8859-2) ISO-8859-3 (ISO8859-3) ISO-8859-4 (ISO8859-4) ISO-8859-5 (ISO8859-5) ISO-8859-6 (ISO8859-6) ISO-8859-7 (ISO8859-7) ISO-8859-8 (ISO8859-8) ISO-8859-9 (ISO8859-9) KOI8-R (CP878) KOI8-U macCentEuro macCroatian macCyrillic macGreek macIceland MacJapanese (MacJapan) macRoman
108
stateless-ISO-2022-JP-KDDI TIS-620 US-ASCII (ASCII) UTF-16 UTF-16BE (UCS-2BE) UTF-16LE UTF-32 UTF-32BE (UCS-4BE) UTF-32LE (UCS-4LE)
UTF-7 (CP65000) UTF-8 (CP65001) UTF8-DoCoMo UTF8-KDDI UTF8-MAC (UTF-8-MAC) UTF8-SoftBank Windows-1250 (CP1250) Windows-1251 (CP1251) Windows-1252 (CP1252)
Windows-1253 (CP1253) Windows-1254 (CP1254) Windows-1255 (CP1255) Windows-1256 (CP1256) Windows-1257 (CP1257) Windows-1258 (CP1258) Windows-31J (CP932) Windows-874 (CP874)
109
CAPTULO 1
Para efetuar divises, utilize o mtodo Numeric#/. Note que o resultado depende da classe do nmero que ser usado como divisor. No caso de nmeros inteiros, o resultado ser um nmero inteiro.
110
3 / 2 3 / 2.0 2 / 1.75
Nmeros absolutos
Para pegar o valor absoluto de um nmero, use o mtodo Numeric#abs.
1.abs -1.abs +1.2.abs -1.2.abs -1.2.magnitude #=> #=> #=> #-> #=> 1 - Mtodo Numeric#abs o mais utilizado 1 1.2 1.2 1.2
111
Felizmente a classe Integer implementa dois mtodos que permitem fazer a mesma veribcao, mas de uma forma muito mais elegante.
4.even? #=> true - um nmero par 5.odd? #=> true - um nmero mpar
O valor retornado ser uma string que pode ser convertida novamente em nmero com o mtodo String#to_i.
112
Fazendo arredondamentos
O Ruby possui dois mtodos que permitem arredondar nmeros de ponto cutuantes para inteiros. O mtodo Float#ceil ir arredondar para o prximo nmero inteiro maior ou igual o prprio nmero.
1.0.ceil #=> 1 1.1.ceil #=> 2 -1.1.ceil #=> -1
J o mtodo Float#floor ir arredondar para o nmero inteiro que for menor ou igual ao prprio nmero.
1.0.floor #=> 1 1.1.floor #=> 1 -1.1.floor #=> -2
O mtodo Float#round ir arredondar nmeros de ponto cutuante para o nmero de casas decimais que foram especibcadas. Por padro, a preciso zero e pode ser um nmero negativo (nesse caso, o nmero retornado ser um inteiro).
1.4.round 1.5.round 1.6.round -1.5.round #=> #=> #=> #=> 1 2 2 -2
113
1.234567.round(2) 1.234567.round(3) 1.234567.round(4) 1.234567.round(5) 12345.67.round(-5) 12345.67.round(-4) 12345.67.round(-3) 12345.67.round(-2) 12345.67.round(-1) 12345.67.round(0)
#=> #=> #=> #=> #=> #=> #=> #=> #=> #=>
114
CAPTULO 1
Outra caracterstica interessante que se nenhum valor padro for especibcado, uma exceo IndexError ser lanada.
items = [1, 2, 3] items.fetch(4) #=> IndexError: index 4 outside of array bounds: -3...3
115
Voc tambm pode inserir novos elementos em uma posio especbca com o mtodo Array#add_elements.rb#insert. Note que o ndice pode ser um valor negativo.
items = [1, 2, 3] items.insert(0, 4) #=> [4, 1, 2, 3] items.insert(-1, 5) #=> [4, 1, 2, 3, 5] #=> insere o nmero 4 na posio 0.
Removendo elementos
Para remover sempre o ltimo elemento de um array, use o mtodo Array#pop. O elemento removido ser o valor de retorno do mtodo.
items = [1, 2, 3] items.pop #=> 3 p items #=> [1, 2]
Para remover todos os elementos de um determinado valor, use o mtodo Array#delete. Caso este elemento no seja encontrado, nil ser retornado. Voc tambm pode fornecer um bloco, cujo valor ser retornado quando o elemento no for encontrado no array.
items = [1, 2, 3, "a", "b", "c"] items.delete("a") #=> a
117
items.delete("d") #=> nil items.delete("e") { "Y U MAD BRO?" } #=> Y U MAD BRO?
Para remover um elemento em um determinado ndice, use o mtodo Array#delete_at. Se o ndice especibcado no existir, nil ser retornado.
items = [1, 2, 3] items.delete_at(1) #=> 2 items.delete_at(3) #=> nil
Alternativamente, voc pode utilizar o mtodo Array#drop com o nmero de elementos que devem ser removidos. Note que o array original no 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 mtodo Array#clear.
items = [1, 2, 3] items.clear items #=> []
Filtrando elementos
Para gerar um novo array com todos os elementos que satisfaam uma determinada condio, use o mtodo 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 mtodos iteradores que no recebem um bloco iro retornar um objeto Enumerator.
items = [1, 2, 3, 4, 5] items.select {|n| n.odd?} #=> [1, 3, 5]
No Ruby 1.9, o exemplo acima pode ser ainda mais simples. partir desta verso do Ruby, o mtodo Symbol#to_proc permite criar um bloco que ir executar o mtodo identibcado pelo smbolo que foi passado.
items = [1, 2, 3, 4, 5] items.select(&:odd?) #=> [1, 3, 5]
Se voc precisa apenas do primeiro elemento que satisfaa a condio, utilize o mtodo Array#find.
items = [1, 2, 3, 4, 5] # recomendado items.find(&:odd?) #=> 1 # estranho items.select(&:odd?).first #=> 1
O mtodo Array#select possui uma contra-parte; trata-se do mtodo 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 mtodo Array#sort. A comparao 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 mtodos 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 tambm pode especibcar qual o tipo de valor deve ser usado na comparao. Isso pode ser feito com o mtodo Enumerator#sort_by. O mdulo Enumerator includo pela classe Array. Veja, por exemplo, como ordenar um array pelo tamanho das strings.
items = %w[Ruby Python PHP C JavaScript] items.sort_by(&:size) #=> ["C", "PHP", "Ruby", "Python", "JavaScript"]
Para pegar um nico elemento aleatriamente, utilize o mtodo Array#sample, disponvel partir do Ruby 1.9.
121
Buscando elementos
Para veribcar se um elemento existe em um determinado array, use o mtodo Array#include?. A comparao feita atravs do mtodo ==.
items = [1, "a"] items.include?(1) items.include?("a") items.include?(0) #=> true #=> true #=> false
Se um bloco for passado para o mtodo Array#index, o ndice ser cujo bloco retorne true.
items = [1, 2, 3, 1] items.index {|i| i == 3} #=> 2
Existe ainda o mtodo Array#rindex, que funciona como o mtodo Array#index mas atua nos elementos de forma reversa.
122
Iterando elementos
A classe Array inclui o mdulo Enumerator, que implementa diversos mtodos iteradores, mas outros destes mtodos so implementados pela prpria classe.
items = [1, 2, 3] items.each {|item| puts item} # # # # Output: 1 2 3
123
Para iterar em cada elemento e, ainda por cima, ter o ndice da iterao, use o mtodo Array#each_with_index.
items = %w[Ruby Python PHP] items.each_with_index {|item, i| puts "#{i} => #{item}"} # # # # Output: 0 => Ruby 1 => Python 2 => PHP
124
[1, 2, 3].collect {|i| i * 2} #=> [2, 4, 6] %w[Ruby Python PHP].collect {|i| i.downcase} #=> ["ruby", "python", "php"]
O mtodo Array#inject ou Array#reduce permite reduzir um array a um nico objeto. Se um valor inicial no for fornecido, o primeiro elemento do array ser utilizado.
items = [1, 2, 3, 4, 5] items.reduce(:+) items.reduce(10, :+) items.reduce(10) do |acc, i| acc += i if i.odd? acc end
#=> 15 - Inicia em 1 e soma todos os elementos #=> 25 - Inicia em 10 e soma todos os elementos #=> 19 - Inicia em 10 e soma apenas os nmeros mpares
Perceba como estamos utilizando o mtodo Symbol#to_proc para executar a soma dos elementos. Esta tcnica ser til apenas para operaes mais simples. Para executar operaes mais complexas, fornea um bloco, que ir receber o acumulador e o elemento da iterao.
125
CAPTULO 1
Para descobrir se um hash possui um determinado valor, use os mtodos Hash#value? e Hash#has_value?.
options = {color: "green", width: 150, height: 30} options.value?("green") #=> true options.has_value?("150") #=> false
Para pegar a primeira chave que debne um determinado valor, utilize o mtodo Hash#key. No Ruby 1.8, este mtodo se chama Hash#index.
options = {color: "green", width: 150, height: 30} options.key("green") #=> :color options.key("missing") #=> nil
Acessando o hash
O mtodo mais utilizado para acessar valores de hashes Hash#[]. Caso uma chave no tenha sido debnida, nil ser retornado por padro.
options = {name: "John Doe", age: 32} options[:name] #=> "John Doe" options[:age] #=> 32 options[:email] #=> nil
127
Voc tambm pode usar o mtodo Hash#fetch para acessar valores em um hash. Ele possui mais opes na hora de lidar com chaves inexistentes.
options = {name: "John Doe", age: 32} options.fetch(:name) #=> John Doe - Funciona como o mtodo Hash#[] options.fetch(:email) #=> Lana a exceo "KeyError: key not found: :email" options.fetch(:email, nil) #=> Retorna nil caso chave no exista options.fetch(:email) do |key| #=> Executa o bloco caso a chave no exista "Missing #{key}" end
Para extrair mais de um valor de um hash, use o mtodo Hash#values_at. Se uma determina chave no existir, nil ser retornado.
options = {name: "John Doe", age: 32} options.values_at(:name, :age, :email) #=> ["John Doe", 32, nil]
Filtrando elementos
Para gerar um novo hash com todos os elementos que satisfaam uma determinada condio, use o mtodo 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 mtodo retorna um array contendo as chaves e valores.
items = {:one => 1, :two => 2, :three => 3, :four => 4, :five => 5} items.select {|key, value| value.even?} #=> {:two=>2, :four=>4} - Ruby 1.9 #=> [[:four, 4], [:two, 2]] - Ruby 1.8
128
Assim como nos arrays, o mtodo 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} items.reject {|key, value| value.odd?} #=> {:two=>2, :four=>4}
Para remover diversas chaves caso uma condio no seja satisfeita, use o mtodo Hash#delete_if.
options = {one: 1, two: 2, three: 3, four: 4} options.delete_if do |key, value| value.odd? end
129
Alternativamente, voc pode usar o mtodo Hash#reject! que tambm ir modibcar o hash original.
options = {one: 1, two: 2, three: 3, four: 4} options.reject! do |key, value| value.odd? end options #=> {:two=>2, :four=>4}
Por bm, para remover todos os elementos de um hash, use o mtodo Hash#clear.
options = {a: 1, b: 2, c: 3} options.clear options #=> {}
Alternativamente, voc pode usar o mtodo Hash#inject ou Hash#reduce, passando um hash vazio como valor inicial.
options = {color: "red", language: "Ruby"} options.reduce({}) do |acc, (key, value)| acc.merge(key => value.upcase) end
Note que estamos explodindo a chave e valor em duas variveis diferentes, conforme vimos em Atribuio de variveis.
Iterando hashes
Para iterar em hashes, utilize o mtodo Hash#each. O bloco ir receber a chave e o valor em cada iterao.
options = {name: "John Doe", age: 32} options.each {|key, value| puts "#{key} => #{value}"} # Output: # name => John Doe # age => 32
Voc tambm pode iterar apenas nas chaves com o mtodo 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 mtodo Hash#each_value.
options = {name: "John Doe", age: 32} options.each_value {|value| puts value} # Output: # John Doe # 32
132
CAPTULO 1
Nenhum destes mtodos ir veribcar a existncia de arquivos e diretrios. Eles apenas permitem compor nomes, sem se importar com sua existncia e tipo (voc pode usar o mtodo File.basename em um diretrio, por exemplo.). O mtodo File.expand_path permite expandir caminhos partir do diretrio atual ou de um caminho raz.
Dir.chdir("/usr/bin") File.expand_path("ruby") #=> muda o diretrio atual para /usr/bin #=> /usr/bin/ruby
133
File.expand_path("~fnando/ruby") #=> /Users/fnando/ruby # Lana a exceo "ArgumentError: user fnando doesn't exist" # caso o diretrio `~/fnando` no exista.
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, no ser possvel efetuar nenhuma operao aps executar o mtodo File#close e, caso alguma tentativa de uso seja realizada, a exceo IOError: closed stream ser lanada.
file = File.new(__FILE__) #=> Abre o prprio arquivo para leitura content = file.read #=> Armazena o contedo do arquivo file.close #=> Encerra o uso de IO
Embora seja extremamente cexvel, este cuxo est sujeito falhas. Um desenvolvedor mais descuidado pode no encerrar o uso de IO. Para evitar que isto acontea, recomendado que voc use o mtodo File#open com um bloco. Este bloco ir receber o objeto File e, aps a execuo do bloco, ter o uso de IO automaticamente encerrado.
File.open(__FILE__) do |file| content = file.read end
134
Por padro, os mtodos File.open e File.new iro 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" File.open(path) File.open(path, File.open(path, File.open(path, File.open(path, File.open(path, File.open(path, #=> #=> #=> #=> #=> #=> #=> Usa a flag "r" Abre o arquivo Abre o arquivo Abre o arquivo Abre o arquivo Abre o arquivo Abre o arquivo (leitura) por padro. para leitura. para escrita, apagando o contedo existente. para escrita, mantendo o contedo existente. para escrita e leitura. para escrita e leitura. para leitura binria.
Veja um exemplo que mostra como ler uma URL, salvar seu contedo em um arquivo e depois exibir o contedo salvo.
require "open-uri" page = open("http://localhost/").read path = "/tmp/localhost.html" File.open(path, "w") do |file| file.write page end puts File.read(path)
135
Note que estamos usando o mtodo File#write para adicionar o contedo ao arquivo. Voc tambm poderia ter usado o mtodo File#<<. Para modibcar um arquivo existente, voc deve se certibcar que est usando a cag a. Caso contrrio, todo o contedo de seu arquivo ser substitudo. O exemplo abaixo mostra como normalizar as quebras de linha de um arquivo para \n.
path = "/tmp/editing.txt" File.open(path, "w") do |file| file << "This is the first line\r\n" file << "This is the second line\n" end content = File.read(path) File.open(path, "w") do |file| file.write content.gsub(/\r?\n/, "\n") end 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
137
file.close
Manipulando diretrios
Para listar todos os arquivos e diretrios, voc pode usar o mtodo Dir.entries. Note que este mtodo ir adicionar os caminhos . e .., que fazem referncia ao diretrio atual e diretrio-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 mtodo Dir.[] permite listar todas as entradas que atenderem um determinado padro. Este mtodo apenas um atalho para Dir.glob e aceita os mesmos padres. Para conhecer todos os padres aceitos, acesse a documentao com o comando ri Dir.glob.
# Arquivos no diretrio atual com a extenso .rb Dir["*.rb"] # Arquivos no diretrio atual que comeam com a letra e. Dir["e*"] # Arquivos com nomes "encoding" e/ou "open" que tenham a extenso .rb Dir["{encoding,open}.rb"] # Qualquer arquivo, de qualquer diretrio, partir do diretrio atual. Dir["./**/{.*,*.*}"] # Qualquer arquivo, de qualquer diretrio, partir do diretrio atual e que # tenha a extenso .rb Dir["./**/*.rb"]
138
Para criar um diretrio, use o mtodo Dir.mkdir. Se o diretrio j existir, a exceo Errno::EEXIST: File exists ser lanada.
Dir.mkdir("/tmp/sample")
Para remover um diretrio, use o mtodo Dir.delete, Dir.unlink ou Dir.rmdir. O diretrio precisa estar vazio. Caso o diretrio no esteja vazio, a exceo Errno::ENOTEMPTY: Directory not empty ser lanada.
Dir.delete("/tmp/sample")
Muitas dessas limitaes que a classe Dir impe podem ser removidas se voc carregar a biblioteca padro FileUtils. Ela possui uma srie de mtodos que permitem criar e remover diretrios, dentre outros mtodos muito teis.
require "fileutils" dir = "/tmp/a/b/c/d/e/f" FileUtils.mkdir_p(dir) File.open(File.join(dir, "sample.txt"), "w") do |file| file << "sample text" end FileUtils.rm_rf(dir)
139
Voc tambm pode veribcar se arquivos e diretrios podem ser lidos ou escritos.
path = "/usr/bin/ruby" File.executable?(path) File.readable?(path) #=> true #=> true
140
141
CAPTULO 1
142
CAPTULO 1
143
CAPTULO 1
Quando o mtodo Kernel#raise chamado sem nenhum argumento, uma exceo do tipo RuntimeError lanada. As duas excees seguir so equivalentes.
raise raise RuntimeError
Alternativamente, voc pode passar uma string para o mtodo Kernel#raise, que equivalente a lanar uma exceo com raise(RuntimeError, message).
raise "You've failed" raise RuntimeError, "You've failed"
Quando uma exceo lanada, a classe que foi utilizada para debnir a exceo 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 mtodo Exception.exception. Se uma instncia da classe Exception for fornecida, ento a exceo obtida com o mtodo Exception#exception. 2. O backtrace da exceo debnido. 3. A varivel global @$! debnida com a ltima exceo ativa. 4. A exceo lanada na pilha de execuo.
Capturando excees
O Ruby permite capturar excees com a clusula rescue.
begin raise "OH NOES!" rescue puts "An exception has been raised" end # An exception has been raised
Voc pode atribuir o objeto de erro a uma varivel, ou pode utilizar a varivel global $!.
begin raise "OH NOES!" rescue => error puts "error: #{error.message}" puts "$!: #{$!.message}" end
Por padro, apenas as classes que herdam de StandardError sero capturadas. Para capturar outras classes, voc pode passar uma lista de argumentos com as classes de exceo.
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 excees nativas do Ruby so lanadas como Exception, em vez de StandardError. o caso das classes LoadError e SyntaxError, lanadas quando um arquivo no consegue ser carregado com o mtodo Kernel#require e quando o Ruby encontra uma sintaxe invlida, respectivamente. Embora seja possvel capturar a classe base Exception, isso no uma boa ideia. Seja especbco quanto s e ex xcees que v voc oc quer captur capturar ar.. Voc pode ter diversas clusulas rescue que tratam excees diferentes de modos diferentes. Voc pode, inclusive, adicionar vrias classes de excees em uma nica clusula enquanto debne diferentes pontos de captura da exceo.
exceptions = [LoadError, RuntimeError, ScriptError, SyntaxError] 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 # # # # LoadError => OH NOES! I RuntimeError => Dammit! ScriptError => Y U MAD? SyntaxError => Y U MAD? couldn't load the specified file Something went wrong JUST GIMME SOME VALID RUBY! JUST GIMME SOME VALID RUBY!
O Ruby tambm possui uma clusula chamada ensure, que ser sempre executada independente de uma exceo ter sido lanada. Isso permite efetuar operaes que, em outras situaes, poderia deixar a sua aplicao 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 prtica fazer com que a clusula ensure execute apenas operaes simples e seguras, diminuindo a probabilidade de uma segunda exceo ser lanada, o que pode fazer com que as operaes de limpeza no sejam concludas, alm de tornar a depurao do erro mais complexa. Em algumas situaes, pode fazer sentido executar novamente um determinado bloco begin..end quando uma exceo lanada. a que entra a clusula retry. O exemplo abaixo mostra como tentar executar um determinado bloco trs 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 Trying Trying Sorry! #1 #2 #3 I couldn't make it work!
Dentro da clusula rescue pode existir qualquer tipo de cdigo. Voc pode, inclusive, lanar uma nova exceo ou, se preferir, a mesma exceo que foi capturada inicialmente. Isso permite criar, por exemplo, mecanismos que iro rastrear uma exceo lanada. O exemplo seguir mostra como enviar para um arquivo de log qualquer tipo de exceo, 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 # E, [2012-01-05T14:23:04.338482 #17431] ERROR -- : RuntimeError => OMG! Something went wrong! # E, [2012-01-05T14:23:04.339039 #17431] ERROR -- : /Users/fnando/exceptions/log.rb:6:in `bogus_method' # /Users/fnando/Sites/howto-books/ruby/code/exceptions/log.rb:10:in `<main>'
Lembre-se que as excees utilizam os mtodos Exception#exception e Exception.exception para debnir se uma nova exceo deve ser instnciada ou se a prpria instncia que deve ser utilizada. Embora excees possam ser utilizadas para controlar cuxos de aplicaes, este no o mecanismo mais recomendado para a tarefa. Neste caso, voc deve usar os mtodos Kernel#throw e Kernel#catch, criados para interromper rapidamente a execuo de loops aninhados e chamadas a mtodos sem necessariamente lanar uma exceo. O mtodo Kernel#throw permite lanar um smbolo que pode ser capturado com o mtodo Kernel#catch. Se voc executar o mtodo Kernel#throw com um argumento que ser usado como valor de retorno do mtodo Kernel#catch. O exemplo abaixo mostra como descobrir quantas vezes um loop teve que ser executado at que o nmero 42 fosse encontrado.
count = catch :done do tries = 0 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 execuo dos loops quando a letra n e o nmero 42 forem encontrados e, ainda assim, ter acesso aos valores encontrados.
number, letter = catch :done do ("a".."z").each do |l| (1..100).each do |n| throw(:done, [n, l]) if l == "n" && n == 42 end end end puts "number: #{number}" puts "letter: #{letter}" # number: 42 # letter: n
150
CAPTULO 1
Conhecendo o Rake
Hmmm. Este contedo est sendo escrito e estar disponvel em breve.
151
CAPTULO 1
152
Organizando o cdigo
Bibliotecas do Ruby seguem mais ou menos o mesmo padro. O cdigo-fonte normalmente bca em um diretrio lib. Caso sua biblioteca seja composta por mais de um arquivo, um diretrio com o mesmo nome da biblioteca deve ser criado no diretrio lib. Arquivos de teste devem ser criados no diretrio test. Organize seu diretrio de modo que ele seja parecido com a imagem seguir.
Para executar os testes ns iremos utilizar uma rake task. Crie o arquivo Rakefile na raz de seu projeto com o seguinte contedo.
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 disponveis, 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 esto sendo debnidos corretamente. Crie o arquivo test/temperature_test.rb. Esse arquivo ser responsvel por carregar o cdigo de nossa biblioteca, alm de debnir um caso de teste teste, que apenas uma classe que herda da classe Test::Unit::TestCase. Todos os testes so mtodos que comeam com as letras test, mas o padro utilizar test_.
Download temperature/test/temperature_test.rb
require "temperature" require "test/unit" class TemperatureTest < Test::Unit::TestCase def test_assign_attributes temp = Temperature.new(32, :celsius) assert_equal 32, temp.number assert_equal :celsius, temp.unit end end
154
Todas as veribcaes que seu teste far so chamadas de asseres asseres. No Test::Unit, voc ir usar os mtodos assert_* para garantir que seu cdigo est de acordo com uma determinada condio. Execute os testes com o comando rake test. Voc ver que este teste ir falhar com um erro. Isso acontece porque ainda no debnimos nossa classe Temperature.
$ rake test # Running tests: E Finished tests in 0.000755s, 1324.5033 tests/s, 0.0000 assertions/s. 1) Error: test_assign_attributes(TemperatureTest): NameError: uninitialized constant TemperatureTest::Temperature /Users/fnando/temperature/test/temperature_test.rb:7:in `test_assign_attributes' 1 tests, 0 assertions, 0 failures, 1 errors, 0 skips rake aborted!
Como a ideia evoluir o seu cdigo em pequenos passos, voc deve executar os testes a cada alterao. Faa 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' 1 tests, 0 assertions, 0 failures, 1 errors, 0 skips
Desta vez, o erro que temos : ArgumentError: wrong number of arguments(2 for 0). Isso signibca que nossa classe no espera receber nenhum argumento, mas estamos passando dois. Crie o mtodo Temperature#initialize de forma que ele receba estes dois parmetros.
Download temperature/lib/temperature.rb
Execute os testes mais uma vez. Agora, o teste ir falhar novamente com um erro. O motivo agora que no 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' 1 tests, 0 assertions, 0 failures, 1 errors, 0 skips
156
class Temperature attr_accessor :number, :unit def initialize(number, unit) end end
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>. 1 tests, 1 assertions, 1 failures, 0 errors, 0 skips
Agora, podemos ver que o motivo da falha exatamente o ponto que queremos testar. O atributo number no est sendo debnido. Para fazer isso, basta fazer a atribuio dos valores recebidos como argumentos para as variveis de instncia de mesmo nome.
Download temperature/lib/temperature.rb
class Temperature attr_accessor :number, :unit def initialize(number, unit) @number = number @unit = unit
157
end end
Execute os testes mais uma vez e voc ver que agora eles iro 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 prprio teste passasse. Esta tcnica chamada de Test-Driven Development. Nossa classe Temperature ter trs mtodos diferentes que iro permitir a converso entre as diferentes unidades. Estes mtodos sero 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 40C, que so equivalentes a 104F. Adicione o teste seguir ao arquivo temperature/test/ temperature_test.rb.
Download temperature/test/temperature_test.rb
Execute os testes. Eles devem falhar dizendo que o mtodo Temperature#to_fahrenheit no existe. Adicione este mtodo.
Download temperature/lib/temperature.rb
class Temperature attr_accessor :number, :unit def initialize(number, unit) @number = number
158
Execute os testes mais uma vez. Ele ir falhar dizendo que o valor esperado era diferente de nil. A frmula que faz a converso 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 initialize(number, unit) @number = number @unit = unit end def to_fahrenheit number * (9 / 5.0) + 32 end end
Perceba que estamos utilizando 5.0 para garantir que a diviso no seja feita entre dois nmeros inteiros. Execute os testes mais uma vez. Veja que a converso est sendo feita de forma correta. Um problema de nossa implementao que estamos assumindo que a temperatura inicial ser sempre em Celsius. Mas o que acontece se passarmos uma unidade diferente, que exige a aplicao de uma outra frmula? Obviamente, o clculo ser feito de forma incorreta.
159
Para evitar que nossos mtodos tenham diversas expresses condicionais que iro fazer o clculo de acordo com a unidade atual, ns iremos sempre utilizar o mtodo Temperature#to_celsius como base para os demais clculos. Dessa forma, teremos que converter apenas de Celsius para Fahrenheit e de Celsius para Kelvin. Altere a classe Temperature de modo que o mtodo que o mtodo Temperature#to_fahrenheit utilize o retorno do mtodo Temperature#to_celsius, em vez de Temperature#number.
Download temperature/lib/temperature.rb
class Temperature attr_accessor :number, :unit def initialize(number, unit) @number = number @unit = unit end def to_fahrenheit to_celsius * (9 / 5.0) + 32 end end
Ao executar os testes, eles iro falhar dizendo que o mtodo Temperature#to_celsius ainda no existe. Vamos adicionar alguns testes para garantir que este mtodo converta os valores corretamente. Primeiro, vamos escrever um teste para a converso de Celsius para Celsius.
Download temperature/test/temperature_test.rb
Execute os testes, que continuaro falhando. Altere o mtodo Temperature#to_celsius, adicionando um case.
Download temperature/lib/temperature.rb
class Temperature attr_accessor :number, :unit def initialize(number, unit) @number = number @unit = unit end def to_fahrenheit to_celsius * (9 / 5.0) + 32 end def to_celsius case unit when :celsius then number end end end
Execute os testes. Agora, ambos os testes iro passar. Adicione mais um teste para garantir que a converso de Fahrenheit para Celsius est sendo feita de forma correta.
Download temperature/test/temperature_test.rb
161
Adicione mais esta converso ao mtodo Temperature#to_celsius. A frmula que faz a converso de Fahrenheit para Celsius C = (F - 32) * (5 / 9).
Download temperature/lib/temperature.rb
class Temperature attr_accessor :number, :unit def initialize(number, unit) @number = number @unit = unit end 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
Execute os testes, que devem passar novamente. A ltima converso que precisa ser feita de Kelvin para Celsius. Esta frmula 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
Execute os testes, que devem falhar dizendo que o valor esperado diferente de nil. Agora, podemos implementar a converso de Kelvin para Celsius.
Download temperature/lib/temperature.rb
class Temperature attr_accessor :number, :unit def initialize(number, unit) @number = number @unit = unit end
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
Execute os testes. Eles devem passar mais uma vez! Muito bem! A converso de temperaturas em Fahrenheit e Kelvin em Celsius j podem ser realizadas, embora o contrrio ainda no seja verdade. Ainda falta implementarmos a converso para Kelvin. O prximo 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 mtodo Temperature#to_kelvin no foi debnido. Voc pode, ento, implementar este mtodo. A frmula de converso de Celsius para Kelvin K = C + 273.15.
164
Download temperature/lib/temperature.rb
class Temperature attr_accessor :number, :unit def initialize(number, unit) @number = number @unit = unit end 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
Parabns! 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" class TemperatureTest < Test::Unit::TestCase def test_assign_attributes temp = Temperature.new(32, :celsius) assert_equal 32, temp.number assert_equal :celsius, temp.unit end 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
Falha se boolean for nil ou false Falha se o bloco block for nil ou false Falha se o mtodo list#empty? retornar false Passa se expected for igual a actual quando o operador == usado Passa se o nmero de ponto cutuante expected for igual a actual dentro do delta esperado Calcula o delta com epsilon * min(expected, actual) e ento executa assert_in_delta com este valor Veribca se collection inclui o objeto element com o mtodo collection#include? Veribca se object uma instncia da classe klass Veribca se object do tipo da classe klass com o mtodo
object.kind_of?(klass)
167
Veribca se o envio da mensagem operator para object1 com o parmetro object2 retorna true Veribca se o bloco block lana uma das excees listadas quando executado
assert_raises(Exception, ..., &block) assert_raise(Exception, ..., &block) assert_respond_to(object, message) assert_same(expected, actual) assert_send(array)
Veribca se object responde a message com o mtodo object.respond_to? Veribca se expected igual a actual com o mtodo expected.equal? Veribca se o valor de retorno do envio da mensagem array[1] para o objeto array[0], com o restante do array como parmetros, true Veribca se a execuo do bloco block lana o smbolo expected com o mtodo throw Veribca expected diferente de actual quando o operador == usado Veribca se a string string no casa a expresso regular regexp Veribca se object no nil Veribca se expected diferente de actual com o mtodo expected.equal? Veribca se o bloco executado sem lanar as excees listadas Veribca se o bloco executado sem lanar o smbolo expected com o mtodo throw Sempre falha o teste com a mensagem message Faz com que o teste no seja executado Faz com que o teste sempre passe
assert_throws(expected, &block) assert_not_equal(expected, actual) assert_not_match(regexp, string) assert_not_nil(object) assert_not_same(expected, actual) assert_nothing_raised(Exception, ..., &block) assert_nothing_thrown(expected, &block) flunk(message = "Epic Fail!") skip(message = nil) pass
168
CAPTULO 1
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
Nesta especibcao, estamos debnindo uma srie de informaes: o nome da gem, debnido como simple_temperature a verso da gem, que vem do arquivo lib/temperature/version.rb e que ainda no foi criado.
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 descrio sobre o que nossa gem faz e que pode ser exibida com o comando gem list simple_temperature. os arquivos que compes a gem, que neste exemplo tudo o que est dentro do diretrio lib. os arquivos de teste, que neste exemplo tudo o que est dentro do diretrio test. Crie o arquivo lib/temperature/version.rb.
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 raz 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.
$ gem build simple_temperature.gemspec Successfully built RubyGem Name: simple_temperature Version: 0.1.0 File: simple_temperature-0.1.0.gem
Para instalar a sua gem localmente, use o comando gem install simple_temperature --local. Para ver mais detalhes sobre a instalao, use o parmetro --verbose.
171
Agora, vamos nos certibcar que est tudo funcionando. Voc pode fazer isso atravs do IRB.
$ irb >> require "temperature" => true >> Temperature.new(60, :celsius).to_fahrenheit => 140.0 >>
Note que no estamos carregando a gem simple_temperature. Toda vez que voc usa require, todos os arquivos das gems que esto no diretrio lib bcam disponveis no $LOAD_PATH. Por isso podemos simplesmente carregar o arquivo temperature.rb. Em uma situao normal, onde o nome do arquivo principal da gem recete o nome da prpria gem, isso no seria motivo de confuso.
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 publicao da gem pode demorar alguns minutos. Para veribcar se sua gem j est disponvel execute o comando gem list simple_temperature -rd.
$ gem list simple_temperature -rd *** REMOTE GEMS *** simple_temperature (0.1.0) Author: Nando Vieira Homepage: http://rubygems.org/gems/simple_temperature Convert temperature between different units.
Sua gem tambm pode ser acessada atravs do endereo https://rubygems.org/gems/simple_temperature. Aproveite que voc acessou a pgina de sua gem e atualize as outras informaes como endereo de onde as pessoas podero reportar bugs e visualizar o cdigofonte.
173
174
Copyright Hellobits & Nando Vieira. Todos os direitos reservados. Veja outras publicaes em http://howtocode.com.br.