You are on page 1of 152

Tutorial de Ruby

Eustaquio TaQ Rangel (eustaquiorangel@gmail.com) 28 de janeiro de 2012

Tutorial de Ruby de Eustquio Rangel licenciado sob uma Licena Creative Commons Atribuio-Uso no-comercial-Compartilhamento pela mesma licena 3.0 Unported..

Para o pessoal que se faz de desentendido e nem consultam a URL da licena, aqui vai o texto completo da mesma (no momento da edio desse documento - ei, revista espertinha, a prxima no passa em branco): Atribuio-Uso no-comercial-Compartilhamento pela mesma licena 3.0 Unported (CC BY-NC-SA 3.0) Voc tem a liberdade de: C OMPARTILHAR copiar, distribuir e transmitir a obra. R EMIXAR criar obras derivadas. Sob as seguintes condies: ATRIBUIO Voc deve creditar a obra da forma especicada pelo autor ou licenciante (mas no de maneira que sugira que estes concedem qualquer aval a voc ou ao seu uso da obra). U SO NO - COMERCIAL Voc no pode usar esta obra para ns comerciais. C OMPARTILHAMENTO PELA MESMA LICENA Se voc alterar, transformar ou criar em cima desta obra, voc poder distribuir a obra resultante apenas sob a mesma licena, ou sob uma licena similar presente. Ficando claro que: R ENNCIA Qualquer das condies acima pode ser renunciada se voc obtiver permisso do titular dos direitos autorais. D OMNIO P BLICO Onde a obra ou qualquer de seus elementos estiver em domnio pblico sob o direito aplicvel, esta condio no , de maneira alguma, afetada pela licena. O UTROS D IREITOS Os seguintes direitos no so, de maneira alguma, afetados pela licena: Limitaes e excees aos direitos autorais ou quaisquer usos livres aplicveis; Os direitos morais do autor; Direitos que outras pessoas podem ter sobre a obra ou sobre a utilizao da obra, tais como direitos de imagem ou privacidade.
A Esse documento foi produzido usando LTEX.

A verso mais recente desse documento pode ser encontrada no site do autor.

ii

Sumrio
1 Ruby 1.1 O que Ruby . . . . . . . . . . . . . . . . . . 1.2 Instalando Ruby . . . . . . . . . . . . . . . . 1.2.1 Instalando via RVM . . . . . . . . . . 1.2.2 Instalando um interpretador Ruby . 1.3 Bsico da linguagem . . . . . . . . . . . . . . 1.3.1 Tipagem dinmica . . . . . . . . . . . 1.3.2 Tipagem forte . . . . . . . . . . . . . . 1.3.3 Tipos bsicos . . . . . . . . . . . . . . 1.3.4 Fixnums . . . . . . . . . . . . . . . . . 1.3.5 Bignums . . . . . . . . . . . . . . . . . 1.3.6 Ponto utuante . . . . . . . . . . . . . 1.3.7 Booleanos . . . . . . . . . . . . . . . . 1.3.8 Nulos . . . . . . . . . . . . . . . . . . . 1.3.9 Strings . . . . . . . . . . . . . . . . . . Substrings . . . . . . . . . . . . . . . Concatenando Strings . . . . . . . . . Encoding . . . . . . . . . . . . . . . . Vriaveis so referncias na memria Congelando objetos . . . . . . . . . . 1.3.10Smbolos . . . . . . . . . . . . . . . . . 1.3.11Expresses regulares . . . . . . . . . Grupos . . . . . . . . . . . . . . . . . . Grupos nomeados . . . . . . . . . . . 1.3.12Arrays . . . . . . . . . . . . . . . . . . 1.3.13Duck Typing . . . . . . . . . . . . . . 1.3.14Ranges . . . . . . . . . . . . . . . . . . 1.3.15Hashes . . . . . . . . . . . . . . . . . . 1.3.16Blocos de cdigo . . . . . . . . . . . . 1.3.17Converses de tipos . . . . . . . . . . 1.3.18Converses de bases . . . . . . . . . . 1.3.19Tratamento de excees . . . . . . . . 1.3.20Estruturas de controle . . . . . . . . Condicionais . . . . . . . . . . . . . . if . . . . . . . . . . . . . . . . . . . . . unless . . . . . . . . . . . . . . . . . . case . . . . . . . . . . . . . . . . . . . Loops . . . . . . . . . . . . . . . . . . while . . . . . . . . . . . . . . . . . . . for . . . . . . . . . . . . . . . . . . . . until . . . . . . . . . . . . . . . . . . . Operadores lgicos . . . . . . . . . . . 1.3.21Procs e lambdas . . . . . . . . . . . . 1.3.22Iteradores . . . . . . . . . . . . . . . . 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1 2 2 3 5 5 5 6 6 8 8 9 9 9 10 11 11 12 12 13 13 14 14 15 17 18 18 20 20 21 21 23 23 23 24 25 26 27 27 28 28 29 32

SUMRIO Selecionando elementos . . . . . . . . . . . . . . . . . . . . . . . . . Selecionando os elementos que no atendem uma condio . . . . Processando e alterando os elementos . . . . . . . . . . . . . . . . Detectando condio em todos os elementos . . . . . . . . . . . . . Detectando se algum elemento atende uma condio . . . . . . . . Detectar e retornar o primeiro elemento que atende uma condio Detectando os valores mximo e mnimo . . . . . . . . . . . . . . . Acumulando os elementos . . . . . . . . . . . . . . . . . . . . . . . Dividir a coleo em dois Arrays obedecendo uma condio . . . . Percorrendo os elementos com os ndices . . . . . . . . . . . . . . . Ordenando uma coleo . . . . . . . . . . . . . . . . . . . . . . . . Combinando elementos . . . . . . . . . . . . . . . . . . . . . . . . . Percorrendo valores para cima e para baixo . . . . . . . . . . . . . Inspecionando no encadeamento de mtodos . . . . . . . . . . . . 1.3.23Mtodos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Retornando valores . . . . . . . . . . . . . . . . . . . . . . . . . . . Enviando valores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Classes e objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.1 Classes abertas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.2 Aliases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.3 Inserindo e removendo mtodos . . . . . . . . . . . . . . . . . . . . 1.4.4 Metaclasses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.5 Variveis de classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . Herana . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.6 Duplicando de modo raso e profundo . . . . . . . . . . . . . . . . . 1.4.7 Brincando com mtodos dinmicos e hooks . . . . . . . . . . . . . 1.4.8 Manipulando mtodos que se parecem com operadores . . . . . . 1.4.9 Closures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Mdulos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.5.1 Instalando pacotes novos atravs do RubyGems . . . . . . . . . . Threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.6.1 Fibers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.6.2 Continuations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.6.3 Processos em paralelo . . . . . . . . . . . . . . . . . . . . . . . . . . 1.6.4 Benchmarks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Entrada e sada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.7.1 Arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.7.2 Arquivos Zip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.7.3 XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.7.4 XSLT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.7.5 YAML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.7.6 Comunicando com a rede . . . . . . . . . . . . . . . . . . . . . . . . TCP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . UDP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . SMTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . FTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . POP3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . HTTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . HTTPS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . SSH . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . XML-RPC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . JRuby . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.8.1 Utilizando classes do Java de dentro do Ruby . . . . . . . . . . . . 1.8.2 Usando classes do Ruby dentro do Java . . . . . . . . . . . . . . . Banco de dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.9.1 Abrindo a conexo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 33 34 34 34 34 35 35 36 36 36 37 38 38 39 39 39 45 49 50 51 51 54 57 59 61 63 67 68 73 78 85 89 90 93 95 95 96 98 100 101 103 103 105 106 106 107 108 109 109 110 115 115 117 118 118

1.4

1.5 1.6

1.7

1.8

1.9

SUMRIO 1.9.2 Consultas que no retornam dados 1.9.3 Consultas que retornam dados . . . 1.9.4 Comandos preparados . . . . . . . . 1.9.5 Metadados . . . . . . . . . . . . . . 1.9.6 ActiveRecord . . . . . . . . . . . . . 1.10 Escrevendo extenses para Ruby, em C . 1.11 Garbage collector . . . . . . . . . . . . . . 1.12 Unit testing . . . . . . . . . . . . . . . . . . 1.13 Criando Gems . . . . . . . . . . . . . . . . 1.13.1Testando a gem . . . . . . . . . . . . 1.13.2Construindo a gem . . . . . . . . . . 1.13.3Publicando a gem . . . . . . . . . . 1.13.4Extraindo uma gem . . . . . . . . . 1.14 Gerando documentao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118 120 121 121 121 124 128 130 133 135 136 136 137 138

Captulo 1

Ruby
1.1 O que Ruby
SANDO

U "Ruby uma pequena descrio encontrada na web, podemos dizer que: de tipagem uma linguagem de programao interpretada multiparadigma,
dinmica e forte, com gerenciamento de memria automtico, originalmente planejada e desenvolvida no Japo em 1995, por Yukihiro "Matz"Matsumoto, para ser usada como linguagem de script. Matz queria uma linguagem de script que fosse mais poderosa do que Perl, e mais orientada a objetos do que Python. Ruby suporta programao funcional, orientada a objetos, imperativa e reexiva. Foi inspirada principalmente por Python, Perl, Smalltalk, Eiffel, Ada e Lisp, sendo muito similar em vrios aspectos a Python. A implementao 1.8.7 padro escrita em C, como uma linguagem de programao de nico passe. No h qualquer especicao da linguagem, assim a implementao original considerada de fato uma referncia. Atualmente, h vrias implementaes alternativas da linguagem, incluindo YARV, JRuby, Rubinius, IronRuby, MacRuby e HotRuby, cada qual com uma abordagem diferente, com IronRuby, JRuby e MacRuby fornecendo compilao Just-In-Time e, JRuby e MacRuby tambm fornecendo compilao Ahead-Of-Time. A srie 1.9 usa YARV (Yet Another Ruby VirtualMachine), como tambm a 2.0 (em desenvolvimento), substituindo a lenta Ruby MRI (Matzs Ruby Interpreter)." Fonte: Wikipedia

CAPTULO 1. RUBY

1.2
1.2.1
I

Instalando Ruby
Instalando via RVM

nstalao pode ser vrias A pacotes especcose feita de sistemamaneiras, em diferentes sistemas operacionais, desde para o operacional, scripts de congurao ou atravs do download, compilao instalao do cdigo-fonte. Vamos instalar Ruby utilizando a RVM - Ruby Version Manager, que uma ferramenta de linha de comando que nos permite instalar, gerenciar e trabalhar com mltiplos ambientes Ruby, de interpretadores at conjunto de gems. A instalao da RVM feita em ambientes que tem o shell bash, sendo necessrio apenas abrir um terminal rodando esse shell e executar: bash < <(curl -s https://rvm.beginrescueend.com/install/rvm) Isso ir gerar um diretrio em nosso home (abreviado a partir de agora como ) parecida com essa: $ ls .rvm total 92K drwxr-xr-x 18 taq taq 4,0K 2010-10-14 23:15 . drwxr-xr-x 170 taq taq 12K 2011-06-20 16:38 .. drwxr-xr-x 2 taq taq 4,0K 2010-10-21 15:30 archives drwxr-xr-x 2 taq taq 4,0K 2010-12-14 15:53 bin drwxr-xr-x 2 taq taq 4,0K 2010-12-14 15:53 config drwxr-xr-x 2 taq taq 4,0K 2010-12-14 15:53 environments drwxr-xr-x 2 taq taq 4,0K 2010-10-14 23:05 examples drwxr-xr-x 8 taq taq 4,0K 2010-11-06 13:35 gems drwxr-xr-x 6 taq taq 4,0K 2010-10-14 23:05 gemsets drwxr-xr-x 2 taq taq 4,0K 2010-10-14 23:05 help drwxr-xr-x 3 taq taq 4,0K 2010-10-14 23:05 lib -rw-r--r-1 taq taq 1,1K 2010-10-21 15:30 LICENCE drwxr-xr-x 5 taq taq 4,0K 2010-11-06 13:35 log drwxr-xr-x 5 taq taq 4,0K 2010-10-14 23:05 patches -rw-r--r-1 taq taq 6,6K 2010-10-21 15:30 README drwxr-xr-x 4 taq taq 4,0K 2010-12-14 15:53 rubies drwxr-xr-x 3 taq taq 4,0K 2010-10-21 15:30 scripts drwxr-xr-x 7 taq taq 4,0K 2010-10-21 15:30 src drwxr-xr-x 2 taq taq 4,0K 2010-10-14 23:05 tmp drwxr-xr-x 6 taq taq 4,0K 2010-12-14 15:53 wrappers e tambm com o diretrio de gems: $ ls .gem total 28K drwxr-xr-x 4 taq taq 4,0K drwxr-xr-x 170 taq taq 12K -rw-r--r-1 taq taq 56 drwxr-xr-x 3 taq taq 4,0K drwxr-xr-x 6 taq taq 4,0K

2010-05-18 2011-06-20 2010-05-18 2009-08-08 2010-11-16

16:55 16:38 16:55 19:26 00:14

. .. credentials ruby specs

Aps a instalao, temos que inserir o comando rvm no path, adicionando no nal do arquivo /.bash_profile:

echo [[ -s "$HOME/.rvm/scripts/rvm" ]] && . "$HOME/.rvm/scripts/rvm" # Load RVM functio >> ~/.bash_profile 2

1.2. INSTALANDO RUBY Agora podemos vericar que o rvm se encontra instalado: $ type rvm | head -n1 rvm uma funo $ rvm -v rvm 1.0.15 by Wayne E. Seguin (wayneeseguin@gmail.com) [http://rvm.beginrescueend.com/] E devemos vericar quais so as notas para o ambiente que estamos instalando a RVM, que no caso do Ubuntu vai retornar: $ rvm notes Notes for Linux ( DISTRIB_ID=Ubuntu DISTRIB_RELEASE=11.04 DISTRIB_CODENAME=natty DISTRIB_DESCRIPTION="Ubuntu 11.04" ) # # # # # # # # # NOTE: MRI stands for Matzs Ruby Interpreter (1.8.X, 1.9.X), ree stands for Ruby Enterprise Edition and rbx stands for Rubinius. curl is required. git is required. patch is required (for ree, some ruby heads). If you wish to install rbx and/or any MRI head (eg. 1.9.2-head) then you must install and use rvm 1.8.7 first. If you wish to have the pretty colors again, set export rvm_pretty_print_flag=1 in ~/.rvmrc.

dependencies: # For RVM rvm: bash curl git # For JRuby (if you wish to use it) you will need: jruby: aptitude install curl sun-java6-bin sun-java6-jre sun-java6-jdk # For MRI & ree (if you wish to use it) you will need (depending on what you # are installing): ruby: aptitude install build-essential bison openssl libreadline5 libreadline-dev curl git zlib1g zlib1g-dev libssl-dev libsqlite3-0 libsqlite3-dev sqlite3 libxml2-dev ruby-head: git subversion autoconf # For IronRuby (if you wish to use it) you will need: ironruby: aptitude install curl mono-2.0-devel No caso do Ubuntu, devemos executar a seguinte linha recomendada, em um terminal: sudo aptitude install build-essential bison openssl libreadline5 libreadline-dev curl git zlib1g zlib1g-dev libssl-dev libsqlite3-0 libsqlite3-dev sqlite3 libxml2-dev Podemos usar sem problemas o apt-get.

1.2.2

Instalando um interpretador Ruby

Aps instalar a RVM, agora hora de instalarmos um interpretador Ruby. Vamos utilizar a verso 1.9.2. Para instalar obrigatrio vericar se os pacotes e conguraes que foram apontados na Seo 1.2.1 esto todos instalados. Aps isso, podemos rodar: 3

CAPTULO 1. RUBY $ rvm install 1.9.2 Installing Ruby from source to: /home/taq/.rvm/rubies/ruby-1.9.2-p180, this may take a while depending on your cpu(s)... ruby-1.9.2-p180 - #fetching ruby-1.9.2-p180 - #downloading ruby-1.9.2-p180, this may take a while depending on your connection... ... Ativando a verso na RVM e vericando: $ rvm 1.9.2 $ ruby -v ruby 1.9.2p180 (2011-02-18 revision 30909) [i686-linux] E agora deixando a 1.9.2 como verso padro do sistema: $ rvm use 1.9.2 --default Using /home/taq/.rvm/gems/ruby-1.9.2-p180 Tudo instalado!

1.3. BSICO DA LINGUAGEM

1.3
1.3.1

Bsico da linguagem
Tipagem dinmica

Ruby uma linguagem de tipagem dinmica. Como mencionado na Wikipedia: Tipagem dinmica uma caracterstica de determinadas linguagens de programao, que no exigem declaraes de tipos de dados, pois so capazes de escolher que tipo utilizar dinamicamente para cada varivel, podendo alter-lo durante a compilao ou a execuo do programa. Algumas das linguagens mais conhecidas a utilizarem tipagem dinmica so: Python, Ruby, PHP , FoxPro e Lisp. Contrasta com a tipagem esttica, que exige a declarao de quais dados podero ser associados a cada varivel antes de sua utilizao. Na prtica, isso signica que:
1 2 3 4

v = "teste" v.class => String v = 1 v.class => Fixnum Pudemos ver que a varivel1 v pode assumir como valor tanto uma String como um nmero (que nesse caso, um Fixnum - mais sobre classes mais adiante), ao passo, que, em uma linguagem de tipagem esttica , como Java, isso no seria possvel, com o compilador j no nos deixando prosseguir:

1 2 3 4 5 6 7

public class Estatica { public static void main(String args[]) { String v = "teste"; System.out.println(v); v = 1; } } Listagem 1.1: Tipagem de Java $ javac Estatica.java Estatica.java:5: incompatible types found : int required: java.lang.String v = 1; ^ 1 error

1.3.2

Tipagem forte

Ruby tambm tem tipagem forte. Segundo a Wikipedia: Linguagens implementadas com tipos de dados fortes, tais como Java e Pascal, exigem que o tipo de dado de um valor seja do mesmo tipo da varivel ao qual este valor ser atribudo. Isso signica que:
1 2 3 4

i = 1 s = "oi" puts i+s TypeError: String cant be coerced into Fixnum


1 Variveis

so referncias para reas da memria

CAPTULO 1. RUBY Enquanto em uma linguagem como PHP, temos tipagem fraca:
1 2 3 4 5

<?php $i=1; $s="oi"; print $i+$s; ?> Listagem 1.2: Tipagem fraca com PHP Rodando isso, resulta em: $ php tipagem_fraca.php 1

1.3.3

Tipos bsicos

No temos primitivos em Ruby, somente abstratos, onde todos so objetos. Temos nmeros inteiros e de ponto utuante, onde podemos dividir os inteiros em Fixnums e Bignums, que so diferentes somente pelo tamanho do nmero, sendo convertidos automaticamente.

1.3.4

Fixnums

Os Fixnums so nmeros inteiros de 31 bits de comprimento (ou 1 word do processador menos 1 bit), usando 1 bit para armazenar o sinal, resultando em um valor mximo de armazenamento, para mquinas de 32 bits, de 30 bits, ou seja:
1 2

(2**30)-1 => 1073741823 ((2**30)-1).class => Fixnum Listagem 1.3: Vericando os limites dos Fixnums Vamos testar isso no IRB, o interpretador de comandos do Ruby. Para acionar o IRB, digite no terminal:

1 2 3 4 5

$ irb ruby-1.9.2-p0 > (2**30)-1 => 1073741823 ruby-1.9.2-p0 > ((2**30)-1).class => Fixnum Listagem 1.4: Acionando o IRB

Dica: Podemos descobrir o tipo de objeto que uma varivel aponta utilizando o mtodo class, como no exemplo acima. Os Fixnums tem caractersticas interessantes que ajudam na sua manipulao mais rpida pela linguagem, que os denem como immediate values, que so tipos de dados apontados por variveis que armazenam seus valores na prpria referncia e no em um objeto que teve memria alocada para ser utilizado, agilizando bastante o seu uso. Para isso vamos utilizar o mtodo object_id. Dica: Todo objeto em Ruby pode ser identicado pelo seu object_id. Por exemplo: 6

1.3. BSICO DA LINGUAGEM


1 2 3 4 5 6

ruby-1.9.2-p0 > n = 1234 => 1234 ruby-1.9.2-p0 > n.object_id => 2469 ruby-1.9.2-p0 > n.object_id >> 1 => 1234 Listagem 1.5: Vericando o valor de um Fixnum pelo object_id Tambm podemos notar que esse comportamento slido vericando que o object_id de vrias variveis apontando para um mesmo valor continua sendo o mesmo: _id

1 2 3 4 5 6 7 8

n1 = 1234 => 1234 n2 = 1234 => 1234 n1.object_id => 2469 n2.object_id => 2469 Os Fixnums, como um immediate value, tambm tem tambm uma caracterstica que permite identic-los entre os outros objetos rapidamente atravs de uma operao de and lgico:

1 2 3 4

n = 1234 => 1234 n.object_id & 0x1 => 1 Listagem 1.6: Identicando um immediate value Isso nos mostra um comportamento interessante: qualquer varivel aponta para um objeto ou algo como um Fixnum ou immediate value, que apesar de carregar o seu prprio valor e ser bem lightweight, ainda mantm caractersticas onde podem ter acessados os seus mtodos como qualquer outro tipo na linguagem:

1 2 3

6 7 8

9 10

11 12

13

14

15

1.methods => [:to_s, :-, :, :-, :, :, :div, :, :modulo, :divmod, :fdiv, :, :abs, :magnitude, :, :, :=>, :, :, :, :, :, :, :, :, :[], :, :, :to_f, :size, : zero?, :odd?, :even?, :succ, :integer?, :upto, :downto, :times, :next, :pred, : chr, :ord, :to_i, :to_int, :floor, :ceil, :truncate, :round, :gcd, :lcm, : gcdlcm, :numerator, :denominator, :to_r, :rationalize, :singleton_method_added, :coerce, :i, :, :eql?, :quo, :remainder, :real?, :nonzero?, :step, :to_c, :real, :imaginary, :imag, :abs2, :arg, :angle, :phase, :rectangular, :rect , :polar, :conjugate, :conj, :pretty_print_cycle, :pretty_print, :between?, :po, :poc, :pretty_print_instance_variables, :pretty_print_inspect, :nil?, :, :!, :hash, :class, :singleton_class, :clone, :dup, :initialize_dup, :initialize_clone, :taint, :tainted?, :untaint, :untrust, :untrusted?, : trust, :freeze, :frozen?, :inspect, :methods, :singleton_methods, : protected_methods, :private_methods, :public_methods, :instance_variables, : instance_variable_get, :instance_variable_set, :instance_variable_defined?, :instance_of?, : kind_of?, 7

CAPTULO 1. RUBY
16

17 18 19

:is_a?, :tap, :send, :public_send, :respond_to?, :respond_to_missing?, : extend, :display, :method, :public_method, :define_singleton_method, :__id__, :object_id, :to_enum, :enum_for, :pretty_inspect, :ri, :equal?, :!, :!, :instance_eval, :instance_exec, :__send__] Listagem 1.7: Usando methods em um Fixnum No exemplo acima, estamos vendo os mtodos pblicos de acesso de um Fixnum. Mais sobre mtodos mais tarde.

1.3.5

Bignums

Os Bignums so os nmeros inteiros que excedem o limite imposto pelos Fixnums, ou seja:
1 2 3 4

(2**30) => 1073741824 (2**30).class => Bignum Listagem 1.8: Bignums Uma coisa muito importante nesse caso, que os Bignums alocam memria, diferentemente dos Fixnums e outros tipos que so immediate values! Podemos ver isso criando algumas variveis apontando para o mesmo valor de Bignum e vendo que cada uma tem um object_id diferente:

1 2 3 4 5 6 7 8

b1 = (2**30) => 1073741824 b2 = (2**30) => 1073741824 b1.object_id => 75098610 b2.object_id => 75095080

Dica: Tanto para Fixnums como para Bignums, para efeito de legibilidade, podemos escrever os nmeros utilizando o sublinhado (_) como separador dos nmeros: 1_234_567 => 1234567 1_234_567_890 => 1234567890

1.3.6

Ponto utuante

Os nmeros de ponto utuante podem ser criados utilizando ... ponto, d. Por exemplo:
1 2 3 4

1.23 => 1.23 1.234 => 1.234 Listagem 1.9: Ponto utuante Importante notar que os Floats no so immediate values: 8

1.3. BSICO DA LINGUAGEM


1 2 3 4 5 6 7 8

f1 = 1.23 => 1.23 f2 = 1.23 => 1.23 f1.object_id => 84116780 f2.object_id => 84114210 Listagem 1.10: Pontos utuantes fazem alocamento

1.3.7

Booleanos

Temos mais dois immediate values que so os booleanos true e false, indicando como object_ids, respectivamente, 2 e 0:
1 2 3 4

true.object_id => 2 false.object_id => 0 Listagem 1.11: Booleanos

1.3.8

Nulos

O tipo nulo em Ruby denido como nil. Ele tambm um immediate value, com o valor de 4 no seu object_id:
1 2

nil.object_id => 4 Listagem 1.12: Nulos Temos um mtodo para vericar se uma varivel armazena um valor nul, chamado nil?:

1 2 3 4 5 6 7 8

v = 1 => 1 v.nil? => false v = nil => nil v.nil? => true Listagem 1.13: Usando nil?

1.3.9

Strings

Strings so cadeias de caracteres, que podemos criar delimitando esses caracteres com aspas simples ou duplas, como por exemplo "azul"ou azul, podendo utilizar simples ou duplas dentro da outra como "o cu azul"ou o cu "azul" e "escapar"utilizando o caracter \: "o cu => "o o cu => "o "o cu => "o o cu => "o azul" cu azul" "azul" cu "azul"" \"azul\"" cu "azul"" \azul\ cu azul" 9

CAPTULO 1. RUBY Tambm podemos criar Strings longas, com vrias linhas, usando o conceito de heredoc:
1 2 3 4 5 6 7

str = <<FIM criando uma String longa com saltos de linha e vai terminar logo abaixo. FIM => "criando uma String longa\ncom saltos de linha e \nvai terminar logo abaixo.\n" Listagem 1.14: Heredocs Para cada String criada, vamos ter espao alocado na memria, tendo um object_id distinto para cada um:

1 2 3 4 5 6 7 8

s1 = "ola" => "ola" s2 = "ola" => "ola" s1.object_id => 84291220 s2.object_id => 84288510 Listagem 1.15: Strings fazem alocamento

Substrings Para pegar algumas substrings:


1 2 3 4 5 6 7 8

str = "string" => "string" str[0..2] => "str" str[3..4] => "in" str[4..5] => "ng" Listagem 1.16: Substrings Podendo tambm usar ndices negativos:

1 2 3 4 5 6 7 8 9 10 11 12

str[-4..3] => "ri" str[-5..2] => "tr" str[-4..-3] => "ri" str[-3..-1] => "ing" str[-1] => "g" str[-2] => "n" Listagem 1.17: Indices negativos nas substrings Ou utilizar o mtodo slice, com um comportamento um pouco diferente:

1 2

str.slice(0,2) => "st" 10

1.3. BSICO DA LINGUAGEM


3 4

str.slice(3,4) => "ing" Listagem 1.18: Usando slice Referenciando um caracter da String, temos algumas diferenas entre as verses 1.8.x e 1.9.x do Ruby:

1 2 3 4 5 6 7

# Ruby 1.9.x str[0] => "s" # Ruby 1.8.x str[0] => 115 Listagem 1.19: Encodings no Ruby 1.9.x

Concatenando Strings Para concatenar Strings, podemos utilizar os mtodos (sim, mtodos!) + ou :
1 2 3 4 5 6 7 8 9 10 11 12 13 14

nome = "Eustaquio" => "Eustaquio" sobrenome = "Rangel" => "Rangel" nome + " " + sobrenome => "Eustaquio Rangel" nome.object_id => 84406670 nome << " " => "Eustaquio " nome << sobrenome => "Eustaquio Rangel" nome.object_id => 84406670 Listagem 1.20: Concatenando Strings A diferena que + nos retorna um novo objeto, enquanto faz uma realocao de memria e trabalha no objeto onde o contedo est sendo adicionado.

Encoding A partir da verso 1.9 temos suporte para encodings diferentes para as Strings em Ruby. Nas verses menores, era retornado o valor do caracter na tabela ASCII. Utilizando um encoding como o UTF-8, podemos utilizar (se desejado, claro!) qualquer caracter para denir at nomes de mtodos! Dica: Para utilizarmos explicitamente um encoding em um arquivo de cdigo-fonte Ruby, temos que especicar o encoding logo na primeira linha do arquivo, utilizando, por exemplo, com UTF-8:
1

# encoding: utf-8 Listagem 1.21: Utilizando um encoding no arquivo

11

CAPTULO 1. RUBY Vriaveis so referncias na memria Com as Strings podemos vericar que as variveis realmente armazenam referncias. Vamos notar que, se criarmos uma varivel apontando para uma String, criamos outra apontando para a primeira (ou seja, para o mesmo local na memria) e alterarmos a primeira, comportamento semelhante notado na segunda varivel:
1 2 3 4 5 6 7 8

nick = "TaQ" => "TaQ" other_nick = nick => "TaQ" nick[0] = "S" => "S" other_nick => "SaQ" Listagem 1.22: Alterando o valor referenciado Para evitarmos que esse comportamento acontea e realmente obter dois objetos distintos, podemos utilizar o mtodo dup:

1 2 3 4 5 6 7 8 9 10

nick = "TaQ" => "TaQ" other_nick = nick.dup => "TaQ" nick[0] = "S" => "S" nick => "SaQ" other_nick => "TaQ" Listagem 1.23: Duplicando um objeto

Congelando objetos Se, por acaso quisermos que um objeto no seja modicado, podemos utilizar o mtodo freeze:
1 2 3 4 5 6

nick = "TaQ" => "TaQ" nick.freeze => "TaQ" nick[0] = "S" RuntimeError: cant modify frozen string Listagem 1.24: Congelando um objeto Alguns mtodos e truques com Strings:

1 2 3 4 5 6 7 8 9 10 11

str = "teste" str["est"] = "nt" str.sizea => 5 str.upcase => "TESTE" str.upcase.downcase => "teste" str.sub("t","d") => "deste" str.gsub("t","d") => "desde" str.capitalize => "Desde" str.reverse => "etset" str.split("t") => ["","es","e"] str.scan("t") => ["t","t"] 12

1.3. BSICO DA LINGUAGEM


12 13

str.scan(/^t/) => ["t"] str.scan(/./) => ["t","e","s","t","e"] Listagem 1.25: Alguns metodos de Strings

1.3.10

Smbolos

Smbolos, antes de mais nada, so instncias da classe Symbol. Podemos pensar em um smbolo como uma marca, um nome, onde o que importa no o que contm a sua instncia, mas o seu nome. Smbolos podem se parecer com um jeito engraado de Strings, mas devemos pensar em smbolos como signicado e no como contedo. Quando escrevemos "azul", podemos pensar como um conjunto de letras, mas quando escrevemos :azul, podemos pensar em uma marca, uma referncia para alguma coisa. Smbolos tambm compartilham o mesmo object_id, em qualquer ponto do sistema:
1 2 3 4 5

:teste.class => Symbol :teste.object_id => 263928 :teste.object_id => 263928 Listagem 1.26: Simbolos Como pudemos ver, as duas referncias para os smbolos compartilham o mesmo objeto, enquanto que foram alocados dois objetos para as Strings. Uma boa economia de memria com apenas uma ressalva: smbolos no so objetos candidatos a limpeza automtica pelo garbage collector, ou seja, se voc alocar muitos, mas muitos smbolos no seu sistema, voc poder experimentar um nada agradvel esgotamento de memria que com certeza no vai trazer coisas boas para a sua aplicao, ao contrrio de Strings, que so alocadas mas liberadas quando no esto sendo mais utilizadas. Outra vantagem de smbolos a sua comparao. Para comparar o contedo de duas Strings, temos que percorrer os caracteres um a um e com smbolos podemos comparar os seus object_ids que sempre sero os mesmos, ou seja, uma comparao O(1) (onde o tempo para completar sempre constante e o mesmo e no depende do tamanho da entrada). Imaginem o tanto que economizamos usando tal tipo de operao!

1.3.11

Expresses regulares

Outra coisa muito til em Ruby o suporte para expresses regulares (regexps). Elas podem ser facilmente criadas das seguintes maneiras:
1 2 3 4 5 6

regex1 = /^[0-9]/ => /^[0-9]/ regex2 = Regexp.new("^[0-9]") => /^[0-9]/ regex3 = %r{^[0-9]} => /^[0-9]/ Listagem 1.27: Criando regexps Para fazermos testes com as expresses regulares, podemos utilizar os operadores = (igual o tiozinho) que indica se a expresso "casou"e ! que indica se a expresso no "casou", por exemplo: 13

CAPTULO 1. RUBY
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

"1 teste" =~ regex1 => 0 "1 teste" =~ regex2 => 0 "1 teste" =~ regex3 => 0 "outro teste" !~ regex1 => true "outro teste" !~ regex2 => true "outro teste" !~ regex3 => true "1 teste" !~ regex1 => false "1 teste" !~ regex2 => false "1 teste" !~ regex3 => false Listagem 1.28: Testando regexps No caso das expresses que "casaram", foi retornada a posio da String onde houve correspondncia. Podemos fazer truques bem legais com expresses regulares e Strings, como por exemplo, dividir a nossa String atravs de uma expresso regular:

1 2

"o rato roeu a roupa do rei de Roma".scan(/r[a-z]+/i) => ["rato", "roeu", "roupa", "rei", "Roma"] Listagem 1.29: Dividindo Strings com regexps Fica uma dica que podemos utilizar alguns modicadores no nal da expresso regular, no caso acima, o /i indica que a expresso no ser case sensitive, ou seja, levar em conta caracteres em maisculo ou minsculo. Grupos Podemos utilizar grupos nas expresses regulares, utilizando ( e ) para delimitar o grupo, e $<nmero> para vericar onde o grupo "casou":

1 2 3 4 5 6 7 8

"Alberto Roberto" =~ /(\w+)( )(\w+)/ => 0 $1 => "Alberto" $2 => " " $3 => "Roberto" Listagem 1.30: Alterando Strings com regexps Tambm podemos utilizar \<nmero> para fazer alguma operao com os resultados da expresso regular assim:

1 2

"Alberto Roberto".sub(/(\w+)( )(\w+)/,\3 \1) => "Roberto Alberto" Listagem 1.31: Grupos em regexps

Grupos nomeados A partir da verso 1.9, podemos usar grupos nomeados em nossas expresses regulares, como por exemplo: 14

1.3. BSICO DA LINGUAGEM


1

2 3 4 5

matcher = /(?<objeto>\w{5})(.*)(?<cidade>\w{4})$/.match("o rato roeu a roupa do rei de Roma") matcher[:objeto] => "roupa" matcher[:cidade] => "Roma" Listagem 1.32: Grupos nomeados

1.3.12

Arrays

Arrays podemos denir como objetos que contm referncias para outros objetos. Vamos ver um Array simples com nmeros:
1

array = [1,2,3,4,5] => [1, 2, 3, 4, 5] Listagem 1.33: Criando Arrays Em Ruby os Arrays podem conter tipos de dados diferentes tambm, como esse onde misturamos inteiros, utuantes e Strings:

array = [1,2.3,"oi"] => [1, 2.3, "oi"] Listagem 1.34: Arrays podem conter elementos de tipos diferentes

Dica: Para criarmos Arrays de Strings o mtodo convencional :


1 2

array = ["um","dois","tres","quatro"] => ["um", "dois", "tres", "quatro"] Listagem 1.35: Criando Arrays de Strings mas temos um atalho que nos permite economizar digitao com as aspas, que o %w e pode ser utilizado da seguinte maneira:

1 2

array = %w(um dois tres quatro) => ["um", "dois", "tres", "quatro"] Listagem 1.36: Criando Arrays de Strings

Podemos tambm criar Arrays com tamanho inicial pr-denido utilizando:


1 2

array = Array.new(5) => [nil, nil, nil, nil, nil] Listagem 1.37: Arrays com tamanho inicial Para indicar qual valor ser utilizado ao invs de nil, podemos usar:

1 2

array = Array.new(5,0) => [0, 0, 0, 0, 0] Listagem 1.38: Arrays com tamanho e valor inicial Vamos vericar um efeito interessante:

1 2

array = Array.new(5,"oi") => ["oi", "oi", "oi", "oi", "oi"] Listagem 1.39: Array com Strings Foi criado um Array com 5 elementos, mas so todos os mesmos elementos. Duvidam? Olhem s: 15

CAPTULO 1. RUBY
1 2 3 4

array[0].upcase! => "OI" array => ["OI", "OI", "OI", "OI", "OI"] Listagem 1.40: Alterando um elemento do Array Foi aplicado um mtodo destrutivo no primeiro elemento do Array, que alterou todos os outros, pois so o mesmo objeto. Para evitarmos isso, podemos utilizar um bloco (daqui a pouco!) para criar o Array:

1 2 3 4 5 6

array = Array.new(5) { "oi" } => ["oi", "oi", "oi", "oi", "oi"] array[0].upcase! => "OI" array => ["OI", "oi", "oi", "oi", "oi"] Listagem 1.41: Alterando um elemento do Array sem afetar os outros Pudemos ver que agora so objetos distintos. Aqui temos nosso primeiro uso para blocos de cdigo, onde o bloco foi passado para o construtor do Array, que cria elementos at o nmero que especicamos transmitindo o valor do ndice (ou seja, 0, 1, 2, 3 e 4) para o bloco. Os Arrays tem uma caracterstica interessante que alguns outros objetos de Ruby tem: eles so iteradores, ou seja, objetos que permitem percorrer uma coleo de valores, pois incluem o mdulo (hein?) Enumerable, que inclui essa facilidade. Como parmetro para o mtodo que vai percorrer a coleo, vamos passar um bloco de cdigo e vamos ver na prtica como que funciona isso. Dos mtodos mais comuns para percorrer uma coleo, temos each, que signica "cada", e que pode ser lido "para cada elemento da coleo do meu objeto, execute esse bloco de cdigo", dessa maneira:

1 2 3 4 5

array.each {|numero| O Array tem o numero O Array tem o numero O Array tem o numero O Array tem o numero

puts "O Array tem o numero "+numero.to_s } 1 2 3 4 Listagem 1.42: Usando um iterador

Ou seja, para cada elemento do Array, foi executado o bloco, ateno aqui, passando o elemento corrente como parmetro, recebido pelo bloco pela sintaxe |<parmetro>|. Podemos ver que as instrues do nosso bloco, que no caso s tem uma linha (e foi usada a conveno de e ), foram executadas com o valor recebido como parmetro.

Dica: Temos um atalho em Ruby que nos permite economizar converses dentro de Strings. Ao invs de usarmos to_s como mostrado acima, podemos utilizar o que conhecido como interpolador de expresso com a sintaxe #{objeto}, onde tudo dentro das chaves vai ser transformado em String acessando o seu mtodo to_s. Ou seja, poderamos ter escrito o cdigo do bloco como:
1

{|numero| puts "O Array tem o numero #{numero}"} Listagem 1.43: Interpolando

Podemos pegar subarrays utilizando o formato [incio..m] ou take: 16

1.3. BSICO DA LINGUAGEM


1 2 3 4 5 6 7 8 9 10

a = %w(john paul george ringo) => ["john", "paul", "george", "ringo"] a[0..1] => ["john", "paul"] a[1..2] => ["paul", "george"] a[1..3] => ["paul", "george", "ringo"] a[0] => "john" a[-1] => "ringo" a.first => "john" a.last => "ringo" a.take(2) => ["john", "paul"] Listagem 1.44: Subarrays Reparem no pequeno truque de usar -1 para pegar o ltimo elemento, o que pode car bem mais claro utilizando o mtodo last (e first para o primeiro elemento). Agora que vimos como um iterador funciona, podemos exercitar alguns outros logo depois de conhecer mais alguns outros tipos. Para adicionar elementos em um Array, podemos utilizar o mtodo push ou o (lembram desse, nas Strings, seo 1.3.9?), desse modo:

1 2 3 4 5

a = %w(john paul george ringo) a.push("stu") => ["john", "paul", "george", "ringo", "stu"] a << "george martin" => ["john", "paul", "george", "ringo", "stu", "george martin"] Listagem 1.45: Subarrays

1.3.13

Duck Typing

Pudemos ver que o operador/mtodo funciona de maneira similar em Strings e Arrays, e isso um comportamento que chamamos de Duck Typing, baseado no duck test, de James Whitcomb Riley, que diz o seguinte: Se parece com um pato, nada como um pato, e faz barulho como um pato, ento provavelmente um pato. Isso nos diz que, ao contrrio de linguagens de tipagem esttica, onde o tipo do objeto vericado em tempo de compilao, em Ruby nos interessa se um objeto capaz de exibir algum comportamento esperado, no o tipo dele. Se voc quer fazer uma omelete, no importa que animal que est botando o ovo (galinha, pata, avestruz, Tiranossauro Rex, etc), desde que voc tenha um jeito/mtodo para botar o ovo. "Ei, mas como vou saber se o um determinado objeto tem um determinado mtodo?"Isso fcil de vericar utilizando o mtodo respond_to?:
1 2 3 4

String.new.respond_to?(:<<) => true Array.new.respond_to?(:<<) => true Listagem 1.46: Vericando se um objeto suporta determinado metodo "Ei, mas eu realmente preciso saber se o objeto em questo do tipo que eu quero. O mtodo suportado por Arrays, Strings, Fixnums mas tem comportamento diferente nesses ltimos!". Nesse caso, voc pode vericar o tipo do objeto utilizando kind_of?: 17

CAPTULO 1. RUBY
1 2 3 4 5 6 7 8

String.new.kind_of?(String) => true 1.kind_of?(Fixnum) => true 1.kind_of?(Numeric) => true 1.kind_of?(Bignum) => false Listagem 1.47: Vericando o tipo de um objeto

1.3.14

Ranges

Ranges so intervalos que podemos denir incluindo ou no o ltimo valor referenciado. Vamos exemplicar isso com o uso de iteradores, dessa maneira:
1 2 3 4 5 6 7 8

range1 = (0..10) => 0..10 range2 = (0...10) => 0...10 range1.each {|valor| print "#{valor} "} 0 1 2 3 4 5 6 7 8 9 10 => 0..10 range2.each {|valor| print "#{valor} "} 0 1 2 3 4 5 6 7 8 9 => 0...10 Listagem 1.48: Criando Ranges Como pudemos ver, as Ranges so declaradas com um valor inicial e um valor nal, separadas por dois ou trs pontos, que denem se o valor nal vai constar ou no no intervalo. Dica: Para se lembrar qual da duas opes que incluem o valor, se lembre que nesse caso menos mais, ou seja, com dois pontos temos mais valores. Um truque legal que podemos criar Ranges com Strings:

1 2 3 4 5 6 7

("a".."z").each {|valor| print "#{valor} "} 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 => "a".."z" ("ab".."az").each {|valor| print "#{valor} "} ab ac ad ae af ag ah ai aj ak al am an ao ap aq ar as at au av aw ax ay az => "ab".."az" Listagem 1.49: Criando Ranges a partir de Strings Outro bem legal converter uma Range em um Array:

1 2

("a".."z").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"] Listagem 1.50: Convertendo uma Range para um Array

1.3.15

Hashes

As Hashes so, digamos, Arrays indexados, com chaves e valores, que podem ser quaisquer tipos de objetos, como por exemplo: 18

1.3. BSICO DA LINGUAGEM


1 2 3 4

hash = {:john=>"guitarra e voz",:paul=>"baixo e voz",:george=>"guitarra", :ringo=>"bateria"} => {:john=>"guitarra e voz", :paul=>"baixo e voz", :george=>"guitarra", :ringo=>"bateria"} Listagem 1.51: Criando Hashes No Ruby 1.9.x as Hashes mantm a ordem dos elementos do jeito que foram criadas, porm em algumas verses do Ruby 1.8.x essa ordem aleatria. Depois de declaradas, podemos buscar os seus valores atravs de suas chaves:

1 2 3 4

hash[:paul] => "baixo e voz" hash[:ringo] => "bateria" Listagem 1.52: Recuperando valores da Hash pela da chave Vamos ver um exemplo de como podemos armazenar diversos tipos tanto nas chaves como nos valores de uma Hash:

1 2 3 4 5 6 7 8

hash = {"fixnum"=>1,:float=>1.23,1=>"um"} => {1=>"um", :float=>1.23, "fixnum"=>1} hash["fixnum"] => 1 hash[:float] => 1.23 hash[1] => "um" Listagem 1.53: Chaves e valores da Hash com diferentes tipos de dados Podemos criar Hashes com valores default:

1 2 3 4 5 6

hash = Hash.new(0) => {} hash[:um] => 0 hash[:dois] => 0 Listagem 1.54: Criando Hashes com valores default Nesse caso, quando o valor da chave ainda no teve nada atribudo e requisitado, retornado o valor default que especicamos em new, que foi 0. Vamos testar com outro valor:

1 2 3 4 5 6

hash = Hash.new(Time.now) => {} hash[:um] => Tue Jun 05 23:53:22 -0300 2011 hash[:dois] => Tue Jun 05 23:53:22 -0300 2011 Listagem 1.55: Criando Hashes com valores default da hora corrente No caso acima, passei Time.now no mtodo new da Hash, e toda vez que tentei acessar um dos valores que ainda no foram atribudos, sempre foi retornado o valor da data e hora de quando inicializei a Hash. Para que esse valor possa ser gerado dinamicamente, podemos passar um bloco para o mtodo new:

1 2 3 4

hash = Hash.new { Time.now } => {} hash[:um] => 2008-12-31 11:31:28 -0200 19

CAPTULO 1. RUBY
5 6 7 8

hash[:dois] => 2008-12-31 11:31:32 -0200 hash[:tres] => 2008-12-31 11:31:36 -0200 Listagem 1.56: Criando Hashes com valores default e dinamicos da hora corrente Hashes so bastante utilizadas como parmetros de vrios mtodos do Rails. Dica: No Ruby 1.9, podemos criar Hashes dessa maneira:
1 2 3 4

hash = {john: "guitarra e voz",paul: "baixo e voz",george: "guitarra", ringo: "bateria"} => {:john=>"guitarra e voz", :paul=>"baixo e voz", :george=>"guitarra", :ringo=>"bateria"} Listagem 1.57: Criando Hashes no Ruby 1.9 Reparem que o operador "rocket"(=>) sumiu.

1.3.16

Blocos de cdigo

Um conceito interessante do Ruby so blocos de cdigo (similares ou sendo a mesma coisa em certos sentidos que funes annimas, closures, lambdas etc). Vamos ir aprendendo mais coisas sobre eles no decorrer do curso, na prtica, mas podemos adiantar que blocos de cdigo so uma das grande sacadas de Ruby e so muito poderosos quando utilizados com iteradores. Por conveno os blocos com uma linha devem ser delimitados por e e com mais de uma linha com do ... end (duende???), mas nada lhe impede de fazer do jeito que mais lhe agradar. Como exemplo de blocos, temos:
1

puts {"Oi, mundo"} Listagem 1.58: Bloco de uma linha

1 2 3 4

do puts "Oi, mundo" puts "Aqui tem mais linhas!" end Esses blocos podem ser enviados para mtodos e executados pelos iteradores de vrias classes. Imaginem como pequenos pedaos de cdigo que podem ser manipulados e enviados entre os mtodos dos objetos.

1.3.17

Converses de tipos

Agora que vimos os tipos mais comuns, podemos destacar que temos algumas mtodos de converso entre eles, que nos permitem transformar um tipo (mas no o mesmo objeto, ser gerado um novo) em outro. Alguns dos mtodos: Fixnum para Float Fixnum para String String para Fixnum String para utuante String para smbolo Array para String Array para String, com delimitador Range para Array Hash para Array 20 1.to_f => 1.0 1.to_s => "1" "1".to_i => 1 "1".to_f => 1.0 "azul".to_sym => :azul [1,2,3,4,5].to_s => "12345" [1,2,3,4,5].join(",") => "1,2,3,4,5" (0..10).to_a => [0,1,2,3,4,5,6,7,8,9,10] {:john=>"guitarra e voz"}.to_a=>[[:john,"guitarra e voz"]]

1.3. BSICO DA LINGUAGEM

1.3.18

Converses de bases

De inteiro para binrio:


1 2

2.to_s(2) => "10" Listagem 1.59: Convertendo inteiro para binary De binrio para inteiro:

1 2 3 4

"10".to_i(2) => 2 0b10.to_i => 2 Listagem 1.60: Convertendo binrio para inteiro De inteiro para hexadecimal:

1 2

10.to_s(16) => "a" Listagem 1.61: Convertendo inteiro para hexadecimal De hexadecimal para inteiro:

1 2

0xa.to_i => 10 Listagem 1.62: Convertendo hexadecimal para inteiro

1.3.19

Tratamento de excees

Excees nos permitem cercar erros que acontecem no nosso programa (anal, ningum perfeito, no mesmo?) em um objeto que depois pode ser analisado e tomadas as devidas providncias ao invs de deixar o erro explodir dentro do nosso cdigo levando resultados indesejados. Vamos gerar um erro de propsito para testar isso. Lembram-se que Ruby tem uma tipagem forte, onde no podemos misturar os tipos de objetos? Vamos tentar misturar:
1 2 3 4 5 6 7

begin numero = 1 string = "oi" numero + string rescue StandardError => exception puts "Ocorreu um erro: #{exception}" end Listagem 1.63: Tentando criar a tipagem forte Rodando o programa, temos: Ocorreu um erro: String cant be coerced into Fixnum O programa gerou uma exceo no cdigo contido entre begin e rescue interceptando o tipo de erro tratado pela exceo do tipo StandardError, em um objeto que foi transmitido para rescue, atravs da varivel exception, onde pudemos vericar informaes sobre o erro, imprimindo-o como uma String. Se no quisermos especicar o tipo de exceo a ser tratada, podemos omitir o tipo, e vericar a classe da exceo gerada dessa maneira: 21

CAPTULO 1. RUBY
1 2 3 4 5 6 7

begin numero = 1 string = "oi" numero + string rescue => exception puts "Ocorreu um erro do tipo #{exception.class}: #{exception}" end Listagem 1.64: Capturando exceptions genericas Rodando o programa, temos: Ocorreu um erro do tipo TypeError: String cant be coerced into Fixnum Podemos utilizar ensure como um bloco para ser executado depois de todos os rescues:

1 2 3 4 5 6 7 8 9 10

begin numero = 1 string = "oi" numero + string rescue => exception puts "Ocorreu um erro do tipo #{exception.class}: #{exception}" ensure puts "Lascou tudo." end puts "Fim de programa." Listagem 1.65: Usando ensure Rodando o programa: Ocorreu um erro do tipo TypeError: String cant be coerced into Fixnum Lascou tudo. Fim de programa. Isso particularmente interessante se houver algum problema dentro de algum bloco de rescue:

1 2 3 4 5 6 7 8 9 10 11

begin numero = 1 string = "oi" numero + string rescue => exception puts "Ocorreu um erro do tipo #{exception.class}: #{exception}" puts msg ensure puts "Lascou tudo." end puts "Fim de programa." Listagem 1.66: Problemas dentro do bloco de rescue Rodando o programa: Ocorreu um erro do tipo TypeError: String cant be coerced into Fixnum Lascou tudo. exc1.rb:7: undefined local variable or method msg for main:Object (NameError) Podemos ver que foi gerada uma nova exceo dentro do bloco do rescue e apesar do comando nal com a mensagem Fim de programa no ter sido impressa pois a exceo jogou o uxo de processamento para fora, o bloco do ensure foi executado. Se por acaso desejarmos tentar executar o bloco que deu problema novamente, podemos utilizar retry: 22

1.3. BSICO DA LINGUAGEM


1 2 3 4 5 6 7 8 9

numero1 = 1 numero2 = "dois" begin puts numero1 + numero2 rescue => exception puts "Ops, problemas aqui (#{exception.class}), vou tentar de novo." numero2 = 2 retry end Listagem 1.67: Executando novamente com retry Rodando o programa: Ops, problemas aqui (TypeError), vou tentar de novo. 3 Se desejarmos ter acesso a backtrace (a lista hierrquica das linhas dos programas onde o erro ocorreu), podemos utilizar:

1 2 3 4 5 6 7 8 9 10

numero1 = 1 numero2 = "dois" begin puts numero1 + numero2 rescue => exception puts "Ops, problemas aqui (#{exception.class}), vou tentar de novo." puts exception.backtrace numero2 = 2 retry end Listagem 1.68: Visualizando a backtrace Rodando o programa, nesse caso chamado exc1.rb, vai nos retornar: Ops, problemas aqui (TypeError), vou tentar de novo. exc1.rb:4:in + exc1.rb:4 3

1.3.20
if

Estruturas de controle

Condicionais

importante notar que tudo em Ruby acaba no m end e vamos ver isso acontecendo bastante com nossas estruturas de controle. Vamos comear vendo nosso velho amigo if:
1 2 3 4 5 6 7 8

i = 10 => 10 if i == puts else puts end i igual

10 "i igual 10" "i diferente de 10" 10 Listagem 1.69: Usando if

Uma coisa bem interessante em Ruby que podemos escrever isso de uma forma que podemos ler o cdigo, se, como no caso do prximo exemplo, estivermos interessados apenas em imprimir a mensagem no caso do teste do if for verdadeiro: 23

CAPTULO 1. RUBY
1 2

puts "i igual 10" if i==10 i igual 10 Listagem 1.70: Usando um modicador de estrutura Isso chamado de modicador de estrutura. Tambm temos mais um nvel de teste no if, o elsif:

1 2 3 4 5 6 7 8 9 10

i = 10 => 10 if i > 10 puts "maior que 10" elsif i == 10 puts "igual a 10" else puts "menor que 10" end igual a 10 Listagem 1.71: Usando elsif Podemos capturar a sada do teste diretamente apontando uma vriavel para ele:

1 2 3 4 5 6 7 8 9 10 11 12 13

i = 10 => 10 result = if i > 10 "maior que 10" elsif i == 10 "igual a 10" else "menor que 10" end => "igual a 10" result => "igual a 10" Listagem 1.72: Capturando o resultado de uma estrutura de controle

unless O unless a forma negativa do if, e como qualquer teste negativo, pode trazer alguma confuso no jeito de pensar sobre eles. Particularmente gosto de evitar testes negativos quando pode-se fazer um bom teste positivo. Vamos fazer um teste imaginando uma daquelas cadeiras de boteco e alguns sujeitos mais avantajados (em peso, seus mentes sujas):
1 2 3 4 5 6 7 8

peso = 150 => 150 puts "pode sentar aqui" unless peso > 100 => nil peso = 100 => 100 puts "pode sentar aqui" unless peso > 100 pode sentar aqui Listagem 1.73: Usando unless 24

1.3. BSICO DA LINGUAGEM D para lermos o comando como diga ao sujeito que ele pode sentar aqui a menos que o peso dele for maior que 100 quilos. Talvez um teste mais limpo seria:
1 2 3 4 5 6 7 8

peso = 150 => 150 puts "pode sentar aqui" if peso <= 100 => nil peso = 100 => 100 puts "pode sentar aqui" if peso <= 100 pode sentar aqui Listagem 1.74: Usando um teste positivo Ler diga ao sujeito que ele pode sentar aqui se o peso for menor ou igual a 100 talvez seja um jeito mais claro de fazer o teste, mas ca a critrio de cada um e do melhor uso. case Podemos utilizar o case para fazer algumas comparaes interessantes. Vamos ver como testar com Ranges:

1 2 3 4 5 6 7 8 9 10 11

i = 10 => 10 case i when 0..5 puts "entre 0 e 5" when 6..10 puts "entre 6 e 10" else puts "hein?" end entre 6 e 10 No caso do case (redundncia detectada na frase), a primeira coisa que ele compara o tipo do objeto, nos permitindo fazer testes como:

1 2 3 4 5 6 7 8 9 10 11

i = 10 => 10 case i when Fixnum puts "Nmero!" when String puts "String!" else puts "hein???" end Nmero! Listagem 1.75: Casa comparando tipos de objetos Para provar que esse teste tem precedncia, podemos fazer:

1 2 3 4 5 6 7 8

i = 10 => 10 case i when Fixnum puts "Nmero!" when (0..100) puts "entre 0 e 100" end 25

CAPTULO 1. RUBY
9

Nmero! Listagem 1.76: Precedencia no case A estrutura case compara os valores de forma invertida, como no exemplo acima, Fixnum === e no i === Fixnum, no utilizando o operador == e sim o operador ===, que implementado das seguintes formas: Para mdulos e classes (que vamos ver mais frente), comparado se o valor uma instncia do mdulo ou classe ou de um de seus descendentes. No nosso exemplo, i uma instncia de Fixnum. Por exemplo:

1 2 3 4

Fixnum === 1 => true Fixnum === 1.23 => false Listagem 1.77: Comparando Fixnum com igual triplo Para expresses regulares, comparado se o valor casou com a expresso:

1 2 3 4

/[0-9]/ === "123" => true /[0-9]/ === "abc" => false Listagem 1.78: Comparando regexps com igual triplo Para Ranges, testado se o valor se inclui nos valores da Range (como no mtodo include?):

1 2 3 4

(0..10) === 1 => true (0..10) === 100 => false Listagem 1.79: Comparando Ranges com igual triplo

Loops Antes de vermos os loops, vamos deixar anotado que temos algumas maneiras de interagir dentro de um loop:
BREAK NEXT

sai do loop

vai para a prxima iterao sai do loop e do mtodo onde o loop est contido

RETURN REDO

repete o loop do incio, sem reavaliar a condio ou pegar o prximo elemento

Vamos ver exemplos disso logo na primeira estrutura a ser estudada, o while. Dica: A partir desse ponto vamos utilizar um editor de texto para escrevermos nossos exemplos, usando o irb somente para testes rpidos com pouco cdigo. Voc pode utilizar o editor de texto que quiser, desde que seja um editor mas no um processador de textos. No v utilizar o Microsoft Word para fazer os seus programas, use um editor como o Vim. Edite o seu cdigo, salve em um arquivo com a extenso .rb e execute da seguinte forma: ruby meuarquivo.rb

26

1.3. BSICO DA LINGUAGEM while Faa enquanto:


1 2 3 4 5

i = 0 while i < 5 puts i i += 1 end Listagem 1.80: Usando while $ruby while.rb 0 1 2 3 4 for O for pode ser utilizado junto com um iterador para capturar todos os seus objetos e envilos para o loop (que nada mais do que um bloco de cdigo):

1 2 3

for i in (0..5) puts i end Listagem 1.81: Usando for $ruby for.rb 0 1 2 3 4 5 Vamos aproveitar que um loop bem simples e utilizar os comandos para interagir mostrados acima (mesmo que os exemplos paream as coisas mais inteis e sem sentido do mundo mas para termos didticos, gente!), menos o return onde precisaramos de um mtodo e ainda no chegamos l. Vamos testar primeiro o break:

1 2 3 4

for i in (0..5) break if i==3 puts i end Listagem 1.82: Usando break $ruby for.rb 0 1 2 Agora o next:

1 2 3 4

for i in (0..5) next if i==3 puts i end Listagem 1.83: Usando next 27

CAPTULO 1. RUBY $ruby for.rb 0 1 2 4 5 Agora o redo:


1 2 3 4

for i in (0..5) redo if i==3 puts i end Listagem 1.84: Usando redo $ruby for.rb 0 1 2 for.rb:2: Interrupt from for.rb:1:in each from for.rb:1 Se no interrompermos com Ctrl+C, esse cdigo vai car funcionando para sempre, pois o redo avaliou o loop novamente mas sem ir para o prximo elemento do iterador. until O faa at que pode ser utilizado dessa maneira:

1 2 3 4 5

i = 0 until i==5 puts i i += 1 end Listagem 1.85: Usando until $ruby until.rb 0 1 2 3 4 Dica: No temos os operadores ++ e - em Ruby. Utilize += e -=.

Operadores lgicos Temos operadores lgicos em Ruby em duas formas: !, &&, || e not, and, or. Eles se diferenciam pela precedncia: os primeiros tem precedncia mais alta que os ltimos sobre os operadores de atribuio. Exemplicando:
1 2 3

a = 1 => 1 b = 2 28

1.3. BSICO DA LINGUAGEM


4 5 6 7 8 9 10 11 12

=> 2 c = a && b => 2 c => 2 d = a and b => 2 d => 1 Listagem 1.86: Precedencia de ands logicos A varivel c recebeu o resultado correto de a && b, enquanto que d recebeu a atribuio do valor de a e seria a mesma coisa escrito como (d = a) and b. O operador avalia o valor mais direita somente se o valor mais a esquerda no for falso. a chamada operao de curto-circuito. Dica: Temos um mtodo chamado dened? que testa se a referncia que passamos para ele existe. Se existir, ele retorna uma descrio do que ou nil se no existir. Exemplo: a, b, c = (0..2).to_a => [0, 1, 2] defined? a => "local-variable" defined? b => "local-variable" defined? String => "constant" defined? 1.next => "method"

Desao: Declare duas variveis, x e y, com respectivamente 1 e 2 como valores, com apenas uma linha. Agora inverta os seus valores tambm com apenas uma linha de cdigo. O que vamos fazer aqui uma atribuio em paralelo.

1.3.21

Procs e lambdas

Procs so blocos de cdigo que podem ser associados uma varivel, dessa maneira:
1 2 3 4 5 6 7 8

vezes3 = Proc.new {|valor| valor*3} => #<Proc:0xb7d959c4@(irb):1> vezes3.call(3) => 9 vezes3.call(4) => 12 vezes3.call(5) => 15 Listagem 1.87: Criando Procs Comportamento similar pode ser alcanada usando lambda:

1 2 3 4

vezes5 = lambda {|valor| valor*5} => #<Proc:0xb7d791d4@(irb):5> vezes5.call(5) => 25 29

CAPTULO 1. RUBY
5 6 7 8

vezes5.call(6) => 30 vezes5.call(7) => 35 Listagem 1.88: Criando lambdas Pudemos ver que precisamos executar call para chamar a Proc, mas tambm podemos utilizar o atalho []:

1 2

vezes5[8] => 40 Listagem 1.89: Executando Procs com [ E tambm o atalho .:

1 2

vezes5.(5) => 25 Listagem 1.90: Executando Procs com . Podemos utilizar uma Proc como um bloco, mas para isso precisamos converte-la usando &:

1 2

(1..5).collect &vezes5 => [5, 10, 15, 20, 25] Listagem 1.91: Convertendo Procs em blocos

Dica: Fica um "gancho"aqui sobre o fato de Procs serem closures , ou seja, cdigo que criam uma cpia do seu ambiente. Quando estudarmos mtodos vamos ver um exemplo prtico sobre isso. Importante notar duas diferenas entre Procs e lambdas: A primeira diferena, a vericao de argumentos. Em lambdas a vericao feita e gera uma exceo:
1 2 3 4 5 6 7 8 9

pnew = Proc.new {|x, y| puts x + y} => #<Proc:0x8fdaf7c@(irb):7> lamb = lambda {|x, y| puts x + y} => #<Proc:0x8fd7aac@(irb):8 (lambda)> pnew.call(2,4,11) 6 => nil lamb.call(2,4,11) ArgumentError: wrong number of arguments (3 for 2) Listagem 1.92: Vericando argumentos em Procs e lambdas A segunda diferena o jeito que elas retornam. O retorno de uma Proc retorna de dentro de onde ela est, como nesse caso:

1 2 3 4 5 6

def testando_proc p = Proc.new { return "Bum!" } p.call "Nunca imprime isso." end puts testando_proc Listagem 1.93: Retornando de uma Proc

30

1.3. BSICO DA LINGUAGEM $ ruby code/procret.rb Bum! Enquanto que em uma lambda, retorna para onde foi chamada:
1 2 3 4 5 6

def testando_lamb l = lambda { return "Oi!" } l.call "Imprime isso." end puts testando_lamb Listagem 1.94: Retornando de uma lambda $ ruby code/lambret.rb Imprime isso. A partir do Ruby 1.9, temos suporte sintaxe "stabby proc":

1 2

p = -> x,y { x* y} puts p.call(2,3) Listagem 1.95: Stabby proc $ ruby code/stabproc.rb 6 E tambm ao mtodo curry, que decompem uma lambda em uma srie de outras lambdas. Por exemplo, podemos ter uma lambda que faa multiplicao:

1 2 3 4

mult = lambda {|n1,n2| n1*n2} => #<Proc:0x8fef1fc@(irb):13 (lambda)> mult.(2,3) => 6 Listagem 1.96: Uma lambda para multiplicar Podemos utilizar o mtodo curry no nal e ter o seguinte resultado:

1 2 3 4

mult = lambda {|n1,n2| n1*n2}.curry => #<Proc:0x8ffe4e0 (lambda)> mult.(2).(3) => 6 Listagem 1.97: Usando curry Reparem que o mtodo call (na forma de .()) foi chamado duas vezes, primeiro com 2 e depois com 3, pois o mtodo curry inseriu uma lambda dentro da outra, como se fosse:

1 2 3 4

multi = lambda {|x| lambda {|y| x*y}} => #<Proc:0x901756c@(irb):23 (lambda)> mult.(2).(3) => 6 Listagem 1.98: Lambdas dentro de lambdas Isso pode ser til quando voc deseja criar uma lambda a partir de outra, deixando um dos parmetros xo, como por exemplo:

1 2 3 4 5

mult = lambda {|n1,n2| n1*n2}.curry => #<Proc:0x901dd40 (lambda)> dobro = mult.(2) => #<Proc:0x901c058 (lambda)> triplo = mult.(3) 31

CAPTULO 1. RUBY
6 7 8 9 10

=> #<Proc:0x9026904 (lambda)> dobro.(8) => 16 triplo.(9) => 27 Listagem 1.99: Fixando um valor da lambda com curry

1.3.22

Iteradores

Agora que conhecemos os tipos bsicos de Ruby, podemos focar nossa ateno em uma caracterstica bem interessante deles: muitos, seno todos, tem colees ou caractersticas que podem ser percorridas por mtodos iteradores.. Um iterador percorre uma determinada coleo, que o envia o valor corrente, executando algum determinado procedimento, que em Ruby enviado como um bloco de cdigo. Um iterador contm o mdulo (hein?) Enumerable, que d as funcionalidades de que ele precisa. Dos mtodos mais comuns para percorrer uma coleo, temos each, que signica "cada", e que pode ser lido "para cada elemento da coleo do meu objeto, execute esse bloco de cdigo", dessa maneira:
1

[1,2,3,4,5].each {|e| puts "o array contem o numero #{e}"} Listagem 1.100: Percorrendo Array com each Rodando o programa: $ o o o o o ruby code/it1.rb array contem o numero array contem o numero array contem o numero array contem o numero array contem o numero 1 2 3 4 5

Ou seja, para cada elemento do Array, foi executado o bloco - ateno aqui - passando o elemento corrente como parmetro, recebido pelo bloco pela sintaxe |<parmetro>|. Podemos ver que as instrues do nosso bloco, que no caso s tem uma linha (e foi usada a conveno de e ), foram executadas com o valor recebido como parmetro. Esse mesmo cdigo pode ser otimizado e refatorado para car mais de acordo com a sua nalidade. No precisamos de um loop de 1 at 5? A maneira mais adequada seria criar uma Range com esse intervalo e executar nosso iterador nela:
1

(1..5).each { |e| puts "a range contem o numero #{e}" } Listagem 1.101: Percorrendo Range com each $ a a a a a ruby code/it2.rb range contem o numero range contem o numero range contem o numero range contem o numero range contem o numero

1 2 3 4 5

Um Array s faria sentido nesse caso se os seus elementos no seguissem uma ordem lgica que pode ser expressa em um intervalo de uma Range! Quaisquer sequncias que podem ser representadas fazem sentido em usar uma Range. Se por acaso quisssemos uma lista de nmeros de 1 at 21, em intervalos de 3, podemos utilizar: 32

1.3. BSICO DA LINGUAGEM


1

(1..21).step(2).each { |e| puts "numero #{e}" } Listagem 1.102: Percorrendo Range com each $ ruby numero numero numero numero numero numero numero numero numero numero numero code/it3.rb 1 3 5 7 9 11 13 15 17 19 21

Em Rails utilizamos bastante a estrutura for <objeto> in <coleo>, da seguinte forma:


1 2 3 4

col = %w(uma lista de Strings para mostrar o for) for str in col puts str end Listagem 1.103: Utilizando for ... in $ ruby code/it4.rb uma lista de Strings para mostrar o for Selecionando elementos Vamos supor que queremos selecionar alguns elementos que atendam alguma condio nos nossos objetos, por exemplo, selecionar apenas os nmeros pares de uma coleo:

1 2

(1..10).select {|e| e.even?} => [2, 4, 6, 8, 10] Listagem 1.104: Selecionando elementos Vamos testar com uma Hash:

1 2

{1=>"um",2=>"dois",3=>"tres"}.select {|chave,valor| valor.length>2} => [[2, "dois"], [3, "tres"]] Listagem 1.105: Selecionando elementos em uma Hash Selecionando os elementos que no atendem uma condio O contrrio da operao acima pode ser feito com reject:

1 2

(0..10).reject {|valor| valor.even?} => [1, 3, 5, 7, 9] Listagem 1.106: Rejeitando elementos Nada que a condio alterada do select tambm no faa. 33

CAPTULO 1. RUBY Processando e alterando os elementos Vamos alterar os elementos do objeto com o mtodo map:
1 2 3 4 5 6 7 8 9 10 11

(0..10).map {|valor| valor*2} => [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20] %w(um dois tres quatro cinco seis sete oito nove dez).map {|valor| "numero #{valor}"} => ["numero um", "numero dois", "numero tres", "numero quatro", "numero cinco", "numero seis", "numero sete", "numero oito", "numero nove", "numero dez"] {1=>"um",2=>"dois",3=>"tres"}.map {|chave,valor| "numero #{valor}" } => ["numero um", "numero dois", "numero tres"] Listagem 1.107: Modicando elementos

Detectando condio em todos os elementos Vamos supor que desejamos detectar se todos os elementos da coleo atendem uma determinada condio com o mtodo all?:
1 2 3 4

(0..10).all? {|valor| valor>1} => false (0..10).all? {|valor| valor>0} => false Listagem 1.108: Detectando condicao em todos os elementos

Detectando se algum elemento atende uma condio Vamos testar se algum elemento atende uma determinada condio com o mtodo any?:
1 2 3 4

(0..10).any? {|valor| valor==3} => true (0..10).any? {|valor| valor==30} => false Listagem 1.109: Detectando se algum elemento atende uma condicao Nesse caso especco, poderamos ter escrito dessa forma tambm:

1 2 3 4

(0..10).include?(3) => true (0..10).include?(30) => false Apesar da facilidade com um teste simples, o mtodo any? muito prtico no caso de procurarmos, por exemplo, um determinado objeto com um determinado valor de retorno em algum de seus mtodos. Detectar e retornar o primeiro elemento que atende uma condio Se alm de detectar quisermos retornar o elemento que atende uma condio, podemos utilizar o mtodo detect?:

1 2

(0..10).detect {|valor| valor>0 && valor%4==0} => 4 Listagem 1.110: Detectar o primeiro elemento em uma condicao

34

1.3. BSICO DA LINGUAGEM Detectando os valores mximo e mnimo Podemos usar max e min para isso:
1 2 3 4

(0..10).max => 10 (0..10).min => 0 Listagem 1.111: Detectando os valores maximo e minimo interessante notar que podemos passar um bloco onde sero comparados os valores para teste atravs do operador <=>:

1 2 3 4 5 6

%w(joao maria antonio).max {|elemento1,elemento2| elemento1.length <=> elemento2.length} => "antonio" %w(joao maria antonio).min {|elemento1,elemento2| elemento1.length <=> elemento2.length} => "joao" Listagem 1.112: Detectando os valores maximo e minimo pelo tamanho

Dica: O operador <=> compara o objeto da esquerda com o objeto da direita e retorna -1 se o objeto esquerda for menor, 0 se for igual e 1 se for maior do que o da direita: 1 <=> 2 => -1 1 <=> 1 => 0 1 <=> -1 => 1

Olhem que interessante comparando valores de Hashes:


1 2 3 4 5 6

{:joao=>33,:maria=>30,:antonio=>25}.max {|elemento1,elemento2| elemento1[1] <=> elemento2[1]} => [:joao, 33] {:joao=>33,:maria=>30,:antonio=>25}.min {|elemento1,elemento2| elemento1[1] <=> elemento2[1]} => [:antonio, 25] Listagem 1.113: Detectando os valores maximo e minimo em Hashes Tem uma mgica de converso escondida ali. ;-) Acumulando os elementos Podemos acmular os elementos com inject, onde vo ser passados um valor acumulador e o valor corrente pego do iterador. Se desejarmos saber qual a soma de todos os valores da nossa Range :

1 2

(0..10).inject {|soma,valor| soma+valor} => 55 Listagem 1.114: Somando os elementos Podemos passar tambm um valor inicial:

1 2

(0..10).inject(100) {|soma,valor| soma+valor} => 155 Listagem 1.115: Somando os elementos 35

CAPTULO 1. RUBY E tambm podemos passar o mtodo que desejamos utilizar para combinao como um smbolo:
1 2 3 4

(0..10).inject(:+) => 55 ruby-1.9.2-p0 > (0..10).inject(100,:+) => 155 Listagem 1.116: Somando os elementos

Dividir a coleo em dois Arrays obedecendo uma condio Vamos separar os nmeros pares dos mpares:
1 2

(0..10).partition {|valor| valor.even?} => [[0, 2, 4, 6, 8, 10], [1, 3, 5, 7, 9]] Listagem 1.117: Dividindo a colecao

Percorrendo os elementos com os ndices Vamos ver onde cada elemento se encontra com each_with_index:
1 2 3 4 5 6 7 8 9 10 11 12

(0..10).each_with_index {|item,indice| puts "#{item} indice #{indice}"} 0 indice 0 1 indice 1 2 indice 2 3 indice 3 4 indice 4 5 indice 5 6 indice 6 7 indice 7 8 indice 8 9 indice 9 10 indice 10 Listagem 1.118: Percorrendo a colecao

Ordenando uma coleo Vamos ordenar um Array de Strings usando sort:


1 2

%w(joao maria antonio).sort => ["antonio", "joao", "maria"] Listagem 1.119: Ordenando uma colecao Podemos ordenar de acordo com algum critrio especco, passando um bloco e usando sort_by:

1 2

%w(antonio maria joao).sort_by {|nome| nome.length} => ["joao", "maria", "antonio"] Listagem 1.120: Ordenando uma colecao por criterio

36

1.3. BSICO DA LINGUAGEM Combinando elementos Podemos combinar elementos com o mtodo zip:
1 2 3 4 5 6

(1..10).zip((11..20)) => [[1, 11], [2, 12], [3, 13], [4, 14], [5, 15], [6, 16], [7, 17], [8, 18], [9, 19], [10, 20]] (1..10).zip((11..20),(21..30)) => [[1, 11, 21], [2, 12, 22], [3, 13, 23], [4, 14, 24], [5, 15, 25], [6, 16, 26], [7, 17, 27], [8, 18, 28], [9, 19, 29], [10, 20, 30]] Listagem 1.121: Combinando elementos com zip Tambm podemos usar combination:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

a = %w(john paul george ringo) => ["john", "paul", "george", "ringo"] a.combination(2) => #<Enumerable::Enumerator:0xb7d711a0> a.combination(2).to_a => [["john", "paul"], ["john", "george"], ["john", "ringo"], ["paul", "george"], ["paul", "ringo"], ["george", "ringo"]] a.combination(2) {|comb| puts "combinando #{comb[0]} com #{comb[1]}"} combinando combinando combinando combinando combinando combinando john com paul john com george john com ringo paul com george paul com ringo george com ringo Listagem 1.122: Combinando elementos com combination Ou permutation:

1 2 3 4 5 6 7 8 9

a = %w(john paul george ringo) => ["john", "paul", "george", "ringo"] a.permutation(2) => #<Enumerable::Enumerator:0xb7ce41c4> a.permutation(2).to_a => [["john", "paul"], ["john", "george"], ["john", "ringo"], ["paul", "john"], ["paul", "george"], ["paul", "ringo"], ["george", "john" ], ["george", "paul"], ["george", "ringo"], ["ringo", "john"], ["ringo", "paul "], ["ringo", "george"]] a.permutation(2) {|comb| puts "combinando #{comb[0]} com #{comb[1]}" } combinando john com paul combinando john com george combinando john com ringo combinando paul com john combinando paul com george combinando paul com ringo combinando george com john 37

10

11 12 13 14 15 16 17 18 19 20

CAPTULO 1. RUBY
21 22 23 24 25

combinando combinando combinando combinando combinando

george com paul george com ringo ringo com john ringo com paul ringo com george Listagem 1.123: Combinando elementos com combination

Ou product:
1 2 3 4 5 6 7 8

beatles = %w(john paul george ringo) => ["john", "paul", "george", "ringo"] stooges = %w(moe larry curly shemp) => ["moe", "larry", "curly", "shemp"] beatles.product(stooges) => [["john", "moe"], ["john", "larry"], ["john", "curly"], ["john", "shemp" ], ["paul", "moe"], ["paul", "larry"], ["paul", "curly"], ["paul", "shemp"], ["george", "moe"], ["george", "larry"], ["george", "curly"], ["george", "shemp"], ["ringo", "moe"], ["ringo", "larry"], ["ringo", "curly "], ["ringo", "shemp"]] Listagem 1.124: Combinando elementos com product

9 10 11

12

Percorrendo valores para cima e para baixo Podemos usar upto, downto e step:
1 2 3 4 5 6

1.upto(5) {|num| print num," "} 1 2 3 4 5 => 1 5.downto(1) {|num| print num," "} 5 4 3 2 1 => 5 1.step(10,2) {|num| print num," "} 1 3 5 7 9 => 1 Listagem 1.125: Percorrendo elementos

Inspecionando no encadeamento de mtodos Um mtodo bem til para o caso de precisarmos inspecionar ou registrar o contedo de algum objeto durante algum encadeamento de iteradores o mtodo tap. Vamos supor que voc tem o seguinte cdigo:
1 2

(0..10).select {|num| num.even?}.map {|num| num*2} => [0, 4, 8, 12, 16, 20] Listagem 1.126: Selecionando e alterando elementos Isso nada mais faz do que separar os nmeros pares e multiplic-los por 2, mas imaginemos que a coleo inicial no formada por nmeros e sim por objetos da nossa tabela de funcionrios onde vamos selecionar somente algumas pessoas que atendem determinadas condies (usando o select) e reajustar o seu salrio baseado em vrias regras complexas (o map), e algum problema est ocorrendo na seleo. O jeito convencional criar uma varivel temporria armazenando o contedo retornado pelo select e a imprimirmos, executando o map logo em seguida. Ou podemos fazer assim: 38

1.3. BSICO DA LINGUAGEM


1 2 3

(0..10).select {|num| num.even?}.tap{|col| p col}.map {|num| num*2} [0, 2, 4, 6, 8, 10] => [0, 4, 8, 12, 16, 20] Listagem 1.127: Selecionando Isso nos mostra o contedo antes de ser enviado para o prximo mtodo encadeado.

1.3.23

Mtodos

Podemos denir mtodos facilmente em Ruby, usando def, terminando (como sempre) com end:
1 2 3 4

def diga_oi puts "Oi!" end diga_oi Listagem 1.128: Denindo um metodo Executando esse cdigo, ser impresso Oi!. J podemos reparar que os parnteses no so obrigatrios para chamar um mtodo em Ruby. Retornando valores Podemos retornar valores de mtodos com ou sem o uso de return. Quando no utilizamos return, o que ocorre que a ltima expresso avaliada retornada, como no exemplo:

1 2 3 4 5

def vezes(p1,p2) p1*p2 end puts vezes(2,3) => 6 Listagem 1.129: Retornando valores No caso, foi avaliado por ltimo p1*p2, o que nos d o resultado esperado. Tambm podemos retornar mais de um resultado, que na verdade apenas um objeto, sendo ele complexo ou no, dando a impresso que so vrios, como no exemplo que vimos atribuio em paralelo. Vamos construir um mtodo que retorna cinco mltiplos de um determinado nmero:

1 2 3 4 5 6 7

def cinco_multiplos(numero) (1..5).map {|valor| valor*numero} end v1,v2,v3,v4,v5 = cinco_multiplos(5) puts "#{v1},#{v2},#{v3},#{v4},#{v5}" => 5,10,15,20,25 Listagem 1.130: Retornando multiplos valores de um metodo

Enviando valores Antes de mais nada, ca a discusso sobre a conveno sobre o que so parmetros e o que so argumentos, convencionando-se : Parmetros so as variveis situadas na assinatura de um mtodo; Argumentos so os valores atribudos aos parmetros Vimos acima um exemplo simples de passagem de valores para um mtodo, vamos ver outro agora: 39

CAPTULO 1. RUBY
1 2 3 4 5

def vezes(n1,n2) n1*n2 end puts vezes(3,4) => 12 Listagem 1.131: Enviando argumentos para um metodo Podemos contar quantos parmetros um mtodo recebe usando arity:

1 2 3 4 5

def vezes(n1,n2) n1*n2 end puts vezes(3,4) puts "o metodo recebe #{method(:vezes).arity} parametros" Listagem 1.132: Contando parametros Mtodos tambm podem receber parmetros default, como por exemplo:

1 2 3 4 5 6 7 8

def oi(nome="Forasteiro") puts "Oi, #{nome}!" end oi("TaQ") oi => Oi, TaQ! => Oi, Forasteiro! Listagem 1.133: Parametros default E tambm valores variveis, bastando declarar o nosso mtodo como recebendo um parmetro com o operador splat (aquele asterisco) antes do nome do parmetro:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

def varios(*valores) valores.each {|valor| puts "valor=#{valor}"} puts "-"*25 end varios(1) varios(1,2) varios(1,2,3) valor=1 ------------------------valor=1 valor=2 ------------------------valor=1 valor=2 valor=3 ------------------------Listagem 1.134: Recebendo parametros variaveis

Dica: O operador splat pode parecer meio estranho, mas ele nada mais faz, na denio do mtodo, do que concentrar todos os valores recebidos em um Array, como pudemos ver acima. Quando usamos o splat na frente do nome de uma varivel que se comporta como uma coleo, ele explode os seus valores, retornando os elementos individuais:

40

1.3. BSICO DA LINGUAGEM array = \%w(um dois tres) => ["um", "dois", "tres"] p *array "um" "dois" "tres" => nil hash = {:um=>1,:dois=>2,:tres=>3} => {:tres=>3, :um=>1, :dois=>2} p *hash [:tres, 3] [:um, 1] [:dois, 2] => nil

Como vimos com iteradores, podemos passar um bloco para um mtodo, e para o executarmos dentro do mtodo, usamos yield :
1 2 3 4 5 6 7 8 9 10

def executa_bloco(valor) yield(valor) end executa_bloco(2) {|valor| puts valor*valor} executa_bloco(3) {|valor| puts valor*valor} executa_bloco(4) {|valor| puts valor*valor} 4 9 16 Listagem 1.135: Usando yield Podemos usar block_given? para detectar se um bloco foi passado para o mtodo:

1 2 3 4 5 6 7 8

def executa_bloco(valor) yield(valor) if block_given? end executa_bloco(2) {|valor| puts valor*valor} executa_bloco(3) executa_bloco(4) {|valor| puts valor*valor} 4 16 Listagem 1.136: Vericando se um bloco foi enviado Podemos tambm converter um bloco em uma Proc especicando o nome do ltimo parmetro com & no comeo:

1 2 3 4 5

def executa_bloco(valor,&proc) puts proc.call(valor) end executa_bloco(2) {|valor| valor*valor} 4 Listagem 1.137: Convertendo uma Proc em um bloco Como recebemos referncias do objeto nos mtodos, quaisquer alteraes que zermos dentro do mtodo reetiro fora. Vamos comprovar:

def altera(valor) 41

CAPTULO 1. RUBY
2 3 4 5 6 7

valor.upcase! end string = "Oi, mundo!" altera(string) puts string => O I , MUNDO! Listagem 1.138: Alterando uma referencia dentro do metodo Uma praticidade grande usarmos o corpo do mtodo para capturarmos uma exceo, sem precisar abrir um bloco com begin e end:

1 2 3 4 5 6 7

def soma(valor1,valor2) valor1+valor2 rescue nil end puts soma(1,2) => 3 puts soma(1,:um) => nil Listagem 1.139: Capturando exceptions direto no corpo do metodo Tambm podemos utilizar os caracteres ! e ? no nal dos nomes dos nossos mtodos. Por conveno, mtodos com ! no nal so mtodos destrutivos e com ? no nal so mtodos para testar algo:

1 2 3 4 5 6 7 8

def revup!(str) str.reverse!.upcase! end str = "teste" puts str.object_id revup!(str) puts str puts str.object_id Listagem 1.140: Criando um metodo destrutivo $ ruby code/destructive.rb 74439960 ETSET 74439960

1 2 3 4 5 6 7 8

def ok?(obj) !obj.nil? end puts puts puts puts ok?(1) ok?("um") ok?(:um) ok?(nil) Listagem 1.141: Criando um metodo para testes $ ruby code/testmeth.rb true true true false

42

1.3. BSICO DA LINGUAGEM Dica: podemos simular argumentos nomeados usando uma Hash:
1 2 3 4 5 6 7 8

def test(args) one = args[:one] two = args[:two] puts "one: #{one} two: #{two}" end test(one: 1, two: 2) test(two: 2, one: 1) Listagem 1.142: Simulando argumentos nomeados $ ruby code/named.rb one: 1 two: 2 one: 1 two: 2

Tambm podemos capturar um mtodo como se fosse uma Proc:


1 2 3 4 5 6 7 8 9 10

class Teste def teste(qtde) qtde.times { puts "teste!" } end end t = Teste.new m = t.method(:teste) p m m.(3) p m.to_proc Listagem 1.143: Capturando um metodo $ ruby code/capture.rb #<Method: Teste#teste> teste! teste! teste! #<Proc:0x8d3c4b4 (lambda)> Como podemos ver, o resultado um objeto do tipo Method, mas que pode ser convertido em uma Proc usando o mtodo to_proc. E agora um mtodo de nome totalmente diferente usando o suporte para encodings do Ruby 1.9:

1 2 3 4 5 6 7 8 9

# encoding: utf-8 module Enumerable def $\sum$ self.inject {|memo,val| memo += val} end end puts [1,2,3].$\sum$ puts (0..3).$\sum$ Listagem 1.144: Usando encodings nos nomes dos metodos

43

CAPTULO 1. RUBY $ ruby code/encodingmeth.rb 6 6 Uau! Para quem quiser inserir esses caracteres malucos no Vim, consulte o help dos digraphs com :help digraphs. Esse do exemplo feito usando, no modo de insero, CTRL+K +Z.

44

1.4. CLASSES E OBJETOS

1.4

Classes e objetos

Como bastante coisas em Ruby so objetos, vamos aprender a criar os nossos. Vamos fazer uma classe chamada Carro, com algumas propriedades:
1 2 3 4 5 6 7 8 9 10 11 12

class Carro def initialize(marca,modelo,cor,tanque) @marca = marca @modelo = modelo @cor = cor @tanque = tanque end end corsa = Carro.new(:chevrolet,:corsa,:preto,50) p corsa puts corsa Listagem 1.145: Denindo a classe Carro

$ ruby code/carro1.rb #<Carro:0x894c674 @marca=:chevrolet, @modelo=:corsa, @cor=:preto, @tanque=50> #<Carro:0x894c674> Para criarmos uma classe, usamos a palavra-chave class, seguida pelo nome da classe. Dica: Segundo as convenes de Ruby, nos nomes dos mtodos deve-se usar letras minsculas separando as palavras com um sublinhado (_), porm nos nomes das classes utilizado camel case, da mesma maneira que em Java, com maisculas separando duas ou mais palavras no nome da classe. Temos ento classes com nomes como MinhaClasse MeuTeste CarroPersonalizado

As propriedades do nosso objeto so armazenadas no que chamamos variveis de instncia, que so quaisquer variveis dentro do objeto cujo nome se inicia com @. Se zermos referncia para alguma que ainda no foi criada, ela ser. Podemos inicializar vrias dessas variveis dentro do mtodo initialize, que o construtor do nosso objeto. Dica: No temos mtodos destrutores em Ruby, mas podemos associar uma Proc para ser chamada em uma instncia de objeto cada vez que ela for limpa pelo garbage collector:
1 2 3

string = "Oi, mundo!" ObjectSpace.define_finalizer(string, lambda {|id| puts "Estou terminando o objeto #{id}"}) Listagem 1.146: Denindo destrutor

Desao: Crie mais alguns objetos como no exemplo acima, associando uma Proc com o finalizer do objeto. Repare que talvez alguns no estejam exibindo a mensagem. Por que?

45

CAPTULO 1. RUBY Pudemos ver acima que usando puts para vericar o nosso objeto, foi mostrada somente a referncia dele na memria. Vamos fazer um mtodo novo na classe para mostrar as informaes de uma maneira mais bonita. Lembram-se que em converses utilizamos um mtodo chamado to_s ? Vamos criar um para a nossa classe:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

class Carro def initialize(marca,modelo,cor,tanque) @marca = marca @modelo = modelo @cor = cor @tanque = tanque end def to_s "Marca:#{@marca} Modelo:#{@modelo} Cor:#{@cor} Tanque:#{@tanque}" end end corsa = Carro.new(:chevrolet,:corsa,:preto,50) p corsa puts corsa Listagem 1.147: Representando como String $ rvm 1.8.7 $ ruby code/carro2.rb #<Carro:0xb75be6b0 @cor=:preto, @modelo=:corsa, @marca=:chevrolet, @tanque=50> Marca:chevrolet Modelo:corsa Cor:preto Tanque:50 $ rvm 1.9.2 $ ruby code/carro2.rb Marca:chevrolet Modelo:corsa Cor:preto Tanque:50 Marca:chevrolet Modelo:corsa Cor:preto Tanque:50 Dica: Sobrescrever o mtodo to_s no deveria afetar o inspect. O pessoal anda discutindo isso.

Dica: Ruby tem alguns mtodos que podem confundir um pouco, parecidos com to_s e to_i, que so to_str e to_int. Enquanto to_s e to_i efetivamente fazem uma converso de tipos, to_str e to_int indicam que os objetos podem ser representados como uma String e um inteiro, respectivamente. Ou seja: to_s signica que o objeto tem representao como String, to_str signica que o objeto uma representao de String.

Dica: Todo mtodo chamado sem um receiver explcito ser executado em self. Vimos como criar as propriedades do nosso objeto atravs das variveis de instncia, mas como podemos acess-las? Isso vai nos dar um erro:
1 2 3 4

class Carro def initialize(marca,modelo,cor,tanque) @marca = marca @modelo = modelo 46

1.4. CLASSES E OBJETOS


5 6 7 8 9 10 11 12 13 14

@cor @tanque

= cor = tanque

end def to_s "Marca:#{@marca} Modelo:#{@modelo} Cor:#{@cor} Tanque:#{@tanque}" end end corsa = Carro.new(:chevrolet,:corsa,:preto,50) puts corsa.marca Listagem 1.148: Tentando acessar propriedades $ ruby code/carro3.rb code/carro3.rb:14:in <main>: undefined method marca for Marca:chevrolet Modelo:corsa Cor:preto Tanque:50:Carro (NoMethodError) Essas variveis so privadas do objeto, e no podem ser lidas sem um mtodo de acesso. Podemos resolver isso usando attr_reader:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

class Carro attr_reader :marca, :modelo, :cor, :tanque def initialize(marca,modelo,cor,tanque) @marca = marca @modelo = modelo @cor = cor @tanque = tanque end def to_s "Marca:#{@marca} Modelo:#{@modelo} Cor:#{@cor} Tanque:#{@tanque}" end end corsa = Carro.new(:chevrolet,:corsa,:preto,50) puts corsa.marca Listagem 1.149: Acessando propriedades $ ruby code/carro4.rb chevrolet Nesse caso, criamos atributos de leitura, que nos permitem a leitura da propriedade. Se precisarmos de algum atributo de escrita, para trocarmos a cor do carro, por exemplo, podemos usar:

1 2 3 4 5 6 7 8 9 10 11 12

class Carro attr_reader :marca, :modelo, :cor, :tanque attr_writer :cor def initialize(marca,modelo,cor,tanque) @marca = marca @modelo = modelo @cor = cor @tanque = tanque end def to_s "Marca:#{@marca} Modelo:#{@modelo} Cor:#{@cor} Tanque:#{@tanque}" 47

CAPTULO 1. RUBY
13 14 15 16 17 18

end end corsa = Carro.new(:chevrolet,:corsa,:preto,50) corsa.cor = :branco puts corsa Listagem 1.150: Escrevendo propriedades $ ruby code/carro5.rb Marca:chevrolet Modelo:corsa Cor:branco Tanque:50 Podemos at encurtar isso mais ainda criando direto um atributo de escrita e leitura com attr_accessor:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

class Carro attr_reader :marca, :modelo, :tanque attr_accessor :cor def initialize(marca,modelo,cor,tanque) @marca = marca @modelo = modelo @cor = cor @tanque = tanque end def to_s "Marca:#{@marca} Modelo:#{@modelo} Cor:#{@cor} Tanque:#{@tanque}" end end corsa = Carro.new(:chevrolet,:corsa,:preto,50) corsa.cor = :branco puts corsa Listagem 1.151: Acessando e escrevendo propriedades $ ruby code/carro6.rb Marca:chevrolet Modelo:corsa Cor:branco Tanque:50 Dica: Se precisarmos de objetos com atributos com escrita e leitura, podemos usar duas formas bem rpidas para criarmos nossos objetos. Uma usando Struct: Carro = Struct.new(:marca,:modelo,:tanque,:cor) corsa = Carro.new(:chevrolet,:corsa,:preto,50) p corsa => #<struct Carro marca=:chevrolet, modelo=:corsa, tanque=:preto, cor=50> Outra mais exvel ainda, usando OpenStruct, onde os atributos so criados na hora que precisamos deles: require "ostruct" carro = OpenStruct.new carro.marca = :chevrolet carro.modelo = :corsa carro.cor = :preto carro.tanque = 50 p carro => #<OpenStruct tanque=50, modelo=:corsa, cor=:preto, marca=:chevrolet>

48

1.4. CLASSES E OBJETOS Tambm podemos criar atributos virtuais, que nada mais so do que mtodos que agem como se fossem atributos do objeto. Vamos supor que precisamos de uma medida como gales, que equivalem a 3,785 litros, para o tanque do carro. Poderamos fazer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

class Carro attr_reader :marca, :modelo, :tanque attr_accessor :cor def initialize(marca,modelo,cor,tanque) @marca = marca @modelo = modelo @cor = cor @tanque = tanque end def to_s "Marca:#{@marca} Modelo:#{@modelo} Cor:#{@cor} Tanque:#{@tanque}" end def galoes @tanque / 3.785 end end corsa = Carro.new(:chevrolet,:corsa,:preto,50) corsa.cor = :branco puts corsa.galoes Listagem 1.152: Atributos virtuais $ ruby code/carro7.rb 13.21003963011889

1.4.1

Classes abertas

Uma diferena de Ruby com vrias outras linguagens que as suas classes so abertas, ou seja, podemos alter-las depois que as declararmos. Por exemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

# encoding: utf-8 class Carro attr_reader :marca, :modelo, :tanque attr_accessor :cor def initialize(marca,modelo,cor,tanque) @marca = marca @modelo = modelo @cor = cor @tanque = tanque end def to_s "Marca:#{@marca} Modelo:#{@modelo} Cor:#{@cor} Tanque:#{@tanque}" end end corsa = Carro.new(:chevrolet,:corsa,:preto,50)

49

CAPTULO 1. RUBY
20 21 22 23 24 25 26 27 28 29 30

class Carro def novo_metodo puts "Novo mtodo!" end end corsa.novo_metodo class Carro remove_method :novo_metodo end corsa.novo_metodo Listagem 1.153: Classes abertas $ ruby code/carro8.rb Novo mtodo! code/carro8.rb:30:in <main>: undefined method novo_metodo for Marca:chevrolet Modelo:corsa Cor:preto Tanque:50:Carro (NoMethodError) Pude inserir e remover um mtodo que incorporado aos objetos que foram denidos sendo daquela classe e para os novos a serem criados tambm. Tambm pudemos remover o mtodo, o que gerou a mensagem de erro.

1.4.2

Aliases

Se por acaso quisermos guardar uma cpia do mtodo que vamos redenir, podemos usar alias_method para dar outro nome para ele:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

# encoding: utf-8 class Carro attr_reader :marca, :modelo, :tanque attr_accessor :cor def initialize(marca,modelo,cor,tanque) @marca = marca @modelo = modelo @cor = cor @tanque = tanque end def to_s "Marca:#{@marca} Modelo:#{@modelo} Cor:#{@cor} Tanque:#{@tanque}" end end class Carro alias_method :to_s_old, :to_s def to_s "Esse um novo jeito de mostrar isso: #{to_s_old}" end end carro = Carro.new(:chevrolet,:corsa,:preto,50) puts carro puts carro.to_s_old Listagem 1.154: Classes abertas

50

1.4. CLASSES E OBJETOS $ ruby code/methalias.rb Esse um novo jeito de mostrar isso: Marca:chevrolet Modelo:corsa Cor:preto Tanque:50 Marca:chevrolet Modelo:corsa Cor:preto Tanque:50

1.4.3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

Inserindo e removendo mtodos

Podemos tambm inserir um mtodo somente em uma determinada instncia: # encoding: utf-8 class Carro attr_reader :marca, :modelo, :tanque attr_accessor :cor def initialize(marca,modelo,cor,tanque) @marca = marca @modelo = modelo @cor = cor @tanque = tanque end def to_s "Marca:#{@marca} Modelo:#{@modelo} Cor:#{@cor} Tanque:#{@tanque}" end end corsa = Carro.new(:chevrolet,:corsa,:preto,50) gol = Carro.new(:volks,:gol,:azul,42) class << corsa def novo_metodo puts "Novo mtodo!" end end corsa.novo_metodo gol.novo_metodo Listagem 1.155: Classes abertas $ ruby code/insmethinst.rb Novo mtodo! code/insmethinst.rb:28:in <main>: undefined method novo_metodo for Marca:volks Modelo:gol Cor:azul Tanque:42:Carro (NoMethodError) Podemos ver que no caso do corsa, o novo mtodo foi adicionado, mas no no gol. O que aconteceu ali com o operador ? Hora de algumas explicaes sobre metaclasses!

1.4.4

Metaclasses

Todo objeto em Ruby em uma hierarquia de ancestrais, que podem ser vistos utilizando ancestors, como:
1 2 3 4

class Teste end p String.ancestors p Teste.ancestors Listagem 1.156: Vericando os ancestrais 51

CAPTULO 1. RUBY $ ruby ancestors.rb [String, Comparable, Object, Kernel, BasicObject] [Teste, Object, Kernel, BasicObject] E cada objeto tem a sua superclasse:
1 2 3 4 5 6 7 8 9

class Teste end class OutroTeste < Teste end p String.superclass p Teste.superclass p OutroTeste.superclass Listagem 1.157: Vericando os ancestrais $ ruby superclasses.rb Object Object Teste Todos os objetos no Ruby 1.9 so derivados de BasicObject, que o que chamamos de Blank Slate, que um objeto que tem menos mtodos que Object.

1 2

BasicObject.instance_methods => [:, :equal?, :!, :!, :instance_eval, :instance_exec, :__send__] Listagem 1.158: BasicObject O que ocorreu no exemplo da insero do mtodo na instncia acima, que o mtodo foi inserido na metaclasse, ou eigenclass, ou classe singleton, ou "classe fantasma"do objeto, que adiciona um novo elo na hierarquia dos ancestrais da classe da qual a instncia pertence, ou seja, o mtodo foi inserido antes da classe Carro. A procura do mtodo (method lookup) se d na eigenclass da instncia, depois na hierarquia de ancestrais.

Dica: em linguagens de tipagem esttica, o compilador checa se o objeto receiver tem um mtodo com o nome especicado. Isso chamdo checagem esttica de tipos(static type checking), da o nome da caracterstica dessas linguagens. Para isso car mais legal e prtico, vamos ver como fazer dinamicamente, j comeando a brincar com metaprogramao 2 . Primeiro, com a classe:
1 2 3 4 5 6 7 8 9 10 11

class Carro attr_reader :marca, :modelo, :tanque attr_accessor :cor def initialize(marca,modelo,cor,tanque) @marca = marca @modelo = modelo @cor = cor @tanque = tanque end
2 Metaprogramao

escrever cdigo que manipula a linguagem em runtime

52

1.4. CLASSES E OBJETOS


12 13 14 15 16 17 18 19 20 21 22 23 24 25

def to_s "Marca:#{@marca} Modelo:#{@modelo} Cor:#{@cor} Tanque:#{@tanque}" end end corsa = Carro.new(:chevrolet,:corsa,:preto,50) gol = Carro.new(:volks,:gol ,:azul ,42) Carro.send(:define_method,"multiplica_tanque") do |valor| @tanque * valor end puts corsa.multiplica_tanque(2) puts gol.multiplica_tanque(2) Listagem 1.159: Criando dinamicamente metodos na classe $ ruby code/carro9.rb 100 84 Dica: Usamos send para acessar um mtodo privado da classe. Agora, com as instncias:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

class Carro attr_reader :marca, :modelo, :tanque attr_accessor :cor def initialize(marca,modelo,cor,tanque) @marca = marca @modelo = modelo @cor = cor @tanque = tanque end def to_s "Marca:#{@marca} Modelo:#{@modelo} Cor:#{@cor} Tanque:#{@tanque}" end end corsa = Carro.new(:chevrolet,:corsa,:preto,50) gol = Carro.new(:volks,:gol ,:azul ,42) (class << corsa; self; end).send(:define_method,"multiplica_tanque") do | valor| @tanque * valor end puts corsa.multiplica_tanque(2) puts gol.multiplica_tanque(2) Listagem 1.160: Criando dinamicamente metodos na instancia 100 code/carro10.rb:25:in <main>: undefined method multiplica_tanque for Marca:volks Modelo:gol Cor:azul Tanque:42:Carro (NoMethodError) 53

21 22 23 24 25

CAPTULO 1. RUBY Dica: Depois de ver tudo isso sobre insero e remoo de mtodos dinamicamente, vamos ver um truquezinho para criar um mtodo autodestrutivo:
1 2 3 4 5 6 7 8 9 10 11 12

class Teste def apenas_uma_vez def self.apenas_uma_vez raise Exception, "Esse metodo se destruiu!" end puts "Vou rodar apenas essa vez hein?" end end teste = Teste.new teste.apenas_uma_vez teste.apenas_uma_vez Listagem 1.161: Metodo autodestrutivo $ ruby code/autodestruct.rb Vou rodar apenas essa vez hein? code/autodestruct.rb:4:in apenas_uma_vez: Esse metodo se destruiu! (Exception) from code/autodestruct.rb:12:in <main>

1.4.5

Variveis de classe

Tambm podemos ter variveis de classes, que so variveis que se encontram no contexto da classe e no das instncias dos objetos da classe. Variveis de classes tem o nome comeado com @@ e devem ser inicializadas antes de serem usadas. Por exemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

class Carro attr_reader :marca, :modelo, :tanque attr_accessor :cor @@qtde = 0 def initialize(marca,modelo,cor,tanque) @marca = marca @modelo = modelo @cor = cor @tanque = tanque @@qtde += 1 end def to_s "Marca:#{@marca} Modelo:#{@modelo} Cor:#{@cor} Tanque:#{@tanque}" end def qtde @@qtde end end corsa gol ferrari 54 = Carro.new(:chevrolet,:corsa,:preto,50) = Carro.new(:volks,:gol,:azul ,42) = Carro.new(:ferrari,:viper,:vermelho ,70)

1.4. CLASSES E OBJETOS


26

puts ferrari.qtde Listagem 1.162: Variaveis de classe $ ruby code/classvar1.rb 3 Para que no precisemos acessar a varivel atravs de uma instncia, podemos criar um mtodo de classe:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

class Carro attr_reader :marca, :modelo, :tanque attr_accessor :cor @@qtde = 0 def initialize(marca,modelo,cor,tanque) @marca = marca @modelo = modelo @cor = cor @tanque = tanque @@qtde += 1 end def to_s "Marca:#{@marca} Modelo:#{@modelo} Cor:#{@cor} Tanque:#{@tanque}" end def self.qtde @@qtde end end corsa = Carro.new(:chevrolet,:corsa,:preto,50) gol = Carro.new(:volks ,:gol ,:azul ,42) ferrari = Carro.new(:ferrari,:enzo ,:vermelho ,70) puts Carro.qtde Listagem 1.163: Metodo para variaveis de classe $ ruby code/classvar2.rb 3 Um problema que acontece com as variveis de classe utilizando @@ que elas no pertencem realmente s classes, e sim hierarquias, podendo permear o cdigo dessa maneira:

1 2 3 4 5 6 7 8 9 10 11 12 13

@@qtde = 10 class Carro attr_reader :marca, :modelo, :tanque attr_accessor :cor @@qtde = 0 puts self def initialize(marca,modelo,cor,tanque) @marca = marca @modelo = modelo @cor = cor @tanque = tanque 55

CAPTULO 1. RUBY
14 15 16 17 18 19 20 21 22 23 24 25 26 27

@@qtde end

+= 1

def to_s "Marca:#{@marca} Modelo:#{@modelo} Cor:#{@cor} Tanque:#{@tanque}" end def self.qtde @@qtde end end puts self puts @@qtde Listagem 1.164: Variaveis de classe rebeldes $ ruby code/classvar3.rb Carro main 0 Est certo que esse no um cdigo comum de se ver, mas j d para perceber algum estrago quando as variveis @@ so utilizadas dessa maneira. Podemos prevenir isso usando variveis de instncia de classe :

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

class Carro attr_reader :marca, :modelo, :tanque attr_accessor :cor class << self attr_accessor :qtde end @qtde = 0 def initialize(marca,modelo,cor,tanque) @marca = marca @modelo = modelo @cor = cor @tanque = tanque self.class.qtde += 1 end def to_s "Marca:#{@marca} Modelo:#{@modelo} Cor:#{@cor} Tanque:#{@tanque}" end end corsa = Carro.new(:chevrolet,:corsa,:preto,50) gol = Carro.new(:volks ,:gol ,:azul ,42) ferrari = Carro.new(:ferrari,:enzo ,:vermelho ,70) puts Carro.qtde Listagem 1.165: Variaveis de instancia de classe $ ruby code/classvar4.rb 3 56

1.4. CLASSES E OBJETOS Herana Muito simples:


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

class Carro attr_reader :marca, :modelo, :tanque attr_accessor :cor @@qtde = 0 def initialize(marca,modelo,cor,tanque) @marca = marca @modelo = modelo @cor = cor @tanque = tanque @@qtde += 1 end def to_s "Marca:#{@marca} Modelo:#{@modelo} Cor:#{@cor} Tanque:#{@tanque}" end def self.qtde @@qtde end end class NovoCarro < Carro def to_s "Marca nova:#{@marca} Modelo:#{@modelo} Cor:#{@cor} Tanque:#{@tanque} " end end carro1 = Carro.new(:chevrolet,:corsa,:preto,50) carro2 = Carro.new(:chevrolet,:corsa,:prata,50) novo_carro = NovoCarro.new(:volks,:gol,:azul,42) puts puts puts puts puts carro1 carro2 novo_carro Carro.qtde NovoCarro.qtde Listagem 1.166: Herdando de outra classe $ ruby code/carro11.rb Marca:chevrolet Modelo:corsa Cor:preto Tanque:50 Marca:chevrolet Modelo:corsa Cor:prata Tanque:50 Marca nova:volks Modelo:gol Cor:azul Tanque:42 3 3 Poderamos ter modicado para usar o mtodo super:

26 27 28 29 30 31 32 33 34 35 36 37

1 2 3 4

class Carro attr_reader :marca, :modelo, :tanque attr_accessor :cor @@qtde = 0 57

CAPTULO 1. RUBY
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

def initialize(marca,modelo,cor,tanque) @marca = marca @modelo = modelo @cor = cor @tanque = tanque @@qtde += 1 end def to_s "Marca:#{@marca} Modelo:#{@modelo} Cor:#{@cor} Tanque:#{@tanque}" end end class NovoCarro < Carro def to_s "Novo Carro: "+super end end carro = Carro.new(:chevrolet,:corsa,:preto,50) novo_carro = NovoCarro.new(:volks,:gol,:azul,42) puts carro puts novo_carro Listagem 1.167: Usando super $ ruby code/carro12.rb Marca:chevrolet Modelo:corsa Cor:preto Tanque:50 Novo Carro: Marca:volks Modelo:gol Cor:azul Tanque:42 O mtodo super chama o mesmo mtodo da classe pai. Sem parnteses, ele envia os mesmos argumentos recebidos pelo mtodo corrente para o mtodo pai. Com parnteses, ele envia os argumentos selecionados:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

class Teste def metodo(parametro1) puts parametro1 end end class NovoTeste < Teste def metodo(parametro1,parametro2) super(parametro1) puts parametro2 end end t1 = Teste.new t2 = NovoTeste.new t1.metodo(1) t2.metodo(2,3) Listagem 1.168: Usando super com argumentos $ ruby code/supermeth.rb 58

1.4. CLASSES E OBJETOS 1 2 3

Dica: Podemos utilizar um hook para descobrir quando uma classe herda de outra:
1 2 3 4 5 6 7 8

class Pai def self.inherited(child) puts "#{child} herdando de #{self}" end end class Filha < Pai end Listagem 1.169: Herdando e usando um hook $ ruby code/inherited.rb Filha herdando de Pai

1.4.6

Duplicando de modo raso e profundo

Podemos duplicar um objeto usando dup e gerando um novo objeto. Essa funcionalidade est implementada automaticamente para os objetos que so instncias da nossa classe, mas ca uma dica: podemos ter propriedades diferentes ao efetuar a cpia. Para isso, utilizamos initialize_copy, que vai ser chamado quando o objeto for duplicado:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

class Carro attr_reader :marca, :modelo, :tanque, :criado attr_accessor :cor def initialize(marca,modelo,cor,tanque) @marca = marca @modelo = modelo @cor = cor @tanque = tanque @criado = Time.now end def initialize_copy(outra) @criado = Time.now end def to_s "Marca:#{@marca} Modelo:#{@modelo} Cor:#{@cor} Tanque:#{@tanque}" end end carro = Carro.new(:chevrolet,:corsa,:preto,50) puts carro.criado sleep 1 outro_carro = carro.dup 59

CAPTULO 1. RUBY
27

puts outro_carro.criado Listagem 1.170: Inicializando uma duplicata

$ ruby code/initializecopy.rb 2011-06-29 22:36:10 -0300 2011-06-29 22:36:11 -0300 Vale lembrar que cpias de objetos em Ruby usando dup so feitas usando o conceito de shallow copy, que duplica um objeto mas no os objetos referenciados dentro dele. Vamos ver um exemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

class A attr_reader :outro def initialize(outro=nil) @outro = outro end def show puts "Estou em #{self.class.name}, #{object_id}" puts "Outro: #{@outro.object_id}" if !@outro.nil? end end class B < A end a = A.new b = B.new(a) a.show b.show b2 = b.dup b2.show Listagem 1.171: Shallow copy Rodando o programa: $ ruby code/shallow.rb Estou em A, 75626430 Estou em B, 75626420 Outro: 75626430 <===== aqui! Estou em B, 75626300 Outro: 75626430 <===== aqui! Pudemos ver que o objeto que consta na varivel b foi duplicado, porm o objeto que consta na referncia em a continua o mesmo em b2! Para evitar esse tipo de coisa, precisamos do conceito de deep copy, que ir duplicar o objeto e os objetos dentro dele, retornando objetos totalmente novos. Em Ruby isso pode ser alcanado atravs de serializao utilizando Marshal, armazenando os objetos como um uxo de dados binrios e depois restaurando todos em posies de memria totalmente novas: 60

1.4. CLASSES E OBJETOS


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

class A attr_accessor :outro def initialize(outro=nil) @outro = outro end def show puts "Estou em #{self.class.name}, #{object_id}" puts "Outro: #{@outro.object_id}" if !@outro.nil? end end class B < A end a = A.new b = B.new(a) a.show b.show b2 = Marshal.load(Marshal.dump(b)) b2.show Listagem 1.172: Deep copy Rodando o programa: $ ruby code/deep.rb Estou em A, 74010500 Estou em B, 74010490 Outro: 74010500 <===== aqui! Estou em B, 74010330 Outro: 74010300 <===== aqui!

1.4.7

Brincando com mtodos dinmicos e hooks

Podemos emular o comportamento de uma OpenStruct utilizando o mtodo method_missing, que chamado caso o seu objeto o tenha declarado, sempre que ocorrer uma exceo do tipo NoMethodError, ou seja, quando o mtodo que tentamos acessar no existe:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

class Teste def method_missing(meth,value=nil) # *args,&block sanitized = meth.to_s.split("=").first if meth =~ /=$/ self.class.send(:define_method,meth) {|val| instance_variable_set("@#{sanitized}",val) } self.send(meth,value) else self.class.send(:define_method,sanitized) { instance_variable_get("@#{sanitized}") } self.send(meth) end end end t = Teste.new t.oi = "oi, mundo!" 61

CAPTULO 1. RUBY
17 18 19 20 21

puts t.oi puts t.hello t.hello = "hello, world!" puts t.hello Listagem 1.173: Usando metodos ainda inexistentes

$ ruby code/methmissing.rb oi, mundo! hello, world! Vamos aproveitar e testar dois hooks para mtodos, o method_added e method_removed:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

# encoding: utf-8 class Teste def self.method_added(meth) puts "Adicionado o mtodo #{meth}" end def self.method_removed(meth) puts "Removido o mtodo #{meth}" end end t = Teste.new t.class.send(:define_method,"teste") { puts "teste!" } t.teste t.class.send(:remove_method,:teste) t.teste Listagem 1.174: Usando hooks

$ ruby code/hooksmeth.rb Adicionado o mtodo teste teste! Removido o mtodo teste code/hooksmeth.rb:16:in <main>: undefined method teste for #<Teste:0x9f3d12c> (NoMethodError) Podemos denir "mtodos fantasmas"(ghost methods) (buuuuu!), brincando com method_missing:
1 2 3 4 5 6 7 8

# encoding: utf-8 class Teste def method_missing(meth) puts "No sei o que fazer com a sua requisio: #{meth}" end end t = Teste.new t.teste Listagem 1.175: Fantasmas!

$ ruby code/ghost.rb No sei o que fazer com a sua requisio: teste 62

1.4. CLASSES E OBJETOS

1.4.8

Manipulando mtodos que se parecem com operadores

Vamos imaginar que temos uma classe chamada CaixaDeParafusos e queremos algum jeito de fazer ela interagir com outra, por exemplo, adicionando o contedo de um outra (e esvaziando a que cou sem contedo). Podemos fazer coisas do tipo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

class CaixaDeParafusos attr_reader :quantidade def initialize(quantidade) @quantidade = quantidade end def to_s "Quantidade de parafusos na caixa #{self.object_id}: #{@quantidade}" end def +(outra) CaixaDeParafusos.new(@quantidade+outra.quantidade) end end caixa1 = CaixaDeParafusos.new(10) caixa2 = CaixaDeParafusos.new(20) caixa3 = caixa1 + caixa2 puts caixa1 puts caixa2 puts caixa3 Listagem 1.176: Somando caixas de parafusos $ ruby code/caixa1.rb Quantidade de parafusos na caixa 69826490: 10 Quantidade de parafusos na caixa 69826480: 20 Quantidade de parafusos na caixa 69826470: 30 Mas espera a! Se eu somei uma caixa com a outra em uma terceira, no deve ter sobrado nada nas caixas originais, mas ao invs disso elas continuam intactas. Precisamos zerar a quantidade de parafusos das outras caixas:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

class CaixaDeParafusos attr_reader :quantidade def initialize(quantidade) @quantidade = quantidade end def to_s "Quantidade de parafusos na caixa #{self.object_id}: #{@quantidade}" end def +(outra) CaixaDeParafusos.new(@quantidade+outra.quantidade) @quantidade = 0 outra.quantidade = 0 end end 63

CAPTULO 1. RUBY
18 19 20 21 22 23 24 25

caixa1 = CaixaDeParafusos.new(10) caixa2 = CaixaDeParafusos.new(20) caixa3 = caixa1 + caixa2 puts caixa1 puts caixa2 puts caixa3 Listagem 1.177: Somando e zerando caixas de parafusos $ ruby code/caixa2.rb code/caixa2.rb:15:in +: undefined method quantidade= for Quantidade de parafusos na caixa 74772290: 20:CaixaDeParafusos (NoMethodError) from code/caixa2.rb:21:in <main> Parece que ocorreu um erro ali. Ah, essa est fcil. Tentamos acessar a varivel de instncia da outra caixa enviada como parmetro mas no temos um attr_writer para ela! Mas espera a: s queremos que essa propriedade seja alterada quando efetuando alguma operao com outra caixa de parafusos ou alguma classe lha, e no seja acessada por qualquer outra classe. Nesse caso, podemos usar um mtodo protegido:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

class CaixaDeParafusos protected attr_writer :quantidade public attr_reader :quantidade def initialize(quantidade) @quantidade = quantidade end def to_s "Quantidade de parafusos na caixa #{self.object_id}: #{@quantidade}" end def +(outra) nova = CaixaDeParafusos.new(@quantidade+outra.quantidade) @quantidade = 0 outra.quantidade = 0 nova end end caixa1 = CaixaDeParafusos.new(10) caixa2 = CaixaDeParafusos.new(20) caixa3 = caixa1 + caixa2 puts caixa1 puts caixa2 puts caixa3 Listagem 1.178: Corretamente somando e zerando caixas de parafusos $ ruby code/caixa3.rb 64

1.4. CLASSES E OBJETOS Quantidade de parafusos na caixa 81467020: 0 Quantidade de parafusos na caixa 81467010: 0 Quantidade de parafusos na caixa 81467000: 30 Agora pudemos ver que tudo funcionou perfeitamente, pois utilizamos protected antes de inserir o attr_writer. Os modicadores de controle de acesso de mtodos so: P BLICOS Podem ser acessados por qualquer mtodo em qualquer objeto. P RIVADOS S podem ser chamados dentro de seu prprio objeto, mas nunca possvel acessar um mtodo privado de outro objeto, mesmo se o objeto que chama seja uma subclasse de onde o mtodo foi denido. P ROTEGIDOS Podem ser acessados em seus descendentes. Dica: Usando a analogia do livro para lembrar do acesso dos mtodos: Vamos supor que voc seja dono de um restaurante. Como voc no quer que seus fregueses quem apertados voc manda fazer um banheiro para o pessoal, mas nada impede tambm que aparea algum maluco da rua apertado, entre no restaurante e use seu banheiro (ainda mais se ele tiver 2 metros de altura, 150 kg e for lutador de alguma arte marcial). Esse banheiro pblico. Para seus empregados, voc faz um banheirinho mais caprichado, que s eles tem acesso. Esse banheiro protegido, sendo que s quem do restaurante tem acesso. Mas voc sabe que tem um empregado seu l que tem uns problemas e ao invs de utilizar o banheiro, ele o inutiliza. Como voc tem enjoos com esse tipo de coisa, manda fazer um banheiro privado para voc, que s voc pode usar. Agora vamos supor que queremos dividir uma caixa em caixas menores com contedos xos e talvez o resto que sobrar em outra. Podemos usar o mtodo /:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

class CaixaDeParafusos protected attr_writer :quantidade public attr_reader :quantidade def initialize(quantidade) @quantidade = quantidade end def to_s "Quantidade de parafusos na caixa #{self.object_id}: #{@quantidade}" end def +(outra) nova = CaixaDeParafusos.new(@quantidade+outra.quantidade) @quantidade = 0 outra.quantidade = 0 nova end def /(quantidade) caixas = Array.new(@quantidade/quantidade,quantidade) 65

CAPTULO 1. RUBY
25 26 27 28 29 30 31 32 33 34 35

caixas << @quantidade%quantidade if @quantidade%quantidade>0 @quantidade = 0 caixas.map {|quantidade| CaixaDeParafusos.new(quantidade)} end end caixa1 = CaixaDeParafusos.new(10) caixa2 = CaixaDeParafusos.new(20) caixa3 = caixa1 + caixa2 puts caixa3 / 8 Listagem 1.179: Dividindo a caixa de parafusos $ ruby code/caixa4.rb Quantidade de parafusos Quantidade de parafusos Quantidade de parafusos Quantidade de parafusos

na na na na

caixa caixa caixa caixa

67441310: 67441300: 67441290: 67441280:

8 8 8 6

Ou podemos simplesmente pedir para dividir o contedo em X caixas menores, distribuindo uniformemente o seu contedo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34

class CaixaDeParafusos protected attr_writer :quantidade public attr_reader :quantidade def initialize(quantidade) @quantidade = quantidade end def to_s "Quantidade de parafusos na caixa #{self.object_id}: #{@quantidade}" end def +(outra) nova = CaixaDeParafusos.new(@quantidade+outra.quantidade) @quantidade = 0 outra.quantidade = 0 nova end def /(quantidade) caixas = Array.new(quantidade,@quantidade/quantidade) (@quantidade%quantidade).times {|indice| caixas[indice] += 1} @quantidade = 0 caixas.map {|quantidade| CaixaDeParafusos.new(quantidade)} end end caixa1 = CaixaDeParafusos.new(10) caixa2 = CaixaDeParafusos.new(20) caixa3 = caixa1 + caixa2

66

1.4. CLASSES E OBJETOS


35

puts caixa3 / 4 Listagem 1.180: Divindo a caixa de parafusos em caixas menores $ ruby code/caixa5.rb Quantidade de parafusos Quantidade de parafusos Quantidade de parafusos Quantidade de parafusos

na na na na

caixa caixa caixa caixa

81385900: 81385890: 81385880: 81385870:

8 8 7 7

1.4.9

Closures

Vamos fazer um gancho aqui falando em classes e mtodos para falar um pouco de closures. Closures so funes annimas com escopo fechado que mantm o estado do ambiente em que foram criadas. Os blocos de cdigo que vimos at agora eram todos closures, mas para dar uma dimenso do fato de closures guardarem o seu ambiente podemos ver:
1 2 3 4 5 6 7 8 9 10

def cria_contador(inicial,incremento) contador = inicial lambda { contador += incremento } end meu_contador = cria_contador(0,1) puts meu_contador.call puts meu_contador.call puts meu_contador.call Listagem 1.181: Closures $ ruby code/closures.rb 1 2 3 A Proc foi criada pela lambda na linha 3, que guardou a referncia para a varivel contador mesmo depois que saiu do mtodo cria_contador.

67

CAPTULO 1. RUBY

1.5

Mdulos

Ruby tem herana nica, mas conta com o conceito de mdulos como mixins para a incorporao de funcionalidades adicionais, usando include:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

class Primata def come puts "Nham!" end def dorme puts "Zzzzzz..." end end class Humano < Primata def conecta_na_web puts "Login ... senha ..." end end module Ave def voa puts "Para o alto, e avante!" end end class Mutante < Humano include Ave end mutante = Mutante.new mutante.come mutante.dorme mutante.conecta_na_web mutante.voa Listagem 1.182: Criando um mutante $ ruby code/mod1.rb Nham! Zzzzzz... Login ... senha ... Para o alto, e avante! Como pudemos ver, podemos mixar vrias caractersticas de um mdulo em uma classe. Isso poderia ter sido feito para apenas uma instncia usando extend, dessa forma:

1 2 3 4 5 6 7 8 9 10 11

class Primata def come puts "Nham!" end def dorme puts "Zzzzzz..." end end class Humano < Primata def conecta_na_web 68

1.5. MDULOS
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33

puts "Login ... senha ..." end end module Ave def voa puts "Para o alto, e avante!" end end class Mutante < Humano end mutante = Mutante.new mutante.extend(Ave) mutante.come mutante.dorme mutante.conecta_na_web mutante.voa mutante2 = Mutante.new mutante2.voa Listagem 1.183: Criando um mutante para uma instancia $ ruby code/mod2.rb Nham! Zzzzzz... Login ... senha ... Para o alto, e avante! code/mod2.rb:33:in <main>: undefined method voa for #<Mutante:0x855465c> (NoMethodError) Dica: O mtodo extend inclui os mtodos de um mdulo na eingenclass do objeto onde est sendo executado. Uma coisa bem importante a ser notada que quanto usamos include os mtodos provenientes do mdulo so includos nas instncias das classes, e no nas classes em si. Se quisermos denir mtodos de classes dentro dos mdulos, podemos utilizar um outro hook chamado included, usando um mdulo interno (???): Dica: os mtodos dos mdulos so inseridos nas procura dos mtodos method lookup logo antes da classe que os incluiu.

1 2 3 4 5 6 7 8 9 10

# encoding: utf-8 module TesteMod module ClassMethods def class_method puts "Esse um mtodo da classe!" end end def self.included(where) where.extend(ClassMethods) end 69

CAPTULO 1. RUBY
11 12 13 14 15 16 17 18 19 20 21 22

def instance_method puts "Esse um mtodo de instncia!" end end class TesteCls include TesteMod end t = TesteCls.new t.instance_method TesteCls.class_method Listagem 1.184: Inserindo o modulo com metodos de classe $ ruby code/mod7.rb Esse um mtodo de instncia! Esse um mtodo da classe! Se incluirmos o mdulo em uma classe, os mtodos do mdulo se tornam mtodos das instncias da classe. Se incluirmos o mdulo na eigenclass da classe, se tornam mtodos da classe. Se incluirmos em uma instncia da classe, se tornam mtodos singleton do objeto em questo. Temos alguns comportamentos bem teis usando mixins. Alguns nos pedem apenas um mtodo para dar em troca vrios outros. Se eu quisesse implementar a funcionalidade do mdulo Comparable no meu objeto, eu s teria que fornecer um mtodo <=> (starship, "navinha") e incluir o mdulo:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

class CaixaDeParafusos include Comparable attr_reader :quantidade def initialize(quantidade) @quantidade = quantidade end def <=>(outra) self.quantidade <=> outra.quantidade end end caixa1 = CaixaDeParafusos.new(10) caixa2 = CaixaDeParafusos.new(20) caixa3 = CaixaDeParafusos.new(10) puts puts puts puts puts caixa1 < caixa2 caixa2 > caixa3 caixa1 == caixa3 caixa3 > caixa2 caixa1.between?(caixa3,caixa2) Listagem 1.185: Inserindo o modulo Comparable $ ruby code/mod3.rb true true 70

1.5. MDULOS true false true Com isso ganhamos os mtodos <, <=, ==, >, >= e between?. Vamos criar um iterador mixando o mdulo Enumerable:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44

# encoding: utf-8 class Parafuso attr_reader :polegadas def initialize(polegadas) @polegadas = polegadas end def <=>(outro) self.polegadas <=> outro.polegadas end def to_s "Parafuso #{object_id} com #{@polegadas}\"" end end class CaixaDeParafusos include Enumerable def initialize @parafusos = [] end def <<(parafuso) @parafusos << parafuso end def each @parafusos.each {|numero| yield(numero) } end end caixa caixa caixa caixa = CaixaDeParafusos.new << Parafuso.new(1) << Parafuso.new(2) << Parafuso.new(3)

puts "o menor parafuso na caixa : #{caixa.min}" puts "o maior parafuso na caixa : #{caixa.max}" puts "os parafusos com medidas par so: #{caixa.select {|parafuso| parafuso.polegadas%2==0}.join(,)}" puts "duplicando a caixa: #{caixa.map {|parafuso| Parafuso.new(parafuso.polegadas*2)}}" Listagem 1.186: Inserindo o modulo Enumerable $ ruby code/mod4.rb o menor parafuso na caixa : Parafuso 72203410 com 1" o maior parafuso na caixa : Parafuso 72203390 com 3" 71

CAPTULO 1. RUBY os parafusos com medidas par so: Parafuso 72203400 com 2" duplicando a caixa: [Parafuso 72203110 com 2", Parafuso 72203100 com 4", Parafuso 72203090 com 6"] Mdulos tambm podem ser utilizados como namespaces:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

module Paulista class Pessoa def comida_preferida "pizza" end end end module Gaucho class Pessoa def comida_preferida "churrasco" end end end pessoa1 = Paulista::Pessoa.new pessoa2 = Gaucho::Pessoa.new puts pessoa1.comida_preferida puts pessoa2.comida_preferida Listagem 1.187: Modulos como namespaces $ ruby code/mod5.rb pizza churrasco Podemos implementar algumas funcionalidades interessantes com mdulos, por exemplo, criar uma classe Singleton:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

# encoding: utf-8 require "singleton" class Teste include Singleton end begin Teste.new rescue => exception puts "No consegui criar usando new: #{exception}" end puts Teste.instance.object_id puts Teste.instance.object_id Listagem 1.188: Criando uma classe singleton $ ruby code/mod6.rb No consegui criar usando new: private method new called for Teste:Class 69705530 69705530 72

1.5. MDULOS Antes de ver mais uma funcionalidade bem legal, vamos ver como fazemos para instalar pacotes novos que vo nos prover essas funcionalidades, atravs das RubyGems.

1.5.1

Instalando pacotes novos atravs do RubyGems

O RubyGems um projeto feito para gerenciar as gems, que so pacotes com aplicaes ou bibliotecas Ruby, com nome e nmero de verso. O suporte gems j se encontra instalado, pois instalamos o nosso interpretador Ruby com a RVM. Se no estivermos utilizando a RVM, apesar de alguns sistemas operacionais j terem pacotes prontos, recomenda-se instalar a partir do cdigo-fonte. Para isso, necessrio ter um interpretador de Ruby instalado e seguir os seguintes passos (lembrando de vericar qual a ltima verso disponvel em http://rubygems.org 3 e executar os comandos seguintes como root ou usando sudo): wget http://production.cf.rubygems.org/rubygems/rubygems-1.8.5.tgz tar xvzf rubygems-1.8.5.tgz cd rubygems-1.8.5 ruby setup.rb gem -v => 1.8.5 Dica: Certique-se de ter instalado a biblioteca zlib (dependendo da sua distribuio, o pacote zlib-devel tambm. Aps instalado, vamos dar uma olhada em algumas opes que temos, sempre usando a opo como parmetro do comando gem :
LIST

Essa opo lista as gems atualmente instaladas. Por no termos ainda instalado nada, s vamos encontrar os sources do RubyGems. Instala a gem requisitada. No nosso caso, vamos instalar a gem memoize, que vamos utilizar logo a seguir: gem install memoize Successfully installed memoize-1.3.1 Installing ri documentation for memoize-1.3.1... Installing RDoc documentation for memoize-1.3.1...

INSTALL

UPDATE

Atualiza a gem especica ou todas instaladas. Voc pode usar -include-dependencies para instalar todas as dependncias necessrias. Lista as gems que precisam de atualizao no seu computador.

OUTDATED CLEANUP

Essa uma opo muito importante aps rodar o update. Para evitar que algo se quebre por causa do uso de uma verso especica de um gem, o RubyGems mantm todas as verses antigas at que voc execute o cleanup. Mas preste ateno se alguma aplicao no precisa de uma verso especca - e antiga - de alguma gem. Desinstala uma gem.

UNINSTALL SEARCH

Procura uma determinada palavra em uma gem: gem search memo *** LOCAL GEMS *** memoize (1.2.3)

3 http://rubygems.org

73

CAPTULO 1. RUBY Dica: Depois de instalado, para atualizar o prprio RubyGems use a opo: gem update -system Instalamos essa gem especico para vericar uma funcionalidade muito interessante, a memoization, que acelera a velocidade do programa armazenando os resultados de chamadas aos mtodos para recuperao posterior. Antes de mais nada temos que indicar que vamos usar as gems atravs de require rubygems. Sem isso o programa no ir saber que desejamos usar as gems, ento "no-no-no se esquea disso, Babalu!". Algumas instalaes j carregam automaticamente, mas no custa prevenir.

Dica: No confunda require com include ou com o mtodo load. Usamos include para inserir os mdulos, require para carregar "bibliotecas"de cdigo e load para carregar e executar cdigo, que pode ter o cdigo carregado como um mdulo annimo, que imediatamente destrudo aps o seu uso, se enviarmos true como o segundo argumento:
1

load("load2.rb",true) Listagem 1.189: Usando load $ ruby load1.rb sendo executado em isolamento

Agora vamos dar uma olhada na tal da memoization. Vamos precisar de um mtodo com muitas chamadas, ento vamos usar um recursivo. Que tal a sequncia de Fibonacci 4 ? Primeiro vamos ver sem usar memoization:
1 2 3 4 5 6 7 8

def fib(numero) return numero if numero < 2 fib(numero-1)+fib(numero-2) end puts Time.now puts fib(ARGV[0].to_i) puts Time.now Listagem 1.190: Fibonacci sem memoization $ ruby code/memo1.rb 10 2011-06-30 20:16:08 -0300 55 2011-06-30 20:16:08 -0300 $ ruby code/memo1.rb 20 2011-06-30 20:16:10 -0300 6765 2011-06-30 20:16:10 -0300 $ ruby code/memo1.rb 30 2011-06-30 20:16:12 -0300 ^[[A832040
4 http://en.wikipedia.org/wiki/Fibonacci_number

74

1.5. MDULOS 2011-06-30 20:16:12 -0300 $ ruby code/memo1.rb 40 2011-06-30 20:16:13 -0300 102334155 2011-06-30 20:16:56 -0300 Recomendo no usar um nmero maior que 40 ali no se vocs quiserem sair da aula antes de acabar de processar. ;-) Vamos fazer uma experincia e fazer o mesmo programa em Java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

import java.text.SimpleDateFormat; import java.util.Calendar; public class Fib { public static long calcula(int numero) { if(numero<2) return numero; return calcula(numero-1)+calcula(numero-2); } public static void main(String args[]) { SimpleDateFormat fmt = new SimpleDateFormat("dd/MM/yyyy H:mm:ss"); System.out.println(fmt.format(Calendar.getInstance().getTime())); System.out.println(calcula(Integer.parseInt(args[0]))); System.out.println(fmt.format(Calendar.getInstance().getTime())); } } Listagem 1.191: Fibonacci em Java $ java Fib 10 30/06/2011 20:18:26 55 30/06/2011 20:18:26 $ java Fib 20 30/06/2011 20:18:28 6765 30/06/2011 20:18:28 $ java Fib 30 30/06/2011 20:18:29 832040 30/06/2011 20:18:29 $ java Fib 40 30/06/2011 20:18:31 102334155 30/06/2011 20:18:32 Bem mais rpido hein? Mas agora vamos refazer o cdigo em Ruby, usando memoization:

1 2 3 4

require "rubygems" require "memoize" include Memoize

75

CAPTULO 1. RUBY
5 6 7 8 9 10 11 12 13

def fib(numero) return numero if numero < 2 fib(numero-1)+fib(numero-2) end memoize(:fib) puts Time.now puts fib(ARGV[0].to_i) puts Time.now Listagem 1.192: Fibonacci com memoization $ ruby code/memo2.rb 40 2011-06-30 20:19:36 -0300 102334155 2011-06-30 20:19:36 -0300 $ ruby code/memo2.rb 50 2011-06-30 20:19:39 -0300 12586269025 2011-06-30 20:19:39 -0300 $ ruby code/memo2.rb 100 2011-06-30 20:19:41 -0300 354224848179261915075 2011-06-30 20:19:41 -0300 Uau! Se quiserem trocar aquele nmero de 40 para 350 agora pode, srio! :-) E ainda d para otimizar mais se indicarmos um arquivo para gravar os resultados:

1 2 3 4 5 6 7 8 9 10 11 12 13

require "rubygems" require "memoize" include Memoize def fib(numero) return numero if numero < 2 fib(numero-1)+fib(numero-2) end memoize(:fib,"memo.cache") puts Time.now puts fib(ARGV[0].to_i) puts Time.now Listagem 1.193: Fibonacci com memoization e cache $ ruby code/memo3.rb 100 2011-06-30 20:21:22 -0300 354224848179261915075 2011-06-30 20:21:22 -0300 $ ruby code/memo3.rb 200 2011-06-30 20:21:25 -0300 280571172992510140037611932413038677189525 2011-06-30 20:21:25 -0300

76

1.5. MDULOS $ ruby code/memo3.rb 350 2011-06-30 20:21:28 -0300 6254449428820551641549772190170184190608177514674331726439961915653414425 2011-06-30 20:21:28 -0300 Dica: Podemos fazer o mesmo comportamento de memoization utilizando uma Hash da seguinte maneira:
1

2 3

fib = Hash.new{ |h, n| n < 2 ? h[n] = n : h[n] = h[n - 1] + h[n - 2] } puts Time.now; puts fib[10]; puts Time.now puts Time.now; puts fib[100]; puts Time.now 2011-11-24 18:12:55 -0200 55 2011-11-24 18:12:55 -0200 2011-11-24 18:12:59 -0200 354224848179261915075 2011-11-24 18:12:59 -0200

Outra dica: Podemos calcular uma aproximao de um nmero de Fibonacci usando a seguinte equao, onde n a sequncia que queremos descobrir e (Phi) a "proporo urea" 5 : n / 5 Podemos denir o clculo da seguinte forma:
1 2 3 4 5 6 7 8 9 10 11 12 13 14

phi = (Math.sqrt(5)/2)+0.5 ((phi**1)/Math.sqrt(5)).round ((phi**2)/Math.sqrt(5)).round ((phi**3)/Math.sqrt(5)).round ((phi**4)/Math.sqrt(5)).round ((phi**5)/Math.sqrt(5)).round ((phi**6)/Math.sqrt(5)).round ((phi**7)/Math.sqrt(5)).round ((phi**8)/Math.sqrt(5)).round ((phi**9)/Math.sqrt(5)).round ((phi**10)/Math.sqrt(5)).round ((phi**40)/Math.sqrt(5)).round ((phi**50)/Math.sqrt(5)).round ((phi**100)/Math.sqrt(5)).round

=> => => => => => => => => => => => => =>

1.618033988749895 1 1 2 3 5 8 13 21 34 55 102334155 12586269025 354224848179263111168

Listagem 1.194: Fibonacci usando Phi Podemos ver que, quanto maior o nmero, mais ocorre algum desvio.

77

CAPTULO 1. RUBY

1.6
MA

Threads

linguagem de programao que U criar threads facilmente com Ruby:se preze tem que ter suporte threads. Podemos
1 2 3 4 5 6 7 8 9 10

# encoding: utf-8 thread = Thread.new do puts "Thread #{self.object_id} iniciada!" 5.times do |valor| puts valor sleep 1 end end puts "j criei a thread" thread.join Listagem 1.195: Criando uma thread $ ruby code/thr1.rb Thread 84077870 iniciada! 0 j criei a thread 1 2 3 4 O mtodo join especialmente til para fazer a thread se completar antes que o interpretador termine. Podemos inserir um timeout:

1 2 3 4 5 6 7 8 9 10

# encoding: utf-8 thread = Thread.new do puts "Thread #{self.object_id} iniciada!" 5.times do |valor| puts valor sleep 1 end end puts "j criei a thread" thread.join(3) Listagem 1.196: Criando uma thread com timeout $ ruby code/thr2.rb j criei a thread Thread 76000560 iniciada! 0 1 2 Podemos criar uma Proc (lembram-se delas?) e pedir que uma Thread seja criada executando o resultado da Proc, convertendo-a em um bloco (lembram-se disso tambm?):

1 2 3 4 5 6

proc = Proc.new do |parametro| parametro.times do |valor| print "[#{valor+1}/#{parametro}]" sleep 0.5 end end 78

1.6. THREADS
7 8 9 10 11 12 13

thread = nil 5.times do |valor| thread = Thread.new(valor,&proc) end thread.join puts "Terminado!" Listagem 1.197: Criando uma thread com uma Proc $ ruby code/thr3.rb [1/4][1/2][1/1][1/3][2/2][2/3][2/4][3/3][3/4][4/4]Terminado! Podemos nos deparar com algumas surpresas com falta de sincronia como:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

# encoding: utf-8 maior, menor = 0, 0 log = 0 t1 = Thread.new do loop do maior += 1 manor -= 1 end end t2 = Thread.new do loop do log = menor+maior end end sleep 3 puts "log vale #{log}" Listagem 1.198: Threads sem sincronia $ ruby code/thr4.rb log vale 1 O problema que no houve sincronia entre as duas threads, o que nos levou a resultados diferentes no log, pois no necessariamente as variveis eram acessadas de maneira uniforme. Podemos resolver isso usando um Mutex, que permite acesso exclusivo aos objetos travados por ele:

1 2 3 4 5 6 7 8 9 10 11

# encoding: utf-8 maior, menor = 0, 0 log = 0 mutex = Mutex.new t1 = Thread.new do loop do mutex.synchronize do maior += 1 menor -= 1 end 79

CAPTULO 1. RUBY
12 13 14 15 16 17 18 19 20 21 22 23 24

end end t2 = Thread.new do loop do mutex.synchronize do log = menor+maior end end end sleep 3 puts "log vale #{log}" Listagem 1.199: Threads com sincronia $ ruby code/thr5.rb log vale 0 Agora correu tudo como desejado! Podemos alcanar esse resultado tambm usando monitores:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

# encoding: utf-8 require "monitor" maior, menor = 0, 0 log = 0 mutex = Monitor.new t1 = Thread.new do loop do mutex.synchronize do maior += 1 menor -= 1 end end end t2 = Thread.new do loop do mutex.synchronize do log = menor+maior end end end sleep 3 puts "log vale #{log}" Listagem 1.200: Threads com sincronia em um monitor $ ruby code/thr6.rb log vale 0 A diferena dos monitores que eles podem ser uma classe pai da classe corrente, um mixin e uma extenso de um objeto em particular.

1 2

require "monitor"

80

1.6. THREADS
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43

class Contador1 attr_reader :valor include MonitorMixin def initialize @valor = 0 super end def incrementa synchronize do @valor = valor + 1 end end end class Contador2 attr_reader :valor def initialize @valor = 0 end def incrementa @valor = valor + 1 end end c1 = Contador1.new c2 = Contador2.new c2.extend(MonitorMixin) t1 = Thread.new { 100_000.times { c1.incrementa } } t2 = Thread.new { 100_000.times { c1.incrementa } } t1.join t2.join puts c1.valor t3 = Thread.new { 100_000.times { c2.synchronize { c2.incrementa } } } t4 = Thread.new { 100_000.times { c2.synchronize { c2.incrementa } } } t3.join t4.join puts c2.valor Listagem 1.201: Monitores como mixins $ ruby code/thr7.rb 200000 200000 Podemos ter variveis de condio que sinalizam quando um recurso est ocupado ou liberado, atravs de wait(mutex) e signal. Vamos fazer duas threads seguindo o conceito de produtor/consumidor:

1 2 3 4 5

require "thread" items = [] lock = Mutex.new cond = ConditionVariable.new 81

CAPTULO 1. RUBY
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36

limit = 0 produtor = Thread.new do loop do lock.synchronize do qtde = rand(50) next if qtde==0 puts "produzindo #{qtde} item(s)" items = Array.new(qtde,"item") cond.wait(lock) puts "consumo efetuado!" puts "-"*25 limit += 1 end break if limit > 5 end end consumidor = Thread.new do loop do lock.synchronize do if items.length>0 puts "consumindo #{items.length} item(s)" items = [] end cond.signal end end end produtor.join Listagem 1.202: Conditions variables $ ruby code/thr8.rb produzindo 48 item(s) consumindo 48 item(s) consumo efetuado! ------------------------produzindo 43 item(s) consumindo 43 item(s) consumo efetuado! ------------------------produzindo 21 item(s) consumindo 21 item(s) consumo efetuado! ------------------------produzindo 29 item(s) consumindo 29 item(s) consumo efetuado! ------------------------produzindo 31 item(s) consumindo 31 item(s) consumo efetuado! ------------------------produzindo 43 item(s) consumindo 43 item(s) 82

1.6. THREADS consumo efetuado! ------------------------O produtor produz os items, avisa o consumidor que est tudo ok, o consumidor consome os items e sinaliza para o produtor que pode enviar mais. Comportamento similar de produtor/consumidor tambm pode ser alcanado utilizando Queues:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

require "thread" queue = Queue.new limit = 0 produtor = Thread.new do loop do qtde = rand(50) next if qtde==0 limit += 1 break if limit > 5 puts "produzindo #{qtde} item(s)" queue.enq(Array.new(qtde,"item")) end end consumidor = Thread.new do loop do obj = queue.deq break if obj == :END_OF_WORK print "consumindo #{obj.size} item(s)\n" end end produtor.join queue.enq(:END_OF_WORK) consumidor.join Listagem 1.203: Queues $ ruby code/thr9.rb produzindo 26 item(s) consumindo 26 item(s) produzindo 26 item(s) consumindo 26 item(s) produzindo 42 item(s) consumindo 42 item(s) produzindo 14 item(s) consumindo 14 item(s) produzindo 4 item(s) consumindo 4 item(s) A implementao das threads das verses 1.8.x usam green threads e no native threads. As green threads podem car bloqueadas se dependentes de algum recurso do sistema operacional, como nesse exemplo:

1 2 3 4

proc = Proc.new do |numero| loop do puts "Proc #{numero}: #{date}" end 83

CAPTULO 1. RUBY
5 6 7 8 9 10 11 12 13 14 15 16 17

end fifo = Proc.new do loop do puts File.read("teste.fifo") end end threads = [] (1..5).each do |numero| threads << (numero==3 ? Thread.new(&fifo) : Thread.new(numero,&proc)) end threads.each(&:join) Listagem 1.204: Green threads bloqueando Podemos interceptar um comportamento "bloqueante"tambm utilizando o mtodo try_lock. Esse mtodo tenta bloquear o Mutex, e se no conseguir, retorna false. Vamos supor que temos uma Thread que efetua um processamento de tempos em tempos, e queremos vericar o resultado corrente, aproveitando para colocar um hook para sairmos do programa usando CTRL+C:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36

# encoding: utf-8 mutex = Mutex.new last_result = 1 last_update = Time.now trap("SIGINT") do puts "saindo do programa ..." exit end Thread.new do loop do sleep 5 puts "atualizando em #{Time.now} ..." mutex.synchronize do # alguma coisa demorada aqui sleep 10 last_result += 1 end last_update = Time.now puts "atualizado em #{last_update}." end end loop do puts "aperte ENTER para ver o resultado:" gets if mutex.try_lock begin puts "resultado atualizado em #{last_update}: #{last_result}" ensure mutex.unlock end else puts "sendo atualizado, resultado anterior em #{last_update}: #{last_result}" 84

1.6. THREADS
37 38

end end Listagem 1.205: Usando try_lock $ ruby code/thr11.rb aperte ENTER para ver o resultado: resultado atualizado em 2011-07-05 18:35:54 -0300: 1 aperte ENTER para ver o resultado: atualizando em 2011-07-05 18:35:59 -0300 ... sendo atualizado, resultado anterior em 2011-07-05 18:35:54 -0300: 1 aperte ENTER para ver o resultado: atualizado em 2011-07-05 18:36:09 -0300. atualizando em 2011-07-05 18:36:14 -0300 ... atualizado em 2011-07-05 18:36:24 -0300. resultado atualizado em 2011-07-05 18:36:24 -0300: 3 aperte ENTER para ver o resultado: resultado atualizado em 2011-07-05 18:36:24 -0300: 3 aperte ENTER para ver o resultado: atualizando em 2011-07-05 18:36:29 -0300 ... ^Csaindo do programa ...

1.6.1

Fibers

Entre as features novas do Ruby 1.9, existe uma bem interessante chamada Fibers, volta e meia denidas como "threads leves". Vamos dar uma olhada nesse cdigo:
1

3.times {|item| puts item} At a tudo bem, aparentemente um cdigo normal que utiliza um iterador, mas vamos dar uma olhada nesse aqui:

1 2 3 4 5 6 7 8

enum1 = 3.times enum2 = %w(zero um dois).each puts enum1.class loop do puts enum1.next puts enum2.next end Listagem 1.206: Enumerators $ ruby code/fibers1.rb Enumerator 0 zero 1 um 2 dois Dando uma olhada no nome da classe de enum1, podemos ver que agora podemos criar um Enumerator com vrios dos iteradores que j estvamos acostumados, e foi o que zemos 85

CAPTULO 1. RUBY ali alternando entre os elementos dos dois Enumerators, at nalizar quando foi gerada uma exceo, capturada pela estrutura loop...do, quando os elementos terminaram. O segredo nos Enumerators que eles esto utilizando internamente as Fibers. Para um exemplo bsico de Fibers, podemos ver como calcular, novamente, os nmeros de Fibonacci:
1 2 3 4 5 6 7 8

fib = Fiber.new do x, y = 0, 1 loop do Fiber.yield y x,y = y,x+y end end 10.times { puts fib.resume } Listagem 1.207: Fibonacci com Fibers $ ruby code/fibers2.rb 1 1 2 3 5 8 13 21 34 55 O segredo ali que Fibers so corrotinas e no subrotinas. Em uma subrotina o controle retornado para o contexto de onde ela foi chamada geralmente com um return, e continua a partir dali liberando todos os recursos alocados dentro da rotina, como variveis locais etc. Em uma corrotina, o controle desviado para outro ponto mas mantendo o contexto onde ele se encontra atualmente, de modo similar uma closure. O exemplo acima funciona dessa maneira: A Fiber criada com new. Dentro de um iterador que vai rodar 10 vezes, chamado o mtodo resume. executado o cdigo do incio do "corpo"da Fiber at yield. Nesse ponto, o controle transferido com o valor de y para onde foi chamado o resume, e impresso na tela. A partir do prximo resume, o cdigo da Fiber executado do ponto onde parou para baixo, ou seja, da prxima linha aps o yield (linha 5, mostrando outra caracterstica das corrotinas, que ter mais de um ponto de entrada) processando os valores das variveis e retornando para o comeo do loop, retornando o controle novamente com yield. Pudemos comprovar que x e y tiveram seus valores preservados entre as trocas de controle. Cdigo parecido seria feito com uma Proc , dessa maneira:

1 2

def create_fib x, y = 0, 1 86

1.6. THREADS
3 4 5 6 7 8 9

lambda do t, x, y = y, y, x+y return t end end proc = create_fib 10.times { puts proc.call } Listagem 1.208: Emulando o comportamento de Fibers com Procs $ ruby code/fibers3.rb 1 1 2 3 5 8 13 21 34 55 Nesse caso podemos ver o comportamento da Proc como uma subrotina, pois o valor que estamos interessados foi retornado com um return explcito ali (lembrem-se que em Ruby a ltima expresso avaliada a retornada, inserimos o return explicitamente apenas para efeitos didticos). H algumas divergncias entre Fibers serem corrotinas ou semi-corrotinas. As semi-corrotinas so diferentes das corrotinas pois s podem transferir o controle para quem as chamou, enquanto corrotinas podem transferir o controle para outra corrotina. Para jogar um pouco de lenha na fogueira, vamos dar uma olhada nesse cdigo:

1 2 3 4 5 6 7 8 9 10 11 12

f2 = Fiber.new do |value| puts "Estou em f2 com #{value}, transferindo para onde vai resumir ..." Fiber.yield value + 40 puts "Cheguei aqui?" end f1 = Fiber.new do puts "Comecei f1, transferindo para f2 ..." f2.resume 10 end puts "Resumindo fiber 1: #{f1.resume}" Listagem 1.209: Comportamento de semi-corrotinas $ ruby code/fibers4.rb Comecei f1, transferindo para f2 ... Estou em f2 com 10, transferindo para onde vai resumir ... Resumindo fiber 1: 50 Comportamento parecido com as semi-corrotinas! Mas e se zermos isso:

1 2 3 4

require "fiber" f1 = Fiber.new do |other| puts "Comecei f1, transferindo para f2 ..." 87

CAPTULO 1. RUBY
5 6 7 8 9 10 11 12 13 14

other.transfer Fiber.current, 10 end f2 = Fiber.new do |caller,value| puts "Estou em f2, transferindo para f1 ..." caller.transfer value + 40 puts "Cheguei aqui?" end puts "Resumindo fiber 1: #{f1.resume(f2)}" Listagem 1.210: Corrotinas ou semi-corrotinas?

$ ruby code/fibers5.rb Comecei f1, transferindo para f2 ... Estou em f2, transferindo para f1 ... Resumindo fiber 1: 50 Nesse caso, f1 est transferindo o controle para f2 (que no quem a chamou!), que transfere de volta para f1 que retorna o resultado em resume. Discusses tericas parte, as Fibers so um recurso muito interessante. Para nalizar, um bate-bola rpido no esquema de "produtor-consumidor"usando Fibers:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

require "fiber" produtor = Fiber.new do |cons| 5.times do items = Array.new((rand*5).to_i+1,"oi!") puts "Produzidos #{items} ..." cons.transfer Fiber.current, items end end consumidor = Fiber.new do |prod,items| loop do puts "Consumidos #{items}" prod, items = prod.transfer end end produtor.resume consumidor Listagem 1.211: Produtor/consumidor usando Fibers

$ ruby code/fibers6.rb Produzidos ["oi!", "oi!", Consumidos ["oi!", "oi!", Produzidos ["oi!", "oi!", Consumidos ["oi!", "oi!", Produzidos ["oi!"] ... Consumidos ["oi!"] Produzidos ["oi!", "oi!", Consumidos ["oi!", "oi!", Produzidos ["oi!", "oi!", Consumidos ["oi!", "oi!", 88

"oi!", "oi!", "oi!", "oi!",

"oi!", "oi!", "oi!", "oi!",

"oi!"] ... "oi!"] "oi!"] ... "oi!"]

"oi!", "oi!", "oi!"] ... "oi!", "oi!", "oi!"] "oi!"] ... "oi!"]

1.6. THREADS As Fibers tambm podem ajudar a separar contextos e funcionalidades em um programa. Se precisssemos detectar a frequncia de palavras em uma String ou arquivo, poderamos utilizar uma Fiber para separar as palavras, retornando para um contador:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

# encoding: utf-8 str =<<FIM texto para mostrar como podemos separar palavras do texto para estatstica de quantas vezes as palavras se repetem no texto FIM scanner = Fiber.new do str.scan(/\w\p{Latin}+/) do |word| Fiber.yield word.downcase end puts "acabou!" end words = Hash.new(0) while word = scanner.resume words[word] += 1 end words.each do |word,count| puts "#{word}:#{count}" end Listagem 1.212: Usando Fibers para separar funcionalidadess $ ruby code/fibers7.rb acabou! texto:3 para:2 mostrar:1 como:1 podemos:1 separar:1 palavras:2 do:1 estatstica:1 de:1 quantas:1 vezes:1 as:1 se:1 repetem:1 no:1

1.6.2

Continuations

Ruby tambm tem suporte Continuations, que so, segundo a Wikipedia, "representaes abstratas do controle de estado de um programa"6 . Um exemplo nos mostra que a call stack de um programa preservada chamando uma Continuation:
1 2 3

require "continuation" def cria_continuation


6 http://en.wikipedia.org/wiki/Continuations

89

CAPTULO 1. RUBY
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

puts "Criando a continuation e retornando ..." callcc {|obj| return obj} puts "Ei, olha eu aqui de volta na continuation!" end puts "Vou criar a continuation." cont = cria_continuation() puts "Verificando se existe ..." if cont puts "Criada, vamos voltar para ela?" cont.call else puts "Agora vamos embora." end puts "Terminei, tchau." Listagem 1.213: Continuations $ ruby code/cont.rb Vou criar a continuation. Criando a continuation e retornando ... Verificando se existe ... Criada, vamos voltar para ela? Ei, olha eu aqui de volta na continuation! Verificando se existe ... Agora vamos embora. Terminei, tchau.

1.6.3

Processos em paralelo

Podemos utilizar a gem Parallel 7 para executar processamento em paralelo usando processos (em CPUs com vrios processadores) ou utilizando as Threads. gem install parallel Vamos ver um exemplo utilizando Threads, que do mais velocidade em operaes bloqueantes, no usam memria extra e permitem modicao de dados globais:
1 2 3 4 5 6 7 8 9 10 11

require "parallel" puts Time.now res = "Quem terminou primeiro? " Parallel.map 1..20, :in_threads => 4 do |nr| 5.times {|t| sleep rand; print "#{nr}/#{t} " } puts "acabei com #{nr} " res += "#{nr} " end puts res puts Time.now Listagem 1.214: Usando Parallel com Threads $ ruby par.rb 2011-07-08 14:56:43 -0300 4/0 3/0 3/1 2/0 4/1 1/0 2/1 1/1 3/2 4/2 1/2 4/3 3/3 1/3 2/2 3/4 acabei com 3
7 https://github.com/grosser/parallel

90

1.6. THREADS 4/4 acabei com 4 5/0 2/3 6/0 1/4 acabei com 1 5/1 2/4 acabei com 2 7/0 5/2 5/3 6/1 6/2 6/3 5/4 acabei com 5 8/0 7/1 8/1 9/0 7/2 7/3 6/4 acabei com 6 8/2 9/1 9/2 10/0 9/3 7/4 acabei com 7 8/3 9/4 acabei com 9 11/0 10/1 8/4 acabei com 8 10/2 13/0 10/3 10/4 acabei com 10 12/0 11/1 13/1 13/2 12/1 14/0 11/2 11/3 13/3 12/2 14/1 14/2 12/3 14/3 13/4 acabei com 13 11/4 acabei com 11 12/4 acabei com 12 15/0 14/4 acabei com 14 18/0 17/0 15/1 18/1 16/0 16/1 16/2 16/3 17/1 18/2 15/2 16/4 acabei com 16 15/3 17/2 19/0 18/3 17/3 19/1 18/4 acabei com 18 15/4 acabei com 15 20/0 19/2 17/4 acabei com 17 19/3 20/1 19/4 acabei com 19 20/2 20/3 20/4 acabei com 20 Quem terminou primeiro? 3 4 1 2 5 6 7 9 8 10 13 11 12 14 16 18 15 17 19 20 2011-07-08 14:56:57 -0300 Agora, utilizando processos, que utilizam mais de um ncleo, do mais velocidade para operaes bloqueantes, protegem os dados globais, usam mais alguma memria e permitem interromper os processos lhos junto com o processo principal, atravs de CTRL+C ou enviando um sinal com kill -2:
1 2 3 4 5 6 7 8 9 10 11

require "parallel" puts Time.now res = "Quem terminou primeiro? " Parallel.map 1..20, :in_processes => 3 do |nr| 5.times {|t| sleep rand; print "#{nr}/#{t} " } puts "acabei com #{nr} " res += "#{nr} " end puts res puts Time.now Listagem 1.215: Usando Parallel com processos $ ruby par2.rb 2011-07-08 15:03:32 -0300 3/0 1/0 2/0 3/1 2/1 1/1 3/2 2/2 2/3 1/2 3/3 2/4 acabei com 2 1/3 3/4 acabei com 3 5/0 4/0 1/4 acabei com 1 5/1 4/1 6/0 5/2 4/2 6/1 6/2 5/3 4/3 5/4 acabei com 5 4/4 acabei com 4 6/3 8/0 8/1 7/0 6/4 acabei com 6 8/2 9/0 9/1 7/1 8/3 9/2 8/4 acabei com 8 9/3 7/2 7/3 7/4 acabei com 7 9/4 acabei com 9 12/0 10/0 12/1 11/0 10/1 12/2 12/3 12/4 acabei com 12 11/1 13/0 10/2 13/1 11/2 10/3 13/2 11/3 11/4 acabei com 11 91

CAPTULO 1. RUBY 10/4 acabei com 10 15/0 13/3 15/1 13/4 14/0 14/1 15/2 15/3 16/1 17/0 16/2 14/4 17/1 18/0 16/3 17/2 18/2 19/0 17/3 17/4 19/1 19/2 18/3 19/3 18/4 acabei com 18 20/1 20/2 20/3 20/4 Quem terminou primeiro? 2011-07-08 15:03:50 -0300

acabei 14/2 acabei 18/1 acabei 20/0

com 13 16/0 14/3 15/4 acabei com 15 com 14 16/4 acabei com 16 com 17 19/4 acabei com 19

acabei com 20

Para executar esse mesmo cdigo utilizando o nmero de processadores da CPU, s no especicar nem in_threads ou in_processes:
1 2 3 4 5 6 7 8 9 10 11

require "parallel" puts Time.now res = "Quem terminou primeiro? " Parallel.map 1..20 do |nr| 5.times {|t| sleep rand; print "#{nr}/#{t} " } puts "acabei com #{nr} " res += "#{nr} " end puts res puts Time.now Listagem 1.216: Usando Parallel com limite de CPUs $ ruby par3.rb 2011-07-08 15:07:05 -0300 1/0 2/0 1/1 2/1 1/2 2/2 1/3 2/3 1/4 acabei com 1 2/4 acabei com 2 3/0 4/0 4/1 3/1 3/2 4/2 4/3 4/4 acabei com 4 3/3 3/4 acabei com 3 5/0 6/0 5/1 5/2 6/1 5/3 6/2 5/4 acabei com 5 6/3 7/0 7/1 7/2 6/4 acabei com 6 8/0 7/3 8/1 7/4 acabei com 7 8/2 9/0 8/3 9/1 9/2 8/4 acabei com 8 10/0 9/3 10/1 9/4 acabei com 9 10/2 11/0 11/1 10/3 11/2 11/3 11/4 acabei com 11 10/4 acabei com 10 13/0 12/0 13/1 13/2 12/1 13/3 13/4 acabei com 13 12/2 12/3 12/4 acabei com 12 14/0 15/0 15/1 14/1 14/2 14/3 15/2 14/4 acabei com 14 16/0 15/3 16/1 15/4 acabei com 15 16/2 17/0 16/3 17/1 16/4 acabei com 16 17/2 17/3 17/4 acabei com 17 19/0 19/1 18/0 19/2 18/1 19/3 18/2 18/3 18/4 acabei com 18 19/4 acabei com 19 20/0 20/1 20/2 20/3 20/4 acabei com 20 Quem terminou primeiro? 2011-07-08 15:07:34 -0300 Fazendo uma comparao com Threads:

1 2

puts Time.now res = "Quem terminou primeiro? " 92

1.6. THREADS
3 4 5 6 7 8 9 10 11 12 13

threads = [] (1..20).each do |nr| threads << Thread.new do 5.times {|t| sleep rand; print "#{nr}/#{t} " } puts "acabei com #{nr} " res += "#{nr} " end end threads.each(&:join) puts res puts Time.now Listagem 1.217: Simulando Parallel com Threads $ ruby par4.rb 2011-07-08 11:11:29 -0300 17/0 1/0 15/0 2/0 15/1 8/0 18/0 7/0 17/1 19/0 14/0 17/2 10/0 5/0 2/1 4/0 9/0 6/0 8/1 2/2 5/1 15/2 12/0 4/1 16/0 11/0 14/1 20/0 16/1 13/0 4/2 3/0 10/1 19/1 20/1 10/2 7/1 18/1 13/1 18/2 1/1 14/2 1/2 17/3 14/3 8/2 6/1 12/1 4/3 6/2 4/4 acabei com 4 15/3 17/4 acabei com 17 5/2 11/1 9/1 16/2 2/3 7/2 14/4 acabei com 14 12/2 19/2 3/1 18/3 13/2 10/3 7/3 20/2 1/3 10/4 acabei com 10 15/4 acabei com 15 6/3 8/3 5/3 5/4 acabei com 5 6/4 acabei com 6 20/3 7/4 acabei com 7 11/2 13/3 12/3 13/4 acabei com 13 2/4 acabei com 2 19/3 9/2 19/4 acabei com 19 11/3 3/2 16/3 1/4 acabei com 1 18/4 acabei com 18 20/4 acabei com 20 8/4 acabei com 8 11/4 acabei com 11 9/3 12/4 acabei com 12 9/4 acabei com 9 16/4 acabei com 16 3/3 3/4 acabei com 3 Quem terminou primeiro? 4 17 14 10 15 5 6 7 13 2 19 1 18 20 8 11 12 9 16 3 2011-07-08 11:11:32 -0300

1.6.4

Benchmarks

Ao invs de medir nosso cdigo atravs do sucessivas chamadas Time.now, podemos utilizar o mdulo de benchmark, primeiro medindo uma operao simples, como criar uma String: require "benchmark" Benchmark.measure { "-"*1_000_000 } => 0.000000 0.000000 0.000000 Ou um pedao de cdigo:
1 2

0.002246

require "benchmark" require "parallel" 93

CAPTULO 1. RUBY
3 4 5 6 7 8 9 10

Benchmark.bm do |bm| bm.report do Parallel.map 1..20, :in_threads => 4 do |nr| 5.times {|t| sleep rand; } end end end Listagem 1.218: Medindo com o Benchmark $ ruby bench1.rb user system 0.040000 0.030000

total real 0.070000 ( 13.937973)

Podemos comparar vrios pedaos de cdigo, dando uma label para cada um: ]code/bench2.rb $ ruby bench2.rb user in_threads: 0.030000 in_processes: 0.000000 using threads: 0.010000 system 0.030000 0.060000 0.000000 total real 0.060000 ( 12.277710) 0.240000 ( 17.514098) 0.010000 ( 3.303277)

94

1.7. ENTRADA E SADA

1.7
1.7.1

Entrada e sada
Arquivos

criar um arquivo V o seguinte contedo: para fazermos testes, com o nome criativo de teste.txt e com
AMOS

Arquivo de teste Curso de Ruby Estamos na terceira linha. E aqui a quarta e ltima. Podemos ler o arquivo facilmente:
1

p File.read("teste.txt") Listagem 1.219: Lendo um arquivo $ ruby code/io1.rb "Arquivo de teste\nCurso de Ruby\nEstamos na terceira linha.\nE aqui a quarta e ltima.\n" Isso gera uma string com todo o contedo do arquivo. Para lermos todas as suas linhas como um Array (que teria o mesmo efeito de quebrar a String resultante da operao acima em ):

p File.readlines("teste.txt") Listagem 1.220: Lendo linhas de um arquivo $ ruby code/io2.rb ["Arquivo de teste\n", "Curso de Ruby\n", "Estamos na terceira linha.\n", "E aqui a quarta e ltima.\n"] Podemos abrir o arquivo especicando o seu modo e armazenando o seu handle. O modo para leitura r e para escrita w. Podemos usar o iterador do handle para ler linha a linha:

1 2 3 4 5

f = File.open("teste.txt") f.each do |linha| puts linha end f.close Listagem 1.221: Lendo um arquivo com handle $ ruby code/io3.rb Arquivo de teste Curso de Ruby Estamos na terceira linha. E aqui a quarta e ltima. Melhor do que isso passar um bloco para File onde o arquivo vai ser aberto e automaticamente fechado no nal do bloco:

1 2 3 4 5

File.open("teste.txt") do |arquivo| arquivo.each do |linha| puts linha end end Listagem 1.222: Lendo um arquivo com um bloco 95

CAPTULO 1. RUBY Isso "automagicamente"vai fechar o handle do arquivo, no nal do bloco. Para ler o arquivo byte a byte, podemos fazer:
1 2 3 4 5

File.open("teste.txt") do |arquivo| arquivo.each_byte do |byte| print "[#{byte}]" end end Listagem 1.223: Lendo um arquivo byte a byte $ ruby code/io5.rb [65][114][113][117][105][118][111][32][100][101][32][116][101][115][116][101] [10][67][117][114][115][111][32][100][101][32][82][117][98][121][10][69][115] [116][97][109][111][115][32][110][97][32][116][101][114][99][101][105][114][97] [32][108][105][110][104][97][46][10][69][32][97][113][117][105][32][195][169] [32][97][32][113][117][97][114][116][97][32][101][32][195][186][108][116][105] [109][97][46][10] Para escrever em um arquivo, fazendo uma cpia do atual:

1 2 3

File.open("novo_teste.txt","w") do |arquivo| arquivo << File.read("teste.txt") end Listagem 1.224: Escrevendo em um arquivo

1.7.2

Arquivos Zip

Podemos ler e escrever em arquivos compactados Zip, para isso vamos precisar da gem rubyzip: gem install rubyzip Vamos criar trs arquivos, 1.txt, 2.txt e 3.txt com contedo livre dentro de cada um, que vo ser armazenados internamente no arquivo em um subdiretrio chamado txts, compactando e logo descompactando:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

require "rubygems" require "zip/zip" require "fileutils" myzip = "teste.zip" File.delete(myzip) if File.exists?(myzip) Zip::ZipFile.open(myzip,true) do |zipfile| Dir.glob("[0-9]*.txt") do |file| puts "Zipando #{file}" zipfile.add("txts/#{file}",file) end end Zip::ZipFile.open(myzip) do |zipfile| zipfile.each do |file| dir = File.dirname(file.name) puts "Descompactando #{file.name} para #{dir}" FileUtils.mkpath(dir) if !File.exists?(dir) zipfile.extract(file.name,file.name) do |entry,file| 96

1.7. ENTRADA E SADA


21 22 23 24 25

puts "Arquivo #{file} existe, apagando ..." File.delete(file) end end end Listagem 1.225: Escrevendo e lendo em um arquivo Zip Rodando o programa: $ ruby code/io7.rb Zipando 3.txt Zipando 1.txt Zipando 2.txt Descompactando txts/3.txt para txts Descompactando txts/1.txt para txts Descompactando txts/2.txt para txts $ ls txts total 20K drwxr-xr-x drwxr-xr-x -rw-r--r--rw-r--r--rw-r--r--

2 6 1 1 1

taq taq taq taq taq

taq 4,0K 2011-07-06 15:16 . taq 4,0K 2011-07-06 15:16 .. taq 930 2011-07-06 15:16 1.txt taq 930 2011-07-06 15:16 2.txt taq 930 2011-07-06 15:16 3.txt

Algumas explicaes sobre o cdigo: Na linha 3 foi requisitado o mdulo FileUtils, que carrega mtodos como o mkpath, na linha 19, utilizado para criar o diretrio (ou a estrutura de diretrios).

Na linha 8 abrimos o arquivo, enviando true como ag indicando para criar o arquivo caso no exista. Para arquivos novos, podemos tambm utilizar new.

Na linha 9 utilizamos Dir.glob para nos retornar uma lista de arquivos atravs de uma mscara de arquivos.

Na linha 11 utilizamos o mtodo add para inserir o arquivo encontrado dentro de um path interno do arquivo compactado, nesse caso dentro de um diretrio chamado txts.

Na linha 15 abrimos o arquivo criado anteriormente, para leitura.

Na linha 16 utilizamos o iterador each para percorrer os arquivos contidos dentro do arquivo compactado.

Na linha 17 extramos o nome do diretrio com dirname.

Na linha 20 extramos o arquivo, passando um bloco que vai ser executado no caso do arquivo j existir. 97

CAPTULO 1. RUBY

1.7.3

XML

Vamos acessar arquivos XML atravs do REXML, um processador XML que j vem com Ruby. Para mais informaes sobre esse processador XML, consulte o tutorial ocial em http://www.germanesoftware.com/software/rexml/docs/tutorial.html. Antes de mais nada, vamos criar um arquivo XML para os nossos testes, chamado aluno.xml, usando o REXML para isso:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

# encoding: utf-8 require "rexml/document" doc = REXML::Document.new decl = REXML::XMLDecl.new("1.0","UTF-8") doc.add decl root = REXML::Element.new("alunos") doc.add_element root alunos = [[1,"Joo"],[2,"Jos"],[3,"Antonio"],[4,"Maria"]] alunos.each do |info| aluno = REXML::Element.new("aluno") id = REXML::Element.new("id") nome = REXML::Element.new("nome") id.text = info[0] nome.text = info[1] aluno.add_element id aluno.add_element nome root.add_element aluno end doc.write(File.open("alunos.xml","w")) Listagem 1.226: Escrevendo um arquivo XML O resultado ser algo como: $ cat alunos.xml <?xml version=1.0 encoding=UTF-8?><alunos><aluno><id>1</id><nome>Joo </nome></aluno><aluno><id>2</id><nome>Jos</nome></aluno><aluno><id>3</id> <nome>Antonio</nome></aluno><aluno><id>4</id><nome>Maria</nome></aluno> </alunos> Ok, agora vamos ler esse arquivo. Vamos supor que eu quero listar os dados de todos os alunos:

1 2 3 4 5 6

require "rexml/document" doc = REXML::Document.new(File.open("alunos.xml")) doc.elements.each("alunos/aluno") do |aluno| puts "#{aluno.elements[id].text}-#{aluno.elements[nome].text}" end Listagem 1.227: Lendo um arquivo XML $ ruby code/xml2.rb 1-Joo 2-Jos 3-Antonio 4-Maria 98

1.7. ENTRADA E SADA Poderamos ter convertido tambm os elementos em um Array e usado o iterador para percorrer o arquivo, o que dar resultado similar:
1 2 3 4 5 6

require "rexml/document" doc = REXML::Document.new(File.open("alunos.xml")) doc.elements.to_a("//aluno").each do |aluno| puts "#{aluno.elements[id].text}-#{aluno.elements[nome].text}" end Listagem 1.228: Lendo um arquivo XML como um Array Se quisssemos somente o segundo aluno, poderamos usar:

1 2 3 4 5 6

require "rexml/document" doc = REXML::Document.new(File.open("alunos.xml")) root = doc.root aluno = root.elements["aluno[2]"] puts "#{aluno.elements[id].text}-#{aluno.elements["nome"].text}" Listagem 1.229: Lendo um determinado elemento de um arquivo XML $ ruby code/xml4.rb 2-Jos Uma abordagem mais moderna para criar XML em Ruby a gem builder: gem install builder

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

# encoding: utf-8 require "builder" alunos = {1=>"Joo",2=>"Jos",3=>"Antonio",4=>"Maria"} xml = Builder::XmlMarkup.new(:indent=>2) xml.alunos do alunos.each do |key,value| xml.aluno do xml.id key xml.nome value end end end puts xml.to_s # para gravar o arquivo File.open("alunos.xml","w") do |file| file << xml.target! end Listagem 1.230: Escrevendo um arquivo XML com Builder $ ruby code/xml5.rb <alunos> <aluno> <id>1</id> <nome>Joo</nome> </aluno> ... 99

CAPTULO 1. RUBY E para a leitura de arquivos XML, podemos utilizar a gem nokogiri: gem install nokogiri
1 2 3 4 5 6 7

require "nokogiri" doc = Nokogiri::XML(File.open("alunos.xml")) doc.search("aluno").each do |node| puts node.search("id").text+":"+ node.search("nome").text end Listagem 1.231: Lendo um arquivo XML com Nokogiri $ ruby code/xml6.rb 1:Joo 2:Jos 3:Antonio 4:Maria

1.7.4

XSLT

Aproveitando que estamos falando de XML, vamos ver como utilizar o XSLT. XSLT uma linguagem para transformar documentos XML em outros documentos, sejam eles outros XML, HTML, o tipo que voc quiser e puder imaginar. XSLT desenhado para uso com XSL, que so folhas de estilo para documentos XML. Alguns o acham muito verboso (sim, existe essa palavra), mas para o que ele proposto, bem til. Voc pode conhecer mais sobre XSLT na URL ocial do W3C 8 . O uso de XSLT em Ruby pode ser feito com o uso da gem ruby-xslt: gem install ruby-xslt Aps isso vamos usar o nosso arquivo alunos.xml para mostrar um exemplo de transformao. Para isso vamos precisar de uma folha de estilo XSL, alunos.xsl:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output method="html" encoding="utf-8" indent="no"/> <xsl:template match="/alunos"> <html> <head> <title>Teste de XSLT</title> </head> <body> <table> <caption>Alunos</caption> <th> <td>Id</td> <td>Nome</td> </th> <tbody> <xsl:apply-templates/> </tbody>
8 http://www.w3.org/TR/xslt

100

1.7. ENTRADA E SADA


20 21 22 23 24 25 26 27 28 29 30 31 32

</table> </body> </html> </xsl:template> <xsl:template match="aluno"> <tr> <td><xsl:value-of select="id"/></td> <td><xsl:value-of select="nome"/></td> </tr> </xsl:template> </xsl:stylesheet> Listagem 1.232: Folha de estilo XSL Agora o cdigo Ruby:

1 2 3 4 5 6 7

require "xml/xslt" xslt = XML::XSLT.new xslt.xsl = "alunos.xsl" xslt.xml = "alunos.xml" xslt.save("alunos.html") puts xslt.serve Listagem 1.233: Usando XSL com Ruby Rodando o programa vamos ter o resultado gravado no arquivo alunos.html e apresentado na tela. Abrindo o arquivo vamos ver: $ ruby xslt.rb | lynx --stdin CAPTION: Alunos Id Nome 1 Joo 2 Jos 3 Antonio 4 Maria

1.7.5

YAML

Podemos denir o YAML (YAML Aint Markup Language - pronuncia-se mais ou menos como ieimel, fazendo rima com a pronncia de camel, em ingls) como uma linguagem de denio ou markup menos verbosa que o XML. Vamos dar uma olhada em como ler arquivos YAML convertendo-os em tipos do Ruby. Primeiro vamos criar um arquivo chamado teste.yml (a extenso dos arquivos YAML yml) que vamos alterar de acordo com nossos exemplos, armazenando um array no nosso arquivo. Insira o seguinte contedo, lembrando que -- indica o comeo de um arquivo YAML:
1 2 3 4 5

--- jos - joo - antonio - maria Listagem 1.234: Arquivo YAML de teste 101

CAPTULO 1. RUBY E agora vamos ler esse arquivo, tendo o resultado convertido em um Array:
1 2 3 4

require "yaml" result = YAML::load(File.open(ARGV[0])) p result Listagem 1.235: Lendo um arquivo YAML $ ruby leryaml.rb teste.yml ["jos", "joo", "antonio", "maria"] Podemos ter Arrays dentro de Arrays:

1 2 3 4 5 6 7

--- joo - jos - maria - antonio Listagem 1.236: Arrays dentro de Arrays em YAML $ ruby leryaml.rb teste2.yml [["joo", "jos"], ["maria", "antonio"]] Agora vamos ver como fazer uma Hash:

1 2 3 4 5

--jos: 1 joo: 2 antonio: 3 maria: 4 Listagem 1.237: Hashes em YAML $ ruby leryaml.rb teste3.yml {"jos"=>1, "joo"=>2, "antonio"=>3, "maria"=>4} Hashes dentro de Hashes:

1 2 3 4 5 6

--pessoas: joo: 1 jos: 2 maria: 3 antonio: 4 Listagem 1.238: Hashes dentro de Hashes em YAML $ ruby leryaml.rb teste4.yml {"pessoas"=>{"joo"=>1, "jos"=>2, "maria"=>3, "antonio"=>4}} O que nos d, com um arquivo de congurao do banco de dados do Rails:

1 2 3 4 5

--development: adapter: mysql database: teste_development username: root 102

1.7. ENTRADA E SADA


6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

password: test host: localhost test: adapter: mysql database: teste_test username: root password: test host: localhost production: adapter: mysql database: teste_production username: root password: test host: localhost Listagem 1.239: Ambientes do Rails em YAML $ ruby leryaml.rb teste5.yml {"development"=>{"adapter"=>"mysql", "database"=>"teste_development", "username"=>"root", "password"=>"test", "host"=>"localhost"}, "test"=>{"adapter"=>"mysql", "database"=>"teste_test", "username"=>"root", "password"=>"test", "host"=>"localhost"}, "production"=>{"adapter"=>"mysql", "database"=>"teste_production", "username"=>"root", "password"=>"test", "host"=>"localhost"}}

1.7.6
TCP

Comunicando com a rede

Vamos vericar um servidor SMTP, usando sockets TCP:


1 2 3 4 5 6 7

require "socket" TCPSocket.open("smtp.mail.yahoo.com",25) do |smtp| puts smtp.gets smtp.puts "EHLO bluefish.com.br" puts smtp.gets end Listagem 1.240: Lendo de um Socket $ ruby sock.rb 220 smtp209.mail.ne1.yahoo.com ESMTP 250-smtp209.mail.ne1.yahoo.com Vamos criar um servidor com TCP:

1 2 3 4 5 6 7 8 9

# encoding: utf-8 require "socket" TCPServer.open("localhost",8081) do |server| puts "servidor iniciado" loop do puts "aguardando conexo ..." con = server.accept puts "conexo recebida!" 103

CAPTULO 1. RUBY
10 11 12 13

con.puts Time.now con.close end end Listagem 1.241: Lendo de um Socket $ ruby tcpserver.rb servidor iniciado aguardando conexo ... conexo recebida! $ telnet localhost 8081 Trying ::1... Connected to localhost.localdomain. Escape character is ^]. 2011-07-06 18:42:48 -0300 Connection closed by foreign host. Podemos trafegar, alm de Strings, outros tipos pela conexo TCP, fazendo uso de pack unpack 10 :
9

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

# encoding: utf-8 require "socket" TCPServer.open("localhost",8081) do |server| puts "servidor iniciado" loop do puts "aguardando conexo ..." con = server.accept rst = con.recv(1024).unpack("LA10A*") fix = rst[0] str = rst[1] hash = Marshal.load(rst[2]) puts "#{fix.class}\t: #{fix}" puts "#{str.class}\t: #{str}" puts "#{hash.class}\t: #{hash}" con.close end end Listagem 1.242: Recebendo dados de um socket TCP require "socket" hash = {um: 1,dois: 2, tres: 3} TCPSocket.open("localhost",8081) do |server| server.write [1,"teste".ljust(10),Marshal.dump(hash)].pack("LA10A*") end Listagem 1.243: Enviando dados para um socket TCP $ ruby tcpserver2.rb servidor iniciado aguardando conexo ...
9 http://ruby-doc.org/core/classes/Array.htmlM000206 10 http://ruby-doc.org/core/classes/String.htmlM001112

1 2 3 4 5 6 7

104

1.7. ENTRADA E SADA Fixnum : 1 String : teste Hash : {:um=>1, :dois=>2, :tres=>3} aguardando conexo ... $ ruby tcpclient.rb UDP Vamos escrever dois programas que nos permitem enviar e receber pacotes usando o protocolo UDP 11 . Primeiro, o cdigo do servidor:
1 2 3 4 5 6 7 8 9 10 11 12 13 14

# encoding: utf-8 require socket server = UDPSocket.new server.bind("localhost",12345) puts "Servidor conectado na porta #{porta}, aguardando ..." loop do msg,sender = server.recvfrom(256) host = sender[3] puts "Host #{host} enviou um pacote UDP: #{msg}" break unless msg.chomp != "kill" end puts "Kill recebido, fechando servidor." server.close Listagem 1.244: Servidor UPD Agora o cdigo do cliente:

1 2 3 4 5 6 7 8 9 10 11 12

# encoding: utf-8 require socket client = UDPSocket.open client.connect("localhost",12345) loop do puts "Digite sua mensagem (quit termina, kill finaliza servidor):" msg = gets client.send(msg,0) break unless !"kill,quit".include? msg.chomp end client.close Listagem 1.245: Cliente UPD Rodando o servidor e o cliente: $ ruby udpserver.rb Servidor conectado na porta 12345, aguardando ... Host 127.0.0.1 enviou um pacote UDP: oi Host 127.0.0.1 enviou um pacote UDP: tudo bem? Host 127.0.0.1 enviou um pacote UDP: kill Kill recebido, fechando servidor. $ ruby code/udpclient.rb Digite sua mensagem (quit termina, kill finaliza servidor): oi
11 http://pt.wikipedia.org/wiki/User atagram rotocol D P

105

CAPTULO 1. RUBY Digite sua mensagem (quit termina, kill finaliza servidor): tudo bem? Digite sua mensagem (quit termina, kill finaliza servidor): kill SMTP O bom que h uma classe SMTP pronta para o uso em Ruby:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

# encoding: utf-8 require "net/smtp" require "highline/import" from pass to = "eustaquiorangel@gmail.com" = ask("digite sua senha:") {|q| q.echo="*"} = "eustaquiorangel@gmail.com"

msg =<<FIM From: #{from} Subject: Teste de SMTP no Ruby Apenas um teste de envio de email no Ruby. Falou! FIM smtp = Net::SMTP.new("smtp.gmail.com",587) smtp.enable_starttls begin smtp.start("localhost",from,pass,:plain) do |smtp| puts "conexo aberta!" smtp.send_message(msg,from,to) puts "mensagem enviada!" end rescue => exception puts "ERRO: #{exception}" puts exception.backtrace end Listagem 1.246: Enviando e-mail $ ruby smtp.rb digite sua senha: ******** conexo aberta! mensagem enviada! Dica: Na linha 3 requisitamos o mdulo highline , que nos permite "mascarar"a digitao da senha na linha 6.

FTP Vamos requisitar um arquivo em um servidor FTP:


1 2

# encoding: utf-8 require "net/ftp" 106

1.7. ENTRADA E SADA


3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

host user pass file

= = = =

"ftp.mozilla.org" "anonymous" "eustaquiorangel@gmail.com" "README"

begin Net::FTP.open(host) do |ftp| puts "Conexo FTP aberta." ftp.login(user,pass) puts "Requisitando arquivo ..." ftp.chdir("pub") ftp.get(file) puts "Download efetuado." puts File.read(file) end rescue => exception puts "ERRO: #{exception}" end Listagem 1.247: Recebendo arquivo por FTP $ ruby ftp.rb Conexo FTP aberta. Requisitando arquivo ... Download efetuado. Welcome to ftp.mozilla.org! This is a distribution point for software and developer tools related to the Mozilla project. For more information, see our home page: ... POP3 Para fechar o pacote de e-mail, temos a classe POP3:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

# encoding: utf-8 require "net/pop" require "highline/import" user pass = "eustaquiorangel@gmail.com" = ask("digite sua senha:") {|q| q.echo="*"}

pop = Net::POP3.new("pop.gmail.com",995) pop.enable_ssl(OpenSSL::SSL::VERIFY_NONE) begin pop.start(user,pass) do |pop| if pop.mails.empty? puts "Sem emails!" return end pop.each do |msg| puts msg.header end end 107

CAPTULO 1. RUBY
21 22 23

rescue => exception puts "ERRO: #{exception}" end Listagem 1.248: Recebendo email $ ruby pop3.rb digite sua senha: ******** Return-Path: <eustaquiorangel@gmail.com> Received: from localhost ([186.222.196.152]) by mx.google.com with ESMTPS id x15sm1427881vcs.32.2011.07.06.14.14.13 (version=TLSv1/SSLv3 cipher=OTHER); Wed, 06 Jul 2011 14:14:17 -0700 (PDT) Message-ID: <4e14d029.8f83dc0a.6a32.5cd7@mx.google.com> Date: Wed, 06 Jul 2011 14:14:17 -0700 (PDT) From: eustaquiorangel@gmail.com Subject: Teste de SMTP no Ruby HTTP Vamos ler o contedo do meu site e procurar alguns elementos H1:

1 2 3 4 5 6

require "net/http" host = Net::HTTP.new("eustaquiorangel.com",80) resposta = host.get("/") return if resposta.message != "OK" puts resposta.body.scan(/<h1>.*<\/h1>/) Listagem 1.249: Lendo via HTTP $ ruby http1.rb <h1>Blog do TaQ</h1> <h1><a href="/posts/dando_um_novo_g_s_para_o_plugin_snipmate_do_vim"> Dando um novo gs para o plugin Snipmate do Vim</a></h1> <h1>Artigos anteriores</h1> <h1>Recomendados!</h1> <h1>Busca</h1> <h1>Twitter</h1> Abrir um uxo HTTP muito fcil, mas d para car mais fcil ainda! Vamos usar o OpenURI, que abre HTTP, HTTPS e FTP, o que vai nos dar resultados similares ao acima:

1 2 3 4

require "open-uri" resposta = open("http://eustaquiorangel.com") puts resposta.read.scan(/<h1>.*<\/h1>/) Listagem 1.250: Lendo via HTTP com OpenURI Podemos melhorar o cdigo usando um parser para selecionar os elementos. Lembrando que j utilizamos a Nokokiri para XML, podemos utilizar tambm para HTTP:

1 2 3 4 5 6

require "rubygems" require "open-uri" require "nokogiri" doc = Nokogiri::HTML(open("http://eustaquiorangel.com")) puts doc.search("h1").map {|elemento| elemento.text} Listagem 1.251: Lendo e "parseando"via HTTP com OpenURI e Nokogiri 108

1.7. ENTRADA E SADA $ ruby http3.rb Blog do TaQ Dando um novo gs para o plugin Snipmate do Vim Artigos anteriores Recomendados! Busca Twitter Aproveitando que estamos falando de HTTP, vamos ver como disparar um servidor web, o WEBrick, que j vem com Ruby:
1 2 3 4 5 6

require "webrick" include WEBrick s = HTTPServer.new(:Port=>2000,:DocumentRoot=>Dir.pwd) trap("INT") { s.shutdown } s.start Listagem 1.252: Disparando o servidor web Webrick

$ ruby webrick.rb [2011-07-06 20:56:54] [2011-07-06 20:56:54] [2011-07-06 20:56:54] [2011-07-06 20:56:54] HTTPS

INFO INFO WARN INFO

WEBrick 1.3.1 ruby 1.9.2 (2010-08-18) [i686-linux] TCPServer Error: Address already in use - bind(2) WEBrick::HTTPServer#start: pid=19677 port=2000

Podemos acessar HTTPS facilmente:


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

require "net/https" require "highline/import" user = "user" pass = ask("digite sua senha") {|q| q.echo="*"} begin site = Net::HTTP.new("api.del.icio.us",443) site.use_ssl = true site.start do |http| req = Net::HTTP::Get.new(/v1/tags/get) req.basic_auth(user,pass) response = http.request(req) print response.body end rescue => exception puts "erro: #{e}" end Listagem 1.253: Utilizando HTTPS

SSH Vamos abrir uma conexo SSH e executar alguns comandos. Para isso precisamos da gem net-ssh: ruby install net-ssh 109

CAPTULO 1. RUBY E agora vamos rodar um programa similar ao seguinte, onde voc deve alterar o host, usurio e senha para algum que voc tenha acesso:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

# encoding: utf-8 require "rubygems" require "net/ssh" require "highline/import" host = "eustaquiorangel.com" user = "taq" pass = ask("digite sua senha") {|q| q.echo="*"} begin Net::SSH.start(host,user,:password=>pass) do |session| puts "Sesso SSH aberta!" session.open_channel do |channel| puts "Canal aberto!" channel.on_data do |ch,data| puts "> #{data}" end puts "Executando comando ..." channel.exec "ls -lah" end session.loop end rescue => exception puts "ERRO:#{exception}" puts exception.backtrace end Listagem 1.254: Utilizando SSH $ ruby code/ssh.rb digite sua senha ************* Sesso SSH aberta! Canal aberto! Executando comando > total 103M drwxr-xr-x 6 taq drwxr-xr-x 6 root -rw------- 1 taq -rw-r--r-- 1 taq -rw-r--r-- 1 taq drwxr-xr-x 2 taq -rw-r--r-- 1 taq -rw-r--r-- 1 taq drwxr-xr-x 2 taq ... XML-RPC XML-RPC , segundo a descrio em seu site
12

... taq root taq taq taq taq taq taq taq 4.0K 4.0K 601 220 3.1K 4.0K 18 675 4.0K Jun Jun Jul Apr Apr May Jun Apr Jun 17 27 4 19 19 11 20 19 17 19:10 22:16 21:30 2010 2010 00:21 17:08 2010 19:09 . .. .bash_history .bash_logout .bashrc .cache .irb-history .profile .ssh

uma especicao e um conjunto de implementaes que permitem softwares rodando em sistemas operacionais diferentes, rodando em diferentes ambientes, fazerem chamadas de procedures pela internet.
12 http://www.xmlrpc.com

110

1.7. ENTRADA E SADA A chamada de procedures remotas feita usando HTTP como transporte e XML como o encoding. XML-RPC desenhada para ser o mais simples possvel, permitindo estruturas de dados completas serem transmitidas, processadas e retornadas. Tentando dar uma resumida, voc pode escrever mtodos em vrias linguagens rodando em vrios sistemas operacionais e acessar esses mtodos atravs de vrias linguagens e vrios sistemas operacionais. Antes de mais nada, vamos criar um servidor que vai responder as nossas requisies, fazendo algumas operaes matemticas bsicas, que sero adio e diviso:
1 2 3 4 5 6 7 8 9 10 11 12 13 14

require "xmlrpc/server" server = XMLRPC::Server.new(8081) # somando nmeros server.add_handler("soma") do |n1,n2| {"resultado"=>n1+n2} end # dividindo e retornando o resto server.add_handler("divide") do |n1,n2| {"resultado"=>n1/n2,"resto"=>n1%n2} end server.serve Listagem 1.255: Servidor XML-RPC $ ruby rpcserver.rb [2011-07-06 21:16:07] INFO [2011-07-06 21:16:07] INFO [2011-07-06 21:16:07] INFO

WEBrick 1.3.1 ruby 1.9.2 (2010-08-18) [i686-linux] WEBrick::HTTPServer#start: pid=20414 port=8081

Agora vamos fazer um cliente para testar (voc pode usar qualquer outra linguagem que suporte RPC que desejar):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

# encoding: utf-8 require "xmlrpc/client" begin client = XMLRPC::Client.new("localhost","/RPC2",8081) resp = client.call("soma",5,3) puts "O resultado da soma #{resp[resultado]}" resp = client.call("divide",11,4) puts "O resultado da divisao #{resp[resultado]} e o resto #{resp[resto]}" rescue => exception puts "ERRO: #{exception}" end Listagem 1.256: Cliente XML-RPC em Ruby $ ruby rpcclient.rb O resultado da soma 8 O resultado da divisao 2 e o resto 3 111

CAPTULO 1. RUBY Vamos acessar agora o servidor de outras linguagens. Python


1 2 3 4 5 6 7 8 9 10

# coding: utf-8 import xmlrpclib server = xmlrpclib.Server("http://localhost:8081") result = server.soma(5,3) print "O resultado da soma :",result["resultado"] result = server.divide(11,4) print "O resultado da diviso ",result["resultado"],"e o resto ",result[" resto"] Listagem 1.257: Cliente XML-RPC em Python $ python rpcclient.py O resultado da soma : 8 O resultado da diviso 2 e o resto 3 PHP

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

<?php // soma $request = xmlrpc_encode_request("soma", array(5,3)); $context = stream_context_create(array(http => array( method => "POST", header => "Content-Type: text/xml", content => $request ))); $file = file_get_contents("http://localhost:8081", false, $context); $response = xmlrpc_decode($file); if ($response && xmlrpc_is_fault($response)) { trigger_error("xmlrpc: $response[faultString] ($response[faultCode])"); } else { print "O resultado da soma ".$response["resultado"]."\n"; } // diviso $request = xmlrpc_encode_request("divide", array(11,4)); $context = stream_context_create(array(http => array( method => "POST", header => "Content-Type: text/xml", content => $request ))); $file = file_get_contents("http://localhost:8081", false, $context); $response = xmlrpc_decode($file); if ($response && xmlrpc_is_fault($response)) { trigger_error("xmlrpc: $response[faultString] ($response[faultCode])"); } else { print "O resultado da diviso ".$response["resultado"]." e o resto " . 112

1.7. ENTRADA E SADA


32 33 34

$response["resto"]."\n"; } ?> Listagem 1.258: Cliente XML-RPC em PHP

$ php rpcclient.php O resultado da soma 8 O resultado da diviso 2 e o resto 3 Java Em Java vamos precisar do Apache XML-RPC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

13

import import import import import

java.net.URL; java.util.Vector; java.util.HashMap; org.apache.xmlrpc.common.*; org.apache.xmlrpc.client.*;

public class RPCClient { public static void main(String args[]){ try{ Vector <Integer>params; XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl(); config.setServerURL(new URL("http://localhost:8081/RPC2")); XmlRpcClient server = new XmlRpcClient(); server.setConfig(config); params = new Vector<Integer>(); params.addElement(new Integer(5)); params.addElement(new Integer(3)); HashMap result = (HashMap) server.execute("soma",params); int sum = ((Integer) result.get("resultado")).intValue(); System.out.println("O resultado da soma "+Integer.toString(sum)) ; params = new Vector<Integer>(); params.addElement(new Integer(11)); params.addElement(new Integer(4)); result = (HashMap) server.execute("divide",params); int divide = ((Integer) result.get("resultado")).intValue(); int resto = ((Integer) result.get("resto")).intValue(); System.out.println("O resultado da diviso "+Integer.toString( sum)+ " e o resto : "+Integer.toString(resto)); }catch(Exception error){ System.err.println("erro:"+error.getMessage()); } } } Listagem 1.259: Cliente XML-RPC em Java
13 http://ws.apache.org/xmlrpc

24 25 26 27 28 29 30 31 32

33 34 35 36 37 38

113

CAPTULO 1. RUBY $ javac -classpath commons-logging-1.1.jar:ws-commons-util-1.0.2.jar: xmlrpc-client-3.1.3.jar:xmlrpc-common-3.1.3.jar: RPCClient.java $ java -classpath commons-logging-1.1.jar:ws-commons-util-1.0.2.jar: xmlrpc-client-3.1.3.jar:xmlrpc-common-3.1.3.jar: RPCClient O resultado da soma 8 O resultado da diviso 8 e o resto : 3

114

1.8. JRUBY

1.8

JRuby

o para dar uma olhada V a RVM instalarde JRubynada, pedimos para verem como integrar Ruby com Java, usando . Antes mais as notas da RVM:
AMOS

$ rvm notes # For JRuby (if you wish to use it) you will need: jruby: /usr/bin/apt-get install curl g++ openjdk-6-jre-headless jruby-head: /usr/bin/apt-get install ant openjdk-6-jdk Vamos instalar a verso jruby-head, pois no momento que estou escrevendo isso o pessoal do JRuby est tendo alguns problemas com os nomes das classes 14 : $ rvm install jruby-head $ rvm use jruby-head $ $export CLASSPATH=/home/aluno/.rvm/rubies/jruby-head/lib/jruby.jar:.: $ jruby -v jruby 1.7.0.dev (ruby-1.8.7-p330) (2011-07-07 9d96293) (OpenJDK Client VM 1.6.0_20) [linux-i386-java] Agora fazendo um pequeno programa em Ruby:
1 2 3

puts "digite seu nome:" nome = gets.chomp puts "oi, #{nome}!" Listagem 1.260: Teste simples em JRuby $ jrubyc jruby.rb $ java jruby digite seu nome: taq oi, taq!

1.8.1
1 2 3

Utilizando classes do Java de dentro do Ruby

require "java" %w(JFrame JLabel JPanel JButton).each { |c| include_class("javax.swing.#{c} ") } class Alistener include java.awt.event.ActionListener def actionPerformed(event) puts "Boto clicado!" end end listener = Alistener.new frame = JFrame.new label = JLabel.new("Clique no boto!") panel = JPanel.new button = JButton.new("Clique em mim!") button.addActionListener(listener)
14 http://jira.codehaus.org/browse/JRUBY-5724

4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

115

CAPTULO 1. RUBY
19 20 21 22 23 24 25 26 27 28

panel.setLayout(java.awt.GridLayout.new(2,1)) panel.add(label) panel.add(button) frame.setTitle("Exemplo de JRuby") frame.getContentPane().add(panel) frame.pack frame.defaultCloseOperation = JFrame::EXIT_ON_CLOSE frame.setVisible(true) Listagem 1.261: Teste simples em JRuby e Java Swing Pudemos ver que criamos a classe Alistener com a interface, no caso aqui com um comportamento de mdulo, java.awt.event.ActionListener, ou seja, JRuby nos permite utilizar interfaces do Java como se fossem mdulos de Ruby! E tem mais, podemos fazer com que nossas classes em Ruby herdem de classes em Java:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

public class Carro { private String marca, cor, modelo; private int tanque; public Carro(String marca, String cor, String modelo, int tanque) { this.marca = marca; this.cor = cor; this.modelo = modelo; this.tanque = tanque; } public String toString() { return "Marca: "+this.marca + "\n" + "Cor: "+this.cor + "\n" + "Modelo:"+this.modelo + "\n" + "Tanque:"+this.tanque; } } Listagem 1.262: Carro em Java

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

require "java" include_class("Carro") carro = Carro.new("VW","prata","polo",40) puts carro class Mach5 < Carro attr_reader :tanque_oxigenio def initialize(marca,cor,modelo,tanque,tanque_oxigenio) super(marca,cor,modelo,tanque) @tanque_oxigenio = tanque_oxigenio end def to_s "#{super}\nTanque oxigenio: #{@tanque_oxigenio}" end end

116

1.8. JRUBY
20 21 22

puts "*"*25 mach5 = Mach5.new("PopsRacer","branco","Mach5",50,10) puts mach5 Listagem 1.263: Carro vindo do Java $ javac Carro.java $ jrubyc carro_java.rb $ java carro_java Marca: VW Cor: prata Modelo:polo Tanque:40 ************************* Marca: PopsRacer Cor: branco Modelo:Mach5 Tanque:50 Tanque oxigenio: 10

1.8.2

Usando classes do Ruby dentro do Java

Existe um jeito de fazer isso, mas vo por mim: no compensa pois vocs vo xingar muito o Java. Para maiores referncias, podem consultar o site ocial de scripting para Java15 .

15 http://java.net/projects/scripting/

117

CAPTULO 1. RUBY

1.9

Banco de dados

Vamos utilizar uma interface uniforme para acesso aos mais diversos bancos de dados suportados em Ruby atravs da interface Sequel16 . Para instal-la, s utilizar a gem: gem install sequel

1.9.1

Abrindo a conexo

Vamos abrir e fechar a conexo com o banco:


1 2

con = Sequel.mysql(:user=>"root",:password=>"aluno",:host=>"localhost", :database=>"aluno") => #<Sequel::MySQL::Database: "mysql://root:aluno@localhost/aluno"> Para dar uma encurtada no cdigo e praticidade maior, vamos usar um bloco logo aps conectar, para onde vai ser enviado o handle da conexo:

1 2 3 4 5 6

require "sequel" Sequel.mysql(:user=>"aluno",:password=>"aluno", :host=>"localhost",:database=>"alunos") do |con| p con end Listagem 1.264: Conectando e desconectando com o banco de dados Desse modo sempre que a conexo for aberta, ela ser automaticamente fechada no m do bloco. Dica: para trocar o banco de dados, podemos alterar apenas o mtodo de conexo. Se, por exemplo, quisermos utilizar o SQLite3, podemos utilizar:
1 2 3 4 5

require "sequel" Sequel.sqlite("alunos.sqlite3") do |con| p con end Listagem 1.265: Conectando com o SQLite3 $ ruby db2.rb #<Sequel::SQLite::Database: "sqlite:/alunos.sqlite3">

1.9.2

Consultas que no retornam dados

Vamos criar uma tabela nova para usamos no curso, chamada de alunos e inserir alguns valores:
1 2 3 4 5 6

# encoding: utf-8 require "sequel" Sequel.mysql(:user=>"aluno",:password=>"aluno", :host=>"localhost",:database=>"alunos") do |con| con.run("drop table if exists alunos")
16 http://sequel.rubyforge.org/

118

1.9. BANCO DE DADOS


7 8 9 10 11 12 13 14 15 16 17 18

sql = <<FIM create table alunos ( id int(6) not null, nome varchar(50) not null) FIM con.run(sql) con[:alunos].insert(:id=>1,:nome=>Joo) con[:alunos].insert(:id=>2,:nome=>Jos) con[:alunos].insert(:id=>3,:nome=>Antonio) con[:alunos].insert(:id=>4,:nome=>Maria) end Listagem 1.266: Consultas sem retornar dados

$ ruby db3.rb mysql -h localhost -u root -p aluno Enter password: mysql> select * from alunos; +----+---------+ | id | nome | +----+---------+ | 1 | Joo | | 2 | Jos | | 3 | Antonio | | 4 | Maria | +----+---------+ 4 rows in set (0.03 sec)
1 2 3 4 5 6 7

# encoding: utf-8 require "sequel" Sequel.mysql(:user=>"aluno",:password=>"aluno", :host=>"localhost",:database=>"alunos") do |con| puts con[:alunos].where(:id=>4).update(:nome=>"Mrio") end Listagem 1.267: Atualizando um registro

$ ruby db13.rb
1 2 3 4 5 6 7 8

# encoding: utf-8 require "sequel" Sequel.mysql(:user=>"aluno",:password=>"aluno", :host=>"localhost",:database=>"alunos") do |con| con[:alunos].insert(:id=>5,:nome=>"Teste") puts con[:alunos].where(:id=>5).delete end Listagem 1.268: Apagando um registro

$ ruby db14.rb 1 119

CAPTULO 1. RUBY

1.9.3

Consultas que retornam dados

Vamos recuperar alguns dados do nosso banco, anal, essa a operao mais costumeira, certo? Para isso, vamos ver duas maneiras. Primeiro, da maneira convencional:
1 2 3 4 5 6 7 8 9

# encoding: utf-8 require "sequel" Sequel.mysql(:user=>"aluno",:password=>"aluno", :host=>"localhost",:database=>"alunos") do |con| con[:alunos].each do |row| puts "id: #{row[:id]} nome: #{row[:nome]}" end end Listagem 1.269: Consultas que retornam dados

$ ruby db4.rb id: 1 nome: Joo id: 2 nome: Jos id: 3 nome: Antonio id: 4 nome: Mrio Podemos recuperar todos as linhas de dados de uma vez usando all:
1 2 3 4 5 6 7 8

require "sequel" Sequel.mysql(:user=>"aluno",:password=>"aluno", :host=>"localhost",:database=>"alunos") do |con| rows = con[:alunos].all puts "#{rows.size} registros recuperados" rows.each {|row| puts "id: #{row[:id]} nome: #{row[:nome]}"} end Listagem 1.270: Recuperando todas as linhas da consulta

$ ruby db5.rb 4 registros recuperados id: 1 nome: Joo id: 2 nome: Jos id: 3 nome: Antonio id: 4 nome: Mrio Ou se quisermos somente o primeiro registro:
1 2 3 4 5 6 7

require "sequel" Sequel.mysql(:user=>"aluno",:password=>"aluno", :host=>"localhost",:database=>"alunos") do |con| row = con[:alunos].first puts "id: #{row[:id]} nome: #{row[:nome]}" end Listagem 1.271: Usando rst

$ ruby db8.rb id: 1 nome: Joo 120

1.9. BANCO DE DADOS

1.9.4

Comandos preparados

Agora vamos consultar registro por registro usando comandos preparados com argumentos variveis, o que vai nos dar resultados similares mas muito mais velocidade quando executando a mesma consulta SQL trocando apenas os argumentos que variam:
1 2 3 4 5 6 7 8 9 10 11 12

require "sequel" Sequel.mysql(:user=>"aluno",:password=>"aluno", :host=>"localhost",:database=>"alunos") do |con| ds = con[:alunos].filter(:id=>:$i) ps = ds.prepare(:select,:select_by_id) (1..4).each do |id| print "procurando id #{id} ... " row = ps.call(:i=>id) puts "#{row.first[:nome]}" end end Listagem 1.272: Comandos SQL preparados $ ruby db9.rb procurando id procurando id procurando id procurando id

1 2 3 4

... ... ... ...

Joo Jos Antonio Mrio

1.9.5

Metadados

Vamos dar uma examinada nos dados que recebemos de nossa consulta e na estrutura de uma tabela:
1 2 3 4 5 6 7

require "sequel" Sequel.mysql(:user=>"aluno",:password=>"aluno", :host=>"localhost",:database=>"alunos") do |con| p con[:alunos].columns p con.schema(:alunos) end Listagem 1.273: Metadados $ ruby db10.rb [{:mysql_flags=>36865, :type_name=>"INTEGER", :dbi_type=>DBI::Type::Integer, :unique=>false, :mysql_length=>6, :mysql_type=>3, :mysql_max_length=>1, :precision=>6, :indexed=>false, :sql_type=>4, :mysql_type_name=>"INT", :scale=>0, :name=>"id", :primary=>false, :nullable=>false}, {:mysql_flags=>4097, :type_name=>"VARCHAR", :dbi_type=>DBI::Type::Varchar, :unique=>false, :mysql_length=>50, :mysql_type=>253, :mysql_max_length=>7, :precision=>50, :indexed=>false, :sql_type=>12, :mysql_type_name=>"VARCHAR", :scale=>0, :name=>"nome", :primary=>false, :nullable=>false}]

1.9.6

ActiveRecord

Como uma chamada para a segunda parte do curso, Rails, e uma forma de mostrar que possvel utilizar o motorzo ORM do Rails sem o Rails, vamos ver como criar e usar um 121

CAPTULO 1. RUBY modelo da nossa tabela alunos. Vamos escrever nosso programa Rails standalone, mas antes vamos atender uma pequena requisio do ActiveRecord, que pede uma coluna chamada id como chave primria: mysql -h localhost -u root -p aluno Enter password: mysql> alter table alunos add primary key (id); Query OK, 4 rows affected (0.18 sec) Records: 4 Duplicates: 0 Warnings: 0 mysql> desc alunos; +-------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+-------+ | id | int(6) | NO | PRI | NULL | | | nome | varchar(50) | NO | | NULL | | +-------+-------------+------+-----+---------+-------+ 2 rows in set (0.00 sec) mysql> quit Bye Agora, nosso programa:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

# encoding: utf-8 require "rubygems" require "active_record" # estabelecendo a conexo ActiveRecord::Base.establish_connection({ :adapter => "mysql", :database => "aluno", :username => "root", :password => "aluno" }) # criando o mapeamento da classe com a tabela # (espera a s isso???) class Aluno < ActiveRecord::Base end # pegando a coleo e usando o seu iterador for aluno in Aluno.all puts "id: #{aluno.id} nome: #{aluno.nome}" end # atualizando o nome de um aluno aluno = Aluno.find(3) puts "encontrei #{aluno.nome}" aluno.nome = "Danilo" aluno.save Listagem 1.274: Acessando o banco com um ORM Rodando o programa: $ ruby arec.rb id: 1 nome: Joo 122

1.9. BANCO DE DADOS id: 2 nome: Jos id: 3 nome: Antonio id: 4 nome: Maria encontrei Antonio Se rodarmos novamente, vamos vericar que o registro foi alterado, quando rodamos anteriormente: $ ruby arec.rb id: 1 nome: Joo id: 2 nome: Jos id: 3 nome: Danilo id: 4 nome: Maria encontrei Danilo

123

CAPTULO 1. RUBY

1.10

Escrevendo extenses para Ruby, em C

Se quisermos incrementar um pouco a linguagem usando linguagem C para a) maior velocidade b) recursos especcos do sistema operacional que no estejam disponveis na implementao padro, c) algum desejo mrbido de lidar com segfaults e ponteiros nulos ou d) todas as anteriores, podemos escrever facilmente extenses em C. Vamos criar um mdulo novo chamado Curso com uma classe chamada Horario dentro dele, que vai nos permitir cadastrar uma descrio da instncia do objeto no momento em que o criarmos, e vai retornar a data e a hora correntes em dois mtodos distintos. Que uso prtico isso teria no sei, mas vamos relevar isso em funo do exemplo didtico do cdigo apresentado. ;-) A primeira coisa que temos que fazer criar um arquivo chamado extconf.rb, que vai usar o mdulo mkmf para criar um Makefile que ir compilar os arquivos da nossa extenso: require "mkmf" extension_name = "curso" dir_config(extension_name) create_makefile(extension_name) Vamos assumir essa sequncia de cdigo como a nossa base para fazer extenses, somente trocando o nome da extenso na varivel extension_name. Agora vamos escrever o fonte em C da nossa extenso, como diria Jack, O Estripador, por partes. Crie um arquivo chamado curso.c com o seguinte contedo:
1 2 3 4 5 6 7 8 9

#include <ruby.h> #include <time.h> VALUE modulo, classe; void Init_curso(){ modulo = rb_define_module("Curso"); classe = rb_define_class_under(modulo,"Horario",rb_cObject); } Listagem 1.275: Primeiro fonte em C Opa! J temos algumas coisas denidas ali! Agora temos que criar um Makefile para compilarmos nossa extenso: $ ruby extconf.rb creating Makefile E agora vamos executador o make para ver o que acontece: $ make gcc -I. -I/home/aluno/.rvm/rubies/ruby-1.9.2-p180/include/ruby-1.9.1/i686-linux -I/home/aluno/.rvm/rubies/ruby-1.9.2-p180/include/ruby-1.9.1/ruby/backward -I/home/aluno/.rvm/rubies/ruby-1.9.2-p180/include/ruby-1.9.1 -I. -D_FILE_OFFSET_BITS=64 -fPIC -O3 -ggdb -Wextra -Wno-unused-parameter -Wno-parentheses -Wpointer-arith -Wwrite-strings -Wno-missing-field-initializers -Wno-long-long -fPIC -o curso.o -c curso.c gcc -shared -o curso.so curso.o -L. -L/home/aluno/.rvm/rubies/ruby-1.9.2-p180/lib -Wl,-R/home/aluno/.rvm/rubies/ruby-1.9.2-p180/lib -L. -rdynamic 124

1.10. ESCREVENDO EXTENSES PARA RUBY, EM C -Wl,-export-dynamic -Wl,-R -Wl,/home/aluno/.rvm/rubies/ruby-1.9.2-p180/lib -L/home/aluno/.rvm/rubies/ruby-1.9.2-p180/lib -lruby -lrt -ldl -lcrypt -lm -lc Dando uma olhada no diretrio, temos: $ ls *.so curso.so Foi gerado um arquivo .so, que um arquivo de bibliotecas compartilhadas do GNU/Linux (a analogia no mundo Windows uma DLL) com o nome que denimos para a extenso, com a extenso apropriada. Vamos fazer um teste no irb para ver se tudo correu bem: require "./curso" => true horario = Curso::Horario.new => #<Curso::Horario:0x991aa4c> Legal, j temos nosso primeiro mdulo e classe vindos diretamente do C! Vamos criar agora o mtodo construtor, alterando nosso cdigo fonte C: Vamos testar, lembrando de rodar o make para compilar novamente o cdigo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14

-lpthread

#include <ruby.h> #include <time.h> VALUE modulo, classe; VALUE t_init(VALUE self, VALUE valor){ rb_iv_set(self,"@descricao",valor); } void Init_curso(){ modulo = rb_define_module("Curso"); classe = rb_define_class_under(modulo,"Horario",rb_cObject); rb_define_method(classe,"initialize",t_init,1); } Listagem 1.276: Construtores em C require "./curso" => true ruby-1.9.2-p180 :002 > horario = Curso::Horario.new ArgumentError: wrong number of arguments(0 for 1) from (irb):2:in initialize from (irb):2:in new from (irb):2 from /home/aluno/.rvm/rubies/ruby-1.9.2-p180/bin/irb:16:in <main> ruby-1.9.2-p180 :003 > horario = Curso::Horario.new(:teste) => #<Curso::Horario:0x8b9e5e4 @descricao=:teste> Foi feita uma tentativa de criar um objeto novo sem passar argumento algum no construtor, mas ele estava esperando um parmetro, denido como o nmero 1 no nal de rb_define_method . Logo aps criamos o objeto enviando um Symbol e tudo correu bem, j temos o nosso construtor! Reparem como utilizamos rb_iv_set (algo como Ruby Instance Variable Set) para criar uma varivel de instncia com o argumento enviado. Mas a varivel de instncia continua sem um mtodo para ler o seu valor, presa no objeto: 125

CAPTULO 1. RUBY horario.descricao NoMethodError: undefined method descricao for #<Curso::Horario:0x8b9e5e4 @descricao=:teste> from (irb):4 Vamos criar um mtodo para acess-la:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

#include <ruby.h> #include <time.h> VALUE modulo, classe; VALUE t_init(VALUE self, VALUE valor){ rb_iv_set(self,"@descricao",valor); } VALUE descricao(VALUE self){ return rb_iv_get(self,"@descricao"); } void Init_curso(){ modulo = rb_define_module("Curso"); classe = rb_define_class_under(modulo,"Horario",rb_cObject); rb_define_method(classe,"initialize",t_init,1); rb_define_method(classe,"descricao",descricao,0); } Listagem 1.277: Reader em C require "./curso" => true horario = Curso::Horario.new(:teste) => #<Curso::Horario:0x8410d04 @descricao=:teste> horario.descricao => :teste Agora para fazer uma graa vamos denir dois mtodos que retornam a data e a hora corrente, como Strings. A parte mais complicada pegar e formatar isso em C. Convm prestar ateno no modo que alocada uma String nova usando rb_str_new2: Dica: Apesar nos nomes parecidos, rb_str_new espera dois argumentos, uma String e o comprimento, enquanto rb_str_new2 espera somente uma String terminada com nulo e bem mais prtica na maior parte dos casos.

1 2 3 4 5 6 7 8 9 10 11 12

#include <ruby.h> #include <time.h> VALUE modulo, classe; VALUE t_init(VALUE self, VALUE valor){ rb_iv_set(self,"@descricao",valor); } VALUE descricao(VALUE self){ return rb_iv_get(self,"@descricao"); } 126

1.10. ESCREVENDO EXTENSES PARA RUBY, EM C


13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41

VALUE data(VALUE self){ time_t dt; struct tm *dc; char str[15]; time(&dt); dc = localtime(&dt); sprintf(str,"%02d/%02d/%04d",dc->tm_mday,dc->tm_mon+1,dc->tm_year+1900); return rb_str_new2(str); } VALUE hora(VALUE self){ time_t dt; struct tm *dc; char str[15]; time(&dt); dc = localtime(&dt); sprintf(str,"%02d:%02d:%02d",dc->tm_hour,dc->tm_min,dc->tm_sec); return rb_str_new2(str); } void Init_curso(){ modulo = rb_define_module("Curso"); classe = rb_define_class_under(modulo,"Horario",rb_cObject); rb_define_method(classe,"initialize",t_init,1); rb_define_method(classe,"descricao",descricao,0); rb_define_method(classe,"data",data,0); rb_define_method(classe,"hora",hora,0); } Listagem 1.278: Retornando valores em C horario = Curso::Horario.new(:teste) => #<Curso::Horario:0x896b6dc @descricao=:teste> horario.descricao => :teste horario.data => "14/07/2011" horario.hora => "15:33:27" Tudo funcionando perfeitamente! Para maiores informaes de como criar extenses para Ruby, uma boa fonte de consultas http://www.rubycentral.com/pickaxe/ext_ruby.html.

127

CAPTULO 1. RUBY

1.11

Garbage collector

Vamos aproveitar que estamos falando de coisa de um nvel mais baixo (no, no de poltica) e vamos investigar como funciona o garbage collector do Ruby, que do tipo mark-and-sweep, fazendo um teste prtico de criar alguns objetos, invalidar algum, chamar o garbage collector e vericar os objetos novamente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

class Teste end t1 = Teste.new t2 = Teste.new t3 = Teste.new count = ObjectSpace.each_object(Teste) do |object| puts object end puts "#{count} objetos encontrados." t2 = nil GC.start count = ObjectSpace.each_object(Teste) do |object| puts object end puts "#{count} objetos encontrados." Listagem 1.279: Garbage collector $ ruby gc1.rb #<Teste:0x850d1a8> #<Teste:0x850d1bc> #<Teste:0x850d1d0> 3 objetos encontrados. #<Teste:0x850d1a8> #<Teste:0x850d1d0> 2 objetos encontrados. Funciona assim:

128

1.11. GARBAGE COLLECTOR Na Fase 1, todos os objetos no esto marcados como acessveis. Na Fase 2, continuam do mesmo jeito, porm o objeto 1 agora no est disponvel no root. Na Fase 3, o algoritmo foi acionado, parando o programa e marcando (mark) os objetos que esto acessveis. Na Fase 4 foi executada a limpeza (sweep) dos objetos no-acessveis, e retirado o ag dos que estavam acessveis (deixando-os em preto novamente), forando a sua vericao na prxima vez que o garbage collector rodar.

129

CAPTULO 1. RUBY

1.12

Unit testing

Se voc for usar Rails e no aprender a usar o recurso de unit testing, que j vem todo estruturado, estar relegando um ganho de produtividade muito grande. Testes unitrios so meios de testar e depurar pequenas partes do seu cdigo, para vericar se no tem alguma coisa errada acontecendo, modularizando a checagem de erros. Um sistema feito de vrias camadas ou mdulos, e os testes unitrios tem que ser rodados nessas camadas. Vamos usar de exemplo uma calculadora que s tem soma e subtrao, ento vamos fazer uma classe para ela:
1 2 3 4 5 6 7 8

class Calculadora def soma(a,b) a+b end def subtrai(a,b) a-b end end Listagem 1.280: Classe da calculadora E agora o nosso teste propriamente dito:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

require "test/unit" require_relative "calc" class TesteCalculadora < Test::Unit::TestCase def setup @calculadora = Calculadora.new end def test_adicao assert_equal(2,@calculadora.soma(1,1),"1+1=2") end def test_subtracao assert_equal(0,@calculadora.subtrai(1,1),"1-1=0") end def teardown @calculadora = nil end end Listagem 1.281: Testes para classe da calculadora Rodando os testes: $ ruby testcalc1.rb Loaded suite testcalc1 Started .. Finished in 0.000331 seconds. 2 tests, 2 assertions, 0 failures, 0 errors, 0 skips Test run options: --seed 28002 Que o resultado esperado quando todos os testes passam. Algumas explicaes do arquivo de teste: 130

1.12. UNIT TESTING A classe estendida de Test::Unit::TestCase, o que vai dedurar que queremos executar os testes contidos ali. Temos o mtodo setup, que o construtor do teste, e vai ser chamado para todos os testes, no somente uma vez. Temos o mtodo teardown, que o destrutor do teste, e vai liberar os recursos alocados atravs do setup. Temos as asseres, que esperam que o seu tipo combine com o primeiro argumento, executando o teste especicado no segundo argumento, usando o terceiro argumento como uma mensagem de ajuda se por acaso o teste der errado. Para demonstrar uma falha, faa o seu cdigo de subtrao car meio maluco, por exemplo, retornando o resultado mais 1, e rode os testes novamente: $ ruby testcalc1.rb Loaded suite testcalc1 Started .F Finished in 0.000516 seconds. 1) Failure: test_subtracao(TesteCalculadora) [testcalc1.rb:12]: 1-1=0. <0> expected but was <1>. 2 tests, 2 assertions, 1 failures, 0 errors, 0 skips Test run options: --seed 25317 Alm de assert_equal, temos vrias outras asseres: assert_nil assert_not_nil assert_not_equal assert_instance_of assert_kind_of assert_match assert_no_match assert_same assert_not_same Vamos incluir algumas outras no nosso arquivo:
1 2 3 4 5 6 7 8

require "test/unit" require_relative "calc" class TesteCalculadora < Test::Unit::TestCase def setup @calculadora = Calculadora.new end def test_objeto 131

CAPTULO 1. RUBY
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

assert_kind_of(Calculadora,@calculadora) assert_match(/^\d$/,@calculadora.soma(1,1).to_s) assert_respond_to(@calculadora,:soma) assert_same(@calculadora,@calculadora) end def test_objetos assert_operator(@calculadora.soma(1,1),:>,@calculadora.soma(1,0)) end def test_adicao assert_equal(2,@calculadora.soma(1,1),"1+1=2") end def test_subtracao assert_equal(0,@calculadora.subtrai(1,1),"1-1=0") end def teardown @calculadora = nil end end Listagem 1.282: Testes para classe da calculadora $ ruby testcalc2.rb Loaded suite testcalc2 Started .... Finished in 0.000412 seconds. 4 tests, 7 assertions, 0 failures, 0 errors, 0 skips Test run options: --seed 27163

132

1.13. CRIANDO GEMS

1.13

Criando Gems

Podemos criar gems facilmente, desde escrevendo os arquivos de congurao "na unha", at utilizando a gem bundle , que provavelmente j se encontra instalada no sistema. Vamos construir uma gem para "aportuguesar"os mtodos even? e odd?, traduzindo-os respectivamente para par? e impar?. Para criar a nova gem, chamada portnum, podemos digitar: $ bundle gem portnum Bundling your gems. This may take a few minutes on first run. create portnum/Gemfile create portnum/Rakefile create portnum/.gitignore create portnum/portnum.gemspec create portnum/lib/portnum.rb create portnum/lib/portnum/version.rb Initializating git repo in /home/aluno/gem/portnum Esse comando gera a seguinte estrutura de diretrio/arquivos, inclusive j dentro de um repositrio do Git: $ cd portnum/ $ ls -lah total 32K drwxr-xr-x 4 aluno drwxr-xr-x 3 aluno -rw-r--r-- 1 aluno drwxr-xr-x 7 aluno -rw-r--r-- 1 aluno drwxr-xr-x 3 aluno -rw-r--r-- 1 aluno -rw-r--r-- 1 aluno

aluno aluno aluno aluno aluno aluno aluno aluno

4,0K 4,0K 91 4,0K 33 4,0K 681 28

2011-07-14 2011-07-14 2011-07-14 2011-07-14 2011-07-14 2011-07-14 2011-07-14 2011-07-14

17:40 17:40 17:40 17:40 17:40 17:40 17:40 17:40

. .. Gemfile .git .gitignore lib portnum.gemspec Rakefile

O ponto-chave o arquivo portnum.gemspec:


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

# -*- encoding: utf-8 -*$:.push File.expand_path("../lib", __FILE__) require "portnum/version" Gem::Specification.new do |s| s.name = "portnum" s.version = Portnum::VERSION s.authors = ["TODO: Write your name"] s.email = ["TODO: Write your email address"] s.homepage = "" s.summary = %q{TODO: Write a gem summary} s.description = %q{TODO: Write a gem description} s.rubyforge_project = "portnum" s.files = s.test_files = s.executables = basename(f) } s.require_paths = end git ls-files.split("\n") git ls-files -- {test,spec,features}/*.split("\n") git ls-files -- bin/*.split("\n").map{ |f| File. ["lib"] Listagem 1.283: Gem spec 133

19 20

CAPTULO 1. RUBY Temos que preencher com os dados necessrios:


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

# -*- encoding: utf-8 -*$:.push File.expand_path("../lib", __FILE__) require "portnum/version" Gem::Specification.new do |s| s.name = "portnum" s.version = Portnum::VERSION s.authors = ["Eustaquio Rangel"] s.email = ["taq@bluefish.com.br"] s.homepage = "http://eustaquiorangel.com/portnum" s.summary = %q{Aportuguesamento de nmeros} s.description = %q{Adiciona os mtodos par? e impar? na classe Numeric} s.rubyforge_project = "portnum" s.files = s.test_files = s.executables = basename(f) } s.require_paths = end git ls-files.split("\n") git ls-files -- {test,spec,features}/*.split("\n") git ls-files -- bin/*.split("\n").map{ |f| File. ["lib"] Listagem 1.284: Gem spec Dentro do diretrio lib, se encontram os seguintes arquivos: $ ls -lah lib total 16K drwxr-xr-x 3 aluno drwxr-xr-x 4 aluno drwxr-xr-x 2 aluno -rw-r--r-- 1 aluno

19 20

aluno 4,0K 2011-07-14 17:40 . aluno 4,0K 2011-07-14 18:16 .. aluno 4,0K 2011-07-14 17:40 portnum aluno 73 2011-07-14 17:40 portnum.rb

$ ls -lah lib/portnum total 12K drwxr-xr-x 2 aluno aluno 4,0K 2011-07-14 17:40 . drwxr-xr-x 3 aluno aluno 4,0K 2011-07-14 17:40 .. -rw-r--r-- 1 aluno aluno 39 2011-07-14 17:40 version.rb Dentro do arquivo version.rb, temos: $ cat lib/portnum/version.rb module Portnum VERSION = "0.0.1" end Que vai denir o nmero de verso da nossa gem. Dentro do arquivo portnum.rb, temos: $ cat lib/portnum.rb require "portnum/version" module Portnum # Your code goes here... end Esse o cdigo que vai ser carregado quando a gem for requisitada. Vamos alterar a classe Numeric l, para implementar os nossos dois mtodos: 134

1.13. CRIANDO GEMS


1 2 3 4 5 6 7 8 9 10

require "portnum/version" class Numeric def par? self%2==0 end def impar? self%2==1 end end Listagem 1.285: Alterando Numeric na gem

1.13.1

Testando a gem

Antes de construir nossa gem, vamos criar alguns testes no diretrio test:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

require "test/unit" require "#{File.expand_path(File.dirname(__FILE__))}/../lib/portnum.rb" class PortNumTest < Test::Unit::TestCase def test_par assert_respond_to 1, :par? end def test_par_ok assert 2.par? assert !1.par? end def test_impar assert_respond_to 1, :impar? end def test_impar_ok assert 1.impar? assert !2.impar? end end Listagem 1.286: Testes da gem Rodando os testes: $ ruby test/portnumtest.rb Loaded suite test/portnumtest Started ..... Finished in 0.000473 seconds. 5 tests, 7 assertions, 0 failures, 0 errors, 0 skips Test run options: --seed 31305 Podemos criar uma task em nosso Rakefile para executar nossos testes:

1 2 3 4 5 6

require bundler/gem_tasks require rake require rake/testtask Rake::TestTask.new(:test) do |test| test.libs << lib << test 135

CAPTULO 1. RUBY
7 8

test.pattern = test/*.rb end Listagem 1.287: Task para testes

$ rake test Loaded suite /home/aluno/.rvm/gems/ruby-1.9.2-p180/gems/rake-0.9.2/lib/rake/rake_test_lo Started ..... Finished in 0.000596 seconds. 5 tests, 7 assertions, 0 failures, 0 errors, 0 skips Test run options: --seed 663

1.13.2

Construindo a gem

Agora que vericamos que tudo est ok, vamos construir a nossa gem: $ rake build portnum 0.0.1 built to pkg/portnum-0.0.1.gem aluno@aluno:~/gem/portnum 1.9.2p180 $ ls -lah pkg/ total 12K drwxr-xr-x 2 aluno aluno 4,0K 2011-07-14 19:42 . drwxr-xr-x 6 aluno aluno 4,0K 2011-07-14 19:42 .. -rw-r--r-- 1 aluno aluno 4,0K 2011-07-14 19:42 portnum-0.0.1.gem Olha l a nossa gem! Agora vamos instal-la: $ rake install portnum 0.0.1 built to pkg/portnum-0.0.1.gem portnum (0.0.1) installed Testando se deu certo: $ irb require "portnum" => true 1.par? => false 1.impar? => true

1.13.3

Publicando a gem

Podemos publicar a gem facilmente para o RubyGems.org. Primeiro temos que criar uma conta l, e indo em https://rubygems.org/prole/edit, salvar a nossa chave da API para um arquivo YAML em /.gem/credentials: A s usar o comando gem push: $ gem push portnum-0.0.1.gem Se quisermos fazer os seguintes passos: Executar o build; Criar uma tag no git e fazer um push para o repositrio de cdigo; Publicar a gem no RubyGems.org; 136

1.13. CRIANDO GEMS

podemos utilizar: $ rake release portnum 0.0.1 built to pkg/portnum-0.0.1.gem Tagged v0.0.1 ... Para ver todas as tasks que o Rake suporta: $ rake -T rake build rake install rake release rake test

# # # #

Build portnum-0.0.1.gem into the pkg directory Build and install portnum-0.0.1.gem into system gems Create tag v0.0.1 and build and push portnum-0.0.1.gem to R... Run tests

1.13.4

Extraindo uma gem

Podemos extrair o cdigo (com toda a estrutura de diretrios) contido em uma gem utilizando o prprio comando gem: $ gem unpack portnum-0.0.1.gem Ou, no caso de no tem as gems instaladas, utilizando a ferramenta GNU tar : $ tar xvf portnum-0.0.1.gem data.tar.gz $ tar tvf data.tar.gz

137

CAPTULO 1. RUBY

1.14

Gerando documentao

Vamos ver como podemos documentar o nosso cdigo utilizando o rdoc, que uma aplicao que gera documentao para um ou vrios arquivos com cdigo fonte em Ruby, interpretando o cdigo e extraindo as denies de classes, mdulos e mtodos. Vamos fazer um arquivo com um pouco de cdigo, usando nossos exemplos de carros:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

# Essa a classe base para todos os carros que vamos # criar no nosso programa. A partir dela criamos carros # de marcas especficas. # # Autor:: Eustquio TaQ Rangel # Licena:: GPL class Carro attr_reader :marca, :modelo, :tanque attr_accessor :cor # Parmetros obrigatrios para criar o carro # No se esquea de que todo carro vai ter os custos de: # * IPVA # * Seguro obrigatrio # * Seguro # * Manuteno def initialize(marca,modelo,cor,tanque) @marca = marca @modelo = modelo @cor = cor @tanque = tanque end # Converte o carro em uma representao mais legvel def to_s "Marca:#{@marca} Modelo:#{@modelo} Cor:#{@cor} Tanque:#{@tanque}" end end Listagem 1.288: Classe de Carro para rdoc Agora vamos rodar o rdoc nesse arquivo: $ rdoc carro.rb Parsing sources... 100% [ 1/ 1] carro.rb Generating Darkfish format into doc... Files: Classes: Modules: Constants: Attributes: Methods: 1 1 0 0 4 2 (0 (0 (0 (4 (0 undocumented) undocumented) undocumented) undocumented) undocumented)

Total: 7 (4 undocumented) 42.86% documented Elapsed: 0.1s 138

1.14. GERANDO DOCUMENTAO Isso vai produzir um diretrio chamado doc abaixo do diretrio atual, que vai conter um arquivo index.html com um contedo como esse:

Clicando na classe Carro, vamos ter algo como:

Pudemos ver algumas convenes para escrever a documentao. Os comentrios so utilizados como as descries das classes, mdulos ou mtodos. Texto do tipo labeled lists, que so listas com o suas descries alinhadas, como no caso do autor e da licena do exemplo, so criados utilizando o valor e logo em seguida dois doispontos (::), seguido da descrio. Listas de bullets so criadas usando asterisco (*) ou hfen no comeo da linha. Para listas ordenadas, temos que usar o nmero do item da lista seguido por um ponto (.). 139

CAPTULO 1. RUBY Dica: Um detalhe muito importante que se precisarmos gerar a documentao novamente sem alterar os fontes, devemos apagar o diretrio onde ela foi gerada antes de rodar o rdoc novamente. Podemos reparar que, se clicarmos no nome de algum mtodo, o cdigo-fonte desse mtodo mostrado logo abaixo, como em:

Vamos inserir alguns cabealhos na nossa documentao. Cabealhos so gerados usando = para determinar o nvel do cabealho, como: = Primeiro nvel == Segundo nvel Linhas podem ser inseridas usando trs ou mais hifens. Negrito pode ser criado usando asteriscos (*) em volta do texto, como em *negrito*, itlico com sublinhados (_) em volta do texto e em fonte de tamanho xo entre sinais de mais (+). Nomes de classes, arquivos de cdigo fonte, e mtodos tem links criados do texto dos comentrios para a sua descrio. Hyperlinks comeando com http:, mailto:, ftp: e www so automaticamente convertidos. Tambm podemos usar o formato texto[url]. O processamento dos comentrios podem ser interrompido utilizando e retornado utilizando ++. Isso muito til para comentrios que no devem aparecer na documentao. Vamos ver nosso exemplo incrementado com todas essas opes e mais um arquivo novo, uma classe lha de Carro chamada Fusca, separando os dois arquivos em um diretrio para no misturar com o restante do nosso cdigo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

# = Classe # Essa a classe base para *todos* os carros que vamos # criar no nosso programa. A partir dela criamos carros # de _marcas_ especficas. Verique o mtodo to_s dessa # classe Carro para uma descrio mais legvel. # --# # == Sobre o autor e licena # # Autor:: Eustquio TaQ Rangel # Website:: http://eustaquiorangel.com # Email:: mailto:naoteconto@eustaquiorangel.com # Licena:: +GPL+ Clique aqui para ver mais[http://www.fsf.org] #-# Ei, ningum deve ler isso. #++ # Obrigado pela preferncia. 140

1.14. GERANDO DOCUMENTAO


18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

class Carro attr_reader :marca, :modelo, :tanque attr_accessor :cor # Parmetros obrigatrios para criar o carro # No se esquea de que todo carro vai ter os custos de: # * IPVA # * Seguro obrigatrio # * Seguro # * Manuteno def initialize(marca,modelo,cor,tanque) @marca = marca @modelo = modelo @cor = cor @tanque = tanque end # Converte o carro em uma representao mais legvel def to_s "Marca:#{@marca} Modelo:#{@modelo} Cor:#{@cor} Tanque:#{@tanque}" end end Listagem 1.289: Classe Carro para rdoc

1 2 3 4 5 6

# Classe de um _vokinho_, derivada da classe Carro. class Fusca < Carro def ipva false end end Listagem 1.290: Classe Fusca para rdoc Rodando o rdoc (prestem ateno que agora no especico o arquivo): $ rdoc Parsing sources... 100% [ 2/ 2] fusca.rb Generating Darkfish format into /home/taq/git/curso-ruby-rails/code/rdoc/doc... Files: Classes: Modules: Constants: Attributes: Methods: 2 2 0 0 4 3 (0 (0 (0 (4 (1 undocumented) undocumented) undocumented) undocumented) undocumented)

Total: 9 (5 undocumented) 44.44% documented Elapsed: 0.1s Vamos ter um resultado como esse:

141

CAPTULO 1. RUBY

142

ndice Remissivo
+, 11 , 11, 17, 51 <=>, 35, 70 ==, 26 ===, 26 ActiveRecord, 121 adicionando elementos, 17 alias_method, 50 aliases, 50 all, 120 all?, 34 ancestors, 51 any?, 34 argumentos nomeados, 43 arity, 40 Array, 15, 32, 40, 95, 102 ASCII, 11 assertions assert_equal, 131 assert_instance_of, 131 assert_kind_of, 131 assert_match, 131 assert_nil, 131 assert_no_match, 131 assert_not_equal, 131 assert_not_nil, 131 assert_not_same, 131 assert_same, 131 atribuio em paralelo, 29 atributos attr_accessor, 48 attr_reader, 47 attr_writer, 64 virtuais, 49 autodestrutivo, 54 backtrace, 23 bash_prole, 2 BasicObject, 52 benchmarks, 93 Bignum, 8 binrio, 21 blank slate, 52 block_given?, 41 blocos, 16, 20, 32, 78 boolean, 9 Bundler bundle, 133 C, 124 denindo mtodo, 125 denindo variveis de instncia, 125 call, 31 case, 25 class, 6, 45 classes, 45 abertas, 49 closures, 30, 67, 86 comandos preparados, 121 combination, 37 concatenao, 11 construtor, 45 continuations, 89 corrotinas, 86 CTRL+C, 84 curry, 31 curto circuito, 29 deep copy, 59, 60 def, 39 destrutivos, 42 destrutor, 45 detect?, 34 Dir, 97 dirname, 97 downto, 38 Duck typing, 17 dup, 12, 59, 60 each, 16, 32, 97 eigenclass, 52, 70 encodings, 11 ensure, 22 Enumerable, 16, 32, 71 Enumerator, 85 escopo, 64 estruturas de controle, 23 case, 25 if, 23 elsif, 24 unless, 24 even, 33 excees, 21, 42 143

NDICE REMISSIVO expresses regulares, 13, 14, 26 extconf.rb, 124 extend, 68 Fibers, 85 Fibonacci, 74, 86 FileUtils, 97 nalizer, 45 rst, 17 Fixnum, 5, 6, 21, 26 Float, 8 for, 27 freeze, 12 FTP, 106, 108 garbage collector, 13, 45, 128 gems, 2, 73 ghost methods, 62 git, 133 glob, 97 GNU, 137 grupos, 14 grupos nomeados, 14 handle, 95 Hash, 18, 33, 35, 43, 77, 102 herana, 57 heredoc, 10 hexadecimal, 21 highline, 106 hook, 59, 69 HTTP, 108 HTTPS, 108 immediate values, 6, 9 include, 68, 74 include?, 26 included, 69 initialize, 45 initialize_copy, 59 inject, 35 inspect, 46 inteiro, 21 IRB, 6 irb, 26 iteradores, 17, 32, 41 Java, 5, 75, 113, 115 join, 78 JRuby, 115 kind_of, 17 lambda, 29, 30, 67 last, 17 lendo senhas no terminal, 106 load, 74 144 loops, 26, 27 mtodos, 39 lookup, 69 method, 43 method_added, 62 method_missing, 61, 62 method_removed, 62 pblicos, 8 privados, 65 protegido, 64 protegidos, 65 mdulos, 16 make, 124 map, 34, 38 mark and sweep, 128 marshal, 59, 60 max, 35 memoization, 74, 75 memoize, 73 metaclasses, 51 metaprogramao, 52 min, 35 mixin, 68, 80 mkmf, 124 mkpath, 97 Monitor, 80 Mutex, 84 namespaces, 72 new, 19 next, 27 nil, 9 nil?, 9 nokogiri, 100, 108 now, 19 nulo, 9 Numeric, 134 Object, 52 object_id, 6, 9, 10, 13 objetos, 45 OpenStruct, 48, 61 OpenURI, 108 operador lgico, 28 rocket, 20 ORM, 121 pblico, 65 pack, 104 permutation, 37 PHP, 112 ponto utuante, 8 POP3, 107 Proc, 29, 41, 45, 67, 78, 86 product, 38

NDICE REMISSIVO push, 17 puts, 46 Python, 112 Queues, 83 Rails, 20, 33, 102 Rakele, 135 Range, 18, 25, 26, 35 rb_str_new, 126 rb_str_new2, 126 rdoc, 138 redo, 28 reject, 33 require, 74 rescue, 21, 22 respond_to?, 17 resume, 86, 88 retry, 22 return, 27, 39, 86 root, 73 RubyGems, 73 RVM, 2, 73, 115 smbolos, 13 select, 33, 38 self, 46 send, 53 Sequel, 118 setup, 131 shallow copy, 59, 60 signal, 81 singleton, 52, 70, 72 slice, 10 SMTP, 103 sockets, 103 sort_by, 36 splat, 40 SSH, 109 stabby, 31 StandardError, 21 step, 38 String, 5, 9, 10 Struct, 48 substrings, 10 sudo, 73 super, 57 superclasse, 52 Symbol, 13 take, 16 tap, 38 tar, 137 TCP, 103 teardown, 131 teste negativo, 24 threads, 78, 84 145 green, 83 native, 83 Time, 19 Time.now, 19 timeout, 78 tipagem dinmica, 5 esttica, 5 forte, 5 fraca, 6 to_i, 46 to_int, 46 to_proc, 43 to_s, 16, 46 to_str, 46 UDP, 105 unpack, 104 until, 28 upto, 38 UTF-8, 11 variveis, 12 de classe, 54 de instncia de classe, 56 privadas, 47 wait, 81 WEBrick, 109 while, 26, 27 XML, 98 XML-RPC, 110 YAML, 101, 136 yield, 41, 86 zip, 37, 96

You might also like