Micro-Tutorial de Ruby – Parte III

dezembro 18, 2008 by admin · Leave a Comment
Filed under: Programação, Ruby on Rails 

Tipos Básicos

Continuando com os tipos básicos de Ruby (no artigo anterior já falamos de Arrays e Hashes):

Strings

Novamente, nada de muito surpreendente aqui:

>> palavra = "bla bla"
=> "bla bla"
>> palavra.size
=> 7

A operação mais comum em string é a concatenação:

>> nome = "Diego"
=> "Diego"
>> sobrenome = "Moreira"
=> "Moreira"
>> nome + " " + sobrenome
=> "Diego Moreira"

Porém, já vimos em seções anteriores outra maneira
de fazer a mesma concatenação:

>> "#{nome} #{sobrenome}"
=> "Diego Moreira"

Tudo que estiver dentro de “#{}” é executado e o resultado convertido
em String e concatenado junto com o resto. Isso deve evitar aquele
monte de “+” o tempo todo quando queremos concatenar as coisas. Um dos
lugares onde mais usamos concatenação é quando queremos strings com
quebras de linha. O jeito comum é fazer assim:

>> nome = "F"
=> "F"
>> query = "SELECT * FROM NOMES \n" +
?>     "WHERE NOME LIKE '%" + nome + "%' \n" +
?>     "ORDER BY NOME"
=> "SELECT * FROM NOMES \nWHERE NOME LIKE '%F%' \nORDER BY NOME"

Ou algo parecido com isso, o que é bem feio e sabemos o quanto isso se
torna impossível de dar manutenção no futuro. Mas em Ruby podemos fazer
um pouco melhor que isso:

>> nome = "F"
=> "F"
>> query = <<-STR
  SELECT * FROM NOMES
  WHERE NOME LIKE '%#{nome}%'
  ORDER BY NOME
STR
=> "  SELECT * FROM NOMES\n  WHERE NOME LIKE '%F%'\n  ORDER BY NOME\n"

Onde está “STR” na realidade pode ser
qualquer palavra em letras maiúsculas, tomando o cuidado para não ter
espaços em branco nem antes nem depois do “STR” da última linha. 

>> nome = "F"
=> "F"
>> query = %{SELECT * FROM NOMES
  WHERE NOME LIKE '%#{nome}%'
  ORDER BY NOME}
=> "SELECT * FROM NOMES\n  WHERE NOME LIKE '%F%'\n  ORDER BY NOME"

E também desta maneira:

>> nome = "F"
=> "F"
>> query = %Q(SELECT * FROM NOMES
  WHERE NOME LIKE '%#{nome}%'
  ORDER BY NOME)
=> "SELECT * FROM NOMES\n  WHERE NOME LIKE '%F%'\n  ORDER BY NOME"

Todas as maneiras acima são jeitos de se criar strings de múltiplas
linhas com a possibilidade de execução e substituição in-place usando
“#{}”. Eu particularmente prefiro fazer alguma coisa do tipo:

>> nome = "F"
=> "F"
>> query = %q(SELECT * FROM NOMES
  WHERE NOME LIKE '%[nome]%'
  ORDER BY NOME)
=> "SELECT * FROM NOMES\n  WHERE NOME LIKE '%[nome]%'\n  ORDER BY NOME"

>> query.gsub!('[nome]', nome)
=> "SELECT * FROM NOMES\n  WHERE NOME LIKE '%F%'\n  ORDER BY NOME"

A sintaxe de “%q()” também permite criar strings de múltiplas linhas mas não suporta a execução de código via “#{}”, por isso coloquei algo que possa encontrar depois (um “placeholder”) como “[nome]” e no final uso o método “gsub” que faz substituição em Strings. Eu disse que “prefiro” mais para separar explicitamente as substituições fora do String. Mas, novamente, existem diversas maneiras de realizar essa mesma tarefa, depende do que você precisa fazer.

Disclaimer: nunca crie consultas SQL da forma como mostrei acima. Se você apenas concatenar o valor que foi passado pelo usuário diretamente no comando SQL que vai executar, seu código estará automaticamente vulnerável à SQL Injection. Esse é o erro mais comum que todo desenvolvedor Web novato comete. Então cuidado! Para isso servem pacotes como ActiveRecord, que abstraem o SQL nativo e realizam as checagens mais comuns antes de criar o SQL.

Symbols

Note que na seção sobre Hashes, criamos um pequeno dicionário ligando uma palavra em inglês à sua tradução em português. Para isso usamos objetos String tanto para os valores quanto para as chaves, por exemplo:

dic1 = { "leg" => "perna" }

Mas você vai notar que em Ruby e principalmente no Rails, não costumamos
usar Strings como chaves.
Em vez disso usamos Symbols:

dic2 = { :leg => "perna" }

Quero dizer, não é proibido usar Strings, mas se não precisar, prefira usar Symbols. Existem dois motivos para isso: o primeiro é porque symbols são mais legíveis e fáceis de visualizar, a segunda é economia de memória.

Em poucas palavras, um Symbol sempre gera um objeto Singleton imutável. Por exemplo:

>> a = :leg
=> :leg
>> b = :leg
=> :leg
>> a.object_id
=> 531778
>> b.object_id
=> 531778

Note como atribuímos :leg às variáveis “a” e “b”. Chamando o método
“object_id”, ambas respondem o mesmo ID, denotando que as duas
variáveis estão apontando ao mesmo objeto Symbol. Vejamos o mesmo
exemplo usando Strings:

>> a = "leg"
=> "leg"
>> b = "leg"
=> "leg"
>> a.object_id
=> 11823830
>> b.object_id
=> 11820130

Note como os dois IDs vieram diferentes. Ou seja, apesar do conteúdo ser o mesmo, as variáveis “a” e “b” estão apontando para objetos String diferentes. A primeira impressão é que são o mesmo objeto, mas na realidade são dois objetos distintos que por acaso tem o mesmo conteúdo. Espero que tenha ficado claro porque Symbols consomem menos memória que Strings.

Existem vários outros objetos singleton em Ruby. Números é um deles, afinal não faz sentido existir mais de um número “1”. O mesmo vale para objetos booleanos. Por exemplo:

>> a = 1
=> 1
>> b = 1
=> 1
>> c = true
=> true
>> d = true
=> true

>> a.object_id == b.object_id
=> true
>> c.object_id == d.object_id
=> true
>> a = 1
=> 1
>> b = 1
=> 1
>> c = true
=> true
>> d = true
=> true

>> a.object_id == b.object_id
=> true
>> c.object_id == d.object_id
=> true
Na prática, sempre use Symbols como chaves de Hash. É onde eles são
mais usados. Em Ruby on Rails, o pacote ActiveSupport consegue traduzir
Hashes com chaves em String para Symbols, desta forma:

>> require 'rubygems'
=> []
>> require 'activesupport'
=> []
>> params = { "nome" => "Diego", "sobrenome" => "Moreira" }
=> {"nome"=>"Diego", "sobrenome"=>"Moreira"}
>> params.symbolize_keys!
=> {:nome=>"Diego", :sobrenome=>"Moreira"}
>> params[:nome]
=> "Diego"
>> params[:sobrenome]
=> "Moreira"

Métodos em Ruby

Depois de tudo isso, podemos ir um pouco além. Em Ruby temos maneiras muito diferentes de se escrever métodos. Em linguagens estáticas, como Java, temos o conceito de overloading. Por exemplo:

public class Pessoa {
  String nome;
  String email;

  public Pessoa(String nome, String email) {
    this.nome = nome;
    this.email = email;
  }

  public Pessoa(String nome) {
  this(nome, "");
  }
}

Esse trecho define a classe “Pessoa” com dois construtores.
Dessa forma podemos instanciar uma nova Pessoa da seguinte forma:
Pessoa diego = new Pessoa("Diego", "diego@foo.com.br");
Pessoa Moreira = new Pessoa("Moreira");

Ou seja, o parâmetro “email” é opcional. Dependendo se passamos ou não esse parâmetro, um dos dois construtores será chamado.

Porém, esse tipo de construção pode começar a ficar extremamente tedioso quando temos muitos parâmetros opcionais. Em Ruby temos algumas maneiras diferentes de lidar com isso. Para começar vejamos uma maneira simples:

class Pessoa
  def initialize(nome, email = "")
    @nome, @email = nome, email
  end
end

Duas novidades: primeiro que um método suporta valores padrão. Dessa forma, se nada for passado no segundo parâmetro, ele assume o padrão vazio “”, conforme descrevemos acima. Segundo, é possível fazer atribuição em massa. Acredito que esteja bastante óbvio entender apenas lendo o trecho acima.

Outra maneira:

class Pessoa
  def initialize(nome, *args)
    @nome = nome
    @args = args
  end
  def args
    @args
  end
end

>> diego = Pessoa.new("Diego", "diego@foo.com.br", "6666-6666")
=> #<Pessoa:0x26ee61c @args=["diego@foo.com.br", "6666-6666"], @nome="Diego">
>> diego.args.first
=> "diego@foo.com.br"
>> diego.args.last
=> "6666-6666"

O que temos no contrutor acima, o “*”, é o “splat”. Pense nele como um
buraco-negro: depois do parâmetro normal “nome”, tudo que vier depois
será literalmente sugado para dentro da variável “args”. A resultante
disso será um Array. Isso permite um método que tenha capacidade para
infinitos argumentos. Outro exemplo de uso é este:

def soma(*args)
  args.inject(0) { |elem, total| total += elem }
end

>> soma(2,2)
=> 4
>> soma(1,2,3,4)
=> 10
>> soma(100,200,300)
=> 600

Felizmente já explicamos para que serve o “inject”. Resumindo: esse método somará todos os elementos passados como parâmetros, independente de quantos forem. E ainda podemos fazer mais: passar um array expandindo seus elementos para serem parâmetros:

>> items = [1,2,3,4,5,6]
=> [1, 2, 3, 4, 5, 6]
>> soma *items
=> 21

Se passássemos o array “items” sem o splat para expandí-lo, o array
inteiro seria considerado um único elemento do array “args” dentro do
método, não dando o efeito esperado, veja:

>> soma items
TypeError: can't convert Fixnum into Array
  from (irb):31:in `+'
  from (irb):31:in `soma'
  from (irb):40:in `inject'
  from (irb):31:in `each'
  from (irb):31:in `inject'
  from (irb):31:in `soma'
  from (irb):40
Outra situação é quando temos métodos com um número muito grande de
parâmetros opcionais. No Ruby on Rails temos exemplos como este:

>> Person.find :first, :conditions => { :nome => "Diego" },  :o rder => :nome
=> SELECT * FROM "people" WHERE ("people"."nome" = 'Diego') ORDER BY nome LIMIT 1

Qualquer um que já tenha lidado com SQL sabe que montar consultas pode ser bastante complexo. Criar um único método que cuide disso numa linguagem estático é impossível. Se tentar criar um conjunto de métodos via overloading como vimos antes, também será um trabalho homérico e totalmente fútil.

Vejamos como é definido o método “find” da classe “ActiveRecord::Base”


def find(*args)
  options = args.extract_options!
  validate_find_options(options)
  set_readonly_option!(options)

  case args.first
    when :first then find_initial(options)
    when :last  then find_last(options)
    when :all   then find_every(options)
    else             find_from_ids(args, options)
  end
end

O método “find” por si só é somente um envelope que chama outros métodos. Ele recebe nada mais, nada menos que um Array, usando o splat como mostramos antes. Logo na primeira linha ele usa um método interno do Rails para extrair tudo que é considerado “options”, no caso, elementos de um Hash.

Dependendo do primeiro argumento, ele repassa o Hash a métodos como “find_initial”, “find_last”, etc. No caso, o Hash em questão é o seguinte:

options = { :conditions => { :nome => "Diego" },  :o rder => :nome }

Vejamos um exemplo mais simples de um método com um parâmetro
obrigatório e uma lista de parâmetros opcionais:
class Pessoa
  attr_accessor :primeiro_nome, :sobrenome, :iniciais, :email
  def initialize(primeiro_nome = "", options = {})
    @primeiro_nome = primeiro_nome
    @sobrenome = options[:sobrenome]
    @iniciais = options[:iniciais]
    @email = options[:email]
  end
end

>> diego = Pessoa.new("Diego", :sobrenome => "Moreira", :iniciais => "DM")
=> #<Pessoa:0x26b2c98 @iniciais="DM", @sobrenome="Moreira", @email=nil, @primeiro_nome="Diego">
>> diego.primeiro_nome
=> "Diego"
>> diego.sobrenome
=> "Moreira"
>> diego.iniciais
=> "DM"
>> diego.email
=> nil

Caso não saiba, o método “attr_accessor” serve para criar métodos
equivalentes a “getters” e “setters” de Java ou C#. Como já falamos em
seções anteriores, classes Ruby podem ser modificadas em tempo de
execução. Veremos isso depois, por enquanto vejamos o construtor
novamente. Depois do primeiro parâmetro, temos o familiar uso do par
chave e valor. O que pode parece estranho é que talvez alguém estivesse
esperando algo assim:

diego = Pessoa.new("Diego", {:sobrenome => "Moreira", :iniciais => "DM"})

Novamente, as chaves “{}” são opcionais: os últimos parâmetros, sendo
pares com a sintaxe de “rocket” (“=>”) são tratados como elementos
do mesmo Hash e todos são “sugados” para o parâmetro “options” no
método construtor. Retirando tudo que é opcional, a chamada poderia
ficar simplesmente assim:

diego = Pessoa.new "Diego", :sobrenome => "Moreira", :iniciais => "DM" 

E assim temos algumas maneiras para criar métodos bastante flexíveis.
Obviamente, aqui cabe cuidados sobre como planejar seus métodos. Fica
muito fácil criar métodos que fazem mais do que deveriam dessa forma.
Assim como mostramos acima com o método “find” tente criar métodos
enxutos, que delegam a execução a outros métodos mais especializados,
por exemplo.

Métodos Dinâmicos

Vejamos mais sobre o método “attr_accessor”:

class Pessoa
  attr_accessor :nome, :email
end

>> diego = Pessoa.new
=> #<Pessoa:0x26a6f24 @iniciais=nil, @sobrenome=nil, @email=nil, @primeiro_nome="">
>> diego.nome = "Diego"
=> "Diego"
>> diego.nome
=> "Diego"

Podemos reimplementar nossa própria versão simplificada de “attr_accessor” desta maneira:

class Object
  def my_accessor( *symbols )
    symbols.each do |symbol|
      module_eval( "def #{symbol}() @#{symbol}; end" )
      module_eval( "def #{symbol}=(val) @#{symbol} = val; end" )
    end
  end
end

class Pessoa
  my_accessor :telefone
end

>> diego = Pessoa.new
=> #<Pessoa:0x2690f30 @iniciais=nil, @sobrenome=nil, @email=nil, @primeiro_nome="">
>> diego.telefone = "6666-6666"
=> "6666-6666"
>> diego.telefone
=> "6666-6666"
Abrindo a classe Object significa que nosso método “my_accessor” estará
disponível a qualquer classe do Ruby, já que todos herdam de Object.
Esse método recebe um array de symbols, como já descrevemos antes. Daí
usamos “each” para iterar symbol a symbol, e para cada um chamamos
“module_eval” que simplesmente executa qualquer String passado a ele,
no caso criamos dinamicamente dois métodos, os equivalente a “getter” e
“setter”. Para o exemplo :telefone, seria o mesmo que escrevêssemos:

class Pessoa
  def telefone() @telefone; end
  def telefone=(val) @telefone = val; end
end

Muita gente confunde “meta-programação” com “reflexão”. Reflexão é
apenas perguntar a um objeto o que ele responde. Podemos fazer isso em
Ruby de várias maneiras:

>> a = "string"
=> "string"
>> a.methods
=> ["chop!", "constantize", ... "squish", "<<"]
>> a.respond_to? :size

>> p = Pessoa.new
=> #<Pessoa:0x261af60 @iniciais=nil, @sobrenome=nil, @email=nil, @primeiro_nome="">
>> p.telefone
=> nil
>> p.instance_variable_set("@telefone", "3333-4444")
=> "3333-4444"
>> p.instance_variable_get("@telefone")
=> "3333-4444"

Aqui vemos como todo objeto Ruby tem um método chamado “methods” que devolve um Array listando todos os métodos que ele responde. Além disso todo objeto Ruby ainda responde ao método “respond_to?” que recebe um symbol como parâmetro e responde se esse objeto responde a essa mensagem.

No exemplo seguinte, com a classe “Pessoa”, podemos ainda manipular diretamente a variável de instância “@telefone” sem sequer precisar de métodos acessores.

Como podemos ver, Reflexão em Ruby é trivial. Já vimos antes como funciona o método “send” para enviar mensagens arbitrárias e também como implementar o método “method_missing” para fazer um objeto responder a mensagens arbitrárias. Mas com exemplos como do “attr_accessor” podemos entender como manipular o comportamento de um objeto em tempo de execução.

Por isso dizemos que Ruby tem capacidades muito poderosas de meta-programação, que é o que o torna particularmente prazeroso de usar, sem precisar contar com sintaxes obscuras ou truques não documentados. Pensar em meta-programação faz parte do dia-a-dia de programação com Ruby.

Indo além do Nulo

Como mais um exemplo da flexibilidade do Ruby, vamos analisar o bom o velho “nulo”. Note que nulo é realmente nada. Não é o mesmo que zero. Não é o mesmo que uma string vazia.

Em Ruby, estamos falando do ‘nil’. Porém, como quase tudo em Ruby é um objeto, assim também é ‘nil’:

>> nil
=> nil
>> nil.class
=> NilClass
>> nil.object_id
=> 4

Ou seja, quando um método não devolve nada, ou seja, ‘nil’, ainda assim ele está devolvendo alguma coisa: a instância singleton da classe NilClass.

Não é difícil cair em situações onde queremos chamar um método em um objeto onde ainda não sabemos se o objeto é nil ou não. Por isso costumamos fazer algo assim:

unless obj.nil?
  obj.say_hello
else
  nil
end

Aqui eu propositadamente fiz um código mais longo do que deveria,
apenas para aproveitar e demonstrar o “unless”. Pense nele como o
oposto de “if”. Em outras linguagens estamos acostumados a fazer algo
assim:

if !obj.nil? ...

Ou seja, “se não for …”. Mas em vez disso, em Ruby preferimos dizer “a
menos que …”. Fica menos poluído do que colocar “!” ("not") o tempo
todo. Além disso chamamos o método “nil?” que checa se o objeto atual é
nulo ou não. Esse método “nil?” está presente direto da classe pai
“Object”, portanto todos os objetos no Ruby respondem a esse método, em
particular, como “nil” também é um objeto, ele também responde a esse
método:

>> nil.nil?
=> true
>> 1.nil?
=> false

E eu disse que o código acima era mais longo do que o necessário porque
sempre podemos usar o operador ternário (que também existe em outras
linguagens):

obj ? obj.say_hello : nil

Ou seja, se “obj” devolver “true” (apenas “false” e “nil” respondem como “false” em condicionais), então chame o método “say_hello”, caso contrário, devolva ‘nil’.

Como em qualquer objeto, se tentarmos chamar um método em ‘nil’ que não existe, ele responderá normalmente com uma exceção:

>> nil.size
NoMethodError: undefined method `size' for nil:NilClass
  from (irb):91
Um outro truque que discutiu-se algum tempo atrás é criar um novo método em Object chamado “try”:

class Object
  def try(metodo, *args)
    send(metodo, *args) if respond_to? metodo
  end
end

>> "".try(:size)
=> 0
>> nil.try(:size)
=> nil

Em vez de chamar um método diretamente quando ainda não sabemos se o objeto é nil ou não, podemos chamar o método “try” passando a mensagem (o nome do método) e seus parâmetros. Se o objeto para onde enviamos a mensagem souber responder essa mensagem (respond_to?), então repassamos normalmente, senão devolvemos nil.

Recomendo que teste todos esses exemplos no IRB para entender o comportamento. Esse tipo de pensamento é crucial na programação Ruby. Não quer dizer, claro, que agora devemos usar coisas como “try” o tempo todo, mas nas situações onde são necessárias, essa pode ser a diferença entre um código extremamente enxuto e expressivo do que um código burocrático e difícil de entender.

Conclusão

Até aqui mostramos apenas a ponta do iceberg da programação com Ruby. Existem dezenas de classes que já vêm na biblioteca padrão do Ruby que vocês precisam saber, como Net, File, FileUtils e muitos outros. Use o site ApiDock sempre que tiver dúvidas quanto à sintaxe de algum método. Lembre-se da questão de parâmetros dinâmicos, o que dificulta a documentação automática. Mas também lembre-se que o código-fonte de bibliotecas Ruby costumam ser muito simples de ler. Portanto recomendo muito que, na dúvida, procure o código-fonte (que já está na sua máquina) e tente ler direto na fonte.

E para ver alguns exemplos muito interessantes de resolução de problemas em Ruby, também recomendo ler o site Ruby Quiz que contém uma biblioteca enorme de pequenos problemas simples com soluções muito criativas usando os recursos de Ruby.

O mais importante: não tente escrever Ruby da mesma forma como se escreve Java ou C#. “Quando se está em Roma, faça como os romanos.” Portanto, nada de usar “camelCasing” para nomear seus métodos, por exemplo, use “metodos_separados_por_underscore”. Escreva Ruby da forma Ruby.

SEO Powered by Platinum SEO from Techblissonline