A idéia deste post não é ser um compendio universal de conhecimento sobre Ruby, bem longe disto inclusive, a idéia é ter apenas uma pequena introdução, apresentar a vocês alguns conceitos básicos, para que aqueles que já programam em alguma outra linguagem consigam brincar um pouco com Ruby, e principalmente para que consigam entender o código dos posts sobre Ruby, Rails e Cucumber do blog, então espero que aproveitem, mesmo não sendo a melhor introdução a linguagem Ruby, espero que ajude.
Uma linguagem dinâmica, open source com foco na simplicidade e na produtividade. Tem uma sintaxe elegante de leitura natural e fácil escrita.
A frase acima foi retirada do site da linguagem de programação Ruby, o endereço para quem quiser conhecer mais e/ou baixar os binários e instalar caso você esteja em uma maquina windows como eu, é: http://www.ruby-lang.org.
No início dessa jornada pelo Ruby, vamos utilizar o irb, sigla em inglês para “Interactive Ruby” (Ruby Interativo). Esse utilitário é muito usado para testar códigos antes de passá-los para o arquivo de destino, permitindo um teste mais rápido e, portanto, uma correção mais rápida dos possíveis problemas. Abra um terminal e digite irb. Você verá um prompt como este:
1 | irb(main):001:0> |
Agora, você está pronto para começar a utilizar o Ruby como uma calculadora!
Lembre-se de que todos os comandos mostrados nas próximas páginas podem ser testados neste aplicativo. Então, vamos aprender um pouco sobre o irb antes de continuar.
Se você digitar 1+1 e pressionar Enter, na próxima linha vai aparecer:
1 | => 2 |
e o prompt muda para:
1 | irb(main):002:0> |
Ou seja, os números entre os dois pontos são correspondentes à linha do comando, como se fosse a linha do arquivo na edição de um arquivo .rb (extensão padrão para programas Ruby). Também podemos concluir que toda e qualquer expressão em Ruby tem um valor de retorno, que é mostrado no irb, no caso o => 2, que é o resultado da soma realizada.
O último número é o nível do bloco atual.
No Ruby, tudo é um objeto, incluindo números. O que fizemos neste exemplo foi enviar a mensagem + para o objeto 1, com o argumento 1, e essa mensagem retorna um resultado que é a soma desses números, no caso 2. Esse conceito de enviar mensagens para objetos vai ser muito importante mais adiante.
Todos os códigos apresentados, você podera escrever linha a linha no IRB para ver o que acontece a cada linha, para facilitar a escrita do tutorial e a posterior atualização do mesmo, vou colocar todos os códigos em arquivos .rb, para executar estes arquivos, salve o arquivo e execute: ruby
Vamos começar com o básico do básico, como definir um método no ruby, na verdade não estamos definindo um método, estamos registrando um endereço para que o carteiro interno do ruby possa entregar mensagens a um objeto, estamos definindo os nomes das mensagens que este objeto pode receber, mas para fazer isto, utiliza-se a palagra “def” como no exemplo a seguir:
intro/01soma.rb
1 2 3 4 5 | def soma a, b a + b end puts soma 1, 2 |
No Ruby não é necessário utilizar return ou qualquer palavra-chave para definir o retorno de um método. O retorno do método é o valor da última expressão executada, em nosso caso, a+b.
Os parênteses são opcionais em quase todas as situações. Por exemplo, poderíamos reescrever o exemplo anterior da seguinte forma:
intro/02soma.rb
1 2 3 4 5 | def soma(a, b) a + b end puts soma(1, 2) |
Como podemos verificar no exemplo, além dos parênteses, o “;” no final das sentenças também é opcional, tornando a sintaxe bastante flexível.
No Ruby, tudo é um objeto, e todo objeto pertence a uma classe. Todas as classes descendem de Object, e é possível verificar em tempo de execução qual o tipo do objeto, como pode ser visto a seguir:
intro/03objects.rb
1 2 3 4 5 6 7 | puts 1.class puts self.class puts [].class puts ({}.class) puts "a".class puts 1.1.class puts 99999999999999999999.class |
A palavra-chave self é utilizada para identificar o objeto atual, o número 1 é do tipo Fixnum, o número 1.1 é do tipo Float e um número muito grande é do tipo Bignum. Não é necessário se preocupar se um número é Fixnum ou Bignum, pois o Ruby vai cuidar das conversões automaticamente.
Ok, mas em que saber isso pode ser útil? Fácil: a classe Fixnum, por consequência, a instância dessa classe 1, tem diversos métodos úteis para trabalhar com números, e graças ao irb, não precisamos decorar todos os métodos.
intro/04methods.rb
1 | puts 1.methods.sort |
A classe Object tem o método methods, que retorna um Array contendo todos os métodos disponíveis para aquele objeto, neste caso, o número 1.
Como podemos verificar pelo resultado do método methods, as operações matemáticas também são métodos da classe Fixnum, como o +, *, -, / e assim por diante.
O Ruby tem um recurso bastante interessante, que o torna uma linguagem muito flexível. Esse recurso se chama “open classes”, ou seja, todas as classes do Ruby estão sempre abertas, o que possibilita que elas sejam alteradas a qualquer momento. Isso é bastante útil, mas também bastante perigoso. Para demonstrar o perigo que isso representa em mãos incautas, faremos uma pequena brincadeira:
intro/05openclass.rb
1 2 3 4 5 6 | class Fixnum def +(other) self - other end end puts 1 + 5 |
O que acabamos de fazer foi alterar o método + da classe Fixnum, o que pode causar uma grande confusão, como visto no exemplo, fazendo com que o método + realize, na verdade, uma operação de subtração. No entanto, esse recurso, quando utilizado corretamente, pode ajudar bastante no desenvolvimento de aplicações, como veremos mais adiante.
No Ruby, não é necessário declarar uma variável: ela será definida no momento em que tiver um valor atribuído. Para que isso seja possível, o Ruby utiliza tipagem dinâmica, ou seja, ele define o tipo de uma variável por seu valor, mas isso não quer dizer que seja uma linguagem de tipagem fraca, pois não é possível somar um número com uma string, como pode ser visto no seguinte código-fonte:
intro/06strongtype.rb
1 | puts 1 + "2" |
Se você executar este código, recebera um erro, informando que não é possível misturar variáveis de tipos diferentes.
O Ruby não tem palavras-chave para definir o escopo de variáveis: isso é feito por meio de símbolos, como na lista a seguir:
| Símbolo | Descrição |
|---|---|
| nome | Variável local. |
| @nome | Variável de instância. |
| @@nome | Variável de classe. |
| $nome | Variável global. |
Blocos de código consistem em um dos recursos mais versáteis e flexíveis do Ruby. São utilizados para iterar em coleções, personalizar o comportamento de algum método e definir “Domain Specific Languages”. Para quase tudo são usados blocos de código, até para criar formulários para páginas da web no Rails e definir boa parte das configurações do framework.
Existem duas sintaxes diferentes para definir um bloco de código. Pode-se fazer isso utilizando os símbolos { e } ou utilizando as palavras-chave do e end.
Blocos de código podem receber parâmetros. Para definir a lista de parâmetros, logo depois de aberto o bloco de código é utilizado o símbolo | para demarcar o início e o fim da lista de parâmetros.
O Ruby tem suporte a closures reais. Isso quer dizer que, se em um bloco de código forem utilizadas variáveis visíveis no contexto da criação do bloco, qualquer alteração nessas variáveis vai ser refletida no contexto original, ou seja, os blocos de código têm um link com o contexto de origem, o que os torna mais úteis ainda e os diferencia muito de ponteiros para métodos do C/C++, por exemplo.
intro/07codeblock.rb
1 2 3 4 | arr = [1, 2, 3, 4] arr.each { |val| print "#{val}\n" } |
Esse exemplo utiliza um bloco de código simples, delimitado por chaves para iterar nos valores de um array. Veja que o valor do array não foi alterado pelo retorno da expressão.
intro/08codeblock.rb
1 2 3 4 | arr = [1, 2, 3, 4] arr.each_with_index do |val, idx| print "Posicao #{idx} valor #{val}\n" end |
Esse segundo exemplo faz praticamente a mesma coisa, mas utiliza outro método de iteração, que passa além do valor. O índice fornece o valor no array e, dessa vez, são utilizados os delimitadores do e end.
intro/09closure.rb
1 2 3 4 5 6 | arr = [1, 2, 3, 4] valor = 1 arr.each do |val| valor += val end puts valor |
Já nesse exemplo é utilizada uma closure, em vez de um bloco de código simples. A diferença básica é que a closure mantém o contexto de onde ela é chamada, neste caso, alterando a variável local valor. Podemos ver no próximo exemplo que, se for utilizado um método para realizar essa iteração, a variável valor não vai existir, o que vai causar um erro que prova que o bloco aponta para seu contexto de origem.
intro/15scope.rb
1 2 3 4 5 6 7 8 9 10 | valor = 1 def iterar arr = [1, 2, 3, 4] arr.each do |val| valor += val end end iterar |
Esse exemplo também demonstra uma das convenções para a nomenclatura de variáveis apresentada no exemplo anterior, ou seja, variáveis que não têm seus nomes iniciados por @, @@ ou $ são variáveis locais no contexto onde foram definidas.
É possível também passar blocos de código como parâmetros para métodos. Isso é muito usado na API padrão do Ruby, e pode ser visto inclusive no exemplo anterior, quando passamos um bloco de código como parâmetro para o método each da classe Array. No próximo exemplo, veremos como criar um método que recebe um bloco de código como parâmetro, ou seja, como utilizar um bloco de código passado para um método. Não será mostrado como armazenar um bloco de código em uma variável, porque nesse caso, o bloco de código se transforma em uma instância da classe Proc e esse é um assunto para o próximo capítulo.
intro/16blockparam.rb
1 2 3 4 5 6 7 8 9 10 | def recebe_proc_e_passa_parametro if block_given? yield else puts "você precisa passar um bloco para este método\n" end end recebe_proc_e_passa_parametro recebe_proc_e_passa_parametro { print "dentro do bloco\n" } |
Podemos também passar parâmetros para blocos recebidos nos métodos:
intro/17blockparam.rb
1 2 3 4 5 6 7 8 9 10 11 12 | #encoding : UTF-8 def recebe_proc_e_passa_parametro if block_given? yield(23) else puts "você precisa passar um bloco para este método\n" end end recebe_proc_e_passa_parametro do |par| puts "Recebi #{par} dentro deste bloco\n" end |
Procs são muito parecidos com blocos de código ou closures simples, que foram vistos na seção anterior. A diferença básica é que podemos armazenar um proc em uma variável, e isso torna um pouco mais “caro” para o Ruby a implementação de procs do que simples blocos de código. Por isso, tiveram a brilhante ideia de, internamente no Ruby, utilizar dois elementos diferentes para representar essas duas entidades que, para o programador usuário da linguagem, são praticamente iguais.
Além disso, o Ruby converte blocos de código em procs de forma transparente, fazendo com que na maioria das situações não seja necessário se preocupar com esse tipo de detalhe. Para deixar isso um pouco mais claro veremos alguns exemplos a seguir:
Converter um bloco recebido em um proc.
intro/18proc.rb
1 2 3 4 5 6 7 8 9 | def recebe_proc(&block) if block block.call end end recebe_proc recebe_proc { print "este bloco vai se tornar uma proc pois vai ser atribuído a uma variável no método" } |
Criar uma variável do tipo Proc de duas formas diferentes.
intro/19proc.rb
1 2 | p = Proc.new { print "este bloco vai se tornar uma proc pois está sendo atribuído a uma variável\n" } p.call |
intro/20lambda.rb
1 2 3 4 | p1 = lambda do print "este bloco vai se tornar uma proc pois está sendo atribuído a uma variável\n" end p1.call |
Nestes exemplos, foi possível verificar as duas formas de criar um objeto do tipo Proc: utilizando o construtor da classe Proc (leia mais sobre construtores na seção sobre classes) ou utilizando a palavra-chave lambda, de modo semelhante ao Python.
Como Proc é uma classe, objetos desse tipo têm diversos métodos. Vejamos alguns destes de forma mais detalhada:
intro/21procclass.rb
1 2 | p = Proc.new { print "a" } puts p.methods.sort |
Diversos métodos padrão da classe Object do Ruby são listados, pois como todas as classes, a classe Proc também é descendente de Object, ou seja, procs são objetos comuns no Ruby. Retomando o tópico anterior, existem alguns métodos que são mais interessantes para nós neste momento:
| Método | Descrição |
|---|---|
| call | Utilizado para executar o proc, os parâmetros que forem definidos no bloco são passados como parâmetros para o método call. |
| [] | Alias para o método call, ou seja, podemos executar um proc com a sintaxe: p[parâmetros]. |
| arity | Informa o número de parâmetros definidos nesse proc. |
| binding | Retorna a Binding correspondente ao local onde foi definido o bloco de código que deu origem a esse proc. |
Como em todas as linguagens de programação, o Ruby tem suporte a processamento numérico, mas diferente de muitas das linguagens, mesmo algumas das supostamente orientadas a objetos, no Ruby todos os números são objetos, portanto, todos são instâncias de alguma classe.
O Ruby tem três classes que representam números. São elas:
| Classe | Descrição |
|---|---|
| Fixnum | Representa inteiros de -1073741824 a 1073741823. |
| Bignum | Representa inteiros fora do intervalo da classe Fixnum. |
| Float | Representa números de ponto flutuante. |
Para definir um número de ponto flutuante basta utilizar um ponto “.”. Se você quiser muito separar milhares, pode utilizar o _, mas não recomendo a utilização, pois o visual da sintaxe fica poluído.
A seguir estão alguns exemplos de números no Ruby.
intro/22numberandclass.rb
1 2 3 4 5 6 7 8 9 10 11 12 | def number_and_class(n) puts "#{n} -> #{n.class}" end i =1 i1 = 1.1 i2 =111_222_333 i3 =999999999999999999 number_and_class i number_and_class i1 number_and_class i2 number_and_class i3 |
Como já foi dito, o Ruby utiliza duas classes diferentes para tratar inteiros. Não é necessário se preocupar com isso, pois a conversão entre esses tipos é feita automaticamente pelo interpretador, ou seja, se um número ficar grande demais para caber em um Fixnum, ele será convertido para Bignum. Caso ele volte a estar no intervalo do Fixnum, essa classe será utilizada novamente.
Valores booleanos, como na maior parte das linguagens de programação, são representados por true e false, ou verdadeiro e falso, respectivamente. Entretanto, no Ruby existe uma particularidade: a palavra-chave nil, que representa “nenhum objeto”, é considerada como falsa em qualquer comparação. Portanto, existem dois valores para falso, e quaisquer outros valores serão considerados como verdadeiros. Resumindo, as comparações booleanas servem também para verificar se uma variável tem um valor.
Veja alguns exemplos que exemplificam melhor o que você acabou de ler:
intro/23boolean.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #encoding: UTF-8 def testa_valor(val) if val print "#{val} é considerado verdadeiro pelo Ruby\n" else print "#{val} é considerado falso pelo Ruby\n" end end testa_valor true testa_valor false testa_valor 1 testa_valor "asda" testa_valor nil |
Strings são o tipo de dados utilizados para representar texto dentro do código. O Ruby tem dois tipos de string, que são diferentes apenas durante sua declaração. Um dos tipos tem expansão de variáveis e suporta caracteres especiais, como os do C, por exemplo, \n para representar uma quebra de linha.
Além desses dois tipos de strings, o Ruby tem diversas formas de declarar strings no código:
| Símbolo | Descrição |
|---|---|
| aspas | String simples com expansão de variáveis. |
| apóstrofes | String simples sem expansão de variáveis. |
| < |
String multilinha com expansão de variáveis. |
| %Q{ } | String multilinha com expansão de variáveis. |
| %q{ } | String multilinha sem expansão de variáveis. |
Veja alguns exemplos de declaração de strings no Ruby:
intro/24strings.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | a = "texto" b = 'texto' c = "texto\nsegunda linha" d = 'texto\nmesma linha' e = "a = #{a} - é assim que se utiliza expansão de variáveis" f = <<__ATE_O_FINAL esta é uma String bem grande e só tesmina quando encontrar o marcador __ATE_O_FINAL no início de uma linha __ATE_O_FINAL g = %Q{Esta também é uma String com mais de uma linha e também suporta #{a} expansão de variáveis } h = %q{Já esta que também é multi linha não suporta #{a} expansão de variáveis} puts a puts b puts c puts d puts e puts f puts g puts h |
Como podemos ver no exemplo anterior, as strings do Ruby suportam um recurso bastante interessante chamado expansão de variáveis, que, na verdade, suporta execução de código Ruby dentro de uma String. Para utilizar isso, basta colocar o código a ser executado dentro dos símbolos #{ e }. O código será executado, e o resultado da expressão será incluído dentro do texto original.
Isso é bastante útil ao se trabalhar com output de texto, seja para exibição aos usuários, seja para criação de arquivos.
O Ruby não tem algo que seja realmente constante, mas a linguagem tem um padrão que diz que variáveis declaradas com a primeira letra maiúscula são constantes. A linguagem não o impede de alterar o valor dessa constante se você realmente quiser: o Ruby vai apenas imprimir um aviso dizendo que você realmente não deveria estar fazendo isso, como mostra o exemplo a seguir:
intro/25constants.rb
1 2 3 4 | CONSTANTE = "asda" CONSTANTE = 1 Constante = 2 Constante = 5 |
Além de números simples, é possível declarar intervalos numéricos no Ruby. O Ruby tem dois tipos de intervalos numéricos: o inclusivo, que inclui o último número, e o exclusivo, que não inclui esse número. Intervalos inclusivos e exclusivos são declarados utilizando os símbolos “..” e “…”, respectivamente, como pode ser visto no exemplo a seguir:
intro/26numericintervals.rb
1 2 3 4 5 6 7 8 9 | a = 1..10 b = 1...10 a.each do |v| print "#{v} " end print "\n" b.each do |v| print "#{v} " end |
Arrays são coleções de valores. No Ruby, arrays não são tipados, ou seja, um array pode conter objetos de diversos tipos. Existem duas formas de se declarar um array genérico e uma forma extra de se declarar um array contendo apenas strings, como podemos ver no exemplo a seguir:
intro/27arrays.rb
1 2 3 4 5 | arr = [] arr = ['a', 1] arr = Array.new arr = %w{ a b c } puts arr.methods.sort |
Junto com os hashes, arrays são as estruturas de dados mais utilizadas na linguagem Ruby, principalmente pela flexibilidade e facilidade de iteração pelo conteúdo de um array. Como vimos nos outros exemplos, é bastante fácil percorrer um array utilizando o método each, ou each_with_index. Além desses, os arrays têm diversos outros métodos que serão muito úteis durante o desenvolvimento das aplicações Rails. Alguns desses estão na lista a seguir:
| Método | Descrição |
|---|---|
| select | Recebe um bloco e retorna um novo array contendo todos os elementos para os quais o bloco retornou true. |
| []= | Define o valor de uma posição no array. |
| [] | Retorna o valor da posição passada como parâmetro. |
| last | Retorna o último item do array. |
| empty? | Retorna verdadeiro se o array estiver vazio. |
| equal? | Compara com outro array. |
| each_index | Recebe um bloco e passa apenas os índices do array para o bloco. |
| sort | Retorna um novo array contendo os itens deste ordenados. Opcionalmente recebe um bloco com dois parâmetros, o qual deve retornar qual dos itens é menor, fazendo assim uma comparação personalizada. |
| sort! | Similar ao sort, mas altera o array de origem. |
| + | Soma dois arrays, criando um novo com os itens de ambos. |
| - | Subtrai dois arrays, criando um novo com os itens do primeiro não contidos no segundo. |
| push | Adiciona um item no final do array. |
| pop | Retorna o último item do array e o remove do array. |
| find | Recebe um bloco com um parâmetro e retorna o primeiro item para o qual o bloco retornar verdadeiro. |
| clear | Remove todos os itens do array. |
| shift | Retorna o primeiro item do array e o remove do array. |
| first | Retorna o primeiro item do array. |
| inject | Recebe um valor inicial e um bloco com dois parâmetros. O primeiro é o valor atual, e o segundo, o item atual do array, retorna o resultado da operação realizada no bloco. O próximo exemplo utiliza o método inject para somar todos os itens de um array numérico. |
intro/28inject.rb
1 2 3 4 5 | arr = [1, 2, 3, 4, 5, 6] v = arr.inject(0) do |val, it| val + it end puts v |
Existe ainda mais uma forma de criar um array no Ruby, que serve para criar métodos com uma lista variável de parâmetros. Para isso, basta declarar o último parâmetro de um método com o nome *nome, de forma que esse parâmetro seja um array contendo todos os parâmetros não declarados passados ao método, como pode ser visto no exemplo a seguir.
intro/29varargs.rb
1 2 3 4 5 6 7 8 9 10 11 | def parametros_variaveis(*arr) if arr arr.each do |v| print "#{v.class} - #{v}\n" end end end parametros_variaveis parametros_variaveis 1, "asda", :simb, :a => "teste", :arr => %w{a b c} |
Hashes representam outra construção bastante utilizada no código Ruby. Utilizaremos muitos hashes no código Rails que será escrito nos próximos capítulos.
Hashes são semelhantes a arrays, mas não são simples coleções de objetos: são coleções do tipo chave=valor. Eles são tão comuns no código Ruby que existe até um operador especial para declarar hashes, que é o =>, que significa associado, ou seja, o valor à esquerda está associado ao valor à direita.
Existem duas formas de se declarar um hash. A primeira é utilizando o atalho {}, e a segunda, utilizando o construtor da classe Hash, como no exemplo a seguir:
intro/30hashes.rb
1 2 3 4 | h = {1 => "asda", "b" => 123} h1 = {} h2 = Hash.new puts h.methods.sort |
Hashes também têm alguns métodos que podem facilitar muito a vida dos programadores Ruby como pode ser visto na tabela abaixo.
| Método | Descrição |
| [] | Retorna o valor da chave passada como parâmetro. |
| []= | Atribui o valor da chave. |
| each | Executa um bloco com dois argumentos para cada posição do mapa. |
| each_key | Executa um bloco com um argumento (a chave) para cada posição do mapa. |
| each_value | Executa um bloco com um argumento (o valor) para cada posição do mapa. |
| has_key? | Retorna verdadeiro se a chave existe no mapa. |
| has_value? | Retorna verdadeiro se o valor corresponde a alguma das chaves do mapa. |
| default= | Possibilita configurar qual valor o mapa vai retornar quando for buscado o valor para uma chave inexistente. |
| default_proc | Idem a default=, mas executa um bloco para criar o valor para as novas chaves. |
| delete | Remove o item correspondente à chave indicada do mapa, retornando o valor da chave. |
Símbolos, no Ruby, são aqueles nomes malucos que começam com “:” por todo o código. São muito utilizados como chaves em hashes e em quaisquer lugares onde você precisar de um rótulo (label) para alguma coisa. Eles são basicamente strings, mas ocupam muito menos processamento do interpretador Ruby e muito menos memória do que strings normais.
Strings podem ser transformadas em símbolos utilizando o método to_sym.
Sempre que for definir chaves em hashes que servirão como nomes de parâmetros a métodos, ou precisar enviar uma mensagem a um objeto, um símbolo é sempre melhor do que uma string, pois utilizará muito menos memória, o que fará com que sua aplicação gaste menos recursos e se comporte de maneira mais sociável com o computador.
Expressões regulares consistem na forma mais fácil de extrair informações de um texto ou alterar textos com padrões razoavelmente complexos.
O Ruby tem duas formas de declarar uma expressão regular e diversos métodos que recebem expressões regulares como parâmetros.
Uma coisa a ser lembrada, e que facilita muito a vida dos programadores Ruby, é que expressões regulares fazem parte da linguagem no Ruby. Isso facilita muito seu uso em comparação às linguagens em que expressões regulares são implementadas em forma de uma biblioteca externa.
As três formas de declarar uma expressão regular no Ruby são /ER/, %r{ER} ou por meio do método new da classe Regexp, como no exemplo a seguir:
intro/31regexp.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | er = /(.*?) .*/ er = %r{(.*?) .*} er = Regexp.new "(.*?) .*" er = /^[0-9]/ puts "123" =~ er puts er =~ "123" puts er =~ "abc" puts er !~ "123" puts er !~ "abc" mt = /(..)\/(..)\/(....)/.match("12/05/2000") puts mt.length puts mt[0] puts mt[1] puts mt[2] puts mt[3] todo, dia, mes, ano = *(/(..)\/(..)\/(....)/.match("12/05/2000")) puts todo puts dia puts mes puts ano puts "Urubatan".gsub(/ru/, "RU") re = /.*/ puts re.methods.sort |
Toda expressão regular é uma instância da classe Regexp e, da mesma forma que os números, a classe Regexp disponibiliza diversos métodos e operadores para facilitar as operações com expressões regulares, como podemos ver no exemplo a seguir.
Alguns métodos importantes disponíveis na classe Regexp são:
| Método | Descrição |
|---|---|
| =~ | Procura pela expressão regular no texto e retorna o índice em que ela foi encontrada. |
| !~ | Informa se existe uma ocorrência da expressão regular no texto. |
| match | Retorna um objeto do tipo MatchData, que contém ponteiros para os locais onde cada grupo da expressão regular foi encontrado. |
O operador * do Ruby utilizado no exemplo, quando usado em um array, expande o array em variáveis. Dessa forma pode-se atribuir um array a uma quantidade de variáveis igual às posições do array.
O método gsub da classe String demonstrado faz substituição de texto utilizando expressões regulares. É possível utilizar grupos na substituição, tornando o método ainda mais flexível.
Classes representam uma das bases da orientação a objetos no Ruby. Tudo no Ruby é um objeto, e todo objeto no Ruby é instância de uma classe. Por exemplo, 1 é uma instância da classe Fixnum, e todos os métodos dessa classe podem ser chamados nessa instância.
Assim, classes também são objetos no Ruby, portanto, podem ter métodos próprios, ou seja, é possível definir métodos de classe, diferentemente do Java ou do C++, que têm métodos estáticos. Métodos de classe são herdados por classes descendentes da classe onde foram definidos e podem saber a qual objeto pertencem, pois nessa situação a palavra-chave self vai apontar para a classe e não para uma de suas instâncias.
Para definir uma classe, é usada a palavra-chave class, seguida por um nome de uma constante, que será utilizado para referenciar aquela classe.
Variáveis de instância são definidas por meio da nomenclatura @nome e variáveis de classe @@nome, mas se o seu código-fonte contiver muitas variáveis de classe, há um sério problema de design.
Vejamos alguns exemplos para facilitar o entendimento:
intro/32class.rb
1 2 3 4 5 6 7 8 9 10 11 | class Carro def initialize(fabricante, modelo, ano) @fabricante = fabricante @modelo = modelo @ano = ano end attr_accessor :fabricante, :modelo, :ano end clio = Carro.new "Renault", "clio", "2000" puts clio.modelo |
Herança é um dos pilares da orientação a objetos (os outros dois são encapsulamento e polimorfismo). O Ruby suporta herança utilizando o operador < na definição de uma classe.
intro/33inheritance.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 | require '32class' class Clio < Carro @@fabricante = "Renault" @@modelo = "clio" def initialize(ano) super(@@fabricante, @@modelo, ano) end end clio = Clio.new(2003) puts clio.modelo |
Métodos de classe são bastante úteis no Ruby, pois podem saber a que classe pertencem. Para simplificar o exemplo, podemos também utilizá-los para criar uma fábrica de carros:
intro/34classmethod.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 | require '33inheritance' class Fabrica def self.clio Clio.new(2003) end def self.megane Carro.new "Renault", "megane", 2003 end end puts Fabrica.clio.inspect puts Fabrica.megane.inspect |
Como foi possível perceber, para criar um método de classe é usada a notação self.método. É possível definir novos métodos em classes a qualquer momento, bem como definir novos métodos apenas em uma instância de uma classe, o que internamente vai fazer com que uma nova classe anônima seja criada apenas para aquele objeto.
Classes têm alguns métodos interessantes para entendermos o código Rails que será usado nos próximos capítulos. A seguir, veja uma breve lista.
| Método | Descrição |
|---|---|
| class | Retorna a classe de um objeto. |
| class_eval | Executa uma string contendo código-fonte Ruby no contexto da classe. |
| class_variable_defined? | Informa se uma variável está definida nessa classe. |
| class_variables | Lista todas as variáveis de classe. |
| const_defined? | Informa se existe uma constante definida na classe. |
| const_get | Lê o valor de uma constante. |
| const_set | Grava o valor em uma constante ou cria uma nova. |
| constants | Lista todas as constantes definidas na classe. |
| instance_eval | Executa uma string contendo código-fonte Ruby no contexto de uma instância da classe. |
| instance_methods | Lista todos os métodos de instância da classe. |
| instance_variable_defined? | Informa se uma variável está definida para as instâncias da classe. |
| instance_variable_get | Lê o valor de uma variável de instância. |
| instance_variable_set | Cria ou altera o valor de uma variável de instância. |
Uma coisa interessante sobre métodos no Ruby é que eles não existem.
É exatamente isso: não há métodos em Ruby! A diferença é bastante sutil, mas ajuda a entender melhor como as coisas funcionam. No Ruby não se chama um método de objeto: envia-se uma mensagem para um objeto, e esta pode ter parâmetros, mas sempre tem um retorno.
É como se cada objeto tivesse uma caixa de correio interna, que só aceita mensagens para destinatários conhecidos, e todos os destinatários desconhecidos vão para o mesmo lugar: uma caixa com o nome de method_missing.
Isso permite que métodos inexistentes sejam adicionados apenas no momento em que se tornam necessários. Veremos isso acontecer bastante no código do Rails, cuja leitura recomendo após a conclusão deste livro, pois a melhor forma de entender profundamente como o Rails funciona é lendo o código-fonte do Rails.
Aqui está um exemplo de utilização do method_missing:
intro/35method_missing.rb
1 2 3 4 5 6 7 8 | class Teste def method_missing(method, *args) print "Método #{method} chamado na classe Teste, com os argumentos #{args.join(', ')}\n" end end t = Teste.new t.imprimir t.qualquer_coisa 1, 2, 3, "asd", :teste => 1 |
Não é considerada uma boa prática utilizar o method_missing o tempo todo, principalmente porque, no exemplo mostrado, se fosse feita a pergunta t.respond_to? :imprimir, o objeto diria que não responde à mensagem, mas ela seria enviada. Tudo funciona sem problemas, o que cria um objeto com comportamento inconsistente.
Uma melhor abordagem seria a utilização do define_method para criar uma caixa de correspondências, no momento em que esta se tornar necessária, da forma como o attr_accessor faz. Para demonstrar isso, criaremos um método que define propriedades em um objeto, semelhante ao attr_accessor. Para tal, vamos utilizar o módulo que é assunto da próxima seção.
intro/36define_method.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | module Propriedades def propriedade(nome) ivarname = "@#{nome}".to_sym self.send :define_method, nome do self.instance_variable_get ivarname end self.send :define_method, "#{nome}=".to_sym do |val| self.instance_variable_set ivarname, val end end end class Teste extend Propriedades propriedade :nome end t = Teste.new t.nome="urubatan" print t.inspect print "\n" |
Módulos no Ruby são repositórios de coisas. Essa foi a melhor explicação de módulos que consegui, falando de uma forma bem genérica.
Eles podem conter classes, sendo usados como pacotes de classes para organizar um domínio muito grande. Ou podem conter métodos para serem utilizados como “mixins”, um conceito bastante interessante que, utilizado junto com as classes abertas, é parcialmente responsável por toda a flexibilidade do Ruby.
Para utilizar módulos como organizadores de classes, basta fazer como neste exemplo:
intro/37modules.rb
1 2 3 4 5 6 7 8 9 | module Administracao class Cliente attr_accessor :nome def initialize @nome = "" end end end |
Utilizar métodos como mixins é bastante semelhante ao exemplo anterior, mas dentro do módulo são definidos métodos, e não classes, como no exemplo a seguir.
intro/38modulemixin.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | class Teste def ola_mundo print "ola mundo\n" end def self.ola_mundo print "ola mundo da classe\n" end end Teste.ola_mundo t = Teste.new t.ola_mundo module MixinTest def self.included(base) base.send :include, InstanceMethods base.send :extend, ClassMethods end module ClassMethods def metodo_de_classe print "Novo método de classe definido no módulo 'ClassMethods'\n" end end module InstanceMethods def metodo_de_instancia print "Novo método de instância definido no módulo 'InstanceMethods'\n" end end end class Teste include MixinTest end Teste.metodo_de_classe t.metodo_de_instancia |
Em qualquer classe, pode-se chamar o método include passando um módulo como parâmetro, e os métodos desse módulo estarão disponíveis para todas as instâncias dessa classe.
Se o método extend for utilizado, os métodos do módulo estarão disponíveis para a classe e não para suas instâncias.
O método send utilizado no exemplo envia uma mensagem para um objeto. No caso, o objeto era a classe Teste.
Ruby é uma linguagem dinâmica, mas também imperativa, e todas as linguagens imperativas têm estruturas de controle de fluxo e loop. Nas próximas seções veremos quais estruturas desse tipo o Ruby contém e como utilizá-las.
A estrutura if é utilizada para executar um conjunto de instruções. Se a condição for verdadeira, não é necessário um then, e o if pode ser utilizado no final de uma instrução também. Dessa forma, o bloco anterior de código só será executado se a condição for verdadeira.
intro/39ifelse.rb
1 2 3 4 5 6 7 8 9 10 | a = 0 if a==0 print "zero" elsif a==1 print "um" else print "não sei que número é este" end b = 5 if a!=1 puts b |
O unless é um atalho, mais fácil de ler para um “if not” em inglês. Ele facilita a leitura do código quando utilizado corretamente, mas semanticamente é um “if not” e pode ser utilizado também no final de uma sentença da mesma forma que o if.
intro/40unlesslseend.rb
1 2 3 4 5 6 7 8 9 10 | a = 1 unless a==0 print "não é zero\n" else print "a é zero\n" end b = 6 unless b puts b b = 7 unless b puts b |
Como pode ser visto no exemplo, o unless pode ser utilizado para definir o valor de uma variável apenas se ela ainda não tiver um valor. É necessário apenas tomar cuidado com essa abordagem se o valor esperado é um valor booleano.
Case é um atalho mais organizado e semântico para uma sequência de elsif. O comando case do Ruby é bastante flexível, mais do que em Java ou C++, por exemplo. Isso porque no Ruby ele pode ser utilizado com qualquer tipo de objeto e não apenas com números. Só não é possível misturar objetos, como pode ser visto no exemplo a seguir.
intro/41casewhenelse.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | a = 5 case a when 1..3 puts "a entre 1 e 3\n" when 4 puts "a=4\n" else puts "nenhuma das anteriores\n" end a = "b" case a when "a" puts "a\n" when "b" puts "b" else puts "outra letra" end |
Os operadores apresentados nesta seção podem ser utilizados com qualquer um dos loops que serão apresentados nas próximas seções:
| Operador | Descrição |
|---|---|
| break | Sai do loop atual. |
| next | Executa o próximo passo do loop. |
| return | Sai do loop e do método atual. |
| redo | Reinicia o loop atual. |
O while é um loop bastante flexível, pois permite o controle da condição do loop, podendo ser utilizado com qualquer condição booleana, derivada da comparação de qualquer tipo de objeto.
intro/42while.rb
1 2 3 4 | i = %w{a b c d e f} while b=i.pop puts b end |
No exemplo, o while foi utilizado para iterar sobre um array de strings. Pense nisso apenas como um exemplo, pois neste caso o mais indicado seria utilizar o método each da classe Array.
O laço for é utilizado para repetir um bloco de código por um número conhecido de vezes. Utilize-o apenas quando for realmente necessário, pois o modo padrão do Ruby de iterar sobre coleções é empregando os métodos apropriados, como o each, por exemplo.
intro/43for.rb
1 2 3 4 5 6 | for i in 1..5 puts i end for a in %w{a b c d} puts a end |
O bloco until é o contrário do while: ele repete o bloco de código até que a condição seja verdadeira.
intro/44until.rb
1 2 3 4 5 | i=5 until i==0 puts i i -=1 end |
O bloco begin é utilizado em conjunto com o while ou until quando se deseja que o bloco seja executado pelo menos uma vez. Assim, a condição fica no final do bloco e não no início, como nos exemplos anteriores.
intro/45begin.rb
1 2 3 4 5 | i=-5 begin puts i i+=1 end while i < 0 |
Nesse exemplo, se um while padrão fosse utilizado, o bloco não teria sido executado nenhuma vez.
O laço loop é o laço mais flexível. Ele será executado até que encontre um comando break ou return no bloco.
intro/46loop.rb
1 2 3 4 | loop do puts "a" break if true end |
É muito importante quando se aprende uma linguagem nova não tentar programar na linguagem anterior com a sintaxe da linguagem nova. Para isso, é importante aprender alguns padrões bastante utilizados no Ruby. Há uma pequena lista de coisas que devem ser lembradas a seguir.
Nomes de arquivos utilizam letras minúsculas e sublinhado para separar palavras. Um arquivo .rb que contém a definição de uma classe de nome ClienteEspecial terá o nome cliente_especial.rb.
Um módulo segue o padrão de nomenclatura de classes e, na maioria, dos casos também define a estrutura de diretórios. Seguindo o exemplo, se a classe ClienteEspecial estiver definida dentro do módulo Clientes, o arquivo cliente_especial.rb estará dentro do diretório clientes.
O Ruby não impõe esses padrões. É possível definir todas as classes da aplicação no mesmo arquivo se desejado, mas a maior parte das aplicações Ruby segue padrões parecidos com os descritos.
Não é obrigatório o nome de uma classe começar com uma letra maiúscula, mas é bastante recomendado, pois dessa forma também se define uma constante que apontará para a classe, facilitando seu uso.
Algumas vezes é necessário criar métodos de acesso para variáveis de instância da classe. O próprio attr_accessor faz exatamente isso.
Métodos de leitura de uma variável de instância têm o mesmo nome da variável, sem o caractere @ no início, e métodos de escrita têm o mesmo nome terminado em =.
intro/47standards.rb
1 2 3 4 5 6 | class Teste attr_accessor :nome end t = Teste.new puts t.methods.sort |
Nomes de métodos no Ruby têm sempre todas as letras minúsculas e utilizam sublinhado “_” para separar palavras.
Métodos que transformam um objeto em outro tem o nome iniciado por to_, como, por exemplo:
| Método | Descrição |
|---|---|
| to_s | Transforma em String. |
| to_i | Transforma em Fixnum. |
| to_a | Transforma em Array. |
| to_sym | Transforma em um símbolo. |
Lembre-se disso ao criar esse tipo de método.
E para quem quiser mais informações, acredito qe todos os que leram até aqui, a página de documentação do site da linguagem tem bastante coisa: http://www.ruby-lang.org/pt/documentacao/.
Tags: basico, introdução, lcucumber, programação, Ruby, tutorial
Excelente post, é uma ótima introdução à linguagem, resumindo as principais características.
Só uma pequena correção: no bloco de código 33 (intro/33inheritance.rb) os atributos “fabricante” e “modelo” não deveriam ter somente um “@”? Afinal, são atributos da instância.
[Translate]
Na verdade são atributos da classe, que são utilizados como valor padrão na criação de novas instâncias
estes atributos de classe, que na realidade deveriam ser constantes, mas achei que atributos na classe resolveriam para este exemplo
A idéia é diminuir a quantidade de parâmetros necessários no construtor, Carro precisa de 3, Clio precisa de apenas 1
[Translate]
Ah, é verdade, faz sentido. Nesse caso o fabricante e o modelo são fixos na classe.
[Translate]
Resolvi aposentar o php e escolhi ruby on rails para seguir adiante com meus trabalhos. Esse post foi excelente para ter uma noção sobre ruby e parece muito com python. Obrigado por essas dicas e tomara que eu tenha feito a escolha certa, pois ao meu ver… irei ganhar em tempo e produtividade com essa troca.
[Translate]
Esta fazendo uma boa escolha de aprender Ruby, mas quanto a aposentar o PHP já não tenho tanta certeza
Nenhuma linguagem ou framework é bom para todas as situações, pode ser que encontre uma situação que o PHP resolva melhor que o rails …
[Translate]