0% acharam este documento útil (0 voto)
34 visualizações262 páginas

Metaprogramming Ruby, 2nd EditionTL

Enviado por

dicarvalho.ti
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
0% acharam este documento útil (0 voto)
34 visualizações262 páginas

Metaprogramming Ruby, 2nd EditionTL

Enviado por

dicarvalho.ti
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
Você está na página 1/ 262

Machine Translated by Google

www.allitebooks.com
Machine Translated by Google

www.allitebooks.com
Machine Translated by Google

O que os leitores estão dizendo sobre


Metaprogramação Ruby 2

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

Engenheiro de software líder, Gilt City

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

Se você deseja seguir o caminho do domínio da metaprogramação Ruby, este livro é o


melhor companheiro que você pode imaginar, não importa qual seja o seu nível.
Lutei com a metaprogramação Ruby por anos até ler este livro; agora tudo faz sentido.

ÿ Fabien Catteau

Desenvolvedor de software, Tech-Angels

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

Metaprogramming Ruby foi um livro extremamente influente para mim, especialmente


durante uma época em que eu queria aprender sobre o funcionamento interno do Ruby.
Paolo “Nusco” Perrotta tornou o que normalmente é um tópico complexo divertido, agradável e
muito acessível.
ÿ Josh Kalderimis
CEO, Travis CI

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.

A equipe que produziu este livro inclui:

Lynn Beighley (editora)


Potomac Indexing, LLC (indexador)
Cathleen Small (editora de texto)
Dave Thomas (compositor)
Janet Furlow (produtora)
Ellie Callahan (suporte)

Para direitos internacionais, entre em contato com [email protected].

Copyright © 2014 The Pragmatic Programmers, LLC.


Todos os direitos reservados.

Nenhuma parte desta publicação pode ser reproduzida, armazenada em um sistema de


recuperação ou transmitida, de qualquer forma ou por qualquer meio, eletrônico, mecânico,
fotocópia, gravação ou outro, sem o consentimento prévio do editor.

Impresso nos Estados Unidos da América.


ISBN-13: 978-1-94122-212-6

Codificado usando os melhores dígitos binários de alta entropia sem ácido.


Versão do livro: P1.0 - agosto de 2014

www.allitebooks.com
Machine Translated by Google

Eu tinha treze anos e estava cansado de ficar na


loja de brinquedos local para jogar Intellivision. Eu
queria meu próprio console de videogame.
Eu estava incomodando meus pais por um
tempo, sem sucesso.

Então encontrei uma alternativa: eu poderia


jogar no computador também. Então, pedi a meus
pais que me comprassem um daqueles novos
computadores de 8 bits — sabe, para aprender coisas úteis.
Meu pai concordou e minha mãe me levou à loja e
comprou um Sinclair ZX Spectrum.

Mãe, pai... Aqui está algo que eu deveria ter dito a


vocês mais vezes na minha vida: obrigado. Este livro
é dedicado a vocês dois. Espero que isso o deixe
orgulhoso, assim como seu filho está orgulhoso de
você. E enquanto estou aqui, tenho algo a
confessar sobre aquele dia que mudou minha vida
trinta anos atrás: eu realmente não queria aprender
coisas. Eu só queria jogar.

Na verdade, é isso que tenho feito todos


esses anos.

www.allitebooks.com
Machine Translated by Google

Conteúdo

Prefácio . . . . . . . . . . . . . xiii

Agradecimentos . . . . . . . . . . . xv

Introdução . . . . . . . . . . . . xvii

Parte I — Metaprogramação Ruby


1. A Palavra M .............3
Cidades fantasmas e mercados 3
A História de Bob, Metaprogramador 4
Metaprogramação e Ruby 7

2. Segunda-feira: O Modelo de Objeto . . . . . . . . . 11


Classes abertas 11
dentro do modelo de objeto 16
Questionário: linhas 26
ausentes O que acontece quando você chama um método? 27
Questionário: Conclusão do 39
Emaranhado de Módulos 42

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

Blocos são fechamentos 77


instância_eval() 85
Objetos chamáveis 88
Como escrever uma linguagem específica de domínio 96
Questionário: Uma DSL melhor 98
Embrulhar 103

5. Quinta-feira: Definições de aula . . . . . . . . 105


Definições de classe desmistificadas 106
Questionário: tabu de classe 112
Métodos Singleton 113
Aulas individuais 118
Questionário: Problema no Módulo 129
Wrappers de método 131
Questionário: matemática quebrada 136
Embrulhar 137

6. Sexta-feira: Código que escreve código . . . . . . . . 139


Codificando seu caminho para o Kernel de fim 139
de semana#eval 141
Questionário: Atributos verificados (Etapa 1) 150
Questionário: Atributos verificados (Etapa 2) 153
Questionário: Atributos verificados (Etapa 3) 154
Questionário: Atributos verificados (Etapa 4) 156
Métodos de Gancho 157
Questionário: Atributos verificados (Etapa 5) 160
Embrulhar 162

7. Epílogo . . . . . . . . . . . . . 163

Parte II — Metaprogramação em Rails

8. Preparando-se para um Tour Rails . . . . . . . . . 167


Ruby on Rails 168
Instalando o 168
Rails O código-fonte do Rails 168

9. O Design do Registro Ativo . . . . . . . . 171


Um breve exemplo de registro ativo 171
Como o registro ativo é organizado 172
Uma lição aprendida 176

www.allitebooks.com
Machine Translated by Google

Conteúdo • xi

10. Módulo Preocupação do Suporte Ativo . . . . . . . 179


Trilhos antes da preocupação 179
Suporte Ativo::Preocupação 183
Uma lição aprendida 188

11. A Ascensão e Queda do alias_method_chain . . . . . 189


A ascensão de alias_method_chain A 189
queda de alias_method_chain Uma 193
lição aprendida 196

12. A evolução dos métodos de atributo . . . . . . . 199


Métodos de atributo em ação 199
Uma história de complexidade 200
Uma lição aprendida 210

13. Uma última lição . . . . . . . . . . . 213


Metaprogramação é apenas programação 213

Parte III - Apêndices


A1. Idiomas Comuns . . . . . . . . . . . 217
Métodos de imitação 217
Guardas Nil 219
Auto Rendimento 222
Symbol#to_proc() 224

A2. Linguagens Específicas de . . . . . . . . 227


Domínio O Caso para Linguagens Específicas 227
de Domínio DSLs Internas e Externas 229
DSLs e Metaprogramação 230

A3. Livro de feitiços . . . . . . . . . . . . . 231


os feitiços 231

Í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.

Aproveite a programação em Ruby.

matz

relatar errata • discutir


Machine Translated by Google

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, leitores que deram feedback e relataram erratas. Obrigado, colaboradores


do código-fonte aberto que mostro neste livro.

Obrigado, Jim Weirich. Devemos muito a você.

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.

relatar errata • discutir


Machine Translated by Google

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.

Na verdade, longe de ser um conceito abstrato ou um pouco de discurso de marketing, a


metaprogramação é uma coleção de técnicas de codificação pragmáticas e realistas. Não soa
apenas legal; é legal . Aqui estão algumas coisas que você pode fazer com a metaprogramação
na linguagem Ruby:

• 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

relatar errata • discutir


Machine Translated by Google

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.

Sobre este livro


Parte I, Metaprogramming Ruby, é o núcleo do livro. O Capítulo 1, The M Word, na página 3,
mostra a ideia básica por trás da metaprogramação. Os capítulos a seguir contam a história de
uma semana na vida de um programador Ruby recém-contratado e seu colega mais experiente:

• O modelo de objeto de Ruby é a terra onde vive a metaprogramação. Capítulo 2, segunda-


feira: O modelo de objeto, na página 11, fornece um mapa para esta terra.
Este capítulo apresenta as técnicas de metaprogramação mais básicas. Ele também
revela os segredos por trás das classes Ruby e pesquisa de métodos, o processo pelo
qual Ruby encontra e executa métodos.

• 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

relatar errata • discutir


Machine Translated by Google

Sobre este livro • xix

capítulos anteriores. O capítulo também completa seu treinamento de metaprogramação


com dois novos tópicos: o método eval , um tanto controverso , e os métodos de retorno
de chamada que você pode usar para interceptar eventos no modelo de objeto.

A Parte II do livro, Metaprogramming in Rails, é um estudo de caso em metaprogramação.


Ele contém capítulos curtos que enfocam diferentes áreas do Rails, o principal framework
Ruby. Olhando para o código fonte do Rails, você verá como os programadores Ruby
mestres usam a metaprogramação no mundo real para desenvolver um ótimo software, e
você também entenderá como algumas técnicas de metaprogramação evoluíram nos últimos
anos.

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.

relatar errata • discutir


Machine Translated by Google

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:

puts 'Testando... testando...'

ÿ 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?

relatar errata • discutir


Machine Translated by Google

Sobre este livro • xxi

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.

Os capítulos da Parte II usam o código-fonte do Rails como fonte de exemplos.


Rails mudou muito desde a primeira edição, então esses capítulos são quase uma
reescrita completa do conteúdo da primeira edição.

1. http://www.ruby-lang.org
2. http://jruby.codehaus.org
3. http://rubini.us/

relatar errata • discutir


Machine Translated by Google

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

Você está a bordo, então? Ótimo! Vamos começar.

4. http://tryruby.org

relatar errata • discutir


Machine Translated by Google

Parte I

Metaprogramação Ruby

www.allitebooks.com
Machine Translated by Google

CAPÍTULO 1

A palavra M

Metaprogramação é escrever código que escreve código.

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.

Cidades fantasmas e mercados


Pense em seu código-fonte como um mundo repleto de cidadãos vibrantes: variáveis, classes,
métodos e assim por diante. Se você quiser ser técnico, pode chamar essas construções de
linguagem de cidadãos.

Em muitas linguagens de programação, as construções de linguagem se comportam mais como


fantasmas do que como cidadãos desenvolvidos: você pode vê-los em seu código-fonte, mas eles
desaparecem antes que o programa seja executado. Veja C++, por exemplo. Depois que o
compilador termina seu trabalho, coisas como variáveis e métodos perdem sua concretude; são
apenas locais na memória. Você não pode pedir a uma classe seus métodos de instância, porque
no momento em que você faz a pergunta, a classe desaparece. Em linguagens como C++, o
tempo de execução é um lugar estranhamente silencioso — uma cidade fantasma.

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.

Vamos observar a introspecção em ação. Dê uma olhada no código a seguir.

relatar errata • discutir


Machine Translated by Google

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.

meu_objeto.classe # => Saudação

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.

my_object.class.instance_methods(false) # => [:bem-vindo]

A classe respondeu com um array contendo um único nome de método: welcome.


(O argumento falso significa: “Liste apenas os métodos de instância que você mesmo
definiu, não aqueles que você herdou.”) Vamos dar uma olhada no próprio objeto, solicitando
suas variáveis de instância.

my_object.instance_variables # => [:@texto]

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 História de Bob, Metaprogramador


Bob, um recém-chegado ao Ruby, tem um grande plano: ele escreverá a maior rede social
da Internet para cinéfilos. Para fazer isso, ele precisa de um banco de dados de filmes e
resenhas de filmes. Bob torna uma prática escrever código reutilizável, então ele decide
construir uma biblioteca simples para manter objetos no banco de dados.

relatar errata • discutir


Machine Translated by Google

A História de Bob, Metaprogramador • 5

A primeira tentativa de Bob

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 inicializar(tabela, ident)


@table = table
@ident = ident
Database.sql "INSERT INTO #{@table} (id) VALUES (#{@ident})" end

def set(col, val)


Database.sql "UPDATE #{@table} SET #{col}='#{val}' WHERE id=#{@ident}" end

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:

classe Filme < Entidade


def inicializar (identidade)
super "filmes", ident end

título def
obter fim do
"título"

título def = (valor)


definir "título", valor final

relatar errata • discutir


Machine Translated by Google

Capítulo 1. A Palavra M • 6

diretor de definição
obter o fim do
"diretor"

diretor def =(valor)


definir "diretor", valor final

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 :

classe Filme < ActiveRecord::Base


fim

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"

relatar errata • discutir


Machine Translated by Google

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

Movie#director= fora do ar enquanto o programa é executado.

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.

A palavra “M” novamente

Agora você tem uma definição mais formal de metaprogramação:

Metaprogramação é escrever código que manipula construções de linguagem em tempo de execução.

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.

relatar errata • discutir


Machine Translated by Google

Capítulo 1. A Palavra M • 8

Geradores e Compiladores de Código

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.

Um programa escrito em C abrange dois mundos diferentes: tempo de compilação, onde


você tem construções de linguagem como variáveis e funções, e tempo de execução, onde
você só tem um monte de código de máquina. Como a maioria das informações do tempo
de compilação é perdida no tempo de execução, C não suporta metaprogramação ou
introspecção. Em C++, algumas construções de linguagem sobrevivem à compilação, e é
por isso que você pode solicitar a classe de um objeto C++. Em Java, a distinção entre
tempo de compilação e tempo de execução é ainda mais confusa. Você tem introspecção
suficiente à sua disposição para listar os métodos de uma classe ou escalar uma cadeia de
superclasses.

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.

Neste mundo, a metaprogramação está em toda parte. A metaprogramação de Ruby não é


uma arte obscura reservada para gurus, e não é um recurso poderoso que é útil apenas
para construir algo tão sofisticado quanto o Active Record.
Se você quiser seguir o caminho da codificação Ruby avançada, encontrará
metaprogramação em cada etapa. Mesmo se você estiver satisfeito com a quantidade de
Ruby que já conhece e usa, provavelmente ainda tropeçará na metaprogramação em

relatar errata • discutir


Machine Translated by Google

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.

relatar errata • discutir


Machine Translated by Google

CAPÍTULO 2

Segunda-feira: O modelo de objeto

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.

relatar errata • discutir


Machine Translated by Google

Capítulo 2. Segunda: O Modelo de Objeto • 12

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'

class ToAlphanumericTest < Test::Unit::TestCase def


test_strip_non_alphanumeric_characters assert_equal
'3 the Magic Number', to_alphanumeric('#3, the *Magic, Number*?') end end

“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

relatar errata • discutir


Machine Translated by Google

Aulas Abertas • 13

Bill também altera os chamadores para usar String#to_alphanumeric. Por exemplo, o


teste se torna o seguinte:

requer 'teste/unidade'

class StringExtensionsTest < Test::Unit::TestCase def


test_strip_non_alphanumeric_characters assert_equal '3
the Magic Number', '#3, the *Magic, Number*?'.to_alphanumeric end end

Para entender o truque anterior, você precisa saber uma ou duas coisas sobre classes
Ruby. Bill está muito feliz em ensiná-lo….

Dentro das Definições de Classe

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á.

www.allitebooks.com relatar errata • discutir


Machine Translated by Google

Capítulo 2. Segunda-feira: O Modelo de Objeto • 14

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"

barganha_preço = Money.from_numeric(99, "USD")


barganha_preço.format # => "$99,00"

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

É bastante comum que as bibliotecas usem Classes Abertas dessa maneira.

Por mais legais que sejam, no entanto, as Classes Abertas têm um lado sombrio - um que você está prestes a
experimentar.

relatar errata • discutir


Machine Translated by Google

Aulas Abertas • 15

O problema das aulas abertas


Você e Bill não precisam procurar muito antes de se depararem com outra oportunidade de usar as
Classes Abertas. A fonte Bookworm contém um método que substitui elementos em uma matriz:

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:

[].methods.grep /^re/ # => [:reverse_each, :reverse, ..., :replace, ...]

relatar errata • discutir


Machine Translated by Google

Capítulo 2. Segunda-feira: O Modelo de Objeto • 16

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.

Dentro do modelo de objeto


Onde você aprende fatos surpreendentes sobre objetos, classes e constantes.

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

Imagine executar este código:

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?

relatar errata • discutir


Machine Translated by Google

Dentro do Modelo de Objeto • 17

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]

Ao contrário de Java ou outras linguagens estáticas, em Ruby não há conexão entre a


classe de um objeto e suas variáveis de instância. Variáveis de instância simplesmente
passam a existir quando você atribui um valor a elas, então você pode ter objetos da
mesma classe que carregam diferentes variáveis de instância. Por exemplo, se você não
tivesse chamado obj.my_method, obj não teria nenhuma variável de instância .
Você pode pensar nos nomes e valores das variáveis de instância como chaves e valores
em um hash. Tanto as chaves quanto os valores podem ser diferentes para cada objeto.
Isso é tudo o que há para saber sobre variáveis de instância. Vamos aos métodos.

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

relatar errata • discutir


Machine Translated by Google

Capítulo 2. Segunda-feira: O Modelo de Objeto • 18

lista de métodos geralmente é bastante longa. Você pode usar Array#grep para verificar se my_method
está na lista de obj :

obj.methods.grep(/meu/) # => [:meu_método]

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.”

Figura 1—As variáveis de instância vivem em objetos; métodos vivem em classes.

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:

String.instance_methods == "abc".methods # => verdadeiro


String.methods == "abc".methods # => falso

relatar errata • discutir


Machine Translated by Google

Dentro do Modelo de Objeto • 19

Vamos resumir tudo: as variáveis de instância de um objeto residem no próprio objeto e os


métodos de um objeto residem na classe do objeto. É por isso que objetos da mesma classe
compartilham métodos, mas não compartilham variáveis de instância.

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.

A verdade sobre as aulas

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:

"olá".class # => String String.class


# => 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:

# O argumento "falso" aqui significa: ignorar métodos herdados


Class.instance_methods(false) # => [:allocate, :new, :superclass]

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:

relatar errata • discutir


Machine Translated by Google

Capítulo 2. Segunda-feira: O Modelo de Objeto • 20

Array.superclass # => Object Object.superclass


# => BasicObject BasicObject.superclass # => nil

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.)

Falando em superclasses, podemos nos fazer mais uma pergunta: o que é a


superclasse de Class?

Módulos
Respire fundo e confira a superclasse da própria classe Class :
Classe.superclasse # => Módulo

A superclasse de Class é Module — ou seja, toda classe também é um módulo.


Para ser preciso, uma classe é um módulo com três métodos de instância adicionais
(new, allocate e superclass) que permitem criar objetos ou organizar classes em
hierarquias.

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

relatar errata • discutir


Machine Translated by Google

Dentro do Modelo de Objeto • 21

Figura 2—Classes são apenas objetos.

"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.

relatar errata • discutir


Machine Translated by Google

Capítulo 2. Segunda-feira: O Modelo de Objeto • 22

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.

Todas as constantes em um programa são organizadas em uma árvore semelhante a um sistema


de arquivos, onde os módulos (e classes) são diretórios e as constantes regulares são arquivos.
Como em um sistema de arquivos, você pode ter vários arquivos com o mesmo nome, desde que
residam em diretórios diferentes. Você pode até se referir a uma constante por seu caminho, como
faria com um arquivo. Vamos ver como.

Os Caminhos das Constantes

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

M::C::X # => "uma constante"

Se você estiver bem dentro da árvore de constantes, poderá fornecer o caminho absoluto para uma
constante externa usando dois-pontos à esquerda como raiz:

relatar errata • discutir


Machine Translated by Google

Dentro do Modelo de Objeto • 23

Y = 'uma constante de nível raiz'

módulo M
Y = 'uma constante em M'
Y # => "uma constante em M" #
::Y => "uma constante no nível raiz"
fim

A classe Module também fornece um método de instância e um método de classe que,


confusamente, são chamados de constantes. Module#constants retorna todas as constantes
no escopo atual, como o comando ls do seu sistema de arquivos (ou comando dir , se você
estiver executando o Windows). Module.constants retorna todas as constantes de nível
superior no programa atual, incluindo nomes de classe:

M.constantes # => [:C, :Y]


Module.constants.include? :Object # => true
Module.constants.include? :Módulo # => verdadeiro

Por fim, se você precisar do caminho atual, confira Module.nesting:

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

www.allitebooks.com relatar errata • discutir


Machine Translated by Google

Capítulo 2. Segunda-feira: O Modelo de Objeto • 24

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.

Resumo de objetos e classes


O que é um objeto? É um monte de variáveis de instância, além de um link para uma classe.
Os métodos do objeto não residem no objeto — eles residem na classe do objeto, onde são
chamados de métodos de instância da classe.

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.”

relatar errata • discutir


Machine Translated by Google

Dentro do Modelo de Objeto • 25

Usando Espaços de Nome

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:

ÿ TypeError: Texto não é uma classe

“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

Você e Bill também alteram todas as referências a Text em referências a Bookworm::Text.


É improvável que uma biblioteca externa defina uma classe chamada Bookworm::Text, então você
deve estar a salvo de conflitos agora.

Foi muito aprendizado em uma única sessão. Você merece uma pausa e uma xícara de café - e um
pequeno teste.

relatar errata • discutir


Machine Translated by Google

Capítulo 2. Segunda-feira: O Modelo de Objeto • 26

Carregando e Requerendo

Falando em Namespaces (23), há um detalhe interessante que envolve Namespaces, constantes e os


métodos load e require do Ruby . Imagine encontrar um arquivo motd.rb na web que exibe uma “mensagem
do dia” no console. Você deseja adicionar este código ao seu programa mais recente, então carregue o
arquivo para executá-lo e exiba a mensagem:

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.

Questionário: Linhas que faltam

Onde você encontra seu caminho no modelo de objeto Ruby.

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:

relatar errata • discutir


Machine Translated by Google

O que acontece quando você chama um método? • 27

classe MinhaClasse;
end obj1 = MyClass.new
obj2 = MyClass.new

O diagrama mostra algumas das conexões entre as entidades do programa.


Agora é sua vez de adicionar mais linhas e caixas ao diagrama e responder a estas perguntas:

• Qual é a classe de Object? •


Qual é a superclasse de Module? • Qual
é a classe da classe?
• Imagine que você executa este código:

obj3 = MyClass.new
obj3.instance_variable_set("@x", 10)

Você pode adicionar obj3 ao diagrama?

Você pode usar o irb e a documentação do Ruby para descobrir as respostas.

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.

O que acontece quando você chama um método?


Onde você aprende que uma humilde chamada de método requer muito trabalho da parte do
Ruby e você lança luz sobre um pedaço de código distorcido.

Depois de algumas horas trabalhando no Bookworm, você e Bill já se sentem confiantes o


suficiente para consertar alguns pequenos bugs aqui e ali - mas agora, quando seu dia de
trabalho está chegando ao fim, você se encontra empacado. Tentando corrigir um bug de
longa data, você se deparou com um emaranhado de classes, módulos e métodos dos quais
não consegue entender.

relatar errata • discutir


Machine Translated by Google

Capítulo 2. Segunda-feira: O Modelo de Objeto • 28

Figura 3—Diagrama de Bill, aprimorado por você

"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.

Quando você chama um método, Ruby faz duas coisas:

1. Encontra o método. Este é um processo chamado pesquisa de método.


2. Executa o método. Para fazer isso, Ruby precisa de algo chamado self.

Esse processo - encontre um método e execute-o - acontece em todas as linguagens orientadas a


objetos. Em Ruby, no entanto, você deve entender o processo em profundidade, porque esse
conhecimento abrirá a porta para alguns truques poderosos.
Falaremos primeiro sobre pesquisa de métodos e depois falaremos sobre self .

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.

relatar errata • discutir


Machine Translated by Google

O que acontece quando você chama um método? • 29

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

class MinhaSubclasse < Fim de


MinhaClasse

obj = MySubclass.new
obj.my_method() # => "meu_metodo()"

Bill desenha este diagrama:

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

relatar errata • discutir


Machine Translated by Google

Capítulo 2. Segunda-feira: O Modelo de Objeto • 30

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.

MyClass não especifica uma superclasse, então herda implicitamente da


superclasse padrão: Object. Se não tivesse encontrado o método em MyClass,
Ruby procuraria o método subindo na cadeia até Object e finalmente BasicObject.
Devido à maneira como a maioria das pessoas desenha diagramas, esse comportamento
também é chamado de regra “um passo para a direita, depois para cima”: vá um passo para a
direita na classe do receptor e, em seguida, suba na cadeia de ancestrais até encontrar o método .
Você pode solicitar a uma classe sua cadeia de ancestrais com o método de ancestrais :

MySubclass.ancestors # => [MySubclass, MyClass, Object, Kernel, BasicObject]

“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

classe D < C; fim

D.ancestors # => [D, C, M1, Object, Kernel, BasicObject]

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

relatar errata • discutir


Machine Translated by Google

O que acontece quando você chama um método? • 31

o módulo abaixo da classe de inclusão (às vezes chamado de includer), em vez de acima dela:

classe C2
preceder M2
final

classe D2 < C2; fim

D2.ancestors # => [D2, M2, C2, Object, Kernel, BasicObject]

Bill desenha o fluxograma a seguir para mostrar como a inclusão e o prefixo funcionam.

Figura 4—Pesquisa de método com módulos

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.

relatar errata • discutir


Machine Translated by Google

Capítulo 2. Segunda: O Modelo de Objeto • 32

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

M3.ancestrais # => [M1, M3, M2]

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:

Kernel.private_instance_methods.grep(/^pr/) # => [:printf, :print, :proc]

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.

relatar errata • discutir


Machine Translated by Google

O que acontece quando você chama um método? • 33

O exemplo de impressão incrível


A gem awesome_print imprime objetos Ruby na tela com recuo, cor e outras sutilezas:

object_model/awesome_print_example.rb
requer "awesome_print"

local_time = {:city => "Roma", :now => Time.now } ap


local_time, :indent => 2

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 ?

www.allitebooks.com relatar errata • discutir


Machine Translated by Google

Capítulo 2. Segunda-feira: O Modelo de Objeto • 34

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.)

relatar errata • discutir


Machine Translated by Google

O que acontece quando você chama um método? • 35

O que realmente significa privado

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

def método_privado; fim fim

C.new.public_method

ÿ NoMethodError: método privado 'private_method' chamado [...]

Você pode fazer esse código funcionar removendo a palavra-chave self .

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

relatar errata • discutir


Machine Translated by Google

Capítulo 2. Segunda-feira: O Modelo de Objeto • 36

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.)

Definições de classe e self

Em uma definição de classe ou módulo (e fora de qualquer método), o papel de self é


assumido pela própria classe ou módulo.

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:

relatar errata • discutir


Machine Translated by Google

O que acontece quando você chama um método? • 37

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:

"meu *1º* refinamento!".to_alphanumeric

ÿ NoMethodError: método indefinido `to_alphanumeric' [...]

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:

"meu *1º* refinamento!".to_alphanumeric # => "meu 1º refinamento"

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

"minha_cadeia".reverse # => "gnirts_ym"

Os refinamentos são semelhantes aos Monkeypatches, mas não são globais. Um


refinamento está ativo em apenas dois locais: o próprio bloco de refinamento e o código inicial

relatar errata • discutir


Machine Translated by Google

Capítulo 2. Segunda: O Modelo de Objeto • 38

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

Olha esse código:

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

relatar errata • discutir


Machine Translated by Google

Questionário: Emaranhado de Módulos • 39

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:

ÿ aviso: Os refinamentos são experimentais e o


comportamento pode mudar em versões futuras do Ruby!

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ê ainda está considerando o poder e a responsabilidade de usar Refinamentos quando


Bill decide lançar um questionário para você.

Questionário: Emaranhado de Módulos

Onde você desvenda um fio retorcido de módulos, classes e objetos.

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

def prepare_cover # ...

fim
fim

relatar errata • discutir


Machine Translated by Google

Capítulo 2. Segunda-feira: O Modelo de Objeto • 40

Módulo Documento
def print_to_screen
prepare_cover
format_for_screen
print
end

def format_for_screen # ...

fim

impressão definitiva
# ...
fim
fim

livro de classe
Incluir Documento Incluir
Imprimível
# ...
fim

Outro arquivo de origem cria um livro e chama print_to_screen:

b = Livro.novo
b.print_to_screen

De acordo com o aplicativo de gerenciamento de bugs da empresa, há um problema com


este código: print_to_screen não está chamando o método de impressão correto . O relatório
de bug não fornece mais detalhes.

Você consegue adivinhar qual versão de impressão é chamada - aquela em Imprimível ou


aquela em Documento? Tente desenhar a cadeia de ancestrais no papel. Como você pode
corrigir rapidamente o código para que print_to_screen chame a outra versão de print ?

Solução do questionário

Você pode pedir ao próprio Ruby a cadeia de ancestrais de Book:

Book.ancestors # => [Livro, Imprimível, Documento, Objeto, Kernel, BasicObject]

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

relatar errata • discutir


Machine Translated by Google

Questionário: Emaranhado de Módulos • 41

Figura 5—A cadeia de ancestrais da classe Book

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.

Quando você chama b.print_to_screen, o objeto referenciado por b torna-se self e a


pesquisa de método começa. Ruby encontra o método print_to_screen em Document, e
esse método então chama outros métodos - incluindo print. Todos os métodos chamados
sem um receptor explícito são chamados em self, portanto, a pesquisa de método começa
mais uma vez em Book (classe de self ) e continua até encontrar um método chamado print.
A impressão mais baixa na cadeia é Printable#print, então é aquela que é chamada.

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

relatar errata • discutir


Machine Translated by Google

Capítulo 2. Segunda-feira: O Modelo de Objeto • 42

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:

• Um objeto é composto de várias variáveis de instância e um link para um


aula.

• Os métodos de um objeto residem na classe do objeto. (Do ponto de vista


da classe, eles são chamados de métodos de instância.)

• A própria classe é apenas um objeto da classe Class. O nome da classe é apenas uma constante.

• Class é uma subclasse de Module. Um módulo é basicamente um pacote de métodos.


Além disso, uma classe também pode ser instanciada (com new) ou hierarquizada (através de sua
superclasse).

• 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.

relatar errata • discutir


Machine Translated by Google

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ê chama um método, o receptor assume o papel de si mesmo.

• Quando você está definindo um módulo (ou uma classe), o módulo assume o papel de
auto.

• As variáveis de instância sempre são consideradas variáveis de instância de self.

• Qualquer método chamado sem um receptor explícito é considerado um método


de si mesmo.

• 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.

www.allitebooks.com relatar errata • discutir


Machine Translated by Google

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.

Linguagens dinâmicas, como Python e Ruby, não têm chamadas de método de


policiamento de compilador. Como consequência, você pode iniciar um programa que
chama talk_simple em um Lawyer e tudo funcionará bem — isto é, até que aquela linha
específica de código seja executada. Só então o Advogado reclama que não entende
aquela ligação.

Essa é uma vantagem importante da verificação de tipo estático: o compilador pode


identificar alguns de seus erros antes que o código seja executado. Essa proteção, no
entanto, tem um preço. As linguagens estáticas muitas vezes exigem que você escreva
muitos métodos tediosos e repetitivos - os chamados métodos padronizados - apenas
para deixar o compilador feliz. (Por exemplo, obter e definir métodos para acessar as
propriedades de um objeto ou dezenas de métodos que não fazem nada além de delegar
a algum outro objeto.)

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.

relatar errata • discutir


Machine Translated by Google

Capítulo 3. Terça: Métodos • 46

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.

relatar errata • discutir


Machine Translated by Google

Um problema de duplicação • 47

Duplo, Agudo... Problema


Você precisa agrupar o DS em um objeto que se encaixe no aplicativo de geração de
relatórios. Isso significa que cada Computador deve ser um objeto. Este objeto possui um
único método para cada componente, retornando uma string que descreve tanto o
componente quanto seu preço. Lembra daquele limite de preço definido pelo departamento de compras?
Tendo esse requisito em mente, você sabe que, se o componente custar US$ 100 ou mais,
a string deve começar com um asterisco para chamar a atenção das pessoas.

Você inicia o desenvolvimento escrevendo os três primeiros métodos na classe Computer :

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

Neste ponto do desenvolvimento de Computer, você se encontra atolado em um pântano


de copiar e colar repetitivos. Você tem uma longa lista de métodos

relatar errata • discutir


Machine Translated by Google

Capítulo 3. Terça: Métodos • 48

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.”

Chamando métodos dinamicamente


Quando você chama um método, geralmente o faz usando a notação de ponto padrão:

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

relatar errata • discutir


Machine Translated by Google

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.

Nomes e símbolos do método


As pessoas que são novas na linguagem às vezes ficam confusas com os símbolos do Ruby.
Símbolos e strings pertencem a duas classes separadas e não relacionadas:

:x.class # => Símbolo


"x".class # => String

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:

# em vez de: 1.send("+", 2) 1.send(:+,


2) # => 3

Independentemente disso, você pode converter facilmente de string para símbolo e vice-versa:

"abc".to_sym #=> :abc :abc.to_s


#=> "abc"

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

relatar errata • discutir


Machine Translated by Google

Capítulo 3. Terça: Métodos • 50

Para cada método de instância como Pry#memory_size, existe um método de classe


correspondente (Pry.memory_size) que retorna o valor padrão do atributo:

Pry.memory_size # => 100

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(:memory_size => 99, :quiet => false)


pry.memory_size # => 99 pry.quiet #
=> false

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]

defaults[:quiet] = Pry.quiet self.quiet


= options[:quiet] if options[:quiet] # o mesmo para todos os
outros atributos...
fim

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 =

[ :input, :output, :commands, :print, :quiet, :exception_handler, :hooks, :custom_completions, :prompt, :

atributos.cada um faz |atributo|


defaults[attribute] = Fim do atributo Pry.send

# ...
defaults.merge!(opções).cada um faz |chave, valor|
send("#{key}=", valor) if respond_to?("#{key}=") end

verdadeiro

fim

relatar errata • discutir


Machine Translated by Google

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.

Definindo métodos dinamicamente


Você pode definir um método no local com Module#define_method. Você só precisa fornecer um
nome de método e um bloco, que se torna o corpo do método:

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

require_relative '../test/assertions' assert_equals


6, obj.my_method(2)

define_method é executado dentro de MyClass, então my_method é definido como um método de


instância de MyClass. Essa técnica de definir um método em tempo de execução é chamada de
Método Dinâmico. Feitiço: Dinâmico
Método

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

relatar errata • discutir


Machine Translated by Google

Capítulo 3. Terça: Métodos • 52

em tempo de execução. Para ver um exemplo dessa técnica, volte ao seu problema de refatoração
original.

Refatorando a classe Computer


Lembre-se do código que colocou você e Bill nesta discussão dinâmica:

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.

Passo 1: Adicionando Despachos Dinâmicos

Você e Bill começam extraindo o código duplicado em seu próprio método de envio de mensagem:

relatar errata • discutir


Machine Translated by Google

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 ÿ

ÿ def component(name) ÿ info


= @data_source.send "get_#{name}_info", @id ÿ price =
@data_source.send "get_#{name}_price", @id ÿ result = "#{name.
capitalize}: #{info} ($#{price})" ÿ return "* #{result}" if price >= 100 ÿ result ÿ
end 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:

my_computer = Computer.new(42, DS.new)


my_computer.cpu # => * CPU: 2,16 Ghz ($220)

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.

Etapa 2: Gerando métodos dinamicamente


Você e Bill refatoram Computer para usar Métodos dinâmicos (51), conforme mostrado
no código a seguir.

relatar errata • discutir


Machine Translated by Google

Capítulo 3. Terça: Métodos • 54

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.

Etapa 3: polvilhando o código com introspecção O

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

www.allitebooks.com relatar errata • discutir


Machine Translated by Google

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?

Vamos tentar de novo


Sua refatoração foi um sucesso retumbante, mas Bill não quer parar por aqui.
“Dissemos que íamos tentar duas soluções diferentes para esse problema, lembra? Encontramos
apenas um, envolvendo Despacho Dinâmico (49) e Métodos Dinâmicos (51). Isso nos serviu bem,
mas, para ser justo, precisamos dar uma chance à outra solução.”

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

ÿ NoMethodError: método indefinido `talk_simple' para #<Lawyer:0x007f801aa81938>

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

relatar errata • discutir


Machine Translated by Google

Capítulo 3. Terça: Métodos • 56

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:

nick.send :method_missing, :my_method

ÿ NoMethodError: método indefinido `my_method' para #<Lawyer:0x007f801b0f4978>

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 ).

Substituindo method_missing Muito

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

ÿ Você ligou: talk_simple(a, b)


(Você também passou um bloco)

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”.

relatar errata • discutir


Machine Translated by Google

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
"="

self[match[1]] = args.first # ...

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.)

relatar errata • discutir


Machine Translated by Google

Capítulo 3. Terça: Métodos • 58

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"

gh = Ghee.basic_auth("usr", "pwd") # Seu nome de usuário e senha do GitHub all_gists =


gh.users("nusco").gists a_gist = all_gists[20]

a_gist.url # => "https://api.github.com/gists/535077"


a_gist.description # => "Soletrar: Proxy Dinâmico"

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 # ...

def method_missing(message, *args, &block)


subject.send(message, *args, &block) end

1. http://www.github.com

relatar errata • discutir


Machine Translated by Google

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

Quando você chama um método que altera o estado de um objeto, como


Ghee::API::Gists#star, Ghee faz uma chamada HTTP para a URL do GitHub correspondente.
No entanto, quando você chama um método que apenas lê um atributo, como url ou
descrição, essa chamada termina em Ghee::ResourceProxy#method_missing. Por sua
vez, method_missing encaminha a chamada para o objeto retornado por
Ghee::ResourceProxy#sub ject. Que tipo de objeto é esse?

Se você se aprofundar na implementação de ResourceProxy#subject, descobrirá que


esse método também faz uma chamada HTTP para a API do GitHub. A chamada
específica depende de qual subclasse de Ghee::ResourceProxy estamos usando. Por
exemplo, Ghee::API::Gists::Proxy chama https://api.github.com/users/nusco/gists.
ResourceProxy#subject recebe o objeto GitHub no formato JSON — em nosso exemplo,
todas as essências do usuário nusco — e o converte em um objeto do tipo hash.

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 .

relatar errata • discutir


Machine Translated by Google

Capítulo 3. Terça: Métodos • 60

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.

Um objeto como Ghee::ResourceProxy, que captura métodos fantasmas e para Spell:


Dynamic Proxy os direciona para outro objeto, é chamado de Dynamic Proxy.

Refatorando a classe Computer (novamente)


"Ok, agora você sabe sobre method_missing", diz Bill. “Vamos voltar para a aula de
Computador e remover a duplicação.”

Mais uma vez, aqui está a classe Computer original:

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

relatar errata • discutir


Machine Translated by Google

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

O computador é apenas um wrapper que coleta chamadas, as ajusta um pouco e as


encaminha para uma fonte de dados. Para remover todos esses métodos duplicados, você
pode transformar o computador em um proxy dinâmico. Leva apenas uma substituição de
method_missing para remover toda a duplicação da classe Computer .

method/computer/method_missing.rb
classe Computador
def initialize(computer_id, data_source) @id =
computer_id
@data_source = data_source end

ÿ def method_missing(name) ÿ super


if !@data_source.respond_to?("get_#{name}_info") ÿ info =
@data_source.send("get_#{name}_info", @id) ÿ price =
@data_source .send("get_#{name}_price", @id) ÿ result = "#{name.capitalize}:
#{info} ($#{price})" ÿ return "* #{result}" if price > = 100 ÿ resultado ÿ fim

fim

O que acontece quando você chama um método como Computer#mouse? A chamada


é roteada para method_missing, que verifica se a fonte de dados encapsulada tem um
método get_mouse_info . Se não tiver um, a chamada volta para BasicOb
ject#method_missing, que lança um NoMethodError. Se a fonte de dados souber sobre
o componente, a chamada original será convertida em duas chamadas para
DS#get_mouse_info e DS#get_mouse_price. Os valores retornados dessas chamadas
são usados para construir o resultado final. Você tenta a nova classe no irb:

relatar errata • discutir


Machine Translated by Google

Capítulo 3. Terça: Métodos • 62

my_computer = Computer.new(42, DS.new)


my_computer.cpu # => * CPU: 2,9 Ghz quad-core ($120)

Funcionou. Bill, porém, está preocupado com um último detalhe.

responder_to_missing?
Se você perguntar especificamente a um computador se ele responde a um método fantasma, ele
mentirá descaradamente:

cmp = Computer.new(0, DS.new)


cmp.respond_to?(:mouse) # => falso

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.

responder_a? chama um método chamado respond_to_missing? que deve retornar true


se um método for um método fantasma. (Em sua mente, você poderia renomear re
spond_to_missing? para algo como ghost_method?.) Para evitar respond_to? de mentir,
substituir respond_to_missing? toda vez que você substituir method_missing:

classe Computador
#...

ÿ def respond_to_missing?(método, include_private = false) ÿ


@data_source.respond_to?("get_#{method}_info") || super ÿ fim

fim

O código neste respond_to_missing? é semelhante à primeira linha de method_missing: descobre se


um método é um Ghost Method. Se for, retorna verdadeiro. Se não for, chama super. Nesse caso,
super é o padrão Object#respond_to_missing?, que sempre retorna false.

Agora respond_to? aprenderá sobre seus métodos fantasmas de respond_to_missing? e retorne o


resultado correto:

cmp.respond_to?(:mouse) # => verdadeiro

Antigamente, os codificadores Ruby costumavam sobrescrever respond_to? diretamente. Agora que


respond_to_missing? está disponível, substituindo respond_to? é considerado um tanto sujo. Em vez
disso, a regra agora é esta: lembre-se de substituir respond_to_missing? toda vez que você substituir
method_missing.

Se você gosta de BasicObject#method_missing, também deve dar uma olhada em Module#con


st_missing. Vamos dar uma olhada.

relatar errata • discutir


Machine Translated by Google

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

ÿ AVISO: Referência obsoleta à constante de nível superior 'Task' encontrada [...]


Use --classic-namespace no comando rake ou 'require "rake/
classic_namespace"' no Rakefile

Após o aviso, você obteve automaticamente o novo nome de classe Namespaced no lugar do
antigo:

task_class # => Rake::Tarefa

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

relatar errata • discutir


Machine Translated by Google

Capítulo 3. Terça: Métodos • 64

Métodos (51) e Despachos Dinâmicos (49), que delegam ao sistema legado.


A segunda versão de Computer faz o mesmo com Ghost Methods (57). Tendo que escolher
uma das duas versões, você e Bill selecionam aleatoriamente a baseada em method_miss
ing, enviam para o pessoal da compra e partem para uma merecida pausa para o almoço... e
um teste inesperado.

Questionário: caça aos insetos

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

número = rand(10) + 1 puts


"#{número}..." end

"#{pessoa} obteve um #{número}"


end
fim

Você pode usar a Roleta assim:

number_of = Roulette.new coloca


number_of.bob coloca
number_of.frank

E aqui está como o resultado deve ser:

ÿ 5...
6...
10...
Bob tirou 3 7...

4...
3...
Frank tirou 10

relatar errata • discutir


Machine Translated by Google

Questionário: Caça aos Insetos • 65

“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.

Em circunstâncias normais, você obteria um NoMethodError explícito que torna o problema


óbvio. Mas neste caso você tem um method_missing, e é aí que a chamada para number
termina. A mesma cadeia de eventos acontece de novo - e de novo e de novo - até que a pilha
de chamadas transborde.

Este é um problema comum com métodos fantasmas: como chamadas desconhecidas se


tornam chamadas para method_missing, seu objeto pode aceitar uma chamada que está
simplesmente errada. Encontrar um bug como este em um programa grande pode ser bastante doloroso.

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

relatar errata • discutir


Machine Translated by Google

Capítulo 3. Terça: Métodos • 66

3. vezes faça
número = rand(10) + 1 puts
"#{número}..." end "#{pessoa}

obteve um #{número}" end

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.

Você tenta o método de exibição no irb e, com certeza, ele falha:

meu_computador = Computer.new(42, DS.new)


meu_computador.display # => nil

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:

Object.instance_methods.grep /^d/ # => [:dup, :display, :define_singleton_method]

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.

relatar errata • discutir


Machine Translated by Google

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 .

Herdar de BasicObject é a maneira mais rápida de definir um Blank Slate em Ruby.


Em alguns casos, no entanto, você pode querer controlar exatamente quais métodos
manter e quais métodos remover de sua classe. Vamos ver como você pode remover um
método específico de uma classe.

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'
}

Esse código produz o seguinte trecho de XML:


ÿ <coder>
<name nickname="Matz">Matsumoto</name>
<language>Ruby</language> </
coder>

relatar errata • discutir


Machine Translated by Google

Capítulo 3. Terça: Métodos • 68

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>

Então, você teria que escrever um código como este:

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
# ...

instance_methods.each { |m| ocultar(m) } fim

O Builder não chega a remover todo e qualquer método do BlankSlate.


Ele mantém instance_eval (um método que você conhecerá no próximo capítulo) e todos os “métodos
reservados” — métodos que são usados internamente pelo Ruby, cujos nomes convencionalmente começam
com um sublinhado duplo. Um exemplo de método reservado é BasicObject#__send__, que se comporta da
mesma forma que send , mas dá um aviso assustador quando você tenta removê-lo. O caso de instance_eval

relatar errata • discutir


Machine Translated by Google

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 .

Consertando a Classe de Computador

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:

ÿ classe Computador < 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

relatar errata • discutir


Machine Translated by Google

Capítulo 3. Terça: Métodos • 70

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

def method_missing(name, *args) super


if !@data_source.respond_to?("get_#{name}_info") info =
@data_source.send("get_#{name}_info", @id) preço =
@data_source. send("get_#{name}_price", @id) result =
"#{name.capitalize}: #{info} ($#{price})" return "* #{result}" if price
>= 100 result

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

um que você mais gosta?

Métodos Dinâmicos vs. Métodos Fantasma


Como você mesmo experimentou, os Métodos Fantasmas (57) podem ser perigosos. Você pode evitar a
maioria de seus problemas seguindo algumas recomendações básicas (sempre chamar super, sempre redefinir
respond_to_missing?) — mas , mesmo assim, os Métodos Fantasma às vezes podem causar bugs intrigantes.2

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.

2. Uma apresentação sobre os perigos do method_missing está em http://www.everytalk.tv/talks/1881-Madison


Ruby-The-Revenge-of-method-missing.

relatar errata • discutir


Machine Translated by Google

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.

Considerando tudo, a escolha entre Métodos Dinâmicos e Fantasmas depende de sua


experiência e estilo de codificação, mas você pode seguir uma regra simples em caso de
dúvida: use Métodos Dinâmicos se puder e Métodos Fantasma se for necessário.

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.

relatar errata • discutir


Machine Translated by Google

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.

O dia dos blocos


Onde você e Bill concordam em adiar o trabalho de hoje, fazer um roteiro e revisar os fundamentos
dos blocos.

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

relatar errata • discutir


Machine Translated by Google

Capítulo 4. Quarta-feira: Blocos • 74

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 revisão dos fundamentos dos blocos

• Uma visão geral dos escopos e como você pode transportar variáveis através dos escopos
usando blocos como fechamentos

• Como você pode manipular escopos passando um bloco para instance_eval

• 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.)

O básico dos blocos


Você se lembra como os blocos funcionam? Aqui está um exemplo simples para atualizar seu
memória:

blocks/
basics_failure.rb def
a_method(a, b) a
+ yield(a, b) end

a_método(1, 2) {|x, y| (x + y) * 3 } # => 10

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 .

Opcionalmente, um bloco pode ter argumentos, como x e y no exemplo anterior.


Quando você cede ao bloco, pode fornecer valores para seus argumentos, exatamente como faz
ao chamar um método. Além disso, como um método, um bloco retorna o resultado da última linha
de código que avalia.

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:

relatar errata • discutir


Machine Translated by Google

Questionário: Ruby# • 75

def a_method
retorna rendimento se block_dado? 'sem
bloqueio'
fim

a_method # => "sem bloqueio"


a_method { "aqui está um bloco!" } # => "aqui está um bloco!"

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#

Onde você é desafiado a fazer algo útil com blocos.

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

Imagine que você está escrevendo um programa C# que se conecta a um servidor


remoto e tem um objeto que representa a conexão:
RemoteConnection conn = new RemoteConnection("meu_servidor"); String stuff =
conn.ReadStuff(); conn.Dispose();
// fecha a conexão para evitar um vazamento

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ê:

RemoteConnection conn = new RemoteConnection("some_remote_server"); usando (conn) {

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.

relatar errata • discutir


Machine Translated by Google

Capítulo 4. Quarta-feira: Blocos • 76

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'

class TestUsing < Test::Unit::TestCase class


Resource
def descarte
@disposed =
verdadeiro fim

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
}

} afirmar r.disposed? fim

fim

Solução do questionário

Dê uma olhada nesta solução para o quiz:

blocos/
usando.rb kernel do módulo
def using(resource) start
yield

garantir

recurso.dispose fim

fim
fim

relatar errata • discutir


Machine Translated by Google

Blocos são fechamentos • 77

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.

Blocos são fechamentos


Onde você descobre que há mais blocos do que aparenta e aprende como contrabandear variáveis entre os
escopos.

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….

Figura 6 — O código executado é, na verdade, composto de duas coisas: o próprio código e um


conjunto de vinculações.

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:

relatar errata • discutir


Machine Translated by Google

Capítulo 4. Quarta-feira: Blocos • 78

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!

Devido às propriedades acima, um cientista da computação diria que um bloco é um fechamento.


Para o resto de nós, isso significa que um bloco captura as ligações locais e as carrega consigo.

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.

relatar errata • discutir


Machine Translated by Google

Blocos são fechamentos • 79

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

local_variables fim # => [:v2]

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.

No âmbito da definição de MyClass, o programa define v2 e um método.


O código do método ainda não foi executado, então o programa nunca abre um novo escopo
até o final da definição da classe. Lá, o escopo aberto com a palavra-chave class é fechado
e o programa volta ao escopo de nível superior.

relatar errata • discutir


Machine Translated by Google

Capítulo 4. Quarta-feira: Blocos • 80

Variáveis globais e variáveis de instância de nível superior

As variáveis globais podem ser acessadas por qualquer escopo:

def a_scope
$var = "algum valor" end

def outro_scope $var


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:

@var = "O @var de nível superior"

def meu_método
@var
fim

my_method # => "O @var de nível superior"

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.

O que acontece quando o programa cria um objeto MyClass e chama my_method


duas vezes? Na primeira vez que o programa insere my_method, ele abre um novo
escopo e define uma variável local, v3. Em seguida, o programa sai do método,
retornando ao escopo de nível superior. Neste ponto, o escopo do método é perdido.
Quando o programa chama my_method uma segunda vez, ele abre outro novo
escopo e define uma nova variável v3 (não relacionada à v3 anterior, que agora está perdida).
Por fim, o programa retorna ao escopo de nível superior, onde você pode ver v1 e
obj novamente. Ufa!

relatar errata • discutir


Machine Translated by Google

Blocos são fechamentos • 81

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.

(É por isso que eles são “locais”.)

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.

Há uma diferença sutil entre classe e módulo de um lado e def em

o outro. O código em uma definição de classe ou módulo é executado imediatamente.

relatar errata • discutir


Machine Translated by Google

Capítulo 4. Quarta-feira: Blocos • 82

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

# ..e aqui fim fim

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?

Se você examinar a documentação do Ruby, encontrará a resposta: Class.new é um substituto


perfeito para class. Você também pode definir métodos de instância na classe se passar um
bloco para Class.new:

blocks/flat_scope_2.rb
minha_var = "Sucesso"

ÿ MyClass = Class.new do ÿ # Agora


podemos imprimir my_var aqui... ÿ coloca "#{my_var} na
definição da classe!"

def my_method
# ...mas como podemos imprimi-lo aqui? fim

fim

relatar errata • discutir


Machine Translated by Google

Blocos são fechamentos • 83

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"

ÿ define_method :my_method do ÿ "#{my_var}


no método" ÿ end end

ÿ MyClass.new.my_method

require_relative "../test/assertions" assert_equals


"Sucesso no método", MyClass.new.my_method

ÿ Sucesso na definição da classe


Sucesso no método

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

Kernel.send :define_method, :counter do


extremidade

compartilhada

Kernel.send :define_method, :inc do |x| compartilhado


+= x
fim
fim

relatar errata • discutir


Machine Translated by Google

Capítulo 4. Quarta-feira: Blocos • 84

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.”

relatar errata • discutir


Machine Translated by Google

instância_eval() • 85

instância_eval()
Onde você aprende outra maneira de misturar código e ligações à vontade.

O programa a seguir demonstra BasicObject#instance_eval, que avalia um bloco no contexto


de um objeto:

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?

Pragmaticamente, existem algumas situações em que o encapsulamento apenas atrapalha.


Por um lado, você pode querer dar uma olhada rápida dentro de um objeto a partir de uma
linha de comando irb. Em um caso como esse, invadir o objeto com in stance_eval costuma
ser o caminho mais curto.

relatar errata • discutir


Machine Translated by Google

Capítulo 4. Quarta-feira: Blocos • 86

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

D.new.twisted_method # => "@x: 1, @y: 2"

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.

relatar errata • discutir


Machine Translated by Google

instância_eval() • 87

Os testes de unidade do Padrino precisam alterar a configuração do logger do aplicativo. Em vez


de passar pelo trabalho de criar e configurar um novo logger, os testes a seguir (escritos com a
gem de teste RSpec) apenas abrem o logger de aplicativo existente e alteram sua configuração
com um Context Probe:

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

deve 'permitir ativar o log de ativos estáticos' fazer


Padrino.logger.instance_eval{ @log_static = true }
# ...
get "/images/something.png"
assert_equal "Foo", corpo
assert_match /GET/, Padrino.logger.log.string
Padrino.logger.instance_eval{ @log_static = false } 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

relatar errata • discutir


Machine Translated by Google

Capítulo 4. Quarta-feira: Blocos • 88

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.

(Curiosamente, BasicObject é ainda mais limpo do que isso: em um BasicObject, as constantes


padrão do Ruby, como String , estão fora do escopo. Se você deseja referenciar uma constante
de um BasicObject, deve usar seu caminho absoluto, como ::String. )

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:

• Em um proc, que é basicamente um objeto virado em bloco •


Em um lambda, que é uma pequena variação de um proc • Em
um método

relatar errata • discutir


Machine Translated by Google

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:

inc = Proc.new {|x| x + 1 } # mais


código...
inc.call(2) # => 3

Essa técnica é chamada de avaliação diferida. Feitiço: Adiado


Avaliação

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:

dez = lambda {|x| x - 1 } dec.class


# => Proc
dec.call(2) # => 1

Além disso, você pode criar um lambda com o chamado operador “stabby lambda”:

p = ->(x) { x + 1 }

Observe a pequena seta. O código anterior é igual ao seguinte:

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.

relatar errata • discutir


Machine Translated by Google

Capítulo 4. Quarta-feira: Blocos • 90

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

def do_math(a, b, &operação) math(a,


b, &operação) end

do_math(2, 3) {|x, y| x * y} # => 6

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

p = meu_método {|nome| "Olá, #{nome}!" } p.class


# => Processo
p.call("Bill") # => "Olá, Bill!"

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

meu_proc = proc { "Bill" }


meu_método("Olá", &meu_proc)

Quando você chama my_method, o & converte my_proc em um bloco e passa esse bloco
para o método.

relatar errata • discutir


Machine Translated by Google

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 = hl.ask("Nome?", lambda {|s| s.capitalize }) puts "Olá,


#{nome}"

ÿ Nome?
ÿ conta
ÿ Olá, Bill

Este é um exemplo de Avaliação Diferida (89).

Procs vs. Lambdas


Você aprendeu várias maneiras diferentes de transformar um bloco em um Proc: Proc.new,
lambda, o operador & …. Em todos os casos, o objeto resultante é um Proc.

relatar errata • discutir


Machine Translated by Google

Capítulo 4. Quarta-feira: Blocos • 92

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.

Procs, Lambdas e retorno


A primeira diferença entre lambdas e procs é que a palavra-chave return significa coisas
diferentes. Em um lambda, return apenas retorna do lambda:

blocks/proc_vs_lambda.rb
def double(callable_object)
callable_object.call * 2 end

l = lambda { return 10 } double(l)


# => 20

Em um proc, o retorno se comporta de maneira diferente. Ao invés de retornar do proc, ele


retorna do escopo onde o próprio proc foi definido:

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

p = Proc.new { return 10 } double(p)


# => LocalJumpError

relatar errata • discutir


Machine Translated by Google

Objetos Chamáveis • 93

O programa anterior tenta retornar do escopo onde p está definido.


Como você não pode retornar do escopo de nível superior, o programa falha. Você pode
evitar esse tipo de erro se evitar o uso de retornos explícitos:

p = Proc.new { 10 }
double(p) # => 20

Agora vamos para a segunda diferença importante entre procs e lambdas.

Procs, Lambdas e Arity A


segunda diferença entre procs e lambdas diz respeito à maneira como eles verificam
seus argumentos. Por exemplo, um determinado proc ou lambda pode ter uma aridade
de dois, o que significa que aceita dois argumentos:

p = Proc.novo {|a, b| [a, b]} paridade


# => 2

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:

p = Proc.new {|a, b| [a, b]} p.call(1,


2, 3) # => [1, 2] p.call(1) # => [1, nil]

Se houver muitos argumentos, um proc descarta os argumentos em excesso. Se houver


poucos argumentos, atribuirá nil aos argumentos ausentes.

Procs vs. Lambdas: o veredicto


Agora você conhece as diferenças entre procs e lambdas. Mas você está se perguntando
que tipo de Proc deve usar em seu próprio código.

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.

relatar errata • discutir


Machine Translated by Google

Capítulo 4. Quarta-feira: Blocos • 94

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…)

Um objeto Method é semelhante a um bloco ou lambda. De fato, você pode converter um


Method em um Proc chamando Method#to_proc e pode converter um bloco em um método
com define_method. No entanto, existe uma diferença importante entre lambdas e métodos:
um lambda é avaliado no escopo em que está definido (é um encerramento, lembra?),
enquanto um método é avaliado no escopo de seu objeto.

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.

Métodos não vinculados

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

relatar errata • discutir


Machine Translated by Google

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

UnboundMethods são usados apenas em casos muito especiais. Vejamos um deles.

O exemplo Active Support A


gem Active Support contém, entre outras utilidades, um conjunto de classes e módulos que
carregam automaticamente um arquivo Ruby quando você usa uma constante definida nesse
arquivo. Este sistema de “carregamento automático” inclui um módulo chamado Loadable que
redefine o método Kernel#load padrão . Se uma classe incluir Loadable, então Loadable#load
ficará abaixo de Kernel#load em sua cadeia de ancestrais - portanto, uma chamada para load
terminará em Loadable#load.

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

relatar errata • discutir


Machine Translated by Google

Capítulo 4. Quarta-feira: Blocos • 96

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.

Resumo dos objetos que podem ser

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.

• Métodos: Vinculados a um objeto, são avaliados no escopo desse objeto.


Eles também podem ser desvinculados de seu escopo e religados para outro objeto ou classe.

Diferentes objetos chamáveis exibem comportamentos sutilmente diferentes. Em métodos e


lambdas, o retorno retorna do objeto que pode ser chamado, enquanto em procs e blocos, o
retorno é retornado do contexto original do objeto que pode ser chamado. Diferentes objetos que
podem ser chamados também reagem de maneira diferente a chamadas com a aridade errada.
Os métodos são mais rígidos, os lambdas são quase tão rígidos (exceto em alguns casos
extremos) e os procs e os blocos são mais tolerantes.

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 & .

Como escrever uma linguagem específica de domínio

Onde você e Bill, finalmente, escrevem algum código.

“Chega de falar sobre bloqueios”, diz Bill. “É hora de focar no trabalho de hoje.
Vamos chamá-lo de projeto RedFlag.”

relatar errata • discutir


Machine Translated by Google

Escrevendo uma Linguagem Específica de Domínio • 97

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:

event "estamos ganhando muito dinheiro" do


recent_orders = ... # (leia do banco de dados)
recent_orders > 1000 end

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.

É hora de escrever RedFlag 0.1.

Sua primeira DSL

Você e Bill montaram um RedFlag DSL funcional rapidamente:

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

evento final "um evento que nunca acontece" faça


falso
fim

Salve redflag.rb e events.rb na mesma pasta e execute redflag.rb:

ÿ ALERTA: um evento que sempre acontece

relatar errata • discutir


Machine Translated by Google

Capítulo 4. Quarta-feira: Blocos • 98

"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

evento "vendas mensais são suspeitamente altas" do


Monthly_sales > target_sales end

evento "as vendas mensais estão terrivelmente baixas "


mensal_vendas < meta_vendas final

Os dois eventos neste arquivo compartilham um método e uma variável local. você corre vermelho

flag.rb, e imprime o que você esperava:

ÿ ALERTA: as vendas mensais estão suspeitamente altas

“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.

Questionário: Uma DSL melhor

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.

relatar errata • discutir


Machine Translated by Google

Questionário: Uma DSL melhor • 99

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

evento "o céu está caindo" do


@sky_height < 300 end

evento "está chegando mais perto" faça


@sky_height < @mountains_height fim

evento "opa... tarde demais" faça


@sky_height < 0 fim

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.

relatar errata • discutir


Machine Translated by Google

Capítulo 4. Quarta-feira: Blocos • 100

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 :

evento def (descrição, &bloco)


@eventos << {:descrição => descrição, :condição => bloco} fim

@eventos = []
carregar 'eventos.rb'

O novo método de evento converte a condição do evento de um bloco para um Proc. Em


seguida, ele agrupa a descrição do evento e a condição Proc-ificada em um hash e armazena
o hash em uma matriz de eventos. A matriz é uma variável de instância de nível superior (como
aquelas sobre as quais você leu em Variáveis globais e Variáveis de instância de nível superior,
na página 80), portanto, pode ser inicializada fora do método de evento .
Finalmente, a última linha carrega o arquivo que define os eventos. Seu plano é escrever um
método de configuração semelhante ao método de evento e, em seguida, escrever o código
que executa eventos e configurações na sequência correta.

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

evento def (descrição, &bloco)


@eventos << {:descrição => descrição, :condição => bloco} fim

@setups = []
@events = []
load 'events.rb'

@events.each faz |evento|


@setups.each do |setup|
setup.call

relatar errata • discutir


Machine Translated by Google

Questionário: Uma DSL melhor • 101

end
coloca "ALERT: #{event[:description]}" if event[:condition].call end

Ambos setup e event convertem o bloco em um proc e armazenam o proc, em @setups e


@events, respectivamente. Essas duas variáveis de instância de nível superior são
compartilhadas por configuração, evento e código principal.

O código principal inicializa @setups e @events, então carrega events.rb. O código no


arquivo de eventos chama de volta para configuração e evento, adicionando elementos a
@setups e @events.

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?”

Removendo as Variáveis “Globais”


Para se livrar das variáveis globais (e da voz de Bill em sua cabeça), você pode usar um
Escopo Compartilhado (84):

blocks/redflag_4/redflag.rb
lambda
{ configurações
= [] eventos = []

Kernel.send :define_method, :setup do |&block|


configurações << fim do
bloco

Kernel.send :define_method, :event do |descrição, &block|


eventos << {:descrição => descrição, :condição => bloco} fim

Kernel.send :define_method, :each_setup do |&block| setups.each


faz |setup| fim da
configuração do
block.call
fim

Kernel.send :define_method, :each_event do |&block| eventos.cada


um faz |evento| evento
block.call
fim
fim
}.chamar

relatar errata • discutir


Machine Translated by Google

Capítulo 4. Quarta-feira: Blocos • 102

carregar 'eventos.rb'

cada_evento faça |evento|


each_setup do |setup| fim da
chamada de
configuração
puts "ALERT: #{event[:description]}" if event[:condition].call end

O escopo compartilhado está contido em um lambda que é chamado imediatamente. O código no


lambda define os métodos RedFlag como Kernel Methods (32) que compartilham duas variáveis:
configurações e eventos. Ninguém mais pode ver essas duas variáveis, porque elas são locais
para o lambda. (De fato, a única razão pela qual temos um lambda aqui é que queremos tornar
essas variáveis invisíveis para todos, exceto para os quatro Métodos do Kernel.) E sim, cada
chamada para Kernel.send está passando um bloco como um argumento para outro bloco.

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.

Adicionando uma sala limpa

Na versão atual do RedFlag, os eventos podem alterar as variáveis de instância de nível superior
compartilhadas uns dos outros:

evento "definir uma variável compartilhada" faça


@x = 1
fim
evento "mudar a variável" do @x = @x + 1 end

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:

#{event[:description]}" if env.instance_eval &(event[:condition]) end

relatar errata • discutir


Machine Translated by Google

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)

• Como executar código no escopo de um objeto (geralmente com instance_eval ou em


stance_exec), ou mesmo em uma Sala Limpa (87)

• Como transformar um bloco em um objeto (um Proc) e vice-versa

• Como transformar um método em um objeto (um Method ou um UnboundMethod) e


voltar

• Quais são as diferenças entre os diferentes tipos de objetos que podem ser chamados:
blocos, Procs, lambdas e métodos antigos simples

• Como escrever seu próprio pequeno DSL

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ã.

relatar errata • discutir


Machine Translated by Google

CAPÍTULO 5

Quinta-feira: Definições de classe

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.

Em Ruby, as definições de classe são diferentes. Ao usar a palavra-chave class , você


não está apenas ditando como os objetos se comportarão no futuro. Pelo contrário, você
está realmente executando o código.

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.

relatar errata • discutir


Machine Translated by Google

Capítulo 5. Quinta-feira: Definições de Aula • 106

Definições de classe desmistificadas


Onde você e Bill pisam em terreno familiar: o aplicativo Bookworm e o 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.”

Dentro das Definições de Classe

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:

resultado = classe MyClass


self
end

resultado # => MinhaClasse

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.

relatar errata • discutir


Machine Translated by Google

Definições de classe desmistificadas • 107

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á!"

relatar errata • discutir


Machine Translated by Google

Capítulo 5. Quinta-feira: Definições de Aula • 108

Module#class_eval é muito diferente de BasicObject#instance_eval, sobre o qual você aprendeu


anteriormente em instance_eval(), na página 85. instance_eval muda apenas self, enquanto
class_eval muda self e a classe atual.

(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

isto. Vamos recapitular os pontos importantes pelos quais acabamos de passar.

Conclusão da aula atual


Você aprendeu algumas coisas sobre definições de classe:

• 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.

relatar errata • discutir


Machine Translated by Google

Definições de classe desmistificadas • 109

• Em uma definição de classe, o objeto atual self e a classe atual são os


mesmo—a classe que está sendo definida.

• Se você tiver uma referência à classe, poderá abri-la com class_eval


(ou module_eval).

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.

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

relatar errata • discutir


Machine Translated by Google

Capítulo 5. Quinta-feira: Definições de Aula • 110

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.)

classe D<C def


meu_método; @@v; fim fim

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.

relatar errata • discutir


Machine Translated by Google

Definições de classe desmistificadas • 111

Trabalhando no Bookworm Novamente

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

relatar errata • discutir


Machine Translated by Google

Capítulo 5. Quinta-feira: Definições de Aula • 112

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'

class TestLoan < Test::Unit::TestCase


def test_conversion_to_string
Loan.instance_eval { @time_class = FakeTime } loan =
Loan.new('Guerra e Paz') assert_equal
'GUERRA E PAZ emprestado em segunda-feira, 06 de abril 12:15:50', loan.to_s end

fim

Bill tem muito orgulho de sua proeza de codificação. Ele diz: "Acho que merecemos uma
pausa - depois que eu fizer um teste".

Questionário: tabu de classe

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:

class MinhaClasse < Array


def meu_método
'Olá!'
fim
fim

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).

relatar errata • discutir


Machine Translated by Google

Métodos Singleton • 113

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:

c.name # => "MinhaClasse"

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

relatar errata • discutir


Machine Translated by Google

Capítulo 5. Quinta-feira: Definições de Aula • 114

título definitivo ?; @text.upcase == @text; final def


reverso; @text.reverse; end def upcase;
@text.upcase; fim #...

Os objetos de parágrafo são criados em um único local no código-fonte do Bookworm.


Além disso, Parágrafo#título? é chamado apenas uma vez em toda a aplicação, a partir de um método
chamado index:

índice def (parágrafo)


add_to_index(parágrafo) se parágrafo.título? fim

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.

Introdução aos Métodos Singleton


Acontece que Ruby permite que você adicione um método a um único objeto. Por exemplo,
veja como você pode adicionar título? para uma string específica:

class_definitions/singleton_methods.rb
str = "apenas uma string regular"

def str.title?
self.upcase == self end

str.title? # => falso


str.methods.grep(/title?/) # => [:título?] # =>
str.singleton_methods [:título?]

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:

relatar errata • discutir


Machine Translated by Google

Métodos Singleton • 115

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.

A verdade sobre os métodos de classe

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

relatar errata • discutir


Machine Translated by Google

Capítulo 5. Quinta-feira: Definições de Aula • 116

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:

def obj.a_singleton_method; end def


MyClass.another_class_method; fim

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

Veja este exemplo, vindo direto do núcleo do Ruby.

Os objetos Ruby de Exemplo


attr_accessor() não possuem atributos. Se você quer algo que se pareça com um atributo, você
deve definir dois Métodos de Mímica (218), um leitor e um escritor:

class_definitions/attr.rb
classe MinhaClasse
def meu_atributo=(valor)
@meu_atributo = valor final

relatar errata • discutir


Machine Translated by Google

Métodos Singleton • 117

def my_attribute
@my_attribute
end
fim

obj = MyClass.new
obj.my_attribute = 'x'
obj.my_attribute # => "x"

Métodos de escrita como esses (também chamados de acessadores) tornam-se entediantes


rapidamente. Como alternativa, você pode gerar acessadores usando um dos métodos da
família Module#attr_* . Module#attr_reader gera o leitor, Module#attr_writer gera o gravador e
Module#attr_accessor gera ambos:

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 # ...

subtítulo def # ...

def lend_to(user) puts


"Emprestando para #{user}"
# ...
fim

relatar errata • discutir


Machine Translated by Google

Capítulo 5. Quinta-feira: Definições de Aula • 118

def self.deprecate(old_method, new_method)


define_method(old_method) do |*args, &block|
warning "Aviso: #{old_method}() está obsoleto. Use #{new_method}()." send(new_method,
*args, &block) end end

depreciar :GetTitle, :title


depreciar :LEND_TO_USER, :lend_to
depreciar :title2, :subtitle end

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")

ÿ Aviso: LEND_TO_USER() está obsoleto. Use lend_to().


Empréstimo para faturar

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. )

O Mistério dos Métodos Singleton Em

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

relatar errata • discutir


Machine Translated by Google

Aulas Simples • 119

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.

Agora, o que acontece se você definir um Método Singleton (114) em obj?

def obj.my_singleton_method; fim

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?

Os métodos de classe são um tipo especial de Método Singleton - e igualmente desconcertante:

def MyClass.my_class_method; fim

Se você observar a figura a seguir, descobrirá que, novamente, my_class_method não parece
residir em nenhum lugar do diagrama de Bill.

A explicação desse mistério pode surpreendê-lo.

relatar errata • discutir


Machine Translated by Google

Capítulo 5. Quinta-feira: Definições de Aula • 120

Classes Singleton Reveladas

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:

class << an_object #


seu código aqui end

Se você deseja obter uma referência à classe singleton, pode retornar self fora do escopo:

obj = Objeto.novo

singleton_class = classe << obj


auto
fim

singleton_class.class # => Classe

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 :

"abc".singleton_class # => #<Class:#<String:0x331df0>>

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:

def obj.my_singleton_method; end


singleton_class.instance_methods.grep(/my_/) # => [:my_singleton_method]

Para entender completamente as consequências deste último ponto, você deve examinar mais
profundamente o modelo de objeto do Ruby.

relatar errata • discutir


Machine Translated by Google

Aulas individuais • 121

Pesquisa de Método Revisitada

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

classe D < C; 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.

relatar errata • discutir


Machine Translated by Google

Capítulo 5. Quinta-feira: Definições de Aula • 122

Classes singleton e pesquisa de


métodos Ao explorar as classes singleton, você pode perceber que seus nomes
não devem ser pronunciados por humanos. Quando você imprime na tela, uma
classe singleton se parece com isto:
obj = Object.new
obj.singleton_class # => #<Class:#<Object:0x007fd96909b588>>

Os diagramas no restante deste capítulo identificam classes singleton com um simples


prefixo # . Por essa convenção, #obj é a classe singleton de obj, #C é a classe
singleton de C e assim por diante.

Armado com o método singleton_class e sua nova convenção de nomenclatura, agora


você pode prosseguir com sua exploração destemida do modelo de objeto. Vamos
voltar ao programa “lab rat” e definir um Método Singleton (114).
class << obj
def a_singleton_method
'obj#a_singleton_method()' end
end

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

A superclasse da classe singleton de obj é D. Tente adicionar esse novo conhecimento


ao diagrama do modelo de objeto “lab rat”. O resultado é mostrado na Figura 7,
Pesquisa de método com classes singleton, na página 123.

Você pode ver como os Métodos Singleton se encaixam no processo normal de


pesquisa de método. Se um objeto tem uma classe singleton, Ruby começa a procurar
métodos na classe singleton ao invés da classe convencional, e é por isso que você
pode chamar Métodos Singleton como obj#a_singleton_method. Se o Ruby não
conseguir encontrar o método na classe singleton, ele sobe na cadeia de ancestrais,
terminando na superclasse da classe singleton - que é a classe do objeto. A partir daí,
tudo é business as usual.

Agora você entende como funcionam os Métodos Singleton. Mas e os métodos de


classe? Sim, eles são apenas um caso especial dos Métodos Singleton, mas merecem
um olhar mais atento.

relatar errata • discutir


Machine Translated by Google

Aulas Simples • 123

Figura 7—Pesquisa de método com classes singleton

Classes singleton e herança Nesta seção,

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ó.

Tente adicionar um método de classe ao programa “lab rat”.

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á

relatar errata • discutir


Machine Translated by Google

Capítulo 5. Quinta-feira: Definições de Aula • 124

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>

Bill pega um pedaço de papel e desenha o seguinte diagrama.

Figura 8—Classes singleton e herança

Este é um diagrama um tanto complicado. As setas marcadas com S ligam classes a


suas superclasses, e as setas marcadas com C ligam objetos (incluindo classes) a
suas classes, que neste caso são todas classes singleton.
As setas marcadas com um C não apontam para as mesmas classes que a classe

relatar errata • discutir


Machine Translated by Google

Aulas individuais • 125

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.

Aparentemente, Ruby organiza classes, classes singleton e superclasses em um padrão muito


intencional. A superclasse de #D é #C, que também é a classe singleton de C. Pela mesma regra, a
superclasse de #C é #Object. Bill tenta resumir tudo, tornando as coisas ainda mais confusas: “A
superclasse da classe singleton é a classe singleton da superclasse. É fácil."

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:

D.a_class_method # => "C.a_class_method()"

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.

A Grande Teoria Unificada

“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:

1. Existe apenas um tipo de objeto - seja um objeto regular ou um módulo.

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.

relatar errata • discutir


Machine Translated by Google

Capítulo 5. Quinta-feira: Definições de Aula • 126

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.

6. A superclasse da classe singleton de um objeto é a classe do objeto. A superclasse da


classe singleton de uma classe é a classe singleton da superclasse da classe. (Tente
repetir isso três vezes, rápido. Em seguida, reveja a Figura 8, Classes singleton e herança,
na página 124, e tudo fará sentido.)

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.

Parabéns — agora você entende todo o modelo de objeto Ruby.

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.

relatar errata • discutir


Machine Translated by Google

Aulas Simples • 127

def MyClass.a_class_method; fim

classe MinhaClasse
def self.another_class_method; fim fim

classe MinhaClasse
classe << eu
def yet_another_class_method; fim fim

fim

A primeira sintaxe geralmente é desaprovada por rubistas especialistas porque ela


duplica o nome da classe, tornando-a mais difícil de refatorar. A segunda sintaxe tira
proveito do fato de que self na definição de classe é a própria classe. A terceira
sintaxe é a mais complicada: o código abre a classe singleton e define o método
nela. Esta última sintaxe reconhece a classe singleton explicitamente, então você
ganhará algum crédito nas ruas do Ruby.

Classes Singleton e instance_eval()


Agora que você conhece as classes singleton, também pode preencher um fragmento
de conhecimento que falta sobre o método instance_eval . Em class_eval(), na
página 107, você aprendeu que instance_eval muda self e class_eval muda tanto
self quanto a classe atual. No entanto, instance_eval também altera a classe atual;
ele o altera para a classe singleton do receptor. Este exemplo usa instance_eval para
definir um Método Singleton (114):
class_definitions/
instance_eval.rb s1, s2 = "abc", "def"

s1.instance_eval do def
swoosh!; reverter; fim fim

s1.swoosh! # => "cba" s2.respond_to?


(:swoosh!) # => false

Você raramente, ou nunca, verá instance_eval usado propositadamente para alterar


a classe atual, como no exemplo acima. O significado padrão de instance_eval é
este: “Quero mudar o eu”.

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?”

relatar errata • discutir


Machine Translated by Google

Capítulo 5. Quinta-feira: Definições de Aula • 128

Vejamos um exemplo envolvendo macros de classe (117). Você se lembra do método


attr_accessor do Exemplo attr_accessor(), na página 116? Ele gera atributos para
qualquer objeto:

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á:

classe MinhaClasse; fim

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

relatar errata • discutir


Machine Translated by Google

Questionário: Problema do Módulo • 129

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.

Figura 9—Os atributos de classe residem na classe singleton da classe.

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.”

Questionário: Problema no Módulo

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!

relatar errata • discutir


Machine Translated by Google

Capítulo 5. Quinta-feira: Definições de Aula • 130

“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

MyClass.my_method # => "olá"

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?”

Métodos de classe e include()


Revendo Extensões de classe, você pode definir métodos de classe misturando-os na classe singleton da
classe. Os métodos de classe são apenas um caso especial dos métodos singleton, portanto, você pode
generalizar esse truque para qualquer objeto. No caso geral, isso é chamado de extensão de objeto. No
Feitiço: Objeto exemplo a seguir, obj é estendido com os métodos de instância de MyModule:
Extensão

class_definitions/

module_trouble_object.rb módulo
MyModule def my_method; 'olá'; fim fim

relatar errata • discutir


Machine Translated by Google

Envoltórios de método • 131

obj = Objeto.novo

class << obj


include MyModule
end

obj.my_method # => "olá"


obj.singleton_methods # => [:my_method]

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

MyClass.my_method # => "olá"

Object#extend é simplesmente um atalho que inclui um módulo na classe única do receptor.


Você sempre pode fazer isso sozinho, se assim o desejar.

“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:

relatar errata • discutir


Machine Translated by Google

Capítulo 5. Quinta-feira: Definições de Aula • 132

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.)

Continuando com o exemplo anterior:

class MyClass
alias_method :m2, :m end

obj.m2 # => "meu_metodo()"

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?)

relatar errata • discutir


Machine Translated by Google

Envoltórios de método • 133

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/

wrapper_around_alias.rb classe String alias_method :real_length, :length

def
comprimento real_length > 5 ? 'longo' : final
'curto'
fim

"Guerra e Paz".length # => "long"


"Guerra e Paz".real_length # => 13

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

alias_method :require_without_record, :require


alias_method :require, :require_with_record end

carregar entrada

relatar errata • discutir


Machine Translated by Google

Capítulo 5. Quinta-feira: Definições de Aula • 134

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

Spell: Around Alias é chamado de Around Alias.

Você pode escrever um Around Alias em três etapas simples:

1. Você apelida um método.


2. Você o redefine.
3. Você chama o método antigo do novo método.

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ê?

relatar errata • discutir


Machine Translated by Google

Envoltórios de método • 135

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.

Mais wrappers de método


Em Refinamentos, na página 36, você aprendeu que um Refinamento (36) funciona
como um patch de código que foi colocado diretamente sobre uma classe. No entanto,
os refinamentos têm um recurso adicional que permite usá-los no lugar de Around
Aliases (134): se você chamar super de um método refinado, chamará o método original
não refinado. Aqui vai um exemplo:
class_definitions/wrapper_refinement.rb
module StringRefinement
refine String do def
length super
>5? 'longo' : final 'curto'

fim
fim

usando StringRefinement

"Guerra e Paz". comprimento # => "longo"

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

relatar errata • discutir


Machine Translated by Google

Capítulo 5. Quinta-feira: Definições de Aula • 136

String.class_eval do
preceder ExplicitString end

"Guerra e Paz". comprimento # => "longo"

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.

Resolvendo o problema da Amazon

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

puts "reviews_of () falhou" [] end

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.

Questionário: matemática quebrada

Onde você descobre que um mais um nem sempre é igual a dois.

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).

relatar errata • discutir


Machine Translated by Google

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.

relatar errata • discutir


Machine Translated by Google

CAPÍTULO 6

Sexta-feira: Código que escreve código

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.

Codificando seu caminho para o fim de semana

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.

relatar errata • discutir


Machine Translated by Google

Capítulo 6. Sexta-feira: Código que Escreve Código • 140

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

attr_checked :idade do |v| v >= 18

fim
fim

eu = Pessoa.novo
me.age = 39 # OK
me.age = 12 # Exceção

Sua tarefa hoje é escrever CheckedAttributes e attr_checked para seu chefe.

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.

Em vez de se engajar na programação em pares, Bill propõe o compartilhamento de funções: ele


gerenciará o desenvolvimento e você escreverá o código. Enquanto você se pergunta o que realmente
significa “gerenciar o desenvolvimento”, Bill lista rapidamente as etapas que você seguirá:

1. Escreva um Kernel Method (32) chamado add_checked_attribute usando eval para adicionar um
atributo validado supersimples para uma classe.

2. Refatore add_checked_attribute para remover eval.

relatar errata • discutir


Machine Translated by Google

Núcleo de avaliação • 141

3. Valide os atributos por meio de um bloco.

4. Altere add_checked_attribute para uma Macro de Classe (117) chamada attr_checked que é
disponível para todas as classes.

5. Escreva um módulo adicionando attr_checked a classes selecionadas por meio de um gancho.

“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

eval("array << elemento") # => [10, 20, 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.

Exemplo de cliente REST


REST Client (instalado com gem install rest-client) é uma biblioteca cliente HTTP simples.
Ele inclui um interpretador onde você pode emitir comandos Ruby regulares junto com métodos HTTP
como get:

ÿ restclient http://www.twitter.com

> html_first_chars = get("/")[0..14]


=> "<!DOCTYPE html>"

relatar errata • discutir


Machine Translated by Google

Capítulo 6. Sexta-feira: Código que Escreve Código • 142

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 ):

def get(caminho, *args, &b)


r[caminho].get(*args, &b) end

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

relatar errata • discutir


Machine Translated by Google

Núcleo de avaliação • 143

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.

Você pode criar um Binding com o método Kernel#binding :

ctwc/
bindings.rb
class MyClass
def

my_method @x = 1 ligação final


fim

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:

valor "@x", b # => 1

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

AnotherClass.new.my_method # => principal

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.

relatar errata • discutir


Machine Translated by Google

Capítulo 6. Sexta-feira: Código que Escreve Código • 144

#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:

eval(declarações, @binding, arquivo, linha)

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:

ÿ ZeroDivisionError: dividido por 0 da exceção.rb:2:in


`/'

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

relatar errata • discutir


Machine Translated by Google

Núcleo de avaliação • 145

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):

eval(declarações, @binding) # , arquivo, linha)

Execute irb exception.rb agora e a exceção relata o arquivo e a linha onde eval é chamado:

ÿ ZeroDivisionError: dividido por 0 de /Users/


nusco/.rvm/rubies/ruby-2.0.0/lib/ruby/2.0.0/irb/workspace.rb:54:in `/'

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.

Strings de Código vs. Blocos


Em Kernel#eval, na página 141, você aprendeu que eval é um caso especial na família
eval* : ele avalia uma String de Código (141) em vez de um bloco, como class_eval e
instance_eval fazem. No entanto, esta não é toda a verdade. Embora seja verdade que eval
sempre requer uma string, instance_eval e class_eval podem receber uma string de código
ou um bloco.

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:

matriz = ['a', 'b', 'c'] x = 'd'

array.instance_eval "self[1] = x"

matriz # => ["a", "d", "c"]

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ê.

O problema com eval()


Strings of Code são poderosas, sem dúvida. Mas com grandes poderes vêm grandes
responsabilidades - e perigos.

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.

relatar errata • discutir


Machine Translated by Google

Capítulo 6. Sexta-feira: Código que Escreve Código • 146

Felizmente, esses aborrecimentos são menores em comparação com o maior problema da


avaliação: a segurança. Este problema específico exige uma explicação mais detalhada.

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”.)

ÿ object_id; Dir.glob("*") ÿ ['a', 'b',


'c'].object_id; Dir.glob("*") => [suas informações privadas aqui]

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 .

relatar errata • discutir


Machine Translated by Google

Kernel#avaliação • 147

Defendendo-se da injeção de código Como

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

Da mesma forma, você pode reescrever o utilitário Array Explorer de Code


Injeção, na página 146, usando um Dynamic Dispatch no lugar de eval:

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.

relatar errata • discutir


Machine Translated by Google

Capítulo 6. Sexta-feira: Código que Escreve Código • 148

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.

Objetos contaminados e níveis


seguros Ruby marca automaticamente objetos potencialmente inseguros—em particular,
objetos que vêm de fontes externas—como contaminados. Objetos contaminados incluem
strings que seu programa lê de formulários da web, arquivos, linha de comando ou até
mesmo uma variável do sistema. Toda vez que você cria uma nova string manipulando
strings contaminadas, o próprio resultado é corrompido. Aqui está um exemplo de programa
que verifica se um objeto está contaminado chamando seu método tainted? método:

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.

relatar errata • discutir


Machine Translated by Google

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

ÿ <p><strong>Acorde!</strong> É uma bela sexta-feira ensolarada.</p>

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

eval(@src, b, (@arquivo || '(erb)'), 0) fim

fim
#...

new_toplevel é um método que retorna uma cópia de TOPLEVEL_BINDING. A variável de


instância @src contém o conteúdo de uma tag de código e a variável de instância @safe_level
contém o nível seguro exigido pelo usuário. Se nenhum nível seguro for definido, o conteúdo da
tag é simplesmente avaliado. Caso contrário, o ERB constrói um Sandbox rápido (149): ele
garante que o nível seguro global seja exatamente o que o usuário pediu e

relatar errata • discutir


Machine Translated by Google

Capítulo 6. Sexta-feira: Código que Escreve Código • 150

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.

Questionário: Atributos verificados (Etapa 1)


Onde você dá o primeiro passo para resolver o desafio do chefe, com Bill olhando por cima do seu
ombro.

Você e Bill analisam as duas primeiras etapas do seu plano de desenvolvimento:

1. Escreva um Kernel Method (32) chamado add_checked_attribute usando eval para adicionar um
atributo validado supersimples para uma classe.

2. Refatore add_checked_attribute para remover eval.

Concentre-se no primeiro passo. O método add_checked_attribute deve gerar um método de leitura


e um método de gravação, assim como attr_accessor faz. No entanto, add_checked_attribute
deve diferir de attr_accessor de três maneiras. Primeiro, enquanto attr_accessor é uma Macro de
Classe (117), add_checked_attribute deve ser um Método de Kernel simples (32). Em segundo
lugar, attr_accessor é escrito em C, enquanto add_checked_attribute deve usar Ruby simples (e
uma String de Código (141)). Por fim, add_checked_attribute deve adicionar um exemplo básico
de validação ao atributo: o atributo gerará uma exceção de tempo de execução se você atribuir nil
ou false.
(Você lidará com a validação flexível no futuro.)

Esses requisitos são expressos mais claramente em um conjunto de testes:

relatar errata • discutir


Machine Translated by Google

Questionário: Atributos verificados (Etapa 1) • 151

ctwc/checked_attributes/
eval.rb requer 'teste/unidade'

classe Pessoa; fim

class TestCheckedAttribute < Test::Unit::TestCase


def setup
add_checked_attribute(Person, :age) @bob
= Person.new end

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

# Aqui está o método que você deve implementar. def


add_checked_attribute(classe, atributo)
# ...
fim

(A referência à classe em add_checked_attribute é chamada klass porque class é uma


palavra reservada em Ruby.)

Você pode implementar add_checked_attribute e passar no teste?

Antes de resolver este questionário…

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:

relatar errata • discutir


Machine Translated by Google

Capítulo 6. Sexta-feira: Código que Escreve Código • 152

def my_attr
@my_attr
end

def my_attr=(valor) @my_attr


= valor final

Solução do questionário

Aqui está uma solução:

def add_checked_attribute(klass, attribute) ÿ eval "

ÿ class #{klass} ÿ def #{attribute}


=(value) ÿ ÿ
aumento 'Atributo inválido' a menos que valor
@#{atributo} = valor final
ÿ
ÿ
ÿ def #{atributo}() ÿ @#{atributo} ÿ fim ÿ
fim ÿ "

fim

Aqui está a String de Código (141) que é avaliada quando você chama
add_checked_attribute(String, :my_attr):

classe String def


my_attr=(valor)
aumento 'Atributo inválido' a menos que valor
@my_attr = valor final

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.”

relatar errata • discutir


Machine Translated by Google

Questionário: Atributos verificados (Etapa 2) • 153

Questionário: Atributos verificados (Etapa 2)


Onde você faz seu código livre de avaliação.

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 .

Você pode refatorar add_checked_attribute com a mesma assinatura de método e os mesmos


testes de unidade, mas usando métodos Ruby padrão no lugar de eval? Esteja avisado que
para resolver este quiz, você terá que fazer alguma pesquisa. Provavelmente, você precisará
vasculhar as bibliotecas padrão do Ruby em busca de métodos que possam substituir as
operações na String of Code atual. Você também precisará gerenciar o escopo com cuidado
para que o atributo seja definido no contexto da classe correta. (Dica: lembra-se de Flat Scopes
(83)?)

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

Você está na classe agora e pode definir os métodos de leitura e gravação.


Anteriormente, você fazia isso usando a palavra-chave def na String de Código. Novamente,
você não pode mais usar def, porque não saberá os nomes dos métodos até o tempo de
execução. No lugar de def, você pode usar Métodos Dinâmicos (51):

def add_checked_attribute(klass, attribute)


klass.class_eval do

relatar errata • discutir


Machine Translated by Google

Capítulo 6. Sexta-feira: Código que Escreve Código • 154

ÿ define_method "#{attribute}=" do |value| ÿ # ... ÿ fim ÿ

ÿ atributo define_method do ÿ # ...

ÿ 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:

def add_checked_attribute(klass, attribute)


klass.class_eval do
define_method "#{attribute}=" do |value| aumento
ÿ 'Atributo inválido' a menos que valor
ÿ instance_variable_set("@#{attribute}", valor)
fim

atributo define_method faça


ÿ instance_variable_get "@#{attribute}" end end

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.”

Questionário: Atributos verificados (Etapa 3)


Onde você espalha alguma flexibilidade sobre o projeto de hoje.

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:

relatar errata • discutir


Machine Translated by Google

Questionário: Atributos verificados (Etapa 3) • 155

ctwc/checked_attributes/
block.rb requer 'teste/unidade'

classe Pessoa; fim

class TestCheckedAttribute < Test::Unit::TestCase


configuração
def ÿ add_checked_attribute(Pessoa, :idade) {|v| v >= 18 }
@bob = Pessoa.novo
fim

def test_accepts_valid_values @bob.age


= 20 assert_equal
20, @bob.age end

ÿ def test_refuses_invalid_values ÿ assert_raises


RuntimeError, 'Invalid attribute' do ÿ
@bob.age = 17
ÿ fim
ÿ fim
fim

ÿ def add_checked_attribute(klass, attribute, &validation)


# ... (O código aqui não passa no teste. Modifique-o.) end

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:

ÿ def add_checked_attribute(klass, attribute, &validation) klass.class_eval do

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

“O passo 3 foi rápido”, observa Bill. “Vamos para o passo 4.”

relatar errata • discutir


Machine Translated by Google

Capítulo 6. Sexta-feira: Código que Escreve Código • 156

Questionário: Atributos verificados (Etapa 4)


Onde você puxa uma Macro de Classe (117) de sua bolsa de truques.

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.

O que isso significa é que, em vez de um método add_checked_attribute , você deseja um


método attr_checked que o chefe possa usar em uma definição de classe. Além disso, em
vez de usar uma classe e um nome de atributo, esse novo método deve usar apenas o
nome do atributo, porque a classe já está disponível como self.

Bill atualiza o caso de teste:

ctwc/checked_attributes/
macro.rb requer 'teste/unidade'

classe Pessoa
ÿ attr_checked :age do |v| ÿ v >= 18
ÿ fim

fim

class TestCheckedAttributes < Test::Unit::TestCase


def setup ÿ
@bob = Person.new end

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

Você pode escrever o método attr_checked e passar nos testes?

Solução do questionário

Lembre-se da discussão sobre definições de classe em Definições de classe desmistificadas,


na página 106. Se você quiser tornar attr_checked disponível para qualquer definição de
classe, pode simplesmente torná-lo um método de instância de Classe ou Módulo.
Vamos para a primeira opção:

relatar errata • discutir


Machine Translated by Google

Métodos de Gancho • 157

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

class MinhaString < String; fim

ÿ String foi herdada por MyString

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.

relatar errata • discutir


Machine Translated by Google

Capítulo 6. Sexta-feira: Código que Escreve Código • 158

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

def meu_método; fim fim

ÿ Novo método: M#my_method

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.

Module#included é provavelmente o gancho mais usado, graças a um feitiço de


metaprogramação comum que é digno de um exemplo próprio.

relatar errata • discutir


Machine Translated by Google

Métodos de Gancho • 159

Conectando-se a métodos padrão


A noção de ganchos se estende além de métodos especializados como Class#inherited ou
Module#method_added. Como a maioria das operações em Ruby são apenas métodos regulares, você pode
facilmente transformá-los em Métodos de Gancho improvisados.

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)

Há uma diferença importante entre substituir Module#included e substituir Module#include. Module#included


existe apenas para ser usado como um Hook Method e sua implementação padrão está vazia. Mas
Module#include tem algum trabalho real a fazer: ele deve realmente incluir o módulo. É por isso que o código
do nosso gancho também deve chamar a implementação básica de Module#include por meio de super. Se
você esquecer o super, ainda receberá o evento, mas não incluirá mais o módulo.

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

class Request #...


include Normalizers::Body #...

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

relatar errata • discutir


Machine Translated by Google

Capítulo 6. Sexta-feira: Código que Escreve Código • 160

Normalizadores::Corpo. Mas uma classe geralmente obtém métodos de instância incluindo


um módulo — não métodos de classe . Como um mixin como Normalizers::Body pode
dobrar as regras e definir métodos de classe em seu includer?

Procure a resposta na definição do próprio módulo Body :

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:

• Ruby chama o gancho incluído em Body.

• O gancho volta para Request e o estende com o módulo ClassMethods .

• O método extend inclui os métodos de ClassMethods na classe singleton do Request .


(Você deve se lembrar desta última parte do truque do Quiz: Module Trouble, na página
129.)

Como resultado, body_from e outros métodos de instância são misturados na classe


singleton de Request, tornando-se efetivamente métodos de classe de Request. Como é
isso para uma mistura de código complicado?

Este idioma ClassMethods-plus-hook costumava ser bastante comum, e foi usado


extensivamente pelo código-fonte do Rails. Como você verá no Capítulo 10, Módulo
Concern do Suporte Ativo, na página 179, Rails desde então mudou para um mecanismo
alternativo—mas você ainda pode encontrar exemplos do idioma em VCR e outras joias.

Questionário: Atributos verificados (Etapa 5)


Onde você finalmente ganha o respeito de Bill e o título de Mestre da Metaprogramação.

A seguir está o código que escrevemos na etapa de desenvolvimento anterior:

relatar errata • discutir


Machine Translated by Google

Questionário: Atributos verificados (Etapa 5) • 161

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

attr_checked :idade do |v| v >= 18

fim
fim

class TestCheckedAttributes < Test::Unit::TestCase


def setup
@bob = Person.new end

def test_accepts_valid_values @bob.age =


18 assert_equal
18, @bob.age end

def test_refuses_invalid_values assert_raises


RuntimeError, 'Atributo inválido' do @bob.age = 17 end end

fim

relatar errata • discutir


Machine Translated by Google

Capítulo 6. Sexta-feira: Código que Escreve Código • 162

Você pode remover attr_checked da classe, escrever o módulo CheckedAttributes e resolver o


desafio do chefe?

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…”

relatar errata • discutir


Machine Translated by Google

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.

Um discípulo subiu a montanha e interrompeu a concentração do mestre.


"Estou lutando terrivelmente, Mestre", disse ele. “Já estudei muitas técnicas avançadas, mas ainda
não sei como aplicá-las corretamente. Diga-me, qual é a essência da metaprogramação?”

“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.”

Com essas palavras, o discípulo foi iluminado.

relatar errata • discutir


Machine Translated by Google

parte II

Metaprogramação em Rails
Machine Translated by Google

Bons artistas copiam, grandes artistas roubam.

ÿ Pablo Picasso

CAPÍTULO 8

Preparando-se para uma turnê Rails

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.

relatar errata • discutir


Machine Translated by Google

Capítulo 8. Preparando-se para um Tour Rails • 168

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

relatar errata • discutir


Machine Translated by Google

O código-fonte do Rails • 169

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.

Agora você tem o código-fonte do Rails e as ferramentas necessárias para explorá-lo.


No próximo capítulo, mergulharemos na primeira parada do nosso tour: uma rápida
olhada no Active Record, o mais icônico dos componentes do Rails.

relatar errata • discutir


Machine Translated by Google

CAPÍTULO 9

O design do registro ativo

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.

Um Exemplo Curto de Registro Ativo


Suponha que você já tenha um banco de dados SQLite baseado em arquivo que segue as convenções do
Active Record: esse banco de dados contém uma tabela chamada ducks, que possui um campo chamado

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.

relatar errata • discutir


Machine Translated by Google

Capítulo 9. O Design do Registro Ativo • 172

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ô.

Como o Active Record é montado


O código do exemplo anterior parece simples, mas ActiveRecord::Base é capaz de muito mais
do que isso. De fato, quanto mais você usa o Active Record, mais os métodos no Base
parecem se multiplicar. Você pode assumir que Base é uma classe enorme com milhares de
linhas de código que definem métodos como save ou vali
data.

Surpreendentemente, o código-fonte de ActiveRecord::Base não contém vestígios desses


métodos. Este é um problema comum para iniciantes no Rails: muitas vezes é difícil

relatar errata • discutir


Machine Translated by Google

Como o registro ativo é organizado • 173

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'.

O mecanismo de carregamento automático

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

autoload :Sem tocar


autoload :Persistência
autoload :QueryCache
autoload :Consultando
autoload :Validações
# ...

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.

relatar errata • discutir


Machine Translated by Google

Capítulo 9. O Design do Registro Ativo • 174

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 DynamicMatchers estender


Explicar estender Enum

estender Delegação::DelegateCache

incluir Núcleo
incluir Persistência
include NoTouching include
ReadonlyAttributes include ModelSchema

incluir Herança incluir Escopo


incluir Sanitização

include AttributeAssignment include


ActiveModel::Conversion
include Integração include
Validações
incluir CounterCache
include Bloqueio::Otimista include
Bloqueio::Pessimista include AttributeMethods

incluir retornos de chamada


incluir Timestamp incluir
Associações
incluir ActiveModel::SecurePassword
include AutosaveAssociation include
NestedAttributes include Agregações
include Transações

incluir Reflexão
incluir Serialização
incluir loja
incluir final do
núcleo

ActiveSupport.run_load_hooks(:active_record, Base) end

relatar errata • discutir


Machine Translated by Google

Como o registro ativo é organizado • 175

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) # ...

Onde está validar? Podemos procurar a resposta em ActiveModel::Validations, um módulo que


ActiveRecord::Validation inclui. Este módulo vem do Active Model, uma biblioteca da qual o Active Record

depende. Com certeza, se você examinar sua origem, descobrirá que a validação está definida em
ActiveModel::Validation.

relatar errata • discutir


Machine Translated by Google

Capítulo 9. O Design do Registro Ativo • 176

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.

Uma lição aprendida

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).

relatar errata • discutir


Machine Translated by Google

Uma Lição Aprendida • 177

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:

relatar errata • discutir


Machine Translated by Google

Capítulo 9. O Design do Registro Ativo • 178

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

Veja como o código acima está bem desacoplado. ActiveModel::Validations não o


força a se intrometer com herança, a se preocupar com questões relacionadas ao
banco de dados ou a gerenciar qualquer outra dependência complicada. Ao incluí-lo,
você obtém um conjunto completo de métodos de validação sem adicionar complexidade.

Falando em ActiveModel::Validations, prometi mostrar a você como este módulo


adiciona métodos de classe como validar ao seu includer. Vou manter essa promessa
no próximo capítulo.

relatar errata • discutir


Machine Translated by Google

CAPÍTULO 10

Módulo Preocupação do Suporte Ativo

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?

A resposta vem de outro módulo: Concern, na biblioteca Active Support. ActiveSupport::Concern


torce e dobra o modelo de objeto Ruby. Ele encapsula a funcionalidade “adicionar métodos
de classe ao seu includer” e facilita a implementação dessa funcionalidade em outros
módulos.

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.

Trilhos antes da preocupação

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.

O mecanismo que rola esses métodos no Base, no entanto, foi alterado.


Vamos ver como funcionou no começo.

O truque de incluir e estender

Na época do Rails 2, todos os métodos de validação eram definidos em ActiveRe


cord::Validations. (Naquela época, não havia nenhuma biblioteca Active Model.) No entanto,
as validações usaram um truque peculiar:

relatar errata • discutir


Machine Translated by Google

Capítulo 10. Módulo Preocupação do Suporte Ativo • 180

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:

1. Os métodos de instância de Validations, como valid?, tornam-se métodos de instância de Base.


Esta é apenas a inclusão regular do módulo.

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”.)

3. O gancho estende Base com o módulo ActiveRecord::Validations::ClassMethods .


Esta é uma Extensão de Classe (130), então os métodos em ClassMethods tornam-se classes
métodos na 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.

relatar errata • discutir


Machine Translated by Google

Trilhos antes da preocupação • 181

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.

O problema das inclusões encadeadas

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

def second_level_instance_method; 'OK'; fim

módulo ClassMethods
def second_level_class_method; 'OK'; fim fim

fim

módulo FirstLevelModule
def self.included(base)
base.extend ClassMethods
fim

def first_level_instance_method; 'OK'; fim

relatar errata • discutir


Machine Translated by Google

Capítulo 10. Módulo Preocupação do Suporte Ativo • 182

módulo ClassMethods
def first_level_class_method; 'OK'; fim fim

incluir fim do SecondLevelModule

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:

BaseClass.new.first_level_instance_method # => "ok"


BaseClass.new.second_level_instance_method # => "ok"

Graças aos métodos include-and-extend em FirstLevelModule::ClassMethods também


tornam-se métodos de classe em BaseClass:

BaseClass.first_level_class_method # => "ok"

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:

BaseClass.second_level_class_method # => NoMethodError

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

# ...

Infelizmente, o código acima tornou todo o sistema menos flexível; forçou


Rails para distinguir módulos de primeiro nível de outros módulos, e cada módulo

relatar errata • discutir


Machine Translated by Google

Suporte Ativo::Preocupação • 183

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

def an_instance_method; "um método de instância"; fim

módulo ClassMethods
def um_class_method; "um método de classe"; fim
fim
fim

class MyClass
include MyConcern
end

MyClass.new.an_instance_method # => "um método de instância"


MyClass.a_class_method # => "um método de classe"

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.

Vamos ver como o Concern faz sua mágica.

Uma olhada no código-fonte da Concern

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:

relatar errata • discutir


Machine Translated by Google

Capítulo 10. Módulo Preocupação do Suporte Ativo • 184

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.

Para introduzir o Concern#append_features, o outro método importante no Concern,


deixe-me levá-lo a uma curta viagem pelas bibliotecas padrão do Ruby.

Module#append_features

Module#append_features é um método básico do Ruby. É semelhante ao Module#included, pois o


Ruby o chamará sempre que você incluir um módulo. No entanto, há uma diferença importante
entre append_features e included: included é um Hook Method que normalmente está vazio e existe
apenas no caso de você querer substituí-lo. Por outro lado, append_features é onde a inclusão real
acontece. append_features verifica se o módulo incluído já está na cadeia de ancestrais do includer
e, se não estiver, adiciona o módulo à cadeia.

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

C.ancestors # => [C, Object, Kernel, BasicObject]

relatar errata • discutir


Machine Translated by Google

Suporte Ativo::Preocupação • 185

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) # ...

Lembra do feitiço Extensão de Classe (130) ? append_features é um método de instância no


Concern, então ele se torna um método de classe em módulos que estendem o Concern. Por
exemplo, se um módulo chamado Validations estende Concern, ele ganha um método de classe
Validation.append_features . Se isso parece confuso, veja esta imagem mostrando as relações
entre Module, Concern, Validations e Validation's singleton class:

Figura 10—ActiveSupport::Concern substitui Module#append_features.

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.

relatar errata • discutir


Machine Translated by Google

Capítulo 10. Módulo Preocupação do Suporte Ativo • 186

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.

Ao inserir append_features, você deseja verificar se o seu includer é uma preocupação. Se


tiver uma variável de classe @_dependencies , você sabe que é uma preocupação. Nesse
caso, em vez de adicionar a si mesmo à cadeia de ancestrais do seu includer, basta adicionar
a si mesmo à lista de dependências e retornar false para sinalizar que nenhuma inclusão
realmente aconteceu. Por exemplo, isso acontece se você for ActiveModel::Validations e for
incluído por ActiveRecord::Validations.

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

relatar errata • discutir


Machine Translated by Google

Suporte Ativo::Preocupação • 187

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

ActiveSupport::Concern é um sistema de gerenciamento de dependência minimalista, agrupado em um único


módulo com apenas algumas linhas de código. Esse código é complicado, mas usar o Concern é fácil, como
você pode ver olhando a fonte do Active Model:

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

relatar errata • discutir


Machine Translated by Google

Capítulo 10. Módulo Preocupação do Suporte Ativo • 188

Uma lição aprendida

Na maioria das linguagens, não há muitas maneiras de vincular componentes.


Talvez você herde de uma classe ou delegue a um objeto. Se você quiser ser sofisticado, pode
usar uma biblioteca especializada em gerenciamento de dependências — ou até mesmo uma
estrutura inteira.

Agora, veja como os autores do Rails uniram as partes de seu framework.


No começo, eles provavelmente apenas incluíam e ampliavam os módulos. Mais tarde, eles
polvilharam seu código com pó mágico de metaprogramação, introduzindo o idioma de inclusão e
extensão. Ainda mais tarde, conforme o Rails continuou crescendo, esse idioma começou a ranger
nas bordas - então eles substituíram o include-and-extend pelo ActiveSupport::Concern, pesado
em metaprogramação. Eles desenvolveram seu próprio sistema de gerenciamento de
dependências, um passo de cada vez.

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.

Este livro está cheio de histórias de sucesso de metaprogramação, e ActiveSupport::Concern é


mais uma delas. No entanto, o código complexo de Concern e a natureza levemente controversa
sugerem um lado mais sombrio da metaprogramação. Esse será o assunto do próximo capítulo,
onde veremos a história dos métodos mais infames do Rails.

relatar errata • discutir


Machine Translated by Google

CAPÍTULO 11

A ascensão e queda de alias_method_chain

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

Quando ActiveRecord::Base inclui o módulo Validations , as linhas marcadas reabrem


Base e chamam um método chamado alias_method_chain. Deixe-me mostrar um
exemplo rápido para explicar o que alias_method_chain faz.

relatar errata • discutir


Machine Translated by Google

Capítulo 11. A ascensão e queda de alias_method_chain • 190

A razão para alias_method_chain


Suponha que você tenha um módulo que defina um método de saudação . Pode parecer com
o código a seguir.

part2/greet_with_aliases.rb
módulo Saudações
def

cumprimentar "olá" fim


fim

class MyClass
include Saudações fim

MyClass.new.greet # => "olá"

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

def greet_with_enthusiasm "Ei,


#{greet_without_enthusiasm}!" fim

alias_method :greet_without_enthusiasm, :greet


alias_method :greet, :greet_with_enthusiasm end

MyClass.new.greet # => "Ei, olá!"

Eu defini dois novos métodos: greet_without_enthusiasm e greet_with_enthusiasm. O primeiro


método é apenas um alias da saudação original. O segundo método chama o primeiro método
e também envolve um pouco de felicidade em torno dele. Eu também criei um alias de greet
para o novo método entusiástico - então os chamadores de greet obterão o comportamento
entusiástico por padrão, a menos que o evitem explicitamente chamando greet_without_enthu
siasm :

MyClass.new.greet_without_enthusiasm # => "olá"

Para resumir tudo, a saudação original agora é chamada greet_without_enthusiasm. Se você


deseja o comportamento entusiástico, pode chamar greet_with_enthusiasm ou greet, que na
verdade são aliases do mesmo método.

relatar errata • discutir


Machine Translated by Google

A Ascensão do alias_method_chain • 191

Essa ideia de envolver um novo recurso em um método existente é comum no Rails. Em


todos os casos, você acaba com três métodos que seguem as convenções de nomenclatura
que acabei de mostrar: method, method_with_feature e method_without_fea ture. Os únicos
dois primeiros métodos incluem o novo recurso.

Em vez de duplicar esses aliases em todo lugar, o Rails forneceu um método de


metaprogramação que fez tudo para você. Foi denominado Mod ule#alias_method_chain e
fazia parte da biblioteca Active Support. Estou dizendo "foi" em vez de "é" por razões que
ficarão claras em breve - mas se você olhar dentro do Active Support, verá que
alias_method_chain ainda está lá. Vamos dar uma olhada.

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?

with_method = "#{aliased_target}_with_#{recurso}#{pontuação}" without_method =


"#{aliased_target}_without_#{recurso}#{pontuação}"

alias_method without_method, destino


alias_method destino, with_method

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 recebe o nome de um método de destino e o nome de um recurso


adicional . A partir desses dois, ele calcula o nome de dois novos métodos:
target_without_feature e target_with_feature. Em seguida, ele armazena o destino original
como target_without_feature e alias target_with_feature para target (supondo que um método
chamado target_with_feature seja definido em algum lugar no mesmo módulo).
Por fim, a opção case define a visibilidade de target_without_feature para que seja a mesma
visibilidade do destino original.

relatar errata • discutir


Machine Translated by Google

Capítulo 11. A ascensão e queda de alias_method_chain • 192

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.

Uma última olhada nas validações

Aqui está o código da versão antiga do ActiveRecord::Validations novamente:

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

aumentar RecordInvalid.new(self) end

fim
def válido?
# ...

relatar errata • discutir


Machine Translated by Google

A Queda de alias_method_chain • 193

A validação real acontece no válido? método. Validation#save_with_validation retorna false se a


validação falhar ou se o chamador desabilita explicitamente a validação.
Caso contrário, ele apenas chama o save_without_validation original.
Validation#save_with_validation! gera uma exceção se a validação falhar e, caso
contrário, retorna ao save_with_validation! original.

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

MyClass.new.greet # => "olá"

relatar errata • discutir


Machine Translated by Google

Capítulo 11. A ascensão e queda de alias_method_chain • 194

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

MyClass.ancestors[0..2] # => [MyClass, EnthusiasticGreetings, Saudações]


MyClass.new.greet # => "Ei, olá!"

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={})

perform_validations(opções) ? super : fim falso

# 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={})
# ...

Validation#save executa a validação real (chamando o método privado perform_validations). Se a validação


for bem-sucedida, ela prosseguirá com o procedimento normal

relatar errata • discutir


Machine Translated by Google

A queda de alias_method_chain • 195

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.

No entanto, ainda há um caso em que você pode argumentar que alias_method_chain


funciona melhor do que sua alternativa orientada a objetos. Vamos dar uma olhada.

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

MyClass.new.greet # => "olá"

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

MyClass.ancestors[0..2] # => [MyClass, EnthusiasticGreetings, Object]


MyClass.new.greet # => "olá"

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.

relatar errata • discutir


Machine Translated by Google

Capítulo 11. A ascensão e queda de alias_method_chain • 196

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:

módulo EnthusiasticGreetings def


greet "Ei,
#{super}!" fim

fim

class MyClass
preceder EnthusiasticGreetings end

MyClass.ancestors[0..2] # => [EnthusiasticGreetings, MyClass, Object]


MyClass.new.greet # => "Ei, olá!"

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 .

Uma lição aprendida


Ao longo deste livro, mostrei como a metaprogramação pode ser conveniente, elegante e
interessante. A história de alias_method_chain, no entanto, é um alerta: o código de
metaprogramação às vezes pode ficar complicado e pode até fazer com que você ignore
técnicas mais tradicionais e mais simples. Em particular, às vezes você pode evitar a
metaprogramação e, em vez disso, usar programação simples e antiquada orientada a
objetos.

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

relatar errata • discutir


Machine Translated by Google

Uma Lição Aprendida • 197

seu objetivo do que a metaprogramação. Se a resposta for não, vá em frente e


metaprograme o seu problema. Em muitos casos, no entanto, você descobrirá que uma
abordagem OOP mais direta faz o trabalho tão bem.

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.

relatar errata • discutir


Machine Translated by Google

CAPÍTULO 12

A evolução dos métodos de atributo

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.

Vamos começar com um exemplo rápido de métodos de atributos.

Métodos de atributo em ação


Suponha que você tenha criado uma tabela de banco de dados que contém tarefas.

part2/ar_attribute_methods.rb
requer 'registro_ativo'
ActiveRecord::Base.establish_connection :adapter => "sqlite3",
:banco de dados => "dbfile"

ActiveRecord::Base.connection.create_table :tasks do |t| t.string :descrição


t.boolean :concluído fim

relatar errata • discutir


Machine Translated by Google

Capítulo 12. A Evolução dos Métodos de Atributo • 200

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:

classe Tarefa < ActiveRecord::Base; fim

tarefa = Tarefa.novo
task.description = 'Limpar garagem' task.completed
= true task.save

task.description # => "Limpar garagem" task.completed?


# => verdadeiro

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.

Uma história de complexidade


Em vez de examinar a implementação atual dos métodos de atributo, deixe-me voltar a
2004 — o ano em que o Rails 1.0.0 foi lançado em um mundo desavisado.

Trilhos 1: Começos Simples


Na primeira versão do Rails, a implementação dos métodos de atributo era apenas algumas
linhas de código:

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

relatar errata • discutir


Machine Translated by Google

Uma História de Complexidade • 201

alias_method :respond_to_without_attributes?, :respond_to?

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

def method_missing(method_id, *argumentos)


method_name = method_id.id2name

if method_name =~ read_method? && @attributes.include?($1) return


read_attribute($1) elsif
method_name =~ write_method?
write_attribute($1, arguments[0]) elsif
method_name =~ query_method? return
query_attribute($1) else

super
final
fim

def read_method?() /^([a-zA-Z][-_\w]*)[^=?]*$/ end def write_method?


() /^([a-zA-Z][-_ \w]*)=.*$/ end def query_method?() /^([a-zA-Z][-_\w]*)
\?$/ end

def read_attribute(attr_name) def # ...


write_attribute(attr_name, value) #... def
query_attribute(attr_name) # ...

Dê uma olhada no método initialize : quando você cria um objeto ActiveRecord::Base ,


sua variável de instância @attributes é preenchida com o nome dos atributos do banco
de dados. Por exemplo, se a tabela relevante no banco de dados tiver uma coluna
chamada descrição , @attributes conterá a string "descrição", entre outras.

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).

relatar errata • discutir


Machine Translated by Google

Capítulo 12. A Evolução dos Métodos de Atributo • 202

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.

Rails 2: foco no desempenho

Você se lembra da explicação de method_missing no Capítulo 3, terça-feira: Métodos, na página


45? Quando você chama um método que não existe, Ruby percorre a cadeia de ancestrais
procurando pelo método. Se atingir BasicObject sem encontrar o método, ele começa de volta
na parte inferior e chama method_missing. Isso significa que, em geral, chamar um Método
Fantasma (57) é mais lento do que chamar um método normal, porque Ruby precisa percorrer
toda a cadeia de ancestrais pelo menos uma vez.

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.

Os autores do Rails poderiam resolver esse problema de desempenho substituindo Métodos


fantasmas por Métodos dinâmicos (51) — usando define_method para criar acessadores de
leitura, gravação e consulta para todos os atributos e eliminando totalmente method_missing .
Curiosamente, no entanto, eles optaram por uma solução mista, incluindo métodos fantasmas e
métodos dinâmicos. Vejamos o resultado.

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:

relatar errata • discutir


Machine Translated by Google

Uma História de Complexidade • 203

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

# Se ainda não geramos nenhum método, gere-os e # veja se criamos o método


que estamos procurando. se !self.class.generated_methods?

self.class.define_attribute_methods if
self.class.generated_methods.include?(method_name) return
self.send(method_id, *args, &block) end

fim

# ...
fim

def read_attribute(attr_name) def # ...


write_attribute(attr_name, value) # ... def
query_attribute(attr_name) # ...

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 .

Veremos o define_attribute_methods em um minuto. Por enquanto, tudo o que você precisa


saber é que ele define métodos dinâmicos de leitura, gravação e consulta (51) para todas as
colunas do banco de dados. Na próxima vez que você chamar description= ou qualquer outro
acessador que mapeie para uma coluna de banco de dados, sua chamada não será tratada
por method_missing. Em vez disso, você chama um método real, não fantasma.

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.

O código a seguir mostra como define_attribute_methods define não fantasmagórico


acessadores.

relatar errata • discutir


Machine Translated by Google

Capítulo 12. A Evolução dos Métodos de Atributos • 204

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

define_read_method(name.to_sym, nome, coluna) end

fim

a menos que instance_method_already_implemented?("#{name}=")


if create_time_zone_conversion_attribute?(nome, coluna)
define_write_method_for_time_zone_conversion(name) else

define_write_method(name.to_sym) end

fim

a menos que instance_method_already_implemented?("#{name}?")


define_question_method(nome)
fim
fim
fim

O instance_method_already_implemented? existe um método para evitar


Monkeypatches involuntários (16): se já existir um método com o nome do atributo,
esse código pula para o próximo atributo. Além disso, o código anterior faz pouco
além de delegar a outros métodos que fazem o trabalho real, como de
fine_read_method ou define_write_method.

Como exemplo, dê uma olhada em define_write_method. Marquei as linhas mais


importantes com setas:

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

ÿ def avalia_attribute_method(attr_name, method_definition, method_name=attr_name)


a menos que method_name.to_s == primary_key.to_s
generate_methods << method_name end

relatar errata • discutir


Machine Translated by Google

Uma História de Complexidade • 205

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:

def descrição=(novo_valor);write_attribute('descrição', novo_valor);end

Assim nasce o método description= . Um processo semelhante ocorre para descrição,


descrição? e os acessadores para todas as outras colunas do banco de dados.

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.

Atributos que permanecem


dinâmicos Acontece que há casos em que o Active Record não deseja definir acessadores
de atributos. Por exemplo, pense em atributos que não são apoiados por uma coluna de
banco de dados, como campos calculados:

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:

relatar errata • discutir


Machine Translated by Google

Capítulo 12. A Evolução dos Métodos de Atributo • 206

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

elsif md = self.class.match_attribute_method?(method_name) attribute_name, method_type


= md.pre_match, md.to_s if @attributes.include?(attribute_name)
__send__("attribute#{method_type}", attribute_name, *args,
&block) else

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

# Handle *= para method_missing. def


attribute=(attribute_name, valor)
write_attribute(attribute_name, valor) fim

Veja o código em method_missing acima. Se você estiver acessando o identificador do


objeto, ele retornará seu valor. Se você estiver chamando um acessador de atributo, ele
chama o acessador com um Dynamic Dispatch (49) (para acessadores de gravação ou
consulta) ou uma chamada direta para read_attribute (para acessadores de leitura). Caso
contrário, method_missing envia a chamada para a cadeia de ancestrais com super.

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.

relatar errata • discutir


Machine Translated by Google

Uma História de Complexidade • 207

Rails 3 e 4: Mais casos especiais


No Rails 1, os métodos de atributo foram implementados com algumas dezenas de linhas de
código. No Rails 2, eles tinham seu próprio arquivo e centenas de linhas de código. No Rails 3,
eles abrangiam nove arquivos de código-fonte, sem incluir os testes.

À 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.)

Vou começar com o código de dentro da implementação dos métodos de atributo:

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

relatar errata • discutir


Machine Translated by Google

Capítulo 12. A Evolução dos Métodos de Atributo • 208

Este código define um método denominado define_method_attribute. Esse método


acabará se tornando um método de classe de ActiveRecord::Base, graças ao mecanismo
que discutimos no Capítulo 10, Módulo Concern do Active Support, na página 179.
Aqui, no entanto, vem uma reviravolta: define_method_attribute é definido de forma
diferente, dependendo do resultado do Module.methods_transplantable? método.

Module.methods_transplantable? vem da biblioteca Active Support e responde a uma


pergunta muito específica: posso vincular um UnboundMethod a um objeto de uma classe
diferente? Em Unbound Methods, na página 94, eu mencionei que você só pode fazer
isso a partir do Ruby 2.0 em diante, então este código define define_method_at tributo
de duas maneiras diferentes dependendo se você está rodando Rails em Ruby 1.9 ou 2.x.

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.

(A chamada para generate_attribute_methods pode parecer confusa - ela retorna uma


Clean Room (87) que serializa as definições de método que ocorrem em diferentes threads.)

Vamos ver como ReaderMethodCache é inicializado. O longo comentário dá uma ideia


de como deve ter sido complicado escrever este código:

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.

# # Também estamos definindo uma constante para manter a string congelada do #


nome do atributo. Usar uma constante significa que não temos # para alocar um objeto
em cada chamada ao método de atributo.
# Torná-lo congelado significa que ele não será enganado quando usado para # digitar
@attributes_cache em read_attribute.

relatar errata • discutir


Machine Translated by Google

Uma História de Complexidade • 209

def method_body(method_name, const_name)


<<-EOMETODO
def #{method_name}
nome = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name} read_attribute(name)
{ |n| missing_attribute(n, chamador) } fim

EOMETODO
fim
}.novo

ReaderMethodCache é uma instância de uma classe anônima — uma subclasse de AttributeMethodCache.


Esta classe define um único método que retorna uma String de Código (141). (Se você está perplexo com a
chamada para Class.new, dê uma olhada em Quiz: Class Taboo, na página 112. Se você não entender as
linhas EOMETHOD , leia sobre “documentos here” em The REST Client Example, na página 141.)

Vamos deixar ReaderMethodCache por um momento e passar para a definição de seu

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

relatar errata • discutir


Machine Translated by Google

Capítulo 12. A Evolução dos Métodos de Atributos • 210

Primeiro, veja AttrNames: é um módulo com um único método, set_name_cache.


Dado um nome e um valor, set_name_cache define uma constante nomeada
convencionalmente com esse valor. Por exemplo, se você passar a string "descrição",
ele definirá uma constante chamada ATTR_description. AttrNames é um pouco
semelhante a uma Sala Limpa (87); ele existe apenas para armazenar constantes que
representam os nomes dos atributos.

Agora vá para AttributeMethodCache. Seu método [] recebe o nome de um atributo e


retorna um acessador para esse atributo como um UnboundMethod. Ele também cuida
de pelo menos um caso especial importante: os acessadores de atributos são métodos
Ruby, mas nem todos os nomes de atributos são nomes de métodos Ruby válidos.
(Você pode ler um contra-exemplo no comentário para ReaderMethod Cache#method_body
acima.) Esse código resolve esse problema decodificando o nome do atributo para uma
sequência hexadecimal e criando um nome de método seguro convencional a partir dele.

Depois de ter um nome seguro para o acessador, AttributeMethodCache#[] chama


method_body para obter uma string de código que define o corpo do acessador e define
o acessador dentro de uma sala limpa chamada simplesmente @module. (Discutimos
argumentos adicionais para method_eval, como __FILE__ e __LINE__, em The irb
Example, na página 144.) Por fim, AttributeMethodCache#[] obtém o método acessador
recém-criado da Clean Room e o retorna como um UnboundMethod.

Nas chamadas subseqüentes, AttributeMethodCache#[] não precisará mais definir o


método. Em vez disso, @method_cache.compute_if_absent armazenará o resultado e o
retornará automaticamente. Essa política economiza algum tempo nos casos em que o
mesmo acessador é definido em várias classes.

Para fechar o loop, reveja o código de ReaderMethodCache. Substituindo method_body


e retornando a string de código para um acessador de leitura, ReaderMethod Cache
transforma o AttributeMethodCache genérico em um cache para acessadores de leitura.
Como você pode esperar, há também uma classe WriterMethodCache que cuida da gravação
acessadores.

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.

Uma lição aprendida

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

relatar errata • discutir


Machine Translated by Google

Uma Lição Aprendida • 211

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.

Pense na otimização em Rails 2: Focus on Performance, na página 202.


A maioria dos acessadores de atributo, em particular aqueles que são apoiados por tabelas de banco
de dados, começam suas vidas como Ghost Methods (57). Quando você acessa um atributo pela
primeira vez, o Active Record aproveita para transformar a maioria desses fantasmas em Métodos
Dinâmicos (51). Alguns outros acessadores, como acessadores de campos calculados, nunca se
tornam métodos reais e permanecem fantasmas para
sempre.

Este é um dos vários designs possíveis. Os autores do Active Record não faltaram alternativas,
incluindo as seguintes:

• Nunca defina os acessadores dinamicamente, confiando exclusivamente nos métodos fantasmas.

• Defina acessadores ao criar o objeto, no método initialize .

• 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.

• Defina os acessadores com define_method em vez de uma String de Código.

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

relatar errata • discutir


Machine Translated by Google

Capítulo 12. A Evolução dos Métodos de Atributos • 212

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.

relatar errata • discutir


Machine Translated by Google

CAPÍTULO 13

Uma última lição

Estivemos juntos em uma aventura ousada, começando com o básico da metaprogramação e


fechando com um tour pelo código-fonte do Rails. Antes de partirmos, há um último insight que vou
compartilhar — possivelmente o mais importante de todos.

Metaprogramação é apenas programação


Quando comecei a aprender metaprogramação, parecia mágica. Senti vontade de deixar minha
programação habitual para trás para entrar em um novo mundo - um mundo que era surpreendente,
emocionante e, às vezes, um pouco assustador.

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.”

relatar errata • discutir


Machine Translated by Google

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:

puts 'Olá, mundo'

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:

relatar errata • discutir


Machine Translated by Google

Apêndice 1. Idiomas Comuns • 218

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"

Escrever obj.my_attribute = 'some value' é o mesmo que escrever obj.my_attribute=('some


value'), mas parece mais limpo.

Como devemos chamar métodos disfarçados como my_attribute e my_attribute=?


Vamos seguir uma sugestão da zoologia: diz-se que um animal que se disfarça de outra
espécie emprega “mimetismo”. Seguindo esse padrão, uma chamada de método que se
disfarça como outra coisa, como puts ou obj.my_attribute=, pode ser chamada
Feitiço: Método de mímica um método de mímica.

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:

class Help < R '/help' def get


#
renderização para HTTP GET...

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.)

relatar errata • discutir


Machine Translated by Google

Guardas do Nil • 219

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.

relatar errata • discutir


Machine Translated by Google

Apêndice 1. Idiomas Comuns • 220

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

obj.set_attribute 11 # Nenhum erro!


obj.send :my_attribute # => 11

relatar errata • discutir


Machine Translated by Google

Guardas do Nil • 221

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.

Nil Guards e valores booleanos

Os Nil Guards têm uma peculiaridade que vale a pena mencionar: eles não funcionam bem com
valores booleanos. Aqui está um exemplo:

def calculate_initial_value coloca


"chamado: calculate_initial_value" false

fim

b = zero
2. vezes fazer
b ||= calcular_valor_inicial fim

ÿ chamado: calcula_inicial_valor chamado:


calcula_inicial_valor

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.

relatar errata • discutir


Machine Translated by Google

Apêndice 1. Idiomas Comuns • 222

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'

conn = Faraday.new("https://twitter.com/search") do |faraday|


faraday.response :logger
faraday.adapter Faraday.default_adapter faraday.params["q"]
= "ruby" faraday.params["src"] = "typd"
end

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 :

relatar errata • discutir


Machine Translated by Google

Auto Rendimento • 223

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

# ...

As coisas interessantes acontecem em Faraday::Connection#initialize. Este método aceita


um bloco opcional e gera o objeto Connection recém-criado para o bloco:

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"

Cadeias de chamadas são desaprovadas na maioria dos idiomas (e às vezes chamadas de


“desastres de trem”). A sintaxe concisa do Ruby torna as cadeias de chamadas geralmente
mais legíveis, mas elas ainda apresentam um problema: se você tiver um erro em algum
lugar ao longo da cadeia, pode ser difícil rastreá-lo.

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):

relatar errata • discutir


Machine Translated by Google

Apêndice 1. Idiomas Comuns • 224

temp = ['a', 'b', 'c'].push('d').shift coloca temp x =

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:

['a', 'b', 'c'].push('d').shift.tap {|x| coloca x }.upcase.next

ÿ 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.

Olha esse código:

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

relatar errata • discutir


Machine Translated by Google

Symbol#to_proc() • 225

Você deseja converter o símbolo em um bloco de uma chamada como este:

{|x| x.maiúsculas }

Como primeiro passo, você pode adicionar um método à classe Symbol , que converte o símbolo
em um objeto Proc :

class Symbol ÿ def


to_proc ÿ Proc.new {|x|
x.send(self) } ÿ end

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:

nomes = ['bob', 'bill', 'heather']


ÿ names.map(&:capitalize.to_proc) # => ["Bob", "Bill", "Heather"]

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:

nomes = ['bob', 'bill', 'heather']


ÿ names.map(&:capitalize) # => ["Bob", "Bill", "Heather"]

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:

# sem Symbol#to_proc: [1, 2, 5].inject(0)


{|memo, obj| memo + obj } # => 8

# com Symbol#to_proc: [1, 2,


5].inject(0, &:+) # => 8

# legal!

relatar errata • discutir


Machine Translated by Google

APÊNDICE 2

Idiomas específicos do domínio

Linguagens específicas de domínio são um tópico popular atualmente. Eles se sobrepõem um


pouco à metaprogramação, então você provavelmente vai querer saber uma ou duas coisas
sobre eles.

O Caso para Linguagens de Domínio Específico


Você tem idade para se lembrar de Zork? Foi uma das primeiras “aventuras de texto”: jogos de
computador baseados em texto que eram populares no início dos anos 80. Aqui estão as
primeiras linhas de um jogo de Zork:

ÿ 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

relatar errata • discutir


Machine Translated by Google

Apêndice 2. Linguagens Específicas de Domínio • 228

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
;

westOfHouse : Sala 'Oeste da casa'


"Você está parado em um campo aberto a oeste de uma casa
branca, com uma porta da frente fechada."
;

+ caixa de correio : OpenableContainer 'caixa de correio' 'pequena caixa de correio';

++ folheto : Coisa 'folheto' 'folheto';

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

relatar errata • discutir


Machine Translated by Google

DSLs Internos e Externos • 229

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.

DSLs internas e externas


Vejamos um exemplo de uma DSL que na verdade é uma GPL disfarçada. Aqui está um
trecho de código Ruby que usa a gem Markaby para gerar HTML:

dsl/markaby_example.rb
requer 'markaby'

html = Markaby::Builder.new do head


{ title "Minha maravilhosa página inicial" } body do
h1 "Bem-
vindo à minha página inicial!" b "Meus
hobbies:" ul do

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,

relatar errata • discutir


Machine Translated by Google

Apêndice 2. Linguagens Específicas de Domínio • 230

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.

Ainda assim, metaprogramação e DSLs têm um relacionamento próximo no mundo Ruby.


Para construir uma DSL interna, você deve modificar a própria linguagem, e isso requer muitas
das técnicas descritas neste livro. Dito de outra forma, a metaprogramação fornece os tijolos
necessários para construir DSLs. Se você estiver interessado em DSLs Ruby internas, este livro
contém informações importantes para você.

relatar errata • discutir


Machine Translated by Google

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

Chame a versão anterior com alias de um método de um método redefinido.

classe String
alias_method :old_reverse, :reverse

def reverso
"x#{old_reverse}x" fim

fim

"abc".reverse # => "xcbax"

Para obter mais informações, consulte a página 134.

Lousa em branco

Remova métodos de um objeto para transformá-los em métodos fantasmas (57).

classe C
def method_missing(nome, *args)
"um método fantasma"
fim
fim

relatar errata • discutir


Machine Translated by Google

Apêndice 3. Livro de Feitiços • 232

obj = C.new
obj.to_s # => "#<C:0x007fbb2a10d2f8>"

class D < BasicObject def


method_missing(name, *args)
"um método fantasma"
fim
fim

blank_slate = D.new
blank_slate.to_s # => "um método fantasma"

Para obter mais informações, consulte a página 66.

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

C.my_method # => "um método de classe"

Para obter mais informações, consulte a página 130.

Variável de Instância de Classe

Armazene o estado de nível de classe em uma variável de instância do objeto Class .

classe C
@my_class_instance_variable = "algum valor"

def self.class_attribute
@my_class_instance_variable end

fim

C.class_attribute # => "algum valor"

Para obter mais informações, consulte a página 109.

relatar errata • discutir


Machine Translated by Google

Os Feitiços • 233

Macro de classe

Use um método de classe em uma definição 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

Para obter mais informações, consulte a página 117.

Quarto limpo

Use um objeto como um ambiente para avaliar um bloco.

classe CleanRoom
def a_useful_method(x); x fim * 2; fim

CleanRoom.new.instance_eval { a_useful_method(3) } # => 6

Para obter mais informações, consulte a página 87.

Processador de código

Processar strings de código (141) de uma fonte externa.

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

Para obter mais informações, consulte a página 144.

relatar errata • discutir


Machine Translated by Google

Apêndice 3. Livro de Feitiços • 234

Sonda de Contexto

Execute um bloco para acessar informações no contexto de um objeto.

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"

Para obter mais informações, consulte a página 85.

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

Para obter mais informações, consulte a página 89.

Despacho Dinâmico
Decida qual método chamar em tempo de execução.

method_to_call = :reverse obj =


"abc"

obj.send(method_to_call) # => "cba"

Para obter mais informações, consulte a página 49.

relatar errata • discutir


Machine Translated by Google

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"

Para obter mais informações, consulte a página 51.

Proxy Dinâmico

Encaminha chamadas de método dinamicamente para outro objeto.

classe MyDynamicProxy
def initialize(target) @target
= target end

def method_missing(nome, *args, &block)


"resultado: #{@target.send(nome, *args, &bloco)}" fim

fim

obj = MyDynamicProxy.new("uma string")


obj.reverse # => "resultado: gnirts a"

Para obter mais informações, consulte a página 60.

Escopo Plano

Use um fechamento para compartilhar variáveis entre dois escopos.

classe C
def an_attribute @attr
end

fim

obj = C.new
a_variable = 100

relatar errata • discutir


Machine Translated by Google

Apêndice 3. Livro de Feitiços • 236

# escopo plano:
obj.instance_eval do @attr =
a_variable end

obj.an_attribute # => 100

Para obter mais informações, consulte a página 83.

Método Fantasma

Responda a uma mensagem que não tenha um método associado.

classe C
def method_missing(name, *args)
name.to_s.reverse end

fim

obj = C.new
obj.my_ghost_method # => "dohtem_tsohg_ym"

Para obter mais informações, consulte a página 57.

Método Gancho

Substituir um método para interceptar eventos de modelo de objeto.

$HERANÇAS = []
classe C
def self.herdado(subclasse)
$INHERITORS << subclasse end
end

classe D<C
fim

classe E<C fim

classe F<E
fim

$HERANÇAS # => [D, E, F]

Para obter mais informações, consulte a página 157.

relatar errata • discutir


Machine Translated by Google

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

a_method # => "um método do kernel"

Para obter mais informações, consulte a página 32.

Variável de instância preguiçosa


Aguarde até o primeiro acesso para inicializar uma variável de instância.

classe C
atributo def
@attribute = @attribute || "algum valor" fim

fim

obj = C.new
obj.attribute # => "algum valor"

Para obter mais informações, consulte a página 221.

Método de imitação

Disfarçar um método como outra construção de linguagem.

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

Para obter mais informações, consulte a página 218.

relatar errata • discutir


Machine Translated by Google

Apêndice 3. Livro de Feitiços • 238

Monkey Patch
Altere os recursos de uma classe existente.

"abc".reverse # => "cba"

classe String
def reverso
"sobrepor"
fim
fim

"abc".reverse # => "substituir"

Para obter mais informações, consulte a página 16.

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

Para obter mais informações, consulte a página 23.

guarda nulo
Substitua uma referência a nil por um “ou”.

x = zero
y = x || "um valor" # => "um valor"

Para obter mais informações, consulte a página 219.

relatar errata • discutir


Machine Translated by Google

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

class << obj


include M
fim

obj.my_method # => "um método singleton"

Para obter mais informações, consulte a página 130.

Aula aberta
Modifique uma classe existente.

class String def


my_string_method "meu
método" end

fim

"abc".my_string_method # => "meu método"

Para obter mais informações, consulte a página 14.

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

"abc".reverse # => "xcbax"

Para obter mais informações, consulte a página 136.

relatar errata • discutir


Machine Translated by Google

Apêndice 3. Livro de Feitiços • 240

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

"abc".reverse # => "cba"


using MyRefinement
"abc".reverse # => "meu reverso"

Para obter mais informações, consulte a página 36.

Invólucro de Refinamento

Chame um método não refinado de seu refinamento.

module StringRefinement
refine String do def
reverse
"x#{super}x"
end
fim
fim

usando StringRefinement
"abc".reverse # => "xcbax"

Para obter mais informações, consulte a página 135.

Caixa de areia

Execute código não confiável em um ambiente seguro.

def sandbox(&code)
proc
{ $SAFE =
2

yield }.call end begin sandbox { File.delete


'a_file' } rescue Exception
=> ex ex # => #<SecurityError: Insecure operation at level 2> end

Para obter mais informações, consulte a página 149.

relatar errata • discutir


Machine Translated by Google

Os Feitiços • 241

Escopo Portão

Isole um escopo com a palavra-chave class, module ou def .

a=1
definiram? a # => "variável local"

módulo MyModule
b=1
definido? a # => nil
definido? b # => "variável local" end

definiram? a # => "variável local"


definiram? b # => nulo

Para obter mais informações, consulte a página 81.

Auto Rendimento

Passe self para o bloco atual.

classe Pessoa
attr_accessor :nome, :sobrenome

def inicializar
render auto
-fim
fim

joe = Person.new do |p|


p.name = 'Joe'
p.surname = 'Smith' end

Para obter mais informações, consulte a página 223.

Escopo Compartilhado

Compartilhe variáveis entre vários contextos no mesmo Flat Scope (83).

lambda
{ compartilhado = 10
self.class.class_eval faça
define_method :counter do end

compartilhado define_method :down do


compartilhado -= 1
fim
fim
}.chamar

relatar errata • discutir


Machine Translated by Google

Apêndice 3. Livro de Feitiços • 242

contador # => 10
3.times { down }
contador # => 7

Para obter mais informações, consulte a página 84.

Método Singleton
Defina um método em um único objeto.

obj = "abcc"

class << obj def


my_singleton_method
"x"
fim
fim

obj.my_singleton_method # => "x"

Para obter mais informações, consulte a página 114.

Cadeia de Código
Avalie uma string de código Ruby.

my_string_of_code = "1 + 1"


eval(my_string_of_code) # => 2

Para obter mais informações, consulte a página 141.

Symbol To Proc
Converte um símbolo em um bloco que chama um único método.

[1, 2, 3, 4].map(&:mesmo?) # => [falso, verdadeiro, falso, verdadeiro]

Para obter mais informações, consulte a página 225.

relatar errata • discutir


Machine Translated by Google

Índice

métodos_transplantáveis? método append_features , 183– 187


SÍMBOLOS &
método, 208–210
operador, blocos, 89, 225 :: notação, Exemplo de UnboundMethod , 95 aridade de
caminhos constantes, plano de desenvolvimento do argumentos,
22 93 blocos, 74
método add_checked_attribute ,
@ caractere, configurações e variáveis, 140 método eval , 141–157 aridade, 93
99 @@ questionários, 150–157, 160 Sobre pseudônimos
prefixo, para variáveis de classe, remoção do método eval , 153
método alias_method_chain ,
110 193
|| operadora, Nil Guards, 219, palavra-chave alias , 132 vs. substituição, 159, 190
238 método alias_method , 132 questionário,
-> operador (stabby lambda), método alias_method_chain , 189– 137 respond_to? método, 202
89 197 spell book, 231

Aliases, ao redor, veja ao redor using, 132–135 Array


{} caracteres, blocos, 74
Apelido class grep
A método allocate , 19
method, 17, 55 herança, 20
métodos de acesso, 7, 116, 200–212
exemplo de rótulo alfanumérico, 12–16,
matrizes
36
explorador de array, 146–148
exemplo de contabilidade, 46–48, 52– métodos de atributo
55, 60–64, 69 método grep , 17, 55
de cadeia de ancestrais, 202
herança, 20
Biblioteca Action Pack, 168 includers, 186
elementos de substituição, 15
Biblioteca de modelos ativos, 173 pesquisa de método, 29–33, 55,
método attr_accessor
121
Biblioteca do Active Record Macros de classe, 116–118
sobre, 7, 168 método method_missing , 202
questionários, 150–157
métodos de atributo, 199– 212 regras de modelo de objeto, 126
revisão, 128–129
prepending, 30–32, 135,
195 método attr_checked vs.
design, exemplo de
exemplo print_to_screen , 40 add_checked_attribute, 151,
banco de dados de filmes 171–178 ,
removendo de, 95 classes 156 plano
6
singleton, 122– 123 de desenvolvimento, 140– 141
Módulo de validações , 175,
179–197
questionários, 156, 160
método ancestral , 30 e
Biblioteca de suporte ativo revisão, 151
sobre, 168 operador, blocos, 89, 225 classes e
método attr_reader , 117
alias_method_chain, 189–197 constantes anônimas, 113
Módulo de carregamento automático , 173
método attr_writer , 117
Módulo de preocupação , 179, métodos de atributo, 199–212
Linguagem das formigas, 228–229
183–188 Módulo AttributeMethodCache , 209
ANTL, 228
método ap , 33
Módulo AttributeMethods , 202
Machine Translated by Google

Í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

25 regras de símbolos, 49, 224, 242 método evolucionários, 210


modelo de objeto, 126 objetos do contador , 84 flexibilidade, 177, 188
como, 19–21, 24 opção de simplicidade, 196, 212
chaves para blocos, 74 classe ou princípios, 177
namespace clássico , 24 módulo atual, 106–
diretórios, módulos e classes
Classe ClassMethods mais ganchos, 109
como, 22
159, 180
objeto atual, 34, 213, consulte também
Método de descarte , 75
Salas limpas a palavra-chave self
blocos, 87, 233 idiomas específicos de domínio
eventos, 102 D (DSLs), 97–103, 227–230
níveis seguros, 149 Classe de data , teste e, 111 cadeias notação de ponto, 48
serialização, 208 palavras-chave do…end ,
de chamadas
livro de feitiços, 233 de depuração, 223 74 exemplo de classe Duck ,
fechamentos Métodos fantasmas, 70 171 digitação de pato,
Objetos de encadernação , method_missing method, 64–
143 blocos como, 77–84, 235 115 duplicação, evitando
66
contabilidade exemplo, 46–48,
código, consulte também Guardas do Nilo, 222
52–55, 60–64, 66, 69
convenções de notação de Aulas Abertas, 15
design, xx precedência e Erguer e vincular objetos,
Métodos dinâmicos, 48–55
refinamentos, 38 143
method_missing method, 48, 55–
Código-fonte do Rails, 168 desacoplamento, 177 71
geradores de código, 8 palavra-chave Vantagens do Ruby, xvii
injeção de código, 146–150, 153 def comparada com class_eval, Despacho Dinâmico, 48–55, 147,
Processadores de Código, 144, 149, 108 vs. define_method, 203, 234
233 51 vs. Métodos Dinâmicos,
linguagens dinâmicas
153
caractere de dois pontos, caminhos duck typing, 115 falta
Scope Gates, 81, 241
constantes, de checagem de tipo, 45
Métodos Singleton, 116
22 tempo de Métodos dinâmicos
Avaliação Adiada, 89–93,
métodos de acesso, 200
compilação, 8 234
métodos de atributo, 202–206
compiladores, 8, 45 concluídos= método define_attribute_methods ,
método, 200 concluídos? 203
evitando duplicação, 48–
método, 200 Classe de computador , métodos de atributo de 55
evitando duplicação exemplo, 46– método define_method , 200, vs. palavra-chave def , 153
48, 52–55, 60–64, 66, 69 202–206 definição, 51
evitando duplicação, 53 depreciação, 118
Machine Translated by Google

Índice • 246

vs. método eval , 147 vs. I


Ghost Methods, 70 spell book, Comando G gem , xx expressões idiomáticas, comuns, 217–
235 Dynamic 225, ver também feitiços
linguagens de uso geral
Proxies, 58–64, 66, (GPLs), 228–230 palavra-chave if ,
235
método 219 imutabilidade de símbolos, 49
@@dynamic_methods, 202 generate_attribute_methods , 208 importação
métodos_gerados? método, 203 de arquivos e níveis seguros,
E
geradores, código, 8 150 bibliotecas, 26
autoclasses, veja singleton
Aulas método get , cliente REST, 141 método inc , 84
incluir método
encapsulamento, 51, 85, 183 exemplo Ghee, 58–60
métodos Ghost Biblioteca do Active Record,
palavra-chave final , 74 cadeia de 175 ancestrais, 30–32
sobre, 56
exemplo de biblioteca ERB, 149 Módulo Concern , 183–188
biblioteca Active Record, 172
método de avaliação ganchos, 159
métodos de atributo, 200–206
add_checked_attribute truques de incluir e estender,
método, 141–157 179–183
cuidados, 65 vs.
Vinculando objetos, 143–145 Extensões de objeto, 130
métodos dinâmicos, 70 proxies
desvantagens, 145–150 exemplo truque de incluir e estender, 179–
dinâmicos, 58 –64 confrontos
irb, 144 removendo, de nomes, 66–69 quiz, 64– 183
153 método incluído vs.
66 livro de
Strings of Code, 141–157 objetos append_features, 184
feitiços, 231, 236 gists, 58
corrompidos, 148–150 atributos ganchos, 158–159, 180
validados, 140, 150–152 GitHub,
truque de inclusão e extensão,
exemplo Ghee, 58–60 variáveis
179–183
Avaliação, diferida, 89-93, globais, 80, 101 GPLs (línguas
recuo, xx
234 de uso geral), 228–230 Great
herança
método de evento , 100 Unified teoria do rubi,
métodos privados, 35
eventos classes singleton, 123–
125
configurações e variáveis, 99 dados 125
de compartilhamento, 98, 102 método greet , 190, 194–195 método superclasses, 19, 27
design evolutivo, 210 método greet_with_enthusiasm , método herdado , 157
190 inicializar método
explore_array , 146–148 método
extend , 131, 175, 179– método greet_without_enthusiasm , métodos de atributo, 201
183 190 Nil Guards, 221
método estendido , 158, 183 método grep , 17, 55 reduzindo a duplicação, 55

Extensões, Classe, 130, 180, 185, escopos internos, 79


232
H instalando
notação hash, xx
Extensões, Objeto, 130, 239 DSLs código com comando gem ,
Hashie, 57, 59 xx
externas, 229
heredocs, 142, 209 Trilhos, 168

F classe de métodos de instância, veja também


hierarquias, no método tr_accessor
Exemplo de Faraday, 222
110 modelo de objeto, 125 biblioteca Active Record e,
variável de recurso , 191 176
Exemplo HighLine, 91
argumento de arquivo , vinculando objetos, classes e, 18–20, 24 ganchos,
144 Livro de feitiços
158, 180
Hook Methods, 236
com métodos padrão, 159
constantes de arquivos
como, 22 avaliação e segurança,
atributos validados, 141, 157–
150
162, 180, 184
Método Fixnum#+ , 137
HTTP
Escopos Planos, 82–84, 98, 108,
Exemplo de Faraday, 222
235
Cliente REST, 142
instance_method_already_implemented?,
linguagens de programação funcional, videocassete, 159
204 métodos e, 18, 24
73
convenções de notação, xx objetos UnboundMethod , 94 v
Machine Translated by Google

Índice • 247

diagrama, 18 método de carregamento , 26, M


linguagens dinâmicas, 27 95,
objeto principal , 36, 80
inicializando com Nil 133, 150 método local_variables ,
tornar a linguagem, 228, 230
Guards, 221 79 pesquisa de método, 32
Joia Markaby, 229
Lazy Instance Variable, 221, Objetos de método , 94
237 níveis procs e lambdas, 89– 93, 100, Mash class, 57, 59 teste
seguros, 149 auto 149, 224, 234, 242 de matemática, 136
palavra-chave, 34, 80, 86 de requerem
Ruby Interpreter do Matz, atributo xxi
nível superior, 79–80, 100, método, 26, 133, 150 segurança,
memory_size , exemplo de 49
102 150
exibindo, 17 mensagens do dia,
26
blocos de método Escopos compartilhados,
instance_eval , 85– 84 método singleton_method , 94 metaclasses, veja classes singleton
88 Builder, 68 livro de feitiços, 237
em comparação com class_eval, método tap , 223 metaprogramação sobre,
108 Self Yield, 223 usando palavra-chave, 75– xvii definido,
classes singleton, 127 Strings 77 variável de classe , 151 3, 7–8, 163
of Code vs. blocks, 145 DSLs e, 230
L flexibilidade, 188
método instance_exec , 86 exemplo de programa de rato de programação como, 8, 163, 213
laboratório, 121, 123
método instance_method , 94
método lambda , 89, 92 execução do método, 28, 33–36
instance_method_already_implement
ed? método, 204 lambda? método, 92 direção de pesquisa
de método, 30, 118, 121, 126
método instance_variable_get , 154 lambdas, 88–89, 91–93, 102,
234
método instance_variable_set , 27, 154 modelo de objeto, 28–33, 55
construções de linguagem, 3, 7 revisão, 121
ficção interativa, 228 DSLs linguagens classes singleton, 118–122
internas, 229 intérpretes flexionando Ruby, xvii
domínio específico (DSLs), 97–
sobre, xxi method method, 94
103, 227–230
Method objects, 94–96
programação funcional,
Pry, 49 73 method_added method, 158
Cliente REST, 141
uso geral (GPLs), 228–230 method_body method, 210
introspecção, 3, 7, 54 irb estático,
@method_cache.compute_if_absent,
45, 70 verificação 210
Vinculando objetos, 144 de tipo, 45
method_missing métodos de
métodos de listagem, 15 Variável de instância preguiçosa, 221, atributo de método, 200– 206
sessões aninhadas, 144 237
exibindo a si mesmo, 35
bibliotecas, importação, evitando duplicação, 48,
55–71
J argumento de 26 linhas , vinculação de objetos,
144 Tábuas em branco, 66–69
Java
Proxies dinâmicos, substituição
tempo de compilação/tempo de listando
de 58–64 , questionário
execução, 8 idiomas exemplos separados por vírgulas,
de 56–64 ,
específicos de 91
recursos de 64–
domínio, 230 visibilidade constantes, 23
aninhada, 79 campos estáticos, 109–110 métodos em irb, 15 66 , método 70 method_removed ,

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

métodos de acesso, 7, 116 herança, 20 método nomenclatura, veja também Around


alias_method_chain, 189–197 instance_method , 94 método Apelido
métodos de atributo, 199– 212 method_added , 158 método Biblioteca Active Record, 173
method_removed , 158 método classes anônimas, 113 caches de
método clichê, 45 como objetos métodos de atributo, 210
chamáveis, 88, 94–96 classes method_undefined , 158 blocos, 89
e, 19, classes, 25
24 classe atual, 107 method_transplantable? constantes, 21,
diagrama, 18 method, 208–210 63 depreciação, 117
prepend method, 30–32, 135,
Métodos de Gancho, 141, 157-162, 195 Métodos fantasmas, 66–69
180, 184, 236 removendo métodos, 67 palavra- métodos e classes
HTTP, 141 chave do módulo Macros, 117
métodos de instância e, 18, 24 current class, 107 Scope métodos e símbolos, 49 métodos
listagem, Gates, 81, 241 ausentes, 57 classes
15 execução singleton, 122, 126
Module#constants, 23
de método, 28,
33–36 Module.constants, 23
sessões irb aninhadas, 144
nomenclatura, 49, 57, 117 método module_eval , 107
escopos lexicais aninhados, consulte Flat
convenções de notação, xx regras método module_exec , 108
Visibilidade
de modelo de objeto, 125 módulos, veja também classes
aninhada de escopos, 79
persistência, 175 biblioteca Active Record e,
métodos privados, 35, 51, 134, 176 métodos de aninhamento ,
220 classes como, 20, 24 23 novos métodos, 19, 82, 113, 209
redefinição, 133 módulo atual, 106–109 definindo, método new_toplevel , 149
remoção, 67–69, 153 métodos 81, 105 Nil Guards, 111, 202, 219– 222, 238
reservados, 68 sintaxe, 126 Métodos de gancho, 158–159
UnboundMethod classes de herança e singleton,
convenções de notação, xx
objetos, 94–96, 207–210 125 pesquisa de
Método numérico#to_money , 14
método, 30–33 regras de
métodos_transplantáveis? método, modelo de objeto, 125
208–210 Refinamentos, 37
O
Scope Gates, 81 aulas Object class
Microsoft Office, 228
individuais e 36 define_singleton_method
Métodos de imitação
method, 114
classes singleton e,
método attr_accessor , 116, 151, extend method, 131, 175,
129–131
154 métodos 179–183
de atributo, 200 livro de feitiços, Gema de dinheiro ,
herança, 20 método
237 using, 217–219 exemplo de utilitário de 14 monitores, 97– instance_eval , 68, 85–88, 108,
@module Clean 103
127, 145, 223
Room, 210 Monkeypatching com
classe de módulo Around Aliases, 134 cuidados, 15, instance_variable_get method, 154
método alias_method , 132 17 confrontos de
método append_features , nomes, 25 métodos de instance_variable_set method, 27,
183–187 prevenção em atributos, 204 154 pry
método attr_accessor , 116– 118, livros de feitiços, method, 143 send
128, 150–157 constantes, 238 exemplos de method, 48–49, 51 singleton_class
23 método banco de dados de filmes, 4–7 method, 120 untaint method, 149
define_method , 51, 53, 83, 95, ressonância magnética, xxi

200, 202– 206 Object Extensions, 130, 239 classes de


N modelo de objeto
método extend , 175 confrontos de nomes como objetos, 19–21, 24
método estendido , 158, 183
Métodos fantasmas, 66–69
método include , 30–32, 130,
Namespaces, 25, 238 constantes, 21–24
159, 183–188 truque de
Namespaces, 23, 25–26, 63, 173, 238 conteúdos, 16–19
incluir e estender, 179–183 método
definidos, 11
incluído ,
métodos de instância e, 24 variáveis
158–159, 180, 184
de instância e, 17–18
Machine Translated by Google

Índice • 249

execução de método, 33–36 métodos de persistência, 175 pós R


pesquisa de método, 28–33 método, 142 Rails, veja também a biblioteca Active
métodos e, 17–18 Record; Biblioteca do ActiveSupport
precedência e refinamentos,
questionários, 26–27, 39–42 38
Refinamentos, revisão 36– Biblioteca Action Pack, 168
39 , 24, 42 regras, método precedente , xxi, 30–32, 135,
alias_method_chain, 189–197
195
125 classes métodos de atributo, 199– 212
singleton e herança, 123–125 Wrappers pré-anexados, 135, 195,
239 método
ClassMethods-plus-hook id iom,
mapeamento objeto-relacional, 171 de impressão , 32 160
exemplo print_to_screen , 40 Módulo de preocupação , 179,
183–188
objetos palavra-chave privada ,
classes como objetos, 19–21, instalando, 168
35 método privado , 218
24 objeto visão geral, 167–169
métodos privados recursos, 168
atual, 34 definido, 24
Em torno de Aliases, 134
objeto principal , gema de trilhos , 168
self e, 35, 220 send
36, 80 Exemplo de módulo Rake , 23
method, 51
Extensões de objeto, 130,
método proc , 89 exemplo rake2thor , 133
239
acessadores de leitura, 117, 200–
regras de modelo de objeto, 125 procedimento

Salas Limpas, 149 201, 210


Auto Rendimento, 222–224, 241
corrompido, 148–150 Avaliação diferida, 89-93, 234 Constante ReaderMethodCache ,
208–210
um passo para a direita, então regra
para cima, 30, 121, 126 Exemplo RedFlag, livro de receptores, 29, 34–35
feitiços 100 , 234, 242
Aliases de classes Exemplo RedFlag, 97–103
Símbolo para Proc, 224, 242
abertas, 134 referências, 21
atributos verificados, 152 palavras- método protegido , 218
refinar método, 36–39
chave de classe , 14 Proxies, dinâmicos, 58–64, 66, 235
Embalagens de Refinamento, 135,
desvantagens, 15 240
Método Fixnum#+ , 137 livro Pry, 49–51, 143
Refinamentos, xxi, 17, 36–39, 135, 240
de feitiços, 239 ou
método pry , 143 método de
operador, Nil Guards, 219,
método put , 142 atualização , 50 método
238
método puts , 217
escopos externos, 79 remove_method , 67

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

execução de método, 28, 34 linguagens específicas


singleton_method_undefined , 158 feitiços sobre, livro
dexix , 231–242
domínio, 229 flexibilidade,
atributos de objeto, 220
métodos privados, 35, 220 variável de instância @src , 149 217
Auto Rendimento, 222–224, 241 operador stabby lambda, 89 rastreamento
Métodos Singleton, 116
de pilha, método eval , 144
Machine Translated by Google

Índice • 251

T U configurações, 99

Tabu, 112 unbind método, 94 compartilhadas, 84

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

TOPLEVEL_BINDING constante, constantes e, 21 variáveis palavras-chave de rendimento , 74

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

Rails Avançados e Node da Maneira Certa


O que costumava ser o domínio dos especialistas está rapidamente se tornando o assunto do
desenvolvimento do dia-a-dia - pule para o início da classe em Ruby on Rails e veja como fazer o Node da maneira certa.
caminho.

Criação de aplicativos do Rails 4


Prepare-se para ver o Rails como você nunca viu antes.
Aprenda a estender a estrutura, alterar seu comportamento e
substituir componentes inteiros para adaptá-la à sua vontade. Oito
diferentes tutoriais guiados por testes irão ajudá-lo a entender o
funcionamento interno do Rails e prepará-lo para lidar com projetos
complicados com soluções bem testadas, modulares e fáceis
de manter.

Esta segunda edição do best-seller Crafting Rails Applications


foi atualizada para Rails 4 e discute novos tópicos como streaming,
mecanismos montáveis e segurança de threads.

José Valim
(200 páginas) ISBN: 9781937785550. $ 36
http://pragprog.com/book/jvrails2

Node.js da maneira certa


Fique na vanguarda da programação JavaScript do lado do
servidor escrevendo aplicativos Node compactos, robustos, rápidos
e em rede que escalam. Pronto para levar o JavaScript além do
navegador, explorar recursos de linguagens dinâmicas e adotar a
programação por eventos? Explore o divertido e crescente
repositório de módulos Node fornecido pelo npm.
Trabalhe com vários protocolos, serviços da Web RESTful com
balanceamento de carga, express, ØMQ, Redis, CouchDB e
muito mais. Desenvolva aplicativos Node de nível de produção
rapidamente.

Jim R Wilson
(148 páginas) ISBN: 9781937785734. $ 17 http://
pragprog.com/book/jwnode
Machine Translated by Google

Coloque a “Diversão” no Funcional

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

Programação Erlang (2ª edição)


Um jogo multiusuário, site da Web, aplicativo em nuvem ou
banco de dados em rede pode ter milhares de usuários, todos
interagindo ao mesmo tempo. Você precisa de uma ferramenta
poderosa, de nível industrial, para lidar com os problemas
realmente difíceis inerentes a ambientes paralelos e simultâneos.
Você precisa de Erlang. Nesta segunda edição do best-seller
Programming Erlang, você aprenderá como escrever programas
paralelos que escalam facilmente em sistemas multicore.

Joe Armstrong
(548 páginas) ISBN: 9781937785536. $ 42
http://pragprog.com/book/jaerlang2
Machine Translated by Google

O que você precisa saber

Cada nova versão da Web traz sua própria corrida do ouro. Aqui estão suas ferramentas.

HTML5 e CSS3 (2ª edição)

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

turas, incluindo animações CSS, IndexedDB e validações do lado do


cliente.

Brian P. Hogan
(300 páginas) ISBN: 9781937785598. $ 38
http://pragprog.com/book/bhh52e

Receitas de Desenvolvimento Web


O desenvolvimento web moderno requer mais do que apenas HTML e
CSS com um pouco de JavaScript misturado. Os clientes querem sites
mais responsivos com interfaces mais rápidas que funcionem em vários
dispositivos, e você precisa das ferramentas e técnicas mais recentes para
fazer isso acontecer. Este livro oferece mais de 40 soluções concisas e
comprovadas para
problemas de desenvolvimento da Web do dia e apresenta novos fluxos
de trabalho que irão expandir seu conjunto de habilidades.

Brian P. Hogan, Chris Warren, Mike Weber, Chris Johnson, Aaron


Godin (344 páginas) ISBN:
9781934356838. $ 35 http://pragprog.com/book/wbdev
Machine Translated by Google

Explorar testes e pepino


Explore as águas desconhecidas dos testes exploratórios e mergulhe mais fundo no Pepino.

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ó.

Matt Wynne e Aslak Hellesøy (336


páginas) ISBN: 9781934356807. $ 30 http://
pragprog.com/book/hwcuc
Machine Translated by Google

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.

Novo e Notável http://


pragprog.com/news
Confira os últimos desenvolvimentos pragmáticos, novos títulos e outras ofertas.

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

Atendimento ao Cliente: [email protected]

Direitos Internacionais: [email protected]


Uso acadêmico: [email protected]

Escreva para nós: http://pragprog.com/write-for-us

Ou chamar: +1 800-699-7764

Você também pode gostar