Apostila do curso RR-71
Desenv. Ágil para Web com Ruby on Rails
- Agilidade na Web
- A linguagem Ruby
- Ruby básico
- Mais Ruby: classes, objetos e métodos
- Metaprogramação
- Ruby on Rails
- Active Record
- Rotas
- Controllers e Views
- Completando o Sistema
- Calculations
- Associações Polimórficas
- Mais sobre views
- Ajax com Rails
- Algumas Gems Importantes
- Apêndice: Testes
- Apêndice: Rotas e Rack
- Apêndice: Design Patterns em Ruby
- Apêndice: Integrando Java e Ruby
- Apêndice: Deployment
Instalação
https://www.ruby-lang.org/pt/
O Ruby possui um gerenciador de pacotes bastante avançado, flexível e eficiente: RubyGems. As gems podem ser vistas como bibliotecas reutilizáveis de código Ruby, que podem até conter algum código nativo (em C, Java, .Net). São análogos aos jars no ambiente Java, ou os assemblies do mundo .Net. RubyGems é um sistema gerenciador de pacotes comparável a qualquer um do mundo *NIX, como os .debs do apt-get, os rpms do yum, entre outros.
Após a instalação do Ruby podemos instalar os pacotes necessários para a criação de uma aplicação Rails com o comando rails new:
rails new nome_da_aplicacao
Ao desenvolver novas aplicações utilizando Ruby, notaremos que uma série de funcionalidades serão necessárias - ler e parsear JSON, fazer autenticação de usuário, entre outras coisas. A maioria dessas funcionalidades já foi implementada em alguma gem, e para usufruir desses recursos basta colocar a gem em nossa aplicação.
Uma forma eficaz de controlar as dependências de nosso projeto Ruby é utilizar uma gem chamada Bundler. Instalar o bundler é bem simples. Basta rodar o comando:
gem install bundler
Com o Bundler, declaramos as dependências necessárias em um arquivo chamado Gemfile. Esse aquivo deverá conter, primeiramente, a fonte de onde onde o Bundler deve obter as gems e em seguida a declaração das dependências que usaremos no projeto.
source "http://rubygems.org"
gem "rails"
gem "devise"
Podemos então rodar o comando bundle (atalho para o comando bundle install) para obter as gems necessárias para o nosso projeto.
gem "rails", "4.0.0"
ou
gem "rails", "~> 4.0.0"
Ao rodar o comando bundle será gerado um novo arquivo chamado Gemfile.lock, que especifica todas as gems obtidas para aquele Gemfile e sua respectiva versão baixada. O Gemfile.lock é uma boa alternativa para congelar as versões das gems a serem utilizadas, uma vez que ao rodarmos o comando bundle sobre a presença de um Gemfile.lock, as versões presentes nesse arquivo serão utilizadas para especificar as gems a serem baixadas.
3.2 - Executando código Ruby no Terminal: IRB e arquivos .rb
O IRB é um dos principais recursos disponíveis aos programadores Ruby. Funciona como um console/terminal, e os comandos vão sendo interpretados ao mesmo tempo em que vão sendo inseridos, de forma interativa. O irb avalia cada linha inserida e já mostra o resultado imediatamente.
3.3 - Variáveis, Strings e Comentários
Podemos imprimir uma mensagem com ruby utilizando pelo menos 3 opções: puts, print e p.
puts "Olá Mundo"
print "Olá Mundo"
p "Olá Mundo"
Comentários em Ruby podem ser de uma linha apenas:
# Imprime uma mensagem
puts "Oi mundo"
Ou comentários de blocos:
=begin
Imprime uma mensagem
=end
puts "Oi mundo"
3.4 - Variáveis e atribuições
ano = 1980
A variável ano é do tipo Fixnum, um tipo do Ruby que representa números inteiros. Mas não informamos isso na declaração da variável ano, isso porque na linguagem Ruby não é necessária esse tipo de informação, já que o interpretador da linguagem infere o tipo da variável automaticamente durante a execução do código. Esta característica é conhecida como inferência de tipos.
gets
O Ruby nos permite atribuir numa variável um valor digitado no console através do teclado, isso é feito através do método %gets%, mas com ele só poderemos atribuir Strings%%, veremos como atribuir outros tipos adiante. Exemplo:
irb(main):001:0> nome = gets
José
=> "José\n"
3.5 - Tipagem
Ruby também é implicitamente e dinamicamente tipada, implicitamente tipada pois os tipos são inferidos pelo interpretador, não precisam ser declarados e dinamicamente tipada porque o Ruby permite que o tipo da variável possa ser alterado durante a execução do programa
idade = 27
idade = "27"
Em Ruby temos a possibilidade de descobrir o tipo de uma variável utilizando o .class:
irb(main):001:0> num = 87
=> 87
irb(main):002:0> puts num.class
Fixnum
=> nil
3.7 - String
irb(main):001:0> mensagem = "Olá Mundo"
=> "Olá Mundo"
irb(main):002:0> mensagem = 'Olá Mundo'
=> "Olá Mundo"
Strings em Ruby é que são mutáveis, diferente do Java, por exemplo.
irb(main):001:0> mensagem = "Bom dia, "
=> "Bom dia,"
irb(main):002:0> mensagem << " tudo bem?"
=> "Bom dia, tudo bem?"
irb(main):003:0> puts mensagem
Bom dia, tudo bem?
=> nil
O operador << é utilizado para a operação append de Strings, ou seja, para a concatenação de Strings em uma mesma instância. Já o operador + também concatena Strings mas não na mesma instância, isso quer dizer que o + gera novas Strings.
#Declaração da variável %%nome%%
print "Nome do restaurante: "
puts nome
#Declaração da variável %%nome%%
print "Nome do restaurante: #{nome}"
Basta colocar dentro da String a variável entre as chaves, em #{}. Mas fique atento, com Strings definidas com aspas simples não é possível fazer uso da interpolação, por isso prefira sempre o uso de String com aspas duplas.
Prefira sempre a interpolação ao invés da concatenação (+) ou do append (<<). É mais elegante e mais rápido.
O método capitalize
irb(main):001:0> nome = "fasano"
=> "fasano"
irb(main):002:0> puts nome.capitalize
Fasano
=> nil
nome = "fasano"
puts nome.capitalize #Fasano
puts nome #fasano
Os métodos terminados em bang(!)
nome = "fasano"
puts nome.capitalize! #Fasano
puts nome #Fasano
3.9 - Tipos e operações básicas
irb(main):001:0> 3*(2+5)/8
=> 2
3 tipos numéricos básicos: Fixnum, Bignum e Float Fixnum é o tipo principal para números inteiros.
Números inteiros muito grandes são convertidos automaticamente para Bignum, de forma a não sofrerem perda de precisão
irb(main):002:0> 9999999999999999999.class
=> Bignum
irb(main):003:0> 15.0.class
=> Float
- + Soma
- - Subtração
- / Divisão
- * Multiplicação
- ** Potência
- % Modulo (retorna o resto de uma divisão)
irb(main):001:0> 2 + 2
=> 4
irb(main):002:0> 5 - 3
=> 2
irb(main):003:0> 10 / 2
=> 5
irb(main):004:0> 15 * 2
=> 30
irb(main):005:0> 3 ** 2
=> 9
Fazer uma divisão cujo resultado não seja um número inteiro, este será arredondado para baixo!
irb(main):001:0> 15/2
=> 7
Pare resolver esse problema, podemos usar um Float no lugar de Fixnum, em qualquer lado da operação:
irb(main):002:0> 15.0 / 2
=> 7.5
Ranges
Ruby fornece uma maneira de trabalharmos com sequências de uma forma bem simples: (1..3) # range representando números de 1 a 3. ('a'..'z') # range representando letras minúsculas do alfabeto (0...5) # range representando números de 0 a 4.
Símbolos
Símbolos também são texto, como as Strings. Só que devem ser precedidos do carácter ':', ao invés de aspas e pertencem à classe Symbol:
>> puts :simbolo
simbolo
=> nil
>> :simbolo.class
=> Symbol
3.11 - Estruturas de controle
>> 3 > 2
=> true
>> 3+4-2 <= 3*2/4
=> false
>> variavel = nil
=> nil
>> if(variavel)
>> puts("so iria imprimir se variavel != null")
>> end
=> nil
>> if(3 == 3)
>> puts("3 é 3")
>> end
3 é 3
=> nil
def procura_sede_copa_do_mundo( ano )
case ano
when 1895..2005
"Não lembro... :)"
when 2006
"Alemanha"
when 2010
"África do Sul"
when 2014
"Brasil"
end
end
puts procura_sede_copa_do_mundo(1994)
Ruby possui bom suporte a expressões regulares, fortemente influenciado pelo Perl. Expressões regulares literais são delimitadas por / (barra).
>> /rio/ =~ "são paulo"
=> nil
>> /paulo/ =~ "são paulo"
=> 4
O operador =~ faz a função de match e retorna a posição da String onde o padrão foi encontrado, ou nil caso a String não bata com a expressão regular.
Operador ou igual
O operador ||= atribui um valor apenas a variável esteja vazia. é muito utilizado para carregar valores de maneira "lazy".
nome ||= "anonimo"
Nesse caso, se nome é nulo, ele será preenchido com "anonimo".
Ruby é considerada uma linguagem puramente orientada a objetos, já que tudo em Ruby é um objeto (inclusive as classes)
Existe um método chamado class(), que retorna o tipo do objeto, enquanto object_id(), retorna o número da referência, ou identificador único do objeto dentro da memória heap.
Aaquele que "transforma" um objeto em uma String, to_s().
Para criar um objeto em Ruby:
# criando um objeto
objeto = Object.new()
4.3 - Definição de métodos
def é uma palavra chave do Ruby para a definição (criação) de métodos, que podem, claro, receber parâmetros:
def pessoa.vai(lugar)
puts "indo para " + lugar
end
a seguir mostra um método que devolve uma String:
def pessoa.vai(lugar)
"indo para " + lugar
end
puts pessoa.vai("casa")
# REFATORANDO
def pessoa.vai(lugar)
"indo para #{lugar}"
end
# Vários Argumentos
def pessoa.troca(roupa, lugar)
"trocando de #{roupa} no #{lugar}"
end
# invocação dos métodos
pessoa.troca('camiseta', 'banheiro')
# Com Valores padrões
def pessoa.troca(roupa, lugar='banheiro')
"trocando de #{roupa} no #{lugar}"
end
# invocação sem o parametro:
pessoa.troca("camiseta")
# invocação com o parametro:
pessoa.troca("camiseta", "sala")
4.5 - Discussão: Enviando mensagens aos objetos
pessoa.send(:fala)
O método send recebe como argumento o nome do método a ser invocado, que pode ser um símbolo ou uma string. De acordo com a orientação a objetos é como se estivéssemos enviando a mensagem "fala" ao objeto pessoa.
4.6 - Classes
class Pessoa
def fala
puts "Sei Falar"
end
def troca(roupa, lugar="banheiro")
"trocando de #{roupa} no #{lugar}"
end
end
p = Pessoa.new
# o objeto apontado por p já nasce com os métodos fala e troca.
O diferencial de classes em Ruby é que são abertas. Ou seja, qualquer classe pode ser alterada a qualquer momento na aplicação. Basta "reabrir" a classe e fazer as mudanças:
class Pessoa
def novo_metodo
# ...
end
end
Caso a classe Pessoa já exista estamos apenas reabrindo sua definição para adicionar mais código. Não será criada uma nova classe e nem haverá um erro dizendo que a classe já existe.
class Restaurante
def qualifica(nota, msg="Obrigado")
puts "A nota do restaurante foi #{nota}. #{msg}"
end
end
restaurante_um = Restaurante.new
restaurante_dois = Restaurante.new
restaurante_um.qualifica(10)
restaurante_dois.qualifica(1, "Ruim!")
4.8 - Desafio: Classes abertas
Qualquer classe em Ruby pode ser reaberta e qualquer método redefinido. Inclusive classes e métodos da biblioteca padrão, como Object e Fixnum.
class Fixnum
def +(outro)
self - outro # fazendo a soma subtrair
end
end
4.9 - self
É análogo ao this de outras linguagens como Java
Todo método em Ruby é chamado em algum objeto, ou seja, um método é sempre uma mensagem enviada a um objeto. Quando não especificado, o destino da mensagem é sempre self:
class Conta
def transfere_para(destino, quantia)
debita quantia
# mesmo que self.debita(quantia)
destino.deposita quantia
end
end
4.11 - Atributos e propriedades: acessores e modificadores
Atributos, também conhecidos como variáveis de instância, em Ruby são sempre privados e começam com @. Não há como alterá-los de fora da classe; apenas os métodos de um objeto podem alterar os seus atributos (encapsulamento!).
class Pessoa
def initialize
puts "Criando nova Pessoa"
end
def muda_nome(novo_nome)
@nome = novo_nome
end
def diz_nome
puts "meu nome é #{@nome}"
end
end
p = Pessoa.new
p.muda_nome("JOSE")
p.diz_nome
Os initializers são métodos privados (não podem ser chamados de fora da classe) e podem receber parâmetros. Veremos mais sobre métodos privados adiante.
class Pessoa
def initialize(nome)
@nome = nome
end
end
joao = Pessoa.new("João")
Métodos acessores e modificadores são muito comuns e dão a ideia de propriedades. Existe uma convenção para a definição destes métodos, que a maioria dos desenvolvedores Ruby segue (assim como Java tem a convenção para getters e setters):
class Pessoa
def nome # acessor
@nome
end
def nome=(novo_nome)
@nome = novo_nome
end
end
pessoa = Pessoa.new
pessoa.nome=("José")
puts pessoa.nome
# => "José"
4.12 - Syntax Sugar: facilitando a sintaxe
pessoa.nome = "José"
Apesar de parecer, a linha acima não é uma simples atribuição, já que na verdade o método nome= está sendo chamado. Este recurso é conhecido como Syntax Sugar, já que o Ruby aceita algumas exceções na sintaxe para que o código fique mais legível.
Prática
class Restaurante_inic
attr_accessor :nota
def initialize (nome)
puts "criando um novo restaurante #{nome}"
@nome = nome
end
def qualifica(msg = "Obrigado")
puts "A nota do restaurante #{@nome} foi Nota: #{@nota}. #{msg}"
end
# propriedades
=begin
Seria muito trabalhoso definir todas as propriedades de acesso a nossa variáveis. Refatore a classe Restaurante para utilizar o attr_accessor :nota Seu arquivo final deve ficar assim:
def nota=(nota)
@nota = nota
end
def nota
@nota
end
=end
end
restautante_um = Restaurante_inic.new("Fasanoo")
restautante_dois = Restaurante_inic.new("FOGO DE CHAO")
restautante_um.nota = 10
restautante_dois.nota = 1
restautante_um.qualifica
restautante_dois.qualifica("Comida SALGADA")
4.14 - Coleções
Arrays em Ruby são instâncias da classe Array, não sendo simplesmente uma estrutura de dados, mas possuindo diversos métodos auxiliares que nos ajudam no dia-a-dia.
lista = Array.new
lista << "RR-71"
lista << "RR-75"
lista << "FJ-91"
puts lista.size
# => 3
resgatar os elementos e para isso usamos [] passando um índice como parametro:
puts lista[1]
# => "RR-75"
puts lista[0]
# => "RR-71"
lista = [1, 2, "string", :simbolo, /$regex^/]
puts lista[2]
# => string
Um exemplo que demonstra uma aparição de arrays é a chamada ao método methods, que retorna uma array com os nomes de todos os métodos que o objeto sabe responder naquele instante. Esse método é definido na classe Object então todos os objetos o possuem:
cliente = "Petrobras"
puts cliente.methods
4.15 - Exemplo: múltiplos parâmetros
def compra(produto1, produto2, produto3, produtoN)
end
#ou - Para receber um número qualquer de parâmetros usamos a sintaxe * do Ruby:
def compra(*produtos)
# produtos é uma array
puts produtos.size
end
def compra(produtos)
# produtos é uma array
puts produtos.size
end
compra( ["Notebook", "Pendrive", "Cafeteira"] )
O operador " * " é chamado de splat.
4.16 - Hashes
Ruby também tem uma estrutura indexada por qualquer objeto, onde as chaves podem ser de qualquer tipo, o que permite atingir nosso objetivo. A classe Hash é quem dá suporte a essa funcionalidade, sendo análoga aos objetos HashMap, HashTable, arrays indexados por String e dicionários de outras linguagens.
config = Hash.new
config["porta"] = 80
config["ssh"] = false
config["nome"] = "Caelum.com.br"
puts config.size
# => 3
puts config["ssh"]
# => false
Por serem únicos e imutáveis, símbolos são ótimos candidatos a serem chaves em Hashes, portanto poderíamos trabalhar com:
config = Hash.new
config[:porta] = 80
existe um movimento que se tornou comum com a popularização do Rails 2, passando parâmetro através de hash:
aluno.transfere( {destino: escola, data: Time.now, valor: 50.00} )
Note que o uso do Hash implicou em uma legibilidade maior apesar de uma proliferação de palavras:
def transfere(argumentos)
destino = argumentos[:destino]
data = argumentos[:data]
valor = argumentos[:valor]
# executa a transferência
end
class Conta
def transfere(valor, argumentos)
destino = argumentos[:para]
data = argumentos[:em]
# executa a transferência
end
end
aluno.transfere(50.00, {para: escola, em: Time.now})
# VERSÂO ANTERIOR 1.9
aluno.transfere(50.00, {:para => escola, :em => Time.now})
Além dos parênteses serem sempre opcionais, quando um Hash é o último parâmetro de um método, as chaves podem ser omitidas (Syntax Sugar).
aluno.transfere destino: escola, valor: 50.0, data: Time.now
Prática (franquia.rb)
class Franquia
def initialize
@restaurantes = []
end
def adiciona(*restaurantes)
for restaurante in restaurantes
@restaurantes << restaurante
end
end
def mostra
for restaurante in @restaurantes
puts restaurante.nome
end
end
end
class Restaurante
attr_accessor :nome
def fechar_conta(dados)
puts "Conta dechado no valor de #{dados[:valor]} e com nota #{dados[:nota]}. Comentário : #{dados[:comentario]}"
end
end
restaurante_um = Restaurante.new
restaurante_um.nome = "RESTAURANTE UM"
restaurante_dois = Restaurante.new
restaurante_dois.nome = "RES FOGO DE CHAO"
franquia = Franquia.new
franquia.adiciona restaurante_um, restaurante_dois
# franquia.adiciona restaurante_dois
franquia.mostra
restaurante_um.fechar_conta valor:30, nota:9, comentario:"Otimo"
4.18 - Blocos e Programação Funcional
class Banco
def initialize(contas)
@contas = contas
end
def status
saldo = 0
for conta in @contas
saldo += conta
end
saldo
end
end
banco = Banco.new([200, 300, 400])
banco.status
4.18 - Blocos e Programação Funcional
class Banco
def initialize(contas)
@contas = contas
end
def status(&block)
saldo = 0
for conta in @contas
saldo += conta
block.call(saldo)
end
saldo
end
end
banco = Banco.new([200,300, 400])
# puts banco.status
banco.status do |saldo_parcial|
puts saldo_parcial
end
Note que block é um objeto que ao ter o método call invocado, chamará o bloco que foi passado, concluindo nosso primeiro objetivo: dar a chance de quem se interessar no saldo parcial, fazer algo com ele.
class Banco
def initialize(contas)
@contas = contas
end
def status(&block)
saldo = 0
for conta in @contas
saldo += conta
if block_given?
block.call(saldo)
end
end
saldo
end
end
banco = Banco.new([200,300, 400])
# puts banco.status
banco.status do |saldo_parcial|
puts saldo_parcial
end
# OU
banco.status { |saldo_parcial| puts saldo_parcial }
Como vimos até aqui, o método que recebe um bloco pode decidir se deve ou não chamá-lo. Para chamar o bloco associado, existe uma outra abordagem com a palavra yield:
class Banco
def initialize(contas)
@contas = contas
end
def status(&block)
saldo = 0
for conta in @contas
saldo += conta
if block_given?
yield(saldo)
end
end
saldo
end
end
banco = Banco.new([200,300, 400])
# puts banco.status
banco.status do |saldo_parcial|
puts saldo_parcial
end
# OU
banco.status { |saldo_parcial| puts saldo_parcial }
Dizer que estamos passando uma função (pedaço de código) como parâmetro a outra função é o mesmo que passar blocos na chamada de métodos.
Para iterar em uma Array possuímos o método each, que chama o bloco de código associado para cada um dos seus items, passando o item como parâmetro ao bloco:
lista = ["rails", "rake", "ruby", "rvm"]
lista.each do |programa|
puts programa
end
funcionarios = ["Guilherme", "Sergio", "David"]
nomes_maiusculos = []
for nome in funcionarios
nomes_maiusculos << nome.upcase
end
# --->> Poderíamos usar o método each:
funcionarios = ["Guilherme", "Sergio", "David"]
nomes_maiusculos = []
funcionarios.each do |nome|
nomes_maiusculos << nome.upcase
end
class Franquia_refatorado_blocos
def initialize
@restaurantes = []
end
def adiciona(*restaurantes)
for restaurante in restaurantes
@restaurantes << restaurante
end
end
# def mostra
# for restaurante in @restaurantes
# puts restaurante.nome
# end
# end
def mostra
@restaurantes.each do |r|
puts r.nome
end
end
def relatorio
@restaurantes.each do |r|
yield r
end
end
end
class Restaurante
attr_accessor :nome
def fechar_conta(dados)
puts "Conta dechado no valor de #{dados[:valor]} e com nota #{dados[:nota]}. Comentário : #{dados[:comentario]}"
end
end
restaurante_um = Restaurante.new
restaurante_um.nome = "RESTAURANTE UM"
restaurante_dois = Restaurante.new
restaurante_dois.nome = "RES FOGO DE CHAO"
franquia = Franquia_refatorado_blocos.new
franquia.adiciona restaurante_um, restaurante_dois
# franquia.adiciona restaurante_dois
franquia.mostra
restaurante_um.fechar_conta valor:30, nota:9, comentario:"Otimo"
franquia.relatorio do |relat|
puts "Restaurante cadastrado: #{relat.nome}"
end
Entendendo Blocks, Procs e Lambdas no Ruby
Conceitos na prática: Ruby block (yield)
- RuntimeError : É a exception padrão lançada pelo método raise.
- NoMethodError : Quando um objeto recebe como parametro de uma mensagem um nome de método que não pode ser encontrado.
- NameError : O interpretador não encontra uma variável ou método com o nome passado.
- IOError : Causada ao ler um stream que foi fechado, tentar escrever em algo read-only e situações similares.
- Errno::error : É a família dos erros de entrada e saída (IO).
- TypeError : Um método recebe como argumento algo que não pode tratar.
- ArgumentError : Causada por número incorreto de argumentos.
print "Digite um numero"
numero = gets.to_i
begin
resultado = 100/numero
rescue
puts "Numero digitado invalido!"
exit
end
puts "100/#{numero} é #{resultado}"
# -----
def verifica_idade(idade)
unless idade > 18
raise ArgumentError, "Voce precisa ser maior de idade..."
end
end
verifica_idade(17)
# -----
class IdadeInsuficienteException < Exception
end
def verificaIdade_dois(idade)
raise IdadeInsuficienteException, "Idade Insuficiente" unless > 18
end
begin
verificaIdade_dois(15)
rescue IdadeInsuficienteException => e
puts "Foi lançada a Exception #{e}"
end
Para saber mais: Throw e catch
def pesquisa_banco(nome)
if nome.size < 10
throw :nome_invalido, "Nome invalido, digite novamente"
end
# executa a pesquisa
"cliente #{nome}"
end
def executa_pesquisa(nome)
catch :nome_invalido do
cliente = pesquisa_banco(nome)
return cliente
end
end
puts executa_pesquisa("ana")
# => "Nome invalido, digite novamente"
puts executa_pesquisa("guilherme silveira")
# => cliente guilherme silveira
4.24 - Arquivos com código fonte ruby
Agora podemos acessar uma conta bastando primeiro importar o arquivo que a define:
require 'conta'
puts Conta.new(500).saldo
Uma forma de carregar o arquivo sem especificar o diretório atual é utilizando a forma abaixo:
require File.expand_path(File.join(File.dirname(__FILE__), 'nome_do_arquivo'))
Assim como qualquer outra linguagem isso resulta em um possível Load Hell, onde não sabemos exatamente de onde nossos arquivos estão sendo carregados. Tome bastante cuidado para a configuração de seu ambiente.
O comando require carrega o arquivo apenas uma vez. Para executar a interpretação do conteúdo do arquivo diversas vezes, utilize o método load.
load 'conta.rb'
load 'conta.rb'
# executado duas vezes!
4.25 - Para saber mais: um pouco de IO
print "Escreva um texto: "
texto = gets
File.open( "caelum.txt", "w" ) do |f|
f << texto
end
Dir.entries('caelum').each do |file_name|
idea = File.read( file_name )
puts idea
end
Podemos lidar de maneira similar com requisições HTTP utilizando o código abaixo e imprimir o conteúdo do resultado de uma requisição:
require 'net/http'
Net::HTTP.start( 'www.caelum.com.br', 80 ) do |http|
print( http.get( '/' ).body )
end
Mais Ruby: classes, objetos e métodos
5.1 - Métodos de Classe
Classes em Ruby também são objetos:
Pessoa.class
# => Class
c = Class.new
instancia = c.new
Variáveis com letra maiúscula representam constantes em Ruby, que até podem ser modificadas, mas o interpretador gera um warning. Portanto, Pessoa é apenas uma constante que aponta para um objeto do tipo Class.
Se classes são objetos, podemos definir métodos de classe como em qualquer outro objeto:
class Pessoa
# ...
end
def Pessoa.pessoas_no_mundo
100
end
Pessoa.pessoas_no_mundo
# => 100
5.2 - Para saber mais: Singleton Classes
A definição class << object define as chamadas singleton classes em ruby. Por exemplo, uma classe normal em ruby poderia ser:
class Pessoa
def fala
puts 'oi'
end
end
p = Pessoa.new
p.fala # imprime 'oi'
Entretanto, também é possível definir métodos apenas para esse objeto "p", pois tudo em ruby, até mesmo as classes, são objetos, fazendo:
def p.anda
puts 'andando'
end
O método "anda" é chamado de singleton method do objeto "p".
Um singleton method "vive" em uma singleton class. Todo objeto em ruby possui 2 classes:
- a classe a qual foi instanciado
- sua singleton class
A singleton class é exclusiva para guardar os métodos desse objeto, sem compartilhar com outras instâncias da mesma classe.
Existe uma notação especial para definir uma singleton class:
class << Pessoa
def anda
puts 'andando'
end
end
Definindo o código dessa forma temos o mesmo que no exemplo anterior, porém definindo o método anda explicitamente na singleton class.É possível ainda definir tudo na mesma classe:
class Pessoa
class << self
def anda
puts 'andando'
end
end
end
Pessoa.anda
Método de Classe é diferente de static do Java
Se você trabalha com java pode confundir o self com o static. Cuidado! O método definido como self roda apenas na classe, não funciona nas instâncias. Você pode testar fazendo:
Restaurante.relatorio
restaurante_um.relatorio
A invocação na instância dará um: NoMethodError: undefined method 'relatorio' for #<Restaurante:0x100137b48 @nome="Fasano", @nota=10>
5.4 - Convenções
Métodos que retornam booleanos costumam terminar com ?, para que pareçam perguntas aos objetos:
texto = "nao sou vazio"
texto.empty? # => false
Métodos que tem efeito colateral (alteram o estado do objeto, ou que costumem lançar exceções) geralmente terminam com ! (bang):
conta.cancela!
A comparação entre objetos é feita através do método == (sim, é um método!)
class Pessoa
def ==(outra)
self.cpf == outra.cpf
end
end
Nomes de variável e métodos em Ruby são sempre minúsculos e separados por '_' (underscore).
Variáveis com nomes maiúsculo são sempre constantes.
Para nomes de classes, utilize as regras de CamelCase, afinal nomes de classes são apenas constantes.
class Animal
def come
"comendo"
end
end
class Pato < Animal
def quack
"Quack!"
end
end
pato = Pato.new
pato.come # => "comendo"
A tipagem em Ruby não é explícita, por isso não precisamos declarar quais são os tipos dos atributos. Veja este exemplo:
class PatoNormal
def faz_quack
"Quack!"
end
end
class PatoEstranho
def faz_quack
"Queck!"
end
end
class CriadorDePatos
def castiga(pato)
pato.faz_quack
end
end
pato1 = PatoNormal.new
pato2 = PatoEstranho.new
c = CriadorDePatos.new
c.castiga(pato1) # => "Quack!"
c.castiga(pato2) # => "Queck!"
Para o criador de patos, não interessa que objeto será passado como parâmetro. Para ele basta que o objeto saiba fazer quack. Esta característica da linguagem Ruby é conhecida como Duck Typing.
5.7 - Modulos
module Caelum
module Validadores
class ValidadorDeCpf
# ...
end
class ValidadorDeRg
# ...
end
end
end
validador = Caelum::Validadores::ValidadorDeCpf.new
Ou como mixins, conjunto de métodos a ser incluso em outras classes:
module Comentavel
def comentarios
@comentarios ||= []
end
def recebe_comentario(comentario)
self.comentarios << comentario
end
end
class Revista
include Comentavel
# ...
end
revista = Revista.new
revista.recebe_comentario("muito ruim!")
puts revista.comentarios
5.8 - Metaprogramação
Por ser uma linguagem dinâmica, Ruby permite adicionar outros métodos e operações aos objetos em tempo de execução.
pessoa = Object.new()
pessoa = Object.new()
def pessoa.fala()
puts "Sei falar"
end
pessoa.fala()
Meta-programação é a capacidade de gerar/alterar código em tempo de execução. Note que isso é muito diferente de um gerador de código comum, onde geraríamos um código fixo, que deveria ser editado na mão e a aplicação só rodaria esse código posteriormente.
class Aluno
# nao sabe nada
end
class Professor
def ensina(aluno)
def aluno.escreve
"sei escrever!"
end
end
end
juca = Aluno.new
juca.respond_to? :escreve
# => false
professor = Professor.new
professor.ensina juca
juca.escreve
# => "sei escrever!"
class Pessoa
attr_accessor :nome
end
p = Pessoa.new
p.nome = "Joaquim"
puts p.nome
# => "Joaquim"
A chamada do método de classe attr_acessor, define os métodos nome e nome= na classe Pessoa.
A técnica de código gerando código é conhecida como metaprogramação, ou metaprogramming, como já definimos.
Como visto, por padrão todos os métodos são públicos. O método de classe private altera a visibilidade de todos os métodos definidos após ter sido chamado:
class Pessoa
private
def vai_ao_banheiro
# ...
end
end
Todos os métodos após a chamada de private são privados. Isso pode lembrar um pouco C++, que define regiões de visibilidade dentro de uma classe (seção pública, privada, ...). Um método privado em Ruby só pode ser chamado em self e o self deve ser implícito. Em outras palavras, não podemos colocar o self explicitamente para métodos privados, como em self.vai_ao_banheiro.
Caso seja necessário, o método public faz com que os métodos em seguida voltem a ser públicos:
class Pessoa
private
def vai_ao_banheiro
# ...
end
public
def sou_um_metodo_publico
# ...
end
end
O último modificador de visibilidade é o protected. Métodos protected só podem ser chamados em self (implícito ou explícito). Por isso, o protected do Ruby acaba sendo semelhante ao protected do Java e C++, que permitem a chamada do método na própria classe e em classes filhas.