Metaprogramming Ruby, 2nd EditionTL
Metaprogramming Ruby, 2nd EditionTL
www.allitebooks.com
Machine Translated by Google
www.allitebooks.com
Machine Translated by Google
Este é o único livro sobre Ruby que faz você pensar “Então é assim que funciona” repetidamente, pois conceitos
como o modelo de objeto, DSLs e blocos se encaixam com aquele “Clique!” satisfatório! som. É um ótimo guia para
o que acontece sob o capô de uma linguagem que parece envolver uma espécie de magia lá no fundo. Eu recomendo.
ÿ Peter Bakhirev
A edição anterior do Metaprogramming Ruby mudou minha vida e meu código e me ajudou a conseguir meu primeiro
trabalho de programação. Você pensaria que não haveria como melhorar um livro tão bom, mas Paolo Perrotta
conseguiu. Aprenda a desvendar o potencial oculto dessa bela linguagem e apaixone-se novamente por Ruby.
ÿ Richard Schneeman
Programador, Heroku
Para autores de gems e desenvolvedores de aplicativos, este livro estabelece a base que todos precisam para
aproveitar todo o poder do Ruby. Paolo descreve a metaprogramação de uma forma divertida e acessível
para todos os níveis de habilidade. O conhecimento adquirido com a leitura deste livro ajudará você a escrever um
código mais limpo e a trabalhar com mais eficiência com as bases de código legadas.
ÿ Paul Elliott
Rocketeer, Hashrocket
www.allitebooks.com
Machine Translated by Google
ÿ Fabien Catteau
Este é um livro que todos que desejam ter uma compreensão mais profunda do
funcionamento interno do Ruby e do Ruby on Rails devem ler. Os “feitiços” descritos neste
livro são ferramentas inestimáveis para entender e usar Ruby em toda a sua extensão.
Não se trata apenas de metaprogramação, mas também de levar sua programação Ruby
a um nível diferente.
ÿ Kosmas Chatzimichalis
Engenheiro de software
Sou um grande fã de Python, então deveria desconsiderar Ruby. Paolo me fez apreciá-lo.
Metaprogramming Ruby não é apenas o livro que me permitiu entender esse tópico
esotérico e fascinante, mas também me fez repensar a maneira como escrevo código em
outras linguagens.
ÿ Arialdo Martini
Programador, JobRapido.com
www.allitebooks.com
Machine Translated by Google
Este livro revela todos os meandros da arte da metaprogramação em Ruby, com uma
abordagem objetiva e uma ironia que transparece de uma prosa vibrante, nunca enfadonha,
sem comprometer nada de sua perspicácia. Metaprogramming Ruby é um daqueles livros que
qualquer rubista sério (e mesmo os profissionais) vai querer revisitar de vez em quando.
ÿ Piergiuliano Bossi
Chefe de engenharia principal, Pontos
www.allitebooks.com
Machine Translated by Google
Metaprogramação Ruby 2
Programe como os profissionais de Ruby
Paolo Perrotta
A estante pragmática
Dallas, Texas • Raleigh, Carolina do Norte
www.allitebooks.com
Machine Translated by Google
Muitas das designações usadas por fabricantes e vendedores para distinguir seus produtos são reivindicadas como
marcas registradas. Onde essas designações aparecem neste livro, e The Pragmatic Programmers, LLC estava
ciente de uma reivindicação de marca registrada, as designações foram impressas em letras maiúsculas iniciais ou
em todas as letras maiúsculas. The Pragmatic Starter Kit, The Pragmatic Programmer, Pragmatic Programming,
Pragmatic Bookshelf, PragProg e o dispositivo de ligação g são marcas comerciais da The Pragmatic Programmers,
LLC.
Todas as precauções foram tomadas na preparação deste livro. No entanto, o editor não assume nenhuma
responsabilidade por erros ou omissões, ou por danos que possam resultar do uso das informações (incluindo listas
de programas) aqui contidas.
Nossos cursos, workshops e outros produtos pragmáticos podem ajudar você e sua equipe a criar um software
melhor e se divertir mais. Para obter mais informações, bem como os títulos mais recentes da Pragmatic, visite-nos
em http://pragprog.com.
www.allitebooks.com
Machine Translated by Google
www.allitebooks.com
Machine Translated by Google
Conteúdo
Prefácio . . . . . . . . . . . . . xiii
Agradecimentos . . . . . . . . . . . xv
Introdução . . . . . . . . . . . . xvii
3. Terça: Métodos . . . . . . . . . . . 45
Um Problema de Duplicação 46
Métodos Dinâmicos 48
method_missing 55
Questionário: Caça 64
aos Bugs Tábuas em Branco 66
Embrulhar 69
4. Quarta-feira: Blocos . . . . . . . . . . . 73
Questionário do Dia dos 73
Blocos: Ruby# 75
www.allitebooks.com
Machine Translated by Google
Conteúdo • x
7. Epílogo . . . . . . . . . . . . . 163
www.allitebooks.com
Machine Translated by Google
Conteúdo • xi
Índice . . . . . . . . . . . . . . 243
Machine Translated by Google
Prefácio
Ruby herda características de várias linguagens — Lisp, Smalltalk, C e Perl, para citar algumas.
Metaprogramação vem de Lisp (e Smalltalk).
É um pouco como mágica, que torna possível algo surpreendente. Existem dois tipos de magia: a
magia branca, que faz coisas boas, e a magia negra, que pode fazer coisas desagradáveis. Da
mesma forma, há dois aspectos na metaprogramação. Se você se disciplinar, poderá fazer coisas
boas, como aprimorar o idioma sem ajustar sua sintaxe por macros ou habilitar idiomas específicos
de domínio interno. Mas você pode cair no lado negro da metaprogramação. A metaprogramação
pode confundir facilmente.
Ruby confia em você. Ruby trata você como um programador adulto. Dá a você grande poder,
como a metaprogramação. Mas você precisa se lembrar que com grandes poderes vêm grandes
responsabilidades.
matz
Agradecimentos
Obrigado, Joe Armstrong, Satoshi Asakawa, Peter Bakhirev, Paul Barry, Juanjo
Bazán, Emmanuel Bernard, Roberto Bettazzoni, Ola Bini, Piergiuliano Bossi, Simone
Busoli, Alessandro Campeis, Kosmas Chatzimichalis, Andrea Cisternino, Davide
D'Alto, Pietro Di Bello , Mauro Di Nuzzo, Marco Di Timoteo, Paul Elliott, Eric Farkas,
Mauricio Fernandez, Francisco Fernández Castaño, Jay Fields, Michele Finelli, Neal
Ford, Florian Frank, Sanne Grinovero, Fed erico Gobbo, Florian Groß, Sebastian
Hennebrüder, Doug Hudson, Jurek Husakowski, Lyle Johnson, Lisa Maria Jones,
Josh Kalderimis, Murtuza Kutub, Marc Lainez, Daniele Manni, Luca Marchetti,
Arialdo Martini, Kado Masanori, MenTaLguY, Nicola Moretto, Sandro Paganotti,
Alessandro Patriarca, Carlo Pecchia, Susanna Perrotta, John Pignata , Andrea
Provaglio, Mike Roberts, Martin Rodgers, Richard Schneeman, Joe Sims, Jeremy
Sydik, Andrea Tomasini, Mauro Tortonesi, Marco Trincardi, Ivan Vaghi, Giancarlo
Valente, Davide Varvello, Elzie Vergine.
Obrigado, pessoas pragmáticas: Ellie Callahan, Janet Furlow, Andy Hunt, David
Kelly, Susannah Pfalzer, Cathleen Small, Dave Thomas, Devon Thomas. Obrigado,
Lynn Beighley, por suavizar minha prosa e me chamar de volta ao trabalho quando
me perdi, como Jill Steinberg havia feito na primeira edição.
Leva muito tempo para atualizar um livro. Você volta quando o trabalho é feito e
fica surpreso com quantas coisas mudaram em sua vida. Por outro lado, algumas
coisas não. Obrigado, Ivana Gancheva, minha preciosa amiga.
Irá escrever código que escreve código que escreve código para comida.
ÿ martin rodgers
Introdução
Metaprogramação... parece legal! Parece uma técnica de design para arquitetos corporativos de
alto nível ou uma palavra da moda da moda que encontrou seu caminho nos comunicados à
imprensa.
• Digamos que você queira escrever um programa Ruby que se conecte a um sistema externo
— talvez um serviço da Web ou um programa Java. Com a metaprogramação, você pode
escrever um wrapper que recebe qualquer chamada de método e a encaminha para o sistema
externo. Se alguém adicionar métodos ao sistema externo posteriormente, você não precisa
alterar seu wrapper Ruby; o wrapper suportará os novos métodos imediatamente. Isso é
mágico.
• Talvez você tenha um problema que seria melhor resolvido com uma linguagem de
programação específica para esse problema. Você pode se dar ao trabalho de escrever seu
próprio idioma, analisador personalizado e tudo. Ou você pode simplesmente usar Ruby,
dobrando sua sintaxe até que pareça uma linguagem específica para o seu problema. Você
pode até escrever seu próprio pequeno interpretador que lê o código escrito em sua linguagem
baseada em Ruby a partir de um arquivo.
• Você pode remover agressivamente a duplicação de seu código Ruby, mantendo-o elegante e
limpo. Imagine vinte métodos em uma classe que parecem todos iguais. Que tal definir todos
esses métodos de uma só vez, com apenas algumas linhas de código? Ou talvez você queira
chamar uma sequência de métodos com nomes semelhantes. Você gostaria de uma única
linha curta de código que chame todos os métodos cujos nomes correspondem a um padrão
- como, digamos, todos os métodos que começam com test?
• Você pode estender e torcer o Ruby para atender às suas necessidades, em vez de se adaptar
à linguagem como ela é. Por exemplo, você pode aprimorar qualquer classe (mesmo uma
classe principal como Array) com aquele método que você tanto sente falta, você pode agrupar
Introdução • xviii
funcionalidade de registro em torno de um método que você deseja monitorar, você pode
executar um código personalizado sempre que um cliente herdar de sua classe favorita...
a lista continua. Você está limitado apenas por sua própria imaginação, sem dúvida fértil.
A metaprogramação lhe dá o poder de fazer todas essas coisas. Vamos ver como este livro o
ajudará a aprender sobre isso.
• Depois de entender a pesquisa de método, você pode fazer algumas coisas sofisticadas
com métodos: você pode criar métodos em tempo de execução, interceptar chamadas de
método, rotear chamadas para outro objeto ou até mesmo aceitar chamadas para métodos
que não existem. Todas essas técnicas são explicadas no Capítulo 3, Terça-feira: Métodos,
na página 45.
• Os métodos são membros de uma família maior que também inclui entidades como blocos
e lambdas. O Capítulo 4, Quarta-feira: Blocos, na página 73, é o seu manual de campo
para tudo relacionado a essas entidades. Ele também apresenta um exemplo de como
escrever uma linguagem de domínio específico, uma poderosa ferramenta conceitual que
os programadores Ruby tendem a amar. Este capítulo também vem com sua própria
parcela de truques, explicando como você pode empacotar o código e executá-lo
posteriormente ou como você pode transportar variáveis entre os escopos.
• Falando em escopos, Ruby tem um escopo especial que merece ser visto de perto: o
escopo das definições de classes. Capítulo 5, quinta-feira: Definições de classe, na página
105, fala sobre esse escopo e apresenta algumas das armas mais poderosas do arsenal
de um metaprogramador. Ele também apresenta classes singleton, o último conceito que
você precisa para entender os recursos mais complexos do Ruby.
• Por fim, o Capítulo 6, sexta-feira: Código que escreve código, na página 139, reúne tudo
por meio de um exemplo estendido que usa técnicas de todos os
Três apêndices fecham o livro. O Apêndice 1, Idiomas comuns, na página 217, é uma
coleção de técnicas comuns que não são explicadas em nenhum outro lugar do livro.
Apêndice 2, Linguagens Específicas de Domínio, na página 227, é uma visão rápida de uma
abordagem de programação que é comum entre os desenvolvedores Ruby. O Apêndice 3,
Livro de Feitiços, na página 231, é um catálogo de todos os feitiços do livro, completo com
exemplos de códigos.
"Espere um minuto", posso ouvir você dizendo. "O que diabos são feitiços?" Ah, certo,
desculpe. Deixe-me explicar.
Feitiços
Este livro contém várias técnicas de metaprogramação que você pode usar em seu próprio
código. Algumas pessoas podem chamar esses padrões ou talvez expressões idiomáticas.
Nenhum desses termos é muito popular entre os rubistas, então vou chamá-los de feitiços .
Mesmo que não haja nada de mágico neles, eles se parecem com feitiços mágicos para os
novatos em Ruby.
Você encontrará referências a feitiços em todo o livro. Eu faço referência a um feitiço com a
convenção Classe Macro (117) ou String de Código (141), por exemplo. O número entre
parênteses é a página onde a magia recebe um nome. Se precisar de uma referência rápida
a um feitiço, você o encontrará no Apêndice 3, Livro de Feitiços, na página 231.
Testes
De vez em quando, este livro também lança um questionário para você. Você pode pular
esses questionários e apenas ler a solução, mas provavelmente desejará resolvê-los sozinho
apenas porque são divertidos.
Alguns questionários são exercícios de codificação tradicionais; outros exigem que você saia
do teclado e pense. Todos incluem uma solução, mas a maioria dos questionários tem mais
de uma resposta possível. Por favor, sinta-se livre para ir à loucura e experimentar.
Introdução • xx
Convenções de notação
Este livro está repleto de exemplos de código. Para mostrar que uma linha de código resulta em
um valor, imprimo esse valor como um comentário na mesma linha:
-1.abs # => 1
Se um exemplo de código deve imprimir um resultado em vez de retorná-lo, mostro esse resultado
após o código:
ÿ Testando... testando...
Na maioria dos casos, o texto usa a mesma sintaxe de código que Ruby usa: My Class.my_method
é um método de classe, MyClass::MY_CONSTANT é uma constante definida dentro de uma
classe e assim por diante. Há algumas exceções a esta regra. Primeiro, identifico métodos de
instância com a notação hash , como a documentação do Ruby faz (MyClass#my_method). Isso
é útil para distinguir métodos de classe e métodos de instância. Em segundo lugar, uso um prefixo
de hash para identificar classes singleton (#MySingletonClass).
Ruby tem uma sintaxe flexível, então existem poucas regras universais para coisas como
indentação e uso de parênteses. Os programadores tendem a adotar a sintaxe que consideram
mais legível em cada caso específico. Neste livro, tento seguir as convenções mais comuns. Por
exemplo, pulo parênteses quando chamo um método sem parâmetros (como em my_string.reverse),
mas costumo usar parênteses quando passo parâmetros (como em my_string.gsub("x", "y")).
Parte do código deste livro vem diretamente de bibliotecas de código aberto existentes. Algumas
delas são bibliotecas Ruby padrão, então você já deve tê-las. Você pode instalar os outros com o
comando gem . Por exemplo, se eu mostrar a você um trecho de código do Builder 3.2.2 e você
quiser instalar a biblioteca inteira para explorar seu código-fonte sozinho, poderá usar o gem install
builder -v 3.2.2. Fique atento à versão, pois o código pode ter mudado em versões mais recentes
do Builder.
Para evitar confusão (e tornar o código mais fácil de entender isoladamente), às vezes tomo a
liberdade de editar um pouco o código original. No entanto, farei o possível para manter intacto o
espírito da fonte original.
Testes de unidade
Este livro segue dois desenvolvedores em seu trabalho diário. À medida que a história se
desenrola, você pode perceber que esses dois personagens raramente escrevem testes.
Este livro tolera código não testado?
Por favor, tenha certeza de que não. Na verdade, o rascunho original deste livro incluía
testes de unidade para todos os exemplos de código. No final, descobri que esses
testes desviaram a atenção das técnicas de metaprogramação que são a essência do
livro, então os testes caíram no chão da sala de edição. Isso não significa que você não
deva escrever testes para seus próprios empreendimentos de metaprogramação.
Nas ocasiões em que mostrei o código de teste neste livro, usei a biblioteca da unidade
de teste . Até o Ruby 2.1, test-unit era uma biblioteca padrão. A partir do Ruby 2.2, você
precisa instalá-lo como uma gem, com o comando gem install test-unit.
Versões Ruby
Ruby está mudando e melhorando continuamente. No entanto, essa mesma fluidez pode
ser problemática quando você tenta um trecho de código na versão mais recente da
linguagem, apenas para descobrir que não funciona mais. Isso não é muito comum, mas
pode acontecer com a metaprogramação, que leva Ruby aos seus limites.
Este livro foi escrito para Ruby 2. Enquanto escrevo, Ruby 2.1 é a versão estável mais
recente da linguagem e é mais compatível com Ruby 2.0. Algumas pessoas ainda
executam versões mais antigas do Ruby, que perdem alguns recursos importantes do
2.x—notadamente, Refinamentos e Módulo#prepend. No texto, me referirei ao Ruby 2.x
e direi quais recursos foram introduzidos no Ruby 2.1 ou no Ruby 2.0.
Quando falo sobre as versões do Ruby, estou falando sobre o interpretador “oficial” (às
vezes chamado de MRI para o Ruby Interpreter1 do Matz ). Existem muitas
implementações alternativas de Ruby. Dois dos mais populares são o JRuby, que roda
na Java Virtual Machine,2 e o Rubinius.3 As implementações alternativas geralmente
levam algumas versões para alcançar o MRI — portanto, se você usar um deles, esteja
ciente de que alguns dos exemplos neste livro pode ainda não funcionar em seu
interpretador.
Edições de livros
A primeira edição deste livro focou no Ruby 1.8, que desde então foi obsoleto. Atualizei
o texto para refletir os novos recursos do Ruby, especialmente os que foram introduzidos
pelo Ruby 2.x.
1. http://www.ruby-lang.org
2. http://jruby.codehaus.org
3. http://rubini.us/
Introdução • xxii
Além das mudanças na linguagem e nas bibliotecas, algumas de minhas opiniões pessoais também
mudaram desde a primeira edição deste livro. Aprendi a desconfiar de algumas técnicas, como
Métodos Fantasmas (57), e a gostar mais de outras, como Métodos Dinâmicos (51). Partes do novo
texto refletem essas mudanças de coração.
Finalmente, esta segunda edição é uma limpeza geral do texto da primeira edição. Atualizei muitos
exemplos que estavam usando gems e código-fonte que foram esquecidos ou alterados desde o livro
anterior; Adicionei alguns feitiços e removi alguns outros que não parecem mais muito relevantes;
Reduzi o tom da “história” no texto quando acrescentava muitas palavras a longas explicações
técnicas; e repassei cada frase novamente, corrigindo coisas que precisavam ser corrigidas e
abordando errata e sugestões dos leitores.
Seja você um novo leitor ou um fã da primeira edição, espero que goste do resultado.
Sobre você
A maioria das pessoas considera a metaprogramação um tópico avançado. Para brincar com as
construções de um programa Ruby, você precisa saber como essas construções funcionam em
primeiro lugar. Como você sabe se é suficientemente “avançado”
Rubyist para lidar com metaprogramação? Bem, se você conseguir entender o código no primeiro
capítulo sem muita dificuldade, estará bem equipado para seguir em frente.
Se você não está confiante em suas habilidades, pode fazer um autoteste simples. Que tipo de
código você escreveria para iterar em uma matriz? Se você pensou sobre cada método, então você
conhece Ruby o suficiente para seguir o texto a seguir. Se você pensou na palavra-chave for ,
provavelmente é novo em Ruby. No segundo caso, você ainda pode embarcar nessa aventura de
metaprogramação - basta levar um texto introdutório de Ruby com você ou fazer o excelente tutorial
interativo no Try Ruby! local.4
4. http://tryruby.org
Parte I
Metaprogramação Ruby
www.allitebooks.com
Machine Translated by Google
CAPÍTULO 1
A palavra M
Em breve chegaremos a uma definição mais precisa, mas esta servirá por enquanto. O que quero
dizer com “código que escreve código” e como isso é útil no seu trabalho diário? Antes de
responder a essas perguntas, vamos dar um passo para trás e examinar as próprias linguagens
de programação.
Em outras linguagens, como Ruby, o tempo de execução é mais como um mercado movimentado.
A maioria das construções de linguagem ainda está lá, zumbindo por toda parte. Você pode até
chegar a uma construção de linguagem e fazer perguntas sobre ela mesma. Isso se chama
introspecção.
Capítulo 1. A Palavra M • 4
the_m_word/introspection.rb
saudação de classe
def initialize(texto) @texto
= fim do texto
definitivamente bem-vindo
@texto
fim
fim
meu_objeto = Saudação.new("Olá")
Eu defini uma classe Greeting e criei um objeto Greeting . Agora posso voltar para as
construções de linguagem e fazer perguntas.
Perguntei a my_object sobre sua classe e ele respondeu em termos inequívocos: “Sou uma
saudação”. Agora posso pedir à classe uma lista de seus métodos de instância.
Mais uma vez, a resposta do objeto foi alta e clara. Como objetos e classes são cidadãos de
primeira classe em Ruby, você pode obter muitas informações da execução do código.
No entanto, esta é apenas metade da imagem. Claro, você pode ler construções de
linguagem em tempo de execução, mas e quanto a escrevê- las? E se você quiser adicionar
novos métodos de instância a Greeting, além de welcome, enquanto o programa estiver em
execução? Você pode estar se perguntando por que diabos alguém iria querer fazer isso.
Permita-me explicar contando uma história.
A biblioteca de Bob mapeia cada classe para uma tabela de banco de dados e cada objeto para um registro.
Quando Bob cria um objeto ou acessa seus atributos, o objeto gera uma string de SQL e a envia para o banco de
dados. Toda essa funcionalidade é agrupada em uma classe:
the_m_word/orm.rb
classe Entidade
attr_reader :table, :ident
def get(col)
Database.sql ("SELECT #{col} FROM #{@table} WHERE id=#{@ident}")[0][0] end
fim
No banco de dados de Bob, cada tabela possui uma coluna id . Cada Entidade armazena o conteúdo desta coluna
e o nome da tabela a que se refere. Quando Bob cria
uma Entidade, a Entidade se salva no banco de dados. Entity#set gera SQL que atualiza o valor de uma coluna e
Entity#get gera SQL que retorna o valor de uma coluna. (Caso você se importe, a classe Database de Bob retorna
conjuntos de registros como matrizes de matrizes.)
Bob agora pode subclassificar Entity para mapear para uma tabela específica. Por exemplo, a classe Movie é
mapeada para uma tabela de banco de dados chamada movie:
título def
obter fim do
"título"
Capítulo 1. A Palavra M • 6
diretor de definição
obter o fim do
"diretor"
fim
Um filme tem dois métodos para cada atributo: um leitor, como Movie#title, e um gravador,
como Movie#title=. Bob agora pode carregar um novo filme no banco de dados ativando o
interpretador interativo Ruby e digitando o seguinte:
movie = Movie.new(1)
movie.title = "Doutor Strangelove" movie.director
= "Stanley Kubrick"
Esse código cria um novo registro em filmes, que possui valores 1, Doctor Strangelove e
Stanley Kubrick para as colunas id, title e director, respectivamente. (Lembre-se que em
Ruby, movie.title = "Doctor Strangelove" é na verdade uma chamada disfarçada para o
método title=—o mesmo que movie.title=("Doctor Strangelove").)
Orgulhoso de si mesmo, Bob mostra o código para seu colega mais velho e experiente, Bill.
Bill olha para a tela por alguns segundos e começa a quebrar o orgulho de Bob em
pedacinhos. “Há muita duplicação neste código”, diz Bill.
“Você tem uma tabela de filmes com uma coluna de título no banco de dados e uma classe
Movie com um campo @title no código. Você também tem um método de título , um método
de título = e duas constantes de string "título" . Você pode resolver esse problema com muito
menos código se polvilhar um pouco de metaprogramação sobre ele.”
Entre na metaprogramação
Por sugestão de seu amigo codificador especialista, Bob procura uma solução baseada em
metaprogramação. Ele encontra exatamente isso na biblioteca Active Record, uma popular
biblioteca Ruby que mapeia objetos para tabelas de banco de dados. Após um breve tutorial,
Bob é capaz de escrever a versão Active Record da classe Movie :
Sim, é simples assim. Bob acabou de subclassificar a classe ActiveRecord::Base . Ele não
precisou especificar uma tabela para mapear Movies . Melhor ainda, ele não precisava
escrever métodos chatos e quase idênticos, como título e diretor. Tudo simplesmente
funciona:
filme = Movie.create
movie.title = "Doutor Strangelove" movie.title #
=> "Doutor Strangelove"
Metaprogramação e Ruby • 7
O código anterior cria um objeto Movie que agrupa um registro na tabela de filmes e acessa a coluna de
título do registro chamando Movie#title e Movie#title=.
Mas esses métodos não podem ser encontrados no código-fonte. Como pode o título
e title= existem se não estiverem definidos em nenhum lugar? Você pode descobrir observando como o
Active Record funciona.
A parte do nome da tabela é direta: o Active Record examina o nome da classe por meio de introspecção
e aplica algumas convenções simples.
Como a classe é chamada de Movie, o Active Record a mapeia para uma tabela chamada de filmes.
(Esta biblioteca sabe como encontrar plurais para palavras em inglês.)
E quanto a métodos como title= e title, que acessam atributos de objeto (métodos de acesso, para
abreviar)? É aqui que entra a metaprogramação: Bob não precisa escrever esses métodos. O Active
Record os define automaticamente, após inferir seus nomes do esquema do banco de dados.
ActiveRecord::Base lê o esquema em tempo de execução, descobre que a tabela de filmes tem duas
colunas denominadas título e diretor e define métodos de acesso para dois atributos do mesmo
nome. Isso significa que o Active Record define métodos como Movie#title e
Este é o “yang” para a introspecção “yin”: em vez de apenas ler as construções da linguagem, você está
escrevendo nelas. Se você acha que esse é um recurso extremamente poderoso, você está certo.
Os autores do Active Record aplicaram esse conceito. Em vez de escrever métodos de acesso para os
atributos de cada classe, eles escreveram um código que define esses métodos em tempo de execução
para qualquer classe herdada de ActiveRecord::Base. Foi isso que quis dizer quando falei sobre “escrever
código que escreve código”.
Você pode pensar que isso é algo exótico e raramente usado - mas se você olhar para o Ruby, como
estamos prestes a fazer, verá que ele é usado com frequência.
Metaprogramação e Ruby
Lembre-se de nossa conversa anterior sobre cidades fantasmas e mercados? Se você deseja manipular
construções de linguagem, essas construções devem existir em tempo de execução.
A este respeito, algumas línguas são melhores do que outras. Dê uma olhada rápida em alguns idiomas e
quanto controle eles oferecem a você em tempo de execução.
Capítulo 1. A Palavra M • 8
Na metaprogramação, você escreve código que escreve código. Mas não é isso que os geradores
e compiladores de código fazem? Por exemplo, você pode escrever código Java anotado e, em
seguida, usar um gerador de código para gerar arquivos de configuração XML. Em um sentido
amplo, essa geração de XML é um exemplo de metaprogramação. Na verdade, muitas pessoas
pensam em geração de código quando a palavra “M” aparece.
Esse tipo específico de metaprogramação implica que você use um programa para gerar ou
manipular um segundo programa distinto - e então execute o segundo programa. Depois de executar
o gerador de código, você pode realmente ler o código gerado e (se quiser testar sua tolerância à
dor) até mesmo modificá-lo manualmente antes de finalmente executá-lo. Isso também é o que
acontece nos bastidores com os modelos C++: o compilador transforma seus modelos em um
programa C++ regular antes de compilá-los e, em seguida, você executa o programa compilado.
Neste livro, vou me ater a um significado diferente de metaprogramação, focando no código que
manipula a si mesmo em tempo de execução. Você pode pensar nisso como metaprogramação
dinâmica para distingui-la da metaprogramação estática de geradores e compiladores de código.
Embora você possa fazer uma certa quantidade de metaprogramação dinâmica em muitas
linguagens (por exemplo, usando manipulação de bytecode em Java), apenas algumas linguagens
permitem que você faça isso de maneira perfeita e elegante - e Ruby é uma delas.
Ruby é uma linguagem muito amigável à metaprogramação. Ele não tem nenhum tempo de
compilação e a maioria das construções em um programa Ruby está disponível em tempo
de execução. Você não se depara com uma parede de tijolos dividindo o código que está
escrevendo do código que seu computador executa quando você executa o programa.
Existe apenas um mundo.
Metaprogramação e Ruby • 9
a fonte de frameworks populares, em sua biblioteca favorita e até mesmo em pequenos exemplos
de blogs aleatórios.
Na verdade, a metaprogramação está tão profundamente arraigada na linguagem Ruby que nem
mesmo está nitidamente separada da programação “regular”. Você não pode olhar para um
programa Ruby e dizer: “Esta parte aqui é metaprogramação, enquanto esta outra parte não é”.
De certo modo, a metaprogramação é parte da rotina do trabalho de todo programador Ruby.
Depois de dominá-lo, você poderá explorar todo o poder do idioma.
Há também outra razão menos óbvia pela qual você pode querer aprender metaprogramação. Por
mais simples que Ruby pareça à primeira vista, você pode rapidamente ficar sobrecarregado com
suas sutilezas. Mais cedo ou mais tarde, você se perguntará: “Um objeto pode chamar um método
privado em outro objeto da mesma classe?” ou “Como você pode definir métodos de classe
importando um módulo?” Em última análise, todos os comportamentos aparentemente
complicados de Ruby derivam de algumas regras simples. Por meio da metaprogramação, você
pode ter uma visão íntima da linguagem, aprender essas regras e obter respostas para suas
perguntas incômodas.
Agora que você sabe do que se trata a metaprogramação, está pronto para mergulhar de cabeça.
CAPÍTULO 2
Dê uma olhada em qualquer programa Ruby e você verá objetos em todos os lugares. Dê uma
olhada e você verá que os objetos são cidadãos de um mundo maior que também inclui outras
construções de linguagem, como classes, módulos e variáveis de instância.
A metaprogramação manipula essas construções de linguagem, então você precisa saber
algumas coisas sobre elas logo de cara.
Você está prestes a mergulhar no primeiro conceito: todas essas construções vivem juntas em
um sistema chamado modelo de objeto. O modelo de objeto é onde você encontrará respostas
para perguntas como “De qual classe vem esse método?” e “O que acontece quando eu incluo
este módulo?” Aprofundando-se no modelo de objeto, no cerne do Ruby, você aprenderá
algumas técnicas poderosas e também como evitar algumas armadilhas.
Segunda-feira promete ser um dia cheio, então silencie seu aplicativo de mensagens, pegue
um donut e prepare-se para começar.
Turmas Abertas
Onde você refatora algum código legado e aprende um ou dois truques ao longo do caminho.
Bem-vindo ao seu novo emprego como programador Ruby. Depois de se instalar em sua nova
mesa com um brilhante computador de última geração e uma xícara de café, você pode
conhecer Bill, seu mentor. Sim, você tem sua primeira tarefa em sua nova empresa, uma nova
linguagem para trabalhar e um novo parceiro de programação em par.
Você está usando Ruby há apenas algumas semanas, mas Bill está lá para ajudá-lo.
Ele tem muita experiência em Ruby e parece um cara legal. Você vai se divertir trabalhando
com ele - pelo menos até sua primeira briga mesquinha sobre as convenções de codificação.
O chefe quer que você e Bill revisem a fonte de um pequeno aplicativo chamado Bookworm.
A empresa desenvolveu o Bookworm para gerenciar sua grande biblioteca interna de livros.
O programa cresceu lentamente fora de controle, pois muitos desenvolvedores diferentes
adicionaram seus recursos favoritos à mistura, desde visualizações de texto até gerenciamento
de revistas e rastreamento de livros emprestados. Como resultado, o código-fonte do
Bookworm é uma bagunça. Você e Bill foram selecionados para
passar pelo código e limpá-lo. O chefe chamou de “apenas um trabalho fácil de refatoração”.
Você e Bill estão navegando pelo código-fonte do Bookworm por alguns minutos quando
você vê sua primeira oportunidade de refatoração. Bookworm tem uma função que formata
títulos de livros para impressão em etiquetas de fita antiquadas.
Ele retira toda a pontuação e caracteres especiais de uma string, deixando apenas caracteres
alfanuméricos e espaços:
object_model/
alphanumeric.rb def
to_alphanumeric(s) s.gsub(/
[^\w\s]/, '') end
Este método também vem com seu próprio teste de unidade (lembre-se de gem install test-
unit antes de tentar executá-lo no Ruby 2.2 e posterior):
requer 'teste/unidade'
“Esse método to_alphanumeric não é muito orientado a objetos, não é?” Bill diz. “Essa é uma
funcionalidade genérica que faz sentido para todas as strings. Seria melhor se pudéssemos
pedir a uma String para se converter, em vez de passá-la por um método externo.”
Mesmo que você seja o cara novo no quarteirão, você não pode deixar de interromper.
“Mas esta é apenas uma String normal. Para adicionar métodos a ela, teríamos que escrever
toda uma nova classe AlphanumericString . Não tenho certeza se valeria a pena.”
“Acho que tenho uma solução mais simples para esse problema”, responde Bill. Ele abre a
classe String e planta o método to_alphanumeric lá:
classe String
def to_alphanumeric
gsub(/[^\w\s]/, '') fim
fim
Aulas Abertas • 13
requer 'teste/unidade'
Para entender o truque anterior, você precisa saber uma ou duas coisas sobre classes
Ruby. Bill está muito feliz em ensiná-lo….
Em Ruby, não há distinção real entre o código que define uma classe e o código de
qualquer outro tipo. Você pode colocar qualquer código que quiser em uma definição de classe:
3. vezes faça
classe C
coloca "Olá" final
fim
ÿ Olá
Olá
Olá
Ruby executou o código dentro da classe da mesma forma que executaria qualquer outro
código. Isso significa que você definiu três classes com o mesmo nome? A resposta é
não, como você mesmo pode descobrir rapidamente:
classe D
def x; 'x'; fim fim
classe D
desafiar ; 'y'; fim fim
obj = D.novo
obj.x # => "x"
obj.y # => "y"
Quando o código anterior menciona a classe D pela primeira vez, ainda não existe
nenhuma classe com esse nome. Assim, Ruby intervém e define a classe - e o método x .
Na segunda menção, a classe D já existe, então Ruby não precisa defini-la. Em vez disso,
ele reabre a classe existente e define um método chamado y lá.
De certo modo, a palavra-chave class em Ruby é mais como um operador de escopo do que uma declaração
de classe. Sim, ele cria classes que ainda não existem, mas você pode argumentar que isso é um efeito
colateral agradável. Para a classe, o trabalho principal é movê-lo no contexto da classe, onde você pode definir
métodos.
Essa distinção sobre a palavra-chave class não é um detalhe acadêmico. Isso tem uma consequência prática
importante: você sempre pode reabrir classes existentes — até mesmo classes de biblioteca padrão, como
String ou Array — e modificá-las rapidamente . Você pode chamar essa técnica de Open Class.
Feitiço: Classe Aberta
Para ver como as pessoas usam o Open Classes na prática, vejamos um exemplo rápido de uma biblioteca
da vida real.
O Exemplo do Dinheiro
Você pode encontrar um exemplo de Classes Abertas na gema do dinheiro , um conjunto de classes utilitárias
para gerenciar dinheiro e moedas. Veja como você cria um objeto Money :
object_model/money_example.rb
requer "dinheiro"
Como atalho, você também pode converter qualquer número em um objeto Money chamando
Numérico#para_dinheiro:
object_model/money_example.rb
requer "dinheiro"
standard_price = 100.to_money("USD")
standard_price.format # => "$100,00"
Como Numeric é uma classe Ruby padrão, você pode se perguntar de onde vem o método Numeric#to_money .
Procure na fonte da gema Money e você encontrará o código que reabre o Numeric e define esse método:
classe numérica
def to_money(moeda = nil)
Money.from_numeric(self, moeda || Money.default_currency) end
fim
Por mais legais que sejam, no entanto, as Classes Abertas têm um lado sombrio - um que você está prestes a
experimentar.
Aulas Abertas • 15
object_model/
replace.rb def replace(array, original, substituição)
array.map {|e| e == originais ? substituição: e } fim
Em vez de focar no funcionamento interno de replace, você pode examinar os testes de unidade do
Book worm para ver como esse método deve ser usado:
def test_replace
original = ['um', 'dois', 'um', 'três'] substituído =
substituir(original, 'um', 'zero') assert_equal ['zero', 'dois',
'zero', ' três'], final substituído
Desta vez, você sabe o que fazer. Você pega o teclado (aproveitando os reflexos mais lentos de Bill) e
move o método para a classe Array :
class Array
def replace(original, substituição) self.map {|
e| e == originais ? substituição: e } fim
fim
Em seguida, você altera todas as chamadas para substituir em chamadas para Array#replace. Por
exemplo, o teste se torna o seguinte:
def test_replace
original = ['um', 'dois', 'um', 'três'] ÿ substituído =
original.replace('um', 'zero') assert_equal ['zero', 'dois', 'zero',
'três'], final substituído
Você salva o teste, executa o conjunto de testes de unidade do Bookworm e... uau! Enquanto
test_replace passa, outros testes falham inesperadamente. Para tornar as coisas mais complexas, os
testes com falha parecem não ter nada a ver com o código que você acabou de editar. O que da?
“Acho que sei o que aconteceu”, diz Bill. Ele ativa o irb, o interpretador interativo do Ruby, e obtém uma
lista de todos os métodos no Array padrão do Ruby que começam com re:
Ao observar a saída do irb, você identifica o problema. A classe Array já possui um método
chamado replace. Ao definir seu próprio método de substituição , você inadvertidamente substituiu
a substituição original, um método do qual alguma outra parte do Bookworm estava contando.
Este é o lado negro das Classes Abertas: se você casualmente adicionar pedaços de funcionalidade
às classes, você pode acabar com bugs como o que acabou de encontrar. Algumas pessoas
desaprovariam esse tipo de correção imprudente de classes e se refeririam ao código anterior com
um nome pejorativo: Feitiço: Monkeypatch eles o chamariam de Monkeypatch.
Agora que você sabe qual é o problema, você e Bill renomeiam sua própria versão de Array#replace
para Array#substitute e corrigem os testes e o código de chamada. Você aprendeu uma lição da
maneira mais difícil, mas isso não estragou sua atitude. No mínimo, esse incidente despertou sua
curiosidade sobre as aulas de Ruby. É hora de você aprender a verdade sobre eles.
Sua experiência recente com Classes Abertas (14) indica que há mais nas classes Ruby do que
aparenta. Muito mais, na verdade. Algumas das verdades sobre as classes Ruby e o modelo de
objeto em geral podem até ser um choque quando você as descobre pela primeira vez.
Há muito a aprender sobre o modelo de objeto, mas não deixe que toda essa teoria o desencoraje.
Se você entender a verdade sobre classes e objetos, estará no caminho certo para se tornar um
mestre da metaprogramação. Vamos começar com o básico: objetos.
O que há em um objeto
classe MinhaClasse
def meu_método
@v = 1
fim
fim
obj = MyClass.new
obj.class # => MinhaClasse
Olhe para o objeto obj . Se você pudesse abrir o interpretador Ruby e olhar para obj, o que você
veria?
Monkeypatching é mau?
Na seção anterior, você aprendeu que Monkeypatch é um termo pejorativo. No entanto, o mesmo termo
é por vezes utilizado em sentido positivo, para se referir às Classes Abertas (14) em geral. Você pode
argumentar que existem dois tipos de Monkeypatches (16). Algumas acontecem por engano, como
aquela que você e Bill vivenciaram, e são invariavelmente más.
Outros são aplicados propositalmente e são bastante úteis — especialmente quando você deseja
adequar uma biblioteca existente às suas necessidades.
Mesmo quando você acha que está no controle, você ainda deve Monkeypatch com cuidado. Como
qualquer outra modificação global, Monkeypatches pode ser difícil de rastrear em uma grande base de
código. Para minimizar os perigos de Monkeypatches, verifique cuidadosamente os métodos existentes
em uma classe antes de definir seus próprios métodos. Além disso, esteja ciente de que algumas
mudanças são mais arriscadas do que outras. Por exemplo, adicionar um novo método geralmente é
mais seguro do que modificar um existente.
Você verá alternativas ao Monkeypatching ao longo do livro. Em particular, veremos em breve que você
pode tornar os Monkeypatches mais seguros usando Refinamentos (36). Infelizmente, os refinamentos
ainda são um novo recurso e não há garantia de que eles substituirão completamente os Monkeypatches
tradicionais.
Variáveis de instância
Mais importante ainda, os objetos contêm variáveis de instância. Mesmo que você não
deva espiá-los, você pode fazer isso de qualquer maneira chamando Object#in
instance_variables. O objeto do exemplo anterior tem apenas uma única variável de
instância:
obj.my_method
obj.instance_variables # => [:@v]
Métodos
Além de possuir variáveis de instância, os objetos também possuem métodos. Você pode
obter uma lista dos métodos de um objeto chamando Object#methods. A maioria dos
objetos (incluindo obj no exemplo anterior) herda vários métodos de Object, então isso
lista de métodos geralmente é bastante longa. Você pode usar Array#grep para verificar se my_method
está na lista de obj :
Se você pudesse abrir o interpretador Ruby e olhar para obj, você notaria que este objeto realmente não
carrega uma lista de métodos. Um objeto contém suas variáveis de instância e uma referência a uma
classe (porque todo objeto pertence a uma classe ou, em linguagem OO, é uma instância de uma
classe)... mas nenhum método. Onde estão os métodos?
Seu amigo de programação dupla, Bill, vai até o quadro branco mais próximo e começa a rabiscar tudo
nele. “Pense nisso por um minuto”, diz ele, desenhando o seguinte diagrama. “Objetos que compartilham
a mesma classe também compartilham os mesmos métodos, então os métodos devem ser armazenados
na classe, não no objeto.”
Antes de continuar, você deve estar ciente de uma distinção importante sobre métodos. Você pode dizer
corretamente que “obj tem um método chamado my_method”, o que significa que você pode chamar
obj.my_method(). Por outro lado, você não deve dizer que “MyClass tem um método chamado
my_method”. Isso seria confuso, porque implicaria que você pode chamar MyClass.my_method() como
se fosse um método de classe.
Para remover a ambigüidade, você deve dizer que my_method é um método de instância (não apenas
“um método”) de MyClass, o que significa que está definido em MyClass e você realmente precisa de um
objeto (ou instância) de MyClass para chamá-lo. É o mesmo método, mas quando você fala sobre a
classe, você o chama de método de instância, e quando você fala sobre o objeto, você simplesmente o
chama de método. Lembre-se dessa distinção e você não ficará confuso ao escrever um código
introspectivo como este:
Isso é tudo o que você realmente precisa saber sobre objetos, variáveis de instância e métodos.
Mas, como trouxemos as classes para o cenário, também podemos analisá-las mais de perto.
Aqui está possivelmente a coisa mais importante que você aprenderá sobre o modelo de objeto
Ruby: as próprias classes nada mais são do que objetos.
Como uma classe é um objeto, tudo o que se aplica a objetos também se aplica a classes.
Classes, como qualquer objeto, têm sua própria classe, chamada — você adivinhou — Classe:
Você pode estar familiarizado com Class de outras linguagens orientadas a objetos. Em
linguagens como Java, no entanto, uma instância de Class é apenas uma descrição somente
leitura da classe. Por outro lado, uma classe em Ruby é literalmente a própria classe, e você pode
manipulá-la como faria com qualquer outro objeto.
Por exemplo, no Capítulo 5, Quinta-feira: Definições de classe, na página 105, você verá que
pode chamar Class.new para criar novas classes enquanto seu programa está em execução.
Essa flexibilidade é típica da metaprogramação de Ruby: enquanto outras linguagens permitem
que você leia informações relacionadas à classe, Ruby permite que você escreva essas
informações em tempo de execução.
Como qualquer objeto, as classes também possuem métodos. Lembra do que você aprendeu em
O que há em um objeto, na página 16? Os métodos de um objeto também são os métodos de
instância de sua classe. Por sua vez, isso significa que os métodos de uma classe são os métodos
de instância de Class:
Você já conhece o novo porque o usa o tempo todo para criar objetos.
O método allocate desempenha um papel de suporte para new. Provavelmente, você nunca
precisará se preocupar com isso.
Por outro lado, você usará muito o método da superclasse . Esse método está relacionado a um
conceito com o qual você provavelmente está familiarizado: herança. Uma classe Ruby herda de
sua superclasse. Dê uma olhada no seguinte código:
A classe Array herda de Object, que é o mesmo que dizer “um array é um objeto”.
Object contém métodos que geralmente são úteis para qualquer objeto — como to_s,
que converte um objeto em uma string. Por sua vez, Object herda de BasicObject, a
raiz da hierarquia de classes Ruby, que contém apenas alguns métodos essenciais.
(Você aprenderá mais sobre BasicObject posteriormente neste livro.)
Módulos
Respire fundo e confira a superclasse da própria classe Class :
Classe.superclasse # => Módulo
De fato, classes e módulos estão tão intimamente relacionados que Ruby poderia
facilmente se safar com uma única “coisa” que desempenhe ambos os papéis. A
principal razão para fazer uma distinção entre módulos e classes é a clareza:
escolhendo cuidadosamente uma classe ou um módulo, você pode tornar seu código
mais explícito. Normalmente, você escolhe um módulo quando deseja incluí-lo em
algum lugar e escolhe uma classe quando deseja instanciá-lo ou herdá-lo. Então,
embora você possa usar classes e módulos de forma intercambiável em muitas
situações, você provavelmente vai querer deixar suas intenções claras usando-os para diferentes
propósitos.
Juntando tudo
Bill conclui sua palestra com um trecho de código e um diagrama de quadro branco:
classe MinhaClasse;
end obj1 = MyClass.new
obj2 = MyClass.new
"Ver?" Bill pergunta, apontando para o diagrama anterior. “Classes e objetos regulares vivem juntos
e felizes.”
Há mais um detalhe interessante no tema “Classes são objetos”: como você faz com qualquer outro
objeto, você se apega a uma classe com uma referência. Uma variável pode referenciar uma classe
como qualquer outro objeto:
minha_classe = MinhaClasse
MyClass e my_class são referências à mesma instância de Class — a única diferença é que
my_class é uma variável, enquanto MyClass é uma constante. Em outras palavras, assim como as
classes nada mais são do que objetos, os nomes das classes nada mais são do que constantes.
Então, vamos olhar mais de perto as constantes.
Constantes
Qualquer referência que comece com letra maiúscula, incluindo nomes de classes e módulos, é
uma constante. Você pode se surpreender ao saber que uma constante Ruby é realmente muito
semelhante a uma variável - na medida em que você pode alterar o valor de uma constante,
embora receba um aviso do interpretador. (Se você estiver em um estado de espírito destrutivo,
você pode até quebrar o Ruby além do reparo alterando o valor do nome da classe String .)
Se você pode alterar o valor de uma constante, como uma constante é diferente de uma variável?
A única diferença importante tem a ver com seu escopo. O escopo das constantes segue suas
próprias regras especiais, como você pode ver no exemplo a seguir.
módulo MeuMódulo
MinhaConstante = 'Constante externa'
classe MinhaClasse
MyConstant = 'Constante interna' fim
fim
Bill tira um guardanapo do bolso da camisa e esboça as constantes desse código. Você pode ver o
resultado na figura a seguir.
Você acabou de aprender que as constantes são aninhadas como diretórios e arquivos. Também
como diretórios e arquivos, as constantes são identificadas exclusivamente por seus caminhos.
Os caminhos das constantes usam dois-pontos duplos como separador (isso é semelhante ao
operador de escopo em C++):
módulo M
classe C
X = 'uma constante'
fim
C::X # => "uma constante"
fim
Se você estiver bem dentro da árvore de constantes, poderá fornecer o caminho absoluto para uma
constante externa usando dois-pontos à esquerda como raiz:
módulo M
Y = 'uma constante em M'
Y # => "uma constante em M" #
::Y => "uma constante no nível raiz"
fim
módulo M
classe C
módulo M2
Module.nesting end # => [M::C::M2, M::C, M]
end
fim
As semelhanças entre as constantes do Ruby e os arquivos vão ainda mais longe: você
pode usar módulos para organizar suas constantes, da mesma forma que usa diretórios
para organizar seus arquivos. Vejamos um exemplo.
O exemplo do
Rake As versões mais antigas do Rake, o popular sistema de construção do Ruby, definiam
classes com nomes óbvios, como Task e FileTask. Esses nomes tinham uma boa chance
de entrar em conflito com outros nomes de classe de diferentes bibliotecas. Para evitar
conflitos, Rake passou a definir essas classes dentro de um módulo Rake :
gems/rake-0.9.2.2/lib/rake/task.rb
módulo de ancinho
classe tarefa
# ...
Agora, o nome completo da classe Task é Rake::Task, que dificilmente entrará em conflito
com o nome de outra pessoa. Um módulo como o Rake, que existe apenas para ser um
contêiner de constantes, é chamado de Namespace. Soletrar: Namespace
Essa mudança para Namespaces tinha um problema: se alguém tivesse um arquivo de construção
do Rake antigo por aí—um que ainda referenciasse os nomes de classe anteriores, sem
Namespaced—esse arquivo não funcionaria com uma versão atualizada do Rake. Por esse
motivo, o Rake manteve a compatibilidade com arquivos de compilação mais antigos por um
tempo. Ele fez isso fornecendo uma opção de linha de comando chamada classic-namespace que
carregava um arquivo de origem adicional. Este arquivo de origem atribuiu os novos nomes de
constantes mais seguros aos antigos e inseguros:
gems/rake-0.9.2.2/lib/rake/classic_namespace.rb Tarefa
= Rake::Tarefa
FileTask = Rake::FileTask
FileCreationTask = Rake::FileCreationTask
# ...
Quando esse arquivo foi carregado, Task e Rake::Task acabaram fazendo referência à mesma
instância de Class, portanto, um arquivo de compilação poderia usar qualquer uma das constantes
para se referir à classe. Algumas versões depois, Rake presumiu que todos os usuários haviam
migrado seu arquivo de compilação e removeu a opção.
Digressão suficiente sobre constantes. Vamos voltar aos objetos e classes e encerrar o que você
acabou de aprender.
O que é uma aula? É um objeto (uma instância de Class), mais uma lista de métodos de instância
e um link para uma superclasse. Classe é uma subclasse de Módulo, então uma classe também
é um módulo. Se isso for confuso, reveja a Figura 2, Classes são apenas objetos, na página 21.
Esses são métodos de instância da classe Class . Como qualquer objeto, uma classe tem seus
próprios métodos, como new. Também como qualquer objeto, as classes devem ser acessadas
por meio de referências. Você já tem uma referência constante para cada classe: o nome da
classe.
“Isso é praticamente tudo o que há para saber sobre objetos e classes”, diz Bill.
“Se você pode entender isso, você está no caminho certo para entender a metaprogramação.
Agora, vamos voltar ao código.”
Leva apenas um curto período de tempo para você ter a chance de aplicar seu novo conhecimento
sobre as aulas. Examinando o código-fonte do Bookworm, você se depara com uma classe que
representa um trecho de texto de um livro:
TEXTO da aula
# ...
Os nomes das classes Ruby são convencionalmente maiúsculas e minúsculas Pascal: as palavras
são concatenadas com a primeira letra de cada maiúscula: ThisTextIsPascalCased, então você
renomeia a classe Text:
Texto da classe
Você altera o nome da classe em todos os lugares em que ela é usada, executa os testes de unidade
e, surpresa!, os testes falham com uma mensagem de erro enigmática:
“D'oh! Claro que é”, você exclama. Bill está tão intrigado quanto você, então vocês dois levam algum
tempo para encontrar a causa do problema. Acontece que o aplicativo Bookworm requer uma versão
antiga da popular biblioteca Action Mailer. O Action Mailer, por sua vez, usa uma biblioteca de
formatação de texto que define um módulo chamado — você adivinhou — Text:
texto do módulo
É aí que está o problema: como Text já é o nome de um módulo, Ruby reclama que não pode ser
também o nome de uma classe ao mesmo tempo.
De certa forma, você teve sorte de que esse conflito de nomes fosse facilmente aparente. Se o
Action Mailer's Text fosse uma classe, talvez você nunca tivesse notado que esse nome já existia.
Em vez disso, você teria inadvertidamente Monkeypatched (16) a classe Text existente . Nesse
ponto, apenas seus testes de unidade o protegeriam de possíveis bugs.
Corrigir o conflito entre sua classe Text e o módulo Text do Action Mailer é tão fácil quanto agrupar
sua classe em um Namespace (23):
módulo Bookworm
Texto da classe
Foi muito aprendizado em uma única sessão. Você merece uma pausa e uma xícara de café - e um
pequeno teste.
Carregando e Requerendo
load('motd.rb')
O uso de carga, no entanto, tem um efeito colateral. O arquivo motd.rb provavelmente define variáveis e
classes. Embora as variáveis saiam do escopo quando o arquivo termina de carregar, as constantes não.
Como resultado, motd.rb pode poluir seu programa com os nomes de suas próprias constantes—em
particular, nomes de classe.
Você pode forçar o motd.rb a manter suas constantes para si mesmo passando um segundo argumento
opcional para carregar:
load('motd.rb', verdadeiro)
Se você carregar um arquivo desta forma, o Ruby cria um módulo anônimo, usa esse módulo como um
Namespace para conter todas as constantes do motd.rb e então destrói o módulo.
O método require é bem parecido com load, mas tem um propósito diferente. Você usa load para executar o
código e require para importar bibliotecas. É por isso que require não tem um segundo argumento: esses
nomes de classe restantes são provavelmente a razão pela qual você importou o arquivo em primeiro lugar.
Além disso, é por isso que require tenta apenas uma vez para carregar cada arquivo, enquanto load executa
o arquivo novamente toda vez que você o chama.
De volta a A verdade sobre as classes, na página 19, Bill mostrou como os objetos e
as classes estão relacionados. Como exemplo, ele usou um trecho de código e este
diagrama de quadro branco:
classe MinhaClasse;
end obj1 = MyClass.new
obj2 = MyClass.new
obj3 = MyClass.new
obj3.instance_variable_set("@x", 10)
Solução do questionário
Sua versão aprimorada do diagrama original está na Figura 3, diagrama de Bill, aprimorado
por você, na página 28.
Como você pode verificar facilmente no irb, a superclasse de Module é Object. Você nem
precisa do irb para saber qual é a classe de Object : como Object é uma classe, sua classe
deve ser Class. Isso vale para todas as classes, o que significa que a classe de Class deve
ser a própria Class . Você não ama a lógica autorreferencial?
Finalmente, chamar instance_variable_set abençoa obj3 com sua própria variável de instância
@x. Se você achar esse conceito surpreendente, lembre-se de que em uma linguagem
dinâmica como Ruby, todo objeto tem sua própria lista de variáveis de instância, independente
de outros objetos — até mesmo de outros objetos da mesma classe.
"Parar!" Bill grita, assustando você. “Esse código é muito complicado. Para entendê-lo, você precisa
aprender em detalhes o que acontece quando você chama um método.” E antes que você possa
reagir, ele mergulha em mais uma palestra.
Pesquisa de método
Você já conhece o caso mais simples de pesquisa de método. Olhe novamente para a Figura 1,
Variáveis de instância residem em objetos; os métodos vivem em classes, na página 18. Quando
você chama um método, o Ruby procura na classe do objeto e encontra o método lá. Antes de
olhar para um exemplo mais complicado, porém, você precisa conhecer dois novos conceitos: o
receptor e a cadeia de ancestrais.
O receptor é o objeto no qual você chama um método. Por exemplo, se você escrever
my_string.reverse(), então my_string será o receptor.
Para entender o conceito de uma cadeia de ancestrais, observe qualquer classe Ruby.
Em seguida, imagine passar da classe para sua superclasse, depois para a superclasse da
superclasse e assim por diante, até chegar a BasicObject, a raiz da hierarquia de classes do
Ruby. O caminho das classes que você acabou de percorrer é a cadeia de ancestrais da
classe. (A cadeia de ancestrais também inclui módulos, mas esqueça-os por enquanto.
Voltaremos aos módulos daqui a pouco.)
Agora que você sabe o que é um receptor e o que é uma cadeia de ancestrais, pode resumir
o processo de pesquisa de métodos em uma única frase: para encontrar um método, o Ruby
vai até a classe do receptor e, a partir daí, sobe a cadeia de ancestrais até ele encontra o
método. Aqui está um exemplo:
object_model/lookup.rb
classe MinhaClasse
def meu_método; 'meu_metodo()'; fim fim
obj = MySubclass.new
obj.my_method() # => "meu_metodo()"
Se você está acostumado com diagramas de classe tradicionais, esta imagem pode parecer
confusa para você. Por que obj, um objeto humilde, está pendurado no mesmo diagrama com
uma hierarquia de classes? Não fique confuso — este não é um diagrama de classes. Cada
caixa no diagrama é um objeto. Acontece que alguns desses objetos são classes, e as classes
são vinculadas por meio do método da superclasse .
Quando você chama my_method, Ruby vai direto de obj, o receptor, para MySubclass.
Como não consegue encontrar my_method lá, Ruby continua sua busca subindo em MyClass ,
onde finalmente encontra o método.
“Ei, o que o Kernel está fazendo lá na cadeia de ancestrais?” você pergunta. “Você me falou
sobre uma cadeia de superclasses, mas tenho certeza de que Kernel é um módulo, não uma
classe.”
"Você tem razão." Bill admite. “Esqueci de falar sobre os módulos. Eles são fáceis...”
Módulos e Lookup
Você aprendeu que a cadeia de ancestrais vai de classe a superclasse. Na verdade, a cadeia
de ancestrais também inclui módulos. Quando você inclui um módulo em uma classe (ou
mesmo em outro módulo), o Ruby insere o módulo na cadeia de ancestrais, logo acima da
própria classe de inclusão:
object_model/modules_include.rb
módulo M1
def my_method
'M1#meu_metodo()'
fim
fim
classe C
incluir M1
fim
A partir do Ruby 2.0, você também tem uma segunda maneira de inserir um módulo na cadeia
de ancestrais de uma classe: o método prepend . Funciona como include, mas insere
o módulo abaixo da classe de inclusão (às vezes chamado de includer), em vez de acima dela:
classe C2
preceder M2
final
Bill desenha o fluxograma a seguir para mostrar como a inclusão e o prefixo funcionam.
Mais adiante neste livro, você verá como usar o prefixo a seu favor. Por enquanto, basta que
você entenda o diagrama anterior. No entanto , há um último caso sobre incluir e preceder - um
que vale a pena mencionar.
ausente.
Múltiplas
inclusões O que acontece se você tentar incluir um módulo na mesma cadeia de
ancestrais várias vezes? Aqui está um exemplo:
object_model/modules_multiple.rb
módulo M1; fim
módulo M2
incluir extremidade
M1
módulo M3
anexar M1
inclui M2
fim
No código anterior, M3 precede M1 e depois inclui M2. Quando M2 também inclui M1,
essa inclusão não tem efeito, porque M1 já está na cadeia de ancestrais. Isso é verdade
toda vez que você inclui ou precede um módulo: se esse módulo já estiver na cadeia, o
Ruby ignora silenciosamente a segunda inclusão. Como resultado, um módulo pode
aparecer apenas uma vez na mesma cadeia de ancestrais. Esse comportamento pode
mudar no futuro Rubies - mas não prenda a respiração.
Já que estamos falando de módulos, vale a pena dar uma olhada naquele módulo do
Kernel que fica aparecendo em todos os lugares.
O Núcleo
Ruby inclui alguns métodos, como print, que você pode chamar de qualquer lugar em seu
código. Parece que todo e qualquer objeto tem o método de impressão . Métodos como
print são, na verdade, métodos de instância privada do módulo Kernel:
O truque aqui é que a classe Object inclui Kernel, então Kernel entra na cadeia de
ancestrais de cada objeto. Cada linha do Ruby é sempre executada dentro de um objeto,
então você pode chamar os métodos de instância no Kernel de qualquer lugar. Isso lhe
dá a ilusão de que print é uma palavra-chave da linguagem, quando na verdade é um
método. Legal, não é?
Você mesmo pode tirar vantagem deste mecanismo: se você adicionar um método ao
Spell: Kernel Method Kernel, este Kernel Method estará disponível para todos os objetos. Para provar que
os Kernel Methods são realmente úteis, você pode ver como algumas bibliotecas Ruby os
usam.
object_model/awesome_print_example.rb
requer "awesome_print"
Isso produz:
ÿ{
:city => "Roma", :now
=> 2013-11-30 12:51:03 +0100
}
Você pode chamar ap de qualquer lugar porque é um Kernel Method (32), que você pode verificar
espiando o código-fonte do Awesome Print:
gems/awesome_print-1.1.0/lib/awesome_print/core_ext/
kernel.rb módulo
Kernel def ap(objeto, opções = {})
# ...
fim
fim
Após essa incursão nos módulos Ruby e no Kernel, você pode finalmente aprender como o Ruby
executa os métodos depois de encontrá-los.
Execução do Método
Quando você chama um método, o Ruby faz duas coisas: primeiro, encontra o método e, segundo,
executa o método. Até agora, você se concentrou na parte de encontrar.
Agora você pode finalmente olhar para a parte de execução.
Imagine ser o interpretador Ruby. Alguém chamou um método chamado, digamos, my_method.
Você encontrou o método indo um passo para a direita, depois para cima, e fica assim:
def my_method
temp = @x + 1
my_other_method(temp)
end
Para executar este método, você precisa responder a duas perguntas. Primeiro, a qual objeto a
variável de instância @x pertence? E segundo, em qual objeto você deve chamar my_other_method ?
Sendo um ser humano inteligente (em oposição a um programa de computador burro), você
provavelmente pode responder a ambas as perguntas intuitivamente: tanto @x quanto
my_other_method pertencem ao receptor - o objeto para o qual my_method foi originalmente chamado.
No entanto, Ruby não tem o luxo da intuição. Quando você chama um método, ele precisa guardar
uma referência para o receptor. Graças a esta referência, ele pode se lembrar de quem é o receptor
ao executar o método.
Essa referência ao receptor também pode ser útil para você - portanto, vale a pena explorar mais.
A palavra-chave
self Cada linha de código Ruby é executada dentro de um objeto — o chamado objeto atual. O
objeto atual também é conhecido como self, porque você pode acessá-lo com a palavra-chave self .
Apenas um objeto pode assumir o papel de si mesmo em um determinado momento, mas nenhum
objeto mantém esse papel por muito tempo. Em particular, quando você chama um método, o
receptor se torna self. A partir desse momento, todas as variáveis de instância são variáveis de
instância de self, e todos os métodos chamados sem um receptor explícito são chamados em self.
Assim que seu código chama explicitamente um método em algum outro objeto, esse outro objeto
se torna self.
Aqui está um exemplo artisticamente complicado para mostrar você mesmo em ação:
object_model/
self.rb class
MyClass def
testing_self @var = 10 # Uma variável de instância de
self my_method() # O mesmo que self.my_method()
self
fim
def meu_método
@var = @var + 1
fim
fim
obj = MyClass.new
obj.testing_self # => #<MyClass:0x007f93ab08a728 @var=11>
Assim que você chamar testing_self, o receptor obj se tornará self. Por causa disso, a variável de
instância @var é uma variável de instância de obj e o método my_method é chamado em obj.
Como my_method é executado, obj ainda é self, então @var ainda é uma variável de instância de
obj. Por fim, testing_self retorna uma referência a self.
(Você também pode verificar a saída para verificar se @var agora é 11.)
Agora que você conhece self, pode lançar uma nova luz sobre a palavra-chave private de Ruby .
Os métodos privados são regidos por uma única regra simples: você não pode chamar um método
privado com um receptor explícito. Em outras palavras, toda vez que você chama um método privado,
ele deve estar no receptor implícito - self. Vejamos um caso de canto:
classe C
def public_method
self.private_method end
privado
C.new.public_method
Este exemplo artificial mostra que os métodos privados vêm de duas regras trabalhando juntas:
primeiro, você precisa de um receptor explícito para chamar um método em um objeto que não seja
você e, segundo, os métodos privados podem ser chamados apenas com um receptor implícito .
Coloque essas duas regras juntas e você verá que só pode chamar um método privado para si mesmo.
Você pode chamar isso de “ regra privada”.
Você pode achar os métodos privados de Ruby desconcertantes — especialmente se vier de Java ou
C#, onde o método privado se comporta de maneira diferente. Quando estiver em dúvida, volte para a
regra privada , e tudo fará sentido. O objeto x pode chamar um método privado no objeto y se os dois
objetos compartilham a mesma classe? A resposta é não, porque não importa a qual classe você
pertença, você ainda precisa de um receptor explícito para chamar o método de outro objeto.
Você pode chamar um método privado que herdou de uma superclasse? A resposta é sim, porque
você não precisa de um receptor explícito para chamar métodos herdados para si mesmo.
Se você quiser se tornar um mestre em Ruby, você deve sempre saber qual objeto tem o
papel self em um determinado momento. Na maioria dos casos, isso é fácil: basta rastrear
qual objeto foi o último receptor do método. No entanto, existem dois casos especiais
importantes que você deve conhecer. Vamos dar uma olhada neles.
O nível
superior Você acabou de aprender que toda vez que chama um método em um objeto,
esse objeto se torna self. Mas então, quem é eu se você ainda não chamou nenhum
método? Você pode executar o irb e pedir uma resposta ao próprio Ruby:
auto # => principal
self.class # => Objeto
Assim que você inicia um programa Ruby, você está dentro de um objeto chamado main
que o interpretador Ruby criou para você. Este objeto às vezes é chamado de contexto
de nível superior, porque é o objeto em que você está quando está no nível superior da
pilha de chamadas: ou você ainda não chamou nenhum método ou todos os métodos
que você chamou retornaram . (Ah, e caso você esteja se perguntando, o main do Ruby
não tem nada a ver com as funções main() em C e Java.)
classe MyClass
self # => MinhaClasse
end
Este último detalhe não será útil agora, mas se tornará um conceito central mais adiante
neste livro. Por enquanto, podemos deixá-lo de lado e voltar ao tópico principal.
Tudo o que você aprendeu até agora sobre a execução do método pode ser resumido em
algumas frases curtas. Quando você chama um método, Ruby procura o método seguindo
a regra “um passo para a direita, depois para cima” e então executa o método com o
receptor como self. Existem alguns casos especiais neste procedimento (por exemplo,
quando você inclui um módulo), mas não há exceções... exceto uma.
Refinamentos
Lembra da primeira refatoração que você codificou hoje, em Open Classes, na página
11? Você e Bill usaram uma Open Class (14) para adicionar um método a Strings:
object_model/
alphanumeric.rb
classe String def to_alphanumeric
gsub(/[^\w\s]/, '') fim
fim
O problema de modificar as classes dessa forma é que as alterações são globais: a partir
do momento em que o código anterior é executado, todas as Strings do sistema recebem
as alterações. Se a mudança for um Monkeypatch incompatível (16), você pode quebrar
algum código não relacionado - como aconteceu com você e Bill quando você redefiniu
inadvertidamente Array#replace.
Feitiço: Refinamento A partir do Ruby 2.0, você pode lidar com esse problema usando um Refinamento.
Comece escrevendo um módulo e chamando refine dentro da definição do módulo:
object_model/refinements_in_file.rb
módulo StringExtensions
refine String do def
to_alphanumeric
gsub(/[^\w\s]/, '') fim
fim
fim
Esse código refina a classe String com um novo método to_alphanumeric . Diferentemente
de uma Aula Aberta regular, no entanto, um Refinamento não está ativo por padrão. Se
você tentar chamar String#to_alphanumeric, receberá um erro:
Para ativar as alterações, você deve fazê-lo explicitamente, com o método using :
usando StringExtensions
A partir do momento que você chamar using, todo o código naquele arquivo fonte Ruby
verá as mudanças:
A partir do Ruby 2.1, você pode até chamar using dentro de uma definição de módulo.
O Refinamento ficará ativo até o final da definição do módulo. O código abaixo corrige o
método String#reverse — mas apenas para o código dentro da definição de StringStuff:
object_model/refinements_in_module.rb
módulo StringExtensions
refine String do def
reverse
"esrever"
fim
fim
fim
módulo StringStuff
usando StringExtensions
"my_string".reverse # => "esrever" end
do local onde você chama using até o final do módulo (se estiver em uma definição de módulo)
ou o final do arquivo (se estiver no nível superior)
No escopo limitado em que está ativo, um refinamento é tão bom quanto uma Open Class ou
um Monkeypatch. Ele pode definir novos métodos, redefinir métodos existentes, incluir ou pré-
anexar módulos e geralmente fazer qualquer coisa que uma Open Class regular pode fazer. O
código em um Refinamento ativo tem precedência sobre o código na classe refinada e também
sobre o código nos módulos que são incluídos ou anexados pela classe. Refinar uma classe é
como aplicar um patch diretamente no código original da classe.
Por outro lado, como não são globais, os refinamentos não apresentam os problemas que você
enfrentou em O problema com classes abertas, na página 15.
Você pode aplicar um refinamento a algumas áreas selecionadas do seu código, e o resto do
seu código permanecerá com a classe não refinada original — portanto, não há muitas chances
de você quebrar seu sistema ao impactar inadvertidamente um código não relacionado. No
entanto, esta qualidade local de Requintes tem potencial para o surpreender, como irá descobrir.
Pegadinhas de refinamento
object_model/refinements_gotcha.rb
classe MinhaClasse
def my_method
"original my_method()" end
def other_method
my_method
end
fim
module MyClassRefinement
refine MyClass do
def my_method
"refined my_method()" end
fim
fim
using MyClassRefinement
MyClass.new.my_method # => "refined my_method()"
MyClass.new.another_method # => "original my_method()"
A chamada para my_method acontece após a chamada para using, portanto, você obtém a
versão refinada do método, exatamente como espera. No entanto, a chamada para another_method
pode pegar você desprevenido: mesmo se você chamar outro_método depois de usar, a
chamada para my_method acontece antes de usar — então ele chama a versão original e
não refinada do método.
Algumas pessoas acham o resultado acima contra-intuitivo. A lição aqui é verificar novamente
suas chamadas de método ao usar Refinamentos (36). Lembre-se também de que os
refinamentos ainda são um recurso em evolução, tanto que o Ruby 2.0 emite um aviso
assustador quando seu programa usa os refinamentos pela primeira vez.
tempo:
Este aviso foi removido no Ruby 2.1, mas ainda existem alguns casos extremos em que os
refinamentos podem não se comportar como você espera - e alguns desses casos
secundários podem mudar em Rubies futuros. Por exemplo, você pode chamar o refine em
um módulo regular, mas não pode chamá-lo em uma classe, mesmo que a própria classe
seja um módulo. Além disso, métodos de metaprogramação, como métodos e ancestrais,
ignoram totalmente os refinamentos. Comportamentos como esses têm justificativas técnicas
sólidas, mas, mesmo assim, podem fazer você tropeçar. Os refinamentos têm o potencial de
eliminar Monkeypatches perigosos, mas levará algum tempo para a comunidade Ruby
entender como usá-los melhor.
Você pode finalmente voltar ao problema que levou Bill a iniciar sua discussão sobre pesquisa
de método, auto e refinamentos. Você teve problemas para entender um arranjo complicado
de classes e módulos. Aqui está a parte confusa:
object_model/
tangle.rb módulo
Imprimível def print
# ...
fim
fim
fim
Módulo Documento
def print_to_screen
prepare_cover
format_for_screen
print
end
fim
impressão definitiva
# ...
fim
fim
livro de classe
Incluir Documento Incluir
Imprimível
# ...
fim
b = Livro.novo
b.print_to_screen
Solução do questionário
Se você desenhar essa cadeia de ancestrais em seu quadro branco, ela se parecerá com a
Figura 5, A cadeia de ancestrais da classe Book, na página 41.
Vamos ver como Ruby constrói a cadeia. Como Book não tem uma superclasse explícita,
ele herda implicitamente de Object, que por sua vez inclui Kernel e herda de BasicObject.
Quando Book inclui Document, Ruby adiciona Document à cadeia de ancestrais de Book
logo acima do próprio Book . Imediatamente depois disso, o livro
inclui Imprimível. Mais uma vez, Ruby coloca Printable na cadeia logo acima de Book,
empurrando o resto da cadeia para cima — de Document para cima.
O relatório de bug sugere que o autor original do código pretendia chamar Document#print .
No código de produção real, você provavelmente gostaria de se livrar dessa confusão e
renomear um dos métodos de impressão conflitantes . No entanto, se você quiser apenas
resolver este questionário, a maneira mais barata de fazer isso é trocar a ordem de
inclusão dos módulos no Livro para que Documento fique menor que Imprimível na cadeia
de ancestrais:
object_model/tangle_untwisted.rb
módulo imprimível
# ...
fim
Módulo Documento
# ...
fim
livro de classe
ÿ incluir Imprimível ÿ incluir
Documento
antepassados # => [Livro, Documento, Imprimível, Objeto, Kernel, Objeto Básico] end
O receptor implícito de ancestrais no código anterior é Book, porque em uma definição de classe o papel
de self é assumido pela classe. A cadeia de ancestrais de Book também contém um terceiro método
chamado print - mas Bill não está dizendo onde ele está. Se você estiver curioso, terá que encontrá-lo
sozinho, talvez com a ajuda de seu amigo irb.
Está quase na hora de ir para casa depois de um dia de trabalho cansativo, mas muito gratificante. Mas
antes de encerrar o dia, Bill faz um resumo completo do que você aprendeu.
Embrulhar
Aqui está uma lista de verificação do que você aprendeu hoje:
• A própria classe é apenas um objeto da classe Class. O nome da classe é apenas uma constante.
• As constantes são organizadas em uma árvore semelhante a um sistema de arquivos, onde os nomes
dos módulos e classes desempenham o papel de diretórios e as constantes regulares desempenham
o papel de arquivos.
• Cada classe possui uma cadeia de ancestrais, começando com a própria classe e indo até BasicObject.
Encerramento • 43
• Quando você chama um método, o Ruby vai direto para a classe do receptor e então sobe a
cadeia de ancestrais, até encontrar o método ou chegar ao final da cadeia.
• Quando você inclui um módulo em uma classe, o módulo é inserido na cadeia de ancestrais
logo acima da própria classe. Quando você precede o módulo, ele é inserido na cadeia de
ancestrais logo abaixo da classe.
• Quando você está definindo um módulo (ou uma classe), o módulo assume o papel de
auto.
• Os refinamentos são como trechos de código corrigidos diretamente sobre uma classe e
substituem a pesquisa de método normal. Por outro lado, um Refinamento funciona em uma
área limitada do programa: as linhas de código entre a chamada de using e o final do
arquivo, ou o final da definição do módulo.
Verificado... verificado... pronto! Agora é hora de ir para casa antes que seu cérebro exploda
com todas as informações que você colocou nele hoje.
CAPÍTULO 3
Terça: Métodos
Ontem você aprendeu sobre o modelo de objeto Ruby e como fazer classes Ruby
cantarem e dançarem para você. Hoje você está realizando todas as chamadas para
focar nos métodos.
Os objetos em seu código conversam entre si o tempo todo. Algumas linguagens - como
Java e C - apresentam um compilador que preside esse bate-papo. Para cada chamada
de método, o compilador verifica se o objeto receptor possui um método correspondente.
Isso é chamado de verificação de tipo estático e os idiomas que o adotam são chamados
de idiomas estáticos. Por exemplo, se você chamar talk_simple em um objeto Lawyer
que não possui tal método, o compilador protesta em voz alta.
Em Ruby, os métodos padrão não são um problema, porque você pode evitá-los
facilmente com técnicas que seriam impraticáveis ou simplesmente impossíveis em uma
linguagem estática. Neste capítulo, vamos nos concentrar nessas técnicas.
Um problema de duplicação
Onde você e Bill enfrentam um problema com código duplicado.
Hoje, seu chefe pediu que você trabalhasse em um programa para o departamento de
contabilidade. Eles querem um sistema que sinalize despesas superiores a US$ 99 em
equipamentos de informática, para que possam reprimir os desenvolvedores que esbanjam
dinheiro da empresa. (Você leu certo: $ 99. O departamento de compras não está brincando.)
Alguns outros desenvolvedores já deram uma facada no projeto, codificando um relatório que
lista todos os componentes de cada computador da empresa e quanto custa cada componente.
Até o momento, eles não conectaram nenhum dado real. Aqui é onde você e Bill entram.
O Sistema Legado
Desde o início, você tem um desafio em mãos: os dados que você precisa carregar no programa
já estabelecido são armazenados em um sistema legado preso atrás de uma classe codificada
desajeitadamente chamada DS (para “fonte de dados” ) :
métodos/computador/
data_source.rb classe DS
def inicializar # conectar a fonte de dados...
def get_cpu_info(workstation_id) # ... def
get_cpu_price ( workstation_id) # ... def
get_mouse_info(workstation_id) # ... def
get_mouse_price(workstation_id) # ... def
get_keyboard_info(workstation_id) # ... def
get_keyboard_price(workstation_id) # ... def
get_display_info(workstation_id) # ... def
get_display_price(workstation_id) # ... # ...e assim por
diante
DS#initialize se conecta ao sistema de dados quando você cria um novo objeto DS . Os outros
métodos - e existem dezenas deles - pegam um identificador de estação de trabalho
e retornar descrições e preços para os componentes do computador. Com Bill pronto para
oferecer apoio moral, você rapidamente experimenta a aula no irb:
ds = DS.novo
ds.get_cpu_info(42) # => "2,9 Ghz quad-core" # =>
ds.get_cpu_price(42) 120
ds.get_mouse_info(42) # => "Toque sem fio"
ds.get_mouse_price(42) # => 60
Parece que a estação de trabalho número 42 tem uma CPU de 2,9 GHz e um luxuoso mouse
de US$ 60. Estes são dados suficientes para você começar.
Um problema de duplicação • 47
métodos/computador/
duplicado.rb classe Computador
def initialize(computer_id, data_source) @id =
computer_id
@data_source = data_source end
def info do
mouse = @data_source.get_mouse_info(@id)
preço = @data_source.get_mouse_price(@id) result
= "Mouse: #{info} ($#{price})" return "* #{result}"
if price > = 100 resultado
fim
def cpu
info = @data_source.get_cpu_info(@id) preço
= @data_source.get_cpu_price(@id) result = "Cpu:
#{info} ($#{price})" return "* #{result}" if price
> = 100 resultado
fim
def keyboard
info = @data_source.get_keyboard_info(@id) price =
@data_source.get_keyboard_price(@id) result =
"Teclado: #{info} ($#{price})" return "* #{result}" if
price > = 100 resultado final
# ...
fim
deixado para lidar, e você também deve escrever testes para cada método, porque é fácil cometer
erros em código duplicado.
“Posso pensar em duas maneiras diferentes de remover essa duplicação”, diz Bill. “Um é um feitiço
chamado Métodos Dinâmicos. O outro é um método especial chamado method_missing. Podemos
tentar ambas as soluções e decidir qual delas gostamos mais.”
Você concorda em começar com Métodos Dinâmicos e chegar a method_missing depois disso.
Métodos Dinâmicos
Onde você aprende como chamar e definir métodos dinamicamente e remover o código duplicado.
“Quando eu era um jovem desenvolvedor aprendendo C++”, conta Bill, “meus mentores me
disseram que quando você chama um método, na verdade está enviando uma mensagem para um objeto.
Demorei um pouco para me acostumar com esse conceito. Se eu estivesse usando Ruby naquela
época, essa noção de enviar mensagens teria vindo mais naturalmente para mim.”
method/dynamic_call.rb
class MinhaClasse
def meu_método(meu_arg)
meu_arg * 2
fim
fim
obj = MyClass.new
obj.my_method(3) # => 6
Você também tem uma alternativa: chame MyClass#my_method usando Object#send no lugar da
notação de ponto:
obj.send(:meu_método, 3) # => 6
O código anterior ainda chama my_method, mas o faz por meio de send. O primeiro argumento a
ser enviado é a mensagem que você está enviando para o objeto — ou seja, um símbolo ou string
representando o nome de um método. (Consulte Nomes e símbolos do método, na página 49.)
Quaisquer argumentos restantes (e o bloco, se houver) são simplesmente passados para o método.
Por que você usaria send em vez da velha notação de ponto? Porque com send, o nome do método
que você deseja chamar torna-se apenas um argumento regular. Você pode esperar literalmente
até o último momento para decidir qual
Métodos Dinâmicos • 49
método para chamar, enquanto o código está em execução. Essa técnica é chamada de
Despacho Dinâmico e você a achará extremamente útil. Para ajudar a revelar sua mágica, Feitiço: Dinâmico
Despacho
vejamos alguns exemplos da vida real.
No entanto, os símbolos são semelhantes o suficiente para strings que você pode se perguntar qual é o sentido de ter
símbolos. Você não pode simplesmente usar strings regulares em todos os lugares?
Existem algumas razões diferentes para usar símbolos no lugar de strings regulares, mas no final a escolha se resume
a convenções. Na maioria dos casos, os símbolos são usados como nomes de coisas - em particular, nomes de coisas
relacionadas à metaprogramação, como métodos. Os símbolos são uma boa opção para esses nomes porque são
imutáveis: você pode alterar os caracteres dentro de uma string, mas não pode fazer isso para os símbolos. Você não
esperaria que o nome de um método mudasse, portanto, faz sentido usar um símbolo ao se referir a um nome de
método.
Por exemplo, quando você chama Object#send, você precisa passar o nome de um método como primeiro argumento.
Embora send aceite esse nome como um símbolo ou uma string, os símbolos geralmente são considerados mais
kosher:
Independentemente disso, você pode converter facilmente de string para símbolo e vice-versa:
O Exemplo do
Pry Um exemplo de Despacho Dinâmico vem do Pry. Pry é uma alternativa popular ao
irb, o interpretador de linha de comando do Ruby. Um objeto Pry armazena a configuração
do interpretador em seus próprios atributos, como memory_size e quiet:
métodos/pry_example.rb
requer "pry"
pry = Pry.new
pry.memory_size = 101
pry.memory_size # => 101
pry.quiet = verdadeiro
Vamos olhar um pouco mais fundo no código-fonte do Pry. Para configurar uma instância Pry ,
você pode chamar um método chamado Pry#refresh. Este método usa um hash que mapeia os
nomes dos atributos para seus novos valores:
Pry#refresh tem muito trabalho a fazer: ele precisa passar por cada atributo (como
self.memory_size); inicialize o atributo com seu valor padrão (como Pry.memory_size); e,
finalmente, verifique se o argumento hash contém um novo valor para o mesmo atributo e, se
contiver, defina o novo valor. Pry#refresh poderia fazer todas essas etapas com um código
como este:
def refresh(options={})
defaults[:memory_size] = Pry.memory_size
self.memory_size = options[:memory_size] if options[:memory_size]
Essas duas linhas de código teriam que ser repetidas para cada atributo.
Isso é muito código duplicado. Pry#refresh consegue evitar essa duplicação e, em vez disso,
usa Dynamic Dispatch (49) para definir todos os atributos com apenas algumas linhas de código:
gems/pry-0.9.12.2/lib/pry/
pry_instance.rb def
refresh(options={})
defaults = {} attribute =
# ...
defaults.merge!(opções).cada um faz |chave, valor|
send("#{key}=", valor) if respond_to?("#{key}=") end
verdadeiro
fim
Métodos Dinâmicos • 51
O código acima usa send para ler os valores de atributo padrão em um hash, mescla esse hash
com o hash de opções e, finalmente, usa send novamente para chamar acessadores de atributo
como memory_size=. O Kernel#respond_to? O método retorna true se métodos como
Pry#memory_size= realmente existirem, de modo que qualquer chave em opções que não
corresponda a um atributo existente será ignorada. Legal, hein?
Questões de
privacidade Lembra do que o tio do Homem-Aranha costumava dizer? "Com grandes poderes
vem grandes responsabilidades." O método Object#send é muito poderoso — talvez muito
poderoso. Em particular, você pode chamar qualquer método com send, incluindo métodos privados.
Se esse tipo de quebra de encapsulamento deixa você desconfortável, você pode usar public_send
em vez disso. É como enviar, mas faz questão de respeitar a privacidade do destinatário. Esteja
preparado, no entanto, para o fato de que o código Ruby na natureza raramente se preocupa com
essa preocupação. Na verdade, muitos programadores Ruby usam send exatamente porque
permite chamar métodos privados, não apesar disso.
Agora você sabe sobre envio e Despacho Dinâmico - mas há mais nos Métodos Dinâmicos do que
isso. Você não está limitado a chamar métodos dinamicamente.
Você também pode definir métodos dinamicamente. É hora de ver como.
métodos/definição_dinâmica.rb
classe MinhaClasse
define_method :meu_método do |meu_arg|
meu_arg * 3
fim
fim
obj = MyClass.new
obj.my_method(2) # => 6
Há uma razão importante para usar define_method sobre a palavra-chave def mais familiar :
define_method permite que você decida o nome do método definido
em tempo de execução. Para ver um exemplo dessa técnica, volte ao seu problema de refatoração
original.
métodos/computador/
duplicado.rb classe Computador
def initialize(computer_id, data_source) @id =
computer_id
@data_source = data_source end
def info do
mouse = @data_source.get_mouse_info(@id)
preço = @data_source.get_mouse_price(@id) result
= "Mouse: #{info} ($#{price})" return "* #{result}"
if price > = 100 resultado
fim
def cpu
info = @data_source.get_cpu_info(@id) preço
= @data_source.get_cpu_price(@id) result = "Cpu:
#{info} ($#{price})" return "* #{result}" if price
> = 100 resultado
fim
def keyboard
info = @data_source.get_keyboard_info(@id) price =
@data_source.get_keyboard_price(@id) result =
"Teclado: #{info} ($#{price})" return "* #{result}" if
price > = 100 resultado
fim
# ...
fim
Nas páginas anteriores você aprendeu como usar Module#define_method no lugar da palavra-
chave def para definir um método e como usar send no lugar da notação de ponto para chamar
um método. Agora você pode usar esses feitiços para refatorar a classe Computer . É hora de
remover algumas duplicações.
Você e Bill começam extraindo o código duplicado em seu próprio método de envio de mensagem:
Métodos Dinâmicos • 53
method/computer/dynamic_dispatch.rb
classe Computador
def initialize(computer_id, data_source) @id =
computer_id
@data_source = data_source end
ÿ def mouse ÿ
component :mouse ÿ end ÿ
ÿ def cpu ÿ
component :cpu ÿ end ÿ ÿ
def
keyboard ÿ
component :keyboard ÿ end ÿ
Uma chamada para o mouse é delegada ao componente, que por sua vez chama
DS#get_mouse_info e DS#get_mouse_price. A chamada também grava o nome em
letras maiúsculas do componente na string resultante. Você abre uma sessão irb e testa
o novo computador:
Essa nova versão do Computer é um passo à frente porque contém muito menos linhas
duplicadas - mas você ainda precisa escrever dezenas de métodos semelhantes. Para
evitar escrever todos esses métodos, você pode usar define_method.
method/computer/dynamic_methods.rb
classe Computador
def initialize(computer_id, data_source) @id =
computer_id
@data_source = data_source end
ÿ def self.define_component(name) ÿ
define_method(name) do ÿ info =
@data_source.send "get_#{name}_info", @id preço = @data_source.send
ÿ
"get_#{name}_price", @id resultado = "#{name.capitalize}: #{info}
ÿ ($#{price})" return "* #{result}" if price >= 100 result
ÿ
ÿ
ÿ end ÿ end
ÿÿ
define_component :mouse ÿ
define_component :cpu ÿ
define_component :keyboard
fim
Observe que as três chamadas para define_component são executadas dentro da definição de
Computer, onde Computer é o self implícito . Como você está chamando o componente
define_component em Computer, você deve torná-lo um método de classe.
Você testa rapidamente a classe Computer simplificada no irb e descobre que ela ainda funciona. É
hora de passar para a próxima etapa.
computador mais recente contém duplicação mínima, mas você pode forçá-lo ainda mais e remover
a duplicação completamente. Como? Ao se livrar de todas essas chamadas para define_component.
Você pode fazer isso examinando o argumento data_source e extraindo os nomes de todos os
componentes:
métodos/computador/more_dynamic_methods.rb
classe Computador
def initialize(computer_id, data_source) @id =
computer_id
@data_source = data_source ÿ
data_source.methods.grep(/^get_(.*)_info$/) { Computer.define_component $1 }
fim
def self.define_component(name)
define_method(name) do # ...
fim
fim
fim
method_missing • 55
A nova linha em initialize é onde a mágica acontece. Para entendê-lo, você precisa saber algumas
coisas.
Primeiro, se você passar um bloco para Array#grep, o bloco é avaliado para cada elemento que
corresponde à expressão regular. Em segundo lugar, a string correspondente à parte entre
parênteses da expressão regular é armazenada na variável global $1. Portanto, se data_source
tiver métodos denominados get_cpu_info e get_mouse_info, esse código chamará
Computer.define_component duas vezes, com as strings "cpu" e "mouse".
Observe que define_method funciona igualmente bem com uma string ou um símbolo.
O código duplicado finalmente se foi para sempre. Como bônus, você nem precisa escrever ou
manter a lista de componentes. Se alguém adicionar um novo componente ao DS, a classe
Computer o suportará automaticamente. Isso não é maravilhoso?
Para esta segunda solução, você precisa conhecer alguns métodos estranhos que não são
realmente métodos e um método muito especial chamado method_missing.
method_missing
Onde você ouve histórias assustadoras sobre métodos fantasmas e proxies dinâmicos e tenta
uma segunda maneira de remover código duplicado.
Com Ruby, não há compilador para impor chamadas de método. Isso significa que você pode
chamar um método que não existe. Por exemplo:
method/method_missing.rb
classe Advogado;
end nick = Lawyer.new
nick.talk_simple
Você se lembra de como funciona a pesquisa de métodos? Quando você chama talk_simple,
Ruby vai para a classe de nick e procura seus métodos de instância. Se não conseguir encontrar
o talk_simple lá, ele pesquisará a cadeia de ancestrais em Object e, eventualmente, em BasicObject.
Como o Ruby não consegue encontrar o talk_simple em nenhum lugar, ele admite a derrota
chamando um método chamado method_missing no nick, o receptor original. Ruby sabe disso
method_missing está lá, porque é um método de instância privada de BasicObject que todo objeto
herda.
Você pode experimentar chamando method_missing você mesmo. É um método privado, mas
você pode acessá-lo através de send:
Você acabou de fazer exatamente o que Ruby faz. Você disse ao objeto: “Tentei chamar um
método chamado my_method em você e você não entendeu”.
BasicObject#method_missing respondeu levantando um NoMethodError. Na verdade, é isso que
method_missing faz da vida. É como o escritório de mensagens mortas de um objeto, o local onde
as mensagens desconhecidas eventualmente acabam (e o local de onde vêm os NoMethodErrors ).
provavelmente, você nunca precisará chamar method_missing por conta própria. Em vez disso,
você pode substituí-lo para interceptar mensagens desconhecidas. Cada mensagem que chega à
mesa de method_missing inclui o nome do método que foi chamado, além de quaisquer
argumentos e blocos associados à chamada.
métodos/more_method_missing.rb
classe
Advogado def method_missing(method, *args)
puts "Você chamou: #{method}(#{args.join(', ')})" puts "(Você
também passou um bloco)" if block_given? fim
fim
bob = Lawyer.new
bob.talk_simple('a', 'b') do # um
bloco
fim
Substituir method_missing permite que você chame métodos que realmente não existem.
Vamos dar uma olhada nessas criaturas estranhas.
Métodos fantasmas
Quando você precisa definir muitos métodos semelhantes, pode se poupar das definições e
apenas responder às chamadas por meio de method_missing. É como dizer ao objeto: “Se eles
perguntarem algo e você não entender, faça isso”.
method_missing • 57
Do lado do chamador, uma mensagem processada por method_missing parece uma chamada
normal, mas do lado do receptor, ela não tem nenhum método correspondente.
Esse truque é chamado de Método Fantasma. Vejamos alguns exemplos do Método Ghost. Feitiço: Método Fantasma
O Exemplo Hashie A
gema Hashie contém um pouco de magia chamada Hashie::Mash. Um Mash é uma versão mais
poderosa da classe OpenStruct padrão do Ruby : um objeto semelhante a um hash cujos atributos
funcionam como variáveis do Ruby. Se você quiser um novo atributo, apenas atribua um valor ao
atributo e ele passará a existir:
requer 'haxixe'
sorvete = Hashie::Mash.new
icecream.flavor = "morango"
icecream.flavor # => "morango"
Isso funciona porque Hashie::Mash é uma subclasse de Hash do Ruby e seus atributos são, na
verdade, Métodos Fantasma, como uma rápida olhada em Hashie::Mash.method_missing confirmará:
gems/hashie-1.2.0/lib/hashie/
mash.rb módulo Hashie
class Mash < Hashie::Hash
def method_missing(method_name, *args, &blk)
return self.[](method_name, &blk) if key?(method_name) match =
method_name.to_s.match(/(.*?)([?=!]?)$/) case match[2] when
"="
outro
default(method_name, *args, &blk) fim fim
# ...
fim
fim
Se o nome do método chamado for o nome de uma chave no hash (como sabor),
Hashie ::Mash#method_missing simplesmente chamará o método [] para retornar o valor
correspondente. Se o nome terminar com um "=", o method_missing cortará o "=" no final para obter
o nome do atributo e, em seguida, armazenará seu valor. Se o nome do método chamado não
corresponder a nenhum desses casos, method_missing apenas retornará um valor padrão.
(Hashie::Mash também oferece suporte a alguns outros casos especiais, como métodos que
terminam em "?", que foram descartados do código acima.)
Proxies Dinâmicos
Métodos fantasmas (57) geralmente são a cereja do bolo, mas alguns objetos realmente dependem quase
que exclusivamente deles. Esses objetos geralmente são wrappers para alguma outra coisa — talvez outro
objeto, um serviço da Web ou um código escrito em uma linguagem diferente. Eles coletam chamadas de
método por meio de method_missing e as encaminham para o objeto envolvido. Vejamos um exemplo
complexo da vida real.
O exemplo do Ghee
Você provavelmente conhece o GitHub,1 o serviço de codificação social extremamente popular. Várias
bibliotecas fornecem acesso fácil às APIs HTTP do GitHub, incluindo uma gem Ruby chamada Ghee. Aqui
está como você usa o Ghee para acessar o “gist” de um usuário – um trecho de código que pode ser publicado
no GitHub:
métodos/ghee_example.rb
preciso de "ghee"
a_gist.star
O código acima se conecta ao GitHub, procura um usuário específico ("nusco") e acessa a lista de gists desse
usuário. Em seguida, ele seleciona um gist específico e lê o URL e a descrição desse gist . Por fim, “estrela” o
gist, para ser avisado de futuras alterações.
As APIs do GitHub expõem dezenas de tipos de objetos além de gists, e o Ghee precisa dar suporte a todos
esses objetos. No entanto, o código-fonte de Ghee é surpreendentemente conciso, graças ao uso inteligente
dos Métodos Ghost (57). A maior parte da mágica acontece na classe Ghee::ResourceProxy :
gems/ghee-0.9.8/lib/ghee/resource_proxy.rb
classe ghee
classe ResourceProxy # ...
1. http://www.github.com
method_missing • 59
def assunto
@assunto ||= connection.get(path_prefix){|req| req.params.merge!params }.body end
fim
fim
Antes de entender esta classe, você precisa ver como o Ghee a usa. Para cada tipo de
objeto do GitHub, como gists ou usuários, Ghee define uma subclasse de
Ghee::ResourceProxy. Aqui está a classe para gists (a classe para usuários é bastante semelhante):
gems/ghee-0.9.8/lib/ghee/api/gists.rb
classe ghee
API do módulo
resumos do módulo
class Proxy < ::Ghee::ResourceProxy
estrela def
connection.put("#{path_prefix}/star").status == 204 fim
# ...
fim
fim
fim
Vá um pouco mais fundo e você descobrirá que esse objeto semelhante a um hash é, na
verdade, um Hashie::Mash, a classe hash mágica sobre a qual falamos em O exemplo do
Hashie, na página 57. Isso significa que uma chamada de método como my_gist .url é
encaminhado para Ghee::ResourceProxy#method_missing, e de lá para
Hashie::Mash#method_missing, que finalmente retorna o valor do atributo url . Sim, são
duas chamadas seguidas para method_missing .
O design do Ghee é elegante, mas usa tanta metaprogramação que pode confundir você
no começo. Vamos resumir em apenas dois pontos:
• Ghee armazena objetos do GitHub como hashes dinâmicos. Você pode acessar os
atributos desses hashes chamando seus Ghost Methods (57), como url e description.
• Ghee também envolve esses hashes dentro de objetos proxy que os enriquecem com
métodos adicionais. Um proxy faz duas coisas. Primeiro, ele implementa métodos que
requerem código específico, como star. Em segundo lugar, ele encaminha métodos
que apenas leem dados, como url, para o hash agrupado.
Graças a este design de dois níveis, Ghee consegue manter seu código muito compacto.
Ele não precisa definir métodos que apenas leem dados, porque esses métodos são
métodos fantasmas. Em vez disso, pode apenas definir os métodos que precisam de código
específico, como star.
Essa abordagem dinâmica também tem outra vantagem: Ghee pode se adaptar
automaticamente a algumas mudanças nas APIs do GitHub. Por exemplo, se o GitHub
adicionasse um novo campo a gists (digamos, lines_count), Ghee suportaria chamadas
para Ghee::API::Gists#lines_count sem nenhuma alteração em seu código-fonte, porque
lines_count é apenas um método fantasma - na verdade, uma cadeia de dois métodos fantasmas.
métodos/computador/
duplicado.rb classe Computador
def initialize(computer_id, data_source) @id =
computer_id
@data_source = data_source end
mouse def
info = @data_source.get_mouse_info(@id) preço =
@data_source.get_mouse_price(@id) result = "Mouse:
#{info} ($#{price})" return "* #{result}" se preço >=
100 resultado final
method_missing • 61
def cpu
info = @data_source.get_cpu_info(@id) preço
= @data_source.get_cpu_price(@id) result = "Cpu:
#{info} ($#{price})" return "* #{result}" if price
> = 100 resultado
fim
def keyboard
info = @data_source.get_keyboard_info(@id) price =
@data_source.get_keyboard_price(@id) result =
"Teclado: #{info} ($#{price})" return "* #{result}" if
price > = 100 resultado
fim
# ...
fim
method/computer/method_missing.rb
classe Computador
def initialize(computer_id, data_source) @id =
computer_id
@data_source = data_source end
fim
responder_to_missing?
Se você perguntar especificamente a um computador se ele responde a um método fantasma, ele
mentirá descaradamente:
Esse comportamento pode ser problemático, porque respond_to? é um método comumente usado. (Se
você precisa ser convencido, apenas observe que o próprio computador está chamando respond_to?
na fonte de dados.) Felizmente, Ruby fornece um mecanismo limpo para fazer respond_to? ciente dos
Métodos Fantasma.
classe Computador
#...
fim
method_missing • 63
const_missing
Lembra-se de nossa discussão sobre Rake em The Rake Example, na página 23? Naquela
seção, dissemos que em um ponto de sua história, Rake renomeou classes como Task para
nomes que são menos prováveis de entrar em conflito, como Rake::Task. Depois de renomear
as classes, Rake passou por um caminho de atualização: para algumas versões, você pode
usar os novos nomes de classe ou os nomes antigos sem namespace. Rake permitiu que você
fizesse isso por Monkepatching (16) o método Module#const_missing :
gems/rake-0.9.2.2/lib/rake/ext/module.rb
módulo de classe
def const_missing(const_name)
caso const_name
quando :Tarefa
Rake.application.const_warning(const_name)
Rake::Task
quando :FileTask
Rake.application.const_warning(const_name)
Rake::FileTask
quando :FileCreationTask
# ...
fim
fim
fim
Quando você faz referência a uma constante que não existe, Ruby passa o nome da constante
para const_missing como um símbolo. Os nomes das classes são apenas constantes, portanto,
uma referência a uma classe Rake desconhecida, como Task , foi roteada para Module#con
st_missing. Por sua vez, const_missing avisou que você estava usando um nome de classe
obsoleto:
métodos/const_missing.rb
requerem 'rake'
task_class = Tarefa
Após o aviso, você obteve automaticamente o novo nome de classe Namespaced no lugar do
antigo:
Chega de falar sobre métodos mágicos. Vamos recapitular o que você e Bill fizeram hoje.
Conclusão da
refatoração Hoje você resolveu o mesmo problema de duas maneiras diferentes. A primeira
versão do Computer examina o DS para obter uma lista de métodos para agrupar e usa o Dynamic
Onde você descobre que bugs em um method_missing podem ser difíceis de eliminar.
Durante o almoço, Bill tem um teste para você. “Minha equipe anterior seguia um ritual de
escritório cruel”, diz ele. “Todas as manhãs, cada membro da equipe escolhia um número aleatório.
Quem tirasse o menor número tinha que ir até o Starbucks próximo e comprar café para toda
a equipe.”
Bill explica que a equipe até escreveu uma classe que deveria fornecer um número aleatório
(e algum suspense no estilo Roda da Fortuna ) quando você chamava o nome de um membro
da equipe. Aqui está a aula:
method/roulette_failure.rb
aula de roleta
def method_missing(name, *args) person
= name.to_s.capitalize 3.times do
ÿ 5...
6...
10...
Bob tirou 3 7...
4...
3...
Frank tirou 10
“Esse código foi claramente projetado demais”, admite Bill. “Poderíamos ter apenas definido
um método regular que tomasse o nome da pessoa como uma string – mas acabamos de
descobrir method_missing , então usamos Ghost Methods (57) . Não foi uma boa ideia; o
código não funcionou como esperado.”
Você consegue identificar o problema com a aula de Roleta ? Se não conseguir, tente executá-
lo em seu computador. Agora você pode explicar o que está acontecendo?
Solução do questionário
A Roleta contém um bug que causa um loop infinito. Ele imprime uma longa lista de números e
finalmente trava.
ÿ 2...
7...
1...
5...
(...mais números aqui...)
roulette_failure.rb:7:in `method_missing': nível de pilha muito profundo (SystemStackError)
Este bug é desagradável e difícil de detectar. A variável number é definida dentro de um bloco
(o bloco que é passado para tempos) e sai do escopo na última linha de method_missing.
Quando o Ruby executa essa linha, ele não pode saber que o número ali deveria ser uma
variável. Como padrão, ele assume que number deve ser uma chamada de método sem
parênteses em self.
Para evitar esse tipo de problema, tome cuidado para não introduzir muitos métodos fantasmas.
Por exemplo, a Roleta poderia ser melhor se simplesmente aceitasse os nomes das pessoas
da equipe de Frank. Além disso, lembre-se de recorrer a BasicOb ject#method_missing quando
receber uma chamada que não sabe como atender. Aqui está uma roleta melhor que ainda usa
method_missing:
métodos/roulette_solution.rb
aula de roleta
def method_missing(name, *args)
person = name.to_s.capitalize ÿ
super excepto %w[Bob Frank Bill].include? pessoa ÿ número = 0
3. vezes faça
número = rand(10) + 1 puts
"#{número}..." end "#{pessoa}
fim
Você também pode desenvolver esse código em etapas pequenas. Comece escrevendo
métodos regulares; então, quando tiver certeza de que seu código está funcionando, refatore
os métodos para um method_missing. Dessa forma, você não ocultará inadvertidamente um
bug por trás de um Método Fantasma.
Tábuas em branco
Onde você e Bill aprendem a evitar outra armadilha comum de falta de método .
Ao voltar do almoço, você encontra um problema inesperado esperando por você no escritório.
O desenvolvedor que escreveu o aplicativo de relatórios se deparou com o que ele considera
“o bug mais estranho de todos”: a classe Computer não pode recuperar informações sobre as
telas das estações de trabalho. Todos os outros métodos funcionam bem, mas Computer#display
não.
Por que Computer#display retorna nil? Você verifica três vezes o código e a fonte de dados
de back-end, mas tudo parece estar bem. Bill teve um insight repentino e listou os métodos de
instância de Object que começam com d:
Parece que Object define um método chamado display (um método raramente usado que
imprime um objeto em uma porta e sempre retorna nil). Computer herda de Object, portanto,
obtém o método de exibição . A chamada para Computer#display encontra um método real
com esse nome, portanto, nunca chega a method_missing. Você está chamando um método
real ao vivo em vez de um método fantasma (57).
Esse problema surge com Proxies Dinâmicos (60). Quando o nome de um método fantasma
entra em conflito com o nome de um método herdado real, o último vence.
Se você não precisar do método herdado, poderá corrigir o problema removendo-o. Enquanto
você está nisso, você pode querer remover a maioria dos métodos da classe, evitando que
tais conflitos de nomes aconteçam novamente. Uma classe pequena Spell: Blank Slate com
um número mínimo de métodos é chamada de Blank Slate. Acontece que
Ruby tem uma lousa em branco pronta para você usar.
Tábuas em Branco • 67
BasicObject
A raiz da hierarquia de classes do Ruby, BasicObject, possui apenas alguns métodos de
instância:
method/basic_object.rb
im = BasicObject.instance_methods im #
=> [:==, :igual?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__]
Se você não especificar uma superclasse, suas classes herdarão por padrão de Object,
que é uma subclasse de BasicObject. Se você quiser um Blank Slate (66), você pode
herdar diretamente de BasicObject . Por exemplo, se Computer herdou diretamente de
BasicObject, ele não teria um método de exibição problemático .
Removendo Métodos
Você pode remover um método de uma classe usando Module#undef_method ou
Module#remove_method. O drástico undef_method remove qualquer método, incluindo
os herdados. O kinder remove_method remove o método do receptor, mas deixa os
métodos herdados sozinhos. Vejamos uma biblioteca da vida real que usa undef_method
para criar uma tábula rasa.
O Exemplo do
Construtor A gema do Construtor é um gerador de XML com um diferencial. Você pode
gerar tags XML chamando métodos em Builder::XmlMarkup:
métodos/builder_example_1.rb
require 'builder' xml =
Builder::XmlMarkup.new(:target=>STDOUT, :indent=>2)
xml.coder
{ xml.name 'Matsumoto', :nickname => 'Matz'
xml.language 'Ruby'
}
O Builder adapta habilmente a sintaxe do Ruby para suportar tags aninhadas, atributos e outras sutilezas. A
ideia principal do Builder é simples: chamadas como nome e idioma são processadas por
XmlMarkup#method_missing, que gera uma tag XML para cada chamada.
Agora imagine que você tem que gerar um pedaço de XML descrevendo um curso universitário. Pode ser
assim:
ÿ <semester>
<class>Egiptologia</class>
<class>Ornitologia</class> </
semester>
métodos/builder_example_2.rb
xml.semester
{ xml.class 'Egiptologia'
xml.class 'Ornitologia'
}
Se XmlMarkup fosse uma subclasse de Object, as chamadas para a classe entrariam em conflito com a classe
de Object . Para evitar esse conflito, XmlMarkup herda de um Blank Slate (66) que remove a classe e a maioria
dos outros métodos de Object. Quando Builder foi escrito, BasicObject ainda não existia. (Foi introduzido no
Ruby 1.9.) Portanto, o Builder define sua própria classe Blank Slate:
gems/builder-3.2.2/lib/blankslate.rb
aula em branco
# Esconda o método chamado +name+ na classe BlankSlate. Não # oculte
+instance_eval+ ou qualquer método que comece com "__". def self.hide(nome)
# ...
if instance_methods.include?(name._blankslate_as_name) &&
nome !~ /^(__|instance_eval$)/
undef_method nome
fim
fim
# ...
Encerramento • 69
é mais um julgamento: você pode optar por removê-lo, mas o Builder decidiu não fazê-lo.
Agora que você conhece o Blank Slates, pode finalmente corrigir o bug na classe Computer .
Para transformar Computer em uma folha em branco (66) e corrigir o bug do método de exibição , você
e Bill o transformam em uma subclasse de BasicObject:
Há uma última melhoria que você pode fazer nesta classe. BasicObject não tem um respond_to? método.
(respond_to? é um método da subclasse Object de BasicObject .)
Porque você não tem respond_to?, você pode deletar o agora inútil re spond_to_missing? método que
você e Bill adicionaram novamente em respond_to_missing?, na página 62. Depois de fazer isso, você
finalmente concluiu a implementação baseada em method_missing de Computer.
Embrulhar
Vamos rever o trabalho de hoje. Você e Bill começaram com uma aula de informática que continha
muita duplicação. (A classe original está em Double, Treble… Trouble, na página 47.) Você removeu a
duplicação de duas maneiras diferentes.
Sua primeira tentativa contou com Métodos Dinâmicos (51) e Despacho Dinâmico (49):
métodos/computador/more_dynamic_methods.rb
classe Computador
def initialize(computer_id, data_source) @id =
computer_id
@data_source = data_source
data_source.methods.grep(/^get_(.*)_info$/) { Computer.define_component $1 } end
def self.define_component(name)
define_method(name) do
info = @data_source.send "get_#{name}_info", @id preço =
@data_source.send "get_#{name}_price", @id result =
"#{name.capitalize}: #{info} ($ #{preço})" return "* #{resultado}" se
preço >= 100 resultado
fim
fim
fim
Sua segunda tentativa centrou-se em Ghost Methods (57) (para ser mais preciso, usou um Dynamic Proxy
(60) que também é um Blank Slate (66)):
métodos/computador/
blank_slate.rb class Computer <
BasicObject def initialize(computer_id, data_source)
@id = computer_id
@data_source = data_source end
fim
fim
Nenhuma das soluções seria prática sem os recursos dinâmicos do Ruby. Se você vem de uma linguagem
estática, provavelmente está acostumado a detectar e remover a duplicação dentro de seus métodos. Em
Ruby, você também pode querer procurar por duplicação entre os métodos. Então você pode remover essa
duplicação com alguns dos feitiços que aprendeu hoje.
Você e Bill podem considerar as duas soluções. É hora de fazer uma escolha. Qual
Os problemas com os Ghost Methods se resumem ao fato de que eles não são realmente métodos; em vez
disso, eles são apenas uma maneira de interceptar chamadas de método. Por causa disso, eles se comportam
de maneira diferente dos métodos reais. Por exemplo, eles não aparecem na lista de nomes retornada por
Object#methods. Em contraste, os Métodos Dinâmicos são apenas métodos regulares que foram definidos
com de fine_method em vez de def, e eles se comportam da mesma forma que qualquer outro método.
Encerramento • 71
Há momentos em que os Métodos Fantasmas são sua única opção viável. Isso
geralmente acontece quando você tem um grande número de chamadas de método ou
quando não sabe quais chamadas de método podem ser necessárias em tempo de
execução. Por exemplo, reveja a biblioteca do Builder em O Exemplo do Builder, na
página 67. O Builder não pode definir um Método Dinâmico para cada uma das tags XML
potencialmente infinitas que você pode querer gerar, então ele usa method_missing para
interceptar chamadas de método em vez de.
Você e Bill decidem seguir esta regra e você confirma a versão baseada em define_method
de Computer para o repositório do projeto. Amanhã certamente será outro dia de desafios
de codificação, então é hora de ir para casa e descansar.
CAPÍTULO 4
Quarta-feira: Blocos
Ontem você aprendeu muito sobre métodos e chamadas de métodos. Hoje você vai lidar com
blocos.
Você provavelmente já está familiarizado com os blocos - você não pode escrever muito código
Ruby sem eles. Mas o que você talvez não saiba é que os blocos são uma ferramenta poderosa
para controlar o escopo, ou seja, quais variáveis e métodos podem ser vistos por quais linhas de
código. Neste capítulo, você descobrirá como esse controle de escopo torna os blocos a pedra
angular da metaprogramação Ruby.
Os blocos são apenas um membro de uma família maior de “objetos que podem ser chamados”,
que inclui objetos como procs e lambdas. Este capítulo mostra como você pode usar esses e
outros objetos chamáveis para sua maior vantagem - por exemplo, para armazenar um bloco e
executá-lo posteriormente.
Apenas um breve anúncio de serviço público antes de começar: os capítulos anteriores nunca se
afastaram muito dos conceitos usuais de orientação a objetos, como classes, objetos e métodos.
Os blocos têm uma herança diferente que pode ser rastreada até linguagens de programação
funcionais, como LISP. Se você pensa em objetos e classes, espere lidar com alguns conceitos
novos neste capítulo.
É provável que você ache esses conceitos estranhos e, ao mesmo tempo, fascinantes.
Com essa espiada no que este capítulo trata, agora é hora de entrar no escritório.
Você mal teve tempo de verificar sua correspondência e Bill já está indo até sua mesa, ansioso
para começar a trabalhar. “Conversei com o chefe sobre o trabalho de hoje”, diz ele. “Não vou
entrar em detalhes agora, mas posso dizer que vamos
precisar de blocos para o projeto de hoje.” Antes de vocês dois entrarem na briga, vocês precisam
entender as nuances dos blocos. Você concorda em passar a manhã conversando sobre blocos,
adiando o projeto de hoje para depois do almoço.
Roteiro de hoje
Em uma folha de papel, Bill lista as coisas que deseja cobrir:
• Uma visão geral dos escopos e como você pode transportar variáveis através dos escopos
usando blocos como fechamentos
• Como você pode converter blocos em objetos chamáveis que você pode reservar
e chamar mais tarde, como Procs e lambdas
Você começa com o primeiro ponto – uma rápida revisão do básico. (Se você já conhece o básico
dos blocos Ruby, pode pular direto para Blocks Are Closures, na página 77.)
blocks/
basics_failure.rb def
a_method(a, b) a
+ yield(a, b) end
Você pode definir um bloco com chaves ou com as palavras-chave do…end . Uma convenção
comum é usar chaves para blocos de uma linha e do…end para blocos de várias linhas.
Você pode definir um bloco somente quando chamar um método. O bloco é passado diretamente
para o método e o método pode chamar de volta o bloco com a palavra-chave yield .
Dentro de um método, você pode perguntar ao Ruby se a chamada atual inclui um bloco.
Você pode fazer isso com o Kernel#block_given? método:
Questionário: Ruby# • 75
def a_method
retorna rendimento se block_dado? 'sem
bloqueio'
fim
Se você usar yield quando block_dado? for false, você obterá um erro de tempo de execução.
Agora você pode aplicar o que sabe sobre blocos a um cenário da vida real.
Questionário: Ruby#
Bill compartilha um pequeno segredo: “Sabe, alguns anos atrás, eu ganhava a vida
escrevendo código C#. Devo admitir que o C# tinha alguns recursos interessantes.
Deixe-me mostrar-lhe um desses.
A palavra-chave usando
Este código descarta corretamente a conexão após usá-la. No entanto, não lida com
exceções. Se ReadStuff lançar uma exceção, a última linha nunca será executada e
conn nunca será descartado. O que o código deve fazer é gerenciar exceções,
descartando a conexão independentemente de uma exceção ser lançada. C# fornece
uma palavra-chave chamada using que passa por todo o processo para você:
conn.ReadData();
FaçaMaisCoisas();
}
A palavra-chave using espera que conn tenha um método chamado Dispose. Esse
método é chamado automaticamente após o código entre chaves, independentemente
de uma exceção ser lançada.
O desafio
Para atualizar os fundamentos dos blocos, Bill lança um desafio para você: escreva uma versão
Ruby de using. Certifique-se de que ele passe neste teste:
blocks/
using_test.rb exigir
'teste/unidade' require_relative 'usando'
definitivamente
descartado?
@disposed fim
fim
def test_disposes_of_resources
r = Recurso.novo
using(r) {}
afirmar r.disposed? fim
def test_disposes_of_resources_in_case_of_exception
r = Recurso.novo
assert_raises(Exception) { using(r)
{ raise
Exception
}
fim
Solução do questionário
blocos/
usando.rb kernel do módulo
def using(resource) start
yield
garantir
recurso.dispose fim
fim
fim
Você não pode definir uma nova palavra-chave, mas pode falsificá-la com um Método Kernel (32). Kernel#using
usa o recurso gerenciado como um argumento. Ele também recebe um bloco, que ele executa.
Independentemente de o bloco ser concluído normalmente, as chamadas de cláusula de garantia dispõem
sobre o recurso para liberá-lo de forma limpa. Não há cláusula de resgate , portanto, qualquer exceção ainda
é propagada para o código que chama Kernel#using.
Agora que você revisou os fundamentos do bloco, pode ir para o segundo item da lista do Roteiro de hoje, na
página 74: fechamentos.
Como Bill observa em um pedaço de papel de rascunho, um bloco não é apenas um pedaço flutuante de
código. Você não pode executar o código no vácuo. Quando o código é executado, ele precisa de um
ambiente: variáveis locais, variáveis de instância, self….
Como essas entidades são basicamente nomes vinculados a objetos, você pode chamá-los de vinculações
para abreviar. O ponto principal sobre os blocos é que eles são completos e vêm prontos para serem
executados. Eles contêm o código e um conjunto de ligações.
Você provavelmente está se perguntando onde o bloco pega suas ligações. Quando você define o bloco, ele
simplesmente pega as ligações que estão lá naquele momento e carrega essas ligações junto quando você
passa o bloco para um método:
blocks/blocks_and_bindings.rb
def my_method
x = "Adeus"
yield("cruel") end
x = "Olá"
meu_método {|y| "#{x}, #{y} mundo" } # => "Olá, mundo cruel"
Ao criar o bloco, você captura as ligações locais, como x. Em seguida, você passa o bloco para
um método que possui seu próprio conjunto separado de associações. No exemplo anterior, essas
vinculações também incluem uma variável chamada x. Ainda assim, o código no bloco vê o x que
estava por perto quando o bloco foi definido, não o x do método, que não é visível no bloco.
Você também pode definir ligações adicionais dentro do bloco, mas elas desaparecem após o
término do bloco:
blocks/block_local_vars_failure.rb
def just_yield
rendimento final
top_level_variable = 1
just_yield do
top_level_variable += 1
local_to_block = 1 end
top_level_variable # => 2
local_to_block # => Erro!
Então, como você usa encerramentos na prática? Para entender isso, observe mais de perto o
local onde residem todas as vinculações - o escopo. Aqui você aprenderá a identificar os pontos
em que um programa muda de escopo e encontrará um problema específico com a mudança de
escopos que pode ser resolvido com fechamentos.
Escopo
Imagine ser um pequeno depurador percorrendo um programa Ruby.
Você pula de instrução em instrução até que finalmente atinge um ponto de interrupção. Agora
recupere o fôlego e olhe ao redor. Veja a paisagem ao seu redor? Esse é o seu
escopo.
Você pode ver ligações em todo o escopo. Olhe para os seus pés e verá um monte de
variáveis locais. Levante a cabeça e verá que está dentro de um objeto, com seus próprios
métodos e variáveis de instância; esse é o objeto atual, também conhecido como self. Mais
longe, você vê a árvore de constantes tão clara que poderia marcar sua posição atual em um
mapa. Aperte os olhos e você pode até ver um monte de variáveis globais à distância.
Mas o que acontece quando você se cansa do cenário e decide seguir em frente?
Mudança de Escopo
Este exemplo mostra como o escopo muda à medida que seu programa é executado,
rastreando os nomes das ligações com o método Kernel#local_variables :
blocks/scopes.rb
v1 = 1
classe MinhaClasse
v2 = 2
local_variables def # => [:v2]
my_method v3 =
3
local_variables fim
obj = MyClass.new
obj.my_method # => [:v3] #
obj.my_method => [:v3] # =>
local_variables [:v1, :obj]
Acompanhe o programa à medida que ele se move pelos escopos. Ele começa no escopo de
nível superior sobre o qual você leu em O nível superior, na página 35. Depois de definir v1
no escopo de nível superior, o programa entra no escopo da definição de MyClass . O que
acontece depois?
Algumas linguagens, como Java e C#, permitem que “escopos internos” vejam variáveis de
“escopos externos”. Esse tipo de visibilidade aninhada não acontece em Ruby, onde os
escopos são nitidamente separados: assim que você insere um novo escopo, os vínculos
anteriores são substituídos por um novo conjunto de vínculos. Isso significa que quando o
programa entra no MyClass, v1 “sai do escopo” e não é mais visível.
def a_scope
$var = "algum valor" end
a_scope
another_scope # => "algum valor"
O problema com as variáveis globais é que todas as partes do sistema podem alterá-las, portanto, em pouco tempo
você achará difícil rastrear quem está alterando o quê. Por esse motivo, a regra geral é esta: quando se trata de
variáveis globais, use-as com moderação, se for o caso.
Às vezes, você pode usar uma variável de instância de nível superior no lugar de uma variável global.
Estas são as variáveis de instância do objeto principal de nível superior , descritas em O nível superior, na página
35:
def meu_método
@var
fim
Você pode acessar uma variável de instância de nível superior sempre que main assume o papel de self, como no
exemplo anterior. Quando qualquer outro objeto é self, a variável de instância de nível superior está fora do escopo.
classe MinhaClasse
def my_method
@var = "Esta não é a @var de nível superior!" fim fim
Sendo menos acessíveis universalmente, as variáveis de instância de nível superior são geralmente consideradas
mais seguras do que as variáveis globais, mas não por uma ampla margem.
Aqui está o ponto importante do exemplo: “Sempre que o programa muda de escopo, algumas ligações são
substituídas por um novo conjunto de ligações.” Concedido, isso não acontece com todas as ligações de
cada vez. Por exemplo, se um método chama outro método no mesmo objeto, as variáveis de instância
permanecem no escopo durante a chamada. Em geral, porém, as vinculações tendem a ficar fora do escopo
quando o escopo é alterado. Em particular, as variáveis locais mudam a cada novo escopo.
Como você pode ver, acompanhar os escopos pode ser uma tarefa complicada. Você pode identificar
escopos mais rapidamente se aprender sobre Scope Gates.
Portões de Escopo
Existem exatamente três lugares onde um programa deixa o escopo anterior para trás e abre um novo:
• Definições de classe
• Definições do módulo
• Métodos
O escopo muda sempre que o programa entra (ou sai) de uma definição de classe ou módulo ou de um
método. Essas três bordas são marcadas pelas palavras-chave class, module e def, respectivamente. Cada
uma dessas palavras-chave funciona como um Scope Gate. Feitiço: Portão de Escopo
Por exemplo, aqui está o programa de exemplo anterior novamente, com Scope Gates claramente marcado
por comentários:
v1 = 1
classe MinhaClasse # SCOPE GATE: entrando na aula
v2 = 2
local_variables def # => ["v2"]
my_method v3 = # SCOPE GATE: entrando em def
3
local_variables fim
# SCOPE GATE: deixando def #
local_variables fim => ["v2"]
# SCOPE GATE: saída da aula
obj = MyClass.new
obj.my_method # => [:v3] #
local_variables => [:v1, :obj]
Agora é fácil ver que este programa abre três escopos separados: o escopo de nível superior, um novo
escopo quando entra em MyClass e um novo escopo quando chama my_method.
Por outro lado, o código em uma definição de método é executado posteriormente, quando
você chama o método. No entanto, ao escrever seu programa, você geralmente não se importa
quando ele muda de escopo - você só se importa com isso.
Agora você pode identificar os lugares onde seu programa muda de escopo - os pontos
marcados por classe, módulo e def. Mas e se você quiser passar uma variável por um desses
pontos? Esta questão leva você de volta aos blocos.
Achatando o escopo
Quanto mais você se torna proficiente em Ruby, mais você se mete em situações difíceis em
que deseja passar ligações por um Scope Gate (81):
blocks/flat_scope_1.rb
minha_var = "Sucesso"
class MyClass #
Queremos imprimir my_var aqui... def my_method
Scope Gates são uma barreira formidável. Assim que você passa por um deles, as variáveis
locais saem do escopo. Então, como você pode carregar my_var através não de um, mas de
dois Scope Gates?
Olhe primeiro para a classe Scope Gate. Você não pode passar my_var por ele, mas pode
substituir a classe por outra coisa que não seja um Scope Gate: uma chamada de método.
Se você pudesse chamar um método em vez de usar a palavra-chave class , poderia capturar
my_var em um encerramento e passar esse encerramento para o método. Você consegue
pensar em um método que faz a mesma coisa que a classe faz?
blocks/flat_scope_2.rb
minha_var = "Sucesso"
def my_method
# ...mas como podemos imprimi-lo aqui? fim
fim
Agora, como você pode passar my_var pelo def Scope Gate? Mais uma vez, você deve substituir a palavra-
chave por uma chamada de método. Pense na discussão sobre Métodos Dinâmicos (51): ao invés de def,
você pode usar Module#define_method:
blocks/flat_scope_3.rb
minha_var = "Sucesso"
MyClass = Class.new do
"#{my_var} na definição da classe"
ÿ MyClass.new.my_method
Se você substituir Scope Gates por chamadas de método, permitirá que um escopo veja variáveis de outro
escopo. Tecnicamente, esse truque deveria ser chamado de escopos lexicais aninhados, mas muitos
programadores Ruby referem-se a ele simplesmente como “achatar o escopo”, significando que os dois
escopos compartilham variáveis como se estivessem espremidos juntos. Para resumir, você pode chamar
esse feitiço de Flat Scope. Feitiço: Mira Plana
Compartilhando o
escopo Depois de conhecer os escopos planos (83), você pode fazer praticamente o que quiser com os
escopos. Por exemplo, suponha que você deseja compartilhar uma variável entre alguns métodos e não deseja
que mais ninguém veja essa variável.
Você pode fazer isso definindo todos os métodos no mesmo Flat Scope da variável:
blocks/shared_scope.rb
def define_methods
compartilhado = 0
compartilhada
define_métodos
contador # => 0
inc(4)
contador # => 4
Este exemplo define dois Kernel Methods (32). (Também usa Dynamic Dispatch (49) para
acessar o método de classe privada define_method no Kernel.) Ambos Kernel#counter e
Kernel#inc podem ver a variável compartilhada . Nenhum outro método pode ser
compartilhado, porque é protegido por um Scope Gate (81) — é para isso que serve o
método define_methods . Essa maneira inteligente de controlar o compartilhamento de
variáveis é chamada de Spell: Shared Scope Shared Scope.
Escopos compartilhados não são muito usados na prática, mas são um truque poderoso e
um bom exemplo do poder dos escopos. Com uma combinação de Scope Gates, Flat
Scopes e Shared Scopes, você pode torcer e dobrar seus escopos para ver exatamente as
variáveis de que precisa, no local desejado. Agora que você tem esse poder, é hora de
encerrar os fechamentos do Ruby.
Conclusão dos
Closures Cada escopo do Ruby contém um monte de ligações, e os escopos são separados
por Scope Gates (81): classe, módulo e def.
Se você quiser esgueirar-se uma ou duas ligações através de um Scope Gate, você pode
usar blocos. Um bloco é um fechamento: quando você define um bloco, ele captura as
ligações no ambiente atual e as carrega. Assim, você pode substituir o Scope Gate por uma
chamada de método, capturar as ligações atuais em um encerramento e passar o
encerramento para o método.
Você pode substituir class por Class.new, module por Module.new e def por Module#de
fine_method. Este é um Flat Scope (83), o feitiço básico relacionado ao fechamento.
Se você definir vários métodos no mesmo Flat Scope, talvez protegidos por um Scope Gate,
todos esses métodos poderão compartilhar vinculações. Isso é chamado de escopo
compartilhado (84).
Bill olha para o roteiro que criou. (Veja Roteiro de hoje, na página 74.)
“Agora que você experimentou Flat Scopes, devemos passar para algo mais avançado:
instance_eval.”
instância_eval() • 85
instância_eval()
Onde você aprende outra maneira de misturar código e ligações à vontade.
blocks/
instance_eval.rb
class MyClass def inicializar
@v = 1
fim
fim
obj = MinhaClasse.new
obj.instance_eval do self
# => #<MyClass:0x3340dc @v=1> # => 1
@v fim
O bloco é avaliado com o receptor como self, para que possa acessar os métodos privados
e as variáveis de instância do receptor, como @v. Mesmo que instance_eval mude self, o
bloco que você passa para instance_eval ainda pode ver os bindings do local onde está
definido, como qualquer outro bloco:
v=2
obj.instance_eval { @v = v }
obj.instance_eval { @v } # => 2
As três linhas no exemplo anterior são avaliadas no mesmo Flat Scope (83), então todas
podem acessar a variável local v—mas os blocos são avaliados com o objeto como self,
então eles também podem acessar a variável de instância de obj @v. Em todos esses casos,
você pode chamar o bloco que você passa para instance_eval de Context Probe, porque é
como um trecho de código que você mergulha dentro de um objeto para fazer Spell: Context Probe algo lá.
Quebrando o encapsulamento
Neste ponto, você pode estar horrorizado. Com uma Sonda de Contexto (85), você pode
causar estragos no encapsulamento! Nenhum dado é mais dados privados. Isso não é uma
coisa muito ruim?
instância_exec()
instance_eval tem um irmão gêmeo um pouco mais flexível chamado instance_exec que
permite que você passe argumentos para o bloco. Esse recurso é útil em alguns casos
raros, como neste exemplo engenhosamente complicado:
blocks/instance_exec.rb
classe C
def inicializar
@x = 1
fim
fim
classe D
def twisted_method @y
=2
C.new.instance_eval { "@x: #{@x}, @y: #{@y}" } end end
"
D.new.twisted_method # => "@x: 1, @y:
Você pode assumir que o bloco em D#twisted_method pode acessar tanto a variável de instância @x
de C quanto a variável de instância @y de D no mesmo Flat Scope (83).
No entanto, as variáveis de instância dependem de self, portanto, quando instance_eval alterna self
para o receptor, todas as variáveis de instância no chamador ficam fora do escopo. O código dentro
do bloco interpreta @y como uma variável de instância de C que não foi inicializada e, como tal, é nil
(e imprime como uma string vazia).
Para mesclar @x e @y no mesmo escopo, você pode usar instance_exec para passar o valor de @y
para o bloco:
classe D
def twisted_method @y
=2
ÿ C.new.instance_exec(@y) {|y| "@x: #{@x}, @y: #{y}" }
fim
fim
Outra razão aceitável para quebrar o encapsulamento é sem dúvida o teste. Aqui está um
exemplo.
O Exemplo Padrino A
estrutura da web Padrino define uma classe Logger que gerencia todos os logs com os
quais um aplicativo da web deve lidar. O Logger armazena sua própria configuração em
variáveis de instância. Por exemplo, @log_static será verdadeiro se o aplicativo precisar
registrar o acesso a arquivos estáticos.
instância_eval() • 87
gems/padrino-core-0.11.3/test/
test_logger.rb descreva
"PadrinoLogger" do context 'para a funcionalidade
do logger' do context "log de ativos
estáticos" do should 'não registrar ativos estáticos por padrão' do
# ...
get "/images/something.png"
assert_equal "Foo", body
assert_match "", Padrino.logger.log.string end
fim
# ...
O primeiro teste acessa um arquivo estático e verifica se o logger não registra nada. Este é o
comportamento padrão do Padrino. O segundo teste usa instance_eval para alterar a configuração
do criador de logs e habilitar o log de arquivo estático. Em seguida, ele acessa a mesma URL do
primeiro teste e verifica se o logger realmente registrou algo. Antes de sair, o segundo teste
redefine o log de arquivo estático para o estado falso padrão .
Você pode facilmente criticar esses testes por serem frágeis: se a implementação do Logger mudar
e a variável de instância @log_static desaparecer, o teste será interrompido. Como muitas outras
coisas em Ruby, o encapsulamento é uma ferramenta flexível que você pode optar por ignorar, e
cabe a você decidir se e quando aceitar esse risco. Os autores do Padrino decidiram que um hack
rápido dentro do objeto logger era uma solução aceitável neste caso.
Salas limpas
Às vezes você cria um objeto apenas para avaliar os blocos dentro dele. Um objeto como esse
pode ser chamado de Sala Limpa: Feitiço: Sala Limpa
blocks/clean_room.rb
classe CleanRoom
def current_temperature # ...
end
end
clean_room = CleanRoom.new
clean_room.instance_eval do
se current_temperature < 20
# TODO: usar jaqueta
ponta ponta
Uma Sala Limpa é apenas um ambiente onde você pode avaliar seus bloqueios. Ele pode expor
alguns métodos úteis que o bloco pode chamar, como current_tem perature no exemplo acima.
No entanto, a Sala Limpa ideal não possui muitos métodos ou variáveis de instância, porque os
nomes desses métodos e variáveis de instância podem entrar em conflito com os nomes no
ambiente que
o bloco vem. Por esse motivo, instâncias de BasicObject geralmente são boas Salas Limpas,
porque são Tábuas em Branco (66) — portanto, quase não têm nenhum método.
Você encontrará um exemplo prático de Sala Limpa em Quiz: A Better DSL, na página
98.
Isso é tudo que você precisa saber sobre instance_eval. Agora você pode passar para o último
tópico do roteiro de hoje: objetos que podem ser chamados.
Objetos chamáveis
Onde você aprende como os blocos são apenas parte de uma família maior, e Bill mostra como
deixar o código de lado e executá-lo mais tarde.
Se você chegar ao fundo disso, usar um bloco é um processo de duas etapas. Primeiro, você
deixa algum código de lado e, segundo, chama o bloco (com yield) para executar o código. Esse
mecanismo de “código de pacote primeiro, chame depois” não é exclusivo de blocos. Existem
pelo menos três outros lugares em Ruby onde você pode empacotar o código:
Objetos Chamáveis • 89
Procs e lambdas são os grandes para falar aqui. Vamos começar com eles e trazer os métodos
de volta à cena mais tarde.
Objetos de processo
Embora a maioria das coisas em Ruby sejam objetos, os blocos não são. Mas por que você se
importaria com isso? Imagine que você deseja armazenar um bloco e executá-lo posteriormente.
Para fazer isso, você precisa de um objeto.
Para resolver este problema, Ruby fornece a classe de biblioteca padrão Proc. Um Proc é um
bloco que foi transformado em um objeto. Você pode criar um Proc passando o bloco para
Proc.new. Mais tarde, você pode avaliar o bloco-objeto com Proc#call:
Existem mais algumas maneiras de criar Procs em Ruby. Ruby fornece dois Métodos do Kernel
(32) que convertem um bloco em um Proc: lambda e proc. Em pouco tempo, você verá que
existem diferenças sutis entre criar Procs com lambda e criá-los de qualquer outra maneira, mas
na maioria dos casos você pode usar o que preferir:
Além disso, você pode criar um lambda com o chamado operador “stabby lambda”:
p = ->(x) { x + 1 }
p = lambda {|x| x + 1 }
Até agora, você viu não uma, mas quatro maneiras diferentes de converter um bloco em um
Proc. Há também uma quinta via, que merece uma seção própria.
O operador &
Um bloco é como um argumento anônimo adicional para um método. Na maioria dos casos,
você executa o bloco ali mesmo no método, usando yield. Em dois casos, o rendimento não é
suficiente:
• Você deseja passar o bloco para outro método (ou mesmo outro bloco). • Você deseja
converter o bloco em um Proc.
Em ambos os casos, você precisa apontar para o bloco e dizer: “Quero usar este bloco” —
para fazer isso, você precisa de um nome. Para anexar uma ligação ao bloco, você pode
adicionar um argumento especial ao método. Este argumento deve ser o último na lista de
argumentos e prefixado por um sinal & . Aqui está um método que passa o bloco para outro
método:
blocks/e
comercial.rb def
math(a, b)
yield(a, b) end
Se você chamar do_math sem um bloco, o argumento &operation será limitado a nil e a
operação yield em math falhará.
E se você quiser converter o bloco em um Proc? Acontece que, se você fez referência à
operação no código anterior, já teria um objeto Proc . O verdadeiro significado do & é este:
“Eu quero pegar o bloco que é passado para este método e transformá-lo em um Proc.”
Apenas solte o &, e você ficará com um Proc novamente:
def my_method(&the_proc)
the_proc
end
Agora você conhece várias maneiras diferentes de converter um bloco em um Proc. Mas e
se você quiser convertê-lo de volta? Novamente, você pode usar o operador & para
converter o Proc em um bloco:
blocks/
proc_to_block.rb def meu_método(saudação)
"#{saudação}, #{rendimento}!"
fim
Quando você chama my_method, o & converte my_proc em um bloco e passa esse bloco
para o método.
Objetos chamáveis • 91
Agora você sabe como converter um bloco em um Proc e vice-versa. Vejamos um exemplo
da vida real de um objeto que pode ser chamado que começa sua vida como um lambda e
depois é convertido em um bloco regular.
O Exemplo HighLine A
gema HighLine ajuda a automatizar a entrada e a saída do console. Por exemplo, você pode
instruir o HighLine a coletar entradas de usuários separadas por vírgulas e dividi-las em uma
matriz, tudo em uma única chamada. Aqui está um programa Ruby que permite inserir uma
lista de amigos separados por vírgulas:
blocks/highline_example.rb
requer 'highline'
hl = HighLine.new
friends = hl.ask("Amigos?", lambda {|s| s.split(',') }) puts "Você é amigo
de: #{friends.inspect}"
ÿ Amigos?
ÿ Ivana, Roberto, Olaf ÿ Você
é amigo de: ["Ivana", " Roberto", " Olaf"]
Você chama HighLine#ask com uma string (a pergunta para o usuário) e um Proc que
contém o código de pós-processamento. (Você pode se perguntar por que o HighLine requer
um argumento Proc em vez de um bloco simples. Na verdade, você pode passar um bloco
para perguntar, mas esse mecanismo é reservado para um recurso diferente do HighLine.)
Se você ler o código de HighLine#ask, verá que ele passa o Proc para um objeto da classe
Question, que armazena o Proc como uma variável de instância. Mais tarde, após coletar a
entrada do usuário, a Questão passa a entrada para o Proc armazenado.
Se você quiser fazer outra coisa com a entrada do usuário - digamos, alterá-la para letras
maiúsculas - basta criar um Proc diferente:
ÿ Nome?
ÿ conta
ÿ Olá, Bill
De forma confusa, porém, os Procs criados com lambda realmente diferem em alguns
aspectos dos Procs criados de qualquer outra maneira. As diferenças são sutis, mas
importantes o suficiente para que as pessoas se refiram aos dois tipos de Procs por nomes
distintos: Procs criados com lambda são chamados de lambdas, enquanto os outros são
simplesmente chamados de procs. (Você pode usar o método Proc#lambda? para verificar
se o Proc é um lambda.)
Uma palavra de advertência antes de mergulhar nesta seção: a diferença entre procs e
lambdas é provavelmente o recurso mais confuso do Ruby, com muitos casos especiais e
distinções arbitrárias. Não há necessidade de entrar em todos os detalhes sangrentos, mas
você precisa saber, pelo menos aproximadamente, as diferenças importantes.
Existem duas diferenças entre procs e lambdas. Uma tem a ver com a palavra-chave return
e a outra diz respeito à verificação de argumentos. Vamos começar com o retorno.
blocks/proc_vs_lambda.rb
def double(callable_object)
callable_object.call * 2 end
def another_double p =
Proc.new { return 10 } result =
p.call return result *
2 # código inacessível! fim
outro_duplo # => 10
Se você está ciente desse comportamento, pode evitar códigos com erros como este:
def double(callable_object)
callable_object.call * 2 end
Objetos Chamáveis • 93
p = Proc.new { 10 }
double(p) # => 20
O que acontece se você chamar esse objeto chamável com três argumentos ou um único
argumento? A resposta longa a essa pergunta é complicada e cheia de casos especiais.1
A resposta curta é que, em geral, lambdas tendem a ser menos tolerantes que procs (e
blocos regulares) quando se trata de argumentos.
Chame um lambda com a aridade errada e ele falhará com um ArgumentError. Por outro
lado, um proc ajusta a lista de argumentos às suas próprias expectativas:
De um modo geral, os lambdas são mais intuitivos do que os procs porque são mais
semelhantes aos métodos. Eles são bastante rígidos quanto à aridade e simplesmente
saem quando você chama return. Por esse motivo, muitos rubistas usam lambdas como
primeira escolha, a menos que precisem dos recursos específicos de procs.
1. Um programa para explorar esses casos especiais, escrito por Paul Cantrell, está em http://innig.net/
software/ruby/closures-in-ruby.rb.
Objetos de método
Para completar, você pode querer dar mais uma olhada no último membro da família dos
objetos que podem ser chamados: os métodos. Se você não está convencido de que métodos,
como lambdas, são apenas objetos que podem ser chamados, veja este código:
blocks/methods.rb
class MyClass
def inicializar (valor)
@x = valor
final
def meu_método
@x
fim fim
object = MyClass.new(1) m =
object.method :my_method m.call
# => 1
Ao chamar Kernel#method, você obtém o próprio método como um objeto Method , que pode
ser executado posteriormente com Method#call. No Ruby 2.1, você também tem Kernel#single
ton_method, que converte o nome de um Singleton Method (114) em um objeto Method . (O
que você está dizendo? Você ainda não sabe o que é um Método Singleton? Oh, você vai,
você vai…)
Ruby tem uma segunda classe que representa métodos — uma que você pode encontrar
por plexing. Vamos dar uma olhada nele primeiro e depois veremos como ele pode ser usado.
UnboundMethods são como métodos que foram separados de sua classe ou módulo original.
Você pode transformar um método em um UnboundMethod chamando Method#unbind. Você
também pode obter um UnboundMethod diretamente chamando Module#in stance_method,
como no exemplo a seguir:
blocks/unbound_methods.rb
module MyModule
def my_method
42
end end
Objetos Chamáveis • 95
unbound = MyModule.instance_method(:my_method)
unbound.class # => UnboundMethod
Você não pode chamar um UnboundMethod, mas pode usá-lo para gerar um método normal que
pode ser chamado. Você faz isso vinculando o UnboundMethod a um objeto com
UnboundMethod#bind. Os UnboundMethods que vêm de uma classe só podem ser vinculados a
objetos da mesma classe (ou uma subclasse), enquanto os UnboundMethods que vêm de um
módulo não têm essa limitação do Ruby 2.0 em diante. Você também pode vincular um
UnboundMethod passando-o para Module#define_method, como no próximo exemplo:
String.class_eval do
define_method :another_method, unbound end
"abc".another_method # => 42
Em alguns casos, você pode querer remover o carregamento automático de uma classe que já
incluiu Loadable. Em outras palavras, você deseja parar de usar Loadable#load e voltar para o
Kernel#load simples. Ruby não tem método uninclude , então você não pode remover Loadable
de seus ancestrais depois de incluí-lo.
O Active Support contorna esse problema com uma única linha de código:
gems/activesupport-4.1.0/lib/active_support/dependencies.rb
módulo carregável
def self.exclude_from(base)
base.class_eval { define_method(:load, Kernel.instance_method(:load)) } end
# ...
Imagine que você tem uma classe MyClass que inclui Loadable. Quando você chama
Loadable.exclude_from(MyClass), o código acima chama instance_method para obter o
Kernel#load original como um UnboundMethod. Em seguida, ele usa esse UnboundMethod para definir uma marca
novo método de carga diretamente no MyClass. Como resultado, MyClass#load é na verdade o
mesmo método que Kernel#load e substitui o método load em Loadable. (Se isso parecer confuso,
tente fazer um desenho da cadeia de ancestrais de MyClass e tudo ficará claro.)
Este truque é um exemplo do poder de UnboundMethods, mas também é uma solução artificial
para um problema muito específico - uma solução que deixa você com uma cadeia confusa de
ancestrais que contém três métodos de carregamento , dois dos quais são idênticos entre si
(Kernel #load e MyClass#load), e dois dos quais nunca são chamados (Kernel#load e
Loadable#load). Provavelmente é uma boa política não tentar esse tipo de invasão de classe em
casa.
chamados Objetos que podem ser chamados são fragmentos de código que você pode avaliar e carregam
seu próprio escopo junto com eles. Podem ser os seguintes:
• Blocos (eles não são realmente “objetos”, mas ainda são “chamáveis”): Avaliados
no escopo em que são definidos.
• Procs: Objetos da classe Proc. Assim como os blocos, eles são avaliados no escopo em que
são definidos.
• Lambdas: Também objetos da classe Proc , mas sutilmente diferentes dos procs regulares.
Eles são encerramentos como blocos e procs e, como tal, são avaliados no escopo em que
são definidos.
Apesar dessas diferenças, você ainda pode converter de um objeto chamável para outro, como
usando Proc.new, Method#to_proc ou o operador & .
“Chega de falar sobre bloqueios”, diz Bill. “É hora de focar no trabalho de hoje.
Vamos chamá-lo de projeto RedFlag.”
RedFlag é um utilitário de monitor para o pessoal do departamento de vendas. Ele deve enviar ao
pessoal de vendas uma mensagem quando um pedido está atrasado, quando o total de vendas
está muito baixo... basicamente, sempre que uma das muitas coisas diferentes acontece. O setor
de vendas deseja monitorar dezenas de eventos diferentes, e a lista deve mudar a cada semana.
Felizmente para você e para Bill, as vendas têm programadores em tempo integral, então você não
precisa escrever os eventos sozinho. Você pode simplesmente escrever uma linguagem específica
de domínio simples. (Você pode ler sobre DSLs no Apêndice 2, Linguagens específicas de domínio,
na página 227.) Os vendedores podem usar essa DSL para definir eventos, como este:
Para definir um evento, você fornece uma descrição e um bloco de código. Se o bloco retornar true,
você receberá um alerta por e-mail. Se retornar falso, nada acontecerá. O sistema deve verificar
todos os eventos a cada poucos minutos.
blocks/redflag_1/
redflag.rb def
event(description) coloca "ALERT: #{description}"
if
yield end load 'events.rb'
A DSL inteira é apenas um método e uma linha que executa um arquivo chamado events.rb. O
código em events.rb deve chamar de volta o método de evento do RedFlag . Para testar o DSL,
você cria um arquivo de eventos rápidos:
blocks/redflag_1/
eventos.rb event "um evento que sempre acontece" do
verdadeiro
"Sucesso!" Bill exclama. “Se programarmos este programa para ser executado a cada poucos minutos,
teremos uma primeira versão funcional do RedFlag. Vamos mostrar para o chefe.”
Compartilhamento entre
eventos Sua chefe se diverte com a simplicidade do RedFlag DSL, mas ela não está completamente
convencida. “As pessoas que escrevem os eventos vão querer compartilhar dados entre os eventos”,
observa ela. “Posso fazer isso com o seu DSL? Por exemplo, dois eventos separados podem acessar a
mesma variável?” ela pergunta a vocês dois.
“Claro que podem”, responde Bill. “Temos um escopo plano (83).” Para provar isso, ele cria um novo
arquivo de eventos:
blocks/redflag_2/
eventos.rb def mensal_vendas
110 # TODO: lê o número real do banco de dados
fim
vendas_alvo = 100
Os dois eventos neste arquivo compartilham um método e uma variável local. você corre vermelho
“Ok, isso funciona”, o chefe concede. “Mas não gosto da ideia de variáveis e métodos como mensal_sales
e target_sales sobrecarregando o escopo de nível superior. Deixe-me mostrar como eu gostaria que o
DSL parecesse”, diz ela. Sem mais delongas, o chefe pega o teclado e começa a produzir códigos como
ninguém.
Onde você inesperadamente é deixado sozinho para desenvolver uma nova versão do RedFlag DSL.
Seu chefe deseja que você adicione uma instrução de configuração ao RedFlag DSL, conforme mostrado
no código a seguir.
blocks/redflag_3/
eventos.rb
setup do puts "Configurando
o céu" @sky_height =
100 end
setup do
puts "Configurando montanhas"
@mountains_height = 200 end
Nesta nova versão do DSL, você está livre para misturar eventos e blocos de configuração
(setups para abreviar). O DSL ainda verifica os eventos e também executa todas as
configurações antes de cada evento. Se você executar redflag.rb no arquivo de teste anterior,
você espera esta saída:
ÿ Configurando o céu
Configurando montanhas
ALERTA: o céu está caindo
Configurando o céu
Configurando montanhas
ALERTA: está cada vez mais perto
Configurando o céu
Configurando montanhas
RedFlag executa todas as configurações antes de cada um dos três eventos. Os dois primeiros
eventos geram um alerta; o terceiro não.
Uma configuração pode definir variáveis usando nomes de variáveis que começam com um
sinal @ , como @sky_height e @mountains_height. Os eventos podem então ler essas variáveis.
Seu chefe acha que esse recurso incentivará os programadores a escrever um código limpo:
todas as variáveis compartilhadas são inicializadas juntas em uma configuração e usadas em
eventos, portanto, é fácil acompanhar as variáveis.
Ainda impressionados com a proeza técnica de seu chefe, você e Bill vão direto ao assunto.
Conta fugitiva
Você e Bill comparam o RedFlag DSL atual com a nova versão que seu chefe sugeriu. O
RedFlag atual executa os blocos imediatamente. O novo RedFlag deve executar as
configurações e os eventos em uma ordem específica. Você começa reescrevendo o método
do evento :
@eventos = []
carregar 'eventos.rb'
Enquanto você pondera seu próximo passo, Bill dá um tapa na testa, murmura algo sobre a
festa de aniversário de sua esposa e sai correndo pela porta. Agora cabe a você sozinho. Você
pode concluir o novo RedFlag DSL e obter a saída esperada do arquivo de teste?
Solução do questionário
Você pode encontrar muitas soluções diferentes para este teste. Aqui está um:
blocks/redflag_3/redflag.rb
configuração def (&block)
@setups << fim do
bloco
@setups = []
@events = []
load 'events.rb'
end
coloca "ALERT: #{event[:description]}" if event[:condition].call end
Com todos os eventos e configurações carregados, seu programa repete os eventos. Para
cada evento, ele chama todos os blocos de configuração e, em seguida, chama o evento.
Você quase pode ouvir a voz de Bill em sua cabeça, soando um pouco como Obi Wan
Kenobi: “Essas variáveis de instância de nível superior, @events e @setups, são como
variáveis globais disfarçadas. Por que você não se livra deles?”
blocks/redflag_4/redflag.rb
lambda
{ configurações
= [] eventos = []
carregar 'eventos.rb'
Agora essas feias variáveis globais se foram, mas o código RedFlag não é tão agradavelmente
simples quanto costumava ser. Cabe a você decidir se essa alteração é uma melhoria ou apenas
uma ofuscação indesejada. Enquanto você decide isso, há uma última mudança que vale a pena
considerar.
Na versão atual do RedFlag, os eventos podem alterar as variáveis de instância de nível superior
compartilhadas uns dos outros:
Você deseja que os eventos compartilhem variáveis com configurações, mas não necessariamente
deseja que os eventos compartilhem variáveis uns com os outros. Mais uma vez, cabe a você
decidir se isso é um recurso ou um possível bug. Se você decidir que os eventos devem ser tão
independentes quanto possível (como testes em um conjunto de testes), você pode querer executar
eventos em uma Sala Limpa (87):
blocks/redflag_5/redflag.rb
cada_evento faça |evento| env =
Object.new each_setup
do |setup| env.instance_eval
&setup end coloca "ALERT:
Encerramento • 103
Agora um evento e suas configurações são avaliadas no contexto de um Objeto que atua como
uma Sala Limpa. As variáveis de instância nas configurações e eventos são variáveis de
instância da Sala Limpa, em vez de variáveis de instância de nível superior. Como cada evento
é executado em sua própria Sala Limpa, os eventos não podem compartilhar variáveis de
instância.
Você pode pensar em usar um BasicObject em vez de um objeto para sua sala limpa.
No entanto, lembre-se de que BasicObject também é uma folha em branco (66) e, como tal,
carece de alguns métodos comuns, como puts. Portanto, você só deve usar um BasicObject se
souber que o código nos eventos RedFlag não vai chamar puts ou outros métodos Object . Você
sorri e adiciona um comentário ao código, deixando essa difícil decisão para Bill.
Embrulhar
Aqui estão alguns feitiços e outras coisas interessantes que você aprendeu hoje:
• O que são Scope Gates (81) e como Ruby gerencia o escopo em geral
• Como tornar as ligações visíveis através de escopos com Flat Scopes (83) e
Escopos compartilhados (84)
• Quais são as diferenças entre os diferentes tipos de objetos que podem ser chamados:
blocos, Procs, lambdas e métodos antigos simples
Foi muita novidade em um único dia. Ao sair furtivamente do escritório, no entanto, você não
consegue se livrar da sensação incômoda de que aprenderá alguns dos segredos mais bem
guardados de Ruby amanhã.
CAPÍTULO 5
Como você sabe, escrever programas orientados a objetos significa gastar uma boa parte
do seu tempo definindo classes. Em Java e C#, definir uma classe é como fazer um
acordo entre você e o compilador. Você diz: “Aqui está como meus objetos devem se
comportar”, e o compilador responde: “Ok, eles vão”. Nada realmente acontece até que
você crie um objeto dessa classe e chame os métodos desse objeto.
Se você aceitar essa noção — de que uma definição de classe Ruby é, na verdade, um
código regular executado —, poderá lançar alguns feitiços poderosos. Dois desses feitiços
sobre os quais você aprenderá neste capítulo são Macros de classe (117) (métodos que
modificam classes) e Around Aliases (134) (métodos que agrupam código adicional em
torno de outros métodos). Para ajudá-lo a aproveitar ao máximo esses feitiços, este
capítulo também descreve as classes singleton, um dos recursos mais elegantes do Ruby.
As classes Singleton são um tópico avançado, então entendê-las fará com que você se
gabe entre os especialistas em Ruby.
Este capítulo também vem com alguns anúncios de serviço público. Primeiro, lembre-se
de que uma aula é apenas um módulo aprimorado; portanto, tudo o que você aprender
sobre as aulas também se aplica aos módulos. Embora eu não repita esse PSA em todas
as seções deste capítulo, lembre-se de que sempre que ler sobre uma “definição de
classe”, você também pode pensar em “definição de módulo”. Em segundo lugar, esteja
preparado: este é provavelmente o capítulo mais avançado de todo o livro. Leia-o e você
será capaz de percorrer os cantos mais sombrios do modelo de objeto Ruby.
Você tropeça sonolento no escritório, ansiando por seu café da manhã de quinta-feira, apenas
para ser emboscado por um animado Bill. "Ei!" ele diz. “Todo mundo gosta das refatorações
do Bookworm que fizemos na segunda-feira, e o chefe quer mais. Mas antes de começarmos,
vamos examinar um pouco da teoria sobre as definições de classe. Começaremos de onde
paramos na segunda-feira: no modelo de objeto Ruby.”
Você provavelmente pensa em uma definição de classe como o local onde define os métodos.
Na verdade, você pode colocar qualquer código que quiser em uma definição de classe:
class MyClass
coloca 'Olá' no
final
ÿ Olá
As definições de classe também retornam o valor da última instrução, assim como métodos e
blocos:
Este último exemplo enfatiza um ponto importante que você deve lembrar da palavra-chave
self, na página 34: em uma definição de classe (ou módulo), a própria classe assume o papel
do objeto atual self. Classes e módulos são apenas objetos, então por que uma classe não
pode ser self? Lembre-se desse ponto sobre definições de classe e self , porque o conceito
se tornará útil um pouco mais tarde.
Enquanto estamos no tópico de self, você pode aprender sobre um conceito relacionado: o da
classe atual.
A Classe Atual
Como você sabe, onde quer que esteja em um programa Ruby, sempre terá um objeto atual:
self. Da mesma forma, você sempre tem uma classe atual (ou módulo). Quando você define
um método, esse método se torna um método de instância do método atual
aula.
Embora você possa obter uma referência ao objeto atual por meio de self, não há palavra-chave
equivalente para obter uma referência à classe atual. No entanto, na maioria das situações, é fácil
acompanhar a classe atual apenas observando o código. Veja como:
• No nível superior de seu programa, a classe atual é Object, a classe de main. (É por isso que,
se você definir um método no nível superior, esse método se tornará um método de instância
de Object.)
• Em um método, a classe atual é a classe do objeto atual. (Tente definir um método dentro de
outro método com def, e você verá que o novo método é definido na classe self. Esta
informação provavelmente vai lhe render algum concurso de curiosidades sobre Ruby.)
• Quando você abre uma classe com a palavra-chave class (ou um módulo com a palavra-chave
module ), essa classe se torna a classe atual.
Este último caso é provavelmente o único com o qual você se preocupa na prática. Na verdade,
você o usa o tempo todo quando abre uma classe com a palavra-chave class e define métodos na
classe com def. No entanto, a palavra-chave class tem uma limitação: ela precisa do nome de
uma classe. Infelizmente, em algumas situações você pode não saber o nome da classe que
deseja abrir. Por exemplo, pense em um método que pega uma classe e adiciona um novo método
de instância a ela:
def add_method_to(a_class)
# TODO: definir o método m() no final de
a_class
Como você pode abrir a classe se não sabe o nome dela? Você precisa de alguma forma diferente
da palavra-chave class para alterar a classe atual. Digite o método class_eval .
class_eval()
Module#class_eval (também conhecido por seu nome alternativo, module_eval) avalia um bloco
no contexto de uma classe existente:
class_definitions/
class_eval.rb def
add_method_to(a_class)
a_class.class_eval do def
m; 'Olá!'; fim fim
fim
add_method_to String
"abc".m # => "Olá!"
(Essa não é toda a verdade: instance_eval também altera a classe atual, mas você terá que
esperar por Singleton Classes e instance_eval(), na página 127, para saber como exatamente.
Por enquanto, você pode ignorar o problema com segurança e assumir que instance_eval só
muda self.)
Ao alterar a classe atual, class_eval efetivamente reabre a classe, assim como a palavra-chave
class faz.
Module#class_eval é realmente mais flexível que class. Você pode usar class_eval em qualquer
variável que faça referência à classe, enquanto a classe requer uma constante. Além disso, class
abre um novo escopo, perdendo de vista as ligações atuais, enquanto class_eval tem um Flat
Scope (83). Como você aprendeu em Scope Gates, na página 81, isso significa que você pode
fazer referência a variáveis do escopo externo em um bloco class_eval .
Finalmente, assim como instance_eval tem um método gêmeo chamado instance_exec, mod
ule_eval/class_eval também tem um método module_exec/class_exec equivalente que pode
passar parâmetros extras para o bloco.
Agora que você conhece instance_eval e class_eval, pode se perguntar qual dos
dois deve usar. Na maioria dos casos, a resposta é fácil: você usa instance_eval
para abrir um objeto que não é uma classe e class_eval para abrir uma definição
de classe e definir métodos com def. Mas e se você quiser abrir um objeto que é
uma classe (ou módulo) para fazer algo diferente de usar def?
Você deve usar instance_eval ou class_eval então?
Se tudo o que você deseja é alterar self, então instance_eval e class_eval farão o trabalho bem.
No entanto, você deve escolher o método que melhor comunique suas intenções. Se você está
pensando: “Quero abrir este objeto e não me importo particularmente que seja uma classe”, então
instance_eval é adequado. Se você está pensando, “Eu quero uma Classe Aberta (14) aqui,”
então class_eval é quase certamente uma combinação melhor.
Isso foi um monte de informações sobre a classe atual e como lidar com
• O interpretador Ruby sempre mantém uma referência à classe atual (ou módulo). Todos os
métodos definidos com def tornam-se métodos de instância da classe atual.
Como essas coisas podem ser úteis na vida real? Para mostrar como você pode aplicar essa teoria sobre
a classe atual, vamos ver um truque chamado Variáveis de instância de classe.
O interpretador Ruby assume que todas as variáveis de instância pertencem ao objeto atual self. Isso
também é verdade em uma definição de classe:
class_definitions/class_instance_variables.rb
classe MinhaClasse
@minha_var =
1 fim
Em uma definição de classe, o papel de self pertence à própria classe, portanto, a variável de instância
@my_var pertence à classe. Não se confunda. As variáveis de instância da classe são diferentes das
variáveis de instância dos objetos dessa classe, como você pode ver no exemplo a seguir:
class MyClass
@my_var = 1
def self.read; @my_var; final def
gravação; @minha_var = 2; fim def
ler; @my_var; fim fim
obj = MyClass.new
obj.read # => nada
obj.write
obj.read # => 2
MyClass.read # => 1
O código anterior define duas variáveis de instância. Ambos são denominados @my_var, mas são
definidos em escopos diferentes e pertencem a objetos diferentes. Para ver como isso funciona, você
deve se lembrar de que as classes são apenas objetos e você deve rastrear a si mesmo por meio do
programa. Um @my_var é definido com obj como self, então é uma variável de instância do objeto obj . O
outro @my_var é definido com MyClass como self, então é uma variável de instância do objeto MyClass
— uma variável de instância de classe.
Feitiço: Instância de Classe
Variável
Se você vem de Java, pode ficar tentado a pensar que as variáveis de instância de classe são semelhantes
aos “campos estáticos” de Java. Em vez disso, eles são apenas regulares
variáveis de instância que por acaso pertencem a um objeto da classe Class. Por causa
disso, uma variável de instância de classe pode ser acessada apenas pela própria classe
— não por uma instância ou por uma subclasse.
Tocamos em muitas coisas: a classe atual, definições de classe, self, class_eval, variáveis
de instância de classe... Agora você pode voltar ao Bookworm e juntar esses recursos.
Variáveis de classe
Se quiser armazenar uma variável em uma classe, você tem mais opções do que apenas usar uma
variável de instância de classe (109). Você também pode usar uma variável de classe, identificada por
um prefixo @@ :
classe C
@@v = 1
fim
As variáveis de classe são diferentes das variáveis de instância de classe porque podem ser acessadas
por subclasses e por métodos de instância regulares. (A esse respeito, eles são mais semelhantes aos
campos estáticos de Java.)
D.new.my_method # => 1
Infelizmente, as variáveis de classe têm o péssimo hábito de surpreendê-lo. Aqui está um exemplo:
@@v = 1
classe MinhaClasse
@@v = 2
fim
@@v # => 2
Você obtém esse resultado porque as variáveis de classe realmente não pertencem a classes — elas
pertencem a hierarquias de classe. Como @@v é definido no contexto de main, ele pertence à classe
Object de main ... e a todos os descendentes de Object. MyClass herda de Object, então acaba
compartilhando a mesma variável de classe. Por mais tecnicamente sólido que seja esse comportamento,
é provável que ele o faça tropeçar.
Por causa de surpresas indesejáveis como a mostrada anteriormente, a maioria dos rubistas hoje em dia
evita variáveis de classe em favor de variáveis de instância de classe. Além disso, o Ruby 2.x emite um
aviso severo sempre que você acessa uma variável de classe do nível superior.
A fonte do Bookworm contém poucos testes de unidade, então cabe a você e ao Bill escrever os
testes conforme você refatora. Às vezes isso se mostra difícil, como é o caso desta classe:
class_definitions/bookworm_classvars.rb
empréstimo de classe
def initialize(book) @book
= book @time =
Time.now end
def to_s
"#{@book.upcase} emprestado em #{@time}" end
fim
Empréstimo armazena o título de um livro e a hora em que foi emprestado, ou seja, a hora em que o
objeto foi criado. Você gostaria de escrever um teste de unidade para o método to_s , mas para
escrever esse teste, você teria que saber a hora exata em que o objeto foi criado. Este é um problema
comum com código que depende de Hora ou Data: esse código retorna um resultado diferente toda
vez que é executado, então você não sabe qual resultado testar.
“Acho que tenho uma solução para esse problema”, anuncia Bill. “É um pouco complicado, então vai
exigir um pouco de atenção de sua parte. Aqui está."
class Loan
def initialize(book) @book
= book ÿ @time
= Loan.time_class.now
fim
ÿ def self.time_class ÿ
@time_class || Tempo ÿ fim
def to_s
# ...
Loan.time_class retorna uma classe e Loan#initialize usa essa classe para obter a hora atual. A
classe é armazenada em uma variável de instância de classe (109) denominada @time_class.
Se @time_class for nil, o Nil Guard (219) em time_class retorna a classe Time como padrão.
Em produção, Loan sempre usa a classe Time , porque @time_class é sempre nulo. Por outro lado,
os testes de unidade podem contar com uma classe de tempo falsa que sempre retorna
o mesmo valor. Os testes podem atribuir um valor à variável privada @time_class usando
class_eval ou instance_eval. Qualquer um dos dois métodos servirá aqui, porque ambos
mudam self:
classe FakeTime
def auto.agora; 'Seg 06 de abril 12:15:50'; fim fim
requer 'teste/unidade'
fim
Bill tem muito orgulho de sua proeza de codificação. Ele diz: "Acho que merecemos uma
pausa - depois que eu fizer um teste".
Onde você escreve um programa inteiro sem nunca usar uma determinada palavra-chave
popular.
Você já jogou Taboo?1 As regras são simples: você recebe uma palavra secreta e uma
lista de palavras que não pode usar. (Eles são “tabu”.) Você deve ajudar um colega de
equipe a adivinhar a palavra secreta. Você pode dar ao seu colega quantas sugestões
quiser, mas nunca deve dizer uma palavra tabu. Se você fizer isso, você perde
imediatamente.
Seu desafio: jogar Taboo com código Ruby. Você tem apenas uma palavra tabu, a palavra-
chave class . Sua “palavra secreta” é na verdade uma classe Ruby:
Você tem que escrever um pedaço de código que tenha exatamente o mesmo efeito que
o anterior, sem nunca usar a palavra-chave class . Você está pronto para o desafio?
(Apenas uma dica: consulte a documentação de Class.new.)
1. http://en.wikipedia.org/wiki/Taboo_(jogo).
Solução do questionário
Como uma classe é apenas uma instância de Class, você pode criá-la chamando Class.new.
Class.new também aceita um argumento (a superclasse da nova classe) e um bloco que é avaliado
no contexto da classe recém-nascida:
c = Class.new(Array) do def
my_method 'Olá!'
fim
fim
Agora você tem uma variável que faz referência a uma classe, mas a classe ainda é anônima .
Você se lembra da discussão sobre nomes de classe em Constantes, na página 21? O nome de
uma classe é apenas uma constante, então você mesmo pode atribuí-la:
MinhaClasse = c
Curiosamente, Ruby está trapaceando um pouco aqui. Quando você atribui uma classe anônima a
uma constante, Ruby entende que você está tentando dar um nome à classe e faz algo especial:
vira-se para a classe e diz: “Aqui está seu novo nome”. Agora, a constante faz referência à classe e
a classe também faz referência à constante. Se não fosse por esse truque, uma classe não seria
capaz de saber seu próprio nome e você não poderia escrever isso:
Você se vira para Bill para mostrar a ele sua solução para o teste - mas ele já está ocupado
navegando na fonte do Bookworm. É hora de voltar à tarefa em questão.
Métodos Singleton
Onde é a sua vez de ensinar alguns truques ao Bill.
É final da manhã, e você e Bill estão no meio do fluxo. Você está percorrendo o código-fonte do
Bookworm, excluindo uma linha inútil aqui, alterando um nome confuso ali e, em geral, polindo o
código... até que você se depara com uma parte particularmente problemática da refatoração.
A classe Paragraph encapsula uma string e então delega todas as chamadas para a string
encapsulada — todas elas, exceto por um método, Paragraph#title?, que retorna true se um
Paragraph estiver todo em letras maiúsculas.
class_definitions/paragraph.rb
classe Parágrafo def
inicializar(texto)
@texto = fim do
texto
Bill franze a testa. “A estúpida classe Parágrafo realmente não sustenta seu próprio peso.
Poderíamos descartá-lo inteiramente e apenas usar Strings normais, se não fosse pelo
título? método."
“Por que não fazemos Monkeypatch (16) a classe String e adicionamos o título? método ali
mesmo?” Sua oferta. “Não estou convencido”, diz Bill. “Um método com esse nome faria
sentido apenas em strings que representam um parágrafo, não em todas as strings.”
Enquanto Bill pondera sobre a ideia de corrigir a classe String com um Refinamento (36),
você decide procurar uma solução no Google.
class_definitions/singleton_methods.rb
str = "apenas uma string regular"
def str.title?
self.upcase == self end
O código anterior adiciona um método chamado title? para estr. Nenhum outro objeto obtém
o método — nem mesmo outras Strings. Um método como este, que é específico para um
Feitiço: Singleton único objeto, é chamado de Método Singleton. Você pode definir um Método Singleton com
Método
a sintaxe acima ou com o método Object#define_singleton_method .
Graças aos Métodos Singleton, agora você pode corrigir seu problema com a fonte Book
worm. Você pode enviar qualquer String antiga para indexar se você aprimorar essa String
com um título? Método Singleton:
class_definitions/
paragraph.rb parágrafo = "qualquer string pode ser um parágrafo"
def parágrafo.título?
self.upcase == self end
índice(parágrafo)
Agora você pode usar strings simples no Bookworm e excluir a classe Paragraph .
Bill está impressionado com sua solução. “Eu conhecia os Métodos Singleton, mas nunca
percebi que você poderia usá-los dessa maneira.”
“Espere um minuto”, você responde. “Você sabia sobre eles? Para que você achou que
eles eram úteis?
“Métodos Singleton não são úteis apenas para aprimorar um objeto específico, como você
acabou de fazer.” Bill responde. “Eles também são a base para um dos recursos mais
comuns do Ruby. E se eu te disser que você usa Métodos Singleton o tempo todo, sem
nunca saber disso?”
Duck Typing
Algumas pessoas ficam horrorizadas com os Métodos Singleton (114), pensando que se cada
objeto puder ter seus próprios métodos, não importa a qual classe ele pertença, seu código se
tornará um emaranhado de espaguete retorcido.
Se você mesmo reagiu dessa forma, provavelmente está acostumado a linguagens estáticas. Em
uma linguagem estática como Java, você diz que um objeto tem o tipo T porque pertence à
classe T (ou porque implementa a interface T). Em uma linguagem dinâmica como Ruby, o “tipo”
de um objeto não está estritamente relacionado à sua classe. Em vez disso, o “tipo” é
simplesmente o conjunto de métodos aos quais um objeto pode responder.
As pessoas se referem a essa segunda noção mais fluida de um tipo como tipagem de pato,
referindo-se ao ditado: “se anda como um pato e grasna como um pato, então deve ser um pato”.
Em outras palavras, você não se importa se um objeto é uma instância da classe Duck. Você só
se importa que ele responda a andar e grasnar, sejam eles métodos regulares, Métodos Singleton
(114) ou até mesmo Métodos Fantasmas (57).
Se você ficar um tempo com Ruby, vai se acostumar com a digitação de pato - e depois de
aprender alguns truques dinâmicos legais, você pode até se perguntar como poderia ter vivido
sem isso em primeiro lugar.
Lembra do que você aprendeu em Inside the Object Model, na página 16? As classes são
apenas objetos e os nomes das classes são apenas constantes. Se você se lembra disso
conceito, você verá que chamar um método em uma classe é o mesmo que chamar um método
em um objeto:
an_object.a_method
AClass.a_class_method
Ver? A primeira linha chama um método em um objeto referenciado por uma variável, e a segunda
linha chama um método em um objeto (que também é uma classe) referenciado por uma
constante. É a mesma sintaxe.
Mas, espere - há mais. Lembra-se de como Bill lhe disse que você sempre usou Métodos
Singleton (114) ? Isso é realmente o que os métodos de classe são: eles são Métodos Singleton
de uma classe. Na verdade, se você comparar a definição de um método de tonelada única e a
definição de um método de classe, verá que eles são os
mesmo:
Assim, a sintaxe para definir um Método Singleton com def é sempre a mesma:
objeto def.método
# Corpo do método aqui
termina
Na definição mostrada anteriormente, object pode ser uma referência de objeto, um nome de
classe constante ou self. A sintaxe pode parecer diferente nos três casos, mas, na verdade, o
mecanismo subjacente é sempre o mesmo. Belo projeto, não acham?
Você ainda não terminou os métodos de classe. Há um feitiço muito útil e comum que depende
exclusivamente de métodos de classe e merece sua própria discussão.
Macros de classe
class_definitions/attr.rb
classe MinhaClasse
def meu_atributo=(valor)
@meu_atributo = valor final
def my_attribute
@my_attribute
end
fim
obj = MyClass.new
obj.my_attribute = 'x'
obj.my_attribute # => "x"
class MyClass
attr_accessor: my_attribute end
Todos os métodos attr_* são definidos na classe Module, então você pode usá-los sempre que
self for um módulo ou uma classe. Um método como attr_accessor é chamado de Macro de
classe . Macros de classe parecem palavras-chave, mas são apenas métodos de classe regulares Spell: Macro
de classe que devem ser usados em uma definição de classe.
“Agora que você conhece as macros de classe”, diz Bill, “acho que conheço um lugar no código-
fonte do Bookworm onde podemos fazer bom uso delas”.
Macros de classe
aplicadas A classe Book no código-fonte Bookworm tem métodos denominados GetTitle,
title2 e LEND_TO_USER. Pelas convenções do Ruby, esses métodos devem ser
nomeados como title, caption e lend_to, respectivamente. No entanto, existem outros
projetos que usam a classe Book e você não tem controle sobre esses projetos. Se você
apenas renomear os métodos, interromperá os chamadores.
Bill tem uma ideia para corrigir essa situação: você pode renomear os métodos se inventar uma
Macro de classe (117) que deprecie os nomes antigos:
class_definitions/deprecated.rb
livro de classe
título de definição # ...
O método obsoleto pega o nome antigo e o novo nome de um método e define um Método
Dinâmico (51) que captura chamadas para o nome antigo. O método dinâmico encaminha as
chamadas para o método renomeado - mas primeiro ele imprime um aviso no console para
notificar os chamadores de que o nome antigo foi obsoleto:
b = Livro.novo
b.LEND_TO_USER("Conta")
Essa foi uma maneira engenhosa de usar uma macro de classe. No entanto, se você realmente
deseja entender as Macros de Classe, bem como os Métodos Singleton em geral, você deve
encaixar uma última peça que falta no modelo de objeto Ruby.
Aulas individuais
Onde você coloca a peça final no quebra-cabeça do modelo de objeto.
Classes Singleton são os OVNIs do mundo Ruby: mesmo que você nunca veja um
pessoalmente, você pode encontrar dicas espalhadas de sua existência por todo o lugar.
Vamos começar nossa investigação sobre esse assunto difícil coletando algumas evidências.
(Esteja ciente de que as próximas páginas contêm material avançado que pode demorar um
pouco para você digerir. Se quiser, você pode pular direto para Method Wrappers, na página
131, em sua primeira leitura e voltar a esta seção mais tarde. )
Method Lookup, na página 28, você aprendeu como Ruby encontra métodos indo direto para
a classe do receptor e então subindo na hierarquia de classes. Por exemplo:
classe
MinhaClasse def
meu_método; fim fim
obj = MyClass.new
obj.my_method
Bill desenha o seguinte fluxograma e diz: “Quando você chama my_method, Ruby vai direto para
MyClass e encontra o método lá”. Até agora tudo bem.
Se você observar o fluxograma anterior, notará que não há um local óbvio para
my_singleton_method ali.
O Método Singleton não pode viver em obj, porque obj não é uma classe. Ele não pode residir
em MyClass porque, se o fizesse, todas as instâncias de MyClass o compartilhariam. E não pode
ser um método de instância da superclasse de MyClass , Object. Então, onde vivem os Métodos
Singleton?
Se você observar a figura a seguir, descobrirá que, novamente, my_class_method não parece
residir em nenhum lugar do diagrama de Bill.
Quando você pergunta a classe de um objeto, Ruby nem sempre diz toda a verdade. Em vez da
classe que você vê, um objeto pode ter sua própria classe oculta especial. Isso é chamado de classe
singleton do objeto. (Você também pode ouvi-la ser chamada de metaclasse ou autoclasse. No
entanto, “classe singleton” é o nome oficial.)
Métodos como Object#class mantêm a classe singleton cuidadosamente escondida, mas você pode
contorná-los. Ruby tem uma sintaxe especial, baseada na palavra-chave class , que coloca você no
escopo da classe singleton:
Se você deseja obter uma referência à classe singleton, pode retornar self fora do escopo:
obj = Objeto.novo
Aquela classe sorrateira de singleton estava tentando se esconder, mas conseguimos encontrá-la.
Nos velhos tempos de Ruby, você tinha que retornar self como acabamos de fazer para obter uma
referência à classe singleton. Hoje em dia, você também pode obter uma referência à classe singleton
com o prático método Object#singleton_class :
O exemplo anterior também mostra que uma classe singleton é uma classe — mas muito especial.
Para começar, é invisível até que você recorra a Object#single ton_class ou à classe exótica <<
sintaxe. Além disso, as classes singleton têm apenas uma única instância (é daí que vem o nome) e
não podem ser herdadas.
Mais importante, uma classe singleton é onde vivem os Métodos Singleton de um objeto:
Para entender completamente as consequências deste último ponto, você deve examinar mais
profundamente o modelo de objeto do Ruby.
Em O que acontece quando você chama um método?, na página 27, você aprendeu sobre o modelo de objeto
Ruby e a pesquisa de método. Naquela época, tínhamos que deixar algumas partes do modelo de objeto
inexploradas. As classes Singleton são o elo que faltava de que precisávamos. Depois de entender as classes
singleton, todas as partes do modelo de objeto finalmente se encaixam.
Revisão de pesquisa de
método Para examinar o modelo de objeto, você precisa de um exemplo prático para se concentrar. Vamos
escrever um programa de “rato de laboratório”:
classe C
def a_method
'C#a_method()'
fim
fim
obj = D.new
obj.a_method # => "C#a_method()"
Se você desenhar um obj e sua cadeia de ancestrais, provavelmente se parecerá com a figura a seguir. (Por
enquanto, você não precisa se preocupar com classes ou módulos singleton.)
Você sabe que a pesquisa de método vai um passo para a direita e depois para cima. Quando você chama
obj.a_method(), Ruby vai direto para a classe D de obj . A partir daí, ele sobe na cadeia de ancestrais até
encontrar a_method na classe C. Agora, vamos adicionar classes singleton à mistura.
Agora, para um experimento. Você sabe que uma classe singleton é uma classe,
então ela deve ter uma superclasse. Qual é a superclasse da classe singleton?
obj.singleton_class.superclass # => D
veremos as conexões entre classes, classes singleton e superclasses. Esta área do modelo de objeto
pode ser um verdadeiro quebra-cabeças. Uma vez que clique em sua mente, no entanto, parecerá elegante
e bonito. Se você estiver preso, apenas olhe as fotos ou abra o irb e experimente
por si só.
classe C
classe << eu
def a_class_method
'C.a_class_method()' fim
fim
fim
Agora você pode explorar o modelo de objeto resultante. (Ao fazer isso, lembre-se de que as classes
singleton se tornaram um pouco mais visíveis no Ruby 2.1. A partir dessa versão, se você perguntar a uma
classe singleton seus ancestrais, o resultado será
incluem ancestrais que são classes singleton. Até o Ruby 2.0, os ancestrais sempre
mostravam apenas classes regulares.)
C.singleton_class # => #<Classe:C>
D.singleton_class # => #<Classe:D>
D.singleton_class.superclass # => #<Class:C>
C.singleton_class.superclass # => #<Class:Object>
método retornaria, porque o método de classe não conhece classes singleton. Por exemplo, obj.class
retornaria D, mesmo que a classe de obj seja na verdade sua classe singleton, #obj.
Este diagrama não inclui módulos. Se você for um completista, pode desenhar o módulo Kernel
entre Object e BasicObject. Por outro lado, você provavelmente não deseja incluir #Kernel neste
diagrama. Embora os módulos possam ter classes singleton como qualquer outro objeto, a classe
singleton do Kernel não faz parte das cadeias ancestrais de obj ou #D.
Esse arranjo complicado de classes, superclasses e classes singleton pode ser desconcertante. Por
que Ruby se esforça tanto para organizar o modelo de objeto dessa maneira? A razão é que, graças
a esse arranjo, você pode chamar um método de classe em uma subclasse:
Mesmo que a_class_method seja definido em C, você também pode chamá-lo em D. Isso
provavelmente é o que você espera, mas só é possível porque a pesquisa de método começa em
#D e vai até a superclasse de #D #C, onde encontra o método.
Engenhoso, não é? Agora você pode finalmente compreender todo o modelo de objeto.
“O modelo de objeto Ruby é um lugar lindo”, observa Bill, com uma expressão sonhadora no rosto.
“Existem classes, classes singleton e módulos. Existem métodos de instância, métodos de classe e
métodos Singleton.”
À primeira vista, tudo parece muito complexo. Olhe mais de perto e a complexidade desaparece. Se
você colocar classes singleton junto com classes e módulos regulares, acabará com as sete regras
do modelo de objeto Ruby:
2. Existe apenas um tipo de módulo - seja um módulo regular, uma classe ou uma classe singleton.
3. Existe apenas um tipo de método e ele reside em um módulo - na maioria das vezes em
uma aula.
Meta ao quadrado
Classes singleton são classes, e classes são objetos, e objetos têm classes singleton…. Você
pode ver onde esta linha de pensamento está indo? Como qualquer outro objeto, uma classe
singleton deve ter sua própria classe singleton:
classe << "abc"
classe << eu
self # => #<Class:#<Class:#<String:0x33552c>>> end end
Se você encontrar um uso prático para classes singleton de classes singleton, deixe o mundo
saber.
4. Todo objeto, classes incluídas, tem sua própria “classe real”, seja uma classe regular
classe ou uma classe singleton.
5. Toda classe, com exceção de BasicObject, tem exatamente um ancestral — uma superclasse
ou um módulo. Isso significa que você tem uma única cadeia de ancestrais de qualquer
classe até BasicObject.
7. Quando você chama um método, o Ruby vai “à direita” na classe real do receptor e então
“para cima” na cadeia de ancestrais. Isso é tudo o que há para saber sobre a maneira
como Ruby encontra métodos.
Qualquer programador Ruby pode se deparar com uma questão difícil sobre o modelo de
objeto. “Qual método nesta hierarquia complicada é chamado primeiro?” Ou talvez, “Posso
chamar este método daquele objeto?” Quando isso acontecer com você, revise as sete regras
listadas anteriormente, talvez desenhe um diagrama rápido do modelo de objeto e você
encontrará a resposta rapidamente.
Sintaxes de métodos de
classe Como os métodos de classe são apenas métodos singleton que vivem na classe
singleton da classe, agora você tem três maneiras diferentes de definir um método de classe.
Eles são mostrados no código a seguir.
classe MinhaClasse
def self.another_class_method; fim fim
classe MinhaClasse
classe << eu
def yet_another_class_method; fim fim
fim
s1.instance_eval do def
swoosh!; reverter; fim fim
Atributos de classe
As explicações detalhadas de Bill deixaram você um pouco perplexo. “Ok”, você diz,
“eu posso ver como as classes singleton são úteis para entender o modelo de objeto.
Mas como eu os utilizo na prática?”
class_definitions/
class_attr.rb
class MyClass
attr_accessor:a end
obj = MyClass.new
obj.a = 2
obj.a # => 2
Mas e se você quiser definir um atributo em uma classe ? Você pode ficar tentado a
reabrir a classe e definir o atributo lá:
class Classe
attr_accessor :b end
MinhaClasse.b = 42
MinhaClasse.b # => 42
Isso funciona, mas adiciona o atributo a todas as classes. Se você deseja um atributo
específico para MyClass, precisa de uma técnica diferente. Defina o atributo na classe
singleton:
classe MinhaClasse
classe << eu
attr_accessor :c fim
fim
MinhaClasse.c = 'Funciona!'
MinhaClasse.c # => "Funciona!"
Para entender como isso funciona, lembre-se de que um atributo é, na verdade, um par
de métodos. Se você definir esses métodos na classe singleton, eles se tornarão métodos
de classe, como se você tivesse escrito isto:
def MinhaClasse.c=(valor)
@c = valor
final
def MinhaClasse.c
@c end
Como de costume, Bill pega o pedaço de papel disponível mais próximo e rabisca o seguinte
diagrama nele. “É assim que você define um atributo em uma classe”, ele
diz.
Você também pode ver outro detalhe interessante neste diagrama. A superclasse de #BasicObject
nada mais é do que a boa e velha Class. Esse fato explica por que você pode chamar MyClass.b e
MyClass.b=.
Claramente feliz com sua própria explicação, Bill se recosta em sua cadeira confortável.
“Coisa legal, né? Agora, vamos tentar um pequeno teste.”
Onde você aprende que classes e módulos singleton se misturam bem uns com os outros.
Bill decide que é hora de contar uma história: “Todos os dias, em algum lugar do mundo, um
programador Ruby tenta definir um método de classe incluindo um módulo. Eu mesmo tentei, mas
não funcionou.”
class_definitions/module_trouble_failure.rb
módulo MeuMódulo
def self.my_method; 'olá'; fim fim
class MyClass
include MyModule
end
MyClass.my_method # NoMethodError!
“Veja,” continua Bill, “quando uma classe inclui um módulo, ela obtém os métodos de instância do módulo –
não os métodos de classe. Os métodos de classe ficam fora de alcance, na classe singleton do módulo.”
“Então, como você encontrou uma solução?” você pergunta. “Oh, eu não fiz,” Bill responde, corando.
“Acabei de pedir a solução em uma lista de discussão, como todo mundo faz. Mas talvez você possa encontrar
uma solução. Pense no modelo de objeto e nas classes singleton. Como você modificaria o código que acabou
de ver para que funcione conforme o esperado?
Solução do questionário
A solução para este teste é simples e sutil ao mesmo tempo. Primeiro, defina my_method como um método de
instância regular de MyModule. Em seguida, inclua o módulo na classe singleton de MyClass.
class_definitions/module_trouble_solution.rb
módulo MeuMódulo
ÿ def meu_método; 'olá'; fim
fim
classe
MinhaClasse ÿ classe << self
ÿ incluir MyModule ÿ fim
fim
my_method é um método de instância da classe singleton de MyClass. Como tal, my_method também é um
método de classe de MyClass. Essa técnica é chamada de Extensão de Classe .
Feitiço: Classe
Extensão
"Isso é brilhante", diz Bill. “Que tal tentar o mesmo truque em um objeto regular em vez de uma classe?”
class_definitions/
module_trouble_object.rb módulo
MyModule def my_method; 'olá'; fim fim
obj = Objeto.novo
Caso você pense que abrir a classe singleton é uma maneira desajeitada de estender uma classe
ou um objeto, vejamos também uma técnica alternativa.
Object#extend
Extensões de classe (130) e Extensões de objeto (130) são comuns o suficiente para
Ruby fornece um método apenas para eles, chamado Object#extend:
class_definitions/
module_trouble_extend.rb módulo
MyModule def my_method; 'olá'; fim fim
obj = Object.new
obj.extend MyModule
obj.my_method # => "olá"
class MyClass
extend MyModule
end
“Chega de falar sobre aulas de singleton hoje,” Bill anuncia. “Eu não quero ter uma meta-dor de
cabeça. Por enquanto, vamos voltar à refatoração do Bookworm.”
Wrappers de método
Onde você aprende como agrupar um método dentro de outro método - três diferentes
caminhos.
À medida que o dia chega ao fim, você e Bill se encontram presos. Muitos métodos no Bookworm
dependem de uma biblioteca de código aberto que recupera as resenhas de um livro do site da
Amazon. O código a seguir mostra um exemplo:
def merece_a_look?(livro)
amazon = Amazon.new
amazon.reviews_of(livro).tamanho > 20
fim
Esse código funciona na maioria dos casos, mas não gerencia exceções. Se uma chamada remota
para a Amazon falhar, o próprio Bookworm deve registrar esse problema e prosseguir.
Você poderia facilmente adicionar o gerenciamento de exceções a cada linha no Bookworm que
chama merece_a_look? — mas existem dezenas dessas linhas e você não deseja alterar todas
elas.
Resumindo o problema: você tem um método que não deseja modificar diretamente porque está
em uma biblioteca. Você deseja agrupar funcionalidade adicional em torno desse método para que
todos os clientes obtenham a funcionalidade adicional automaticamente. Você pode fazer isso de
algumas maneiras, mas para chegar à primeira delas, você precisa conhecer os aliases.
Sobre pseudônimos
Você pode dar um nome alternativo para um método Ruby usando Module#alias_method:
class_definitions/
alias.rb classe
MinhaClasse def meu_método;
'meu_metodo()'; end
alias_method :m, :my_method end
obj = MyClass.new
obj.my_method # => "my_method()" obj.m
# => "my_method()"
Em alias_method, o novo nome do método vem primeiro e o nome original vem depois. Você pode
fornecer os nomes como símbolos ou strings.
(Ruby também possui uma palavra-chave alias , que é uma alternativa para Module#alias_method.
Pode ser útil se você quiser criar um alias para um método no nível superior, onde Mod
ule#alias_method não está disponível.)
class MyClass
alias_method :m2, :m end
Aliases são comuns em qualquer lugar em Ruby, incluindo as bibliotecas principais. Por exemplo,
String#size é um alias de String#length, e a classe Integer tem um método com nada menos que
cinco nomes diferentes. (Você consegue identificar?)
O que acontece se você criar um alias para um método e depois o redefinir? Você pode tentar isso com um
programa simples:
class_definitions/
def
comprimento real_length > 5 ? 'longo' : final
'curto'
fim
O código anterior redefine String#length, mas o alias ainda se refere ao método original. Isso fornece uma
visão de como a redefinição de método funciona. Quando você redefine um método, você realmente não muda
o método. Em vez disso, você define um novo método e anexa um nome existente a esse novo método. Você
ainda pode chamar a versão antiga do método, desde que tenha outro nome que ainda esteja anexado a ele.
Essa ideia de criar um alias para um método e depois redefini-lo é a base de um truque interessante — que
merece seu próprio exemplo.
O exemplo do Thor
Thor é uma jóia Ruby para construir utilitários de linha de comando. Algumas versões do Thor incluem um
programa chamado rake2thor que converte arquivos de compilação do Rake em scripts do Thor. Como parte
disso, o rake2thor deve carregar um Rakefile e armazenar os nomes de todos os arquivos que, por sua vez,
são necessários para esse Rakefile. Aqui está o código onde a mágica acontece:
gems/thor-0.17.0/bin/
rake2thor entrada = ARGV[0] ||
'Rakefile' $requer = []
módulo Kernel
def require_with_record(file) $requires
<< file if caller[1] =~ /rake2thor:/ require_without_record file
end
carregar entrada
O código acima prepara um array global para armazenar os nomes dos arquivos necessários; em seguida,
ele abre o módulo Kernel e executa alguns truques com aliases de método; e, finalmente, carrega o Rakefile.
Concentre-se na parte do meio - o código que lida com o Kernel. Para entender o que está acontecendo,
veja esta versão ligeiramente simplificada do código original:
kernel do módulo
alias_method :require_without_record, :require
def require(file)
$requires << file if caller[1] =~ /rake2thor:/
require_without_record file end
fim
A Classe Aberta (14) acima faz três coisas. Primeiro, ele cria um alias para o método padrão Kernel#require
com outro nome (require_without_record). Em segundo lugar, os patches do Monkey (16) exigem o
armazenamento dos nomes dos arquivos exigidos pelo Rakefile.
(Ele faz isso obtendo a pilha de chamadores com o método Kernel#callers . Se o segundo chamador na
pilha for o próprio rake2thor , isso significa que o Rakefile deve ser o primeiro chamador na pilha - aquele
que realmente chamou require.) Por fim, o novo require volta para o require original, agora chamado
require_without_record.
Comparado a esta versão simplificada, o código rake2thor original vai um passo além: ele também cria um
alias para o novo require chamado require_with_record. Embora esse último alias torne os métodos mais
explícitos, o resultado importante é praticamente o mesmo em ambas as versões do código: Kernel#require
foi alterado e o novo require é “envolto” pelo antigo require. É por isso que este truque
Uma desvantagem de Around Aliases é que eles poluem suas classes com um nome de método adicional.
Você pode corrigir esse pequeno problema de alguma forma, tornando a versão antiga do método privada
depois de criar um alias. (Em Ruby, é o nome do método, não o método em si, que é público ou privado.)
Outro problema potencial de Around Aliases tem a ver com o carregamento. Você nunca deve carregar um
Around Alias duas vezes, a menos que queira acabar com uma exceção ao chamar o método. Você pode
ver por quê?
O principal problema com Around Aliases, no entanto, é que eles são uma forma de
Monkeypatching. Como todos os Monkeypatches, eles podem quebrar o código existente
que não esperava que o método mudasse. Por esse motivo, o Ruby 2.0 introduziu não
uma, mas duas maneiras adicionais de agrupar funcionalidades adicionais em torno de
um método existente.
fim
fim
usando StringRefinement
O código acima refina a classe String para agrupar funcionalidades adicionais em torno
de seu método length . Como outros refinamentos, este wrapper de refinamento se Feitiço: Refinamento
Embrulho
aplica apenas até o final do arquivo (ou, no Ruby 2.1, a definição do módulo). Isso
geralmente o torna mais seguro do que o equivalente Around Alias, que se aplica a
todos os lugares.
Finalmente, você tem uma terceira forma de agrupar um método: você pode usar
Module#prepend, que você deve se lembrar de Módulos e Lookup, na página 30. Mod
ule#prepend funciona um pouco como include, mas insere o módulo abaixo do includer
na cadeia de ancestrais, e não acima dela. Isso significa que um método em um módulo
anexado pode substituir um método no includer e chamar a versão não substituída com
super:
class_definitions/wrapper_prepend.rb
módulo ExplicitString def
comprimento
super > 5? 'longo' : final 'curto'
fim
String.class_eval do
preceder ExplicitString end
Feitiço: Anteposto Você pode chamar isso de wrapper prefixado. Não é local como um Refinement Wrapper, mas
Embrulho
geralmente é considerado mais limpo e explícito do que um Refinement Wrapper e um Around Alias.
Agora você sabe mais do que o suficiente para voltar ao código-fonte do Bookworm.
Você se lembra de onde surgiu essa discussão sobre wrappers de método? Você e Bill queriam agrupar
o log e o tratamento de exceções em torno do método Amazon#reviews_of . Agora você pode finalmente
fazer isso com um Around Alias (134), um Refinement Wrapper (135) ou um Prepended Wrapper (136).
A terceira opção parece mais limpa, pois não envolve Monkeypatching ou regras estranhas de
refinamento:
class_definitions/bookworm_wrapper.rb
módulo AmazonWrapper
def reviews_of(book) start
= Time.now
resultado = super
time_taken = Time.now - start coloca
"reviews_of() levou mais de #{time_taken} segundos" if time_taken > 2 resultado
resgatar
fim
Amazon.class_eval do
preceder o final do
AmazonWrapper
Enquanto você admira esse pedaço de código inteligente, Bill o atinge com um questionário inesperado.
A maioria dos operadores Ruby são, na verdade, métodos. Por exemplo, o operador + em números
inteiros é um açúcar sintático para um método chamado Fixnum#+. Quando você escreve 1 + 1, o
analisador o converte internamente em 1.+(1).
Encerramento • 137
O legal dos métodos é que você pode redefini-los. Então, aqui está o seu desafio: quebrar as
regras da matemática redefinindo Fixnum#+ para que ele sempre retorne o resultado correto mais
um. Por exemplo:
1+1 # => 3
Solução do questionário
Você pode resolver este questionário com uma Aula Aberta (14). Apenas reabra Fixnum e redefina
+ para que (x + y) se torne (x + y + 1). Isso não é tão fácil quanto parece, no entanto. A nova
versão de + depende da versão antiga de +, então você precisa agrupar sua versão antiga com a
nova versão. Você pode fazer isso com um Around Alias (134):
class_definitions/broken_math.rb
classe Fixnum
alias_method :old_plus, :+
def +(valor)
self.old_plus(value).old_plus(1) end end
Agora você tem o poder de causar estragos na aritmética básica do Ruby. Aproveite este código
com responsabilidade.
Embrulhar
Você cobriu muito terreno hoje. Vamos resumir tudo:
• Você examinou os efeitos das definições de classe em self (o receptor padrão dos métodos
que você chama) e na classe atual (o lar padrão dos métodos que você define).
• Você se familiarizou com Métodos Singleton (114) e classes singleton, obtendo novos insights
sobre o modelo de objeto e pesquisa de método.
• Você adicionou alguns novos truques à sua mochila, incluindo variáveis de instância de classe
(109), Macros de classe (117) e Wrappers pré-anexados (136).
Lembre-se também que hoje você usou a palavra “classe” como um atalho para “classe ou
módulo”, e tudo o que você aprendeu sobre classes também pode ser aplicado a módulos: a
“classe atual” pode na verdade ser um módulo, uma “variável de instância de classe ” poderia
muito bem ser uma “variável de instância do módulo” e assim por diante.
Isso foi um mergulho profundo no modelo de objeto do Ruby. Enquanto vocês dois se preparam
para deixar o escritório, Bill faz uma promessa de que amanhã haverá menos conversas e mais
programação. "Estou realmente ansioso por isso", diz ele.
CAPÍTULO 6
Até agora você viu muitos feitiços de metaprogramação maravilhosos - mas é possível que o
significado da palavra com "m" tenha se tornado mais confuso para você. O fato é que a definição
original de metaprogramação como “escrever código que escreve código” não se encaixa em
todas as técnicas descritas neste livro.
Em vez de procurar uma definição atualizada e digna da Wikipedia, podemos aceitar que a
metaprogramação não é uma abordagem única que você pode definir em uma frase curta. É mais
como um saco heterogêneo de truques que giram em torno do modelo de objeto Ruby. E, como
qualquer outro saco de truques, a metaprogramação realmente se destaca quando você começa
a misturar muitos desses truques.
Hoje você aprenderá alguns novos truques que pode adicionar a essa sacola, incluindo um que
literalmente “escreve código”. Mas ainda melhor, você verá como pode misturar e combinar vários
truques para resolver um difícil desafio de codificação.
Onde seu chefe desafia você e Bill a escrever um código melhor do que ela.
Depois de uma semana tão agitada, você está ansioso por uma sexta-feira relaxante. Mas assim
que você se senta com Bill e sua xícara de café, seu chefe aparece.
"Vocês fizeram um bom trabalho esta semana", diz ela. “Olhando para o seu código, fiquei tão
empolgado com a metaprogramação que decidi aprender sozinho. Mas ontem à noite fiquei preso
em um problema de codificação difícil. Pode me ajudar?"
Ter um chefe que era programador e ainda gosta de sujar as mãos às vezes pode tornar sua vida
mais difícil. Mas você é novo neste trabalho e não pode dizer não quando seu chefe pede sua
ajuda.
Desafio do chefe
Há alguns dias, seu chefe aprendeu sobre o método attr_accessor sobre o qual você leu em O exemplo
attr_accessor(), na página 116. Agora ela está usando attr_accessor o tempo todo para gerar os atributos
de seus objetos. Enquanto ela fazia isso, seu chefe também teve a ideia de escrever sua própria Macro
de classe (117), semelhante a tr_accessor, que gera um atributo validado . "Eu chamo de attr_checked",
diz ela.
Seu chefe explica como esse método attr_checked deve funcionar, lembrando que ele deve levar o nome
do atributo, além de um bloco. O bloco é usado para validação. Se você atribuir um valor ao atributo e o
bloco não retornar verdadeiro para esse valor, você obterá uma exceção de tempo de execução.
O primeiro requisito de seu chefe é uma macro de classe attr_checked , e ela explica seu requisito
secundário: “Não quero que esse método attr_checked esteja disponível para todas as classes, porque
não gosto da ideia de sobrecarregar classes padrão com meu métodos próprios. Em vez disso, uma
classe deve obter acesso a attr_checked somente quando incluir um módulo CheckedAttributes .” Ela
fornece este exemplo:
class Person ÿ
include CheckedAttributes
fim
fim
eu = Pessoa.novo
me.age = 39 # OK
me.age = 12 # Exceção
Um Plano de Desenvolvimento
O desafio do chefe é um pouco demais para lidar com uma única explosão de codificação.
Você chegará a uma solução em pequenos passos.
1. Escreva um Kernel Method (32) chamado add_checked_attribute usando eval para adicionar um
atributo validado supersimples para uma classe.
4. Altere add_checked_attribute para uma Macro de Classe (117) chamada attr_checked que é
disponível para todas as classes.
“Não deveríamos trabalhar como um par?” você pergunta. “Eu nem entendo esses passos.”
"Não se preocupe", diz Bill. “Você realmente só precisa aprender duas coisas antes de começar a
desenvolver: uma é um método chamado eval e a outra é o conceito de um método Hook.” Ele
promete contar tudo o que você precisa saber sobre eval, porque eval é necessário para a primeira
etapa do desenvolvimento. Você lidará com métodos de gancho mais tarde.
Kernel#eval
Onde você aprende que, quando se trata disso, o código é apenas texto.
Você já aprendeu sobre instance_eval e class_eval (em instance_eval(), na página 85, e class_eval(),
na página 107, respectivamente). Agora você pode se familiarizar com o terceiro membro da família
*eval — um Kernel Method (32) chamado simplesmente de eval. Kernel#eval é o mais direto dos três
métodos *eval .
Em vez de um bloco, é preciso uma string que contém o código Ruby - uma String of Code Spell: String of Code para
abreviar. Kernel#eval executa o código na string e retorna o resultado:
ctwc/simple_eval.rb
matriz = [10, 20]
elemento = 30
Executar uma string literal de código Ruby é um exercício bastante inútil, mas o poder de eval torna-
se aparente quando você calcula suas Strings de Código em tempo real. Aqui está um exemplo.
ÿ restclient http://www.twitter.com
Se você procurar na fonte da gema, verá que get e os outros três básicos
Os métodos HTTP são definidos na classe Resource :
gems/rest-client-1.6.7/lib/restclient/
resource.rb módulo RestClient
recurso de classe
def get(additional_headers={}, &block) # ... def post(payload,
Added_headers={}, &block) # ... def put(payload, adicionais_headers={},
&block) # ... def delete(additional_headers ={}, &block) # ...
Para disponibilizar get e seus irmãos no interpretador, o REST Client define quatro
métodos de nível superior que delegam aos métodos de um Recurso em uma URL
específica. Por exemplo, aqui está como o nível superior obtém delegados para um
Recurso (retornado pelo método r ):
Você pode esperar encontrar essa definição de get no código-fonte, juntamente com
definições semelhantes para colocar, postar e excluir. No entanto, aqui vem uma
reviravolta. Em vez de definir os quatro métodos separadamente, o REST Client define
todos eles de uma só vez, criando e avaliando quatro Strings of Code (141) em um loop:
gems/rest-client-1.6.7/bin/restclient
POSSIBLE_VERBS = ['obter', 'colocar', 'postar', 'excluir']
POSSIBLE_VERBS.each do |m|
eval <<-end_eval def
#{m}(caminho, *args, &b)
r[caminho].#{m}(*args, &b)
end
end_eval
end
O código acima usa uma sintaxe exótica conhecida como here document, ou heredoc para
abreviar. O que você vê após a avaliação é apenas uma string Ruby normal, embora não
seja delimitada pelas aspas usuais. Em vez disso, começa com uma sequência <<-
seguida por uma sequência de terminação arbitrária — neste caso, end_eval.
A string termina na primeira linha que contém apenas a sequência de terminação, portanto,
essa string específica abrange as linhas de def até a primeira extremidade incluída.
O código usa a substituição regular de strings para gerar e avaliar quatro Strings
de Código, cada uma para as definições de get, put, post e delete.
A maioria das Strings of Code apresenta algum tipo de substituição de string, como no
exemplo acima. Para uma maneira alternativa de usar eval, você pode avaliar arbitrariamente
Strings de código de uma fonte externa, construindo efetivamente seu próprio interpretador
Ruby simples.
Se você quiser usar o Kernel#eval em todo o seu potencial, também deve aprender sobre a
classe Binding .
Objetos de ligação
Um Binding é um escopo inteiro empacotado como um objeto. A ideia é que você possa criar
um Binding para capturar o escopo local e carregá-lo. Posteriormente, você pode executar o
código nesse escopo usando o objeto Binding em conjunto com eval.
ctwc/
bindings.rb
class MyClass
def
b = MyClass.new.my_method
Você pode pensar nos objetos Binding como formas “mais puras” de encerramentos do que
blocos porque esses objetos contêm um escopo, mas não contêm código. Você pode avaliar o
código no escopo capturado passando o Binding como um argumento adicional para eval:
Ruby também fornece uma constante predefinida chamada TOPLEVEL_BINDING, que é apenas
um Binding do escopo de nível superior. Você pode usá-lo para acessar o escopo de nível
superior de qualquer lugar em seu programa:
classe outra classe
def my_method
eval "self", TOPLEVEL_BINDING end
fim
Uma gem que faz bom uso de bindings é Pry, que você conheceu em O exemplo do Pry, na
página 49. Pry define um método Object#pry que abre uma sessão interativa dentro do escopo
do objeto, semelhante ao que o irb faz com sessões aninhadas.
Você pode usar essa função como uma espécie de depurador: em vez de definir um ponto de
interrupção, você adiciona uma linha ao seu código que chama o pry nas associações atuais,
conforme mostrado no código a seguir.
#código...
exigir "pry"; binding.pry # mais código...
A chamada para binding.pry abre um interpretador Ruby nas ligações atuais, dentro do processo
em execução. A partir daí, você pode ler e alterar suas variáveis à vontade. Quando quiser sair
do interpretador, basta digitar exit para continuar rodando o programa. Graças a esse recurso, o
Pry é uma ótima alternativa aos depuradores tradicionais.
Pry não é o único interpretador de linha de comando que usa ligações. Vejamos também o irb, o
utilitário de linha de comando Ruby padrão.
Exemplo do irb
Em sua essência, o irb é apenas um programa simples que analisa a entrada padrão ou um arquivo
e passa cada linha para eval. (Este tipo de programa às vezes é chamado de Spell: Code Processor
Code Processor.) Aqui está a linha que chama eval, no fundo do código-fonte do irb,
em um arquivo chamado workspace.rb:
O argumento de declarações é apenas uma linha de código Ruby. Mas e esses três argumentos
adicionais para avaliar? Vamos passar por eles.
O primeiro argumento opcional para eval é um Binding, e o irb pode alterar esse argumento para
avaliar o código em diferentes contextos. Isso acontece, por exemplo, quando você abre uma
sessão irb aninhada em um objeto específico, digitando irb seguido do nome de um objeto em uma
sessão irb existente. Como resultado, seus próximos comandos serão avaliados no contexto do
objeto, semelhante ao que instance_eval faz.
E quanto a file e line, os dois argumentos opcionais restantes para eval? Esses argumentos são
usados para ajustar o rastreamento de pilha em caso de exceções. Você pode ver como eles
funcionam escrevendo um programa Ruby que gera uma exceção:
ctwc/exception.rb #
este arquivo gera uma exceção na segunda linha x=1/0
Você pode processar este programa com o irb digitando irb exception.rb no prompt.
Se você fizer isso, receberá uma exceção na linha 2 de exception.rb:
Quando o irb chama eval, ele o chama com o nome do arquivo e o número da linha que está
processando no momento. É por isso que você obtém as informações corretas na pilha da exceção
vestígio. Apenas por diversão, você pode hackear a fonte do irb e remover os dois últimos
argumentos da chamada para eval (lembre-se de desfazer a alteração depois):
Execute irb exception.rb agora e a exceção relata o arquivo e a linha onde eval é chamado:
Esse tipo de hacking do rastreamento de pilha é especialmente útil quando você escreve
Processadores de código — mas considere usá-lo em todos os lugares em que avalia uma
String de código (141) para obter um rastreamento de pilha melhor em caso de exceção.
Isso não deveria ser uma grande surpresa. Afinal, o código em uma string não é tão diferente
do código em um bloco. Strings of Code podem até acessar variáveis locais como os blocos:
Como um bloco e uma string de código são muito semelhantes, em muitos casos você tem
a opção de usar qualquer um deles. Qual deles você deve escolher? A resposta curta é que
você provavelmente deve evitar Strings of Code sempre que tiver uma alternativa. Vejamos
porquê.
Para começar, Strings of Code nem sempre funciona bem com a coloração de sintaxe e
preenchimento automático do seu editor. Mesmo quando se dão bem com todo mundo,
Strings of Code tende a ser difícil de ler e modificar. Além disso, o Ruby não relatará um
erro de sintaxe em uma string de código até que essa string seja avaliada, resultando
potencialmente em programas frágeis que falham inesperadamente em tempo de execução.
Injeção de
código Suponha que, como a maioria das pessoas, você tenha problemas para lembrar o que
cada um dos inúmeros métodos de Array faz. Como uma maneira rápida de refrescar sua
memória, você pode escrever um utilitário baseado em avaliação que permite chamar um método
em uma matriz de amostra e visualizar o resultado (chame-o de explorador de matriz):
ctwc/array_explorer.rb
def explore_array(method) code
= "['a', 'b', 'c'].#{method}" puts "Avaliando:
#{code}" código de avaliação
fim
loop { p explore_array(gets()) }
O loop infinito na última linha coleta strings da entrada padrão e alimenta essas strings para
explore_array. Por sua vez, explore_array transforma as strings em chamadas de método em uma
matriz de amostra. Por exemplo, se você alimentar a string "revert()" para explore_array, o método
avaliará a string "['a', 'b', 'c'].revert()". É hora de experimentar este utilitário:
ÿ find_index("b")
ÿ Avaliando: ['a', 'b', 'c'].find_index("b")
1
ÿ mapa! {|e| e.next }
ÿ Avaliando: ['a', 'b', 'c'].map! {|e| e.next } ["b", "c", "d"]
Agora imagine que, sendo uma pessoa do tipo que compartilha, você decide tornar este programa
amplamente disponível na web. Você hackeia uma página da Web rápida e — pronto! — tem um
site onde as pessoas podem chamar métodos de array e ver os resultados. (Para soar como uma
startup adequada, você pode chamar este site de “Arry” ou talvez “MeThood”.)
A entrada é uma chamada inconseqüente para a matriz, seguida por um comando que lista todos
os arquivos no diretório do seu programa. Oh, o horror! Seu usuário mal-intencionado agora pode
executar código arbitrário em seu computador – código que faz algo terrível, como limpar seu
disco rígido ou postar suas cartas de amor em toda a sua lista de endereços. Esse tipo de
exploração é chamado de ataque de injeção de código .
Kernel#avaliação • 147
você pode proteger seu código da injeção de código? Você pode analisar todas as Strings of Code
(141) para identificar operações potencialmente perigosas. Essa abordagem pode ser ineficaz,
porém, porque há muitas maneiras possíveis de escrever código malicioso. Tentar enganar um
hacker determinado pode ser perigoso tanto para o seu computador quanto para o seu ego.
Quando se trata de injeção de código, algumas strings são mais seguras que outras. Apenas strings
que derivam de uma fonte externa podem conter código malicioso, portanto, você pode
simplesmente limitar o uso de eval às strings que você mesmo escreveu.
Esse é o caso em Exemplo de cliente REST, na página 141. Em casos mais complicados,
entretanto, pode ser surpreendentemente difícil rastrear quais strings vêm de onde.
Com todos esses desafios, alguns programadores defendem a proibição total do eval .
Os programadores tendem a ser paranóicos com qualquer coisa que possa dar errado, então essa
proibição de avaliação acaba sendo uma escolha bastante popular. (Na verdade, não somos
paranóicos. É o governo colocando algo na água da torneira que nos faz sentir assim.)
Se você acabar com a avaliação, terá que procurar técnicas alternativas caso a caso. Por exemplo,
reveja a avaliação em O exemplo de cliente REST, na página 141. Você pode substituí-lo por um
Método dinâmico (51) e Despacho dinâmico (49):
ctwc/rest_client_without_eval.rb
POSSIBLE_VERBS.each do |m|
define_method m do |caminho, *args, &b|
r[caminho].send(m, *args, &b) fim
fim
ctwc/array_explorer_without_eval.rb
def explore_array(método, *argumentos)
['a', 'b', 'c'].send(método, *argumentos) fim
Ainda assim, há momentos em que você pode simplesmente perder a avaliação. Por exemplo, esta
versão mais recente e segura do Array Explorer não permitiria que seu usuário da web chamasse
um método que usasse um bloco. Se você quiser descrever um bloco Ruby em uma interface web,
você precisa permitir que o usuário insira Strings de Código arbitrárias.
Não é fácil atingir o ponto ideal entre muito eval e nenhum eval . Se você não quiser se
abster completamente da avaliação , o Ruby fornece alguns recursos que o tornam um
pouco mais seguro. Vamos dar uma olhada neles.
ctwc/tainted_objects.rb
# leia a entrada do usuário
user_input = "Entrada do usuário: #{gets()}" puts
user_input.tainted?
ÿ x=1
ÿ verdadeiro
Se você tivesse que verificar todas as strings em busca de contaminação, não estaria em
uma posição muito melhor do que se tivesse simplesmente rastreado as strings inseguras
por conta própria. Mas o Ruby também fornece a noção de níveis seguros, que
complementam bem os objetos corrompidos. Quando você define um nível seguro (o que
pode ser feito atribuindo um valor à variável global $SAFE ), você não permite certas
operações potencialmente perigosas.
Você pode escolher entre quatro níveis seguros, do padrão 0 (“comuna hippie”, onde você
pode abraçar árvores e formatar discos rígidos) a 3 (“ditadura militar”, onde todos os objetos
que você cria são corrompidos por padrão). Um nível seguro de 2, por exemplo, não permite
a maioria das operações relacionadas a arquivos. Qualquer nível seguro maior que 0
também faz com que Ruby se recuse a avaliar strings contaminadas:
$SAFE = 1
user_input = "Entrada do usuário: #{gets()}" eval
user_input
ÿ x=1
ÿ SecurityError: operação insegura - avaliação
O Ruby 2.0 e anteriores também tinham um nível seguro de 4 que nem permitia que você
saísse do programa livremente. Por várias razões, esse nível extremamente seguro acabou
não sendo tão seguro quanto as pessoas pensavam que seria, então ele foi removido no
Ruby 2.1.
Kernel#avaliação • 149
Para ajustar a segurança, você pode remover explicitamente a contaminação em Strings of Code
antes de avaliá-los (você pode fazer isso chamando Object#untaint) e, em seguida, confiar em
níveis seguros para impedir operações perigosas, como acesso ao disco.
Usando níveis seguros com cuidado, você pode escrever um ambiente controlado para avaliação.
Esse ambiente é chamado de Sandbox. Vamos dar uma olhada em um Sandbox Spell: Sandbox tirado de uma
biblioteca da vida real.
O Exemplo ERB A
biblioteca padrão ERB é o sistema de modelo Ruby padrão. Esta biblioteca é um processador de
código (144) que você pode usar para incorporar Ruby em qualquer arquivo, como este modelo
contendo um trecho de HTML:
ctwc/template.rhtml
<p><strong>Acorde !</strong> Está um lindo sol <%= Time.new.strftime("%A") %>.</p>
A tag especial <%= ... > contém código Ruby incorporado. Ao passar esse template pelo ERB, o
código é avaliado:
ctwc/erb_example.rb
require 'erb' erb
= ERB.new(File.read('template.rhtml')) erb.run
Em algum lugar no código-fonte do ERB, deve haver um método que pega um trecho de código
Ruby extraído do modelo e o passa para eval. Com certeza, aqui está:
classe ERB
def result(b=new_toplevel) if
@safe_level proc
{ $SAFE
= @safe_level eval(@src,
b, (@filename || '(erb)'), 0) }.call else
fim
#...
também usa um Proc como Sala Limpa (87) para executar o código em um escopo separado.
(Observe que o novo valor de $SAFE se aplica apenas dentro do Proc. Ao contrário do que
acontece com outras variáveis globais, o interpretador Ruby toma o cuidado de redefinir $SAFE
para seu valor anterior após a chamada.)
“Agora,” Bill diz, finalmente concluindo sua longa explicação, “você sabe sobre a avaliação e como
ela pode ser perigosa. Mas eval é ótimo para colocar o código em funcionamento rapidamente. É
por isso que você pode usar esse método como primeiro passo para resolver seu problema original:
escrever o gerador de atributos para o chefe.”
Kernel#eval() e Kernel#load()
Ruby tem métodos como Kernel#load e Kernel#require que pegam o nome de um arquivo fonte e
executam o código desse arquivo. Se você pensar sobre isso, avaliar um arquivo não é tão diferente de
avaliar uma string. Isso significa que load e require são um pouco semelhantes a eval. Embora esses
métodos não sejam realmente parte da família *eval , você pode considerá-los primos de primeiro grau.
Normalmente, você pode controlar o conteúdo de seus arquivos, portanto, não tem tantas preocupações
de segurança com carga e necessidade quanto com eval. Ainda assim, níveis seguros superiores a 1
colocam algumas limitações na importação de arquivos. Por exemplo, um nível seguro de 2 ou superior
impede que você use load com um nome de arquivo corrompido.
1. Escreva um Kernel Method (32) chamado add_checked_attribute usando eval para adicionar um
atributo validado supersimples para uma classe.
ctwc/checked_attributes/
eval.rb requer 'teste/unidade'
def test_accepts_valid_values
@bob.age = 20
assert_equal 20, @bob.age end
def test_refuses_nil_values
assert_raises RuntimeError, 'Invalid attribute' do @bob.age = nil
end
fim
def test_refuses_false_values
assert_raises RuntimeError, 'Atributo inválido' do
@bob.idade = falso
fim
fim
fim
Você precisa gerar um atributo como attr_accessor faz. Você pode apreciar uma breve
revisão de attr_accessor, sobre a qual falamos primeiro em The attr_accessor()
Exemplo, na página 116. Quando você diz ao attr_accessor que deseja um atributo
chamado, digamos, :my_attr, ele gera dois Métodos Simulados (218) como o seguinte:
def my_attr
@my_attr
end
Solução do questionário
fim
Aqui está a String de Código (141) que é avaliada quando você chama
add_checked_attribute(String, :my_attr):
def my_attr()
@my_attr
end
end
A classe String é tratada como Open Class (14), e ganha dois novos métodos.
Esses métodos são quase idênticos aos que seriam gerados por attr_accessor, com
uma verificação adicional que gera uma exceção se você chamar my_attr= com nil ou
false.
“Foi um bom começo”, diz Bill. “Mas lembre-se do nosso plano. Usamos eval apenas
para passar nos testes de unidade rapidamente; não queremos ficar com eval para a
solução real. Isso nos leva ao passo 2.”
Você olha para o plano de desenvolvimento. Sua próxima etapa é refatorar add_checked_attribute
e substituir eval por métodos regulares do Ruby.
Você pode estar se perguntando por que a obsessão em remover eval. Como
add_checked_attribute pode ser um alvo para um ataque de injeção de código se deve ser
usado apenas por você e seus colegas de equipe? O problema é que você nunca sabe se esse
método pode ser exposto ao mundo em algum momento no futuro.
Além disso, se você reescrever o mesmo método sem usar Strings of Code (141), ele ficará
mais claro e elegante para leitores humanos e menos confuso para ferramentas como destaques
de sintaxe. Essas considerações são motivo suficiente para seguir em frente e abandonar
completamente a avaliação .
Solução do questionário
Para definir métodos em uma classe, você precisa entrar no escopo dessa classe. A versão
anterior de add_checked_attribute fazia isso usando uma Classe Aberta (14) dentro de uma
String de Código. Se você remover eval, não poderá mais usar a palavra-chave class , porque
class não aceitará uma variável para o nome da classe. Em vez disso, você pode entrar no
escopo da classe com class_eval.
ctwc/checked_attributes/
no_eval.rb def add_checked_attribute(klass, attribute)
ÿ klass.class_eval do ÿ # ... ÿ
end end
ÿ fim
fim
fim
O código anterior define dois Métodos Mimic (218) que devem ler e gravar uma variável de
instância. Como o código pode fazer isso sem avaliar uma String of Code? Se você navegar pela
documentação do Ruby, encontrará alguns métodos que manipulam variáveis de instância,
incluindo Object#instance_variable_get e Object#instance_variable_set. Vamos usá-los:
fim
"É isso", diz Bill. “Agora temos um método que entra em um escopo de classe e define métodos
de instância que manipulam variáveis de instância, e não há nenhuma avaliação baseada em
string para falar. Agora que nosso código está funcionando e sem avaliação, podemos passar
para a terceira etapa do nosso plano de desenvolvimento.”
Para resolver o desafio do chefe, você e Bill ainda precisam implementar alguns recursos
importantes. Uma dessas funcionalidades está descrita na terceira etapa do seu plano de
desenvolvimento: “validar atributos por meio de um bloco”. No momento, seu atributo gerado gera
uma exceção se você atribuir nil ou false. Mas deve oferecer suporte à validação flexível por meio
de um bloco.
Como essa etapa altera a interface de add_checked_attribute, ela também exige uma atualização
do conjunto de testes. Bill substitui os dois casos de teste que verificaram atributos nulos ou falsos
por um único novo caso de teste:
ctwc/checked_attributes/
block.rb requer 'teste/unidade'
Você pode modificar add_checked_attribute para que passe nos novos testes?
Solução do questionário
Você pode passar nos testes e resolver o questionário alterando algumas linhas em
add_checked_attribute:
atributo define_method do
instance_variable_get "@#{attribute}" end
fim
fim
A quarta etapa em seu plano de desenvolvimento solicita que você altere o método Kernel
para uma macro de classe (117) disponível para todas as classes.
ctwc/checked_attributes/
macro.rb requer 'teste/unidade'
classe Pessoa
ÿ attr_checked :age do |v| ÿ v >= 18
ÿ fim
fim
def test_accepts_valid_values
@bob.age = 20
assert_equal 20, @bob.age end
def test_refuses_invalid_values
assert_raises RuntimeError, 'Atributo inválido' do @bob.age = 17
end end end
Solução do questionário
ctwc/checked_attributes/
macro.rb ÿ class
Class ÿ def attr_checked(attribute, &validation)
define_method "#{attribute}=" do |value| raise
'Atributo inválido' a menos que validação.call(value)
instance_variable_set("@#{attribute}", value) end
atributo define_method do
instance_variable_get "@#{attribute}" end ÿ end
ÿ
end
Esse código nem precisa chamar class_eval, pois quando o método é executado, a classe já
está assumindo o papel de self.
"Isso é ótimo", diz Bill. “Mais um passo e estaremos prontos.” Para esta última etapa, no
entanto, você precisa aprender sobre um recurso sobre o qual ainda não falamos: Hook Methods.
Métodos de Gancho
Onde você obtém uma das lições completas de Bill sobre codificação avançada.
O modelo de objeto é um lugar agitado. Muitas coisas acontecem lá enquanto seu código é
executado: classes são herdadas, módulos são misturados em classes e métodos são definidos,
indefinidos e removidos. Imagine se você pudesse “capturar” esses eventos como você captura
eventos de clique do mouse da GUI. Você seria capaz de executar o código sempre que uma
classe fosse herdada ou sempre que uma classe ganhasse um novo método.
Bem, acontece que você pode fazer todas essas coisas. Este programa imprime uma notificação
na tela quando uma classe herda de String:
ctwc/hooks.rb
class String def
self.inherited(subclasse)
puts "#{self} foi herdado por #{subclass}" end
fim
O método herdado é um método de instância de Class, e Ruby o chama quando uma classe é
herdada. Por padrão, Class#inherited não faz nada, mas você pode substituí-lo por seu próprio
código, como no exemplo anterior. Um método como Class#inher ited é chamado de Método
Gancho porque você pode usá-lo para conectar-se a um feitiço específico: Método Gancho
evento.
Mais ganchos
Ruby fornece um conjunto variado de ganchos que cobrem os eventos mais interessantes
no modelo de objeto. Assim como você substitui Class#inherited para se conectar ao ciclo
de vida das classes, você pode se conectar ao ciclo de vida dos módulos substituindo
Module#included e (no Ruby 2.0) Module#prepended:
módulo M1
def self.included(othermod)
puts "M1 foi incluído em #{othermod}" end
fim
módulo M2
def self.prepended(othermod) coloca
"M2 foi anexado a #{othermod}" end
fim
classe C
inclui M1
preceder M2
final
ÿ M1 foi incluído em C
M2 foi anexado a C
Você também pode executar código quando um módulo estende um objeto substituindo
Module#extended. Por fim, você pode executar eventos relacionados ao método substituindo
Module#method_added, method_removed ou method_undefined.
módulo M
def self.method_added(método)
puts "Novo método: M##{método}" end
Esses ganchos funcionam apenas para métodos de instância regulares, que vivem na classe
do objeto. Eles não funcionam para Métodos Singleton (114), que residem na classe
singleton do objeto. Para capturar eventos do Método Singleton, você pode usar
BasicObject#sin gleton_method_added, singleton_method_removed e singleton_method_undefined.
Por exemplo, em Hook Methods, na página 157, você aprendeu como substituir Module#included para
executar código quando um módulo é incluído. Mas você também pode se conectar ao mesmo evento, por
assim dizer, do outro lado: porque você inclui um módulo com o método include , em vez de substituir
Module#included, você pode substituir o próprio Module#include .
Por exemplo:
módulo M; fim
classe C
def self.include(*modules) coloca
"Chamado: C.include(#{modules})" super end
incluir M
final
ÿ Chamado: C.include(M)
Como alternativa à substituição, você pode transformar um método regular em um método de gancho usando
um alias ao redor (134). Você pode encontrar um exemplo dessa técnica em The Thor Example, na página
133.
O exemplo do videocassete
VCR é uma joia que grava e reproduz chamadas HTTP. A classe Request no VCR inclui
um módulo Normalizers::Body :
módulo videocassete
O módulo Body define métodos que lidam com o corpo de uma mensagem HTTP, como
body_from. Após a inclusão, esses métodos se tornam métodos de classe em Request.
Sim, você leu certo: Request está ganhando novos métodos de classe incluindo
gems/vcr-2.5.0/lib/vcr/structs.rb
módulo videocassete
normalizadores de módulo
módulo Body
def self.included(klass)
klass.extend ClassMethods end
módulo ClassMethods
def body_from(hash_or_string) # ...
O código acima faz um truque complicado. Body tem um módulo interno chamado
ClassMethods que define body_from e outros métodos como métodos de instância regular.
O corpo também tem um método de gancho incluído (157). Quando Request inclui Body,
ele dispara uma cadeia de eventos:
classe classe
def attr_checked(atributo, &validação)
define_method "#{attribute}=" do |value| raise 'Atributo
inválido' a menos que validação.call(value) instance_variable_set("@#{attribute}",
value) end
atributo define_method do
instance_variable_get "@#{attribute}" end
fim
fim
Este código define uma Macro de Classe (117) chamada attr_checked. Esta macro de classe é
um método de instância de classe, portanto, está disponível para todas as classes. A etapa
final em seu plano de desenvolvimento solicita que você restrinja o acesso a attr_checked: ele
deve estar disponível apenas para as classes que incluem um módulo chamado CheckedAttributes.
O conjunto de testes para esta etapa é praticamente o mesmo que você usou na etapa 4, com
uma única linha adicional:
ctwc/checked_attributes/module.rb
requer 'teste/unidade'
classe Pessoa
ÿ incluir CheckedAttributes
fim
fim
fim
Solução do questionário
Você pode copiar o truque que aprendeu em O exemplo do videocassete, na página 159.
CheckedAttributes define attr_checked como um método de classe em seus includers:
ÿ módulo CheckedAttributes ÿ def
self.included(base) ÿ base.extend
ClassMethods ÿ end ÿ
ÿ módulo ClassMethods
def attr_checked(atributo, &validação)
define_method "#{attribute}=" do |value| raise 'Atributo
inválido' a menos que validação.call(value) instance_variable_set("@#{attribute}",
value) end
atributo define_method do
instance_variable_get "@#{attribute}" end
fim
ÿ fim ÿ fim
Seu chefe ficará encantado. Estes são os mesmos módulos e macros de classe que ela desafiou
você a escrever esta manhã. Se você pode escrever um código como este, você está no
caminho certo para dominar a arte da metaprogramação.
Embrulhar
Hoje você resolveu um difícil problema de metaprogramação, escrevendo sua própria Macro de
Classe útil (117). Ao longo do caminho, você também aprendeu sobre o poderoso método de
avaliação , seus problemas e como contorná-los. Finalmente, você foi apresentado aos Métodos
Gancho de Ruby (157) e os usou a seu favor.
“Você aprendeu muito esta semana, meu amigo,” Bill diz, sorrindo pelo que parece ser a primeira
vez esta semana. “Agora você sabe o suficiente para trilhar o caminho da metaprogramação
por conta própria. Antes de partirmos para o fim de semana, deixe-me contar uma última história.
“Um desenvolvedor mestre,” Bill começa, “sentado no topo de uma montanha, meditando…”
CAPÍTULO 7
Epílogo
Um desenvolvedor mestre estava meditando no topo de uma montanha íngreme. Tão profunda
era sua meditação, tão profundamente entrelaçada com seu código e sua alma, que ele começou
a roncar suavemente.
“Olhe para esta pequena árvore ao meu lado”, respondeu o mestre, acenando languidamente com
a mão. “Vê como ela se curva delicadamente em direção ao solo, como se alimentasse de suas
próprias raízes? Assim deve ser o seu código: simples e claro, fechando-se sobre si mesmo como
um círculo.”
“Ainda estou confuso, Mestre”, disse o discípulo, ainda mais preocupado do que antes.
“Eles sempre me ensinaram que o código automodificável é ruim. Como saberei que estou
manejando esta arte corretamente?”
“Olhe para o seu código com um coração puro e uma mente limpa”, o mestre treinou o discípulo.
“Você saberá quando o código ficar obscuro. Exercite seu conhecimento para lançar luz, não para
ofuscar e confundir.”
“Mas, Mestre”, argumentou o discípulo, “me falta experiência. Preciso de regras simples para
distinguir o certo do errado.
O mestre começou a ficar irritado. “Você é inteligente o suficiente para aprender”, disse ele, “mas
você é inteligente o suficiente para esquecer o que aprendeu? Não existe metaprogramação. É só
programar o tempo todo. Agora desapareça e deixe-me meditar em paz.”
parte II
Metaprogramação em Rails
Machine Translated by Google
ÿ Pablo Picasso
CAPÍTULO 8
Na primeira parte deste livro, você passou uma semana brigando com outro programador
e abrindo caminho pelas partes internas do Ruby. Você também encheu sua caixa de
ferramentas com truques mágicos de metaprogramação, como Métodos Dinâmicos (51) e
Macros de Classe (117).
Então, você tem o know-how e as ferramentas. Mas agora você está se perguntando
como combiná-los em código da vida real. Como você pode manter suas Aulas Abertas
(14) sob controle? Quando você deve usar um Método Fantasma (57) em vez de um
Método Dinâmico (51)? Como você testa suas Macros de Classe (117)? Para responder
a esses tipos de perguntas, você precisa de mais do que conhecimento e ferramentas.
Você precisa de experiência.
Você não pode obter experiência simplesmente lendo um livro, mas pode obter muito valor
ao observar o trabalho de programadores experientes. Os curtos capítulos desta segunda
parte do livro levam você a um passeio pelo código-fonte do Ruby on Rails (ou apenas
“Rails”, para abreviar), o projeto Ruby por excelência.
O código do Rails usa metaprogramação em cada etapa e geralmente é mais complexo
do que qualquer código que você viu até agora neste livro. Por causa dessa complexidade,
é um ótimo exemplo tanto do poder da metaprogramação quanto de seus perigos
potenciais.
Em vez de uma exploração exaustiva do Rails, este passeio é como uma excursão
turística em um daqueles ônibus de dois andares ao ar livre. Traçarei algumas rotas
cênicas através do código-fonte Rails e, no processo, mostrarei como alguns dos melhores
programadores Ruby aplicam feitiços de metaprogramação para resolver problemas da
vida real. Mas primeiro, vamos falar sobre o próprio Rails.
Ruby on Rails
É provável que você já saiba que Rails é uma estrutura para desenvolver aplicativos da Web
baseados em banco de dados em Ruby. O Rails é tão popular que muitas pessoas entram no
Ruby apenas para poder usar o Rails.
Mesmo que você não saiba muito sobre Rails e seus recursos, você ainda pode acompanhar este
tour. Vamos nos concentrar no código-fonte do Rails, não nos recursos.
Sempre que os recursos forem importantes para entender o código-fonte, dedicarei um tempo
para demonstrá-los. Embora você não precise, você pode querer obter uma rápida introdução ao
Rails em seu site oficial1 se você for completamente novo nele.
Ao visitar o código-fonte do Rails, mostrarei os trechos de código nos quais quero me concentrar.
No entanto, você também pode querer manter o código-fonte à mão para explorá-lo por conta
própria. Para fazer isso, você precisa instalar o Rails.
Instalando trilhos
Como o Rails está sempre evoluindo, é bem possível que o código-fonte tenha mudado
significativamente quando você ler este capítulo. Felizmente, você pode facilmente instalar a
mesma versão do Rails que usei para escrever este livro, digitando gem install rails -v 4.1.0.
Alguns dos próximos capítulos também discutem o código de uma versão muito mais antiga do
Rails, para mostrar como o código-fonte do Rails evoluiu ao longo do tempo. Se desejar, você
pode instalar esta versão mais antiga junto com a mais recente, digitando gem install rails -v 2.3.2.
Executar os comandos acima instala todas as gems que compõem o Rails 4.1.0 e 2.3.2. A gem
rails contém apenas ajudantes, como geradores de código e tarefas Rake, bem como o código
de cola que une as outras gems. Essas outras joias são as que fazem o trabalho real. Três dos
mais importantes são activerecord (que mapeia objetos de aplicativos para tabelas de banco de
dados), actionpack (que lida com a parte “web” do framework web) e activesupport (que contém
utilitários para problemas genéricos, como cálculos de tempo e exploração madeireira).
O código-fonte do Rails
Ao me referir a um arquivo fonte específico, darei a você o caminho do arquivo dentro do diretório
gems do sistema , como gems/activerecord-4.1.0/lib/active_record.rb. Se quiser explorar por
conta própria, você pode usar o comando unpack do RubyGem para
1. http://rubyonrails.org
acesse todo o código-fonte do Rails com o mínimo de confusão. Por exemplo, gem
unpack activerecord -v=4.1.0 copiará toda a distribuição do Active Record 4.1.0 para o
diretório atual.
A partir da versão 4, Rails e suas bibliotecas principais contêm quase 170.000 linhas
de código (incluindo linhas brancas e comentários). Você pode amontoar muitas
informações em apenas algumas linhas de código Ruby - sem falar em centenas de
milhares. Além disso, você mal consegue encontrar um arquivo fonte do Rails que não
faça uso pesado de feitiços de metaprogramação e outras expressões e técnicas
sofisticadas. Considerando tudo, o código-fonte do Rails contém informações suficientes
para ser intimidador.
Esses desafios não devem impedir você de navegar por esse código maravilhoso. O
código-fonte do Rails pode ser assustador, mas também está repleto de truques
interessantes de metaprogramação. Comece devagar, não desanime ao juntar as
peças básicas e, em breve, você poderá entrar na lista crescente de contribuidores do
Rails.
Além disso, não se esqueça dos testes de unidade. Quando você se depara com um
trecho de código confuso, procure seus testes e descubra como ele deve ser usado.
Depois de entender a intenção deles, as linhas de código mais desconcertantes de
repente farão sentido.
CAPÍTULO 9
Active Record é a biblioteca em Rails que mapeia objetos Ruby para registros de banco de dados. Essa
funcionalidade é chamada de mapeamento objeto-relacional e permite obter o melhor do banco de dados
relacional (usado para persistência) e da programação orientada a objetos (usada para lógica de negócios).
Neste capítulo, assim como nos próximos dois, veremos o design de alto nível do código-fonte do Active
Record e como suas partes se encaixam. Estamos menos interessados no que o Active Record faz do que
em como ele faz. Tudo o que precisamos é
um exemplo muito curto de mapeamento de uma classe para um banco de dados - apenas o suficiente para
iniciar nossa exploração dos componentes internos do Active Record.
name. Você deseja mapear os registros na tabela ducks para objetos da classe Duck em seu código.
Vamos começar solicitando o Active Record e abrindo uma conexão com o banco de dados. (Se você deseja
executar este código em seu sistema, também precisa instalar o banco de dados SQLite e a gem sqlite3 .
Mas provavelmente você pode acompanhar apenas lendo o exemplo, sem executá-lo.)
part2/ar_example.rb
requer 'registro_ativo'
ActiveRecord::Base.establish_connection :adapter => "sqlite3",
:banco de dados => "dbfile"
Observe que em uma aplicação Rails, você não precisa se preocupar em abrir a conexão; o aplicativo lê os
nomes do adaptador e do banco de dados de um arquivo de configuração e chama o estabelecimento_conexão
para você. Estamos usando o Active Record sozinho aqui, então temos que abrir a conexão nós mesmos.
ActiveRecord::Base é a classe mais importante no Active Record. Ele não apenas contém
métodos de classe que fazem coisas importantes, como abrir conexões de banco de dados,
mas também é a superclasse de todas as classes mapeadas, como Duck:
classe Pato < ActiveRecord::Base
validar do
errors.add(:base, "Nome de pato ilegal.") excepto nome[0] == 'D' end
fim
O método de validação é uma Macro de Classe (117) que recebe um bloco. Você não precisa
se preocupar com os detalhes do código no bloco - apenas saiba que, neste exemplo, ele
garante que o nome de um Pato comece com um D. (As políticas de nomenclatura de patos
de nossa empresa exigem isso.) Se você tentar salvar um pato com um nome ilegal para o
banco de dados, o salvamento! O método gerará uma exceção, enquanto o salvamento
mais discreto falhará silenciosamente.
Por convenção, o Active Record mapeia automaticamente os objetos Duck para a tabela
ducks . Olhando para o esquema do banco de dados, o Active Record também descobre que
os Patos têm um nome e define um Método Fantasma (57) para acessar esse campo.
Graças a essas convenções, você pode usar a classe Duck imediatamente:
my_duck = Duck.new
my_duck.name = "Donald"
my_duck.valid? # => true my_duck.save!
Verifiquei se my_duck é válido (começa com D) e salvei no banco de dados. Lendo de volta,
você obtém isto:
duck_from_database = Duck.first
duck_from_database.name # => "Donald"
duck_from_database.delete
Isso é código suficiente por enquanto para lhe dar uma noção de como o Active Record deve
ser usado. Agora vamos ver o que está acontecendo sob o capô.
para entender de onde vem um método específico e como ele entra em uma classe como Base.
O restante deste breve capítulo examinará como a funcionalidade de ActiveRe cord::Base é
montada.
Vamos começar voltando para a primeira linha em nosso exemplo: require 'active_record'.
Aqui está o código em active_record.rb, o único arquivo Active Record que você provavelmente
precisará :
gems/activerecord-4.1.0/lib/
active_record.rb exigir
'active_support' exigir 'active_model'
# ...
módulo ActiveRecord
estender ActiveSupport::Autoload
carregamento automático:Base
O Active Record depende fortemente de duas outras bibliotecas que carrega imediatamente:
Active Support e Active Model. Abordaremos o Active Model em breve, mas uma parte do Active
Support já é usada neste código: o módulo ActiveSupport::Autoload , que define um método
autoload . Esse método usa uma convenção de nomenclatura para localizar e exigir
automaticamente o código-fonte de um módulo (ou classe) na primeira vez que você usar o
nome do módulo. Active Record estende ActiveSupport::Autoload, então autoload torna-se um
método de classe no próprio módulo ActiveRecord . (Se você está confuso com este mecanismo,
reveja o feitiço Extensão de Classe (130) .)
O Active Record então usa o autoload como uma Class Macro (117) para registrar dezenas de
módulos, alguns dos quais você pode ver no código acima. Como resultado, o Active Record
atua como um Namespace inteligente (23) que carrega automaticamente todos os bits e peças
que compõem a biblioteca. Por exemplo, quando você usa ActiveRe cord::Base pela primeira
vez, o carregamento automático requer automaticamente o arquivo active_record/base.rb, que
por sua vez define a classe. Vamos dar uma olhada nesta definição.
ActiveRecord::Base
Aqui está a definição completa de ActiveRecord::Base:
gems/activerecord-4.1.0/lib/active_record/base.rb
módulo ActiveRecord
Classe Base
extend ActiveModel::Nomeando extend
ActiveSupport::Benchmarkable extend
ActiveSupport::DescendantsTracker extend ConnectionHandling
extend QueryCache::ClassMethods
extend Consultando extend Translation
estender Delegação::DelegateCache
incluir Núcleo
incluir Persistência
include NoTouching include
ReadonlyAttributes include ModelSchema
incluir Reflexão
incluir Serialização
incluir loja
incluir final do
núcleo
Não é incomum ver uma classe que monta sua funcionalidade a partir de módulos, mas ActiveRecord::Base
faz isso em grande escala. O código acima não faz nada além de estender ou incluir dezenas de módulos.
(Mais uma linha adicional, a chamada para run_load_hooks, que permite que alguns desses módulos executem
seu próprio código de configuração depois de serem carregados automaticamente.) Acontece que muitos dos
módulos incluídos pelo Base também incluem ainda mais módulos .
É aqui que o mecanismo de carregamento automático compensa. ActiveRecord::Base não precisa exigir o
código-fonte de um módulo e, em seguida, incluir o módulo. Em vez disso, inclui apenas o módulo. Graças ao
carregamento automático, classes como Base podem fazer várias inclusões de módulos com o mínimo de
código.
Em alguns casos, não é muito difícil descobrir de qual módulo vem um método específico no Base . Por
exemplo, métodos de persistência como save vêm de ActiveRecord::Persistence:
gems/activerecord-4.1.0/lib/active_record/
persistence.rb módulo ActiveRecord
módulo Persistência
def save(*) # ... def save!(*)
# ... def delete # ...
Outras definições de método são mais difíceis de encontrar. Em Um exemplo curto de registro ativo, na página
171, você examinou métodos de validação como valid? e validar. Vamos caçá-los.
Os módulos de validação
Entre os outros módulos, ActiveRecord::Base inclui um módulo denominado ActiveRe cord::Validations. Este
módulo parece ser um bom candidato para definir métodos como valid? e validar. De fato, se você procurar
em ActiveRecord::Validations, encontrará a definição de valid?—mas não valid:
gems/activerecord-4.1.0/lib/active_record/validations.rb módulo
ActiveRecord
validações do módulo
include ActiveModel::Validations
# ...
def válido?(contexto = nil) # ...
depende. Com certeza, se você examinar sua origem, descobrirá que a validação está definida em
ActiveModel::Validation.
Existem alguns detalhes intrigantes nesta sequência de inclusões de módulos. A primeira é esta:
normalmente, uma classe ganha métodos de instância ao incluir um módulo. Mas validar é um
método de classe em ActiveRecord::Base. Como o Base pode obter métodos de classe incluindo
módulos? Este é o tópico do próximo capítulo, o módulo Concern do Active Support, onde também
veremos o tesouro de metaprogramação que se esconde por trás desse conjunto de módulos. Por
enquanto, observe que os módulos no Active Record são especiais. Você ganha métodos de
instância e de classe ao incluí-los.
Você também pode ter esta pergunta: por que ActiveRecord::Base precisa de ActiveRecord::Validations
e ActiveModel::Validations? Há uma história por trás desses dois módulos com nomes semelhantes:
nas versões anteriores do Rails não havia nenhuma biblioteca Active Model, e a validação foi de
fato definida em ActiveRecord::Validations. À medida que o Active Record continuava crescendo,
seus autores perceberam que ele estava fazendo dois trabalhos separados.
O primeiro trabalho foi lidar com as operações do banco de dados, como salvar e carregar. A
segunda tarefa era lidar com o modelo de objeto: manter os atributos de um objeto ou rastrear
quais desses atributos eram válidos.
Neste ponto, os autores do Active Record decidiram dividir a biblioteca em duas bibliotecas
separadas, e assim nasceu o Active Model. Enquanto as operações relacionadas ao banco de
dados permaneceram no Active Record, as relacionadas ao modelo foram movidas para o Active
Model. Em particular, o válido? O método tem seus próprios motivos para se envolver com o banco
de dados (ele se preocupa se um objeto já foi salvo no banco de dados) - então ele permaneceu
em ActiveRecord::Validations. Pelo contrário, validar não tem relação com o banco de dados, e só
se preocupa com os atributos do objeto. Então mudou para ActiveModel::Validations.
Poderíamos procurar mais definições de método, mas agora você pode ver no que o design de alto
nível do Active Record se resume: a classe mais importante, ActiveRecord ::Base, é um conjunto
de módulos. Cada módulo adiciona métodos de instância (e até mesmo métodos de classe) ao mix
Base . Alguns módulos, como o Validations, por sua vez, incluem mais módulos, às vezes de
bibliotecas diferentes, trazendo ainda mais métodos para o Base.
Antes de nos aprofundarmos na estrutura do Active Record, vamos ver o que esse design incomum
pode nos ensinar.
Por incluir tantos módulos, ActiveRecord::Base acaba sendo uma classe muito grande. Em uma
instalação simples do Rails, o Base tem mais de 300 métodos de instância e impressionantes 550
métodos de classe. ActiveRecord::Base é a Classe Aberta definitiva (14).
Quando olhei para o Active Record pela primeira vez, eu era um programador Java há anos. O
código-fonte do Active Record me deixou chocado. Nenhum codificador Java são jamais escreveria
uma biblioteca que consiste quase exclusivamente em uma única classe enorme com muitas
centenas de métodos. Tal biblioteca seria uma loucura – impossível de entender e manter!
E, no entanto, é exatamente assim que o design do Active Record é: a maioria dos métodos na
biblioteca, em última análise, são colocados dentro de uma classe. Mas espere, fica pior. Como
discutiremos mais adiante, alguns dos módulos que compõem o Active Record não pensam duas
vezes antes de usar a metaprogramação para definir ainda mais métodos em seu includer. Para
adicionar insulto à lesão, até mesmo bibliotecas adicionais que trabalham com o Active Record
geralmente tomam a liberdade de estender o ActiveRecord::Base com módulos e métodos próprios.
Você pode pensar que o resultado desse acúmulo implacável de métodos seria um emaranhado
de espaguete. Mas não é.
Considere a evidência: o Active Record não apenas se sai bem com esse design, mas também se
mostra fácil de ler e alterar. Muitos usuários modificam e Monkeypatch (16) Active Record para
seus próprios propósitos, e centenas de contribuidores trabalharam no código-fonte original. Ainda
assim, o código-fonte evolui tão rapidamente que os pobres autores de livros como este precisam
reescrever a maior parte de seu conteúdo a cada nova edição. O Active Record consegue se
manter estável e confiável mesmo quando muda, e a maioria dos programadores fica feliz em usar
a versão mais recente da biblioteca em seus sistemas de produção.
Aqui está a diretriz mais importante que aprendi com o design do Active Record: as técnicas de
design são relativas e dependem da linguagem que você está usando.
Em Ruby, você usa expressões idiomáticas diferentes daquelas de outras linguagens com as
quais você pode estar acostumado. Não é que as boas regras de design de antigamente tenham
se tornado obsoletas de repente. Pelo contrário, os princípios básicos do design (desacoplamento,
simplicidade, sem duplicação) são verdadeiros em Ruby tanto quanto em qualquer outra linguagem.
Em Ruby, porém, as técnicas que você usa para atingir esses objetivos de design podem ser
surpreendentemente diferentes.
Veja ActiveRecord::Base novamente. É uma classe enorme, mas essa classe complexa não existe
no código-fonte. Em vez disso, ele é composto em tempo de execução pela montagem de módulos
fracamente acoplados, fáceis de testar e fáceis de reutilizar. Se você só precisa dos recursos de
validação, pode incluir ActiveModel::Validations em sua própria classe e ignorar ActiveRecord::Base
e todos os outros módulos, como no código a seguir:
part2/
validations.rb requer 'modelo_ativo'
class User
include ActiveModel::Validations
attr_accessor :senha
validar faça
errors.add(:base, "Não deixe o pai escolher a senha.") if senha == '1234' end end
usuário = Usuário.novo
usuário.senha = '12345'
usuário.válido? # => verdadeiro
usuário.senha = '1234'
usuário.válido? # => falso
CAPÍTULO 10
No capítulo anterior, você viu que os módulos do Rails são especiais: ao incluí-los, você
ganha métodos de instância e de classe. Como isso acontece?
ActiveSupport::Concern é mais fácil de entender se você souber como ele surgiu em primeiro
lugar. Começaremos olhando as versões mais antigas do Rails, antes de a Concern entrar
em cena.
O código-fonte do Rails mudou muito ao longo dos anos, mas algumas ideias básicas não
mudaram muito. Um deles é o conceito por trás do ActiveRe cord::Base. Como você viu em
ActiveRecord::Base, essa classe é um conjunto de dezenas de módulos que definem
métodos de instância e métodos de classe. Para
Por exemplo, Base inclui ActiveRecord::Validations e, no processo, obtém métodos de
instância e classe.
gems/activerecord-2.3.2/lib/active_record/
validations.rb módulo ActiveRecord
validações do módulo
# ...
def self.included(base)
base.extend ClassMethods
# ...
fim
módulo ClassMethods
def valida_comprimento_of(*attrs) # ...
# ...
fim
def válido?
# ...
fim
# ...
fim
fim
O código acima parece familiar? Você já viu essa técnica em O exemplo do videocassete, na página
159. Aqui está uma rápida recapitulação. Quando ActiveRecord::Base inclui validações, três coisas
acontecem:
2. Ruby chama o Hook Method incluído (157) em Validations, passando ActiveRe cord::Base como
um argumento. (O argumento de incluído também é chamado de base, mas esse nome não
tem nada a ver com a classe Base — em vez disso, ele vem do fato de que o incluídor de um
módulo às vezes é chamado de “a classe base”.)
Como resultado, Base obtém ambos os métodos de instância como válido? e métodos de classe
como valides_length_of.
Essa expressão é tão específica que hesito em chamá-la de feitiço. Vou me referir a ele como o
truque de incluir e estender . VCR pegou emprestado do Rails, assim como muitos outros projetos
Ruby ao longo dos anos. Incluir e estender oferece uma maneira poderosa de estruturar uma
biblioteca: cada módulo contém uma parte bem isolada da funcionalidade que você pode inserir em
suas classes com uma simples inclusão . Essa funcionalidade pode ser implementada com métodos
de instância, métodos de classe ou ambos.
Por mais inteligente que seja, incluir e estender tem sua própria cota de problemas. Por um
lado, todo e qualquer módulo que define métodos de classe também deve definir um gancho
incluído semelhante que estende seu includer. Em uma grande base de código como a do
Rails, esse gancho foi replicado em dezenas de módulos. Como resultado, as pessoas
frequentemente questionavam se o esforço de incluir e estender valia a pena. Afinal, eles
observaram, você pode obter o mesmo resultado adicionando uma linha de código ao
includer:
Classe Base
incluir validações
validações estendidas::ClassMethods
# ...
Incluir e estender permite pular a linha de extensão e apenas escrever a linha de inclusão .
Você pode argumentar que remover essa linha de Base não compensa a complexidade
adicional em Validações.
No entanto, a complexidade não é a única falha de incluir e estender. O truque também tem
um problema mais profundo - que merece ser examinado de perto.
Imagine que você inclui um módulo que inclui outro módulo. Você viu um exemplo disso em
The Validations Modules: ActiveRecord::Base inclui ActiveRecord::Validations, que inclui
ActiveModel::Validations. O que aconteceria se ambos os módulos usassem o truque de
incluir e estender? Você pode encontrar uma resposta
olhando para este exemplo mínimo:
part2/chained_inclusions_broken.rb
módulo SecondLevelModule
def self.included(base)
base.extend ClassMethods
fim
módulo ClassMethods
def second_level_class_method; 'OK'; fim fim
fim
módulo FirstLevelModule
def self.included(base)
base.extend ClassMethods
fim
módulo ClassMethods
def first_level_class_method; 'OK'; fim fim
classe ClasseBase
incluir Módulo PrimeiroNível
fim
BaseClass inclui FirstLevelModule, que por sua vez inclui SecondLevelModule. Ambos os módulos
entram na cadeia de ancestrais de BaseClass , então você pode chamar os métodos de instância
de ambos os módulos em uma instância de BaseClass:
SecondLevelModule também usa include-and-extend, então você pode esperar que métodos em
SecondLevelModule::ClassMethods se tornem métodos de classe em BaseClass. No entanto, o
truque não funciona neste caso:
Percorra o código passo a passo e você verá onde está o problema. Quando Ruby chama
SecondLevelModule.included, o parâmetro base não é BaseClass, mas FirstLevelModule. Como resultado,
os métodos em SecondLevelModule::ClassMethods tornam-se
métodos de classe em FirstLevelModule—que não é o que queríamos.
O Rails 2 incluiu uma correção para esse problema, mas a correção não foi bonita: em vez de usar include-
and-extend tanto no FirstLevelModule quanto no SecondLevelModule, o Rails usou apenas no
FirstLevelModule. Então FirstLevelModule#included forçou o includer a também incluir o SecondLevelModule,
assim:
part2/chained_inclusions_fixed.rb
módulo FirstLevelModule
def self.included(base)
base.extend ClassMethods
ÿ base.send :include, SecondLevelModule
fim
# ...
tinha que saber se era para ser de primeiro nível. (Para tornar as coisas mais desajeitadas,
o Rails não podia chamar Module#include diretamente, porque era um método privado—
então tinha que usar um Despacho Dinâmico (49) . Rubis recentes tornados públicos
include , mas estamos falando de história antiga aqui .)
Neste ponto de nossa história, você seria perdoado por pensar que incluir e estender
criou mais problemas do que resolveu em primeiro lugar. Esse truque forçou vários
módulos a conter o mesmo código clichê e falhou se você tivesse mais de um nível de
inclusões de módulo. Para resolver esses problemas, os autores do Rails criaram o
ActiveSupport::Concern.
Suporte Ativo::Preocupação
ActiveSupport::Concern encapsula o truque de incluir e estender e corrige o problema
de inclusões encadeadas. Um módulo pode obter essa funcionalidade estendendo
Concern e definindo seu próprio módulo ClassMethods :
part2/using_concern.rb
requer 'suporte_ativo'
módulo MyConcern
extend ActiveSupport::Concern
módulo ClassMethods
def um_class_method; "um método de classe"; fim
fim
fim
class MyClass
include MyConcern
end
No restante deste capítulo, usarei a palavra “concern” com C minúsculo para significar
“um módulo que estende ActiveSupport::Concern”, como MyConcern faz no exemplo
acima. No Rails moderno, a maioria dos módulos são preocupações, incluindo
ActiveRecord::Validations e ActiveModel::Validations.
O código-fonte do Concern é bastante curto, mas também bastante complicado. Ele define
apenas dois métodos importantes: extended e append_features. Aqui é estendido:
gems/activesupport-4.1.0/lib/active_support/
concern.rb módulo
ActiveSupport módulo Preocupação
class MultipleIncludedBlocks < StandardError #:nodoc: def inicializar
super "Não é possível definir vários blocos 'incluídos' para uma preocupação" end
fim
def self.extended(base)
base.instance_variable_set(:@_dependencies, []) fim
# ...
Quando um módulo extende Concern, Ruby chama o Extended Hook Method (157), e
extended define uma @_dependencies Class Instance Variable (109) no includer.
Mostrarei o que acontece com essa variável em algumas páginas. Por enquanto, lembre-
se de que todas as preocupações o possuem e, inicialmente, é um array vazio.
Module#append_features
Há uma razão pela qual você não leu sobre append_features na primeira parte deste
livro: em sua codificação normal, você deve substituir includes, não append_features.
Se você substituir append_features, poderá obter alguns resultados surpreendentes,
como no exemplo a seguir:
part2/append_features.rb
módulo M
def self.append_features(base); fim fim
classe C
incluir M
fim
Como mostra o código acima, substituindo append_features você pode impedir que um módulo seja
incluído. Curiosamente, é exatamente isso que a Concern quer fazer, como veremos em breve.
Concern#append_features
Concern define sua própria versão de append_features.
gems/activesupport-4.1.0/lib/active_support/
concern.rb módulo
ActiveSupport módulo Preocupação
def append_features(base) # ...
Vamos recapitular o que aprendemos até agora. Primeiro, os módulos que estendem o Concern
obtêm uma variável de classe @_dependencies . Em segundo lugar, eles substituem append_features.
Com esses dois conceitos em vigor, podemos olhar para o código que faz o Concern funcionar.
Inside Concern#append_features
Aqui está o código em Concern#append_features:
gems/activesupport-4.1.0/lib/active_support/
concern.rb módulo
ActiveSupport módulo Preocupação
def append_features(base)
se base.instance_variable_defined?(:@_dependencies)
base.instance_variable_get(:@_dependencies) << self return false
else
retorna false se base < self
@_dependencies.each { |dep| base.send(:include, dep) }
super
base.extend const_get(:ClassMethods) \ if
const_defined?(:ClassMethods) # ...
fim
fim
# ...
Este é um pedaço de código difícil de entender, mas sua ideia básica é simples: nunca inclua
uma preocupação em outra preocupação. Em vez disso, quando as preocupações tentarem
incluir umas às outras, basta vinculá-las em um gráfico de dependências. Quando uma
preocupação é finalmente incluída por um módulo que não é uma preocupação em si, role
todas as suas dependências para o includer de uma só vez.
Vejamos o código passo a passo. Para entendê-lo, lembre-se que ele é executado como um
método de classe da preocupação. Neste escopo, self é o interesse, e base é o módulo que o
inclui, podendo ou não ser um interesse em si.
O que acontece se o seu incluídor não for uma preocupação — por exemplo, quando você é
ActiveRecord::Validations e é incluído por ActiveRecord::Base? Nesse caso, você verifica se
já é ancestral desse includente, talvez porque tenha sido incluído por meio de outra cadeia de
preocupações. (Esse é o significado de base < self.) Se não for, você chegará ao ponto crucial
de todo o exercício: você incluirá recursivamente suas dependências em seu includer. este
minimalista
O sistema de gerenciamento de dependência resolve o problema sobre o qual você leu em O problema das
inclusões encadeadas, na página 181.
Depois de rolar todas as suas preocupações dependentes para a cadeia de ancestrais do seu includente, você
ainda tem algumas coisas a fazer. Primeiro, você deve se adicionar a essa cadeia de ancestrais, chamando o
padrão Module.append_features com super. Por fim, não se esqueça para que serve todo esse maquinário:
você precisa estender o includer com seu próprio módulo ClassMethods , como faz o truque de incluir e
estender. Você precisa de Kernel#const_get para obter uma referência a ClassMethods, porque você deve ler
a constante do escopo de self, não do escopo do módulo Concern , onde esse código está localizado
fisicamente.
A preocupação também contém mais algumas funcionalidades, mas você já viu o suficiente para entender a
ideia por trás deste módulo.
Resumo da Preocupação
gems/activemodel-4.1.0/lib/active_model/validations.rb módulo
ActiveModel
validações do módulo
extend ActiveSupport::Concern # ...
módulo ClassMethods
def validar(*args, &block)
# ...
Fazendo o que foi dito acima, ActiveModel::Validation adiciona um método de classe de validação a
ActiveRecord::Base, sem se preocupar com o fato de ActiveRecord::Validation estar no meio. A preocupação
trabalhará nos bastidores para resolver as dependências entre as preocupações.
O ActiveSupport::Concern é inteligente demais para seu próprio bem? Cabe a você decidir.
Alguns programadores acham que a Concern esconde muita mágica por trás de uma chamada aparentemente
inócua para incluir, e essa complexidade oculta carrega custos ocultos.1 Outros programadores elogiam a
Concern por ajudar a manter os módulos do Rails tão finos e simples quanto possível.
Qualquer que seja sua opinião sobre o ActiveSupport::Concern, você pode aprender muito explorando seu
interior. Aqui está uma lição que tirei pessoalmente dessa exploração.
1. http://blog.coreyhaines.com/2012/12/why-i-dont-use-activesupportconcern.html
Ao longo dos anos, aprendemos que o design de software não é uma questão de “fazer certo da
primeira vez”. Isso é especialmente verdadeiro em uma linguagem maleável como Ruby, onde
você pode usar a metaprogramação para mudar algo tão fundamental quanto a forma como os
módulos interagem. Portanto, aqui está a principal lição que aprendi com a história da Concern: a
metaprogramação não é sobre ser inteligente — é sobre ser flexível.
Quando escrevo meu código, não busco um design perfeito no início e não uso feitiços complexos
de metaprogramação antes de precisar deles. Em vez disso, tento manter meu código simples,
usando as técnicas mais óbvias que fazem o trabalho. Talvez em algum momento meu código
fique confuso ou eu identifique alguma duplicação teimosa. É quando procuro ferramentas mais
precisas, como a metaprogramação.
CAPÍTULO 11
Nos dois capítulos anteriores, examinamos o design modular do Rails e como esse
design mudou ao longo do tempo. Agora vou falar sobre uma mudança mais dramática
na história do Rails: como um método chamado alias_method_chain alcançou a fama,
caiu em desgraça e acabou sendo descartado quase inteiramente da base de código do Rails.
A Ascensão do alias_method_chain
Em The Include-and-Extend Trick, na página 179, mostrei a você um trecho de código
de uma versão antiga do Rails... menos algumas linhas interessantes. Aqui está o
mesmo código novamente, com essas linhas agora visíveis e marcadas com setas:
gems/activerecord-2.3.2/lib/active_record/
validations.rb módulo ActiveRecord
validações do módulo
def self.included(base)
base.extend ClassMethods
ÿ base.class_eval do
ÿ alias_method_chain :salvar, :validação
ÿ alias_method_chain :salvar!, :validação ÿ fim
# ...
fim
part2/greet_with_aliases.rb
módulo Saudações
def
class MyClass
include Saudações fim
Agora, suponha que você queira envolver a funcionalidade opcional em saudação - por
exemplo, você deseja que suas saudações sejam um pouco mais entusiásticas. Você pode
fazer isso com alguns apelidos ao redor (134):
classe MyClass
inclui Saudações
Dentro de alias_method_chain
Aqui está o código de alias_method_chain:
gems/activesupport-4.1.0/lib/active_support/core_ext/module/
aliasing.rb
class Module def alias_method_chain(target, feature)
# Retire a pontuação em predicados ou métodos bang desde # por exemplo,
target?_without_feature não é um nome de método válido. aliased_target,
pontuação = target.to_s.sub(/([?!=])$/, ''), $1 yield(aliased_target, pontuação) if
block_given?
caso
quando public_method_defined?(sem_metodo)
alvo público
quando protected_method_defined?(without_method)
alvo protegido
quando private_method_defined?(without_method)
fim de destino
privado
fim
fim
alias_method_chain também possui mais alguns recursos, como ceder a um bloco para
que o chamador possa substituir a nomenclatura padrão e lidar com métodos que
terminam com uma exclamação ou um ponto de interrogação - mas, essencialmente, ele
apenas cria um Around Alias ( 134 ) . Vejamos como esse mecanismo foi utilizado em
ActiveRecord::Validações.
def self.included(base)
base.extend ClassMethods
# ...
base.class_eval do
alias_method_chain :save, :validation
alias_method_chain :save!, :validation end
# ...
fim
Essas linhas reabrem a classe ActiveRecord::Base e hackeiam seu save e save! métodos para adicionar
validação. Esse alias garante que você obtenha validação automática sempre que salvar um objeto no banco
de dados. Se você deseja salvar sem validar, pode chamar as versões com alias do método original, agora
chamadas de save_without_validation.
Para que todo o esquema funcione, o módulo Validations ainda precisa definir dois métodos chamados
save_with_validation e save_with_validation!:
gems/activerecord-2.3.2/lib/active_record/
validations.rb módulo ActiveRecord
validações do módulo
def save_with_validation(perform_validation = true)
se perform_validation && válido? || !perform_validation save_without_validation
else
falso
fim
fim
def save_with_validation! se
válido?
salvar_sem_validação! outro
fim
def válido?
# ...
Era assim que o alias_method_chain era usado na época do Rails 2. As coisas mudaram desde
então, como explicarei a seguir.
A Queda de alias_method_chain
Nos dois capítulos anteriores, você viu que as bibliotecas no Rails são construídas principalmente
pela montagem de módulos. De volta ao Rails 2, muitos desses módulos usavam
alias_method_chain para envolver a funcionalidade dos métodos de seus includers. Os autores
de bibliotecas que estenderam Rails adotaram o mesmo mecanismo para agrupar sua própria
funcionalidade em torno dos métodos Rails. Como resultado, alias_method_chain foi usado em
todos os lugares, tanto no Rails quanto em dezenas de bibliotecas de terceiros.
alias_method_chain era bom em remover aliases duplicados, mas também apresentava alguns
problemas próprios. Para começar, alias_method_chain é apenas um encapsulamento de um
Around Alias (134), e Around Aliases tem os problemas sutis que você deve se lembrar de O
exemplo de Thor, na página 133. Para piorar as coisas, alias_method_chain acabou sendo muito
inteligente para seu próprio bem: com toda a renomeação e embaralhamento de métodos que
estava acontecendo no Rails, poderia ser difícil rastrear qual versão de um método você estava
realmente chamando.
No entanto, o problema mais contundente do alias_method_chain era que ele era simplesmente
desnecessário na maioria dos casos. Ruby é uma linguagem orientada a objetos, portanto,
fornece uma maneira mais elegante e integrada de agrupar funcionalidades em torno de um
método existente. Pense novamente em nosso exemplo de adicionar entusiasmo ao método de saudação :
part2/greet_with_super.rb
módulo Saudações
def
cumprimentar "olá"
fim
fim
class MyClass
include Saudações fim
Em vez de usar aliases para agrupar funcionalidades adicionais em torno de saudação, você pode apenas
redefinir saudação em um módulo separado e incluir esse módulo:
part2/greet_with_super.rb
módulo Saudações Entusiasmadas
incluem Saudações
def
cumprimentar "Ei,
#{super}!" fim
fim
class MyClass
include EnthusiasticGreetings end
A cadeia de ancestrais de MyClass inclui EnthusiasticGreetings e depois Greetings, nessa ordem. É por isso
que, ao chamar greet, você acaba chamando EnthusiasticGreet ings#greet, e EnthusiasticGreetings#greet
pode, por sua vez, chamar Greetings#greet com super. Essa solução não é tão glamorosa quanto
alias_method_chain, mas é mais simples e melhor por isso. Versões recentes de ActiveRecord::Validations
reconhecem essa simplicidade usando uma substituição regular em vez de alias_method_chain:
gems/activerecord-4.1.0/lib/active_record/
validations.rb módulo ActiveRecord
validações do módulo
# O processo de validação ao salvar pode ser ignorado passando # <tt>validate:
false</tt>.
# O método regular Base#save é substituído por este quando o # módulo de validações
é misturado, o que é por padrão. salvar def (opções={})
# Tenta salvar o registro exatamente como Base#save, mas gerará # uma exceção
+RecordInvalid+ em vez de retornar +false+ se # o registro não for válido.
def salvar!(options={})
perform_validations(opções) ? super : raise(RecordInvalid.new(self)) end
def perform_validations(options={})
# ...
salve o código em ActiveRecord::Base chamando super. Se a validação falhar, ela retornará false.
Validação#salve! segue as mesmas etapas, exceto que gera uma exceção se a validação falhar.
Hoje em dia, Rails quase nunca usa alias_method_chain. Você ainda pode encontrar esse
método chamado no Active Support e em algumas bibliotecas de terceiros, mas não há
vestígios dele em bibliotecas como o Active Record. O outrora popular alias_method_chain
quase desapareceu do ambiente Rails.
O Nascimento do Módulo#prepend
Vamos adicionar uma reviravolta ao nosso exemplo de método de saudação em andamento : em vez de
definir a saudação em um módulo, vamos supor que seja definido diretamente na classe.
part2/greet_with_prepend.rb
class MyClass
def
cumprimentar "olá"
fim
fim
Nesse caso, você não pode envolver a funcionalidade de saudação simplesmente incluindo
um módulo que o substitua:
part2/greet_with_prepend_broken.rb
módulo EnthusiasticGreetings def
greet "Ei,
#{super}!" fim
fim
class MyClass
include EnthusiasticGreetings end
O código acima mostra que quando você inclui EnthusiasticGreetings, esse módulo fica mais
alto que a classe na cadeia de ancestrais da classe. Como resultado, o método greet na
classe substitui o método greet no módulo, e não o contrário.
Você pode resolver esse problema extraindo a saudação de MyClass em seu próprio
módulo, como o módulo Greetings na seção anterior. Se você fizer isso, poderá inserir um
módulo intermediário como EnthusiasticGreetings na cadeia e usar a técnica override-and-
call-super, assim como fazíamos naquela época.
No entanto, você pode não conseguir fazer isso - por exemplo, porque MyClass faz parte
de uma biblioteca como Rails e você está estendendo essa biblioteca em vez de trabalhar
diretamente em seu código-fonte. Esta limitação é a principal razão pela qual muitos rubistas
ainda usam alias_method_chain quando estendem o Rails.
No entanto, Ruby 2.0 veio com uma solução elegante para este problema na forma de
Module#prepend:
fim
class MyClass
preceder EnthusiasticGreetings end
Este é um Prepended Wrapper (136), uma alternativa moderna para Around Aliases (134).
Como usamos o prefixo, EnthusiasticGreetings #greet ficou mais baixo do que MyClass#greet
na cadeia de ancestrais de MyClass , então voltamos ao truque usual de sobrescrever a
saudação e chamar super.
Enquanto escrevo, Rails ainda não está usando Module#prepend , porque ainda pretende
ser compatível com Ruby 1.9. Quando o Rails acabar com essa restrição, espero que o
prefixo apareça no Rails e em suas extensões. Nesse ponto, não haverá mais motivo
urgente para chamar alias_method_chain .
A lição que aprendi pessoalmente com essa história é: resista à tentação de ser inteligente
demais em seu código. Pergunte a si mesmo se existe uma maneira mais simples de chegar
Neste capítulo, mostrei a você que a metaprogramação pode ser usada em demasia e,
às vezes, substituída por técnicas mais simples. Para ser justo, no entanto, a
metaprogramação ainda é um dos ingredientes mais saborosos da torta do Rails. No
próximo capítulo, mostrarei como um dos recursos definidores do Rails deve sua própria
existência a uma mistura inteligente de truques de metaprogramação.
CAPÍTULO 12
A essa altura da sua leitura, você já viu vários trechos e exemplos de metaprogramação. No
entanto, você ainda pode se perguntar o que acontece quando você usa a metaprogramação em
um sistema real e grande. Como essas técnicas sofisticadas se saem no mundo confuso lá fora,
onde o código geralmente cresce em complexidade e evolui em direções inesperadas?
Para responder a essa pergunta, encerraremos nosso tour com uma olhada nos métodos de
atributo, um dos recursos mais populares do Rails. Seu código-fonte contém muita
metaprogramação e tem mudado constantemente desde a primeira versão do Rails. Se
rastrearmos o histórico dos métodos de atributo, veremos o que aconteceu quando o código deles
se tornou mais complicado e cheio de nuances.
Uma palavra de advertência antes de começarmos: há muitos códigos complexos neste capítulo
e seria inútil explicá-los com muitos detalhes. Em vez disso, vou apenas tentar enfatizar, dando
a você uma ideia de alto nível do que está acontecendo.
Não se sinta obrigado a entender cada linha de código ao ler as próximas páginas.
part2/ar_attribute_methods.rb
requer 'registro_ativo'
ActiveRecord::Base.establish_connection :adapter => "sqlite3",
:banco de dados => "dbfile"
Agora você pode definir uma classe Task vazia que herda de ActiveRecord::Base, e você
pode usar objetos dessa classe para interagir com o banco de dados:
tarefa = Tarefa.novo
task.description = 'Limpar garagem' task.completed
= true task.save
O código anterior chama quatro métodos acessadores para ler e gravar os atributos do
objeto: dois acessadores de gravação (descrição= e concluído=), um acessador de leitura
(descrição) e um acessador de consulta (concluído?). Nenhum desses Métodos de Mímica
(218) vem da definição de Tarefa. Em vez disso, o Active Record os gerou observando as
colunas da tabela de tarefas . Esses acessadores gerados automaticamente são chamados
de métodos de atributo.
Você provavelmente espera que os métodos de atributo como descrição= sejam Métodos
Fantasmas (57) implementados por meio de method_missing ou Métodos Dinâmicos (51)
definidos com define_method. As coisas são realmente mais complicadas do que isso,
como você descobrirá em breve.
gems/activerecord-1.0.0/lib/active_record/
base.rb módulo ActiveRecord
Classe Base
def inicializar(atributos = nil)
@attributes = attribute_from_column_definition # ...
fim
def attribute_names
@attributes.keys.sort end
def responde_to?(método)
@@dynamic_methods ||= attribute_names +
attribute_names.collect { |attr| attr + "=" } + attribute_names.collect
{ |attr| atr + "?" }
@@dynamic_methods.include?(method.to_s) ?
verdadeiro :
respond_to_without_attributes?(método)
fim
super
final
fim
Agora pule para method_missing, onde esses nomes de atributos se tornam os nomes
dos Ghost Methods (57). Quando você chama um método como descrição=,
method_missing percebe duas coisas: primeiro, descrição é o nome de um atributo; e
segundo, o nome de description= corresponde à expressão regular para acessadores
de gravação. Como resultado, method_missing chama write_attribute("description"),
que grava o valor da descrição no banco de dados. Um processo semelhante ocorre
para os acessadores de consulta (que terminam em um ponto de interrogação) e os
acessadores de leitura (que são iguais aos nomes dos atributos).
No Capítulo 3, Terça-feira: Métodos, na página 45, você também aprendeu que geralmente é
uma boa ideia redefinir respond_to? (ou respond_to_missing?) junto com method_missing. Por
exemplo, se eu puder chamar my_task.description, espero que my_task.respond_to?(:description)
retorne true. O ActiveRecord::Base#respond_to? method é um Around Alias (134) do respond_to?
original e também verifica se um nome de método corresponde às regras para leitores, gravadores
ou consultas de atributo.
O respond_to substituído? usa um Nil Guard (219) para calcular esses nomes apenas uma vez
e armazená-los em uma variável de classe @@dynamic_methods .
Parei antes de mostrar o código que acessa o banco de dados, como read_attribute,
write_attribute e query_attribute. Além disso, você acabou de ver toda a implementação dos
métodos de atributo no Rails 1. Quando o Rails 2 foi lançado, no entanto, esse código havia se
tornado mais complexo.
Na maioria dos casos concretos, essa diferença de desempenho entre métodos fantasmas e
métodos regulares é insignificante. No Rails, no entanto, os métodos de atributo são chamados
com muita frequência. No Rails 1, cada uma dessas chamadas também tinha que percorrer a
cadeia extremamente longa de ancestrais do ActiveRecord::Base . Como resultado, o
desempenho foi prejudicado.
Fantasmas Encarnados
Se você verificar o código-fonte do Rails 2, verá que o código dos métodos de atributo foi movido
do próprio ActiveRecord::Base para um módulo ActiveRecord::AttributeMeth ods separado , que
é então incluído pelo Base. O method_missing original também se tornou complicado, então
vamos discuti-lo em duas partes separadas. Aqui está a primeira parte:
gems/activerecord-2.3.2/lib/active_record/attribute_methods.rb
módulo ActiveRecord
módulo AttributeMethods
def method_missing(method_id, *args, &block)
method_name = method_id.to_s
se self.class.private_method_defined?(method_name)
raise NoMethodError.new("Tentativa de chamar método privado", method_name, args) end
self.class.define_attribute_methods if
self.class.generated_methods.include?(method_name) return
self.send(method_id, *args, &block) end
fim
# ...
fim
Quando você chama um método como Task#description= pela primeira vez, a chamada é
entregue a method_missing. Antes de fazer seu trabalho, method_missing garante que você
não esteja ignorando inadvertidamente o encapsulamento e chamando um método privado.
Em seguida, ele chama um método define_attribute_methods que soa intrigante .
Quando você digitou method_missing, description= era um Ghost Method (57). Agora de
scription= é um método regular de carne e osso, e method_missing pode chamá-lo com um
Dynamic Dispatch (49) e retornar o resultado. Este processo ocorre apenas uma vez para cada
classe herdada de ActiveRecord::Base. Se você inserir method_missing uma segunda vez por
qualquer motivo, o método de classe generate_methods? retorna true e esse código é ignorado.
gems/activerecord-2.3.2/lib/active_record/attribute_methods.rb
# Gera todos os métodos relacionados a atributos para colunas no banco de dados
# acessadores, modificadores e métodos de
consulta. def define_attribute_methods
retornar se os métodos_gerados?
column_hash.each do |nome, coluna| a
menos que instance_method_already_implemented?(name) if
self.serialized_attributes[name]
define_read_method_for_serialized_attribute(name) elsif
create_time_zone_conversion_attribute?(name, column)
define_read_method_for_time_zone_conversion(name) else
fim
define_write_method(name.to_sym) end
fim
gems/activerecord-2.3.2/lib/active_record/
attribute_methods.rb ÿ def
define_write_method(attr_name) ÿ
avalia_attribute_method attr_name, ÿ "def #{attr_name}=(new_value);write_attribute('#{attr_name}',
new_value);end", ÿ
"#{attr_name}=" ÿ end
começar
ÿ class_eval(method_definition, __FILE__, __LINE__)
resgate SyntaxError => err
generate_methods.delete(attr_name) if
logger
logger.warn "Ocorreu uma exceção durante a compilação do método do leitor."
logger.warn "Talvez #{attr_name} não seja um identificador Ruby válido?"
logger.warn err. fim da
mensagem
fim
fim
O método define_write_method constrói uma String de Código (141) que é avaliada por
class_eval. Por exemplo, se você chamar description=, avalie_attribute_method avaliará esta
string de código:
Aqui está uma recapitulação do que cobrimos até agora. Quando você acessa um atributo
pela primeira vez, esse atributo é um Método Fantasma (57). ActiveRecord::Base#method_missing
aproveita esta chance para transformar o Método Fantasma em um método real. Enquanto
estiver lá, method_missing também define dinamicamente acessadores de leitura, gravação
e consulta para todas as outras colunas do banco de dados. Na próxima vez que chamar esse
atributo ou outro atributo baseado em banco de dados, você encontrará um método acessador
real esperando por você e não precisará mais inserir method_missing .
No entanto, essa lógica não se aplica a todo e qualquer acessador de atributo, como você
descobrirá observando a segunda metade de method_missing.
part2/ar_attribute_methods.rb
my_query = "tasks.*, (descrição como '%garage%') como heavy_job" task =
Task.find(:first, :select => my_query) task.heavy_job? #
=> verdadeiro
Atributos como heavy_job podem ser diferentes para cada objeto, então não adianta gerar
Métodos Dinâmicos (51) para acessá-los. A segunda metade de method_missing lida com
estes atributos:
gems/activerecord-2.3.2/lib/active_record/attribute_methods.rb módulo
ActiveRecord
módulo AttributeMethods
def method_missing(method_id, *args, &block)
# ...
if self.class.primary_key.to_s == method_name
eu ia
super
final
elsif @attributes.include?(method_name)
read_attribute(method_name) else
super
final
fim
privado #
Identificar *? para method_missing. atributo def ?
(attribute_name)
query_attribute(attribute_name) fim
Não quero perder seu tempo com detalhes desnecessários, então mostrei apenas parte
do código dos métodos de atributo no Rails 2. O que você viu, no entanto, mostra que
tanto o recurso quanto seu código ficaram mais complicados no segunda versão principal
do Rails. Vamos ver como essa tendência continuou nas versões seguintes.
À medida que os aplicativos Rails se tornaram maiores e mais sofisticados, os autores do framework
continuaram descobrindo pequenas reviravoltas, otimizações de desempenho e casos extremos
relacionados a métodos de atributo. O código e o número de
os truques de metaprogramação usados cresceram com o número de casos extremos. Mostrarei
apenas um desses casos extremos, mas mesmo esse único exemplo é muito longo para caber
neste capítulo, então mostrarei apenas alguns trechos de código o mais rápido possível. Prepara-
te.
O exemplo que escolhi é uma das otimizações mais extremas do Rails moderno. Vimos que o
Rails 2 melhorou o desempenho ao transformar Métodos Fantasma (57) em Métodos Dinâmicos
(51). O Rails 4 vai um passo além: ao definir um acessador de atributo, ele também o transforma
em um UnboundMethod e o armazena em um cache de método. Se uma segunda classe tiver um
atributo com o mesmo nome e, portanto, precisar do mesmo acessador, o Rails 4 apenas recupera
o acessador definido anteriormente do cache e o vincula à segunda classe. Dessa forma, se
atributos diferentes em classes separadas tiverem o mesmo nome, o Rails definirá apenas um
único conjunto de métodos de acesso e reutilizará esses métodos para todos os atributos. (Estou
tão surpreso quanto você que essa otimização tem um efeito visível no desempenho — mas no
caso do Rails, tem.)
gems/activerecord-4.1.0/lib/active_record/attribute_methods/
read.rb módulo ActiveRecord
módulo AttributeMethods
módulo lido
estender ActiveSupport::Preocupação
módulo ClassMethods
se Module.methods_transplantable?
def define_method_attribute(name) method =
ReaderMethodCache[name]
gerado_attribute_methods.module_eval { define_method nome, método } end
outro
def define_method_attribute(nome)
# ...
fim
fim
Suponha que você esteja executando o Ruby 2.0 ou posterior. Nesse caso,
define_method_attribute recupera um UnboundMethod de um cache de métodos e vincula
o método ao módulo atual com define_method. O cache de métodos é armazenado em
uma constante chamada ReaderMethodCache.
gems/activerecord-4.1.0/lib/active_record/attribute_methods/
read.rb módulo ActiveRecord
módulo AttributeMethods
módulo Ler
ReaderMethodCache = Class.new(AttributeMethodCache) {
private #
Queremos gerar os métodos via module_eval em vez de # define_method, porque
define_method é mais lento no envio.
# A avaliação de muitos métodos semelhantes pode usar mais memória, pois as sequências # de
instrução são duplicadas e armazenadas em cache (em MRI). define_method pode # ser
mais lento no envio, mas se você for cuidadoso com o fechamento # criado, então
define_method consumirá muito menos memória.
#
# Mas, às vezes, o banco de dados pode retornar colunas com # caracteres
que não são permitidos em nomes de métodos normais (como # 'my_column(omg)'.
Portanto, para contornar isso, primeiro definimos com # o identificador __temp__ e, em
seguida, usamos o método alias para renomear # para o que queremos.
EOMETODO
fim
}.novo
superclasse AttributeMethodCache:
gems/activerecord-4.1.0/lib/active_record/attribute_methods.rb
módulo ActiveRecord
módulo AttributeMethods
AttrNames = Module.new { def
self.set_name_cache(nome, valor) const_name =
"ATTR_#{nome}" a menos que
const_defined? const_name
const_set const_name, value.dup.freeze end
fim
}
classe AttributeMethodCache
def inicializar
@module = Module.new
@method_cache = ThreadSafe::Cache.new end def
[]
(name)
@method_cache.compute_if_absent(name) do
safe_name = name.unpack('h*').first temp_method
= "__temp__#{safe_name }"
ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, nome @module.module_eval
method_body(temp_method, safe_name),
__FILE__, __LINE__
@module.instance_method temp_method end
fim
private
def method_body; aumentar NotImplementedError; fim fim
Sua cabeça está girando um pouco depois dessa longa explicação? O meu é. Este
exemplo mostra o quão profundos e complexos os métodos de atributos se tornaram,
quantos casos especiais eles cobriram e o quanto eles mudaram desde seu começo
simples. Agora podemos tirar algumas conclusões gerais.
Aqui está uma pergunta que os desenvolvedores costumam fazer a si mesmos: quantos
casos especiais devo cobrir em meu código? Em um extremo, você sempre pode buscar
um código que seja perfeito desde o início e não deixe pedra sobre pedra. vamos
chame essa abordagem de Faça Certo da Primeira Vez. No outro extremo, você pode reunir algum
código simples que apenas resolva seu problema óbvio hoje e talvez torná-lo mais abrangente
posteriormente, à medida que descobrir mais casos especiais. Vamos chamar essa abordagem de
Design Evolucionário. O ato de projetar código consiste basicamente em encontrar o equilíbrio certo
entre essas duas abordagens.
O que os métodos de atributo do Rails nos ensinam sobre design? No Rails 1, o código para métodos
de acesso era tão simples que você pode considerá-lo simplista. Embora fosse correto e bom o
suficiente para casos simples, ele ignorou muitos casos de uso não óbvios e seu desempenho acabou
sendo problemático em grandes aplicações. À medida que as necessidades dos usuários do Rails
evoluíram, os autores do framework continuaram trabalhando para torná-lo mais flexível. Este é um
ótimo exemplo de Design Evolucionário.
Este é um dos vários designs possíveis. Os autores do Active Record não faltaram alternativas,
incluindo as seguintes:
• Defina acessadores apenas para o atributo que está sendo acessado, não para os outros atributos.
• Sempre defina todos os acessadores para cada objeto, incluindo os acessadores para cálculo
campos relacionados.
Não sei você, mas eu não conseguiria escolher entre todas essas opções apenas adivinhando quais
são mais rápidas. Como os autores do Active Record definiram o design atual? Você pode facilmente
imaginá-los tentando alguns designs alternativos e, em seguida, criando o perfil de seu código em um
sistema da vida real para descobrir onde estavam os gargalos de desempenho... e então otimizando .
O exemplo anterior focou em otimizações, mas os mesmos princípios se aplicam a todos os aspectos
do design do Rails. Pense no código no Rails 2 que impede você de usar method_missing para
chamar um método privado — ou no código no Rails 4
que mapeia nomes de colunas no banco de dados para nomes de métodos Ruby seguros. Você
certamente poderia prever casos especiais como esses, mas pegá-los todos pode ser muito difícil.
É indiscutivelmente mais fácil cobrir um número razoável de casos especiais como o Rails 1 fez e,
em seguida, alterar seu código à medida que mais casos especiais se tornam visíveis.
A abordagem do Rails parece ser muito tendenciosa para o Design Evolutivo, em vez de Faça
Certo da Primeira Vez. Há duas razões óbvias para isso.
Em primeiro lugar, Ruby é uma linguagem flexível e maleável, especialmente quando você usa
metaprogramação, por isso geralmente é fácil evoluir seu código à medida que avança. E segundo,
escrever um código de metaprogramação perfeito de antemão pode ser difícil, porque pode ser
difícil descobrir todos os casos possíveis.
Para resumir tudo em uma única frase: mantenha seu código o mais simples possível e adicione
complexidade conforme necessário. Ao começar, esforce-se para tornar seu código correto nos
casos gerais e simples o suficiente para que você possa adicionar mais casos especiais
posteriormente. Essa é uma boa regra prática para a maioria dos códigos, mas parece ser
especialmente relevante quando a metaprogramação está envolvida.
Essa última consideração também nos leva a uma lição final e mais profunda — uma que tem a
ver com o significado da própria metaprogramação.
CAPÍTULO 13
Quando termino de revisar este livro, a sensação de magia ainda está presente. No entanto,
percebo agora que, na prática, não há linha dura separando a metaprogramação da velha e simples
programação vanilla. A metaprogramação é apenas outro conjunto poderoso de ferramentas de
codificação que você pode usar para escrever um código simples, limpo e bem testado.
Vou me arriscar para fazer uma afirmação mais ousada: com Ruby, a distinção entre
metaprogramação e código regular é confusa - e, em última análise, sem sentido.
Uma vez que você tenha uma compreensão profunda da linguagem, você terá dificuldade em
decidir quais técnicas e expressões são “meta” e quais são programação simples e antiga.
Na verdade, a metaprogramação está tão arraigada em Ruby que você mal consegue escrever um
programa idiomático em Ruby sem usar alguns feitiços de metaprogramação. A linguagem realmente
espera que você ajuste o modelo de objeto, reabra classes, defina métodos dinamicamente e
gerencie escopos com blocos.
Como Bill poderia dizer em um momento zen: “Não existe metaprogramação. É só programar até
o fim.”
Parte III
Apêndices
Machine Translated by Google
APÊNDICE 1
Idiomas Comuns
Este apêndice é uma mistura de expressões populares do Ruby. Eles não são realmente “meta”,
então não se encaixam na história principal deste livro. No entanto, eles são tão comuns e são a
base de tantos feitiços de metaprogramação que você provavelmente vai querer se familiarizar
com eles.
Métodos de imitação
Grande parte do apelo do Ruby vem de sua sintaxe flexível. Você pode encontrar um exemplo
dessa flexibilidade mesmo no programa mais básico:
Os recém-chegados ao Ruby muitas vezes confundem puts com uma palavra-chave da linguagem,
quando na verdade é um método. As pessoas geralmente omitem os parênteses ao chamar puts,
então não parece um método. Reinsira os parênteses e a natureza dos puts se torna óbvia:
puts('Olá, mundo')
Graças a chamadas de método disfarçadas como esta, Ruby consegue fornecer muitos métodos
úteis semelhantes a funções enquanto mantém o núcleo da linguagem relativamente pequeno e
organizado.
Essa ideia simples de remover parênteses de chamadas de método é usada com bastante
frequência por codificadores experientes. Às vezes, você desejará manter os parênteses porque
eles tornam a natureza de um método óbvia — ou talvez porque o analisador requeira os
parênteses para dar sentido a uma linha complexa de código. Outras vezes, você vai querer
eliminar os parênteses para tornar o código mais limpo ou para fazer um método parecer uma
palavra-chave, como é o caso de puts.
Para outro exemplo de sintaxe flexível, pense em atributos de objeto, que na verdade são métodos
disfarçados:
common_idioms/mimic_methods.rb
classe C
def meu_atributo=(valor) @p =
valor final
def meu_atributo @p
fim
fim
obj = C.new
obj.my_attribute = 'algum valor'
obj.my_attribute # => "algum valor"
Métodos de imitação são um conceito muito simples, mas quanto mais você olha para
Ruby, mais você encontra usos criativos para eles. Por exemplo, modificadores de acesso,
como private e protected , são Métodos Mimic, assim como Macros de Classe (117) , como
attr_reader. As bibliotecas populares fornecem outros exemplos. Aqui está um desses
exemplos.
O Exemplo de Acampamento
O trecho de código a seguir vem de um aplicativo escrito com a estrutura da web Camping.
Ele vincula a URL /help a uma ação específica do controlador:
A ajuda de classe parece herdar de uma classe chamada R. Mas o que é aquela pequena
string peculiar logo após R? Você pode supor que Ruby simplesmente recusaria essa
sintaxe, até perceber que R é na verdade um método imitador que pega uma string e
retorna uma instância de Class. Essa é a classe da qual Help realmente herda. (Se a
noção de um método retornando uma classe soa estranha para você, considere que
classes Ruby são apenas objetos, como você pode ler no Capítulo 2, segunda-feira: O
modelo de objeto, na página 11.)
Graças a truques criativos como este, o Camping se parece menos com um framework web
Ruby e mais com uma linguagem específica de domínio para desenvolvimento web.
Em geral, isso é bom, como argumento no Apêndice 2, Linguagens específicas de domínio, na
página 227.
Guardas Nil
A maioria dos iniciantes em Ruby que examinam o código de outra pessoa ficam perplexos
com esse idioma exótico:
common_idioms/nil_guards.rb um ||
= []
Neste exemplo, o valor à direita é uma matriz vazia, mas pode ser qualquer valor atribuível. O ||
= é na verdade um atalho de sintaxe para o seguinte:
um || (a = [])
Para entender esse código, você precisa entender os detalhes do operador “ou” (||).
Superficialmente, o || operator simplesmente retorna verdadeiro se qualquer um dos dois
operandos for verdadeiro — mas há alguma sutileza nisso. Aqui está o caminho que || realmente
funciona.
Lembre-se que em uma operação booleana, qualquer valor é considerado verdadeiro com
exceção de nil e false. Se o primeiro operando for verdadeiro, então || simplesmente retorna o
primeiro operando e o segundo operando nunca é avaliado. Se o primeiro operando não for
verdadeiro, então || avalia e retorna o segundo operando. Isso significa que o resultado será
verdadeiro, a menos que ambos os operandos sejam falsos, o que é consistente com a noção
intuitiva de um operador or.
Agora você pode ver que o código anterior tem o mesmo efeito que este:
se definido?(a) && a
a
outro
a = []
fim
Você pode traduzir este código assim: “Se a for nil, ou false, ou ainda não foi definido, então
torne-o um array vazio e me dê seu valor; se for qualquer outra coisa, apenas me dê o seu
valor.” Nesses casos, programadores Ruby experientes geralmente consideram o operador ||=
mais elegante e legível do que um if. Você não está limitado a arrays, então você pode usar o
mesmo idioma para inicializar praticamente qualquer coisa. Este idioma às vezes é chamado
de Nil Guard, porque é usado para fazer Spell: Nil Guard garantir que uma variável não seja nula.
Problema de Atributo
Os atributos de objeto (que descrevo em Métodos de imitação, na página 217) contêm uma
armadilha oculta para o programador desavisado:
common_idioms/attribute_trouble.rb
classe MinhaClasse
attr_accessor :meu_atributo
def set_attribute(n)
my_attribute = n fim
fim
obj = MyClass.new
obj.set_attribute 10
obj.my_attribute # => nada
Este resultado provavelmente não é o que você esperava. O problema é que o código em set_at
tributo é ambíguo. Ruby não tem como saber se esse código é uma atribuição a uma variável
local chamada my_attribute ou uma chamada para um Método Mimic (218) chamado my_attribute=.
Em caso de dúvida, o padrão do Ruby é a primeira opção. Ele define uma variável chamada
my_attribute, que imediatamente sai do escopo.
Para evitar esse problema, use self explicitamente ao atribuir um atributo do objeto atual.
Continuando com o exemplo anterior:
class MyClass
def set_attribute(n) ÿ
self.my_attribute = n end end
obj.set_attribute 10
obj.my_attribute # => 10
Se você é um experiente especialista em Ruby, pode se fazer uma pergunta sutil que me escapou
completamente enquanto escrevia a primeira edição deste livro. E se MyClass#my_attribute= for
privado? Em What private Really Means, na página 35, eu disse que você não pode chamar um
método private com um auto- receptor explícito - então parece que você está sem sorte neste
caso (extremamente raro). A resposta para esse enigma é uma das poucas exceções ad hoc do
Ruby. Os configuradores de atributos como my_attribute= podem ser chamados com self mesmo
que sejam privados:
class MyClass
private :my_attribute end
Nil Guards também são usados frequentemente para inicializar variáveis de instância. Olha essa
aula:
classe C
def inicializar
@a = []
fim
elementos de def
@a fim
fim
Usando um Nil Guard, você pode reescrever o mesmo código de forma mais sucinta:
elementos
def classe C
@a ||= [] fim
fim
O código anterior inicializa a variável de instância no último momento possível, quando ela é
realmente acessada. Esse idioma é chamado de variável de instância preguiçosa . Às vezes, como
no exemplo anterior, você consegue substituir todo o método initialize por uma ou mais variáveis de Feitiço: Instância Preguiçosa
Variável
instância preguiçosas.
Os Nil Guards têm uma peculiaridade que vale a pena mencionar: eles não funcionam bem com
valores booleanos. Aqui está um exemplo:
fim
b = zero
2. vezes fazer
b ||= calcular_valor_inicial fim
O Nil Guard no código acima parece não funcionar—calculate_initial_value é chamado duas vezes,
em vez de uma vez como você poderia esperar. Para ver onde está o problema, vamos escrever o
if equivalente a esse Nil Guard.
se definido?(b) && b #
se b já estiver definido e nem nulo nem falso: b
else
# se b for indefinido, nulo ou falso: b =
calculate_initial_value end
Se você olhar para esta tradução baseada em if de um Nil Guard, verá que os Nil Guards
são incapazes de distinguir falso de nulo. Em nosso exemplo anterior, b é falso, então o
Nil Guard o reinicializa todas as vezes.
Essa pequena ruga de Nil Guards geralmente passa despercebida, mas também pode
causar um bug ocasional difícil de detectar. Por esse motivo, você não deve usar Nil
Guards para inicializar variáveis que podem ter false (ou nil, nesse caso) como um valor
legítimo.
Auto Rendimento
Quando você passa um bloco para um método, espera que o método chame de volta
para o bloco com yield. Uma reviravolta nos callbacks é que um objeto também pode se
passar para o bloco. Vamos ver como isso pode ser útil.
O exemplo de Faraday
Na biblioteca Faraday HTTP, você normalmente inicializa uma conexão HTTP com uma
URL e um bloco:
common_idioms/
faraday_example.rb requer 'faraday'
resposta = conn.get
response.status # => 200
Este código define os parâmetros para a conexão. Se desejar, você pode obter os
mesmos resultados passando um hash de parâmetros para Faraday.new — mas o estilo
baseado em bloco tem a vantagem de deixar claro que todas as instruções no bloco
estão focadas no mesmo objeto. Se você gosta desse estilo, talvez queira dar uma
espiada no código-fonte de Faraday e ver como ele é implementado.
Faraday.new realmente cria e retorna um objeto Faraday::Connection :
gems/faraday-0.8.8/lib/
faraday.rb módulo Faraday
classe << eu
def new(url = nil, opções = {})
# ...
Faraday::Connection.new(url, opções, &bloco) end
# ...
gems/faraday-0.8.8/lib/faraday/
connection.rb
módulo Faraday
class Connection def initialize(url = nil, options = {})
# ...
ceder auto se block_dado? # ...
fim
# ...
Este idioma simples é conhecido como Self Yield. Self Yields são bastante comuns em Spell: Self Yield
Ruby - mesmo instance_eval e class_eval opcionalmente rendem self ao bloco, embora
esse recurso raramente seja usado na prática:
common_idioms/
self_yield_in_eval.rb
String.class_eval do |
classe| classe # => Fim da string
Para um exemplo mais criativo de Self Yield, você pode conferir o método tap .
O exemplo tap()
Em Ruby, é comum encontrar longas cadeias de chamadas de método como esta:
idiomas_comuns/
tap.rb ['a', 'b', 'c'].push('d').shift.upcase.next # => "B"
Por exemplo, talvez você esteja preocupado que a chamada para o turno não esteja
retornando o que você espera. Para confirmar suas suspeitas, quebre a cadeia e imprima o
resultado do turno (ou defina um ponto de interrupção em seu depurador):
temp.upcase.next
ÿ um
Esta é uma maneira desajeitada de depurar seu código. Se você não deseja dividir a cadeia de chamadas,
pode usar o método tap para inserir operações intermediárias no meio de uma cadeia de chamadas:
ÿ um
O método tap já existe no Kernel. No entanto, é um bom exercício imaginar como você mesmo escreveria se
não fosse fornecido pelo Ruby:
classe Object
def tap
yield self self
fim
fim
Symbol#to_proc()
Este feitiço exótico é popular entre os programadores Ruby de faixa preta. Quando me deparei com esse
feitiço pela primeira vez, tive dificuldade em entender o raciocínio por trás dele. É mais fácil chegar lá dando
um pequeno passo de cada vez.
common_idioms/
symbol_to_proc.rb nomes = ['bob', 'bill',
'heather'] nomes.map {|nome| nome.capitalize } # => ["Bob", "Bill", "Heather"]
Concentre-se no bloco — um simples “bloco de uma chamada” que recebe um único argumento e chama um
único método nesse argumento. Blocos de uma chamada são muito comuns em Ruby, especialmente (mas
não exclusivamente) quando você está lidando com arrays.
Em uma linguagem como Ruby, que se orgulha de ser sucinta e direta, até mesmo um bloco de uma chamada
parece prolixo. Por que você tem que se dar ao trabalho de criar um bloco, com chaves e tudo, apenas para
pedir ao Ruby para chamar um método? A ideia de Symbol#to_proc é que você pode substituir um bloco de
uma chamada por uma construção mais curta. Vamos começar com a menor informação que você precisa,
que é o nome do método que você deseja chamar, como um símbolo:
:capitalizar
Symbol#to_proc() • 225
{|x| x.maiúsculas }
Como primeiro passo, você pode adicionar um método à classe Symbol , que converte o símbolo
em um objeto Proc :
fim
Viu como esse método funciona? Se você chamá-lo, digamos, no símbolo :capitalize , ele
retornará um proc que recebe um argumento e chama capitalize no argumento.
Agora você pode usar to_proc e o operador & para converter um símbolo em um Proc e depois
em um bloco:
Você pode tornar esse código ainda mais curto. Acontece que você pode aplicar o operador & a
qualquer objeto e ele se encarregará de converter esse objeto em um Proc chamando to_proc.
(Você não achou que escolhemos o nome do método to_proc aleatoriamente, não é?) Então,
você pode simplesmente escrever o seguinte:
Esse é o truque conhecido como Symbol To Proc. Legal, hein? Feitiço: Símbolo Para
Processo
A boa notícia é que você não precisa escrever Symbol#to_proc, porque já é fornecido pelo Ruby.
Na verdade, a implementação do Ruby de Symbol#to_proc também suporta blocos com mais
de um argumento, que são exigidos por métodos como inject:
# legal!
APÊNDICE 2
ÿ Oeste da casa
Você está em um campo aberto a oeste de uma casa branca, com a
porta da frente fechada com tábuas.
Você vê uma pequena caixa de correio aqui.
ÿ caixa de correio
aberta ÿ Ao abrir a caixa de correio pequena, é possível encontrar um folheto.
ÿ levar folheto
ÿ Tomado.
Suponha que você tenha que escrever uma aventura de texto como seu próximo trabalho. Em
que idioma você o escreveria?
Você provavelmente escolheria uma linguagem que fosse boa em manipular strings e suportasse
programação orientada a objetos. Mas seja qual for o idioma que você escolher, ainda haverá
uma lacuna entre esse idioma e o problema que está tentando resolver. Isso provavelmente
também acontece em seu trabalho diário de programação. Por exemplo, muitos aplicativos Java
grandes lidam com dinheiro, mas o Money não é um tipo Java padrão. Isso significa que cada
aplicativo precisa reinventar o dinheiro, geralmente como uma classe.
No caso do nosso jogo de aventura, você terá que lidar com entidades como salas e itens.
Nenhuma linguagem de propósito geral suporta essas entidades
diretamente. Você gostaria de um idioma voltado especificamente para aventuras de texto? Dada
essa linguagem, você poderia escrever um código como este:
eu: ator
localização = westOfHouse
;
Este não é um exemplo simulado - é um código real. Está escrito em uma linguagem chamada
TADS, especificamente projetada para criar “ficção interativa” (o nome mais sofisticado de hoje
para aventuras de texto). TADS é um exemplo de uma linguagem específica de domínio (DSL),
uma linguagem que se concentra em um domínio de problema específico.
O oposto de uma DSL é uma linguagem de uso geral (GPL), como C++ ou Ruby. Você pode usar
uma GPL para lidar com uma ampla variedade de problemas, mesmo que ela seja mais adequada
para alguns problemas do que para outros. Sempre que você escreve um programa, cabe a você
escolher entre uma GPL flexível e uma DSL focada.
Vamos supor que você decida seguir a rota DSL. Como você procederia então?
Usando DSLs
Se você quiser uma DSL para o seu problema específico, pode ter sorte. Existem centenas de DSLs por aí,
com foco em uma ampla variedade de domínios. O shell UNIX é uma DSL para unir utilitários de linha de
comando. O VBA da Microsoft foi projetado para estender o Excel e outros aplicativos do Microsoft Office. A
linguagem make é uma DSL focada na construção de programas C, e Ant é um equivalente baseado em XML
de make para programas Java. Algumas dessas linguagens são limitadas em escopo, enquanto outras são
flexíveis o suficiente para cruzar a linha em GPL-dom.
E se você não conseguir encontrar uma DSL pronta que se encaixe no domínio em que está
trabalhando? Nesse caso, você pode escrever sua própria DSL e então usar essa DSL para
escrever seu programa. Você poderia dizer que esse processo – escrever uma DSL e depois usá-
la – é outra abordagem da metaprogramação. Pode ser um caminho escorregadio, no entanto.
Você provavelmente precisará definir uma gramática para sua linguagem com um sistema como
ANTLR ou Yacc, que são DSLs para escrever analisadores de linguagem. À medida que o escopo
do seu problema se expande, sua humilde linguagem
pode se transformar em uma GPL antes mesmo de você perceber. Nesse ponto, sua
incursão vagarosa na escrita da linguagem terá se transformado em uma maratona exaustiva.
Para evitar essas dificuldades, você pode escolher uma rota diferente. Em vez de escrever
uma DSL completa, você pode transformar uma GPL em algo semelhante a uma DSL
para o seu problema específico. A próxima seção mostra como.
dsl/markaby_example.rb
requer 'markaby'
li "Malabarismo"
li "Tricô" li
"Metaprogramação" end
fim
fim
Este código é o antigo Ruby, mas parece uma linguagem específica para geração de
HTML. Você pode chamar o Markaby de DSL interno, porque ele reside em uma
linguagem de uso geral maior. Por outro lado, as linguagens que possuem seu próprio
analisador, como make, costumam ser chamadas de DSLs externas. Um exemplo de DSL
externo é a linguagem de construção Ant. Embora o interpretador Ant seja escrito em
Java, a linguagem Ant é completamente diferente de Java.
Vamos deixar a correspondência GPL x DSL para trás e assumir que você deseja usar
uma DSL. Qual DSL você prefere? Uma DSL interna ou uma DSL externa?
Uma vantagem de uma DSL interna é que você pode recorrer facilmente à GPL subjacente
sempre que precisar. No entanto, a sintaxe da sua DSL interna será limitada pela sintaxe
da GPL por trás dela. Este é um grande problema com alguns idiomas. Por exemplo,
você pode escrever uma DSL interna em Java, mas o resultado provavelmente ainda será
muito parecido com Java. Mas com Ruby, você pode escrever uma DSL interna que se
pareça mais com uma linguagem ad hoc adaptada ao problema em questão. Graças ao
Ruby flexível,
sintaxe organizada, o exemplo de Markaby mostrado anteriormente mal se parece com Ruby.
É por isso que os programadores Ruby tendem a usar Ruby onde os programadores Java usariam
uma linguagem externa ou um arquivo XML. É mais fácil adaptar Ruby às suas próprias
necessidades do que adaptar Java. Como exemplo, considere construir linguagens. As linguagens
de construção padrão para Java e C (Ant e make, respectivamente) são DSLs externas, enquanto
a linguagem de construção padrão para Ruby (Rake) é apenas uma biblioteca Ruby - uma DSL
interna.
DSLs e Metaprogramação
No início deste livro, definimos metaprogramação como “escrever código que escreve código” (ou,
se você quiser ser mais preciso, “escrever código que manipula as construções da linguagem em
tempo de execução”). Agora que você conhece as DSLs, você tem outra definição de
metaprogramação: “projetar uma linguagem específica de domínio e, em seguida, usar essa DSL
para escrever seu programa”.
Este é um livro sobre a primeira definição, não um livro sobre DSLs. Para escrever uma DSL, você
precisa lidar com vários desafios que estão fora do escopo deste livro. Você precisa entender seu
domínio, se preocupar com a facilidade de uso de seu idioma e avaliar cuidadosamente as
restrições e compensações de sua gramática. Ao escrever este livro, optei por manter esta lata de
minhocas fechada.
Sempre que alguém disser que tem “um truque legal”, leve-o para fora
e dê um tapa nele.
ÿ Jim Weirich (1956–2014)
APÊNDICE 3
Livro de feitiços
Este apêndice é um “livro de feitiços” – uma referência rápida a todos os “feitiços” do livro, em ordem alfabética.
A maioria desses feitiços está relacionada à metaprogramação (mas os do Apêndice 1, Idiomas comuns, na
página 217, provavelmente não são “meta”). Cada feitiço vem com um pequeno exemplo e uma referência à
página onde é introduzido. Vá para as páginas associadas para exemplos estendidos e o raciocínio por trás
de cada feitiço.
os feitiços
Em torno de Alias
classe String
alias_method :old_reverse, :reverse
def reverso
"x#{old_reverse}x" fim
fim
Lousa em branco
classe C
def method_missing(nome, *args)
"um método fantasma"
fim
fim
obj = C.new
obj.to_s # => "#<C:0x007fbb2a10d2f8>"
blank_slate = D.new
blank_slate.to_s # => "um método fantasma"
Extensão de classe
Defina métodos de classe misturando um módulo na classe singleton de uma classe (um caso especial de
Object Extension (130)).
classe C; fim
módulo M
def my_method
'um método de classe'
fim
fim
classe << C
incluir M
fim
classe C
@my_class_instance_variable = "algum valor"
def self.class_attribute
@my_class_instance_variable end
fim
Os Feitiços • 233
Macro de classe
classe C; fim
classe <<C
def my_macro(arg)
"my_macro(#{arg}) chamado"
end
fim
classe C
my_macro :x # => "my_macro(x) chamado" end
Quarto limpo
classe CleanRoom
def a_useful_method(x); x fim * 2; fim
Processador de código
File.readlines("a_file_containing_lines_of_ruby.txt").each do |line|
puts "#{line.chomp} ==> #{eval(line)}" end
# >> 1 + 1 ==> 2
# >> 3 * 2 ==> 6
# >> Math.log10(100) ==> 2.0
Sonda de Contexto
classe C
def inicializar
@x = "uma variável de instância privada" end
fim
obj = C.new
obj.instance_eval { @x } # => "uma variável de instância privada"
Avaliação diferida
Armazene um pedaço de código e seu contexto em um proc ou lambda para avaliação posterior.
classe C
def store(&block)
@my_code_capsule = final do
bloco
executar definitivamente
@my_code_capsule.call end
fim
obj = C.new
obj.store { $X = 1 }
$X=0
obj.execute
$X # => 1
Despacho Dinâmico
Decida qual método chamar em tempo de execução.
Os Feitiços • 235
Método Dinâmico
Decida como definir um método em tempo de execução.
classe C
fim
C.class_eval do
define_method :my_method do
"método dinâmico" end
end
obj = C.new
obj.my_method # => "um método dinâmico"
Proxy Dinâmico
classe MyDynamicProxy
def initialize(target) @target
= target end
fim
Escopo Plano
classe C
def an_attribute @attr
end
fim
obj = C.new
a_variable = 100
# escopo plano:
obj.instance_eval do @attr =
a_variable end
Método Fantasma
classe C
def method_missing(name, *args)
name.to_s.reverse end
fim
obj = C.new
obj.my_ghost_method # => "dohtem_tsohg_ym"
Método Gancho
$HERANÇAS = []
classe C
def self.herdado(subclasse)
$INHERITORS << subclasse end
end
classe D<C
fim
classe F<E
fim
Os Feitiços • 237
Método Kernel
Defina um método no módulo Kernel para tornar o método disponível para todos os objetos.
kernel do módulo
def a_method
"um método do kernel"
fim
fim
classe C
atributo def
@attribute = @attribute || "algum valor" fim
fim
obj = C.new
obj.attribute # => "algum valor"
Método de imitação
def BaseClass(nome)
nome == "string" ? String: fim do objeto
class C < BaseClass "string" # um método que se parece com uma classe
attr_accessor :an_attribute # um método que se parece com uma palavra-chave end
obj = C.new
obj.an_attribute = 1 # um método que se parece com um atributo
Monkey Patch
Altere os recursos de uma classe existente.
classe String
def reverso
"sobrepor"
fim
fim
Namespace
Defina constantes dentro de um módulo para evitar conflitos de nome.
module MyNamespace
class Array
def to_s
"my class"
end
end
fim
Array.new # => []
MyNamespace::Array.new # => minha classe
guarda nulo
Substitua uma referência a nil por um “ou”.
x = zero
y = x || "um valor" # => "um valor"
Os Feitiços • 239
Extensão de objeto
Defina Métodos Singleton misturando um módulo na classe singleton de um objeto.
obj = Objeto.novo
módulo M
def my_method
'um método singleton'
end
fim
Aula aberta
Modifique uma classe existente.
fim
Wrapper pré-anexado
Chame um método de sua substituição anexada.
módulo M
def reverso
"x#{super}x"
fim
fim
String.class_eval precede
M end
refinamento
Corrija uma classe até o final do arquivo ou até o final do módulo incluído.
module MyRefinement
refine String do def
reverse
Fim do "meu
avesso"
fim
fim
Invólucro de Refinamento
module StringRefinement
refine String do def
reverse
"x#{super}x"
end
fim
fim
usando StringRefinement
"abc".reverse # => "xcbax"
Caixa de areia
def sandbox(&code)
proc
{ $SAFE =
2
Os Feitiços • 241
Escopo Portão
a=1
definiram? a # => "variável local"
módulo MyModule
b=1
definido? a # => nil
definido? b # => "variável local" end
Auto Rendimento
classe Pessoa
attr_accessor :nome, :sobrenome
def inicializar
render auto
-fim
fim
Escopo Compartilhado
lambda
{ compartilhado = 10
self.class.class_eval faça
define_method :counter do end
contador # => 10
3.times { down }
contador # => 7
Método Singleton
Defina um método em um único objeto.
obj = "abcc"
Cadeia de Código
Avalie uma string de código Ruby.
Symbol To Proc
Converte um símbolo em um bloco que chama um único método.
Índice
Índice • 244
atributos, veja também método convertendo para procs, 89, cadeias de chamadas, 223
attr_access sor; método attr_checked 100, 224, 242 atributos verificados, veja no método
método instance_eval , 85– tr_checked
métodos de atributo, 199– 212 88
class << sintaxe, classes singleton,
self método instance_exec , 86
120, 126
e, 220 classes questionários, 75–
classe classe
singleton, 127– 129 sintaxe, 217 77 Self Yield, 222–224, 241 vs.
sobre, 19
Strings of Code, 145–
150 herança, 20 método
herdado , 157 novo método,
variável @attributes , 201 método de validação , 172
82
atributos validados, 141, 154–155
Módulo AttrNames , 210 método
definições de classe
de carregamento automático , sobre, 13, 105
métodos padronizados, 45
173 carregamento automático, apelidos, 132–135, 137, 190
Exemplos de bookworm
95, 173 gema awesome_print , 33
refatoração de rótulos, 12–16, classe atual, 106–109
36
Scope Gates, 81 self
refatoração de empréstimo, e, 36, 106 classes
Operador de bar B , Nil Guards, 111 wrappers de método, 131–
219, 238 singleton, 118–122, 126
136
Conexões de classe base (Active Namespaces, 25
Métodos Singleton, 113–118
Record), 172 definição, métodos de renomeação, 117
174 design, 172– Métodos Singleton, 113–118
teoria, 106–112
178 respond_to?
Extensões de classe, 130, 180, 185,
método, 202 Módulo de Operadores e valores booleanos,
validações , 175, 179–197 232
Guardas do Nilo, 219–222
Variáveis de Instância de Classe,
chaves, blocos, 74
109, 232
Classe BasicObject
teste de matemática quebrado, 136
Folhas em branco, 67, 103 palavra-chave
ganchos, 158 Construtor, exemplo de ardósia em branco, class sobre,
67–69 14 classe atual, 107
herança, 20 método
instance_eval , 68, 85–88, 108, substituindo por Class.new,
127, 145, 223 C 82
C substituindo por Module#de
method_missing method, 48, 55– método attr_accessor , 150 fine_method, 83
71, 200–206 tempo de compilação/tempo de escopo, 81, 108, 241
execução, 8 idiomas classes singleton, 120
método de ligação , 143
específicos de domínio, 230 Exemplo de
Objetos de ligação , 143–145
C#, visibilidade aninhada, 79 macros de classe attr_accessor , 116–
métodos de 118
atributo de ligação, 208–210
Tempo de compilação/tempo de método attr_checked , 140– 141,
execução C++, 156, 161 método
Objetos de ligação , 143–145
8 modelos, 8 usando palavra- autoload , 173
blocos e, 77, 89 método
Métodos de mímica, 218
instance_eval , 85– chave, 75–77 cache, objetos
88 UnboundMethod , 207–210 livro de feitiços, 233
método de validação , 172
escopo e, 78-84 método calculate_initial_value , 221 método
métodos de classe
Objetos UnboundMethod , 94 de chamada , 94
Tábuas em branco
Biblioteca Active Record e, 176
objetos que podem ser chamados,
Clean Rooms, 88, 103 consulte blocos; ganchos, 159, 180
method_missing method, 66–
lambdas; método chamadores procs , 134 convenções de notação, xx como
69
Exemplo de estrutura de acampamento, Métodos Singleton,
livro de feitiços, 231 115–118
218
block_dado? método, 74 sintaxe, 126
Cantrell, Paul, 93
blocos, veja também procs visualização, 119
cadeia de ancestrais, ver cadeia de
anexando ligações, 89 básicos, variáveis de classe
ancestrais
73–77 Salas
Variável @_dependencies ,
Limpas, 87, 233 fechamentos inclusões encadeadas, 181–183,
como, 77–84 186
prefixo 184 , 110
Machine Translated by Google
Índice • 245
método class_eval , 107, 145, 205, Módulo de preocupação (porta de vinculando UnboundMethods, 95
223 método suporte ativo), 179, 183–188 substituindo def por, 83
class_exec , 108 classes, método const_get , 187 usando, 51
consulte também definições de classe; método const_missing , 63 define_method_attribute method, 208
Aulas Abertas; solteiro constantes
aulas de toneladas método define_singleton_method , 114
classes anônimas e, 113 métodos
anônimo, 113 de
Extensões de classe, 130, 180, atributo, 208, 210 método define_write_method , 204
185, 232 classe
excluir método, 142
atual, 106–109 hierarquias, 110 palavra-chave class ,
herança e classes dependências, 184–186, 188 método
108 método const_missing ,
singleton, 123–125 métodos de obsoleto , 117 método de
63 using, 21–24
instância e, 18–19, 24 descrição , 200, 205 descrição=
método constante , 23
variáveis de instância e, 17–18
Context Probes, 85–88, 234 método, 200, 203, 205
wrappers de
método, 131– convertendo
descrição? método, 205 design
blocos em procs, 89, 100, 224,
242 números Biblioteca
136 em objetos Money , Active Record, 171–178
métodos e, 19, 24 módulos 14
como, 20, 24 nomenclatura, strings, 12–16, 36 métodos de atributos, 211
Índice • 246
Índice • 247
Java Virtual Machine, xxi método de carga método 158 method_undefined , 158
pseudônimos, 133 métodos, consulte também Around
JRuby, xxi
Namespaces e, 26 segurança, Apelido; Macros de classe; métodos
150
k de classe; Métodos Dinâmicos;
Exemplo de UnboundMethod , 95 métodos de instância;
Kernel Methods ap
Classe carregável , UnboundMethod Métodos do Kernel; pesquisa de
method, 33 binding
exemplo, 95 método; Métodos Mímicos;
method, 143 block_given?,
Métodos Singleton; envolver
74 callers method, método local_variables , 79
134 const_get method, Exemplo de classe de registrador , 86
pessoas sobre, 17
187
Machine Translated by Google
Índice • 248
Índice • 249
substituindo removendo
da cadeia de ancestrais, 95 método
o método append_features , Acessores de consulta Q ,
184–185 e de avaliação , 153
201 atributos métodos, 67–69, 158
técnica call-super, 194, 196
Métodos Singleton, 158 método
silenciosos , 49
Métodos Hook, 158–162 de substituição , 15
questionários
method_missing method, 56– requerem aliases
64 sobre, xix blocos, 75–77
de método, 133
matemática quebrada, 136
Namespaces e, 26 segurança,
P atributos
150 método
verificados, 150–157, 160
Padrino exemplo, 86 require_with_record , 134 método
linguagens específicas de
Classe de parágrafo , 113 require_without_record ,
domínio (DSLs), 98–103 method_missing method, 64–
parâmetros e parênteses, 66 134
xx
modelo de objeto, 26–27, 39– métodos reservados, 68
parênteses, xx, 217 42
classe ResourceProxy , 58
Pascal maiúsculas e classes singleton, 129–
recursos
131
minúsculas, 25 caminhos de method_missing method, 70 Rails,
Tabu, 112
constantes, 22 padrões, 168 Ruby,
xxii
veja feitiços método perform_validations ,
respond_to? método, 62, 69, 202
194 acessadores
de cache de desempenho, 207 respond_to_missing? método, 62,
Métodos fantasmas, 202 69
Machine Translated by Google
Índice • 250
Exemplo de cliente REST, 141– classes singleton, 120, argumento de declarações , vinculação
143 127 de objetos, 144
palavra-chave de retorno , 92 usando explicitamente com como campos estáticos, 109–110
atributos de assinatura, 220
Exemplo de aula de roleta , 64–66 linguagens estáticas, 45, 70
Auto Rendimento, 222–224, 241
Rubinius, xxi verificação de tipo estático, 45
enviar método, 48–49, 51
Classe de string
Vantagens do Ruby, xvii, 8 __send__ method, 68 Métodos Singleton, 113–114
tempo de compilação/tempo de set_attribute method, 220
execução, 8 criação de set_name_cache method, 210 setups, método to_alfanumérico , 12,
linguagens específicas, xvii 37
domain-specific lan guages (DSLs), 98
Grande Teoria Unificada, 125 strings
sintaxe, xx, 229 convertendo exemplo, 12–16, 36
tutorial, versões Escopos compartilhados, 83, 101, 241
xxii , xxi variável compartilhada , 84
convertendo símbolos para, 49
tempo de execução, 8, 234 compartilhamento de dados entre Métodos Singleton, 113–
eventos, 98, 102 114
S atributos de classe de substituição, 142
Variável global $SAFE , 148 níveis classes singleton, 127–129 ganchos, Strings de métodos
seguros, 148–150 variável 160 método de de atributo de código, 205, 209
inclusão , 130 herança, vs.
de instância @safe_level , 149
123–125 instance_eval, 127 blocos, 145–150 atributos
pesquisa de método, verificados, 152 desvantagens,
Caixas de areia, 149, 240
118–122 nomes, 122 convenções método de avaliação 145–150 ,
salvar método, 172, 192, 194 de notação, xx livro de feitiços 141–157 ,
salvar! método, 172, 192, 194 regras de modelo de objeto, 126 substituição 242 ,
ligações questionário, 129–131 de classes
string, 142 super palavra-chave
de escopo, 78–84, 143 singleton, 126 livro
método
palavras-chave de classe , de feitiços, 232 superclasses, 122,
append_features ,
108 preocupações, 126
187
186 constantes, 21 ganchos e, 159
definidas, 73 Definições de classe de substituir e chamar super
nivelamento, 82–84, 98, 108, Métodos Singleton, 113–118
técnica, 194, 196
235 Refinamentos, 135
Objetos de método , 94 convertendo para objetos validações, 194
procs e lambdas, 92 removendo Method , 94
métodos de superclasse , 19
o método eval , hooks, 158
153 instance_eval, 127 superclasses
lookup, 118–122 spell Classe base (Active Record), 172
Scope Gates, 81–84, 241
Escopos Compartilhados, 83, 101, book, 242 view, 118,
241 herança, 19, 27, 123– 125 regras
120 singleton_class
de
classes singleton, 120 variáveis method, 120 singleton_method
e, 80 modelo de objeto, 126 classes
method, 94 singleton_method_added
singleton, 122– 126
Scope Gates, 81–84, 241 segurança,
method, 158
146–150, 153 definições de
Símbolo para Proc, 224, 242
classe de palavra- método singleton_method_removed ,
símbolos, 49, 224, 242 atributos
chave self e, 36, 106 158
de
método sintaxe, 217 métodos
preocupações, 186
variáveis de instância, 34, 80, de classe, 126 constantes,
86 21 convenções, xx
Índice • 251
T U configurações, 99
VBA, 228
linguagem TADS, 228 objetos Objetos UnboundMethod , 94–96, 207–
210 Exemplo de videocassete, 159, 180
contaminados, 148–150
contaminados? método, 148 método undef_method , 67 testes versões
Trilhos, 168
método de toque , 223 de unidade, xx, 111, 169
Rubi, xxi
método de destino , 191 UNIX, 228
visibilidade, aninhada, 79
Classe de tarefa , métodos de atributo, comando descompactar , 168
200–212 método untaint , 149 C
sequência de terminação, 142 teste de usando a palavra-chave, 75– wrappers
quebra 77 usando o método, 37 sobre, xvii
de encapsulamento, alias_method_chain, 191–197
85 V método, 131–136
unit, xx, 111, 169 Thor válido? método, 175, 180, 193 Wrappers pré-anexados, 135,
exemplo, 133 Time class, validar método, 172, 175 195, 239
Embalagens de Refinamento,
testing e, 111 title? método, 113 Módulo de validações (ativo
135, 240
Record)
acessadores de gravação, 117, 200,
alias_method_chain, 189–197
método to_alphanumeric , 12, 37 método 210
métodos de localização, 175
to_money , 14 método Classe WriterMethodCache , 210
truque de incluir e estender, 179–
to_proc , 224, 242 contexto de 188
nível superior, 36 variáveis variáveis, veja também variáveis de
Y
de instância de nível superior, 79–80, instância Yacc, 228
100, 102 variáveis de classe, 110, 184 blocos de definição de
143, 149 globais, 80, 101 atributos de objeto, Auto Rendimento, 222–224, 241
220
destroços de trem, 223
Scope Gates, 82 Z
árvore, constantes, 22
palavras-chave próprias , 80, 86 Zork, 227
verificação de tipo, 45
Machine Translated by Google
José Valim
(200 páginas) ISBN: 9781937785550. $ 36
http://pragprog.com/book/jvrails2
Jim R Wilson
(148 páginas) ISBN: 9781937785734. $ 17 http://
pragprog.com/book/jwnode
Machine Translated by Google
O Elixir coloca a “diversão” de volta na programação funcional, além do ambiente robusto, testado em
batalha e de força industrial de Erlang.
Elixir de Programação
Você quer explorar a programação funcional, mas é
desencorajado pela sensação acadêmica (fale-me sobre
mônadas só mais uma vez). Você sabe que precisa de
aplicativos simultâneos, mas também sabe que é quase
impossível acertar. Conheça o Elixir, uma linguagem simultânea
e funcional construída na sólida VM Erlang. A sintaxe
pragmática do Elixir e o suporte integrado para metaprogramação
o tornarão produtivo e o manterão interessado por muito tempo.
Este livro é a introdução ao Elixir para programadores
experientes.
Dave Thomas
(240 páginas) ISBN: 9781937785581. $ 36
http://pragprog.com/book/elixir
Joe Armstrong
(548 páginas) ISBN: 9781937785536. $ 42
http://pragprog.com/book/jaerlang2
Machine Translated by Google
Cada nova versão da Web traz sua própria corrida do ouro. Aqui estão suas ferramentas.
HTML5 e CSS3 são mais do que apenas chavões — eles são a base
para os aplicativos da web de hoje.
Este livro coloca você a par dos elementos HTML5 e recursos CSS3 que
você pode usar agora mesmo em seus projetos atuais, com soluções
compatíveis com versões anteriores que garantem que você não deixe
para trás os usuários de navegadores mais antigos. Esta nova edição
abrange ainda mais recursos novos
Brian P. Hogan
(300 páginas) ISBN: 9781937785598. $ 38
http://pragprog.com/book/bhh52e
Explore!
Descubra surpresas, riscos e bugs potencialmente sérios com
testes exploratórios. Em vez de projetar todos os testes com
antecedência, os exploradores projetam e executam experimentos
pequenos e rápidos, usando o que aprenderam com o último
pequeno experimento para informar o próximo. Aprenda as
habilidades essenciais de um explorador mestre, incluindo
como analisar software para descobrir pontos-chave de
vulnerabilidade, como projetar experimentos em tempo real,
como aprimorar suas habilidades de observação e como concentrar seus esforços.
Elisabeth Hendrickson
(160 páginas) ISBN: 9781937785024. $ 29
http://pragprog.com/book/ehxta
O Livro Do Pepino
Seus clientes querem um software sólido e livre de bugs que faça
exatamente o que eles esperam que ele faça. No entanto, eles
nem sempre conseguem articular suas ideias com clareza
suficiente para que você as transforme em código. O Livro do
Pepino mergulha direto no cerne do problema: a comunicação
entre as pessoas. Pepino salva o dia; é uma ferramenta de
teste, comunicação e requisitos – tudo em um só.
A estante pragmática
O Pragmatic Bookshelf apresenta livros escritos por desenvolvedores para desenvolvedores. Os títulos continuam o conhecido
estilo Pragmatic Programmer e continuam a receber prêmios e ótimas críticas. À medida que o desenvolvimento fica cada vez
mais difícil, os programadores pragmáticos estarão lá com mais títulos e produtos para ajudá-lo a ficar no topo do seu jogo.
Visite-nos on-line
A página inicial deste
livro http://pragprog.com/
book/ppmetr2 Código-fonte deste livro, errata e outros recursos. Venha nos dar feedback também!
Registre-se para
atualizações http://pragprog.com/
updates Seja notificado quando atualizações e novos livros estiverem disponíveis.
Junte-se à comunidade
http://pragprog.com/
community Leia nossos weblogs, participe de nossas discussões on-line, participe de nossa lista de e-
mails, interaja com nosso wiki e beneficie-se da experiência de outros programadores pragmáticos.
Compre o livro
Se você gostou deste eBook, talvez queira ter uma cópia impressa do livro. Está disponível para compra
em nossa loja: http://pragprog.com/book/ppmetr2
Contate-nos
Pedidos on-line: http://pragprog.com/catalog
Ou chamar: +1 800-699-7764