Eustáquio Rangel

Desenvolvedor, pai, metalhead, ciclista

Novidades no Ruby 1.8.7

Publicado em Developer


Logotipo do Ruby

É engraçado que alguns dias atrás saíram as versões novas do Ruby (1.8.7) e do Rais (2.1) e até agora há muitos posts por aí detalhando o que mudou no Rails mas até agora só vi alguns posts listando algumas novidades do Ruby novo, geralmente listando os métodos novos sem muitos comentários. Até parece que o Rails nem é feito em Ruby. Vai entender.

Aqui nesse post vou mostrar algumas das novidades da versão 1.8.7, já dizendo que vou seguir a ordem em que elas aparecem no arquivo http://svn.ruby-lang.org/repos/ruby/tags/v1_8_7/NEWS, mas complementando com algumas explicações.

Houve muitas alterações em vários métodos que antes recebiam blocos e agora podem ser chamados sem o bloco, fazendo-os retornar um iterator para percorrer os seus elementos. Esse tipo de iterator é um objeto do tipo Enumerable::Enumerator, cuja descrição na documentação é:

A class which provides a method `each' to be used as an Enumerable object.

Vamos ver um exemplo utilizando Array#each:

1 array = [1,2,3]
2 enum  = array.each
3 loop do
4    puts enum.next
5 end

Como houve muitos métodos que assumiram esse comportamento, vou listar a grande maioria aqui e vamos detalhar alguns no decorrer desse artigo:

A classe Array teve o seu método flatten modificado, agora ele permite indicar qual o nível de "achatamento" desejado:

1 array = [1,2,[3,[4,[5]]]]
2 p array.flatten
3 p array.flatten(1)
4 p array.flatten(2)
[1, 2, 3, 4, 5]
[1, 2, 3, [4, [5]]]
[1, 2, 3, 4, [5]]

Os métodos index e rindex agora aceitam blocos também:

1 array = [1,2,3,4,5]
2 p array
3 puts array.index {|item| item%2==0}
4 puts array.rindex {|item| item%2==0}

[1, 2, 3, 4, 5]
1
3

Falando em index, temos o método novo find_index que aceita um parâmetro ou um bloco:

1 enum = (1..10) # eu usei arrays para todo lado mas pode ser uma Range etc.
2 puts enum.find_index(5)
3 puts enum.find_index {|item| item>7}

Um comportamento interessante pode ser visto em métodos como o map!, sem o bloco:

1 array = [1,2,3,4,5]
2 enum  = array.map!
3 puts "Vou processar o array na hora que der na telha."
4 puts "Quem sabe agora?"
5 p array
6 enum.each {|item| item*2}
7 p array
Vou processar o array na hora que der na telha.
Quem sabe agora?
[1, 2, 3, 4, 5]
[2, 4, 6, 8, 10]

Os métodos pop e shift agora permitem especificar quantos elementos processar:

1 array = [1,2,3,4,5]
2 p array.pop(2)
3 p array.shift(2)
4 p array
5 p array.pop
6 p array

[4, 5]
[1, 2]
[3]
3
[]

Lembram-se de alguns truques que utilizávamos para pegar um elemento aleatório de uma coleção? Pois agora Array ganhou o método choice que faz isso. Uma coisa interessante que eu notei é que aparentemente chamando rand antes de usar choice leva a melhores resultados aleatórios. Experimentem comentar as linhas 2 e 3 do código a seguir:

1 array = [1,2,3,4,5]
2 puts "jeito antigo"
3 puts array.instance_eval{self[rand * size]}
4 puts "jeito novo"
5 puts array.choice
6 puts array.choice
7 puts array.choice
jeito antigo
5
jeito novo
2
5
4

É interessante notar que o Rails já tem um método rand:

1 >> a = [1,2,3,4,5]
2 => [1, 2, 3, 4, 5]
3 >> a.rand
4 => 1
5 >> a.rand
6 => 1
7 >> a.rand
8 => 3

O Array também ganhou o método combination. Vamos supor que queremos combinar os Beatles em duplas ou trios. Podemos usar:

1 beatles = %w(John Paul George Ringo)
2 puts "combinação:"
3 p beatles.combination(2).to_a
4 p beatles.combination(3).to_a

O resultado será:

combinação:
[["John", "Paul"], ["John", "George"], ["John", "Ringo"], 
 ["Paul", "George"], ["Paul", "Ringo"], ["George", "Ringo"]]
[["John", "Paul", "George"], ["John", "Paul", "Ringo"], 
 ["John", "George", "Ringo"], ["Paul", "George", "Ringo"]]

Não é por nada não, mas fico com a primeira dupla. ;-) Podemos usar blocos também:

1 puts "com bloco agora:"
2 beatles = %w(John Paul George Ringo)
3 beatles.combination(2) {|c| puts c.join(" e ")}

O resultado será:

com bloco agora:
John e Paul
John e George
John e Ringo
Paul e George
Paul e Ringo
George e Ringo

Podemos criar "timinhos" repetindo os integrantes mas fazendo uma permutação deixando cada um de líder do seu time usando o método novo permutation, que pode ser utilizado com blocos também:

1 puts "permutação:"
2 beatles = %w(John Paul George Ringo)
3 p beatles.permutation(2).to_a
4 p beatles.permutation(3).to_a
5 
6 beatles.permutation(2) {|item| puts item.join(" e ")}

O resultado será:

permutação:
[["John", "Paul"], ["John", "George"], ["John", "Ringo"], 
 ["Paul", "John"], ["Paul", "George"], ["Paul", "Ringo"], 
 ["George", "John"], ["George", "Paul"], ["George", "Ringo"], 
 ["Ringo", "John"], ["Ringo", "Paul"], ["Ringo", "George"]]
[["John", "Paul", "George"], ["John", "Paul", "Ringo"], ["John", "George", "Paul"], 
 ["John", "George", "Ringo"], ["John", "Ringo", "Paul"], ["John", "Ringo", "George"], 
 ["Paul", "John", "George"], ["Paul", "John", "Ringo"], ["Paul", "George", "John"], 
 ["Paul", "George", "Ringo"], ["Paul", "Ringo", "John"], ["Paul", "Ringo", "George"], 
 ["George", "John", "Paul"], ["George", "John", "Ringo"], ["George", "Paul", "John"], 
 ["George", "Paul", "Ringo"], ["George", "Ringo", "John"], ["George", "Ringo", "Paul"], 
 ["Ringo", "John", "Paul"], ["Ringo", "John", "George"], ["Ringo", "Paul", "John"], 
 ["Ringo", "Paul", "George"], ["Ringo", "George", "John"], ["Ringo", "George", "Paul"]]
John e Paul
John e George
John e Ringo
Paul e John
Paul e George
Paul e Ringo
George e John
George e Paul
George e Ringo
Ringo e John
Ringo e Paul
Ringo e George

Podemos também fazer uma combinação dos elementos com o método novo product, que retorna todas as combinações da coleção da esquerda com a da direita:

1 p %w(John Paul).product(%w(George Ringo))
[["John", "George"], ["John", "Ringo"], ["Paul", "George"], ["Paul", "Ringo"]]

O método novo cycle percorre a coleção quantas vezes forem especificadas:

1 beatles = %w(John Paul George Ringo)
2 beatles.cycle(2) {|item| puts item}

O método novo drop permite excluir elementos da coleção, com uma quantidade específica indicada ou utilizando um bloco:

1 beatles = %w(John Paul George Ringo)
2 p beatles.drop(2)
3 p beatles.drop_while {|item| item.length==4}
["George", "Ringo"]
["George", "Ringo"]

Os métodos novos take e take_while retornam, respectivamente, o número de elementos especificado na coleção no primeiro caso e no segundo os elementos até que a condição não seja mais atendida:

1 beatles = %w(John Paul George Ringo)
2 p beatles.take(2)
3 p beatles.take_while {|item| item.length<=4}
["John", "Paul"]
["John", "Paul"]

O que fazíamos com sort_by combinado rand agora podemos fazer com shuffle e shuffle! e reordenar uma coleção randomicamente:

1 beatles = %w(John Paul George Ringo)
2 p beatles.sort_by { rand } # jeito antigo
3 p beatles.shuffle
4 beatles.shuffle!
5 p beatles
["Ringo", "Paul", "John", "George"]
["George", "Paul", "Ringo", "John"]
["George", "John", "Ringo", "Paul"]

Os métodos each_slice e each_cons agora estão incorporados direto (sem precisar fazer require "enumerator" como no 1.8.6):

1 enum = (1..10)
2 enum.each_slice(3) do |slice|
3    p slice
4 end
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
[10]

Ganhamos um método Symbol#to_proc de maneira similar ao que temos no Rails:

1 enum = (1..10)
2 puts enum.inject(&:+)
3 puts enum.inject(&:*)

O método novo group_by agrupa nossa coleção de acordo com os critérios do bloco:

1 beatles = %w(John Paul George Ringo)
2 p beatles.group_by {|item| item.length}
3 p beatles.group_by {|item| item.length%2==0}
{5=>["Ringo"], 6=>["George"], 4=>["John", "Paul"]}
{false=>["Ringo"], true=>["John", "Paul", "George"]}

Podemos usar alguns métodos novos para encontrar os elementos maiores e menores de acordo com os nossos critérios:

1 beatles = %w(John Paul George Ringo)
2 # encontrando o maior nome
3 p beatles.max_by {|item| item.length}
4 # encontrando o menor nome
5 p beatles.min_by {|item| item.length}

6 # encontrando o menor e o maior
7 p beatles.minmax {|a,b| a.length <=> b.length}
8 # encurtando um pouco mais para fazer a mesma coisa ...
9 p beatles.minmax_by {|item| item.length}
"George"
"John"
["John", "George"]
["John", "George"]

Podemos verificar se nenhum ou apenas um elemento da coleção atende nossos critérios:

1 beatles = %w(John Paul George Ringo)
2 puts beatles.none? {|item| item.length>10}
3 puts beatles.one? {|item| item.length==6}

Finalmente ganhamos aqueles metodozinhos que sempre implementavamos quando íamos fazer uma apresentação e mostrar classes abertas, odd e even:


1 puts 1.odd?
2 puts 2.even?

Como já existia o succ, faltava o pred:

1 puts 1.pred
2 puts 1.succ

Os métodos upto, downto, step e times entraram todos na dança do Enumerable::Enumerator:

 1 upto_e   = 1.upto(3)
 2 downto_e = 10.downto(7)
 3 times_e  = 5.times
 4 step_e   = 100.step(120,2)
 5 
 6 3.times do
 7    puts upto_e.next
 8    puts downto_e.next
 9    puts times_e.next
10    puts step_e.next
11 end
1
10
0
100
2
9
1
102
3
8
2
104

Agora um método bem importante para já irmos adaptando o nosso código para a versão 1.9: o método ord. Vamos dar uma olhada na versão atual, como é o comportamento com caracteres:

[taq@~]irb
irb(main):001:0> "taq"[1]
=> 97

Porém, na versão 1.9:

[taq@~/code/ruby]irb1.9 
irb(main):001:0> "taq"[1]
=> "a"

Aí entra todo aquele esquema de basear em caracteres blá blá blá, mas o X da coisa é q se você já quer ir adaptando onde precisa pegar o valor do caracter, pode usar ord:

[taq@]irb
irb(main):001:0> "taq"[1].ord
=> 97
[taq@~/code/ruby]irb1.9 
irb(main):001:0> "taq"[1].ord
=> 97
[taq@~/code/ruby]irb irb(main):001:0> ?a => 97 irb(main):002:0> ?a.ord => 97

Ganhamos uma bela economia de class << self; self; end com os novos métodos class_exec, module_exec e instance_exec (eba!):

 1 class HelloWorld
 2    def hello
 3       print "hello, "
 4    end
 5 end
 6 
 7 HelloWorld.class_exec do
 8    def world
 9       print "world!\n"
10    end
11 end
12 
13 h = HelloWorld.new
14 h.hello; h.world
15 
16 module OlaMundo
17    def ola
18       print "olá, "
19    end
20 end
21 
22 OlaMundo.module_exec do
23    def mundo
24       print "mundo!\n"
25    end
26 end
27 
28 class HelloWorld
29    include OlaMundo
30 end
31 h.ola; h.mundo
32 
33 h.instance_exec {
34    def beleza
35       puts "beleza?"
36    end
37 }
38 h.ola; h.mundo; h.beleza
hello, world!
olá, mundo!
olá, mundo!
beleza?

Falando em métodos, ganhamos os métodos name, owner e receiver, e de quebra o __method__ que contém o nome do método corrente evitando ter que fazer coisas desse tipo:

1 def oi
2    puts "Estou no método #{__method__}."
3 end
4 m = method(:oi)
5 puts m.name
6 puts m.owner
7 puts m.receiver
8 oi

E olhem o tap aí! Eu havia mencionado ele antes, como um recurso do 1.9 mas a galerinha já deu uma adiantada no bicho:

1 p [1,2,3,4,5].select {|item| item%2==0}.tap {|item| 
puts "encontrei #{item.size} números pares que vou multiplicar por 2" }.map {|item| item*2}

Nem o ObjectSpace escapou:

1 enum = ObjectSpace.each_object(String)
2 puts "#{enum.count} Strings criadas:"
3 puts enum.next
4 puts enum.next
5 puts enum.next

Expressões regulares ganharam o método union para concatenar os padrões enviados, seja por String ou por Regexps:

 1 regexp = Regexp.union(%w(dog cat))
 2 p regexp
 3 puts "dog" =~ regexp
 4 puts "cat" =~ regexp
 5 puts "Cat" =~ regexp
 6 puts "rat" =~ regexp
 7 
 8 regexp = Regexp.union(/dog/,/cat/i)
 9 p regexp
10 puts "dog" =~ regexp
11 puts "cat" =~ regexp
12 puts "Cat" =~ regexp
13 puts "rat" =~ regexp

Falando em expressões regulares, a String ganhou dois métodos novos, start_with? e end_with?, mas eu particularmente prefiro usar expressões regulares para fazer a mesma coisa:

1 puts "teste".start_with?("t")
2 puts "teste".end_with?("e")
3 # mesma coisa 
4 puts "teste" =~ /^t/
5 puts "teste" =~ /e$/

A String também ganhou os métodos novos partition e rpartition, além de uma opção para o upto para especificar se o último valor deve ser incluído:

1 p "teste".partition("e")
2 
3 p "teste".rpartition("t")
4 "a1".upto("a5") {|item| puts item}
5 "a1".upto("a5",true) {|item| puts item}
["t", "e", "ste"]
["tes", "t", "e"]
a1
a2
a3
a4
a5
a1
a2
a3
a4

A classe IPAddr ganhou os métodos succ, <=> (navinha!) e to_range. Vamos dar uma olhada especificando um CIDR que nos permita ver os IP's da range aqui:

 1 require "ipaddr"
 2 
 3 ip1 = IPAddr.new("192.168.0.1")
 4 ip2 = ip1.succ
 5 p ip1
 6 p ip2
 7 
 8 puts "ip1 < ip2: #{ip1 < ip2}"
 9 puts "ip2 < ip1: #{ip2 < ip1}"
10 puts "ip1 <=> ip2: #{ip1 <=> ip2}"
11 
12 ip = IPAddr.new("192.168.0.1/29")
13 p ip.to_range
14 p ip.to_range.to_a.map {|item| item.to_s}
#<IPAddr: IPv4:192.168.0.1/255.255.255.255>
#<IPAddr: IPv4:192.168.0.2/255.255.255.255>
ip1 < ip2: true
ip2 < ip1: false
ip1 <=> ip2: -1
#<IPAddr: IPv4:192.168.0.0/255.255.255.248>..
#<IPAddr: IPv4:192.168.0.7/255.255.255.248>
["192.168.0.0", "192.168.0.1", "192.168.0.2", "192.168.0.3", 
 "192.168.0.4", "192.168.0.5", "192.168.0.6", "192.168.0.7"]

A classe Net::SMTP ganhou suporte SSL. Vamos mandar um email pelo Gmail:

 1 require "net/smtp"
 2 
 3 user  = "eustaquiorangel"
 4 pass  = "saiforajacare" # vocês tão achando que é essa mesmo? ;-) 
 5 
 6 mail  = <<FIM
 7 From: Eustáquio Rangel <eustaquiorangel@gmail.com>
 8 Subject: Loser!
 9 
10 Tá no Yahoo! ainda?
11 FIM
12 
13 smtp  = Net::SMTP.new("smtp.gmail.com",465)
14 smtp.enable_ssl(OpenSSL::SSL::SSLContext.new)
15 smtp.start("localhost.localdomain",user,pass,:login)
16 smtp.send_message(mail,"eustaquiorangel@gmail.com","eustaquiorangel@yahoo.com")
17 smtp.finish

Os arquivos temporários podem ter sufixos agora, e temos um método para informar qual o diretório de arquivos temporários da plataforma:

1 require "tempfile"
2 
3 puts Dir.tmpdir
4 temp = Tempfile.new(["bla",".txt"])
5 puts temp.path

E, finalmente, o parse de um objeto Date agora segue o formato padrão de YYYY-MM-DD e não mais MM-DD-YYYY como anteriormente:

1 require "date"
2 
3 d = Date.parse("2008-06-04")
4 puts d.strftime("%d/%m/%Y")
04/06/2008

UFA! É isso. Há mais novidades, mais eu procurei concentrar em algumas que achei mais interessantes. Para conhecer as outras, por favor deêm uma olhada na URL que eu informei no começo do artigo. Somente 0.3 para a 1.9! :-)




Comentários

Comentários fechados.

  • TaQ

    Mereghost, fazia tempo que não ouvia essa do Jack. ;-)
    Em relação à "quebradeira", o bom é que a turminha trabalha rápido para consertar os problemas que aparecem. Eu nem postei nada nesse sentido, mas se em uns 15 dias ainda continuar algum problema mais chato eu faço um "catadão" e publico aqui.
    E vale aquela regra: nunca dê uma de doido e bote uma versão nova saindo do forno em um ambiente de produção! ;-)
    []'s!

  • Mereghost

    Fala, grande TaQ Nicholson!

    Então, alguns detalhes do release do 1.8.7 a mais: quebrou algumas coisas. Entre elas o rails. O problema é no cgi.rb mas que foi consertado do tag 1.8.7.7 ou no stable snapshot para quem gosta de "arriscar" (arriscar e stable não ficam bem na mesma frase) mais.
    O require_gem foi de fato removido o que pode gerar alguns problemas com algumas gems.
    Alguns plugins *parecem* quebrar também, como o will_paginate, mas pode ser algum problema da view que eu estava testando.

  • Jackson Pires

    Tranquilo!

  • Luiz Rocha

    "Não é por nada não, mas fico com a primeira dupla." - John e Paul? Sério? Mas o melhor Beatle é o George, todo mundo sabe isso.

    Fora isso, sensacional!

  • Carlos J. Souza

    Muito bom o artigo TaQ. Parabéns.

    Abraco.

  • TaQ

    Jackson, putz, eu "limei" ali aqueles pontos pois o comentário ficou muito grande, desculpe ... mas realmente eu deixei aqueles sem respostas pois algumas são fáceis de deduzir e algumas outras muito compridas. E também como forma de vocês ficarem curiosos e instalarem o 1.8.7 para ver as respostas! :-)
    Abraço!

  • Jackson Pires

    Adorei as novidades!

    Só não entendi pq alguns ítens ficaram sem a resposta... ou foi proposital? :(

    Estou enviando os ítens para se for o caso você completar..

    Abraço!

    vlw!

  • TaQ

    Rafael, obrigado, corrigi o erro! Isso que dá ficar escrevendo de madrugada ehehe. :-)

  • Rafael Ferreira Silva

    Muito bom o artigo, só um errinho em: "Até parece que o Rails nem é feito em Rails. Vai entender." acho que você quis dizer "Até parece que o Rails nem é feito em Ruby. Vai entender."

    Abração!

Artigos anteriores