Nenhuma parte desta publicação pode ser reproduzida, armazenada em bancos de dados ou transmitida sob qualquer forma ou meio, seja eletrônico, eletrostático, mecânico, por fotocópia, gravação, mídia magnética ou algum outro modo, sem permissão por escrito do detentor do copyright.
O conteúdo que você tem agora nas mãos é a evolução do meu conhecido “Tutorial de Ruby”, lançado em Janeiro de 2005, que se transformou em 2006 no primeiro livro de Ruby do Brasil, “Ruby - Conhecendo a Linguagem”, da Editora Brasport, cujas cópias se esgotaram e, como não vai ser reimpresso, resolvi atualizar e lançar material nos formatos de ebook que agora você tem em mãos.
Quando comecei a divulgar Ruby aqui no Brasil, seja pela internet, seja por palestras em várias cidades, eram poucas pessoas que divulgavam e a linguagem era bem desconhecida, e mesmo hoje, vários anos após ela pegar tração principalmente liderada pela popularidade do framework Rails, que sem sombra de dúvidas foi o principal catalizador da linguagem, ainda continua desconhecida de grande parte das pessoas envolvidas ou começando com desenvolvimento de sistemas, especialmente a molecada que está começando a estudar agora em faculdades.
Como eu sou mais teimoso que uma mula, ainda continuo promovendo a linguagem por aí, disponibilizando esse material para estudos, não por causa do tutorial, do livro ou coisa do tipo, mas porque ainda considero a linguagem muito boa, ainda mais com toda a evolução que houve em todos esses anos, em que saímos de uma performance mais sofrível (mas, mesmo assim, utilizável) nas versões 1.8.x até os avanços das versões 1.9.x e agora, as 2.x.
Espero que o material que você tem em mãos sirva para instigar você a conhecer mais sobre a linguagem (aqui não tem nem de longe tudo o que ela disponibiliza) e a conhecer as ferramentas feitas com ela. É uma leitura direta e descontraída, bem direto ao ponto. Em alguns momentos eu forneço alguns “ganchos” para alguma coisa mais avançada do que o escopo atual, e até mostro algumas, mas no geral, espero que seja conteúdo de fácil digestão.
Durante o livro, faço alguns “desafios”, que tem a sua resposta no final do livro. Tentem fazer sem colar! :-)
Esse material também serve como base para os treinamentos de Ruby on Rails que ministramos na minha empresa, a Bluefish. Se precisar de cursos de Ruby on Rails, seja na nossa empresa, seja in-company, ou precisar de consultoria em projetos que utilizam essa ferramenta, entre em contato conosco através de contato@bluefish.com.br.
“Ruby é uma linguagem de programação interpretada multiparadigma, de tipagem dinâmica e forte, com gerenciamento de memória automático, originalmente planejada e desenvolvida no Japão 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 programação funcional, orientada a objetos, imperativa e reﬂexiva.
Foi inspirada principalmente por Python, Perl, Smalltalk, Eiffel, Ada e Lisp, sendo muito similar em vários aspectos a Python.
A implementação padrão é escrita em C, como uma linguagem de programação de único passe. Não há qualquer especiﬁcação da linguagem, assim a implementação original é considerada de fato uma referência. Atualmente, há várias implementações alternativas da linguagem, incluindo YARV, JRuby, Rubinius, IronRuby, MacRuby e HotRuby, cada qual com uma abordagem diferente, com IronRuby, JRuby e MacRuby fornecendo compilação Just-In-Time e, JRuby e MacRuby também fornecendo compilação Ahead-Of-Time.
A série 1.9 usa YARV (Yet Another Ruby VirtualMachine), como também a 2.0, substituindo a mais lenta Ruby MRI (Matz’s Ruby Interpreter).”
A instalação pode ser feita de várias maneiras, em diferentes sistemas operacionais, desde pacotes especíﬁcos para o sistema operacional, scripts de conﬁguração ou através do download, compilação e instalação do código-fonte. Abaixo vão algumas dicas, mas não execute nenhuma delas pois vamos fazer a instalação de uma maneira diferente e mais moderna e prática.
E até no Windows tem um instalador automático. Mais detalhes para esse tipo de instalação podem ser conferidas no site oficial da linguagem. Particularmente eu não recomendo utilizar a linguagem no Windows, aí vai de cada um, mas já aviso que vão arrumar sarna para se coçarem.
Vamos instalar Ruby utilizando a RVM - Ruby Version Manager, que é uma ferramenta de linha de comando que nos permite instalar, gerenciar e trabalhar com múltiplos ambientes Ruby, de interpretadores até conjunto de gems. Como alternativa ao RVM, temos também a rbenv. Vamos utilizar a RVM, mas se mais tarde vocês quiserem investigar a rbenv, fiquem à vontade pois o comportamento é similar.
A instalação da RVM é feita em ambientes que tem o shell bash (por isso ela não está disponível para Windows, nesse caso, verifique a ferramenta pik), sendo necessário apenas abrir um terminal rodando esse shell e executar:
Após a instalação, dependendo da versão da RVM que foi instalada, temos que inserir o comando rvm no path, adicionando no ﬁnal do arquivo ~/.bashrc (ou, dependendo da sua distribuição Linux, no ~/.bash_profile):
Talvez na versão corrente, isso não seja mais necessário. Mas para confirmar se é necessário ou se a RVM já se encontra corretamente configurada e instalada, podemos executar os seguintes comandos:
E dependendo da versão da RVM instalada, devemos veriﬁcar quais são as notas para o ambiente que estamos instalando a RVM, que no caso do Ubuntu vai retornar:
No caso do Ubuntu e da versão retornar esse tipo de informação, devemos executar a seguinte linha recomendada, em um terminal:
Desse modo, satisfazemos todas as ferramentas necessárias para utilizar a RVM. Apesar dela citar nas instruções o aptitude, podemos usar sem problemas o apt-get.
vão ser instaladas as dependências necessárias, talvez requisitando acesso à mais permissões utilizando o sudo.
Após instalar a RVM e suas dependências, agora é hora de instalarmos um interpretador Ruby. Vamos utilizar a versão mais atual. Para verificar qual é, visitem a página de downloads da linguagem e verifiquem qual é a versão estável, ou executem esse comando no terminal (levando em conta que já esteja instalado o utilitário curl, que é ferramenta essencial hoje em dia):
Ali vimos que a última versão reconhecida (no momento em que estou escrevendo esse texto) é a 2.3.1.
Ali acima eu utilizei alguns recursos de shell scripting para automatizar o processo de recuperar a versão mais recente estável da linguagem. Aprender shell scripting é uma coisa muito interessante hoje em dia pois as ferramentas disponíveis em um terminal (que ainda assusta muita gente adepta do “next-next-finish”) são imensamente poderosas e podem economizar muito tempo gasto fazendo de outra maneira.
Levando em conta que <versão> é a última versão que encontramos acima, podemos executar no terminal:
Após instalado, temos que ativar a versão na RVM e veriﬁcar se ficou ok, digitando o comando rmv seguido do número da versão para ativá-la, o que pode ser conferido logo depois com o comando ruby -v:
Uma coisa que enche o saco é ficar toda santa hora indicando qual a versão que queremos rodar. Para evitar isso, vamos deixar a versão instalada como a padrão do sistema:
Outra opção para gerenciar qual versão está ativa, é criar um arquivo chamado .ruby-version, com o número da versão que queremos ativar:
Importante notar que essa versão vai ser ativada somente quando navegarmos para o diretório onde o arquivo se encontra. Ou seja, toda vez que utilizamos, por exemplo, o comando cd para irmos para o diretório onde o arquivo se encontra, a versão especificada vai ser ativada.
A partir da versão 2.1, começou a ser empregado o versionamento semântico, onde é adotado o esquema de MAIOR.MENOR.AJUSTE no número da versão, sendo:
Convenhamos, não é toda linguagem por aí que permite instalar várias versões e vários interpretadores da linguagem e permitem alterar entre elas de maneira prática. Algumas são tão complicadas em fazer isso que parecem CMB (Coisa da Maria Barbuda). Usando uma ferramenta como a RVM (e similares) permitem uma facilidade em lidar com seus projetos tanto de versões legadas e muito antigas como projetos de ponta utilizando as versões mais recentes e talvez ainda nem liberadas como estáveis.
Vamos conhecer agora alguns dos recursos, características, tipos e estruturas básicas da linguagem. Eu sempre cito em palestras e treinamentos uma frase do Alan Perlis, que é:
O que vamos ver (pelo menos é a minha intenção) é o que Ruby tem de diferente para valer a pena ser estudada. Não vamos ver só como os if's e while's são diferentes, mas sim meios de fazer determinadas coisas em que você vai se perguntar, no final, “por que a minha linguagem preferida X não faz isso dessa maneira?”.
Tipagem dinâmica é uma característica de determinadas linguagens de programação, que não exigem declarações de tipos de dados, pois são capazes de escolher que tipo utilizar dinamicamente para cada variável, podendo alterá-lo durante a compilação ou a execução do programa.
Algumas das linguagens mais conhecidas a utilizarem tipagem dinâmica são: Python, Ruby, PHP e Lisp. A tipagem dinâmica contrasta com a tipagem estática, que exige a declaração de quais dados poderão ser associados a cada variável antes de sua utilização. Na prática, isso significa que:
Pudemos ver que a variável 1 v pode assumir como valor tanto uma String como um número (que nesse caso, é um Fixnum - mais sobre classes mais adiante), ao passo que, em uma linguagem de tipagem estática, como Java, isso não seria possível, com o compilador já não nos deixando prosseguir:
Existe alguma discussão que linguagens com tipagem estática oferecem mais “segurança”, para o programador, pois, como no caso acima, o compilador executa uma crítica no código e nos aponta se existe algum erro. Particularmente, eu acredito que hoje em dia é delegada muita coisa desse tipo para os compiladores e ferramentas do tipo, removendo um pouco de preocupação do programador, sendo que também não adianta checar tipos se no final o comportamento do seu código pode não corresponder com o que é necessário dele, que é o cerne da questão. Nesse caso, prefiro utilizar metodologias como Test Driven Development, que vamos dar uma breve olhada mais para o final do livro, para garantir que o software esteja de acordo com o que esperamos dele.
Também existe a questão de performance, que o código compilado é muito mais rápido que o interpretado. Ruby melhorou muito na questão de performance nas versões 1.9 e 2.x, não a ponto de bater código compilado e linkado para a plataforma em que roda, mas hoje em dia a não ser que o seu aplicativo exija muita performance, isso não é mais um problema. Inclusive, podemos até rodar código Ruby na VM do Java, como veremos mais adiante com o uso de JRuby, e utilizar algumas técnicas que vão deixar Ruby dezenas de vezes mais rápida que Java, onde a implementação da mesma técnica seria dezenas de vezes mais complicada.
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 variável ao qual este valor será atribuído.
Não temos primitivos em Ruby, somente abstratos, onde todos exibem comportamento de objetos. Temos números inteiros e de ponto flutuante, onde podemos dividir os inteiros em Fixnums e Bignums, que são diferentes somente pelo tamanho do número, sendo convertidos automaticamente. A partir da versão 2.4 de Ruby, os inteiros foram convertidos em Integer, facilitando em um nível mais alto e mantendo o comportamento anterior em um nível mais baixo, se necessário identificar qual o tipo exato. Vamos ver alguns deles agora.
Para ver como é a implementação até antes das versões 2.4.x da linguagem, tudo o que estiver relacionado com Fixnums e Bignums nessa seção vai estar sendo executado em versões anteriores da 2.4.x. Se você estiver utilizando a 2.4.x ou maior (sendo que a 2.4 tem a data de liberação como 25/12/2016, sempre temos uma versão nova no Natal, ho-ho-ho), pode abstrair ambos os tipos como Integers.
Os Fixnums são números inteiros de 31 bits de comprimento (ou 1 word do processador menos 1 bit), usando 1 bit para armazenar o sinal e 1 bit para indicar que a referência corrente é um Fixnum (mais sobre isso logo abaixo, mencionando immediate values), resultando em um valor máximo de armazenamento, para máquinas de 32 bits, de 30 bits, ou seja:
Podemos descobrir o tipo de objeto que uma variável aponta utilizando o método class, como no exemplo acima.
Quando for encontrado um cifrão ($), significa que o código deve ser rodado no terminal do sistema operacional, como no exemplo:
Os Fixnums tem características interessantes que ajudam na sua manipulação mais rápida pela linguagem, que os definem como immediate values, que são tipos de dados apontados por variáveis que armazenam seus valores na própria referência e não em um objeto que teve memória alocada para ser utilizado, agilizando bastante o seu uso. Para verificar isso vamos utilizar o método object_id.
Também podemos notar que esse comportamento é sólido verificando que o object_id de várias variáveis apontando para um mesmo valor continua sendo o mesmo:
Os Fixnums, como immediate values, também tem também uma característica que permite identificá-los entre os outros objetos rapidamente através de uma operação de and lógico, onde é verificado justamente o último bit, que é um dos 2 utilizados para controle, como explicado anteriormente:
Isso nos mostra um comportamento interessante: qualquer variável que aponta para um objeto ou algo como um Fixnum ou immediate value, que apesar de carregar o seu próprio valor e ser bem light weight, ainda mantém características onde podem ter acessados os seus métodos como qualquer outro tipo na linguagem. Olhem, por exemplo, o número 1 (e qualquer outro número):
No exemplo acima, estamos vendo os métodos públicos de acesso de um Fixnum. Mais sobre métodos mais tarde!
Como vimos acima, os Fixnums tem limites nos seus valores, dependendo da plataforma. Os Bignums são os números inteiros que excedem o limite imposto pelos Fixnums, ou seja, em um computador de 32 bits:
Uma coisa muito importante nesse caso, é que os Bignums alocam memória, diferentemente dos Fixnums e outros tipos que são immediate values!
Podemos ver isso criando algumas variáveis apontando para o mesmo valor de Bignum e vendo que cada uma tem um object_id diferente:
Como vimos anteriormente, somente os Fixnums que são immediate values, então isso nos permite diferenciar dois Integers a partir do and lógico do object_id (agora rodando em uma máquina de 64 bits):
Como podemos ver, somente o primeiro que retornou 1, indicando que a representação interna do Integer é um Fixnum.
Sempre prefiram um computador com 64 bits. Agora que sabemos como funciona as internas da linguagem, temos noção que são criados bem menos Bignums quando a word do processador é maior. Tudo bem que alguns valores desses só apareceriam se a sua aplicação estivesse analisando alguns números da “Operação Lava-Jato” e algumas outras parecidas, mas vocês entenderam a idéia.
Mando um recado lá pro meu irmão: Se tiver usando droga, tá ruim na minha mão. Ele ainda tá com aquela mina. Pode crer, moleque é gente fina.
Temos mais dois immediate values que são os booleanos, os tradicionais true e false, indicando como object_ids, respectivamente, 2 e 0:
O tipo nulo em Ruby é definido como nil. Ele também é um immediate value, com o valor fixo de 4 no seu object_id:
Strings são 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 céu é ‘azul’” ou ‘o céu é “azul”’ e “escapar” utilizando o caracter :
Também podemos criar Strings longas, com várias linhas, usando o conceito de heredoc, onde indicamos um terminador logo após o sinal de atribuição (igual) e dois sinais de menor (<<):
O terminador tem que vir logo no começo da linha onde termina a String, e ser o mesmo indicado no começo. Nesse exemplo, foi utilizado o terminador FIM. Utilizar heredocs evita que façamos muitas linhas cheias de Strings uma concatenando com a outra.
Para cada String criada, vamos ter espaço alocado na memória, tendo um object_id distinto para cada uma:
São partes de uma String (antes eu havia escrito “pedaços” de uma String, mas ficou um lance muito Tokyo Ghoul/Hannibal Lecter, então achei “partes” mais bonito). Para pegar algumas substrings, podemos tratar a String como um Array:
Referenciando um caracter da String, temos algumas diferenças entre as versões 1.8.x e 1.9.x (ou maiores) do Ruby:
Para concatenar Strings, podemos utilizar os métodos (sim, métodos, vocês não imaginam as bruxarias que dá para fazer com métodos em Ruby, como veremos adiante!) + ou <<:
A diferença é que + nos retorna um novo objeto, enquanto << faz uma realocação de memória e trabalha no objeto onde o conteúdo está sendo adicionado, como demonstrado acima, sem gerar um novo objeto.
A partir da versão 1.9 temos suporte para encodings diferentes para as Strings em Ruby. Nas versões 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 definir até nomes de métodos!
Para utilizarmos explicitamente um encoding em um arquivo de código-fonte Ruby, temos que especificar o encoding logo na primeira linha do arquivo, utilizando, por exemplo, com UTF-8:
A partir da versão 2.x, esse comentário mágico (magic comment, como é chamado) não é mais necessário se o encoding for UTF-8
Temos agora o método b que converte uma String para a sua representação “binária”, ou seja, em ASCII:
Em Ruby, os valores são transmitidos por referência, podendo verificar isso com Strings, constatando que as variáveis realmente armazenam referências na memória. Vamos notar que, se criarmos uma variável apontando para uma String, criamos outra apontando para a primeira (ou seja, para o mesmo local na memória) e se alterarmos a primeira, comportamento semelhante é notado na segunda variável:
Não temos um método unfreeze, mas podemos gerar uma cópia do nosso objeto “congelado” com dup, e assim fazer modificações nessa nova cópia:
Importante notar que é criada uma tabela de Strings congeladas, assim toda String congelada vai ser o mesmo objeto:
Alguns métodos acima, como sub, gsub e scan aceitam expressões regulares (vamos falar delas daqui a pouco) e permitem fazer algumas substituições como essa:
Símbolos, antes de mais nada, são instâncias da classe Symbol. Podemos pensar em um símbolo como uma marca, um nome, onde o que importa não é o que contém a sua instância, mas o seu nome.
Símbolos podem se parecer com um jeito engraçado de Strings, mas devemos pensar em símbolos como significado e não como conteúdo. Quando escrevemos “azul”, podemos pensar como um conjunto de letras, mas quando escrevemos :azul, podemos pensar em uma marca, uma referência para alguma coisa.
Como pudemos ver, as duas referências para os símbolos compartilham o mesmo objeto, enquanto que foram alocados dois objetos para as Strings. Uma boa economia de memória com apenas uma ressalva: símbolos não são objetos candidatos a limpeza automática pelo garbage collector, ou seja, se você alocar muitos, mas muitos símbolos no seu sistema, você poderá experimentar um nada agradável esgotamento de memória que com certeza não vai trazer coisas boas para a sua aplicação, ao contrário de Strings, que são alocadas mas liberadas quando não estão sendo mais utilizadas.
Outra vantagem de símbolos é a sua comparação. Para comparar o conteúdo de duas Strings, temos que percorrer os caracteres um a um e com símbolos podemos comparar os seus object_ids que sempre serão os mesmos, ou seja, uma comparação O(1) (onde o tempo para completar é sempre constante e o mesmo e não depende do tamanho da entrada).
Outra coisa muito útil em Ruby é o suporte para expressões regulares (regexps). Elas podem ser facilmente criadas das seguintes maneiras:
Para fazermos testes com as expressões regulares, podemos utilizar os operadores =~ (“igual o tiozinho quem vos escreve”) que indica se a expressão “casou” e !~ que indica se a expressão não “casou”, por exemplo:
No caso das expressões que “casaram”, foi retornada a posição da String onde houve correspondência. Também podemos utilizar, a partir da versão 2.4, o método match?:
O detalhe é que o método match?, que retorna somente um boolean indicando se a expressão “casou” ou não (diferente do =~ que retorna onde casou) é até 3 vezes mais rápido.
Podemos fazer truques bem legais com expressões regulares e Strings, como por exemplo, dividir a nossa String através de uma expressão regular, encontrando todas as palavras que começam com r:
Fica uma dica que podemos utilizar alguns modificadores no final da expressão regular, no caso acima, o /i indica que a expressão não será case sensitive, ou seja, levará em conta caracteres em maiúsculo ou minúsculo.
Outra dica interessante é o construtor %r{}, mostrado acima. Quando temos barras para “escapar” dentro da expressão regular, ele nos permite economizar alguns caracteres, como nesse exemplo:
Reparem que eu usei os marcadores de início de linha \A e de fim de linha \z. Geralmente costumamos utilizar ^ e $, mas tem um pequeno grande problema: esses marcadores não levam em conta a quebra de linha (\n) dentro da String:
Podemos utilizar grupos nas expressões regulares, utilizando ( e ) para delimitar o grupo, e $<número> para verificar onde o grupo “casou”:
Também podemos utilizar \<número> para fazer alguma operação com os resultados da expressão regular assim:
Onigmo vai ser o novo engine de expressões regulares da versão 2.0. Ela parece ser bem baseada em Perl e aqui tem vários recursos que podem estar presentes nesse engine. Como exemplo de novidades, podemos utilizar esse aqui:
Ali é declarado o seguinte: (?(cond)yes|no) (reparem no primeiro ? e em |, que funcionam como o operador ternário ? e :), onde se cond for atendida, é avaliado yes, senão, no, por isso que foo, iniciando e terminando com caracteres minúsculos, casa com no, foO com maiúsculo no final não casa com nada e Foo casa com yes.
A partir da versão 1.9, podemos usar grupos nomeados em nossas expressões regulares, como por exemplo:
A partir da versão 2.4, temos o método named_captures que nos retorna uma Hash com os valores que foram capturados:
E se precisarmos utilizar caracteres com acento nas expressões? Por exemplo, eu juro que o meu nome está correto, mas:
Arrays podemos definir como objetos que contém coleções de referências para outros objetos. Vamos ver um Array simples com números:
Em Ruby os Arrays podem conter tipos de dados diferentes também, como esse onde misturamos inteiros, flutuantes e Strings:
mas temos um atalho que nos permite economizar digitação com as aspas, que é o %w e pode ser utilizado da seguinte maneira:
Podemos também criar Arrays com tamanho inicial pré-definido utilizando o tamanho na criação do objeto:
Vamos verificar um efeito interessante, criando um Array com tamanho de 5 e algumas Strings como o valor de preenchimento:
Foi aplicado um método destrutivo (que alteram o próprio objeto da referência, não retornando uma cópia dele no primeiro elemento do Array, que alterou todos os outros elementos, pois são o mesmo objeto. Para evitarmos isso, podemos utilizar um bloco (daqui a pouco mais sobre blocos!) para criar o Array:
Aqui temos nosso primeiro uso para blocos de código, onde o bloco foi passado para o construtor do Array, que cria elementos até o número que especificamos transmitindo o valor do índice (ou seja, 0, 1, 2, 3 e 4) para o bloco.
Os Arrays tem uma característica interessante que vários outros objetos de Ruby tem: eles são iteradores, ou seja, objetos que permitem percorrer uma coleção de valores, pois incluem o módulo (hein? mais adiante falaremos sobre módulos!) Enumerable, que inclui essa facilidade.
Como parâmetro para o método que vai percorrer a coleção, vamos passar um bloco de código e vamos ver na prática como que funciona isso. Dos métodos mais comuns para percorrer uma coleção, temos each, que significa “cada”, e que pode ser lido “para cada elemento da coleção do meu objeto, execute esse bloco de código”, dessa maneira:
Ou seja, para cada elemento do Array, foi executado o bloco - atenção aqui - passando o elemento corrente como parâmetro, recebido pelo bloco pela sintaxe |<parâmetro>| (o caracter | é o pipe no Linux). Podemos ver que as instruções do nosso bloco, que no caso só tem uma linha (e foi usada a convenção de { e }), foram executadas com o valor recebido como parâmetro.
Temos um atalho em Ruby que nos permite economizar conversões dentro de Strings. Ao invés de usarmos to_s como mostrado acima, podemos utilizar o que é conhecido como interpolador de expressão com a sintaxe #{objeto}, onde tudo dentro das chaves vai ser transformado em String acessando o seu método to_s. Ou seja, poderíamos ter escrito o código do bloco como:
Reparem no pequeno truque de usar -1 para pegar o último elemento, o que pode ficar bem mais claro utilizando o método 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.
Pudemos ver que o operador/método << 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, então provavelmente é um pato”.
Isso nos diz que, ao contrário de linguagens de tipagem estática, onde o tipo do objeto é verificado em tempo de compilação, em Ruby nos interessa se um objeto é capaz de exibir algum comportamento esperado, não o tipo dele.
Se você quer fazer uma omelete, não importa que animal que está botando o ovo (galinha, pata, avestruz, Tiranossauro Rex, etc), desde que você tenha um jeito/método para botar o ovo.
“Ei, mas como vou saber se um determinado objeto tem um determinado método?” Isso é fácil de verificar utilizando o método respond_to?:
“Ei, mas eu realmente preciso saber se o objeto em questão é do tipo que eu quero. O método << é suportado por Arrays, Strings, Fixnums mas tem comportamento diferente nesses últimos!”. Nesse caso, você pode verificar o tipo do objeto utilizando kind_of?:
Ranges são intervalos que podemos definir incluindo ou não o último valor referenciado. Vamos exemplificar isso com o uso de iteradores, dessa maneira:
Como pudemos ver, as Ranges são declaradas com um valor inicial e um valor final, separadas por dois ou três pontos, que definem se o valor final vai constar ou não no intervalo.
Para se lembrar qual da duas opções que incluem o valor, se lembre que nesse caso menos é mais, ou seja, com dois pontos temos mais valores.
As Hashes são, digamos, Arrays indexados, com chaves e valores, que podem ser quaisquer tipos de objetos, como por exemplo:
A partir de Ruby 1.9.x as Hashes mantém a ordem dos elementos do jeito que foram criadas, porém em algumas versões do Ruby 1.8.x essa ordem é aleatória. Depois de declaradas, podemos buscar os seus valores através de suas chaves:
Utilizar símbolos como chaves de Hashes é uma operação costumeira em Ruby. Se utilizarmos Strings, elas serão tratadas como Strings congeladas, o que podemos verificar comparando os seus object_ids:
Vamos ver um exemplo de como podemos armazenar diversos tipos tanto nas chaves como nos valores de uma Hash:
Nesse caso, quando o valor da chave ainda não teve nada atribuído e é requisitado, é retornado o valor default que especificamos em new, que foi 0. Vamos testar com outro valor:
No caso acima, passei Time.now no método new da Hash, e toda vez que tentei acessar um dos valores que ainda não foram atribuídos, 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 método new:
Reparem que o operador “rocket” (=>) sumiu e por convenção esse tem sido o método utilizado desde então. Se você quer ser vintage, continue usando o “rocket”, se quiser ser cool, adote a forma nova. E fique de olho quando for vintage te transformar automaticamente em cool, tem uma galerinha aí cheia dessas firulas …
Um conceito interessante do Ruby são blocos de código (similares ou sendo a mesma coisa em certos sentidos que funções anônimas, closures, lambdas etc). Vamos ir aprendendo mais coisas sobre eles no decorrer do curso, na prática, mas podemos adiantar que blocos de código são uma das grande sacadas de Ruby e são muito poderosos quando utilizados com iteradores.
Por convenção 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 lheagradar. Como exemplo de blocos, temos:
Agora que vimos os tipos mais comuns, podemos destacar que temos algumas métodos de conversão entre eles, que nos permitem transformar um tipo (mas não o mesmo objeto, será gerado um novo) em outro. Alguns dos métodos:
Exceções nos permitem “cercar” erros que acontecem no nosso programa (afinal, ninguém é perfeito, não é mesmo?) em um objeto que depois pode ser analisado e tomadas as devidas providências ao invés de deixar o erro explodir dentro do nosso código levando à resultados indesejados. Vamos gerar um erro de propósito para testar isso.
Lembram-se que Ruby tem uma tipagem forte, onde não podemos misturar os tipos de objetos? Vamos tentar misturar:
O programa gerou uma exceção no código contido entre begin e rescue interceptando o tipo de erro tratado pela exceção do tipo StandardError, em um objeto que foi transmitido para rescue, através da variável exception, onde pudemos verificar informações sobre o erro, imprimindo-o como uma String.
Se não quisermos especificar o tipo de exceção a ser tratada, podemos omitir o tipo, e verificar a classe da exceção gerada dessa maneira:
Podemos ver que foi gerada uma nova exceção dentro do bloco do rescue e apesar do comando final com a mensagem “Fim de programa” não ter sido impressa pois a exceção “jogou” o fluxo de processamento para fora, o bloco do ensure foi executado.
Podemos descobrir qual foi a exceção que foi disparada anteriormente utilizando cause, que nos dá acesso as nested exceptions (a partir da versão 2.1):
Se por acaso quisermos criar nossas próprias classes de exceções, é muito fácil, basta criá-las herdando de StandardError. Vamos criar uma que vamos disparar se um nome for digitado errado, NameNotEqual:
Também podemos utilizar catch e throw para terminar o processamento quando nada mais é necessário, indicando através de um Symbol para onde o controle do código deve ser transferido (opcionalmente com um valor), indicado com catch, usando throw:
É importante notar que tudo em Ruby acaba no fim – end – e vamos ver isso acontecendo bastante com nossas estruturas de controle. Vamos começar vendo nosso velho amigo if:
Uma coisa bem interessante em Ruby é que podemos escrever isso de uma forma que podemos “ler” o código, se, como no caso do próximo exemplo, estivermos interessados apenas em imprimir a mensagem no caso do teste do ‘if’ ser verdadeiro:
O unless é a forma negativa do if, e como qualquer teste negativo, pode trazer alguma confusão 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):
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:
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 fica a critério de cada um e do melhor uso.
Podemos utilizar o case para fazer algumas comparações interessantes. Vamos ver como testar com Ranges:
No caso do case (redundância detectada na frase), a primeira coisa que ele compara é o tipo do objeto, nos permitindo fazer testes como:
A estrutura case compara os valores de forma invertida, como no exemplo acima, Fixnum === e não i === Fixnum, não utilizando o operador == e sim o operador ===, que é implementado das seguintes formas:
Para módulos e classes (que vamos ver mais à frente), é comparado se o valor é uma instância do módulo ou classe ou de um de seus descendentes. No nosso exemplo, i é uma instância de Fixnum. Por exemplo:
A partir desse ponto vamos utilizar um editor de texto para escrevermos nossos exemplos, usando o irb somente para testes rápidos com pouco código. Você pode utilizar o editor de texto que quiser, desde que seja um editor mas não um processador de textos. Não vá utilizar o Microsoft Word © para fazer os seus programas, use um editor como o Vim. Edite o seu código, salve em um arquivo com a extensão .rb e execute da seguinte forma (onde $ é o prompt do terminal):
O for pode ser utilizado junto com um iterador para capturar todos os seus objetos e enviá-los para o loop (que nada mais é do que um bloco de código):
Vamos aproveitar que é um loop bem simples e utilizar os comandos para interagir mostrados acima (mesmo que os exemplos pareçam as coisas mais inúteis e sem sentido do mundo - mas é para efeitos didáticos, gente!), menos o return onde precisaríamos de um método e ainda não chegamos lá. Vamos testar primeiro o break:
Se não interrompermos com Ctrl+C, esse código vai ficar funcionando para sempre, pois o redo avaliou o loop novamente mas sem ir para o próximo elemento do iterador.
Temos operadores lógicos em Ruby em duas formas: !, &&, || e not, and, or. Eles se diferenciam pela precedência: os primeiros tem precedência mais alta que os últimos sobre os operadores de atribuição. Exemplificando:
A variável c recebeu o resultado correto de a && b, enquanto que d recebeu a atribuição 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 não for falso. É a chamada operação de “curto-circuito”.
O que ocorre ali é o seguinte: é atribuído o valor à variável apenas se o valor dela for false ou nil, do contrário, o valor é mantido. Essa é uma forma de curto-circuito pois seria a mesma coisa que:
que no final das contas retorna a se o valor for diferente de false e nil ou, do contrário, faz a atribuição do valor para a variável. Seria basicamente
Temos um método chamado defined? que testa se a referência que passamos para ele existe. Se existir, ele retorna uma descrição do que é ou nil se não existir. Exemplo:
Declare duas variáveis, x e y, com respectivamente 1 e 2 como valores, com apenas uma linha. Agora inverta os seus valores também com apenas uma linha de código. O que vamos fazer aqui é uma atribuição em paralelo. Resposta no final do livro!
Pudemos ver que precisamos executar call para chamar a Proc, mas também podemos utilizar o atalho []:
Fica um “gancho” aqui sobre o fato de Procs serem closures, ou seja, códigos que criam uma cópia do seu ambiente. Quando estudarmos métodos vamos ver um exemplo prático sobre isso.
A primeira diferença, é a verificação de argumentos. Em lambdas a verificação é feita e gera uma exceção:
A segunda diferença é o jeito que elas retornam. O retorno de uma Proc retorna de dentro de onde ela está, como nesse caso:
E também ao método curry, que decompõe uma lambda em uma série de outras lambdas. Por exemplo, podemos ter uma lambda que faça multiplicação:
Reparem que o método call (na forma de .()) foi chamado duas vezes, primeiro com 2 e depois com 3, pois o método curry inseriu uma lambda dentro da outra, como se fosse:
Isso pode ser útil quando você deseja criar uma lambda a partir de outra, deixando um dos parâmetros fixo, como por exemplo:
Agora que conhecemos os tipos básicos de Ruby, podemos focar nossa atenção em uma característica bem interessante deles: muitos, senão todos, tem coleções ou características que podem ser percorridas por métodos iteradores.
Um iterador percorre uma determinada coleção, que o envia o valor corrente, executando algum determinado procedimento, que em Ruby é enviado como um bloco de código e contém o módulo (hein?) Enumerable, que dá as funcionalidades de que ele precisa.
Dos métodos mais comuns para percorrer uma coleção, temos each, que significa “cada”, e que pode ser lido “para cada elemento da coleção do meu objeto, execute esse bloco de código”, dessa maneira:
Ou seja, para cada elemento do Array, foi executado o bloco - atenção aqui - passando o elemento corrente como parâmetro, recebido pelo bloco pela sintaxe |<parâmetro>|. Podemos ver que as instruções do nosso bloco, que no caso só tem uma linha (e foi usada a convenção de { e }), foram executadas com o valor recebido como parâmetro.
Esse mesmo código pode ser otimizado e refatorado para ficar mais de acordo com a sua finalidade. Não precisamos de um loop de 1 até 5? A maneira mais adequada seria criar uma Range com esse intervalo e executar nosso iterador nela:
Inclusive, podemos também utilizar times em um Fixnum, que se comporta como uma coleção nesse caso, que começa em 0:
Um Array só faria sentido nesse caso se os seus elementos não seguissem uma ordem lógica que pode ser expressa em um intervalo de uma Range! Quaisquer sequências que podem ser representadas fazem sentido em usar uma Range. Se por acaso quiséssemos uma lista de números de 1 até 21, em intervalos de 3, podemos utilizar:
Vamos supor que queremos selecionar alguns elementos que atendam alguma condição nos nossos objetos, por exemplo, selecionar apenas os números pares de uma coleção:
Se não utilizássemos lazy, íamos precisar de um CTRL+C, pois o conjunto de números naturais é infinito, e a seleção nunca pararia para que fossem pegos os 5 elementos.
Vamos supor que desejamos detectar se todos os elementos da coleção atendem uma determinada condição com o método all?:
Apesar da facilidade com um teste simples, o método any? é muito prático no caso de procurarmos, por exemplo, um determinado objeto com um determinado valor de retorno em algum de seus métodos.
Se além de detectar quisermos retornar o elemento que atende à uma condição, podemos utilizar o método detect?:
É interessante notar que podemos passar um bloco onde serão comparados os valores para teste através do operador <=> (conhecido por “navinha”):
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:
A partir da versão 2.4, a diferença entre os métodos min e max é brutal. Vamos rodar o seguinte código em ambas as versões (ok, a parte de benchmarks ainda está bem na frente aqui no livro, mas vamos considerar somente os resultados aqui):
Um método muito útil para coleções é o método grep (mesmo nome do utilitário de linha de comando - muito útil, por sinal). Podemos, por exemplo, encontrar determinadas Strings em um Array, no exemplo abaixo, todas as que tem comprimento entre 3 e 7 caracteres:
Selecionar os valores de uma determinada faixa, no exemplo abaixo, criando um Array com 10 elementos preechidos por números de até 10, selecionando somente os únicos que estão entre 5 e 10:
Utilizando uma lambda para selecionar determinada condição (no exemplo, as Strings cujo comprimento é maior que 3):
E que tal fazer um sorteador de números da Megasena (se alguém ganhar, lembra de mim!) em apenas uma linha?
Um método bem útil para o caso de precisarmos inspecionar ou registrar o conteúdo de algum objeto durante algum encadeamento de iteradores é o método tap. Vamos supor que você tem o seguinte código:
Isso nada mais faz do que separar os números pares e multiplicá-los por 2, mas imaginemos que a coleção inicial não é formada por números e sim por objetos da nossa tabela de funcionários onde vamos selecionar somente algumas pessoas que atendem determinadas condições (usando o select) e reajustar o seu salário baseado em várias regras complexas (o map), e algum problema está ocorrendo na seleção.
O jeito convencional é criar uma variável temporária armazenando o conteúdo retornado pelo select e a imprimirmos, executando o map logo em seguida. Ou podemos fazer assim:
Executando esse código, será impresso Oi!. Já podemos reparar que os parênteses não são obrigatórios para chamar um método em Ruby.
Podemos retornar valores de métodos com ou sem o uso de return. Quando não utilizamos return, o que ocorre é que a última expressão avaliada é retornada, como no exemplo:
No caso, foi avaliado por último p1 * p2, o que nos dá o resultado esperado. Também podemos retornar mais de um resultado, que na verdade é apenas um objeto, sendo ele complexo ou não, dando a impressão que são vários, como no exemplo que vimos atribuição em paralelo.
Antes de mais nada, fica a discussão sobre a convenção sobre o que são parâmetros e o que são argumentos, convencionando-se à:
Parâmetros são as variáveis situadas na assinatura de um método; Argumentos são os valores atribuídos aos parâmetros
E também valores variáveis, bastando declarar o nosso método como recebendo um parâmetro com o operador splat (asterisco, *) antes do nome do parâmetro:
O operador splat pode parecer meio estranho, mas ele nada mais faz, na definição do método, do que concentrar todos os valores recebidos em um Array, como pudemos ver acima. Pensem nele como um buraco negro que suga todos os valores!
Quando usamos o splat na frente do nome de uma variável que se comporta como uma coleção, ele “explode” os seus valores, retornando os elementos individuais:
Importante notar que a definição do método retorna um símbolo com o nome do método, o que nos permite chamar ele mais tarde direto por essa referência:
Podemos também converter um bloco em uma Proc especificando o nome do último parâmetro com & no começo:
Como recebemos referências do objeto nos métodos, quaisquer alterações que fizermos dentro do método refletirão fora, como já vimos um pouco acima quando falando sobre variáveis. Vamos comprovar:
Uma praticidade grande é usarmos o corpo do método para capturarmos uma exceção, sem precisar abrir um bloco com begin e end:
Vale aqui lembrar que temos uma diferença de performance utilizando rescue dessa maneira, onde podemos utilizar o operador ternário:
Rodando o programa, podemos ver que a diferença em utilizar o ternário é mais brutal que escutar “Abyssal Gates”, do Krisiun:
Também podemos utilizar os caracteres ! e ? no final dos nomes dos nossos métodos. Por convenção, métodos com ! no final são chamados de métodos destrutivos e com ? no final são chamados de métodos predicados e são utilizados para testar algo e devem ter retorno booleano, retornando true ou false:
Como podemos ver, o resultado é um objeto do tipo Method, mas que pode ser convertido em uma Proc usando o método to_proc.
E agora um método de nome totalmente diferente usando o suporte para encodings do Ruby a partir das versões 1.9.x:
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 inserção, CTRL+K +Z.
Como bastante coisas em Ruby são objetos, vamos aprender a criar os nossos. Vamos fazer uma classe chamada Carro, com algumas propriedades:
Segundo as convenções de Ruby, nos nomes dos métodos deve-se usar letras minúsculas separando as palavras com um sublinhado (_), porém nos nomes das classes é utilizado camel case, da mesma maneira que em Java, com maiúsculas separando duas ou mais palavras no nome da classe. Temos então classes com nomes como MinhaClasse, MeuTeste, CarroPersonalizado.
As propriedades do nosso objeto são armazenadas no que chamamos de variáveis de instância, que são quaisquer variáveis dentro do objeto cujo nome se inicia com @. Se fizermos referência para alguma que ainda não foi criada, ela será. Podemos inicializar várias dessas variáveis dentro do método initialize, que é o construtor do nosso objeto, chamado após o método new, que aloca espaço na memória para o objeto sendo criado.
Não temos métodos destrutores em Ruby, mas podemos associar uma Proc para ser chamada em uma instância de objeto cada vez que ela for limpa pelo garbage collector. Vamos verificar isso criando o arquivo destructor.rb:
Crie mais algumas variáveis/referências como no exemplo acima, associando uma Proc com o finalizer do objeto. Repare que talvez algumas não estejam exibindo a mensagem. Porque?
Pudemos ver acima que usando puts para verificar o nosso objeto, foi mostrada somente a referência dele na memória. Vamos fazer um método novo na classe para mostrar as informações de uma maneira mais bonita. Lembram-se que em conversões utilizamos um método chamado to_s, que converte o objeto em uma String?
Sobrescrever o método to_s não deveria afetar o inspect. O pessoal discutiu muito isso e nas versões 2.x foi restaurado o comportamento antes das 1.9.x, como visto acima.
Ruby tem alguns métodos que podem confundir um pouco, parecidos com to_s e to_i, que são to_str e to_int. Enquanto to_s e to_i efetivamente fazem uma conversão de tipos, to_str e to_int indicam que os objetos podem ser representados como uma String e um Fixnum, respectivamente. Ou seja: to_s significa que o objeto tem representação como String, to_str significa que o objeto é uma representação de String.
Todo método chamado sem um receiver explícito será executado em self, que especifica o próprio objeto ou classe corrente.
Vimos como criar as propriedades do nosso objeto através das variáveis de instância, mas como podemos acessá-las? Isso vai nos dar um erro:
Essas variáveis são privadas do objeto, e não podem ser lidas sem um método de acesso. Podemos resolver isso usando attr_reader:
Podemos até encurtar isso mais ainda criando direto um atributo de escrita e leitura com attr_accessor:
Se precisarmos de objetos com atributos com escrita e leitura, podemos usar duas formas bem rápidas para criarmos nossos objetos. Uma é usando Struct:
Outra é mais flexível ainda, usando OpenStruct, onde os atributos são criados na hora que precisamos deles:
Também podemos criar atributos virtuais, que nada mais são do que métodos que agem como se fossem atributos do objeto. Vamos supor que precisamos de uma medida como galões, que equivalem a 3,785 litros, para o tanque do carro. Poderíamos fazer:
Uma diferença de Ruby com várias outras linguagens é que as suas classes, mesmo as definidas por padrão e base na linguagem, são abertas, ou seja, podemos alterá-las depois que as declararmos. Por exemplo:
Pude inserir e remover um método que é incorporado aos objetos que foram definidos sendo daquela classe e para os novos a serem criados também. Também pudemos remover o método, o que gerou a mensagem de erro.
Às vezes temos que testar determinados objetos e métodos verificando antes de eles existem. Podemos ver isso no código abaixo, onde os objetos e métodos são verificados usando primeiro um if verificando se não existe alguma referência nula, depois, comentado, o método try do ActiveSupport do Rails e por último o “navegador de operação segura” &., onde é tentado acessar objeto&.propriedade, retornando o valor ou nulo se falhar.
Se por acaso quisermos guardar uma cópia do método que vamos redefinir, podemos usar alias_method para dar outro nome para ele:
Podemos ver que no caso do corsa, o novo método foi adicionado, mas não no gol. O que aconteceu ali com o operador/método <<? Hora de algumas explicações sobre metaclasses!
Todo objeto em Ruby tem uma hierarquia de ancestrais, que podem ser vistos utilizando ancestors, como:
Todos os objetos a partir das versões 1.9.x são derivados de BasicObject, que é o que chamamos de blank slate, que é um objeto que tem menos métodos que Object.
O que ocorreu no exemplo da inserção do método na instância acima (quando utilizamos <<), é que o método 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 instância pertence, ou seja, o método foi inserido antes da classe Carro. A procura do método (method lookup) se dá na eigenclass da instância, depois na hierarquia de ancestrais.
Em linguagens de tipagem estática, o compilador checa se o objeto receiver tem um método com o nome especificado. Isso é chamado checagem estática de tipos (static type checking), daí o nome da característica dessas linguagens.
Para isso ficar mais legal e prático, vamos ver como fazer dinamicamente, já começando a brincar com metaprogramação 4. Primeiro, com a classe:
Depois de ver tudo isso sobre inserção e remoção de métodos dinamicamente, vamos ver um truquezinho para criar um método “autodestrutivo”:
Também podemos ter variáveis de classes, que são variáveis que se encontram no contexto da classe e não das instâncias dos objetos da classe. Variáveis de classes tem o nome começado com @@ e devem ser inicializadas antes de serem usadas. Por exemplo:
Para que não precisemos acessar a variável através de uma instância, podemos criar um método de classe, utilizando self. antes do nome do método:
Os métodos de classe também podem ser chamados de métodos estáticos, em que não precisam de uma instância da classe para funcionar. Fazendo uma pequena comparação com variáveis e métodos estáticos em Java, no arquivo CarroEstatico.java:
O método self é particularmente interessante para desenvolvermos interfaces fluentes 5, que visa a escrita de código mais legível, geralmente implementada utilizando métodos encadeados, auto-referenciais no contexto (ou seja, sempre se referindo ao mesmo objeto) até que seja encontrado e retornado um contexto vazio. Poderíamos ter uma interface fluente bem básica para montar alguns comandos select SQL dessa forma:
Reparem que self sempre foi retornado em todos os métodos, automaticamente retornando o próprio objeto de onde o método seguinte do encadeiamento foi chamado.
Um problema que acontece com as variáveis de classe utilizando @@ é que elas não pertencem realmente às classes, e sim à hierarquias, podendo permear o código dessa maneira:
Está certo que esse não é um código comum de se ver, mas já dá para perceber algum estrago quando as variáveis @@ são utilizadas dessa maneira. Repararam que a @@qtde externa teve o seu valor atribuído como 0 dentro da classe?
Vejam que a variável está na instância da classe (sim, classes tem uma instância “flutuando” por aí) e não em instâncias de objetos criados pela classe (os @) e nem são variáveis de classe (os @@).
Em Ruby, temos herança única, que significa que uma classe pode apenas ser criada herdando de apenas outra classe, reduzindo a complexidade do código. Como exemplo de alguma complexidade (pouca, nesse caso), vamos pegar de exemplo esse código em C++:
Não foi possível resolver qual método ligar era para ser chamado. Para isso, temos que indicar explicitamente em qual das classes herdadas o método vai ser chamado, trocando
Para fazermos a herança nas nossas classes em Ruby, é muito simples, é só utilizarmos class <nome da classe filha> < <nome da classe pai>:
Se estivermos com pressa e não quisermos fazer uma declaração completa de uma classe com seus readers, writers ou acessors, podemos herdar de uma Struct (lembram dela?) com alguns atributos da seguinte maneira:
Sabemos que os valores são transferidos por referência, e se quisermos criar novos objetos baseados em alguns existentes? Para esses casos, podemos duplicar um objeto usando dup, gerando um novo objeto:
Essa funcionalidade está implementada automaticamente para os objetos que são instâncias da nossa classe, mas fica uma dica: existem casos em que precisamos ter propriedades diferentes ao efetuar a cópia, como por exemplo, a variável de instância @criado, onde se utilizarmos dup, vai ser duplicada e não vai refletir a data e hora que esse novo objeto foi criado através da duplicação do primeiro:
Apesar de esperarmos 1 segundo utilizando o método sleep, o valor de @criado na cópia do objeto feita com dup permaneceu o mesmo. Para evitar isso, utilizamos initialize_copy na nossa classe, que vai ser chamado quando o objeto for duplicado, atualizando o valor da variável de instância @criado_em:
Vale lembrar que cópias de objetos em Ruby usando dup são feitas usando o conceito de shallow copy, que duplica um objeto mas não os objetos referenciados dentro dele. Vamos ver um exemplo:
Pudemos ver que o objeto que consta na variável b foi duplicado, porém o objeto que consta na referência 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 alcançado através de serialização utilizando Marshal, armazenando os objetos como um fluxo de dados binários e depois restaurando todos em posições de memória totalmente novas:
Podemos emular o comportamento de uma OpenStruct utilizando o método method_missing, que é chamado caso o seu objeto o tenha declarado, sempre que ocorrer uma exceção do tipo NoMethodError, ou seja, quando o método que tentamos acessar não existe:
Vamos imaginar que temos uma classe chamada CaixaDeParafusos e queremos algum jeito de fazer ela interagir com outra, por exemplo, adicionando o conteúdo de um outra (e esvaziando a que ficou sem conteúdo). Podemos fazer coisas do tipo:
Mas espera aí! Se eu somei uma caixa com a outra em uma terceira, não deveria ter sobrado nada nas caixas originais, mas ao invés disso elas continuam intactas. Precisamos zerar a quantidade de parafusos das outras caixas:
Parece que ocorreu um erro ali, mas está fácil de descobrir o que é. Tentamos acessar a variável de instância da outra caixa enviada como parâmetro mas não temos um attr_writer para ela!
Mas espera aí: só queremos que essa propriedade seja alterada quando efetuando alguma operação com outra caixa de parafusos ou alguma classe filha, e não seja acessada por qualquer outra classe. Nesse caso, podemos usar um método protegido:
Agora pudemos ver que tudo funcionou perfeitamente, pois utilizamos protected antes de inserir o attr_writer. Os modificadores de controle de acesso de métodos são:
Privados (private) - Só podem ser chamados dentro de seu próprio objeto, mas nunca é possível acessar um método privado de outro objeto, mesmo se o objeto que chama seja uma sub-classe de onde o método foi definido.
Usando a seguinte analogia para lembrar do acesso dos métodos: vamos supor que você seja dono de um restaurante. Como você não quer que seus fregueses fiquem apertados você manda fazer um banheiro para o pessoal, mas nada impede também que apareça 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 é público.
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 invés 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 conteúdos fixos e talvez o resto que sobrar em outra. Podemos usar o método /:
Ou podemos simplesmente pedir para dividir o conteúdo em X caixas menores, distribuindo uniformemente o seu conteúdo:
Vamos fazer um gancho aqui falando em classes e métodos para falar um pouco de closures. Closures são funções anônimas com escopo fechado que mantém o estado do ambiente em que foram criadas.
Os blocos de código que vimos até agora eram todos closures, mas para dar uma dimensão do fato de closures guardarem o seu ambiente podemos ver:
A Proc foi criada pela lambda na linha 3, que guardou a referência para a variável contador mesmo depois que saiu do escopo do método cria_contador.
Ruby tem herança única, como vimos quando criamos nossas próprias classes, mas conta com o conceito de módulos (também chamados nesse caso de mixins) para a incorporação de funcionalidades adicionais. Para utilizar um módulo, utilizamos include:
Como pudemos ver, podemos mixar várias características de um módulo em uma classe. Isso poderia ter sido feito para apenas uma instância de um objeto usando extend, dessa forma:
Uma coisa bem importante a ser notada é que quanto usamos include os métodos provenientes do módulo são incluídos nas instâncias das classes, e não nas classes em si. Se quisermos definir métodos de classes dentro dos módulos, podemos utilizar um outro hook chamado included, usando um módulo interno (???):
Se incluirmos o módulo em uma classe, os métodos do módulo se tornam métodos das instâncias da classe. Se incluirmos o módulo na eigenclass da classe, se tornam métodos da classe. Se incluirmos em uma instância da classe, se tornam métodos singleton do objeto em questão.
Temos alguns comportamentos bem úteis usando mixins. Alguns nos pedem apenas um método para dar em troca vários outros. Se eu quisesse implementar a funcionalidade do módulo Comparable no meu objeto, eu só teria que fornecer um método <=> (starship, “navinha”) e incluir o módulo:
Com isso ganhamos os métodos <, <=, ==, >, >= e between?. Vamos criar um iterador mixando o módulo Enumerable:
Reparem que o módulo foi inserido na cadeia de chamadas após a classe corrente, tanto que quando temos na classe um método com o mesmo nome que o do módulo, é chamado o método da classe.
A partir da versão 2, temos o método prepend, que insere o módulo antes na cadeia de chamada de métodos:
Outro ponto bem importante para se notar é que, se houverem métodos em comum entre os módulos inseridos, o método do último módulo incluído é que vai valer. Vamos fazer um arquivo chamado overmod.rb com o seguinte código:
Pudemos ver que o módulo Radio foi incluído por último, consequentemente o seu método ligar é que foi utilizado. Isso é fácil de constatar verificando os ancestrais de Carro:
Aqui tem um lance meio inception: um módulo pode estender a si mesmo! Imaginem que precisamos de um módulo, que não precisa de uma instância de um objeto, que tem alguns métodos que podem ser chamamos como métodos estáticos. Poderíamos ter algum como:
No primeiro exemplo, ficou bem claro que os métodos são estáticos, através do uso de self, enquanto no segundo, ficou meio “feitiçaria”, fazendo com que o extend self no início fizesse com que o módulo estendesse a si mesmo, injetando os seus métodos de instância (hein?), da sua eigenclass, como métodos de classe (hein, de novo?)!
O resultado vai ser similar, mas convém analisar a clareza do código levando em conta a visibilidade do primeiro exemplo contrastando com a forma prática, porém “vodu”, do segundo.
Módulos também podem ser utilizados como namespaces, que nos permitem delimitar escopos e permitir a separação e resolução de identificadores, como classes e métodos, que sejam homônimos. Vamos pegar como exemplo um método chamado comida_preferida, que pode estar definido em várias classes de mesmo nome, porém em módulos diferentes:
Apesar de ambas as classes chamarem Pessoa e terem métodos chamados comida_preferida, elas estão separadas através de cada módulo em que foram definidas. É uma boa idéia utilizar namespaces quando criarmos algo com nome, digamos, comum, que sabemos que outras pessoas podem criar com os mesmos nomes. Em Java, por exemplo, existe a convenção que um namespace pode ser um domínio invertido, utilizando a keyword package, como por exemplo:
Está certo que cada arquivo tem que ser criado na estrutura de diretórios de acordo com o nome do package e outros detalhes, mas, depois de compilados (e opcionalmente empacotados), funciona direitinho:
Podemos implementar algumas funcionalidades interessantes com módulos, por exemplo, criar uma classe Singleton 6:
A classe TracePoint só está disponível a partir da versão 2.0 de Ruby. Em outras versões, comportamento similar pode ser obtido através do método set_trace_func7.
A classe TracePoint nos permite coletar informações durante a execução do nosso programa, interceptando vários tipos (ou todos) de eventos que ocorrem. Os eventos são:
Quando interceptamos alguns desses eventos, temos na TracePoint as seguintes informações disponíveis:
Para ativarmos a TracePoint, criamos uma nova instância da classe, com os eventos que queremos monitorar, e logo após chamamos o método enable. Vamos ver como funciona no arquivo tpoint.rb:
A classe TracePoint nos permite fazer algumas coisas bem legais no nosso código. Como exemplo disso, vi em um Metacast um exemplo para tentar definir uma interface8 em Ruby, e dei uma mexida nele para ficar assim:
Tentem comentar alguns dos métodos definidos em Test e rodar o programa, vai ser disparada uma exceção do tipo NotImplementedError!
Antes de ver mais uma funcionalidade bem legal relacionada à módulos, vamos ver como fazemos para instalar pacotes novos que vão nos prover essas funcionalidades, através das RubyGems.
O RubyGems é um projeto feito para gerenciar as gems, que são pacotes com aplicações ou bibliotecas Ruby, com nome e número de versão. O suporte à gems já se encontra instalado, pois instalamos o nosso interpretador Ruby com a RVM.
Se não estivermos utilizando a RVM, apesar de alguns sistemas operacionais já terem pacotes prontos, recomenda-se instalar a partir do código-fonte. Para isso, é necessário ter um interpretador de Ruby instalado e seguir os seguintes passos (lembrando de verificar qual é a última versão disponível em http://rubygems.org e executar os comandos seguintes como root ou usando sudo):
Certifique-se de ter instalado a biblioteca zlib (e, dependendo da sua distribuição, o pacote zlib-devel também.
Após instalado, vamos dar uma olhada em algumas opções que temos, sempre usando a opção como parâmetro do comando gem:
list - Essa opção lista as gems atualmente instaladas. Por não termos ainda instalado nada, só vamos encontrar os sources do RubyGems.
update - Atualiza a gem especifica ou todas instaladas. Você pode usar --include-dependencies para instalar todas as dependências necessárias.
cleanup - Essa é uma opção muito importante após rodar o update. Para evitar que algo se quebre por causa do uso de uma versão especifica de um gem, o RubyGems mantém todas as versões antigas até que você execute o comando cleanup. Mas preste atenção se alguma aplicação não precisa de uma versão específica - e antiga - de alguma gem.
Podem ser especificadas chaves para procurar as gems locais (-l) e remotas (-r). Verifique qual o comportamento padrão da sua versão do Ruby executando search sem nenhuma dessas chaves.
Instalamos essa gem especifica para verificar uma funcionalidade muito interessante, a memoization, que acelera a velocidade do programa armazenando os resultados de chamadas aos métodos para recuperação posterior.
Se estivermos utilizando uma versão de Ruby anterior a 1.9.x, antes de mais nada temos que indicar, no início do programa, que vamos usar as gems através de
Sem isso o programa não irá saber que desejamos usar as gems, então “no-no-no se esqueça disso, Babalu!”. Algumas instalações e versões de Ruby da 1.9.x já carregam as RubyGems automaticamente, mas não custa prevenir.
Não confunda require com include ou com o método load. Usamos include para inserir os módulos, require para carregar “bibliotecas” de código e load para carregar e executar código, que pode ter o código carregado como um módulo anônimo, que é imediatamente destruído após o seu uso, se enviarmos true como o segundo argumento. Vamos ver sem utilizar true:
Agora vamos dar uma olhada na tal da memoization. Vamos precisar de um método com muitas chamadas, então vamos usar um recursivo. Que tal a sequência de Fibonacci 9? Primeiro vamos ver sem usar memoization:
Recomendo não usar um número maior que 40 ali não se vocês quiserem dormir em cima do teclado antes de acabar de processar. ;-)
Uau! Se quiserem trocar aquele número de 40 para 350 agora pode, sério! :-) E ainda dá para otimizar mais se indicarmos um arquivo (nesse caso, chamado memo.cache) para gravar os resultados:
Podemos calcular uma aproximação de um número de Fibonacci usando a seguinte equação, onde n é a sequência que queremos descobrir e Φ (Phi) é a “proporção áurea”:
Uma linguagem de programação que se preze tem que ter suporte à threads. Podemos criar threads facilmente com Ruby utilizando a classe Thread:
O método join é especialmente útil para fazer a thread se completar antes que o interpretador termine. Podemos inserir um timeout:
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 também?):
Mas temos que ficar atentos à alguns pequenos detalhes. Podemos nos deparar com algumas surpresas com falta de sincronia em versões antigas da linguagem, como:
O problema é que não houve sincronia entre as duas threads, o que nos levou a resultados diferentes no log, pois não necessariamente as variáveis eram acessadas de maneira uniforme. Lógico que não vamos ficar utilizando versões antigas da linguagem, mas temos que aprender o que podemos fazer quando tivermos essa falta de sincronia em alguma situação em versões recentes.
A diferença dos monitores é que eles podem ser uma classe pai da classe corrente, um mixin e uma extensão de um objeto em particular.
Também para evitar a falta de sincronia, podemos ter variáveis de condição que sinalizam quando um recurso está ocupado ou liberado, através de wait(mutex) e signal. Vamos fazer duas Threads seguindo o conceito de produtor/consumidor:
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.
A implementação das threads das versões 1.8.x usam green threads e não native threads. As green threads podem ficar bloqueadas se dependentes de algum recurso do sistema operacional, como nesse exemplo, onde utilizamos um FIFO 10 (o do exemplo pode ser criado em um sistema Unix-like com mkfifo teste.fifo) para criar o bloqueio:
Podemos interceptar um comportamento “bloqueante” também utilizando o método try_lock. Esse método tenta bloquear o Mutex, e se não conseguir, retorna false. Vamos supor que temos uma Thread que efetua um processamento de tempos em tempos, e queremos verificar o resultado corrente, aproveitando para colocar um hook para sairmos do programa usando CTRL+C:
Entre as features introduzidas na versão 1.9, existe uma bem interessante chamada Fibers, volta e meia definidas como “threads leves”. Vamos dar uma olhada nesse código:
Até aí tudo bem, aparentemente um código normal que utiliza um iterador, mas vamos dar uma olhada nesse aqui:
Dando uma olhada no nome da classe de enum1, podemos ver que agora podemos criar um Enumerator com vários dos iteradores à que já estávamos acostumados, e foi o que fizemos ali alternando entre os elementos dos dois Enumerators, até finalizar quando foi gerada uma exceção, capturada pela estrutura loop...do, quando os elementos terminaram.
O segredo nos Enumerators é que eles estão utilizando internamente as Fibers. Para um exemplo básico de Fibers, podemos ver como calcular, novamente, os números de Fibonacci:
O segredo ali é que Fibers são corrotinas e não 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 variáveis 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:
Nesse ponto, o controle é transferido com o valor de y para onde foi chamado o resume, e impresso na tela.
A partir do próximo resume, o código da Fiber é executado do ponto onde parou para baixo, ou seja, da próxima linha após o yield (linha 5, mostrando outra característica das corrotinas, que é ter mais de um ponto de entrada) processando os valores das variáveis e retornando para o começo do loop, retornando o controle novamente com yield.
Nesse caso podemos ver o comportamento da Proc como uma subrotina, pois o valor que estamos interessados foi retornado com um return explícito (lembrem-se que em Ruby a última expressão avaliada é a retornada, inserimos o return explicitamente apenas para efeitos didáticos).
Mas ainda há algumas divergências entre Fibers serem corrotinas ou semi-corrotinas. As semi-corrotinas são diferentes das corrotinas pois só podem transferir o controle para quem as chamou, enquanto corrotinas podem transferir o controle para outra corrotina.
Nesse caso, f1 está transferindo o controle para f2 (que não é quem a chamou!), que transfere de volta para f1 que retorna o resultado em resume.
Discussões teóricas à parte, as Fibers são um recurso muito interessante. Para finalizar, um bate-bola rápido no esquema de “produtor-consumidor”usando Fibers:
As Fibers também podem ajudar a separar contextos e funcionalidades em um programa. Se precisássemos detectar a frequência de palavras em uma String ou arquivo, poderíamos utilizar uma Fiber para separar as palavras, retornando para um contador:
Estão vendo como eu escrevi a expressão regular acima? O \p{Latin} é uma propriedade de caracter que habilita a expressão regular a entender os nossos caracteres acentuados.
Podemos utilizar a gem Parallel 12 para executar processamento em paralelo usando processos (em CPUs com vários processadores) ou utilizando as Threads:
Vamos ver um exemplo utilizando Threads, que dão mais velocidade em operações bloqueantes, não usam memória extra e permitem modificação de dados globais:
Agora, utilizando processos, que utilizam mais de um núcleo, dão mais velocidade para operações bloqueantes, protegem os dados globais, usam mais alguma memória e permitem interromper os processos filhos junto com o processo principal, através de CTRL+C ou enviando um sinal com kill -2:
Reparem que a frase “Quem terminou primeiro?” veio sem mais nada no final, e reparem também que estamos utilizando agora processos e que eu disse que Threads permitem a modificação de dados globais e processos não.
Para executar esse mesmo código utilizando o número de processadores da CPU, é só não especificar nem in_threads ou in_processes:
Ao invés de medir nosso código através do sucessivas chamadas à Time.now, podemos utilizar o módulo de benchmark, primeiro medindo uma operação simples, como criar uma String enorme:
Ler, escrever e processar arquivos e fluxos de rede são requisitos fundamentais para uma boa linguagem de programação moderna. Em algumas, apesar de contarem com vários recursos para isso, às vezes são muito complicados ou burocráticos, o que com tantas opções e complexidade várias vezes pode confundir o programador. Em Ruby, como tudo o que vimos até aqui, vamos ter vários meios de lidar com isso de forma descomplicada e simples.
Antes de começarmos a lidar com arquivos, vamos criar um arquivo novo para fazermos testes, com o nome criativo de teste.txt. Abra o seu editor de texto (pelo amor, eu disse editor e não processador de textos, a cada vez que você confunde isso e abre o Word alguém solta um pum no elevador) e insira o seguinte conteúdo:
Isso gera uma String com todo o conteúdo do arquivo, porém sem a quebra de linhas presente no arquivo. Para lermos todas as suas linhas como um Array (que teria o mesmo efeito de quebrar a String resultante da operação acima em \n):
“FileReader … FileInputStream … BufferedReader … como que era mesmo?” Vai falar, você aí que programa em Java, não é bem mais prático por aqui?
Podemos abrir o arquivo especificando 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:
Melhor do que isso é passar um bloco para File onde o arquivo vai ser aberto e automaticamente - ou “automagicamente” - fechado no final do bloco:
Rodando o programa, é o mesmo resultado acima, com a diferença que isso “automagicamente” vai fechar o handle do arquivo, no final do bloco. Confessa aí, você já deixou um handle de arquivo, conexão com o banco, conexão de rede aberta alguma vez né não?
Ruby tem um objeto de IO chamado DATA, que retorna as linhas definidas após __END__ e o fim do arquivo. Dessa forma, podemos carregar alguns dados junto com nosso código-fonte.
Podemos ler e escrever em arquivos compactados Zip, para isso vamos precisar instalar a gem rubyzip:
Vamos criar três arquivos, 1.txt, 2.txt e 3.txt com conteúdo livre dentro de cada um, que vão ser armazenados internamente no arquivo .zip em um subdiretório chamado txts, compactando e logo descompactando:
Na linha 3 foi requisitado o módulo FileUtils, que carrega métodos como o mkpath, na linha 19, utilizado para criar o diretório (ou a estrutura de diretórios).
Na linha 8 abrimos o arquivo, enviando true como flag indicando para criar o arquivo caso não exista. Para arquivos novos, podemos também utilizar new.
Na linha 9 utilizamos Dir.glob para nos retornar uma lista de arquivos através de uma máscara de arquivos.
Na linha 11 utilizamos o método add para inserir o arquivo encontrado dentro de um path interno do arquivo compactado, nesse caso dentro de um diretório chamado txts.
Na linha 20 extraímos o arquivo, passando um bloco que vai ser executado no caso do arquivo já existir.
Vamos acessar arquivos XML através do REXML, um processador XML que já vem com Ruby. Para mais informações sobre esse processador XML, consulte o tutorial oficial em http://www.germane- software.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:
Poderíamos ter convertido também os elementos em um Array e usado o iterador para percorrer o arquivo, o que dará resultado similar:
Talvez para fazer a instalação da Nokogiri, vai ser preciso instalar algumas libs no sistema operacional. No Ubuntu, são:
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 são 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 oficial do W3C 13.
Após isso vamos usar o nosso arquivo alunos.xml criado anteriormente para mostrar um exemplo de transformação. Para isso vamos precisar de uma folha de estilo XSL, alunos.xsl:
Aproveitando que estamos falando de XML, nada melhor do que comparar com a alternativa mais do que otimizada utilizada largamente hoje em dia na web para transmissão de dados sem utilizar os “monstrinhos” de XML: JSON. 14 Não é aquele cara do “Sexta-Feira 13” não hein! É o JavaScript Object Notation, que nos permite converter, por exemplo, uma Hash em uma String que pode ser enviada nesse formato:
Podemos definir o YAML (YAML Ain’t Markup Language - pronuncia-se mais ou menos como “ieimel”, fazendo rima com a pronúncia de “camel”, em inglês) como uma linguagem de definição 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 extensão dos arquivos YAML é yml) que vamos alterar de acordo com nossos exemplos, armazenando um Array no nosso arquivo.
O TCP é um dos protocolos que nos permitem utilizar a Internet e que define grande parte do seu funcionamento. Falar em utilizar comunicação de rede sem utilizar TCP hoje em dia é quase uma impossilibidade para grande parte das aplicações que utilizamos e que pretendemos construir. Outra vantagem é a quantidade e qualidade de documentação que podemos encontrar sobre o assunto, o que, alguns anos antes, quando alguns protocolos como o IPX/SPX e o X25 dominam respectivamente na parte de redes de computadores e transmissão telefônica, era uma tarefa bem complicada, principalmente pelo fato de não haver nem Internet para consultarmos algo. Lembro que demorei tanto para arrumar um livro decente sobre IPX/SPX que 1 ano depois, nem precisava mais dele (e não sei para onde diabos que ele foi).
Para começar a aprender sobre como utilizar TCP em Ruby, vamos verificar um servidor SMTP, usando sockets TCP, abrindo a URL indicada na porta 25:
O serviço mailtrap.io é uma opção bem interessante para brincar um pouco e testar os emails enviados para a sua aplicação.
Agora vamos criar um servidor com TCP novinho em folha, na porta 8081, do localhost (quem não souber o que é localhost arrume uma ferramenta de ataque com algum script kiddie e aponte para esse tal de localhost - dependendo do seu sistema operacional e configurações de segurança dele, vai aprender rapidinho) 15:
Podemos trafegar, além de Strings, outros tipos pela conexão TCP, fazendo uso dos métodos pack, para “empacotar” e unpack, para “desempacotar” os dados que queremos transmitir. Primeiro, com o arquivo do servidor, tcpserver2.rb:
O protocolo UDP 16 utiliza pacotes com um datagrama encapsulado que não tem a garantia que vai chegar ao seu destino, ou seja, não é confiável para operações críticas ou que necessitem de alguma garantia de entrega dos dados, mas pode ser uma escolha viável por causa da sua velocidade, a não necessidade de manter um estado da conexão e algumas outras que quem está desenvolvendo algum programa para comunicação de rede vai conhecer e levar em conta.
Vamos escrever dois programas que nos permitem enviar e receber pacotes usando esse protocolo. Primeiro, o código do servidor:
No método send o argumento 0 é uma flag que pode usar uma combinação T> de constantes (utilizando um or binário das constantes presentes em Socket::MSG.
O SMTP é um protocolo para o envio de emails, baseado em texto. Há uma classe SMTP pronta para o uso em Ruby:
Na linha 3 requisitamos o módulo highline, que nos permite “mascarar”a digitação da senha na linha 6.
Para abrir uma conexão com o Gmail, como demonstrado, temos que indicar na conta do Gmail que é permitido o acesso por aplicações “inseguras”, que não usam a autenticação em dois fatores do Gmail.
O FTP é um protocolo para a transmissão de arquivos. Vamos requisitar um arquivo em um servidor FTP:
Para “fechar o pacote” de e-mail, temos a classe POP3, que lida com o protocolo POP3, que é utilizado para receber emails. Troque o servidor, usuário e senha para os adequados no código seguinte:
O HTTP é talvez o mais famoso dos protocolos, pois, apesar dos outros serem bastante utilizados, esse é o que dá mais as caras nos navegadores por aí, quando acessamos vários site. É só dar uma olhada na barra de endereço do navegador que sempre vai ter um http:// (ou https://, como vamos ver daqui a pouco) por lá.
Vamos utilizar o protocolo para ler o conteúdo de um site (o meu, nesse caso) e procurar alguns elementos HTML H1 (com certeza o conteúdo vai estar diferente quando você rodar isso):
Abrir um fluxo HTTP é muito fácil, mas dá para ficar mais fácil ainda! Vamos usar o OpenURI, que abre HTTP, HTTPS e FTP, o que vai nos dar resultados similares ao acima:
Podemos melhorar o código usando um parser para selecionar os elementos. Lembrando que já utilizamos a Nokokiri para XML, podemos utilizar também para HTTP:
Reparem que agora veio mais um H1, que é justamente o único elemento desse tipo que tem um atributo class. A Nokogiri já trata esse tipo de coisas, que não temos na expressão regular que utilizamos anteriormente.
O HTTPS é o primo mais seguro do HTTP. Sempre o utilizamos quando precisamos de uma conexão segura onde podem ser enviados dados sigilosos como senhas, dados de cartões de crédito e coisas do tipo que, se caírem nas mãos de uma turma por aí que gosta de fazer coisas erradas, vai nos dar algumas belas dores de cabeça depois.
O SSH é ao mesmo tempo um programa e um protocolo, que podemos utilizar para estabelecer conexões seguras e criptografadas com outro computador. É um telnet super-vitaminado, com várias vantagens que só eram desconhecidas (e devem continuar) por um gerente de uma grande empresa que prestei serviço, que acreditava que o bom mesmo era telnet ou FTP, e SSH era … “inseguro”. Sério! O duro que esse tipo de coisa, infelizmente, é comum entre pessoas em cargo de liderança em tecnologia por aí, e dá para arrumar umas boas discussões inúteis por causa disso. Mas essa é outra história …
Vamos começar a trabalhar com o SSH e abrir uma conexão e executar alguns comandos. Para isso precisamos da gem net-ssh:
E agora vamos rodar um programa similar ao seguinte, onde você deve alterar o host, usuário e senha para algum que você tenha acesso:
Podemos nos comunicar diretamente com o sistema operacional, executando comandos e recuperando as respostas.
O uso dos backticks fazem um fork do processo atual, executando o comando em um novo processo, criando uma operação bloqueante, esperando o comando terminar e o resultado é passado para o processo atual, podendo ser armazenado em uma variável. Se ocorrer algum erro no comando, esse erro é convertido em uma exceção:
O retorno é booleano ou nulo, com true indicando que o comando foi bem sucedido, false se não foi bem sucedido e nil indicando um erro na execução.
Utilizar exec substitui o processo atual pelo processo executando o comando. Então, se estivermos no irb e utilizarmos exec, vamos sair do irb e ir para o processo com o comando sendo executado, então muito cuidado com isso:
Roda o comando em um processo novo e retorna os fluxos de entrada e saída conectados à um objeto IO:
É o que dá controle mais granular para os fluxos de IO envolvidos. Vamos imaginar que temos o seguinte shell script para ler um nome digitado e mostrar o resultado na tela:
Agora queremos interagir com o script, conseguindo enviar alguma coisa para o fluxo de entrada (STDIN), ler do fluxo de saída (STDOUT) e do fluxo de erros (STDERR). Podemos utilizar o módulo Open3 para isso:
Enviamos a String “taq” para o fluxo de entrada (STDIN), que estava esperando ser digitado algum nome.
É uma especificação e um conjunto de implementações que permitem á softwares rodando em sistemas operacionais diferentes, rodando em diferentes ambientes, fazerem chamadas de procedures pela internet.
A chamada de procedures remotas é feita usando HTTP como transporte e XML como o encoding. XML-RPC é desenhada para ser o mais simples possível, permitindo estruturas de dados completas serem transmitidas, processadas e retornadas.
Tentando dar uma resumida, você pode escrever métodos em várias linguagens rodando em vários sistemas operacionais e acessar esses métodos através de várias linguagens e vários sistemas operacionais.
Antes de mais nada, vamos criar um servidor que vai responder as nossas requisições, fazendo algumas operações matemáticas básicas, que serão adição e divisão:
Agora vamos fazer um cliente para testar (você pode usar qualquer outra linguagem que suporte RPC que desejar):
Vamos instalar JRuby para dar uma olhada em como integrar Ruby com Java, usando a RVM. Antes de mais nada, pedimos para ver as notas da RVM e procurar as instruções para instalar JRuby:
Precisamos inserir as classes do JRuby no CLASSPATH do Java. Teste as duas opções abaixo, se você estiver em um SO que suporte o comando locate, a primeira é bem mais rápida, do contrário, use a segunda.
Pudemos ver que criamos a classe Alistener com a interface, no caso aqui com um comportamento de módulo, java.awt.event.ActionListener, ou seja, JRuby nos permite utilizar interfaces do Java como se fossem módulos de Ruby! E tem mais, podemos fazer com que nossas classes em Ruby herdem de classes do Java, primeiro, escrevendo o arquivo Carro.java:
Existe um jeito de fazer isso, mas vão por mim: não compensa pois vocês vão xingar muito o Java. Para maiores referências, podem consultar o site oficial de scripting para Java em http://java.net/projects/scripting/.
Vamos utilizar uma interface uniforme para acesso aos mais diversos bancos de dados suportados em Ruby através da interface Sequel19. Para instalá-la, é só utilizar a gem sequel:
Também vamos instalar a gem sqlite3, que nos dá suporte ao banco de dados auto-contido, sem servidor, com configuração zero e relacional (quanta coisa!) SQLite, que vai nos permitir testar rapidamente os recursos da Sequel sem precisar ficar configurando um banco de dados, já que o banco é criado em um arquivo simples no diretório corrente.
Pelo amor do ET de Varginha, não vão utilizar o SQLite para produção em alguma aplicação! É somente para pequenos bancos.
Para dar uma encurtada no código e praticidade maior, vamos usar um bloco logo após conectar, para onde vai ser enviado o handle da conexão:
Dica Para trocar o banco de dados, podemos alterar apenas o método de conexão, ou seja, o sqlite ali após Sequel.
Apesar de ter criado a coluna id como auto-incremento, estou especificando os valores dela para efeitos didáticos.
Aqui vamos utilizar o método where para selecionar o registro com o id que queremos atualizar, e o método update para fazer a atualização:
Vamos recuperar alguns dados do nosso banco, afinal, essa é a operação mais costumeira, certo? Para isso, vamos ver duas maneiras. Primeiro, da maneira “convencional”:
Agora vamos consultar registro por registro usando comandos preparados com argumentos variáveis, o que vai nos dar resultados similares mas muito mais velocidade quando executando a mesma consulta SQL trocando apenas os argumentos que variam:
Agora vamos ver uma forma de mostrar que é possível utilizar o “motorzão” ORM do Rails sem o Rails, vamos ver como criar e usar um modelo da nossa tabela alunos, já atendendo à uma pequena requisição do ActiveRecord, que pede uma coluna chamada id como chave primária, o que já temos:
Se rodarmos novamente, vamos verificar que o registro foi alterado, quando rodamos o programa anteriormente:
Vamos criar um módulo novo chamado Curso com uma classe chamada Horario dentro dele, que vai nos permitir cadastrar uma descrição da instância do objeto no momento em que o criarmos, e vai retornar a data e a hora correntes em dois métodos distintos.
Que uso prático isso teria não sei, mas vamos relevar isso em função do exemplo didático do código apresentado. ;-)
A primeira coisa que temos que fazer é criar um arquivo chamado extconf.rb, que vai usar o módulo mkmf para criar um Makefile que irá compilar os arquivos da nossa extensão:
Vamos assumir essa sequência de código como a nossa base para fazer extensões, somente trocando o nome da extensão na variável extension_name.
Agora vamos escrever o fonte em C da nossa extensão, como diria Jack, O Estripador, “por partes”. Crie um arquivo chamado curso.c com o seguinte conteúdo:
Opa! Já temos algumas coisas definidas ali! Agora temos que criar um Makefile20 para compilarmos nossa extensão. O bom que ele é gerado automaticamente a partir do nosso arquivo extconf.rb:
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 definimos para a extensão, com a extensão apropriada. Vamos fazer um teste no irb para ver se tudo correu bem:
Legal, já temos nosso primeiro módulo e classe vindos diretamente do C! Vamos criar agora o método construtor, alterando nosso código fonte C:
Foi feita uma tentativa de criar um objeto novo sem passar argumento algum no construtor, mas ele estava esperando um parâmetro, definido com o número 1 no final de rb_define_method.
Reparem como utilizamos rb_iv_set (algo como Ruby Instance Variable Set) para criar uma variável de instância com o argumento enviado. Mas a variável de instância continua sem um método para ler o seu valor, presa no objeto:
Agora para fazer uma graça vamos definir dois métodos que retornam a data e a hora corrente, como Strings. A parte mais complicada é pegar e formatar isso em C. Convém prestar atenção no modo que é alocada uma String nova usando rb_str_new2.
Apesar dos 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 prática na maior parte dos casos.
Tudo funcionando perfeitamente! Para maiores informações de como criar extensões para Ruby, uma boa fonte de consultas é http://www.rubycentral.com/pickaxe/ext_ruby.html.
Vamos supor que precisamos fazer uma integração do nosso código Ruby com alguma lib externa, já pronta. Para isso temos que dar um jeito de acessar as funções dessa lib de dentro do nosso código Ruby. Aproveitando o código que vimos acima para recuperar a hora, vamos fazer uma pequena lib, chamada libhora que faz isso na função hora.
Para desencargo de consciência, vamos fazer código em C para utilizar essa lib, para o caso de acontecer algum problema e isolarmos direto em C para não achar que a causa é a integração com Ruby. Primeiro o arquivo header:
Para rodar o programa para testar, temos que indicar onde encontrar a lib compartilhada (que foi feito na compilação ali acima utilizando -L$(pwd)):
Agora vamos utilizar essa lib dentro do nosso código Ruby. Para isso, vamos utilizar o módulo fiddle, com o seguinte programa:
Temos que adequar as requisições para as referências e chamadas de funções para o número e tipo correto de valores que vamos enviar e receber. Para mais informações de como fazer isso na documentação do Fiddle.
Vamos aproveitar que estamos falando de coisa de um nível mais baixo (não, não é de política) e vamos investigar como funciona o garbage collector do Ruby. Várias linguagens modernas tem um garbage collector, que é quem recolhe objetos desnecessários e limpa a memória para nós. Isso evita que precisemos alocar memória sempre que criar um objeto e libera-lá após a sua utilização. Quem programa em C conhece bem malloc e free, não é mesmo? E ainda mais os famigerados null pointer assigments.
Em Ruby, o garbage collector é do tipo mark-and-sweep, que atua em fases separadas onde marca os objetos que não são mais necessários e depois os limpa. Vamos ver fazendo um teste prático de criar alguns objetos, invalidar algum, chamar o garbage collector e verificar os objetos novamente:
Na Fase 3, o algoritmo foi acionado, parando o programa e marcando (mark) os objetos que estão acessíveis.
Na Fase 4 foi executada a limpeza (sweep) dos objetos não-acessíveis, e retirado o flag dos que estavam acessíveis (deixando-os em preto novamente), forçando a sua verificação na próxima vez que o garbage collector rodar.
Não custa ver como uma linguagem com alocação e limpeza automática de memória quebra nosso galho. Considerem esse código:
Oh-oh. Como não houve alocação de memória, a chamada a free disparou uma mensagem de erro. Comentando a linha 10, onde se encontra free:
Aparentemente sem problemas, não é mesmo? Só que copiar uma String para um ponteiro de memória não inicializado pode nos dar algumas dores de cabeça …
Mas temos que aprender a verificar se um simples programa como esse tem alguma falha. Para isso, podemos utilizar o Valgrind21, que é uma ferramenta ótima para esse tipo de coisa. Vamos executar o comando valgrind pedindo para verificar memory leaks no nosso pequeno programa, no estado em que está:
Não vamos entrar a fundo no uso do Valgrind, mas isso significa que nosso programa tem um problema. Vamos tentar remover o comentário da linha 10, onde está free, compilar e rodar o comando valgrind novamente:
Ainda não deu certo, e vamos voltar no comportamento já visto de erro do programa na hora em que executarmos ele. Vamos remover agora o comentário da linha 7, onde está malloc, e rodar novamente o valgrind:
Agora temos certeza de que está tudo ok! O Valgrind é uma ferramenta muito poderosa que quebra altos galhos.
Para termos um retorno exato do Valgrind de onde está o nosso problema, compilem o programa utilizando a opção -g, que vai inserir informações de debugging no executável. Se comentarmos novamente a linha 7, onde está malloc, vamos ter o seguinte resultado do valgrind quando compilarmos e executarmos ele novamente:
Reparem que agora ele já dedurou que o problema está na linha 8 (null.c:8), onde está sendo copiado um valor para uma variável não alocada.
Apesar de mostrar e chorar as pitangas sobre malloc e free acima (ah vá, vocês gostaram das dicas em C), nem toda String em Ruby (pelo menos nas versões 1.9.x para cima) são alocadas com malloc, diretamente no heap. Esses são os casos das chamadas “Strings de heap **”. Existem também as “Strings compartilhadas**”, que são Strings que apontam para outras, ou seja, quando utilizamos algo como str2 = str1, e vão apontar para o mesmo local.
Mas tem outro tipo de Strings. As com até 11 caracteres em máquinas 32 bits e 23 caracteres em máquinas 64 bits, são consideradas “Strings embutidas”, e tem, na estrutura interna de Ruby, um array de caracteres desses tamanhos respectivos já alocado, para onde a String é copiada direto, sem precisar da utilização de malloc e free, consequentemente, aumentando a velocidade. O nosso programa acima seria algo como:
Fica até mais simples, mas a sequência de caracteres fica “engessada” nos 15 caracteres. As Strings que ultrapassam esses limites são automaticamente criadas ou promovidas para Strings de heap, ou seja, usam malloc/free. Se você ficou curioso com os limites, pode compilar (compilado aqui com o GCC em um GNU/Linux) e rodar esse programa:
Se repararmos na primeira union definida, podemos ver que é ali que é gerenciado se vai ser utilizada uma String de heap ou embutida. Lembrem-se (ou saibam) que unions em C permitem que sejam armazenados vários tipos dentro dela, mas permite acesso a apenas um deles por vez. Esse programa aqui vai produzir um efeito indesejado, pois é atribuído um valor no primeiro membro e logo após no segundo membro, que sobreescreve o valor do primeiro, deixando ele totalmente maluco no caso da conversão para um int:
Se você for usar Rails e não aprender a usar os recursos de testes do framework, que já vem todo estruturado, estará relegando um ganho de produtividade muito grande.
Testes unitários são meios de testar e depurar pequenas partes do seu código, para verificar se não tem alguma coisa errada acontecendo, “modularizando” a checagem de erros. Um sistema é feito de várias “camadas” ou “módulos”, e os testes unitários tem que ser rodados nessas camadas.
Vamos usar de exemplo uma calculadora que só tem soma e subtração, então vamos fazer uma classe para ela, no arquivo calc.rb:
Deixei um método da calculada, media, sem testes agora de propósito, para criarmos um teste mais tarde, mas sempre testem todos os métodos do objeto.
Temos o método setup, que é o “construtor” do teste, e vai ser chamado para todos os testes, não somente uma vez.
Temos o método teardown, que é o “destrutor” do teste, e vai liberar os recursos alocados através do setup.
Temos as asserções, que esperam que o seu tipo combine com o primeiro argumento, executando o teste especificado no segundo argumento, usando o terceiro argumento como uma mensagem de ajuda se por acaso o teste der errado.
Para demonstrar uma falha, faça o seu código de subtração da classe Calculadora ficar meio maluco, por exemplo, retornando o resultado mais 1, e rode os testes novamente:
Reparem em --seed 45816. Ali é indicado que os testes são executados em ordem randômica, prevenindo a sua suíte de testes de ser executada dependente da ordem dos testes, o que ajuda a previnir algo chamado de “state leakage” (“vazamento de estado”) entre os testes. Os testes tem que ser executados independente de sua ordem, e para isso o Minitest gera uma seed randômica para a execução dos testes. Se precisarmos executar os testes novamente com a mesma seed, já que ela vai ser alterada a cada vez que executamos os testes, podemos utilizar:
Agora já mudou bastante! Podemos usar alguns atalhos como let, ao invés do método before, que é um método lazy e só executa o bloco quando é invocado:
Temos um sistema básico e fácil para utilizar mocks, onde podemos simular o comportamento de um objeto complexo, ainda não acessível ou construído ou impossível de ser incorporado no teste. Um mock é recomendado se 22:
Gera resultados não deterministicos (ou seja, que exibem diferentes comportamentos cada vez que são executados)
Teriam que adicionar informações e métodos exclusivamente para os testes (e não para sua função real)
Existem algumas gems para utilizarmos mocks, como a Mocha (https://github.com/freerange/mocha), que tem vários recursos interessantes, mas com o Minitest grande parte do que precisamos já está pronto.
Agora vamos utilizar o método chamado media, que vai receber e calcular a média de uma coleção e utilizar um Mock para simular um objeto de coleção (apesar que poderia facilmente ser um Array). Para isso, vamos ver agora o teste, mostrando somente o método que utiliza o Mock:
“Falsificamos” um objeto, com um método chamado valores, que retorna um Array de 3 Fixnum's: [1,2,3]. A instrução ali é algo como “ei, quando o método valores for acionado em colecao, retorne aquele Array que indicamos”.
Também podemos ter stubs, que podem ser utilizados como substitutos temporários de métodos que demorem muito para executar, consumam muito processamento, etc. No caso dos Stubs do Minitest, eles duram dentro e enquanto durar o bloco que foram definidos:
Esse exemplo foi para efeitos puramente didáticos - e inúteis, do ponto de vista de uma calculadora que iria retornar um valor totalmente inválido - mas serve para mostrar como podemos fazer uso de stubs.
Algumas das expectations do Minitest. Para testarmos uma condição inversa, na maioria das vezes é só trocar must para wont, por exemplo, must_be por wont_be:
must_be_within_delta(exp,act,delta,msg) - Compara Floats, verificando se o valor de exp tem uma diferença de no máximo delta de act, comparando se delta é maior que o o valor absoluto de exp-act (delta>(exp-act).abs):
Já deixando claro que existe uma pequena grande diferença entre kind_of? (tipo de) e instance_of? (instância de). Deêm uma olhada nesse código:
Nada mais chato do que ficar rodando os testes manualmente após alterarmos algum conteúdo. Para evitar isso, temos algumas ferramentas como o Guard, que automatizam esse processo. Podemos instalar as seguintes gems para utilizar Guard e Minitest:
Criar um diretório chamado spec (viram ele referenciado ali em cima?) com arquivos chamados *_spec.rb (também viram a máscara *_spec.rb ali?), copiar o arquivo calc_spec3.rb para spec/calc_spec.rb e finalmente rodar o comando guard:
Os testes encontrados vão ser avaliados sempre que algum arquivo com a extensão .rb no diretório corrente ou algum arquivo com o nome *_spec.rb for alterado. Note que a configuração do Guardfile procura saber qual é o teste para ser rodado através do nome do arquivo .rb modificado, inserindo _spec no final do nome dele.
Podemos criar gems facilmente, desde escrevendo os arquivos de configuração “na unha”, até utilizando a gem bundle, que provavelmente já se encontra instalada no sistema.
Vamos construir uma gem para “aportuguesar”os métodos even? e odd?, traduzindo-os respectivamente para par? e impar?. Para criar a nova gem, chamada portnum, podemos digitar o comando abaixo e responder algumas questões que nos são apresentadas da maneira que acharmos melhor (ficando a recomendação de responder minitest quando perguntado sobre testes):
Esse comando gera a seguinte estrutura de diretório/arquivos, inclusive já dentro de um repositório do Git:
Esse é o código que vai ser carregado quando a gem for requisitada. Vamos alterar a classe Numeric nesse arquivo (lib/portnum.rb), para implementar os nossos dois métodos:
Podemos publicar a gem facilmente para o RubyGems.org, que é o repositório oficial de gems para Ruby. Primeiro temos que criar uma conta lá, e indo em https://rubygems.org/profile/edit e salvar a nossa chave da API para um arquivo YAML em ~/.gem/credentials:
Podemos extrair o código (com toda a estrutura de diretórios) contido em uma gem utilizando o comando gem com a opção unpack:
Em razão de um problema de comprometimento do RubyGems em Janeiro de 2013, os autores de gems foram instruídos a assinarem as suas gems usando um certificado auto-assinado baseado em RSA, de forma que quando instaladas ou atualizadas, as gems possam ter a sua integridade verificada.
Para criar o seu certificado, digite o seguinte comando, trocando <seu_email> para o email que deseja que esteja associado ao certificado, digitando a senha do certificado (não se esqueça dela!) duas vezes:
Uma grande diferença entre esses arquivos é que o private tem que ficar bem guardado em segredo, sem divulgar ou entregar para alguém, para evitar que alguém se faça passar por você, enquanto o public pode e deve ser publicado para que as pessoas possam conferir a assinatura da gem que usa esse certificado, no velho esquema de chaves públicas e privadas.
Vamos pegar como exemplo uma gem que mantenho, a Traquitana. Para indicar que ela vai utilizar o meu certificado, vou inserir as seguintes linhas no final do arquivo traquitana.gemspec:
Isso vai indicar que o arquivo private vai ser utilizado para assinar a gem, e o arquivo public vai ser utilizado para conferir a assinatura. Podemos publicar nosso arquivo public em algum lugar na web, mas vamos facilitar e distruibuí-lo junto com o código da nossa gem. Para isso, vá para o diretório onde está o arquivo .gemspec da gem (no caso acima, o traquitana.gemspec) e copie o arquivo public do seu diretório ~/.gemcert (ou de onde você armazenou os arquivos):
Agora podemos construir a gem assinada, utilizando o rake build como vimos acima, com a diferença que agora vai ser solicitada a senha utilizada na criação do certificado:
Podemos também utilizar rake release para fazer o processo completo, como demonstrado um pouco antes, sem problemas.
Com a gem assinada e publicada, agora podemos instalá-la ou atualizá-la pedindo para que seja verificada no momento da operação selecionada. Para isso, vamos precisar importar os certificados públicos disponibilizados pelos desenvolvedores das gems e utilizar a opção -P HighSecurity. Se, por exemplo, eu tentar atualizar a gem em um computador que não tem o certificado importado, não conseguindo verificar a integridade da gem, vai acontecer isso:
Não foi retornado nada aqui, então vamos importar o certificado disponibilizado com a gem, que nesse caso, se encontra disponível em https://raw.githubusercontent.com/taq/traquitana/master/gem-public_cert.pem, de onde vamos fazer download para um arquivo local, importar o certificado e logo depois apagar o arquivo local:
Para utilizar a verificação com o Bundler, podemos utilizar a opção --trust-policy HighSecurity, que funciona da mesma forma demonstrada acima. Por exemplo:
Vimos no capítulo anterior uma ferramenta poderosíssima que utilizamos com bastante frequência no ecossistema Ruby: o rake. O rake foi inspirado no make, que é utilizado com frequência para automatizar tarefas, especialmente para compilar e gerar programas executáveis no mundo Unix. Sorte nossa que o rake é bem mais descomplicado e prático que o make, onde a geração de um Makefile mais completo (e complexo) demanda a utilização de outras ferramentas como o automake.
Definir uma tarefa no rake é bem fácil. Primeiro, vamos precisar de um arquivo Rakefile (primo do Makefile), uma descrição e a definição da tarefa. Para o nosso exemplo, vamos fazer algumas tarefas para listar, criar o zip, e extrair os arquivos, mas utilizando os utilitários do sistema operacional (e não os meios que aprendemos em um capítulo anterior, para simplificar e focar aqui somente no rake).
Vamos criar os arquivos texto 1.txt, 2.txt e 3.txt, com qualquer conteúdo, somente para utilizarmos novamente nesse capítulo:
Ops, criamos uma task chamada list mas não especificamos qual seria a task default se rodarmos o rake sem uma task específica. Podemos indicar qual a task default utilizando task default: <task>:
A partir desse momento, já podemos listar quais são as tarefas definidas no Rakefile do diretório corrente, utilizando rake -T:
Agora vamos imaginar que essa task list, como vimos aqui, lista os arquivos candidatos à compactação (que nesse caso, são apenas os arquivos *.txt que temos no diretório corrente), mas queremos também listar somente os arquivos já compactados, ou seja, os arquivos .zip presentes no diretório corrente. Seria outra task list, mas como evitar que uma task conflite com a outra? Da mesma forma que resolvemos isso com classes, utilizando namespaces:
Uma diferença importante se não tivéssemos utilizado namespaces ali é que se definirmos outra tarefa com o mesmo nome de uma existente, elas não se sobrepõem, e sim a última é adicionada como uma continuação da anterior. Então, fiquem de olho nisso e organizem o seu código.
Vamos fazer uma tarefa agora para compactar os arquivos, apagando o arquivo .zip anterior se ele existir, definida na tarefa clean:
Até agora estamos executando todas as tarefas em um arquivo Rakefile, porém podemos ter vários arquivos .rake com código especificos, indicados na linha de comando, como por exemplo, dependent.rake:
Podemos definir uma task de arquivo, que somente vai ser executada se o arquivo não existir. Vamos criar uma chamada rake.zip, que vai executar, através de invoke, como vimos acima, a task build:
Vimos que utilizamos Dir.glob para pegar a lista de arquivos, mas o próprio rake tem um método para selecionar e lidar com arquivos e nome de arquivos. Vamos adicionar alguns arquivos *.txt com nomes de letras (a.txt, b.txt, etc) e reescrever nosso Rakefile para:
Dessa forma pedimos para excluir os arquivos que começam com letras (e mantenha os restantes) e quando rodamos temos:
Podemos ter regras de construção definidas através de expressões regulares, onde vai ser enviado o valor que “casa” com a expressão, através de um objeto do tipo Rake::FileTask:
Lembram-se que se definirmos uma tarefa com o mesmo nome todas elas são executadas? Também podemos deixar esse comportamento mais explícito com enhance, que vai ser executado no final da tarefa que foi estendida:
Vamos ver como podemos documentar o nosso código utilizando o rdoc, que é uma aplicação que gera documentação para um ou vários arquivos com código fonte em Ruby, interpretando o código e extraindo as definições de classes, módulos e métodos. Vamos fazer um arquivo com um pouco de código, usando nossos exemplos de carros:
Isso vai produzir um diretório chamado doc abaixo do diretório atual, que vai conter um arquivo index.html com um conteúdo como esse:
Pudemos ver algumas convenções para escrever a documentação. Os comentários são utilizados como as descrições das classes, módulos ou métodos. Podemos reparar que, se clicarmos no nome de algum método, o código-fonte desse método é mostrado logo abaixo, como em:
Um detalhe muito importante é que se precisarmos gerar a documentação novamente sem alterar os fontes, devemos apagar o diretório onde ela foi gerada antes de rodar o rdoc novamente.
Texto do tipo labeled lists, que são listas com o suas descrições alinhadas, como no caso do autor e da licença do exemplo, são criados utilizando o valor e logo em seguida 2 dois pontos (::), seguido da descrição.
Hyperlinks começando com http:, mailto:, ftp: e www são automaticamente convertidos. Também podemos usar o formato texto[url].
Nomes de classes, arquivos de código fonte, e métodos tem links criados do texto dos comentários para a sua descrição.
O processamento dos comentários podem ser interrompido utilizando – e retornado utilizando ++. Isso é muito útil para comentários que não devem aparecer na documentação.
Vamos ver nosso exemplo incrementado com todas essas opções e mais um arquivo novo, uma classe filha de Carro chamada Fusca, separando os dois arquivos em um diretório para não misturar com o restante do nosso código:
E podemos deixar no diretório do projeto em um arquivo chamado .doc_options, para não ter que repetir toda vez, utilizando
A atribuição em paralelo mostra que primeiro o lado direito da expressão de atribuição é avaliado (ou seja, tudo à direita do sinal de igual) e somente após isso, os resultados são enviados para a esquerda, “encaixando” nos devidos locais, dessa maneira:
Cada elemento da Hash é convertido em um Array para ser comparado. Por isso que podemos utilizar algo como elemento1[1], onde no caso do primeiro elemento, vai ser convertido em [:joao, 33].
Isso significa que v3 não vai apresentar a mensagem pois um Fixnum não aloca espaço na memória, que consequentemente não é processado pelo garbage collector.
O código que utilizou threads manteve a sincronia da variável res, indicando no final a ordem em que foram terminando. O código que utilizou processes, não.
Ou seja, estamos enviando um inteiro (Fixnum) (L), seguido de uma String com tamanho 10 (A10), seguido de uma String sem tamanho definido (A*), assumindo o resto dos bytes, que é o resultado do uso de Marshal na Hash. Mais informações na URL da documentação de unpack
Aqui foi utilizado alguns recursos de shell scripting. O arquivo necessário é chamado jruby.jar, e está gravado em algum lugar abaixo do diretório home do usuário (que podemos abreviar como ~, no meu caso toda vez que utilizo ~ é entendido como /home/taq/), então utilizamos find ~ -iname 'jruby.jar' para encontrá-lo.
Como esse comando está contido entre $(), o seu resultado já é automaticamente inserido no local, deixando a CLASSPATH como o path encontrado, o diretório local e o que já havia nela.