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

Guia Avançado para Rails 4 Applications

O livro 'Crafting Rails 4 Applications' de José Valim é uma introdução abrangente às APIs de plug-in do Rails, focando em como personalizar e estender o framework para atender às necessidades dos desenvolvedores. Com exemplos práticos e desenvolvimento orientado a testes, o autor guia leitores intermediários e avançados na construção de aplicações Rails mais modulares e eficientes. O conteúdo abrange desde a criação de renderizadores até a gestão de eventos e tradução de aplicações.

Enviado por

dvitoriano89
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)
4 visualizações203 páginas

Guia Avançado para Rails 4 Applications

O livro 'Crafting Rails 4 Applications' de José Valim é uma introdução abrangente às APIs de plug-in do Rails, focando em como personalizar e estender o framework para atender às necessidades dos desenvolvedores. Com exemplos práticos e desenvolvimento orientado a testes, o autor guia leitores intermediários e avançados na construção de aplicações Rails mais modulares e eficientes. O conteúdo abrange desde a criação de renderizadores até a gestão de eventos e tradução de aplicações.

Enviado por

dvitoriano89
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

Machine Translated by Google

[Link]
Machine Translated by Google

[Link]
Machine Translated by Google

Elogios iniciais para o livro "Creating Rails 4 Applications"

Excelente — o livro sobre Rails mais avançado do mercado.


ÿ Xavier Noria

Consultor Ruby on Rails

Em "Crafting Rails 4 Applications", José Valim me mostrou como fazer o Ruby on Rails funcionar perfeitamente.
Escrevo código melhor e perco menos tempo lutando contra o framework graças aos truques que ele me ensinou.
Se você trabalha com Ruby on Rails (ou gostaria de trabalhar), faça um favor a si mesmo e leia este livro.

ÿ Avdi Grimm

Chefe de cozinha, Ruby Tapas

Este é, sem dúvida, o melhor livro de educação continuada sobre Rails que já li.
Você aprende como as coisas funcionam internamente e como pode usar isso a seu favor ao construir aplicações
Rails. ÿ James Edward Gray, Desenvolvedor,

Gray Software Productions Inc.

O livro "Crafting Rails 4 Applications" é a melhor introdução aos detalhes internos do Rails disponível.
Depois de ler, rapidamente me tornei um colaborador do Rails. ÿ Guillermo

Iguaran, desenvolvedor líder

[Link]
Machine Translated by Google

Criando aplicativos para Rails 4


Práticas especializadas para o desenvolvimento diário em Rails

José Valim

A Estante Pragmática
Dallas, Texas • Raleigh, Carolina do Norte

[Link]
Machine Translated by Google

Muitas das designações usadas por fabricantes e vendedores para distinguir seus produtos são reivindicadas
como marcas registradas. Quando essas designações aparecem neste livro, e a The Pragmatic Programmers,
LLC tinha conhecimento de uma reivindicação de marca registrada, as designações foram impressas com
iniciais maiúsculas ou todas em maiúsculas. The Pragmatic Starter Kit, The Pragmatic Programmer,
Pragmatic Programming, Pragmatic Bookshelf, PragProg e o dispositivo de ligação g são marcas registradas
da The Pragmatic Programmers, LLC.

Todas as precauções foram tomadas na preparação deste livro. No entanto, a editora não assume qualquer
responsabilidade por erros ou omissões, nem por danos que possam resultar da utilização das informações
(incluindo a programação) aqui contidas.

Nossos cursos, workshops e outros produtos da Pragmatic podem ajudar você e sua equipe a criar softwares
melhores e se divertir mais. Para obter mais informações, bem como os lançamentos mais recentes da
Pragmatic, visite-nos em [Link]

A equipe que produziu este livro inclui:

Brian P. Hogan (editor)


Potomac Indexing, LLC (indexador)
Candace Cunningham (revisora de texto)
David J. Kelly (compositor)
Janet Furlow (produtora)
Julieta Benda (direitos)
Ellie Callahan (apoio)

Copyright © 2013 The Pragmatic Programmers, LLC.


Todos os direitos reservados.

Nenhuma parte desta publicação pode ser reproduzida, armazenada em sistema de recuperação ou
transmitida de qualquer forma ou por qualquer meio, seja eletrônico, mecânico, por fotocópia, gravação
ou outros, sem o consentimento prévio da editora.

Impresso nos Estados Unidos da América.


ISBN-13: 978-1-937785-55-0

Codificado usando os dígitos binários de alta entropia mais finos e isentos de ácido.
Versão do livro: P1.0—Novembro de 2013

[Link]
Machine Translated by Google

Conteúdo

Agradecimentos . . . . . . . . . . . vii

Prefácio . . . . . . . . . . . . . . ix

1. Criando nosso próprio renderizador . . . . . . . . . 1


1.1 Criando seu primeiro plug-in para Rails 2
1.2 Escrevendo o renderizador 5
1.3 Entendendo a pilha de renderização do Rails 9
1.4 Levando para o próximo nível 14
1.5 Concluindo 16

2. Construindo modelos com o Active Model . . . . . . . 17


2.1 Criando nosso modelo 17
2.2 Testes de integração com Capybara 28
2.3 Levando para o próximo nível 32
2.4 Concluindo 37

3. Recuperando modelos de visualização de repositórios personalizados . . . . 39


3.1 Revisitando a Pilha de Renderização 39
3.2 Configurando um SqlResolver 41
3.3 Configurando nosso Resolver para Produção 48
3.4 Servindo Templates com Metal 3.5 55
Concluindo 59

4. Envio de e-mails multipartes usando manipuladores de modelos . . . 61


4.1 Explorando a API Template-Handler 4.2 63
Criando um Template Handler com Markdown + ERB 4.3 66
Personalizando os Geradores do Rails 71
4.4 Estendendo o Rails com Railties 78
4.5 Concluindo 80

[Link]
Machine Translated by Google

Conteúdo • vi

5. Transmissão assíncrona de eventos do servidor para os clientes . . . 83


5.1 Estendendo o Rails com Engines 5.2 84
Transmissão ao vivo 87
5.3 Notificações do sistema de arquivos com 92
Threads 5.4 Técnicas de 100
carregamento de código 5.5 Conclusão 104

6. Escrevendo controladores DRY com respondedores . . . . . 105


6.1 Compreendendo os Responders 106
6.2 Explorando ActionController::Responder 6.3 O 109
Flash Responder 6.4 O HTTP 114
Cache Responder 6.5 Mais 119
maneiras de personalizar os Generators 6.6 122
Concluindo 128

7. Gerenciando eventos de aplicativos com mecanismos montáveis . . 131


7.1 Motores Montáveis e Isolados 7.2 Armazenamento 131
de Notificações no Banco de Dados 7.3 Trilhos e Rack 133
140
7.4 Pilhas de Middleware 143
7.5 Streaming com Rack 7.6 150
Conclusão 154

8. Tradução de aplicações usando back-ends de chave-valor . . 155


8.1 Revisitando Rails::Application 8.2 Back- 156
ends e extensões I18n 159
8.3 Trilhos e Sinatra 163
8.4 Levando a experiência para o próximo nível com Devise e 169
Capybara 8.5 Concluindo 175

Índice . . . . . . . . . . . . . . 177

[Link]
Machine Translated by Google

Agradecimentos

Em primeiro lugar, sou grato à minha esposa pelo carinho, pelo amor e por, ocasionalmente,
me arrastar para fora para apreciar o mundo ao nosso redor. Também quero enviar muito
amor aos meus pais e familiares, que orgulhosamente exibiam a primeira edição deste livro a
todos que entravam em nossa casa. Agora eles também receberão uma nova edição!

Gostaria também de agradecer à equipe da Plataformatec, em especial a George Guimarães,


Hugo Baraúna e Marcelo Park. Sem eles, este livro não teria sido possível. Todos na
Plataformatec contribuíram desde o primeiro dia, quando estávamos definindo o conteúdo dos
capítulos, até os parágrafos finais.

Os resenhistas fizeram um trabalho excepcional com este livro. Agradeço a Daniel Bretoi,
Rafael França, Kevin Gisi, Jeff Holland, Landrus Kurt, Xavier Noria, Stephen Orr, Yves Senn,
Neeraj Singh e Charley Stran.

Um agradecimento especial ao meu editor, Brian Hogan, e ao The Pragmatic Programmers,


que me ajudaram a elevar este livro de ótimo para excelente; e a Yehuda Katz por me apoiar
não apenas durante a escrita deste livro, mas também no desenvolvimento do Rails Core
como um todo.

[Link] errata do relatório • discutir


Machine Translated by Google

Prefácio

Quando o Rails foi lançado em 2004, revolucionou o desenvolvimento web ao adotar conceitos como "Don't
Repeat Yourself" (DRY) e convenção sobre configuração. À medida que o Rails ganhava popularidade, as
convenções que faziam tudo funcionar tão bem começaram a atrapalhar os desenvolvedores que desejavam
estender o comportamento do Rails ou até mesmo substituir componentes inteiros.

Alguns desenvolvedores acharam que usar o DataMapper como mapeador objeto-relacional (ORM) em vez
do Active Record era a melhor opção. Outros optaram pelo MongoDB e outros bancos de dados não
relacionais, mas ainda queriam usar seus frameworks web favoritos. Havia também desenvolvedores que
preferiam frameworks de teste como o RSpec ao Test::Unit. Esses desenvolvedores improvisaram,
improvisaram ou fizeram gambiarras para atingir seus objetivos, pois as versões anteriores do Rails não
forneciam uma API (Interface de Programação de Aplicativos) sólida ou a modularidade necessária para
fazer essas alterações de forma limpa e sustentável.

Com o tempo, a equipe do Rails começou a ouvir esses desenvolvedores e, após anos, o resultado é um
conjunto robusto e abrangente de APIs de plug-in, voltadas para desenvolvedores que desejam personalizar
seus fluxos de trabalho, substituir componentes inteiros e adaptar o Rails às suas necessidades sem
gambiarras complicadas.

Este livro guia você por essas APIs de plug-in com exemplos práticos. Em cada capítulo, usaremos o
desenvolvimento orientado a testes para construir um plug-in ou aplicativo Rails que abrange essas APIs e
como elas se encaixam na arquitetura do Rails.
Ao terminar este livro, você entenderá melhor o Rails e aumentará sua produtividade, escrevendo aplicações
Rails mais modulares e rápidas.

Quem deve ler este livro?

Se você é um desenvolvedor Rails de nível intermediário ou avançado que busca se aprofundar e fazer o
framework Rails funcionar para você, este é o livro certo. Iremos além do básico do Rails; em vez de mostrar
como o Rails permite que você use seus recursos integrados, vamos explorar o potencial do framework.

[Link]
errata do relatório • discutir
Machine Translated by Google

Prefácio • x

Vou mostrar como o método render() funciona para renderizar HTML ou XML a partir de um controlador, para
que você possa modificá-lo e aceitar opções personalizadas, como :pdf.

Versões do Rails

Todos os projetos em "Crafting Rails 4 Applications" foram desenvolvidos e testados com o Rails 4.0.0.
Versões estáveis futuras, como Rails 4.0.1, 4.0.2 e outras, também devem funcionar corretamente. Você pode

verificar sua versão do Rails com o seguinte comando:

trilhos -v

E você pode usar o comando `gem install` para obter a versão mais adequada:

gem install rails -v 4.0.0

Este livro também contém trechos do código-fonte do Rails. Todos esses trechos foram extraídos do Rails
4.0.0.

A maioria das APIs descritas neste livro deve permanecer compatível com todas as versões do Rails.
Pouquíssimas delas foram alteradas desde o lançamento da primeira edição deste livro.1

Nota para desenvolvedores do Windows

Alguns capítulos têm dependências que utilizam extensões C. Essas dependências são instaladas sem
problemas em sistemas UNIX, mas os desenvolvedores do Windows precisam do DevKit,² um conjunto de
ferramentas que permite compilar muitas das extensões nativas C/C++ disponíveis para Ruby.

As instruções de download e instalação estão disponíveis online em [Link] .


Alternativamente, você pode obter tudo o que precisa instalando o RailsInstaller,3, que inclui Ruby, Rails e o
DevKit, além de vários outros recursos.
outras bibliotecas comuns.

O que contém o livro?

Vamos explorar o funcionamento interno do Rails ao longo de oito capítulos.

No Capítulo 1, "Criando Nosso Próprio Renderizador", na página 1, apresentarei o `rails plugin`, uma
ferramenta usada ao longo deste livro para criar plugins do Rails, e personalizaremos o método `render()` para
aceitar ` :pdf` como opção, com um comportamento que definiremos. Este capítulo inicia uma série de
discussões sobre a pilha de renderização do Rails.

1. [Link]
2. [Link]
3. [Link]

[Link] errata do relatório • discutir


Machine Translated by Google

O que há no livro? • xi

No Capítulo 2, "Construindo Modelos com Active Model", na página 17, vamos examinar o Active Model e seus
módulos enquanto criamos uma extensão chamada Formulário de E-mail.

que recebe dados através de um formulário e os envia para um endereço de e-mail pré-configurado.

Em seguida, no Capítulo 3, "Recuperando Modelos de Visualização de Armazenamentos Personalizados", na


página 39, revisitaremos a pilha de renderização do Rails e a personalizaremos para ler modelos de um banco
de dados em vez do sistema de arquivos. Ao final do capítulo, você aprenderá como criar controladores mais
rápidos usando a modularidade do Rails.

No Capítulo 4, "Enviando e-mails multipartes usando manipuladores de modelos", na página 61, criaremos um
novo manipulador de modelos (como ERB e Haml) sobre o Markdown.4 Em seguida, criaremos novos
geradores e os integraremos perfeitamente ao Rails.

No Capítulo 5, "Transmitindo Eventos do Servidor para Clientes de Forma Assíncrona", na página 83,
construiremos um mecanismo Rails que transmite dados para os clientes. Também veremos como usar a
classe `Queue` da Biblioteca Padrão do Ruby para sincronizar a troca de informações entre threads e
finalizaremos com uma discussão sobre segurança de threads e carregamento antecipado (eager loading).

No Capítulo 6, "Escrevendo Controladores DRY com Responders", na página 105, estudaremos os responders
do Rails e como podemos usá-los para encapsular o comportamento dos controladores, tornando-os mais
simples e nossas aplicações mais modulares.
Em seguida, estenderemos os respondedores do Rails para adicionar cache HTTP e mensagens Flash
internacionalizadas por padrão. Ao final do capítulo, você aprenderá como personalizar os geradores de
scaffold do Rails para aumentar a produtividade.

No Capítulo 7, "Gerenciando Eventos de Aplicação com Mecanismos Montáveis", na página 131, construiremos
um mecanismo montável que armazena informações sobre cada ação processada pela nossa aplicação em
um banco de dados MongoDB e as expõe para análises posteriores por meio de uma interface web.
Concluiremos o capítulo discutindo o Rack e seus middlewares, enquanto escrevemos nosso próprio middleware.

Finalmente, no Capítulo 8, "Traduzindo Aplicativos Usando Back-Ends Chave-Valor", na página 155,


discutiremos a estrutura de internacionalização (I18n) e a personalizaremos para ler e armazenar traduções
em um banco de dados Redis. Criaremos um

aplicação que usa o Sinatra como uma extensão do Rails para que possamos modificar essas traduções do
Redis por meio de uma interface web. Protegeremos essa interface de tradução usando o Devise e
demonstraremos a flexibilidade do Capybara para escrever testes de integração para diferentes navegadores.5,6

4. [Link] 5. https://
[Link]/plataformatec/devise 6. [Link]
jnicklas/capybara

[Link] errata do relatório • discutir


Machine Translated by Google

Prefácio • xii

Alterações na Segunda Edição


Todos os projetos e exemplos de código foram atualizados e testados para funcionar com o Rails 4. Os
projetos também utilizam fluxos de trabalho atualizados para a criação de plug-ins do Rails e para a
integração com o framework.

Além disso, o Capítulo 5, " Transmitindo Eventos do Servidor para Clientes Assincronamente", na página
83, é totalmente novo; ele aborda o suporte do Rails para Eventos Enviados pelo Servidor e explora o
carregamento antecipado e a segurança de threads.

Nesta edição, também exploramos engines isoladas e montáveis, bem como aplicações Rails de arquivo
único.

Como ler este livro

Em cada capítulo, construiremos um projeto do zero. Embora esses projetos não dependam uns dos
outros, a maior parte das discussões em cada capítulo se baseia no que você aprendeu anteriormente.
Por exemplo, no Capítulo 1, " Criando Nosso Próprio Renderizador", na página 1, discutimos a pilha de
renderização do Rails e, em seguida, aprofundamos essa discussão no Capítulo 3, " Recuperando
Modelos de Visualização de Stores Personalizadas", na página 39, e finalizamos no Capítulo 4, " Enviando
E-mails Multipart Usando Manipuladores de Modelo", na página 61. Em outras palavras, você pode pular
capítulos, mas para ter uma visão geral, recomendamos que leia os capítulos na ordem em que são
apresentados.

Recursos online
O site do livro contém links para um fórum de discussão interativo, bem como

Errata para o livro.7 Você também encontrará o código-fonte de todos os projetos que desenvolvemos. Os
leitores do ebook podem clicar na caixa cinza acima de um determinado trecho de código para baixá-lo
diretamente.

Se encontrar algum erro, por favor, crie uma entrada na página de erratas para que possamos corrigi-lo.
Se você tiver uma cópia eletrônica deste livro, clique no link no rodapé de qualquer página para nos enviar
erratas facilmente.

Vamos começar criando um plug-in do Rails que personaliza o método render() para que você possa
aprender como funciona a pilha de renderização do Rails.

José Valim
[Link]@[Link]

7. [Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Neste capítulo, veremos: •


Plugins do Rails e sua estrutura básica • Como
personalizar o método render() para aceitar parâmetros personalizados
opções •
Noções básicas da pilha de renderização do Rails

CAPÍTULO 1

Criando nosso próprio renderizador

Assim como muitos frameworks web, o Rails utiliza o padrão de arquitetura Model-View-Controller (MVC) para
organizar o código. O controlador geralmente é responsável por coletar informações dos modelos e enviar os
dados para a visualização para renderização. Em outras ocasiões, o modelo é responsável por se representar,
e então a visualização não participa da requisição; isso ocorre com mais frequência em requisições JSON
(JavaScript Object Notation) . A ação `index` a seguir ilustra esses dois cenários:

class PostsController < ApplicationController def index if

client_authenticated? render json: [Link]


else

render template: "shared/not_authenticated", status: 401 end

fim
fim

A interface comum para renderizar um determinado modelo ou template é o método `render()` . Além de saber
como renderizar um ` :template` ou um ` :file`, o Rails pode renderizar texto bruto (`:text`) e alguns formatos,
como ` :xml`, `:json` e `:js`. Embora o conjunto padrão de opções do Rails seja suficiente para inicializar nossas
aplicações, às vezes precisamos adicionar novas opções, como `:pdf` ou `:csv`, ao método `render() `.

Para isso, o Rails fornece uma interface de programação de aplicativos (API) que podemos usar para criar
nossos próprios renderizadores. Vamos explorar essa API enquanto modificamos o método `render()` para
aceitar ` :pdf` como opção e retornar um PDF criado com o Prawn,¹ uma biblioteca pequena, rápida e ágil para
criação de PDFs em Ruby.

1. [Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 1. Criando nosso próprio renderizador • 2

Como na maioria dos capítulos deste livro, usaremos o gerador de plugins do Rails para criar um plugin
que expanda as funcionalidades do Rails. Vamos começar!

1.1 Criando seu primeiro plug-in para Rails


Se você já tem o Rails instalado, está pronto para criar seu primeiro plug-in.
Vamos chamar esse plug-in de pdf_renderer:

$ rails plugin novo pdf_renderer

Ao executarmos esse comando, vemos a seguinte saída:

Criar
[Link]
Criar arquivo Rakefile
criar pdf_renderer.gemspec criar MIT-LICENSE

Criar .gitignore Criar Gemfile

criar lib/pdf_renderer.rb criar lib/tasks/


pdf_renderer_tasks.rake criar lib/pdf_renderer/[Link] criar test/
test_helper.rb criar test/pdf_renderer_test.rb adicionar
Rakefile vendor_app test/dummy executar
bundle install

Este comando cria a estrutura básica do plug-in, contendo um arquivo pdf_renderer.gem-spec , um


Rakefile, um Gemfile e as pastas lib e test . O penúltimo passo no texto anterior é um pouco mais
interessante; ele gera uma aplicação Rails completa dentro do diretório test/dummy , o que nos permite
executar nossos testes dentro do contexto de uma aplicação Rails.

O gerador termina executando o comando `bundle install`, que usa o Bundler para instalar todas as
dependências necessárias para o nosso projeto.² Com tudo configurado, vamos explorar os arquivos
gerados.

pdf_renderer.gemspec
O arquivo pdf_renderer.gemspec fornece uma especificação básica da gem. A especificação declara os
autores, a versão, as dependências, os arquivos de origem e muito mais da gem.
Isso nos permite empacotar facilmente nosso plug-in em uma gem Ruby, facilitando o compartilhamento do
nosso código entre diferentes aplicações Rails.

Observe que a gem tem o mesmo nome do arquivo dentro do diretório lib , que é pdf_renderer. Seguindo
essa convenção, sempre que você declarar isso

2. [Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Criando seu primeiro plug-in para Rails • 3

Ao adicionar a gem `pdf_renderer` ao Gemfile de uma aplicação Rails , o arquivo `lib/pdf_renderer.rb` será
automaticamente incluído. Por enquanto, este arquivo contém apenas a definição do módulo ` PdfRenderer` .

Por fim, observe que nosso gemspec não define explicitamente a versão do projeto.
Em vez disso, a versão é definida em lib/pdf_renderer/[Link], que é referenciado no gemspec como
PdfRenderer::VERSION. Essa é uma prática comum em gems Ruby.

Gemfile

Em uma aplicação Rails, o Gemfile é usado para listar todos os tipos de dependências, sejam elas de
desenvolvimento, teste ou produção. No entanto, como nosso plugin já possui um Gemspec para listar as
dependências, o Gemfile simplesmente reutiliza as dependências do Gemspec . O Gemfile pode eventualmente
conter dependências adicionais que você considere convenientes durante o desenvolvimento, como o
depurador ou a excelente gem pry .

Para gerenciar as dependências do nosso plug-in, usamos o Bundler. O Bundler restringe nosso ambiente para
usar apenas as gems listadas tanto no arquivo pdf_renderer.gemspec quanto no Gemfile, garantindo que os
testes sejam executados usando as gems especificadas. Podemos adicionar novas dependências e atualizar
as existentes executando os comandos `bundle install` e `bundle update` na raiz do nosso plug-in.

Arquivo Rake

O Rakefile fornece tarefas básicas para executar o conjunto de testes, gerar documentação e publicar nossa
gem publicamente. Podemos obter a lista completa executando `rake -T` na raiz do `pdf_renderer` :

$ ancinho -T
construção de ancinho # Crie o pacote pdf_renderer-[Link] no diretório pkg com o comando `rake
clobber_rdoc` # Remova os arquivos HTML do RDoc com o comando
`rake install` # Compile e instale o pdf_renderer-[Link] nos gems do sistema # Crie arquivos HTML RDoc
rake rdoc
liberação do ancinho # Crie a tag v0.0.1 e compile e envie o pdf_renderer...
rake rerdoc rake # Recriar arquivos HTML do RDoc
teste # Executar testes

Iniciando o aplicativo fictício


O plugin Rails cria uma aplicação fictícia dentro do nosso diretório de testes, e o processo de inicialização
dessa aplicação é semelhante ao de uma aplicação normal criada com o comando Rails .

3. [Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 1. Criando nosso próprio renderizador • 4

O arquivo config/[Link] tem apenas uma responsabilidade: configurar os caminhos de carregamento da


nossa aplicação. O arquivo config/[Link] deve então carregar todas as dependências necessárias e
configurar a aplicação, que é inicializada em config/[Link].

O arquivo de inicialização gerado pelo plugin Rails está localizado em `test/dummy/config/[Link]` e é


semelhante ao arquivo da aplicação. A principal diferença é que ele precisa apontar para o Gemfile na raiz
do plugin ` pdf_renderer` . Ele também adiciona explicitamente o diretório `lib` do plugin ao caminho de
carregamento do Ruby, tornando nosso plugin disponível dentro da aplicação de teste.

pdf_renderer/1_prawn/test/dummy/config/[Link] #
Configure as gems listadas no Gemfile.

ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__)

require 'bundler/setup' if [Link]?(ENV['BUNDLE_GEMFILE'])


$LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__)

O arquivo de inicialização delega ao Bundler a responsabilidade de configurar as dependências e seus


caminhos de carregamento. O arquivo `test/dummy/config/[Link]` é uma versão simplificada do
arquivo `config/[Link]` encontrado em aplicações Rails:

pdf_renderer/1_prawn/test/dummy/config/
[Link] require File.expand_path('../boot', __FILE__)
require 'rails/all'

[Link](*[Link]) require
"pdf_renderer"

módulo Classe
fictícia Application < Rails::Application
# ...
fim
fim

O arquivo config/[Link] é exatamente igual ao que você encontraria em uma aplicação Rails
comum:

pdf_renderer/1_prawn/test/dummy/config/[Link]
# Carregar a aplicação Rails. require
File.expand_path('../application', __FILE__)

# Inicialize a aplicação Rails.


Dummy::[Link]!

Executando testes

Por padrão, o plugin do Rails gera um teste de sanidade para o nosso plugin. Vamos executar nossos
testes e verificar se eles são aprovados com o seguinte comando:

teste de rastelo $

[Link] errata do relatório • discutir


Machine Translated by Google

Escrevendo o Renderizador • 5

O resultado será algo parecido com isto:

Opções de execução: --seed 20094

# Executando testes:

Testes concluídos em 0,096440s, 10,3691 testes/s, 10,3691 asserções/s.

1 teste, 1 asserção, 0 falhas, 0 erros, 0 omissões

O teste, definido em test/pdf_renderer_test.rb, verifica se o nosso plug-in definiu um módulo chamado


PdfRenderer.

pdf_renderer/1_prawn/test/pdf_renderer_test.rb require
'test_helper' class PdfRendererTest
< ActiveSupport::TestCase
teste "verdade" faça
assert_kind_of Module, PdfRenderer end

fim

Por fim, observe que nosso arquivo de teste requer o arquivo `test/test_helper.rb`, responsável por carregar
nossa aplicação e configurar nosso ambiente de teste. Com a estrutura básica do plug-in criada e um conjunto
de testes funcionando corretamente, podemos começar a escrever nosso primeiro renderizador personalizado.

1.2 Escrevendo o Renderizador


No início deste capítulo, discutimos brevemente o método render() e algumas opções que ele aceita, mas não
descrevemos formalmente o que é um renderizador .

Um renderizador nada mais é do que um gancho exposto pelo método `render()` para personalizar seu
comportamento. Adicionar nosso próprio renderizador ao Rails é bastante simples. Vamos considerar o
renderizador `:json` no código-fonte do Rails como exemplo:

rails/actionpack/lib/action_controller/metal/[Link]
adicione :json faça |json, opções|
json = json.to_json(options) unless json.kind_of?(String) if options[:callback].present?

self.content_type ||= Mime::JS "#{options[:callback]}


(#{json})" else

self.content_type ||= Mime::JSON json end

fim

[Link]
errata do relatório • discutir
Machine Translated by Google

Capítulo 1. Criando nosso próprio renderizador • 6

Portanto, sempre que invocarmos o seguinte método em nossa aplicação.

renderizar json: @post

Isso invocará o bloco definido como o renderizador `:json` . A variável local `json` dentro do bloco aponta
para o objeto `@post` , e as outras opções passadas para `render()` estarão disponíveis na variável
`options` . Neste caso, como o método foi chamado sem nenhuma opção extra, trata-se de um hash
vazio.

Nas seções seguintes, adicionaremos um renderizador `:pdf` que cria um documento PDF a partir de um
modelo fornecido e o envia ao cliente com os cabeçalhos apropriados. O valor fornecido à opção `:pdf` deve
ser o nome do arquivo a ser enviado.

Segue abaixo um exemplo da API que queremos fornecer:

render pdf: 'conteúdo', modelo: 'caminho/para/modelo'

Embora o Rails saiba como renderizar templates e enviar arquivos para o cliente, ele não sabe como
lidar com arquivos PDF. Para isso, vamos usar o Prawn.

Trabalhando com Prawn.

Prawn é uma biblioteca para criação de PDFs em Ruby 4. Como ela será uma dependência do nosso
plugin, precisamos adicioná-la ao nosso arquivo pdf_renderer.gemspec:

pdf_renderer/1_prawn/pdf_renderer.gemspec
s.add_dependency "prawn", "0.12.0"

Em seguida, vamos pedir ao Bundler para instalar nossa nova dependência e testá-la de forma interativa.
Rubi:

$ bundle install $ irb

Dentro do irb, vamos criar um PDF de exemplo:

requer "camarão"

pdf = Prawn::[Link]
[Link]("Um PDF em quatro linhas de código")
pdf.render_file("[Link]")

Saia do irb e você poderá ver um arquivo PDF no diretório em que iniciou a sessão . O Prawn oferece sua
própria sintaxe para criar PDFs e, embora isso nos dê uma API flexível, a desvantagem é que não é
possível criar PDFs a partir de arquivos HTML.

4. [Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Escrevendo o Renderizador • 7

Código em ação

Vamos escrever alguns testes antes de mergulharmos no código. Como temos um aplicativo de teste em test/
dummy, podemos criar controladores como em um aplicativo Rails real e usá-los para testar toda a pilha de
requisições. Vamos nomear o controlador usado em nossos testes como HomeController e adicionar o
seguinte conteúdo:

pdf_renderer/1_prawn/test/dummy/app/controllers/home_controller.rb class
HomeController < ApplicationController def index

responder_para |formato |
[Link]
[Link] { render pdf: "conteúdo" } fim

fim
fim

Agora vamos criar a visualização em PDF que o controlador utiliza:

pdf_renderer/1_prawn/test/dummy/app/views/home/
[Link] Este modelo foi renderizado com o Prawn.

E adicione uma rota para a ação de índice:

pdf_renderer/1_prawn/test/dummy/config/[Link]
Dummy::[Link] do get "/home", to:
"home#index", as: :home end

Por fim, vamos escrever um teste de integração que verifique se um PDF está sendo retornado quando
acessamos /[Link]:

pdf_renderer/1_prawn/test/integration/pdf_delivery_test.rb requer
"test_helper"

classe PdfDeliveryTest < ActionDispatch::IntegrationTest


teste "a solicitação de pdf envia um pdf como arquivo" faça
obter home_path(formato: :pdf)

assert_match "PDF", [Link] assert_equal


"binary", headers["Content-Transfer-Encoding"]

assert_equal "attachment; filename=\"[Link]\"",


cabeçalhos["Content-Disposition"]
assert_equal "application/pdf", headers["Content-Type"] end

fim

O teste utiliza os cabeçalhos de resposta para verificar se um arquivo PDF codificado em binário foi enviado
como anexo, incluindo o nome de arquivo esperado. Embora não possamos afirmar muito sobre o conteúdo
do PDF, visto que ele está codificado, podemos pelo menos...

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 1. Criando nosso próprio renderizador • 8

Afirme que o arquivo contém a string "PDF" , que o Prawn adiciona ao corpo do PDF. Vamos executar
nosso teste com `rake test` e observar a falha:

1) Falha:
test_pdf_request_sends_a_pdf_as_file(PdfDeliveryTest): Esperava-se que /
PDF/ correspondesse a "Este modelo foi renderizado com Prawn.\n".

O teste falha como esperado. Como não ensinamos ao Rails como lidar com a opção `:pdf` em `render()`,
ele está simplesmente renderizando o template sem envolvê-lo em um PDF. Podemos fazer o teste passar
implementando nosso renderizador em apenas algumas linhas de código dentro de `lib/pdf_renderer.rb`:

pdf_renderer/1_prawn/lib/pdf_renderer.rb
requer "camarão"
ActionController::[Link] :pdf do |filename, options| pdf =
Prawn::[Link] [Link]
render_to_string(options) send_data([Link],
filename: "#{filename}.pdf",
disposição: "apego"
fim

E pronto! Neste bloco de código, criamos um novo documento PDF, adicionamos algum texto a ele e
enviamos o PDF como anexo usando o método `send_data()` disponível no Rails. Agora podemos executar
os testes e ver que eles são aprovados. Também podemos ir para `test/dummy`, iniciar o servidor com
`rails server` e testá-lo acessando `http:// localhost:3000/ [Link]`.

Embora nosso teste tenha sido aprovado, ainda há algumas explicações a serem dadas. Primeiramente,
observe que em nenhum momento definimos o Content-Type como application/pdf. Como o Rails soube
qual tipo de conteúdo definir em nossa resposta?

O tipo de conteúdo foi definido corretamente porque o Rails já vem com um conjunto de formatos e tipos
MIME registrados:

rails/actionpack/lib/action_dispatch/http/mime_types.rb
Mime::[Link] "text/html", :html, %w( application/xhtml+xml ), %w( xhtml )
Mime::[Link] "text/plain", :text, [], %w(txt)
Mime::[Link] "text/javascript", :js,
%w(application/javascript application/x-javascript)
Mime::[Link] "text/css", :css
Mime::[Link] "text/calendar", :ics Mime::[Link]
"text/csv", :csv

Mime::[Link] "image/png", :png, [], %w(png)


Mime::[Link] "image/jpeg", :jpeg, [], %w(jpg jpeg jpe pjpeg)
Mime::[Link] "image/gif", :gif, [], %w(gif)
Mime::[Link] "image/bmp", :bmp, [], %w(bmp)
Mime::[Link] "image/tiff", :tiff, [], %w(tif tiff)

[Link] errata do relatório • discutir


Machine Translated by Google

Entendendo a pilha de renderização do Rails • 9

Mime::[Link] "video/mpeg", :mpeg, [], %w(mpg mpeg mpe)

Mime::[Link] "application/xml", :xml, %w(text/xml application/x-xml)


Mime::[Link] "application/rss+xml", :rss
Mime::[Link] "application/atom+xml", :atom
Mime::[Link] "application/x-yaml", :yaml, %w( text/yaml )

Mime::[Link] "multipart/form-data", :multipart_form Mime::[Link]


"application/x-www-form-urlencoded", :url_encoded_form

Mime::[Link] "application/json", :json, %w(text/x-json


application/jsonrequest)

Mime::[Link] "application/pdf", :pdf, [], %w(pdf)


Mime::[Link] "application/zip", :zip, [], %w(zip)

Observe como o formato PDF é definido com seu respectivo tipo de conteúdo. Quando solicitamos a URL /
[Link] , o Rails recuperou o formato PDF da URL, verificou se ele correspondia ao bloco [Link] definido
em HomeController#index e, em seguida, definiu o tipo de conteúdo apropriado antes de invocar o bloco que
chamou render.

Voltando à nossa implementação de renderização, embora `send_data()` seja um método público do Rails e
esteja disponível desde as primeiras versões, você talvez não tenha ouvido falar do método `render_to_string()` .
Para entendê-lo melhor, vamos dar uma olhada no processo de renderização do Rails como um todo.

1.3 Compreendendo a pilha de renderização do Rails


Action Mailer e Action Controller compartilham diversas funcionalidades, como renderização de templates, helpers

e layouts. Para evitar duplicação de código, essas responsabilidades compartilhadas são centralizadas no
Abstract Controller, que serve de base tanto para Action Mailer quanto para Action Controller. Ao mesmo tempo,
algumas funcionalidades são necessárias apenas para uma das duas bibliotecas. Levando em conta esses

requisitos, o Abstract Controller foi projetado de forma que os desenvolvedores possam escolher a funcionalidade
que desejam. Por exemplo, se quisermos que um objeto tenha recursos básicos de renderização, onde ele
simplesmente renderiza um template, mas não inclui um layout, incluímos o módulo AbstractController::Rendering
em nosso objeto, omitindo AbstractController::Layouts.

Quando incluímos AbstractController::Rendering em um objeto, a pilha de renderização procede conforme


mostrado na Figura 1, Visualização da pilha de renderização quando chamamos render com
AbstractController::Rendering, na página 10 , toda vez que chamamos render().

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 1. Criando nosso próprio renderizador • 10

Figura 1 — Visualização da pilha de renderização quando chamamos render() com


AbstractController::Rendering

Cada retângulo representa um método, seguido pelas classes ou módulos que o implementam. As setas
representam chamadas de método. Por exemplo, `render()` chama `_normalize_render()` e, em seguida,
chama `render_to_body()`. A pilha de chamadas pode ser confirmada observando a implementação de
`AbstractController::Rendering` no código-fonte do Rails:

rails/actionpack/lib/abstract_controller/[Link] def
render(*args, &block) options =
_normalize_render(*args, &block) self.response_body =
render_to_body(options) end

def _normalize_render(*args, &block)


opções = _normalize_args(*args, &block) _normalize_options(opções)
opções fim

def render_to_body(options = {})


_process_options(options)
_render_template(options) end

[Link] errata do relatório • discutir


Machine Translated by Google

Entendendo a pilha de renderização do Rails • 11

A pilha de renderização do Abstract Controller é responsável por normalizar os argumentos e opções fornecidos
e convertê-los em um hash de opções que o ActionView::Renderer#render() aceita, o qual se encarregará de
renderizar o template. Cada método na pilha desempenha um papel específico dentro dessa responsabilidade
geral. Esses métodos podem ser privados (iniciados com um sublinhado) ou fazer parte da API pública.

O primeiro método relevante na pilha é `_normalize_args()`, invocado por `_normalized_render()`, e converte


os argumentos fornecidos pelo usuário em um hash. Isso permite que o método `render()` seja invocado como
`render(:new)`, que `_normalize_args()` converte em `render(action: "new")`. O hash retornado por
`_normalize_args()` é então normalizado por `_normalize_options()`. Não há muita normalização ocorrendo
dentro de `AbstractController::Rendering#_normalize_options()`, já que é o módulo básico, mas ele converte
chamadas de `render(partial: true)` em ` render(partial: action_name)`.

Portanto, sempre que você definir `partial: true` em uma ação `show()` , ela se tornará `partial: "show"` em
toda a pilha de ações.

Após a normalização, o método `render_to_body()` é invocado. É aqui que a renderização propriamente dita
começa. O primeiro passo é processar todas as opções que não têm significado para a view, usando o método
`_process_options()` . Embora `AbstractController::Rendering# _process_options()` seja um método vazio,
podemos consultar ` ActionController::Rendering# _process_options()` para obter alguns exemplos de como
utilizá-lo.
Por exemplo, nos controladores, podemos invocar o seguinte:

render template: "shared/not_authenticated", status: 401

Aqui, a opção `:status` não tem significado para as views, já que `status` se refere ao status da resposta HTTP.
Portanto, é responsabilidade de `ActionController::Rendering#_process_options()` interceptar e lidar com essa
opção e outras.

Após o processamento das opções, o método `_render_template()` é invocado e diferentes objetos começam
a colaborar. Em particular, uma instância de `ActionView::Renderer` chamada `view_renderer` é criada e o
método `render()` é chamado nela com dois argumentos: o `view_context` e nosso hash de opções
normalizadas.

rails/actionpack/lib/abstract_controller/[Link]
renderizador_de_visualizaçã[Link](contexto_de_visualização, opções)

O contexto de visualização é uma instância de `ActionView::Base`; é o contexto no qual nossos templates são

avaliados. Quando chamamos `link_to()` em um template, funciona porque é um método disponível dentro de
`ActionView::Base`. Ao ser instanciado, o contexto de visualização recebe `view_assigns()` como argumento.

O termo `assigns` se refere ao grupo de variáveis do controlador que serão acessíveis na visualização. Por
padrão, sempre que você define uma variável de instância em seu controlador como `@posts = [Link]`,
`@posts` é marcada como uma atribuição e também estará disponível nas visualizações.

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 1. Criando nosso próprio renderizador • 12

Neste ponto, é importante destacar a inversão de responsabilidades que ocorreu entre o Rails
2.3 e o Rails 3.0. No primeiro, a view é responsável por obter os atributos do controller,
enquanto no segundo, o controller informa à view quais atributos usar.

Imagine que desejamos um controlador que não envie nenhuma atribuição para a visualização.
No Rails 2.3, como a view automaticamente busca todas as variáveis de instância dos
controllers, para obter esse resultado, devemos parar de usar variáveis de instância em nosso
controller ou garantir que todas as variáveis de instância sejam removidas antes de renderizar
um template. No Rails 3 e versões posteriores, essa responsabilidade é tratada no controller.
Basta sobrescrever o método view_assigns() para retornar um hash vazio:

class UsersController < ApplicationController protected def view_assigns


{} end

fim

Ao retornar um hash vazio, garantimos que nenhuma das ações no passe do controlador seja
atribuída à visualização.

Com o contexto de visualização e o hash de opções normalizadas em mãos, nossa instância


de ActionView::Renderer tem tudo o que precisa para encontrar um modelo, com base nas
opções, e finalmente renderizá-lo dentro do contexto de visualização.

Essa pilha modular e bem definida permite que qualquer pessoa se integre ao processo de
renderização e adicione seus próprios recursos. Quando incluímos AbstractController::Layouts
acima de AbstractController::Rendering, a pilha de renderização é estendida, como mostrado
na Figura 2, Visualização da pilha de renderização quando chamamos render com
AbstractController::Rendering e AbstractController::Layouts, na página 13.

AbstractController::Layouts simplesmente sobrescreve _normalize_options() para suportar a


opção :layout . Caso nenhuma opção :layout seja definida ao chamar render(), uma poderá
ser definida automaticamente com base no valor configurado pelo desenvolvedor no nível da
classe do controlador. Action Controller estende ainda mais a pilha de renderização do Abstract
Controller, adicionando e processando opções que fazem sentido apenas no escopo do
controlador. Essas extensões são divididas em quatro módulos principais:

• ActionController::Rendering: Sobrescreve o método render() para verificar se ele foi chamado duas vezes,
lançando uma exceção DoubleRenderError em caso afirmativo; também sobrescreve o método
_process_options() para lidar com opções como :location, :status e :content_type.

• ActionController::Renderers: Adiciona a API que usamos neste capítulo, que nos permite
acionar um comportamento específico sempre que uma determinada chave (como :pdf)
for fornecida.

[Link]
errata do relatório • discutir
Machine Translated by Google

Entendendo a pilha de renderização do Rails • 13

Figura 2 — Visualização da pilha de renderização quando chamamos render() com


AbstractController::Rendering e AbstractController::Layouts

• ActionController::Instrumentation: Sobrecarrega o método render() para que ele possa realizar medições.
Quanto tempo foi gasto na pilha de renderização?

• ActionController::Streaming: Sobrecarrega o método _process_options() para lidar com o :stream ,


definindo os cabeçalhos HTTP apropriados, e o método _render_template() para permitir que os
modelos sejam transmitidos.

A Figura 3, Visualização da pilha de renderização quando chamamos render com AbstractController e


ActionController, na página 14, mostra a pilha final com os módulos de renderização AbstractController e
ActionController.

Agora que entendemos como o método `render()` funciona, estamos prontos para entender como o método
`render_to_string()` funciona. Vamos começar vendo sua definição em `AbstractController::Rendering`:

rails/actionpack/lib/abstract_controller/[Link] def
render_to_string(*args, &block)
opções = _normalize_render(*args, &block) render_to_body(opções)
fim

À primeira vista, o método `render_to_string()` parece bastante semelhante ao método `render()`. A única
diferença é que `render_to_string()` não armazena o modelo renderizado como string.

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 1. Criando nosso próprio renderizador • 14

Figura 3 — Visualização da pilha de renderização quando chamamos render() com


AbstractController e ActionController

corpo da resposta. No entanto, ao analisarmos toda a pilha de renderização, vemos que alguns módulos do
Controlador de Ação sobrecarregam o método render() para adicionar comportamento, enquanto deixam o
método render_to_string() inalterado.

Por exemplo, ao usar `render_to_string()` em nosso renderizador, garantimos que os eventos de


instrumentação definidos por `ActionController::Instrumentation` não sejam acionados duas vezes e não
gerem um erro de renderização dupla, visto que essas funcionalidades foram adicionadas.

apenas para o método render() .

Em alguns outros casos, o método `render_to_string()` também pode ser sobrecarregado. Ao


usar o `ActionController`, o corpo da resposta pode ser outro objeto que não seja uma string, o
que ocorre no streaming de templates. Por esse motivo, `ActionController::Rendering`
sobrescreve o método `render_to_string()` para sempre retornar uma string, como o nome indica.

1.4 Levando isso para o próximo nível

Voltando à nossa implementação de renderização, agora entendemos o que acontece quando adicionamos
a seguinte linha aos nossos controladores:

[Link] { render pdf: "conteúdo" }

Em nosso renderizador, fica assim:

[Link]
errata do relatório • discutir
Machine Translated by Google

Levando a coisa para o próximo nível • 15

pdf = Prawn::[Link] [Link]


render_to_string({}) send_data([Link],
filename: "[Link]",
disposição: "apego"

Quando invocamos `render_to_string()` com um hash vazio, o método `_normalize_options()` na pilha de


renderização detecta o hash vazio e o altera para renderizar o modelo com o mesmo nome da ação atual.
No final, `render_to_string({})` simplesmente passa ` template: "#{controller_name}/#{action_name}"` para
o objeto `view-renderer`.

O fato de nosso renderizador depender de render_to_string() nos permite usar também as seguintes
opções:

render pdf: "conteúdo", modelo: "caminho/para/modelo"

Internamente, o código anterior é idêntico ao seguinte:

pdf = Prawn::[Link] [Link]


render_to_string(template: "path/to/template") send_data([Link], filename:
"[Link]",
disposição: "apego"

Desta vez, `render_to_string()` recebe um modelo explícito para renderizar. Para finalizar nosso
renderizador de PDF, vamos adicionar um teste para confirmar se o modelo escolhido será de fato
renderizado. Nosso teste invoca uma nova ação em `HomeController` que chama `render()` com as opções
`:pdf` e `:template` :

pdf_renderer/2_final/test/dummy/app/controllers/home_controller.rb def outro


render pdf:
"conteúdo", template: "home/index" fim

Vamos adicionar uma rota para esta nova ação:

pdf_renderer/2_final/test/dummy/config/[Link] obter
"/outro", para: "home#outro", como: :outro

Nosso teste simplesmente acessa "/[Link]" e garante que um PDF esteja sendo retornado:

pdf_renderer/2_final/test/integration/pdf_delivery_test.rb teste
"o renderizador de pdf usa o modelo especificado" faça
obter outro_caminho(formato: :pdf) assert_match
"PDF", [Link] assert_equal "binário",
headers["Content-Transfer-Encoding"] assert_equal "anexo; nome_do_arquivo=\"[Link]\"",

cabeçalhos["Content-Disposition"]
assert_equal "application/pdf", headers["Content-Type"] end

Agora execute os testes e veja-os serem aprovados mais uma vez!

[Link]
errata do relatório • discutir
Machine Translated by Google

Capítulo 1. Criando nosso próprio renderizador • 16

1.5 Concluindo
Neste capítulo, criamos um renderizador para o formato PDF. Usando essas ideias, você pode facilmente
criar renderizadores para formatos como CSV e ATOM e encapsular qualquer lógica específica da sua
aplicação em um renderizador. Você pode até mesmo criar um wrapper para outras bibliotecas de PDF que
conseguem converter arquivos HTML em PDF, como a biblioteca paga Prince XML ou a biblioteca de código
aberto Flying Saucer, escrita em Java, mas facilmente acessível via JRuby.<sup>5,6,7</sup>

Também discutimos a pilha de renderização do Rails e sua modularidade. Como o próprio Rails depende
dessa pilha bem definida para estender o ActionController e o ActionMailer, isso torna a API de renderização

mais robusta; ela foi testada e comprovada pelos próprios recursos do Rails e por diversos casos de uso.
Como veremos nos próximos capítulos, essa é uma prática comum em todo o código-fonte do Rails.

Os renderizadores do Rails abrem diversas possibilidades para expandir sua pilha de renderização.
Mas, como acontece com qualquer outra ferramenta poderosa, lembre-se de usar os renderizadores com sabedoria.

Em seguida, vamos analisar o Active Model e seus módulos e criar uma extensão para Rails.
Para usar em controladores e visualizações do Rails.

5. [Link] 6.
[Link] http://
7. [Link]/

[Link] errata do relatório • discutir


Machine Translated by Google

Neste capítulo, veremos: • O


Active Model e seus módulos • Como
fazer um objeto estar em conformidade com a API do Active Model
exigida pelo Rails •
Validadores do Rails e busca de constantes em Ruby

CAPÍTULO 2

Construindo modelos com o Active Model

No capítulo anterior, falamos brevemente sobre o Abstract Controller e como ele reduziu a duplicação de
código no código-fonte do Rails, já que é desacoplado tanto do Action Mailer quanto do Action Controller.
Agora, vamos dar uma olhada no Active Model, que
é semelhante.

O Active Model foi originalmente criado para conter o comportamento compartilhado entre o Active Record e
o Active Resource.1 Assim como no Abstract Controller, as funcionalidades desejadas podem ser
selecionadas incluindo apenas os módulos necessários.
O Active Model também é responsável por definir a interface de programação de aplicativos (API) exigida
pelos controladores e visualizações do Rails, de modo que qualquer outro mapeador objeto-relacional
(ORM) possa usar o Active Model para garantir que o Rails se comporte exatamente como se comportaria
com o Active Record.

Neste capítulo, vamos explorar as duas facetas do Active Model criando um plug-in chamado Mail Form ,
que usaremos em nossos controladores e visualizações. O objetivo do Mail Form é receber um hash de
parâmetros enviados por uma requisição POST, validá-los e enviá-los por e-mail para um endereço
especificado. Essa abstração nos permitirá criar formulários de contato totalmente funcionais em apenas
alguns minutos!

2.1 Criando nosso modelo


Os objetos do tipo `Mail Form` pertencem à parte de modelos na arquitetura MVC (Model-View-Controller),
pois recebem as informações enviadas por meio de um formulário e as entregam a um destinatário
especificado pelo modelo de negócios. Vamos estruturar o `Mail Form` da mesma forma que o Active Record
funciona: forneceremos uma classe chamada `MailForm::Base` que contém os recursos mais comuns que
esperamos em um modelo, como a capacidade de especificar atributos e a integração perfeita com
formulários do Rails. Como fizemos no capítulo anterior, vamos usar o plugin do Rails para criar nosso novo
plug-in:

1. Desde então, o Active Resource foi extraído do código-fonte do Rails e está disponível.
em [Link]

[Link]
errata do relatório • discutir
Machine Translated by Google

Capítulo 2. Construindo Modelos com o Active Model • 18

$ rails plugin novo formulário_de_email

Nossa primeira funcionalidade é implementar um método de classe chamado `attributes()` que permite ao
desenvolvedor especificar quais atributos o objeto `Mail Form` contém. Vamos criar um modelo dentro de `test/
fixtures/sample_mail.rb` como um fixture para usar em nossos testes:

mail_form/1_attributes/test/fixtures/sample_mail.rb classe
SampleMail < MailForm::Base atributos :name, :email
fim

Em seguida, adicionaremos um teste para garantir que os atributos definidos, nome e e-mail, estejam corretos.

Disponível como acessadores no objeto Formulário de E-mail:

mail_form/1_attributes/test/mail_form_test.rb require
"test_helper" require "fixtures/
sample_mail"

classe MailFormTest < ActiveSupport::TestCase


teste "o e-mail de exemplo tem nome e e-mail como atributos" faça sample =
[Link] [Link] =
"User" assert_equal "User",
[Link] [Link] = "user@[Link]"
assert_equal "user@[Link]", [Link] fim

fim

Ao executar o conjunto de testes com `rake test`, ele falha porque `MailForm::Base` ainda não está definido.

Vamos defini-lo em `lib/mail_form/[Link]` e implementar o método ` attributes() `:

mail_form/1_attributes/lib/mail_form/[Link] módulo
MailForm
classe Base
def [Link](*names)
attr_accessor(*names) end end

fim

Nossa implementação delega a criação de atributos para `attr_accessor()`. Antes de executarmos nossos
testes novamente, precisamos garantir que `MailForm::Base` esteja carregado. Uma opção seria incluir
explicitamente `mail_form/base` em `lib/mail_form.rb`. No entanto, vamos usar o `autoload()` do Ruby .

mail_form/1_attributes/lib/mail_form.rb
módulo MailForm

autoload :Base, "mail_form/base" fim

[Link] errata do relatório • discutir


Machine Translated by Google

Criando nosso modelo • 19

A função `autoload()` permite carregar uma constante de forma preguiçosa quando ela é referenciada pela
primeira vez. Assim, notamos que `MailForm` possui uma constante chamada ` Base` definida em `mail_form/
[Link]`. Quando `MailForm::Base` é referenciado pela primeira vez, o Ruby carrega o arquivo `mail_form/[Link]` .

Isso é frequentemente usado em gems do Ruby e no próprio Rails para um processo de inicialização rápido,
já que não precisa carregar tudo antecipadamente.

Com o autoload() configurado, nosso primeiro teste foi aprovado. Temos um modelo simples com atributos,
mas até agora não utilizamos nenhum dos recursos do Active Model. Vamos fazer isso agora.

Adicionando Métodos de Atributo

O ActiveModel::AttributeMethods é um módulo que rastreia todos os atributos definidos, permitindo-nos


adicionar um comportamento comum a todos eles dinamicamente. Para mostrar como funciona, vamos definir
dois métodos de conveniência, clear_name() e clear_email(), que limparão o valor do atributo associado quando
invocados. Vamos escrever

Primeiro, um teste:

mail_form/2_attributes_prefix/test/mail_form_test.rb teste
"o e-mail de exemplo pode limpar atributos usando o prefixo clear_" faça
amostra = [Link]

[Link] = "User"
[Link] = "user@[Link]" assert_equal
"User", [Link] assert_equal
"user@[Link]", [Link]

sample.clear_name
sample.clear_email assert_nil
[Link] assert_nil
[Link] end

Invocar `clear_name()` e `clear_email()` redefine o valor de seus respectivos atributos para `nil`. Com
`ActiveModel::AttributeMethods`, podemos definir `clear_name()` e `clear_email()` dinamicamente em quatro
etapas simples, conforme descrito em nossa nova implementação de `MailForm::Base` :

mail_form/2_attributes_prefix/lib/mail_form/[Link]
módulo MailForm

classe Base
include ActiveModel::AttributeMethods # 1) comportamento dos métodos de atributo attribute_method_prefix
'clear_' # 2) clear_ é o prefixo do atributo

def [Link](*names)
attr_accessor(*names)

[Link]
errata do relatório • discutir
Machine Translated by Google

Capítulo 2. Construindo Modelos com o Active Model • 20

# 3) Solicitar a definição dos métodos de prefixo para os nomes de atributos fornecidos


define_attribute_methods(names) end

protegido

# 4) Como declaramos um prefixo "clear_", espera-se que haja um método "clear_attribute" definido,
que recebe um nome de atributo e implementa a lógica de limpeza. def clear_attribute(attribute)
send("#{attribute}=", nil) end end

fim

Execute `rake test` e todos os testes deverão voltar a passar. `ActiveModel::AttributeMethods` usa
`method_missing()` para compilar os métodos `clear_name()` e `clear_email()` quando são acessados
pela primeira vez. A implementação desses métodos invoca `clear_attribute()`, passando o nome do
atributo como parâmetro.

Se quisermos definir sufixos em vez de um prefixo como `clear_`, precisamos usar o método
`attribute_method_suffix()` e implementá-lo com a lógica de sufixo escolhida. Como exemplo, vamos
implementar os métodos `name?()` e `email?()` , que devem retornar `true` se o respectivo valor do
atributo estiver presente, como no teste a seguir:

mail_form/3_attributes_suffix/test/mail_form_test.rb teste
"o e-mail de exemplo pode perguntar se um atributo está presente ou não" faça
amostra = [Link] assert !
[Link]?

[Link] = "Usuário" assert


[Link]?

""
[Link] = assert !
[Link]? end

Ao executarmos o conjunto de testes, nosso novo teste falha. Para que ele seja aprovado, vamos
definir `?` como um sufixo, alterando nossa implementação de `MailForm::Base` para o seguinte:

mail_form/3_attributes_suffix/lib/mail_form/[Link]
módulo MailForm

classe Base
incluir ActiveModel::AttributeMethods
prefixo_método_de_atributo 'limpar_'

# 1) Adicione o sufixo do atributo


attribute_method_suffix '?'

[Link]
errata do relatório • discutir
Machine Translated by Google

Criando nosso modelo • 21

def [Link](*names)
attr_accessor(*names)
define_attribute_methods(names) end protected

def clear_attribute(attribute) send("#{attribute}


=", nil) end

# 2) Implemente a lógica exigida pelo sufixo '?' def attribute?(attribute)

enviar(atributo).presentar? fim

fim
fim

Agora temos os métodos de prefixo e sufixo definidos e os testes estão passando.


Mas e se quisermos definir tanto o prefixo quanto o sufixo ao mesmo tempo?
Poderíamos usar o método attribute_method_affix() , que aceita um hash especificando tanto o
prefixo quanto o sufixo.

O Active Record utiliza métodos de atributos extensivamente. Um exemplo é o método


`attribute_before_type_cast() `, que usa `_before_type_cast` como sufixo para retornar dados
brutos, conforme recebidos de formulários. A funcionalidade de alteração de atributos (dirty),
que também faz parte do Active Model, é construída sobre ` ActiveModel::AttributeMethods` e
define diversos métodos como `attribute_changed?()`, `reset_attribute!()`, e assim por diante.
Você pode verificar o código-fonte da implementação de alteração de atributos no repositório do Rails.²

Visando uma API compatível com o Active Model: Mesmo

tendo adicionado atributos aos nossos modelos para armazenar dados de formulário, precisamos garantir que
nosso modelo esteja em conformidade com a API Active Model; caso contrário, não poderemos usá-lo em
nossos controladores e visualizações.

Como de costume, alcançaremos essa conformidade por meio do desenvolvimento orientado a


testes (TDD), exceto que desta vez não precisaremos escrever os testes — o Rails já os fornece
em um módulo chamado ActiveModel::Lint::Tests. Quando incluído, este módulo define vários
testes que verificam se cada método exigido em uma API compatível com Active Model existe.
Cada um desses testes espera que uma variável de instância chamada @model retorne o objeto
que queremos verificar. No nosso caso, @model deve conter uma instância de SampleMail, que
estará em conformidade se MailForm::Base estiver em conformidade. Vamos criar um novo
arquivo de teste chamado test/compliance_test.rb com o seguinte conteúdo:

2. [Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 2. Construindo Modelos com o Active Model • 22

mail_form/4_am_compliance/test/compliance_test.rb require
'test_helper' require 'fixtures/
sample_mail'

classe ComplianceTest < ActiveSupport::TestCase inclui


ActiveModel::Lint::Tests

def setup
@model = [Link] end

fim

Ao executar o comando `rake test`, obtemos várias falhas, todas com o seguinte motivo:

O objeto deve responder a to_model.

Quando os controladores e auxiliares de visualização do Rails recebem um modelo, eles primeiro


chamam o método `to_model()` e manipulam o resultado retornado em vez de manipular o modelo diretamente.
Isso permite que implementações de ORM que não desejam adicionar métodos do Active Model à sua
API retornem um objeto proxy onde esses métodos estão definidos.
No nosso caso, queremos adicionar métodos do Active Model diretamente ao MailForm::Base.
Consequentemente, nossa implementação de to_model() deve retornar self, como mostrado aqui:

def to_model self

fim

Embora pudéssemos adicionar esse método ao MailForm::Base, não o


implementaremos nós mesmos. Em vez disso, vamos incluir o ActiveModel::Conversion,
que implementa o método to_model() exatamente como discutimos, além de outros
três métodos exigidos pelo Active Model: to_key(), to_param() e to_partial_path().

O método `to_key()` deve retornar um array de chaves que identifica o modelo de forma única, caso exista, e é
usado por `dom_id()` nas views. O método `dom_id()` foi adicionado ao Rails juntamente com `dom_class()` e
vários outros helpers para melhor organizar nossas views. Por exemplo, `div_for(@post)`, onde `@post` é uma
instância Active Record da classe `Post` com o id 42, depende de ambos os métodos para criar uma div onde o
atributo `id` é igual a `post_42` e o atributo `class` é `post`. Para Active Record, `to_key()` retorna um array

contendo o ID do registro do banco de dados.

Por outro lado, `to_param()` é usado no roteamento e pode ser sobrescrito em qualquer modelo para
gerar uma URL única para esse modelo. Quando invocamos `post_path(@post)`, o Rails chama
`to_param()` no objeto `@post` e usa seu resultado para gerar a URL final. Para o Active Record, o
padrão é retornar o ID como uma string.

[Link] errata do relatório • discutir


Machine Translated by Google

Criando nosso modelo • 23

Por fim, temos o método `to_partial_path()`. Este método é invocado sempre que passamos um registro ou
uma coleção de registros para o método `render()` em nossas views. O Rails percorrerá cada um desses
registros e recuperará o caminho para o seu partial. Por exemplo, o caminho para uma instância da classe
`Post` é `posts/post`.

É importante entender não apenas o que esses métodos fazem, mas também o que eles nos permitem
alcançar. Por exemplo, ao personalizar o método `to_param()`, podemos alterar facilmente as URLs dos
nossos objetos. Imagine uma classe `Post` com atributos `id` e `title` ; alterar as URLs dessas postagens para
incluir o título é tão simples quanto isto:

def to_param "#{id}-


#{[Link]}" end

Da mesma forma, imagine que cada postagem tenha um formato diferente. Pode ser um vídeo, um link ou um
texto extenso, e cada um desses formatos deve ser renderizado de forma diferente. Se armazenarmos o

formato da postagem do blog no atributo `format` , poderíamos renderizar cada postagem da seguinte maneira:

@[Link] do |post|
render partial: "posts/post_#{[Link]}",
locais: { post: @post }
fim

No entanto, ao sobrescrever to_partial_path() desta forma

def to_partial_path "posts/


post_#{format}" end

Nossa opinião simplesmente chamaria

renderizar @posts

Isso não só torna nosso código mais limpo, como também melhora o desempenho da nossa aplicação. No
primeiro exemplo, acabamos percorrendo a pilha de renderização do Rails várias vezes, buscando templates
e duplicando esforços. No entanto, ao personalizar o método `to_partial_path()`, chamamos o método `render()`
apenas uma vez, permitindo que o Rails busque todos os partials de forma eficiente em uma única chamada.

A implementação padrão de `to_partial_path() ` disponível em `ActiveModel::Conversion` permite fornecer


caminhos parciais para objetos `MailForm::Base`, assim como em qualquer objeto do Active Record. No
entanto, como nossos objetos nunca são persistidos, eles não são identificados de forma única, o que significa
que tanto `to_key()` quanto `to_param()` devem retornar `nil`. Esse é exatamente o comportamento fornecido
por `ActiveModel::Conversion`. Vamos incluí-lo em nossa classe `MailForm::Base` :

[Link]
errata do relatório • discutir
Machine Translated by Google

Capítulo 2. Construindo Modelos com o Active Model • 24

mail_form/4_am_compliance/lib/mail_form/[Link] módulo
MailForm
classe Base
incluir ActiveModel::Conversion

Ao incluir este módulo e executar o comando `rake test`, obtemos erros com as seguintes mensagens (você
pode recebê-las em ordem diferente):

O modelo deve responder ao nome do modelo.

O modelo deve responder aos erros.

O modelo deve responder à persistência?

Para corrigir o primeiro teste com falha, precisamos estender a classe MailForm::Base com
ActiveModel::Nomeação:

mail_form/4_am_compliance/lib/mail_form/[Link] módulo
MailForm
classe Base
incluir ActiveModel::Conversion
estender ActiveModel::Naming

Após estendermos nossa classe com `ActiveModel::Naming`, ela responde a um método chamado
`model_name()` que retorna uma instância de `ActiveModel::Name`, que se comporta como uma string e
fornece alguns métodos, como `human()`, `singular()` e outros que são inflexionados a partir do nome do
modelo. Vamos adicionar um pequeno caso de teste ao nosso conjunto para mostrar esses métodos e o

que eles retornam:

mail_form/4_am_compliance/test/compliance_test.rb teste
"model_name expõe nome singular e nome humano" faça
assert_equal "sample_mail", @[Link].model_name.singular assert_equal "Sample mail",
@[Link].model_name.human end

Isso é semelhante ao comportamento exibido pelo Active Record. A única diferença é que o Active Record
suporta internacionalização (I18n) e o MailForm não. Felizmente, isso pode ser facilmente corrigido
estendendo MailForm::Base com ActiveMod-el::Translation. Vamos escrever um teste primeiro:

mail_form/4_am_compliance/test/compliance_test.rb teste
"model_name.human usa I18n" faça
início
[Link].store_translations :en,
activemodel: { models: { sample_mail: "Meu e-mail de exemplo" } }

assert_equal "Meu e-mail de exemplo", @[Link].model_name.human


garantir

[Link]!
fim
fim

[Link]
errata do relatório • discutir
Machine Translated by Google

Criando nosso modelo • 25

O teste adiciona uma nova tradução ao backend I18n que contém o desejado.

Nome humano para a classe SampleMail . Precisamos envolver o código na cláusula begin...ensure para
garantir que o backend I18n seja recarregado, removendo a tradução que armazenamos. Vamos atualizar
MailForm::Base para que o novo teste seja aprovado:

mail_form/4_am_compliance/lib/mail_form/[Link]
módulo MailForm
classe Base
incluir ActiveModel::Conversion
estender ActiveModel::Naming estender
ActiveModel::Translation

Após adicionarmos comportamentos de nomenclatura e tradução, o comando `rake test` retorna menos falhas,
mostrando que estamos progredindo. Desta vez, nossos testes falham pelos seguintes motivos:
motivos:

O modelo deve responder aos erros.

O modelo deve responder à persistência?

A primeira falha está relacionada às validações. O Active Model não menciona nada sobre macros de validação
(como `validates_presence_of()`), mas exige que definamos um método chamado `errors()`, que retorna um
Hash, e cada valor nesse hash é um Array. Podemos corrigir essa falha incluindo `ActiveModel::Validations`

em nosso modelo:

mail_form/4_am_compliance/lib/mail_form/[Link]
módulo MailForm
class Base
include ActiveModel::Conversion extend
ActiveModel::Naming extend
ActiveModel::Translation
incluir ActiveModel::Validações

Agora, nossa instância de modelo responde a errors() e valid?(), comportando-se exatamente como no
Active Record. Além disso, ActiveModel::Validations adiciona várias macros de validação, como validates(),
validates_format_of() e validates_inclusion_of().

Por agora, vamos executar o comando `rake test` e ver o que falta para que nosso conjunto de testes volte a
passar:

O modelo deve responder à persistência?

Desta vez, o Rails não nos ajudará. Felizmente, é bastante fácil implementar o método `persisted?()` nós
mesmos. Tanto nossos controladores quanto nossas views usam o método ` persisted?()` , em diferentes
circunstâncias. Por exemplo, sempre que invocamos `form_for(@model)`, ele verifica se o modelo foi persistido.
Se sim, ele cria um formulário que realiza uma requisição PUT.

[Link]
errata do relatório • discutir
Machine Translated by Google

Capítulo 2. Construindo Modelos com o Active Model • 26

solicitação; caso contrário, deve fazer uma solicitação POST. O mesmo acontece em url_for()
quando gera uma URL com base no seu modelo.

No Active Record, o objeto é persistido se for salvo no banco de dados; em outras palavras, se
não for um registro novo nem destruído. No entanto, em nosso caso, nosso objeto não será salvo
em nenhum banco de dados e, consequentemente, persisted?() deve sempre retornar falso.

Vamos adicionar o método persisted?() à nossa implementação de MailForm::Base :

mail_form/4_am_compliance/lib/mail_form/[Link] def
persistiu? falso

fim

Desta vez, após executar o comando `rake test`, todos os testes foram aprovados! Isso significa que nosso modelo
está em conformidade com a API Active Model. Muito bem!

Entrega do formulário
O próximo passo na implementação do nosso formulário de e-mail é adicionar a lógica que entrega
um e-mail com os atributos do modelo. O método `deliver() ` cuida da entrega e envia um e-mail
para o endereço armazenado no atributo `email` do nosso modelo . O corpo do e-mail contém
todos os atributos do modelo e seus respectivos valores. Vamos especificar esse comportamento
adicionando um novo teste ao arquivo `test/mail_form_test.rb`:

mail_form/5_delivery/test/mail_form_test.rb setup
do
ActionMailer::[Link]
fim

teste "envia um e-mail com atributos" faça


amostra = [Link] # Simule
dados do formulário
[Link] = "user@[Link]" [Link]

assert_equal 1, ActionMailer::[Link] mail = ActionMailer::[Link]

assert_equal ["user@[Link]"], [Link] assert_match "Email:


user@[Link]", [Link] end

Ao executarmos o novo teste, obtemos uma falha porque o método `deliver()` ainda não existe.
Como nosso modelo possui o conceito de validade de `ActiveModel::Validations`, o método
`deliver()` deveria entregar o e-mail se o objeto `MailForm` for válido.

[Link] errata do relatório • discutir


Machine Translated by Google

Criando nosso modelo • 27

mail_form/5_delivery/lib/mail_form/[Link]
entrega definida
se válida?
MailForm::[Link](self).deliver else false

fim
fim

A classe responsável por criar e enviar o e-mail é MailForm::Notifier.


Vamos implementar isso usando o Action Mailer:

mail_form/5_delivery/lib/mail_form/[Link]
módulo MailForm
classe Notificador < ActionMailer::Base
append_view_path File.expand_path("../../views", __FILE__)

def contato(formulário_de_e-mail)
@mail_form = mail_form
mail(mail_form.headers) fim

fim
fim

A ação `contact()` em nosso mailer atribui um valor a `@mail_form` e, em seguida, invoca o método `
headers()` no objeto `MailForm` fornecido. Esse método deve retornar um hash com os dados do e-mail como
chaves, como `:to` , `:from` e `:subject` , e não deve ser definido em `MailForm::Base`, mas sim em cada
classe filha. Este é um contrato de API simples, porém poderoso, que permite ao desenvolvedor personalizar
o envio de e-mails sem a necessidade de redefinir ou modificar a classe `Notifier` .

Nosso MailForm::Notifier também chama append_view_path(), que adiciona lib/views dentro da nossa pasta
de plug-ins como um novo local para buscar templates. O último passo antes de executarmos o conjunto de
testes novamente é carregar automaticamente nossa nova classe:

mail_form/5_delivery/lib/mail_form.rb
carregamento automático: Notificador, "mail_form/notificador"

Em seguida, vamos definir o método headers() na classe SampleMail :

mail_form/5_delivery/test/fixtures/sample_mail.rb
cabeçalhos de definição
{ para: "recipient@[Link]", de: [Link] } fim

Agora, quando executamos o comando `rake test`, ele falha com a seguinte mensagem:

1) Falha:
test_delivers_an_email_with_attributes(MailFormTest):
ActionView::MissingTemplate: Modelo ausente mail_form/notifier/contact

[Link]
errata do relatório • discutir
Machine Translated by Google

Capítulo 2. Construindo Modelos com o Active Model • 28

Isso é esperado, já que não adicionamos um modelo ao nosso sistema de e-mail. Nosso modelo
de e-mail padrão exibirá o assunto da mensagem e imprimirá todos os atributos e seus respectivos
valores:

mail_form/5_delivery/lib/views/mail_form/notifier/[Link] <%=
[Link] %> <%
@mail_form.attribute_names.each do |key| -%>
<%= @mail_form.class.human_attribute_name(key) %>: <%= @mail_form.send(key) %> <% end -%>

Para exibir todos os atributos, precisamos de uma lista com todos os nomes dos atributos, mas
ainda não mantemos essa lista. Podemos implementar essa lista definindo uma classe `attribute()`
chamada `attributes_names()` que é atualizada sempre que chamamos `attributes()`:

mail_form/5_delivery/lib/mail_form/[Link] # 1)
Defina um atributo de classe e inicialize-o : class_attribute :attribute_names
self.attribute_names = []

def [Link](*names)
attr_accessor(*names)
define_attribute_methods(names)

# 2) Adicione novos nomes conforme forem definidos:


self.attribute_names += names end

Quando usamos `class_attribute()` para definir os nomes, a herança funciona automaticamente.


Portanto, se uma classe eventualmente herdar da nossa fixture `SampleMail` , ela herdará
automaticamente todos os nomes de seus atributos também.

Após executarmos o comando `rake test`, todos os testes devem passar e estar concluídos,
indicando que a implementação do formulário de e-mail está finalizada. Sempre que precisarmos
criar um formulário de contato, criamos uma classe que herda de `MailForm::Base`, definimos
nossos atributos e os cabeçalhos do e-mail, e pronto! Para garantir que tudo funcione exatamente
como esperado, vamos verificar todo o processo com um teste de integração.

2.2 Testes de integração com Capybara


No capítulo anterior, usamos os recursos de teste do Rails para garantir que um PDF fosse
enviado de volta ao cliente. Para garantir que nosso projeto funcione como um formulário de
contato, devemos criar um formulário real, enviá-lo para o endpoint apropriado e verificar se o e-
mail foi enviado. Esse tipo de teste é particularmente difícil de escrever usando apenas as
ferramentas de teste do Rails. Na maioria das vezes, acabamos escrevendo requisições diretas
para os endpoints:

post "/contact_form", contact_form: { email:


"jose@[Link]", message: "olá"}

[Link] errata do relatório • discutir


Machine Translated by Google

Testes de integração com Capybara • 29

Escrever um teste usando post() e parâmetros explícitos funciona bem em alguns cenários, principalmente para
APIs, mas não é adequado para testar o fluxo de trabalho de um formulário de contato.
Por exemplo, como garantimos que haja um botão "Enviar" na página? O que acontece quando clicamos nele? A
solicitação é enviada para o URL correto? E se esquecermos o campo de e-mail?

Para garantir que todas essas perguntas sejam respondidas, é comum usar uma ferramenta de teste mais robusta
como o Capybara.3 O Capybara torna esse tipo de teste trivial, fornecendo uma linguagem específica de domínio
(DSL) fácil de usar, que utilizaremos ao longo do livro. O primeiro passo é adicioná-lo como uma dependência de
desenvolvimento ao nosso gemspec:

mail_form/5_delivery/mail_form.gemspec
s.add_development_dependency "capybara", "~> 2.0.0"

Para usar o Capybara, vamos definir uma nova classe de caso de teste chamada ActiveSupport::IntegrationCase.
Essa classe é construída sobre ActiveSupport::TestCase e inclui os helpers de URL do Rails e a DSL do Capybara:

mail_form/5_delivery/test/test_helper.rb
requer "capivara" requer
"capivara/rails"

# Define um caso de teste básico para usar com a classe Capybara


ActiveSupport::IntegrationCase < ActiveSupport::TestCase
include Capybara::DSL include
[Link].url_helpers end

Agora estamos prontos para escrever nosso primeiro teste usando-o:

mail_form/5_delivery/test/integration/navigation_test.rb requer
"test_helper"

classe NavigationTest < ActiveSupport::IntegrationCase


setup do
ActionMailer::[Link]
fim

teste "envia um e-mail após preencher o formulário de contato" faça


visite "/"

Preencha os campos com: "John Doe"


"Nome" e " Email" com: "[Link]@[Link]" e " Mensagem " com: "O
MailForm é incrível!"

click_button "Entregar"

3. [Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 2. Construindo Modelos com o Active Model • 30

assert_match "Sua mensagem foi enviada com sucesso.", [Link]

assert_equal 1, ActionMailer::[Link] mail = ActionMailer::[Link]

assert_equal ["[Link]@[Link]"], [Link] assert_equal


["recipient@[Link]"], [Link] assert_match "Message: MailForm rocks!",
[Link] end

fim

O teste de integração navega até o caminho raiz, que retorna um formulário com os campos nome, e-mail e mensagem.

Ao enviar o formulário, o servidor entrega um e-mail ao destinatário configurado com a mensagem fornecida e exibe uma

mensagem de sucesso para o usuário. Para que o teste seja aprovado, vamos adicionar o modelo, o controlador, as

visualizações e as rotas ao nosso aplicativo de teste, começando pelas rotas:

mail_form/5_delivery/test/dummy/config/[Link]
Dummy::[Link] do
recursos :contact_forms, somente: :criar raiz para:
"contact_forms#new" fim

O controlador e a visualização são os seguintes:

mail_form/5_delivery/test/dummy/app/controllers/contact_forms_controller.rb classe
ContactFormsController < ApplicationController
def novo

@contact_form = [Link] end

def criar

@contact_form = [Link](params[:contact_form])

if @contact_form.deliver redirect_to
root_url, notice: "Sua mensagem foi enviada com sucesso." else

ação de renderização: "novo"


fim
fim
fim

mail_form/5_delivery/test/dummy/app/views/contact_forms/[Link] <h1>Novo
Formulário de Contato </h1>

<%= form_for(@contact_form) do |f| %> <% if


@contact_form.[Link]? %> <div id="errorExplanation">
<h2>Ops, algo deu errado:</h2> <ul>

<% @contact_form.errors.full_messages.each do |msg| %>

[Link]
errata do relatório • discutir
Machine Translated by Google

Testes de integração com Capybara • 31

<li><%= msg %></li> <% end


%>
</ul></
div> <%
end %> <div
class="field">
<%= [Link] :name %><br /> <%=
f.text_field :name %> </div> <div

class="field">
<%= [Link] :email %><br /> <%=
f.text_field :email %> </div> <div

class="field">
<%= [Link] :message %><br /> <%=
f.text_field :message %> </div> <div

class="actions"> <%= [Link]


"Entregar" %> </div> <% end %>

E, finalmente, aqui está o modelo:

mail_form/5_delivery/test/dummy/app/models/contact_form.rb classe
ContactForm <MailForm::Base
atributos :nome, :email, :mensagem

cabeçalhos de definição

{ para: "recipient@[Link]", de: [Link] } fim

fim

Como nossos testes usam mensagens flash, precisamos adicioná-las ao layout logo antes da chamada
`yield` :

mail_form/5_delivery/test/dummy/app/views/layouts/[Link] <p
style="color: green"><%= aviso %></p>

Com tudo configurado, vamos executar o conjunto de testes e… ocorre uma falha inesperada:

1) Erro:
test_sends_an_e-mail_after_filling_the_contact_form(NavigationTest):
ArgumentError: número incorreto de argumentos (1 em vez de 0)
app/controllers/contact_forms_controller.rb:7:in `initialize'

A falha ocorre porque o método `initialize()` em `MailForm::Base`, diferentemente do Active Record, não
espera um hash como argumento. Observe que uma API compatível com Active Model não especifica
como nossos modelos devem ser estruturados.

[Link]
errata do relatório • discutir
Machine Translated by Google

Capítulo 2. Construindo Modelos com o Active Model • 32

Inicializado. Vamos implementar um método `initialize()` , que recebe um hash como argumento e define
os valores dos atributos:

mail_form/5_delivery/lib/mail_form/[Link] def
initialize(atributos = {})
[Link] faça |atributo, valor|
self.public_send("#{attr}=", valor)
fim se atributos fim

Após definirmos o método anterior, nosso teste de integração é bem-sucedido, demonstrando que tudo
funciona conforme o esperado. Lembre-se de que, se você acessar o aplicativo de exemplo dentro de `test/
dummy`, poderá executar o comando `rails s` como em qualquer outro aplicativo Rails. Sinta-se à vontade
para iniciar o servidor, adicionar algumas validações à sua classe `ContactForm` e se divertir com ele.

2.3 Levando isso para o próximo nível

Na seção anterior, criamos nosso plug-in de formulário de e-mail com alguns recursos básicos e
adicionamos testes de integração para garantir seu funcionamento. No entanto, podemos fazer muito mais
com o Active Model. Vejamos alguns exemplos.

Validadores

Todo desenvolvedor Rails está familiarizado com as validações do Rails, pois elas são frequentemente
usadas para exemplificar a produtividade que pode ser alcançada com o Rails. No código-fonte do Rails,
cada validação é respaldada por uma classe validadora . Vejamos a macro ` validates_presence_of()`
como exemplo:

rails/activemodel/lib/active_model/validations/[Link] def
validates_presence_of(*attr_names) validates_with
PresenceValidator, _merge_attributes(attr_names) end

O método `validates_with()` é responsável por inicializar a classe `ActiveModel::Validations::PresenceValidator`


fornecida , e o método `_merge_attributes()` converte os atributos fornecidos em um hash. Quando você
invoca o seguinte

valida_presença_de :nome

Na verdade, você está fazendo isso:

valida_com PresenceValidator, atributos: [:nome]

que é praticamente o mesmo que isto:

validar [Link](atributos: [:nome])

Esse processo é semelhante ao que acontece com o método validates() :

[Link] errata do relatório • discutir


Machine Translated by Google

Levando a coisa para o próximo nível • 33

valida: nome, presença: verdadeiro

Isso tem o mesmo efeito que o seguinte:

validar [Link](atributos: [:nome])

A questão é: como o Rails sabe que a chave `:presence` deve usar o `PresenceValidator`? Simples:
ele converte a chave `:presence` para "PresenceValidator" e então tenta encontrar uma constante
chamada `PresenceValidator` na classe atual, assim como no exemplo a seguir:

const_get("#{key.to_s.camelize}Validator")

É importante discutirmos isso porque agora podemos adicionar qualquer validador a qualquer classe,
confiando exclusivamente na busca de constantes do Ruby. Para entender exatamente como
funciona, vamos iniciar uma nova sessão do irb e digitar o seguinte:

módulo Foo
módulo Bar
fim
fim

classe Baz
inclui Foo
fim

Baz::Bar # => Foo::Bar

Observe como a última linha do script retorna `Foo::Bar` mesmo que `Bar` não esteja definido dentro
da classe `Baz` . Isso ocorre porque, sempre que uma constante é procurada, o Ruby pesquisa em
todos os objetos na cadeia de ancestrais. Como `Foo` está incluído em `Baz`, `Foo` é um ancestral
de `Baz`, permitindo que o Ruby encontre a constante `Foo::Bar` (você pode verificar os ancestrais
de `Baz` digitando `[Link]` na sessão anterior do IRB ).

Para demonstrar como podemos usar isso na prática, vamos implementar um validador de ausência
em nosso MailForm::Base. Como muitos spams costumam chegar por meio de formulários de
contato, usaremos o validador de ausência como uma isca.

O honeypot funciona criando um campo, como o apelido, e ocultando-o com CSS. Dessa forma, os
humanos não veem o campo e, consequentemente, não o preenchem, mas os robôs o preenchem
como qualquer outro campo. Assim, sempre que o valor do apelido estiver presente, o e-mail não
deve ser enviado, pois certamente se trata de spam.

Dadas as regras de busca constante do Ruby, podemos adicionar uma opção `:absence` ao método
` validates()` de qualquer classe implementando a interface `AbsenceValidator` dentro de um módulo
e incluindo esse módulo na classe desejada. Vamos começar escrevendo um teste simples para isso:

[Link]
errata do relatório • discutir
Machine Translated by Google

Capítulo 2. Construindo Modelos com o Active Model • 34

mail_form/6_final/test/mail_form_test.rb teste
"valida a ausência de apelido "
amostra = [Link](apelido: "Spam") assert ![Link]?
assert_equal ["é inválido"],
[Link][:apelido] end

O teste mostra que o registro deve ser inválido se o campo de apelido contiver qualquer valor. Vamos adicionar
o campo de apelido com a validação `:absence` ao nosso objeto `SampleMail` :

mail_form/6_final/test/fixtures/sample_mail.rb
atributos: apelido

valida :apelido, ausência: verdadeiro

Ao executar o comando `rake test`, obtemos uma falha no teste, pois o `SampleMail` não pode mais ser
carregado porque o `AbsenceValidator` não está definido em nenhum lugar. Vamos criá-lo:

mail_form/6_final/lib/mail_form/[Link]
Módulo MailForm
Módulo Validadores
classe ValidadorDeAusência < ActiveModel::EachValidator
def validate_each(registro, atributo, valor)
[Link](attribute, :invalid, options) unless [Link]? end

fim
fim
fim

Nosso validador herda de EachValidator. Para cada atributo fornecido na inicialização, EachValidator chama
o método validate_each() com o registro, o atributo e seu respectivo valor. Para cada atributo, adicionamos
uma mensagem de erro, a menos que o valor esteja em branco.

Em seguida, vamos incluir MailForm::Validators em MailForm::Base:

mail_form/6_final/lib/mail_form/[Link]
incluir MailForm::Validators

Isso adicionará `MailForm::Validators` à cadeia de ancestrais de `MailForm::Base` . Assim, sempre que

passarmos `:absence` como chave para `validates()`, o sistema buscará uma constante `AbsenceValidator` ,
a encontrará dentro de `MailForm::Validators` e a inicializará, de forma semelhante ao que foi feito com `

PresenceValidator`. Para garantir que funcione corretamente, precisamos carregar automaticamente o


contêiner de validadores:

mail_form/6_final/lib/mail_form.rb
autoload :Validadores, 'mail_form/validadores'

Execute `rake test` e todos os testes serão aprovados novamente. A beleza dessa implementação é que
adicionar a chave `:absence` ao `validates()` não exigiu que registrássemos a opção.

[Link]
errata do relatório • discutir
Machine Translated by Google

Levando a coisa para o próximo nível • 35

em qualquer lugar. Essas opções são descobertas em tempo de execução usando a pesquisa de constantes do
Ruby.

Fique à vontade para adicionar o campo de apelido às nossas visualizações de formulário de contato e ocultá-lo com

CSS, habilitando totalmente nosso honeypot. Cabe a você escrever um teste de integração para ele, já que ainda
temos mais investigações sobre o Active Model a fazer.

Retornos de chamada

Não seria ótimo se pudéssemos fornecer ganchos em torno do método `deliver() ` para adicionar algum
comportamento antes e depois da entrega? Isso é bem fácil de fazer com `ActiveModel::Callbacks`. Para ilustrar a
API que pretendemos construir, vamos alterar nosso fixture `SampleMail` :

mail_form/6_final/test/fixtures/sample_mail.rb
before_deliver do
evaluated_callbacks << :before end

after_deliver do
evaluated_callbacks << :after end

def evaluated_callbacks
@evaluated_callbacks ||= [] end

Adicionamos um método `evaluated_callbacks()` para armazenar todos os callbacks avaliados e implementamos os


callbacks `before_deliver()` e `after_deliver()` . Nosso teste deve chamar o método `deliver()` em nossa instância de
`SampleMail` e verificar se ambos os callbacks foram avaliados.

mail_form/6_final/test/mail_form_test.rb teste
"fornece ganchos de entrega antes e depois "
amostra = [Link](email: "user@[Link]") [Link]
assert_equal
[:before, :after], amostra.evaluated_callbacks end

Por fim, vamos adicionar suporte a callbacks em MailForm::Base. Isso pode ser feito em três etapas: estender nossa
classe com a funcionalidade ActiveModel::Callbacks , definir nossos callbacks e, finalmente, sobrescrever a
implementação de deliver() para executar os callbacks antes e depois da entrega:

mail_form/6_final/lib/mail_form/[Link] # 1)
Adicionar comportamento de retorno de
chamada estendendo ActiveModel::Callbacks

# 2) Defina os callbacks. A linha abaixo criará callbacks `before_deliver` e `after_deliver` com a mesma semântica que
em `define_model_callbacks :deliver` do Active Record.

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 2. Construindo Modelos com o Active Model • 36

# 3) Alterar `deliver` para executar os callbacks: `def deliver if


valid?

run_callbacks(:deliver) do`
MailForm::[Link](self).deliver fim

outro
falso
fim
fim

Como funções de retorno de chamada do Active Record, você pode fornecer procedimentos, strings, símbolos e
qualquer objeto que responda ao nome da função de retorno. Sinta-se à vontade para experimentar essas opções!

Um modelo básico

Em muitas circunstâncias, um desenvolvedor deseja obter uma API compatível com o Active Model: em formulários
de contato, ao implementar funcionalidades de busca ou mesmo ao dividir um fluxo de cadastro em várias etapas.
Por esse motivo, o Active Model fornece um módulo ActiveModel::Model que pode ser incluído em qualquer classe.

class Person
include ActiveModel::Model
attr_accessor :nome, :idade fim

pessoa = [Link](nome: 'bob', idade: '18') [Link] # =>


'bob' [Link] # => 18

Ao incluir `ActiveModel::Model`, garantimos que ele passará em todos os testes de `ActiveModel::Lint::Tests` .


Vejamos o código-fonte:

rails/activemodel/lib/active_model/[Link]
módulo ActiveModel módulo
Modelo
def [Link](base) base.class_eval
do extend ActiveModel::Naming
extend ActiveModel::Translation

incluir ActiveModel::Validations incluir


ActiveModel::Conversion fim

fim

def inicializar(params={})
[Link] do |attr, value|
self.public_send("#{attr}=", value) end if params end

[Link] errata do relatório • discutir


Machine Translated by Google

Concluindo • 37

def persistiu? falso

fim
fim
fim

Como podemos ver, este módulo básico contém um subconjunto do comportamento que implementamos neste

capítulo. É um bom ponto de partida sempre que precisarmos dessas funcionalidades em nossas aplicações!

2.4 Concluindo
Neste capítulo, abordamos como usar o Active Model para criar rapidamente nossos próprios
modelos que se integram perfeitamente aos controladores e visualizações do Rails. Falamos
sobre ActiveModel::AttributeMethods, ActiveModel::Conversion, ActiveModel::Naming,
ActiveModel::Translation, ActiveModel::Validations e, finalmente, ActiveModel::Callbacks. Também
exploramos os validadores do Rails e como podemos estender facilmente o comportamento do método validates() .

Mesmo depois de tudo isso, o Active Model ainda tem alguns outros módulos para explorar, como ActiveModel::Dirty
e ActiveModel::Serialization. Eles nos permitem incorporar atributos modificados e serializadores como to_xml() e
to_json() diretamente em nossos modelos, assim como nos modelos do Active Record.

Por fim, se você gostou do Mail Form aqui apresentado, confira o Mail Form da Platafor-matec,4 uma gem pronta
para produção criada com os mesmos conceitos explorados neste capítulo. Ela também possui recursos
adicionais, como o tratamento de anexos e a capacidade de adicionar informações à solicitação.

No próximo capítulo, voltaremos a estudar a pilha de renderização do Rails e a estenderemos para buscar um
modelo no banco de dados em vez do sistema de arquivos, mantendo o foco no desempenho.

4. [Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Neste capítulo, veremos: •


Como personalizar a pilha de renderização do Rails para buscar
templates no banco de
dados; • Como funciona a busca por
hash em Ruby ; • Como acelerar os controladores com ActionController::Metal.

CAPÍTULO 3

Recuperando modelos de visualização

de repositórios personalizados

Quando o Rails renderiza um modelo, ele precisa obter esse modelo de algum lugar.
Por padrão, o Rails serve templates do sistema de arquivos, mas não precisa ser limitado a
isso. O Rails fornece hooks que nos permitem recuperar templates de qualquer lugar que
desejarmos, desde que implementemos a API (Interface de Programação de Aplicativos)
necessária. Vamos explorar isso construindo um mecanismo que nos permita servir templates
de um banco de dados — templates que podem ser criados, atualizados e excluídos através
da interface web. Mas primeiro, vamos dar uma olhada mais aprofundada na pilha de
renderização do Rails.

3.1 Revisitando a Pilha de Renderização


Na Seção 1.3, Compreendendo a Pilha de Renderização do Rails, na página 9, vimos que a
principal responsabilidade da pilha de renderização dos controladores do Rails é normalizar as
opções e enviá-las para uma instância de `ActionView::Renderer`. Quando invocado, o
renderizador recebe uma instância de `ActionView::Base`, chamada de contexto de visualização,
e um hash de opções normalizadas usado para encontrar, compilar e renderizar um modelo
específico.

Sempre que renderizamos um template no Rails, seu código-fonte precisa primeiro ser
compilado em código Ruby executável. Cada vez que um código Ruby é executado, sua
execução ocorre dentro de um contexto específico e, em uma aplicação Rails, as views são
executadas dentro do objeto de contexto da view. Todos os helpers disponíveis em nossos
templates, como ` form_for()` e `link_to()`, são definidos em módulos incluídos no objeto de
contexto da view.

Além do contexto de visualização, o renderizador de visualização tem acesso a uma instância


de ActionView::LookupContext, geralmente chamada de lookup_context. O contexto de pesquisa

[Link]
errata do relatório • discutir
Machine Translated by Google

Capítulo 3. Recuperando modelos de visualização de repositórios personalizados • 40

é compartilhado entre controladores e visualizações e contém todas as informações necessárias para encontrar um

modelo. Por exemplo, sempre que uma solicitação JSON (JavaScript Object Notation) é recebida, o formato da
solicitação é armazenado no objeto lookup_context , de modo que o Rails procurará apenas modelos vinculados ao
formato JSON.

O contexto de pesquisa também é responsável por armazenar todos os caminhos de visualização. Um caminho de
visualização é uma coleção de objetos capaz de encontrar modelos com base em um conjunto de condições. Todos
os controladores em uma aplicação Rails possuem um caminho de visualização por padrão, que é um caminho do
sistema de arquivos apontando para app/views. Dado um conjunto de condições como nome do modelo, localidade
e formato, esse caminho de visualização encontra um modelo específico em app/views. Por exemplo, sempre que
houver uma requisição HTML na ação index de um UsersController, esse caminho de visualização padrão tentará
encontrar um modelo em app/views/users/[Link].*. Se o modelo desejado for encontrado, ele será compilado e
renderizado, como mostrado na imagem a seguir.

Figura 4 — Fluxo de trabalho de renderização entre um controlador, um renderizador de visualização, um contexto de pesquisa

e um caminho de visualização.

Na seção "Entrega do Formulário", na página 26, manipulamos o caminho de visualização em nosso e-mail.
Objeto Form::Notifier para incluir outro caminho na pesquisa de modelo:

módulo MailForm
classe Notificador < ActionMailer::Base
append_view_path File.expand_path("../../views", __FILE__) end

fim

O código anterior indica que, se um modelo não for encontrado em app/views, o programa de envio de e-mail deverá
procurar no diretório lib/views em seguida.

Embora geralmente definamos novos caminhos de visualização como strings, representando caminhos do sistema
de arquivos, o Rails fornece uma API bem definida para adicionar qualquer objeto como um caminho de visualização.
Isso significa que não somos obrigados a armazenar modelos de visualização no sistema de arquivos. Podemos
armazenar modelos em qualquer lugar que quisermos, desde que forneçamos um objeto que saiba como encontrá-
los. Embora externamente esses objetos sejam chamados de caminhos de visualização,

[Link]
errata do relatório • discutir
Machine Translated by Google

Configurando um SqlResolver • 41

Internamente, o Rails os chama de resolvedores de modelo, e eles devem estar em conformidade com a
API de Resolvedores.

O Rails fornece uma implementação abstrata de resolvedor chamada ActionView::Resolver.


Neste capítulo, usaremos essa funcionalidade para criar um resolver que utiliza o banco de dados como
um repositório de templates, permitindo que armazenemos nossas páginas no banco de dados e as
editemos por meio de uma interface web, utilizando nosso gerenciador de templates favorito (como Liquid,
ERB ou Haml). Podemos implementar essa funcionalidade com um único scaffold e algumas linhas de
código!

3.2 Configurando um SqlResolver


Desta vez, em vez de usar um plugin do Rails para implementar a funcionalidade desejada, desenvolveremos
o sistema de gerenciamento de templates criando uma aplicação Rails chamada templater. Vamos criá-la
usando a linha de comando:

novo modelo do Rails

Em seguida, vamos definir o modelo que armazenará os templates no banco de dados usando o
Gerador de estrutura Rails:

$ rails generate scaffold SqlTemplate body:text path:string \ format:string


locale:string handler:string partial:boolean

O atributo `body` é uma coluna de texto usada para armazenar todo o modelo; o atributo `path` deve
armazenar uma string semelhante a um caminho do sistema de arquivos (por exemplo, a ação `index()` em
`UsersController` terá `users/index` como caminho); os atributos `format` e `locale` armazenam o formato e
a localidade do modelo; o atributo `handler` armazena o manipulador do modelo (por exemplo, Liquid, ERB
ou Haml); e, finalmente, o atributo `partial` indica se o modelo armazenado é parcial.

Antes de executar a migração gerada, vamos fazer uma alteração, definindo `false` como o valor padrão
para o atributo parcial :

[Link] :partial, padrão: falso

E agora estamos prontos para executar nossas migrações:

$ bundle exec rake db:migrate

Até aqui, nenhuma surpresa. Em seguida, vamos criar um resolvedor de modelos, que usará o modelo
SqlTemplate para ler modelos do banco de dados e expô-los de acordo com a API do Resolvedor (descrita
a seguir).

A API do Resolver
A API Resolver é composta por um único método, chamado find_all(), que deve retornar uma matriz de
modelos e tem a seguinte assinatura:

[Link]
errata do relatório • discutir
Machine Translated by Google

Capítulo 3. Recuperando modelos de visualização de repositórios personalizados • 42

def find_all(name, prefix, partial, details, cache_key, locals)

Para uma requisição HTML na ação index() de um UsersController, esses argumentos são exatamente
como mostrado aqui:

encontrar_todos("índice", "usuários", falso, { formatos: [:html],


localidade: [:en, :en], manipuladores: [:erb, :builder, :rjs] }, nil, [])

Para esta solicitação simples, podemos ver que `name` corresponde ao nome da ação, enquanto `prefix` se
refere ao nome do controlador. Em seguida, `partial` é um booleano que indica se o modelo que está sendo
renderizado é parcial, e `details` é um hash com informações extras para a pesquisa, como os formatos de
solicitação, a localidade atual do framework de internacionalização (I18n) seguida pela localidade padrão e
os manipuladores de modelo disponíveis. Os dois últimos argumentos são ` cache_key` (que consideraremos
nulo por enquanto) e a variável `locals` (que é um array vazio, já que as variáveis locais são usadas apenas
ao renderizar uma parcial).

O Rails fornece uma implementação abstrata de resolver, chamada ActionView::Resolver, que usaremos
como base para o nosso resolver. Parte do seu código-fonte é mostrada a seguir.

Em seguida, vamos nos concentrar nos métodos find_all() e find_templates() :

rails/actionpack/lib/action_view/template/[Link]
módulo ActionView
classe Resolver
cattr_accessor :caching [Link]
= true

def inicializar
@cache = [Link] fim

def clear_cache
@[Link] end

def find_all(name, prefix=nil, partial=false, details={}, key=nil, locals=[]) cached(key, [name, prefix, partial], details, locals) do
find_templates(name, prefix, partial, details) end

fim

privado

def encontrar_modelos(nome, prefixo, parcial, detalhes)


raise NotImplementedError end

fim
fim

[Link] errata do relatório • discutir


Machine Translated by Google

Configurando um SqlResolver • 43

O método `find_all()` implementa um mecanismo básico de cache, onde o bloco passado para
`cached()` é retornado somente se não existir nenhuma entrada anterior no cache. Quando o bloco é
invocado, ele chama `find_templates()`, que gera um erro `NotImplementedError`, indicando que ele
deve ser implementado nas classes filhas. Observe que ` cache_key` e variáveis locais são usadas
apenas pelo mecanismo de cache; elas não são passadas para a busca de modelos.

Vamos herdar de ActionView::Resolver e implementar o método find_templates() usando o modelo


SqlTemplate para recuperar modelos do banco de dados, resultando na mesma pesquisa de modelo,
como mostra a próxima figura.

Figura 5 — Pesquisa de modelo com SqlTemplate

Escrevendo o código

Vamos chamar nossa implementação de resolvedor de SqlTemplate::Resolver e implementá-la em três etapas


principais. A primeira recebe o nome, o prefixo, a parte e os detalhes como argumentos e os normaliza. Em seguida,

criamos uma instrução SQL a partir dos argumentos normalizados e consultamos o banco de dados. A última etapa é
transformar o array de registros retornado do banco de dados em instâncias de ActionView::Template .

Vamos escrever primeiro um teste para demonstrar a funcionalidade que desejamos.

templater/1_resolver/test/models/sql_template_test.rb
requer 'test_helper'

classe SqlTemplateTest < ActiveSupport::TestCase


teste "o resolvedor retorna um modelo com o corpo salvo" faça resolver =
SqlTemplate::[Link] detalhes = { formatos:
[:html], locale: [:en], handlers: [:erb] }

# 1) Verifique se o nosso resolvedor não consegue encontrar nenhum modelo, pois o banco de dados
está vazio # find_all(name, prefix, partial, details) assert
resolver.find_all("index", "posts", false, details).empty?

# 2) Crie um modelo no banco de dados


[Link]!(
corpo: "<%= 'Olá do SqlTemplate!' %>", caminho:
"posts/index",

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 3. Recuperando modelos de visualização de repositórios personalizados • 44

formato: "html",
localidade: "en",
manipulador: "erb",
parcial: falso)

# 3) Verifique se um modelo foi encontrado template =


resolver.find_all("index", "posts", false, details).first assert_kind_of ActionView::Template, template

# 4) Afirme informações específicas sobre o modelo encontrado assert_equal "<%= 'Olá do


SqlTemplate!' %>", [Link] assert_kind_of ActionView::Template::Handlers::ERB,
[Link] assert_equal [:html], [Link] assert_equal "posts/index", template.virtual_path
assert_match %r[SqlTemplate - \d+ - "posts/index"],
[Link] end

fim

O método find_all() em nosso resolver deve retornar uma instância de ActionView::Template .


Esta instância de modelo é inicializada da seguinte forma:

ActionView::[Link](source, identifier, handler, details)

A fonte é o corpo do modelo armazenado no banco de dados. O identificador é uma string


exclusiva usada para representar o modelo. Garantiremos sua exclusividade adicionando o ID
do modelo ao banco de dados.

O manipulador é o objeto responsável por compilar o modelo. O manipulador não é uma string
— como as que armazenamos no banco de dados — mas sim um objeto recuperado usando
o método `registered_template_handler()` da classe `ActionView::Template`.

ActionView::Template.registered_template_handler("erb") # =>
#<ActionView::Template::Handlers::ERB:0x007fc722516490>

Por fim, o último parâmetro fornecido na inicialização do modelo é um hash com três chaves:
o formato do modelo encontrado ( :format ), a última vez que o modelo foi atualizado
( :updated_at) e um caminho virtual (:virtual_path) que representa o modelo.

Como os templates não precisam mais estar no sistema de arquivos, eles não necessariamente
possuem um caminho, e isso quebra algumas funcionalidades do Rails que dependem
explicitamente de templates no sistema de arquivos. Um exemplo é o atalho I18n
`t(".message")` dentro das suas views. Ele usa o caminho do template no sistema de arquivos
para recuperar a tradução, então sempre que você estiver dentro de um template em `app/
views/users/index`, o atalho tenta encontrar a tradução I18n em `[Link]`.

Para contornar essa necessidade de um caminho, o Rails exige que os templates forneçam
um `:virtual_path`. Você pode armazenar seus templates em qualquer lugar e atribuir a eles
qualquer origem ou identificador, mas precisa fornecer um `:virtual_path` que represente o quê?

[Link]
errata do relatório • discutir
Machine Translated by Google

Configurando um SqlResolver • 45

O caminho seria esse se o modelo estivesse armazenado no sistema de arquivos. Isso permite que
t(".message") funcione como esperado, definindo o caminho virtual para users/index.

Com os testes implementados e uma compreensão de como os modelos são inicializados, vamos
implementar nosso resolver herdando de ActionView::Resolver e implementando find_templates().

É importante considerar em nosso resolvedor que a ordem dos detalhes fornecidos importa. Em
outras palavras, se o array de localidades contiver [:es, :en], um modelo em espanhol (:es) terá
preferência sobre um em inglês, caso ambos existam. Uma solução é gerar uma cláusula de
ordenação para cada detalhe e obter o resultado devidamente ordenado do banco de dados. Outra
opção é ordenar os modelos retornados em Ruby. No entanto, por simplicidade, em vez de passar
todas as localidades e formatos para a consulta SQL, vamos simplesmente selecionar os primeiros
do array.

Sem mais delongas, vamos implementar nosso resolvedor:

templater/1_resolver/app/models/sql_template.rb classe
SqlTemplate < ActiveRecord::Base
valida :body, :path, presence: true valida :format, inclusion:
Mime::[Link](&:to_s) valida :locale, inclusion: I18n.available_locales.map(&:to_s)
valida :handler, inclusion: ActionView::Template::[Link](&:to_s)

classe Resolver < ActionView::Resolver protegido

def find_templates(name, prefix, partial, details) conditions = { path:


normalize_path(name,
prefix), locale: normalize_array(details[:locale]).first,
format: normalize_array(details[:formats]).first, handler:
normalize_array(details[:handlers]), partial: partial || false

}
::[Link](condições).map do |registro|
inicializar_template(registro) fim

end
# Normaliza o nome e o prefixo, de forma que a tupla ["index", "users"] se torne # "users/index" e a tupla
["template", nil] se torne "template". def normalize_path(name, prefix) [Link]? ? "#{prefix}/#{name}" :
name end

# Normaliza arrays convertendo todos os símbolos em strings. def normalize_array(array)


[Link](&:to_s) end

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 3. Recuperando modelos de visualização de repositórios personalizados • 46

# Inicializa um objeto ActionView::Template com base no registro encontrado. def initialize_template(record)


source = [Link] identifier = "SqlTemplate -
#{[Link]} -
#{[Link]}" handler = ActionView::Template.registered_template_handler([Link])

detalhes =
{ formato: Mime[[Link]],
atualizado_em: registro.atualizado_em,
caminho_virtual: caminho_virtual([Link], [Link])
}
ActionView::[Link](source, identifier, handler, details) end

# Faça com que os caminhos "users/user" se tornem "users/_user" para parciais. def virtual_path(path,
partial) return path unless partial if index =
[Link]("/")

[Link](index + 1, "_") else

"_#{caminho}"
fim
fim
fim
fim

Nossa implementação normaliza os argumentos fornecidos, consulta o banco de dados e cria


objetos de modelo a partir do conjunto de resultados. Também adicionamos diversas validações
ao nosso modelo, garantindo que os valores do corpo e do caminho não possam estar em branco
e assegurando que um formato, localidade e manipulador válidos sejam fornecidos.

Como resultado da adição de algumas validações aos nossos modelos, alguns testes funcionais
estão falhando, pois nossos fixtures agora contêm dados inválidos. Para que eles sejam aprovados,
vamos alterar o fixture em test/fixtures/sql_templates.yml para incluir um formato, localidade e
manipulador válidos:

templater/1_resolver/test/fixtures/sql_templates.yml

um:
id: 1
caminho: "algum/caminho"
formato: "html"
localidade: "en"
manipulador: "erb"
parcial: falso corpo:
"Corpo"

Agora que nosso resolvedor está implementado e o conjunto de testes está passando, podemos
criar um novo scaffold e fazê-lo usar modelos do banco de dados em vez do sistema de arquivos.
Vamos criar um scaffold de usuário executando o seguinte comando:

[Link] errata do relatório • discutir


Machine Translated by Google

Configurando um SqlResolver • 47

$ rails gerar scaffold Nome de usuário:string

Em seguida, executaremos nossas migrações:

$ bundle exec rake db:migrate

Agora podemos iniciar o servidor, acessar /users e executar todas as operações de criação, leitura, atualização e
exclusão normalmente.

Em seguida, vamos acessar o caminho /sql_templates e criar um novo modelo preenchendo o corpo do modelo com
o mesmo conteúdo do arquivo em app/views/users/[Link]; definindo o caminho com users/index; definindo o
formato, a localidade e o manipulador como html, en e erb, respectivamente; e mantendo a caixa Parcial desmarcada.

Salve este novo modelo e volte para o caminho /users . Agora, exclua o arquivo de visualização app/views/
users/[Link] e renderize a página novamente. Você deverá receber um erro "Modelo ausente", mas
não se preocupe, pois isso é esperado. O modelo está armazenado no banco de dados, mas ainda não
instruímos o UsersController a usar nosso novo resolver para recuperá-lo.

Vamos fazer isso adicionando a seguinte linha ao UsersController:

templater/1_resolver/app/controllers/users_controller.rb
class UsersController < ApplicationController append_view_path
SqlTemplate::[Link]

Ao atualizarmos a página em /users, vemos a página inicial completa recuperada do banco de dados! E embora o
modelo esteja no banco de dados, o layout ainda vem do sistema de arquivos. Em outras palavras, em uma única
requisição, podemos obter modelos de diferentes resolvers em nossos caminhos de visualização.

Sinta-se à vontade para retornar a /sql_templates, manipular o corpo do modelo armazenado e observar que a ação
index() no UsersController será alterada de acordo. O fato de podermos alcançar esse comportamento com tão
poucas linhas de código demonstra o poder da abstração ActionView::Resolver .

Antes de prosseguirmos para a próxima seção, vamos executar o conjunto de testes mais uma vez. Um teste está
falhando com a seguinte mensagem de erro:

1) Erro:
test_should_get_index(UsersControllerTest)
ActionView::MissingTemplate: Modelo ausente users/index,
application/index com
{:locale=>[:en], :formats=>[:html], :handlers=>[:erb, :builder, :raw, :ruby, :jbuilder, :coffee]}. Pesquisado em:
* "templater/app/views"
* "#<SqlTemplate::Resolver:0x007f9774fbc0d0>"

Isso acontece porque excluímos o modelo do sistema de arquivos. Embora tenhamos adicionado o mesmo modelo
ao nosso banco de dados de desenvolvimento, nosso banco de dados de teste também o excluiu.

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 3. Recuperando modelos de visualização de repositórios personalizados • 48

permanece limpo, gerando esse erro MissingTemplate no ambiente de teste. Para corrigir isso, vamos alterar
nosso fixture sql_templates .

templater/1_resolver/test/fixtures/sql_templates.yml
users_index: id: 2

caminho: "users/index"
formato: "html"
localidade: "en"
manipulador: "erb"
partial: false body:
"<h1>Listando usuários</h1> <table>

<tr>
<th>Nome</th>
<th></th>
<th></th>
<th></th> </
tr> <%
% @[Link] do |user| %>
<tr>
<td><%%= [Link] %></td>
<td><%%= link_to 'Mostrar', user %></td> <td><%%= link_to
'Editar', edit_user_path(user) %></td> <td><%%= link_to 'Excluir', user, data: { confirm:
'Tem certeza?' }, method: :delete %></td> </tr> <%%
end %>

</table>
<br /> <%
%= link_to 'Novo usuário', new_user_path %>"

Nosso fixture é apenas uma cópia do template. A única ressalva é que o Rails interpreta os fixtures com ERB,
então precisamos escapar nossas tags ERB usando <%% ... %>. E é só isso — nossos testes estão todos
passando novamente.

3.3 Configurando nosso resolvedor para produção

Para garantir que a busca de templates seja rápida em produção, o Rails oferece alguns recursos de cache.
Vamos explorar esses recursos para entender como habilitar o cache para nossos templates e as estratégias
disponíveis para expirar esse cache sempre que um template for salvo no banco de dados.

Como mencionado anteriormente, o Rails fornece ao nosso resolver uma `cache_key` através do método
`find_all()` . Nosso primeiro passo é entender por que o Rails cria essa chave de cache e como nosso resolver
a utiliza.

[Link]
errata do relatório • discutir
Machine Translated by Google

Configurando nosso resolvedor para produção • 49

O cache de resolvedores

Como vimos no código da página 42, o método `find_all()` do `ActionView::Resolver` armazena


automaticamente os templates em cache usando o método `cached()` . O cache é criado na
inicialização e referenciado pela variável de instância `@cached`. O resolvedor armazena os
templates em cache somente se `[Link].cache_classes` retornar `true`; além
disso, ele expõe um método `clear_cache()` para limpar o cache.

Cada modelo deve ser armazenado em cache em função de cinco valores: a chave do cache
(cache_key), seu prefixo, seu nome, se é parcial ou não e o conjunto de variáveis locais. Dadas
essas cinco chaves, podemos armazenar os modelos no cache de três maneiras:

# Hash aninhado
@cached[chave][prefixo][nome][parcial][locais]

# Hash simples com array como chave


@cached[[key, prefix, name, partial, locals]]

# Hash simples com hash como chave


@cached[key: key, prefix: prefix, name: name, partial: partial, locals: locals]

Todas as três implementações de cache nos proporcionam o comportamento desejado. No


entanto, existe uma diferença entre elas: o desempenho. Precisamos explorar como o Ruby
realiza buscas em hashes para entender isso.

Consulta de hash Ruby

Sempre que armazenamos um valor para uma determinada chave em um objeto Hash , o Ruby
armazena três coisas: a chave em si, o valor em si e o hash do objeto para a chave em questão.
Este hash é o resultado do método `Object#hash()` chamado no objeto fornecido como chave.
Existe uma maneira fácil de provar que o `Hash` do Ruby de fato depende de `Object#hash()`;
basta iniciar uma sessão do IRB e digitar o seguinte:

classe NoHash
método_indefinido :hash
fim

hash = [Link]
hash[[Link]] = 1 # =>
NoMethodError: método `hash' indefinido para #<NoHash:0x101643820>

Se removermos a definição do método `hash()` em nosso objeto, ele não poderá mais ser
armazenado no hash. Adicionar um elemento ao hash é semelhante a criar uma nova entrada
em uma tabela, como mostrado na Figura 6, Ilustração do que um hash armazena para cada
entrada, na página 50.

Sempre que tentamos recuperar o valor de uma determinada chave em um objeto Hash , como
hash[:b], o Ruby calcula o Object#hash() para essa chave e então realiza a busca.

[Link]
errata do relatório • discutir
Machine Translated by Google

Capítulo 3. Recuperando Modelos de Visualização de Repositórios Personalizados • 50

Figura 6 — Ilustração do que um hash armazena para cada entrada. Lembre-se de que a
implementação em Ruby usa ponteiros em vez de uma estrutura de tabela, mas a tabela é uma maneira
fácil de representar como funciona.

Se uma ou mais entradas no objeto Hash possuem o mesmo valor de hash.


Por exemplo, o valor retornado por :[Link] é 231228 na figura anterior.
Ao constatar que uma ou mais entradas possuem o valor 231228, o Ruby verifica se alguma chave dessas
entradas é igual à chave fornecida, utilizando o operador de igualdade `eql?()`. Como `:[Link]?(:b)` retorna
verdadeiro, o acesso a `hash[:b]` em nosso exemplo retorna com sucesso o valor 2 como resultado.

Para provar que o Ruby usa Object#hash() para localizar entradas, vamos iniciar outra sessão do irb e
fazer alguns experimentos.

hash = {} objeto
= [Link] hash[objeto] = 1
hash[objeto] # => 1

def [Link]; 123; fim

hash[objeto] # => hash nulo


# => {#<Objeto:0x1016e3de8>=>1}

Desta vez, usamos um objeto Ruby arbitrário como chave de hash e conseguimos definir e obter valores
com sucesso. No entanto, depois de modificarmos o objeto de valor #hash

[Link]
errata do relatório • discutir
Machine Translated by Google

Configurando nosso resolvedor para produção • 51

Ao retornar o valor, ele não pôde ser encontrado, embora o mesmo objeto ainda esteja presente no hash.

Ruby armazena um `Object#hash()` para cada chave, proporcionando buscas mais rápidas. Comparar valores
de hash (inteiros) é muito mais rápido do que comparar cada objeto armazenado no hash.

Essa implementação implica que a perda de desempenho ao encontrar um valor no hash não se limita ao
método ` eql?()` , mas também ao cálculo de `Object#hash()` para a chave fornecida. Lembre-se, poderíamos
implementar nosso cache de resolução usando um hash aninhado, um hash simples com arrays como chave
ou um hash simples com hashes como chaves. Devemos escolher a primeira opção, pois, no caso do hash
aninhado, as chaves do hash são strings ou booleanos, e o Ruby sabe como calcular o valor de `Object#hash()`
para esses valores muito rapidamente. Por outro lado, calcular `Object#hash()` e a igualdade para arrays e
hashes é mais custoso.

Podemos demonstrar isso em outra sessão do irb :

requer "benchmark" foo = "foo"

bar = "barra"
array = [foo, bar] hash = {a:
foo, b: bar}

nested_hash = [Link] { |h,k| h[k] = {} } nested_hash[foo][bar] = true

array_hash = { array => true } hash_hash = { hash


=> true }

[Link] { [Link] { nested_hash[foo][bar] } } # => 0.000342 [Link] { [Link]


{ array_hash[array] } } # => 0.000779 [Link] { [Link] { hash_hash[hash] } }
# => 0,001645

A implementação com hash aninhado produz melhores resultados. Embora a escolha por um hash aninhado
aparentemente não traga ganhos substanciais, os conceitos que abordamos sobre buscas em hash em Ruby
são fundamentais para a compreensão da próxima seção.

A chave do cache

Já sabemos que nossos resolvers vêm com um cache integrado. Também sabemos que esse cache usa um
hash aninhado para armazenar modelos e que o cache depende de cinco valores: @cached[key][prefix][name]
[partial][locals]. No entanto, a assinatura find_all() recebe seis argumentos:

def find_all(name, prefix=nil, partial=false, details={}, key=nil, locals=[])

[Link]
errata do relatório • discutir
Machine Translated by Google

Capítulo 3. Recuperando modelos de visualização de repositórios personalizados • 52

`details` é um hash que contém o formato, a localidade e outras informações úteis para a busca do modelo. O
contexto de busca armazena essas informações, que são essenciais para recuperar o modelo correto do
sistema de arquivos. Então, por que o cache não usa esses detalhes?

Lembra-se de como determinamos que calcular o `Object#hash()` para um objeto hash é custoso
em comparação com estruturas mais simples, como strings? Se usássemos `details` como chave
no hash aninhado do cache, seria lento, já que `details` é um hash de arrays:

detalhes # =>
{ formatos: [:html],
localidade: [:en, :en],
manipuladores: [:erb, :builder, :rjs]
}

# Lento porque details é um hash de arrays @cached[details][prefix]


[name][partial][locals]

Em vez disso, o contexto de pesquisa gera um objeto Ruby simples para cada hash de detalhes e
o envia como `cache_key` para os resolvers. Todo o processo é semelhante ao seguinte código:

# Gera um objeto para o hash de detalhes @details_key ||= {}


key = @details_key[details] ||
= [Link]

# E envie para cada resolvedor


resolver.find_all(name, prefix, partial, details, key)

# Dentro do resolvedor, o valor de detalhes não é usado no cache.


# Em vez disso, usamos a chave, que é um objeto simples e rápido @cached[key][prefix]
[name][partial][locals]

Em outras palavras, os detalhes não são usados diretamente no cache, mas sim por meio da
`cache_key`. Isso é importante porque, durante uma requisição, o hash dos detalhes raramente
muda, já que o formato e a localidade geralmente são definidos antes da renderização de qualquer
modelo. Portanto, independentemente de quantos modelos sejam renderizados e quantos
resolvedores estejam envolvidos em uma requisição, a `cache_key` provavelmente será calculada
apenas uma vez. Se um detalhe for alterado, como o formato da requisição, uma nova `cache_key` será gerada.

Vamos executar o irb novamente e realizar nosso último teste de desempenho deste capítulo.
Nosso teste mostrará como o acesso a um hash usando um objeto simples, como o `cache_key`,
se compara ao uso de um hash de arrays, como o hash ` details` :

requer "benchmark"
cache_key = [Link] detalhes
={

[Link]
errata do relatório • discutir
Machine Translated by Google

Configurando nosso resolvedor para produção • 53

formatos: [:html, :xml, :json], localidade:


[:en], manipuladores:
[:erb, :builder, :rjs]
}

hash_1 = { chave_cache => 10 }


hash_2 = { detalhes => 10 }

[Link] { [Link] { hash_1[cache_key] } } # => 0.000202 [Link]


{ [Link] { hash_2[details] } } # => 0.003937

Vinte vezes mais lento é uma diferença considerável! Para aplicações que exigem alto
desempenho, esses milissegundos podem se acumular facilmente em requisições que
renderizam diversas coleções e parciais, afetando drasticamente o tempo de resposta. Em
alguns benchmarks realizados com Rails, o uso do hash ` details` consumiu até dez por
cento do tempo gasto na pilha de renderização, enquanto o uso do `cache_key` reduziu
esse percentual para menos de um por cento.

Expirando o cache
Como o Rails gerencia automaticamente o cache dentro dos resolvers, precisamos nos
preocupar apenas em expirar o cache usando o método `Resolver#clear_cache()` . Esse
cache é armazenado na instância do resolver, portanto, para expirá-lo, precisaríamos
rastrear todas as instâncias de `SqlTemplate::Resolver` e chamar `clear_cache()` em cada
uma delas sempre que adicionarmos ou atualizarmos um template no banco de dados.

No entanto, faz sentido criar várias instâncias de SqlTemplate::Resolver ?


Como o cache está na instância, criar várias instâncias criaria vários caches, reduzindo sua
eficácia. Portanto, não queremos várias instâncias de resolvedor. Queremos apenas uma,
compartilhada por toda a aplicação.

Precisamos de uma classe singleton . Felizmente, o Ruby possui um módulo Singleton em


sua Biblioteca Padrão, que faz todo o trabalho pesado para nós. Incluir este módulo em
SqlTemplate::Resolver torna SqlTemplate::[Link]() um método privado e expõe
SqlTemplate::[Link]() em seu lugar, que sempre retorna o mesmo objeto.

Além disso, ter um objeto singleton facilita muito a expiração do cache. Como sempre
podemos acessar o resolvedor instanciado com SqlTemplate::[Link](), basta
chamar clear_cache() nele sempre que salvarmos uma instância de SqlTemplate .

Vamos começar com essas alterações. A primeira é exigir e incluir o Singleton dentro de
SqlTemplate::Resolver:

templater/2_improving/app/models/sql_template.rb
exigir "singleton" incluir
Singleton

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 3. Recuperando modelos de visualização de repositórios personalizados • 54

Após essa simples alteração, precisamos atualizar os arquivos app/controllers/users_controller.rb e


test/models/sql_template_test.rb para chamar SqlTemplate::[Link]() em vez de
SqlTemplate::[Link]():

templater/2_improving/app/controllers/users_controller.rb
append_view_path SqlTemplate::[Link]

templater/2_improving/test/models/sql_template_test.rb
resolver = SqlTemplate::[Link]

Com nosso resolvedor singleton implementado, vamos escrever um teste em `test/models/


sql_template_test.rb` que verifique se nosso cache expirou corretamente. Este novo teste deve
atualizar o `SqlTemplate` a partir dos fixtures e verificar se nosso resolvedor retornará o template
atualizado.

templater/2_improving/test/models/sql_template_test.rb teste
"sql_template expira o cache na atualização" faça cache_key = [Link]
resolver =
SqlTemplate::[Link] details = { formats: [:html], locale:
[:en], handlers: [:erb] }

t = resolver.find_all("index", "users", false, details, cache_key).first assert_match "Listando usuários", [Link]

sql_template = sql_templates(:users_index)
sql_template.update_attributes(body: "Novo corpo para o modelo")

t = resolver.find_all("index", "users", false, details, cache_key).first assert_equal "Novo corpo para o modelo", [Link]
end

Observe que geramos uma chave de cache falsa com [Link] para passar para find_all() porque o
cache é ativado somente se uma chave de cache for fornecida.

Finalmente, para que nosso teste seja aprovado, vamos adicionar um callback ` after_save` ao
`SqlTemplate`, logo após as validações do modelo:

templater/2_improving/app/models/sql_template.rb
after_save do
SqlTemplate::[Link].clear_cache end

Agora, sempre que um modelo é criado ou atualizado, o cache expira, permitindo que o modelo
modificado seja capturado e recompilado. Infelizmente, essa abordagem tem uma limitação grave:
funciona apenas para implantações de instância única. Por exemplo, se sua infraestrutura contém
mais de um servidor ou se você usa Passenger ou Unicorn com um pool de instâncias, uma solicitação
chegará a uma instância específica, que terá apenas seu próprio cache limpo. Em outras palavras,
esse cache não é sincronizado entre as máquinas.

[Link] errata do relatório • discutir


Machine Translated by Google

Moldes para servir com metal • 55

Felizmente, podemos resolver esse problema:

• Uma opção é reimplementar o cache usando memcached ou Redis, de forma que ele seja compartilhado
entre máquinas com um mecanismo de expiração apropriado.1,2

• Outra opção é notificar cada instância sempre que o cache expirar — por exemplo, por meio de uma fila.
Nesse esquema, after_save() simplesmente enviaria uma mensagem para a fila, que enviaria uma
notificação para todas as instâncias inscritas.

• Também podemos resolver isso definindo `config.action_view.cache_template_loading` como `false` em


produção. Anteriormente, mencionei que o cache do resolvedor é habilitado somente se
`config.cache_classes` for `true`.

No entanto, sempre que `cache_template_loading()` estiver definido, ele terá prioridade sobre a
configuração `cache_classes()` . Se o cache de templates estiver desativado, o Rails consulta o banco
de dados sempre que uma busca de template ocorre, mas o template só será recompilado se o valor
de `updated_at` definido na criação do template for mais recente do que o valor armazenado em cache
pelo resolver. Portanto, mesmo que uma nova consulta seja disparada, a compilação dispendiosa do
template só acontece se necessário. Esse é o comportamento padrão do Rails em desenvolvimento.

A melhor estratégia para você depende da sua infraestrutura, dos requisitos de desempenho e da frequência
com que os modelos são alterados.

Com isso, finalizamos nossa implementação de SqlTemplate::Resolver !

3.4 Moldes para servir com metal


Agora que podemos criar e editar modelos a partir da interface do usuário e disponibilizá-los com nosso
próprio resolvedor, estamos prontos para dar o próximo passo. Vamos usar nossas ferramentas de criação
de modelos como um sistema de gerenciamento de conteúdo (CMS) simples.

Criando o CmsController
Já podemos criar, atualizar e excluir modelos acessando /sql_templates; agora precisamos expô-los
dependendo da URL acessada.

Para isso, vamos mapear todas as requisições em /cms/* para um controlador que usará nosso resolver para
encontrar o template no banco de dados e renderizá-lo de volta para o cliente. Uma requisição em /cms/about
deve renderizar um SqlTemplate armazenado no banco de dados com o caminho igual a about.

1. [Link]
2. [Link]

[Link]
errata do relatório • discutir
Machine Translated by Google

Capítulo 3. Recuperando modelos de visualização de repositórios personalizados • 56

Podemos implementar essa funcionalidade com algumas linhas de código. Vamos começar com um teste de
integração que escreveremos usando o Capybara. O primeiro passo é configurar o Capybara como fizemos na
Seção 2.2, Testes de Integração com Capybara, na página 28:

templater/2_improving/test/test_helper.rb requer
"capivara" requer "capivara/
rails"

# Define um caso de teste básico para usar com a classe Capybara


ActiveSupport::IntegrationCase < ActiveSupport::TestCase
include Capybara::DSL include
[Link].url_helpers end

Adicione-o como uma dependência ao Gemfile:

templater/2_improving/Gemfile
grupo :test do gem
'capivara', '~> 2.0.0' end

Por fim, escreva o teste que cria um modelo e o renderiza:

templater/2_improving/test/integration/cms_test.rb requer
'test_helper'

classe CmsTest < ActiveSupport::IntegrationCase teste "pode acessar


qualquer página em SqlTemplate" faça
Acesse "/sql_templates" e clique no
link "Novo modelo SQL".

fill_in "Corpo", fill_in com: "Meu primeiro modelo de CMS" com:


"Caminho", fill_in "sobre"
"Formato", com: "html" fill_in "Localidade", com:
"en" fill_in "Manipulador", com: "erb"

click_button "Criar modelo SQL" assert_match "Modelo


SQL criado com sucesso.", [Link]

visite "/cms/about"
assert_match "Meu primeiro modelo CMS", [Link] fim

fim

Para que nosso novo teste seja aprovado, vamos escrever uma rota que mapeie para o nosso CmsController:

templater/2_improving/config/[Link] obter
"cms/*page", para: "cms#respond"

[Link] errata do relatório • discutir


Machine Translated by Google

Moldes para servir com metal • 57

Este mapeamento de rotas direciona todas as requisições em /cms/* para a ação respond() no CmsController,
que implementamos da seguinte forma:

templater/2_improving/app/controllers/cms_controller.rb classe
CmsController < ApplicationController
prepend_view_path SqlTemplate::[Link]

def respond
render template: params[:page] end

fim

Simplesmente passamos a rota fornecida como nome de modelo, que é encaminhada para o nosso
SqlTemplate::Resolver , que busca o modelo. Observe que, desta vez, adicionamos o nosso resolvedor aos
caminhos das visualizações, pois ele é a principal fonte de modelos.
Nosso conjunto de testes está verde novamente!

Se você quiser testar nosso CMS simples manualmente, inicie o servidor, acesse /sql_templates, crie um
novo modelo com o caminho igual a about e adicione algum conteúdo. Em seguida, acesse /cms/about e
veja sua nova página!

Brincando com Metal


Nosso CmsController herda de ApplicationController, que herda de ActionController::Base, e,
consequentemente, possui todas as funcionalidades disponíveis nos controladores Rails comuns. Ele inclui
todos os helpers, adiciona proteção contra falsificação de requisição entre sites (CSRF), permite ocultar
ações com hide_action(), suporta mensagens flash e adiciona o método respond_to() — faz muito mais do
que o necessário, já que nosso controlador lida apenas com requisições GET. Não seria ótimo se pudéssemos
ter um controlador mais simples, com apenas o comportamento necessário?

Já discutimos o Abstract Controller e como ele fornece uma estrutura básica compartilhada entre o Action
Mailer e o Action Controller. No entanto, o AbstractController::Base não sabe nada sobre HTTP. Por outro
lado, o ActionController::Base vem com todo o pacote. Não haveria um meio-termo?

Sim, existe! Chama-se ActionController::Metal. ActionController::Metal herda de

AbstractController::Base implementa a funcionalidade mínima necessária para que nossos controladores


sejam uma aplicação Rack válida e funcionem com HTTP. A cadeia de herança é mostrada na Figura 7,
superclasses de CmsController, na página 58.

Ao darmos uma olhada rápida em ActionController::Base no código-fonte do Rails, notamos que ele herda
de Metal e adiciona uma série de comportamentos:

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 3. Recuperando modelos de visualização de repositórios personalizados • 58

Figura 7 — Superclasses de CmsController

rails/actionpack/lib/action_controller/[Link]
módulo ActionController
classe Base < Metal
resumo!

incluir AbstractController::Layouts incluir


AbstractController::Translation
incluir AbstractController::AssetPaths incluir Helpers incluir
HideActions

incluir UrlFor
incluir Redirecionamento
incluir Renderização incluir
Renderizadores::Todos
incluir ConditionalGet incluir
RackDelegation incluir Caching
incluir MimeResponds
incluir ImplicitRender incluir
StrongParameters incluir Cookies

inclui Flash
incluir RequestForgeryProtection incluir ForceSSL
incluir Streaming incluir
DataStreaming incluir
RecordIdentifier

include HttpAuthentication::Basic::ControllerMethods include


HttpAuthentication::Digest::ControllerMethods

[Link] errata do relatório • discutir


Machine Translated by Google

Concluindo • 59

include HttpAuthentication::Token::ControllerMethods include


AbstractController::Callbacks
inclui Resgate
Incluir instrumentação, incluir
ParamsWrapper

ActiveSupport.run_load_hooks(:action_controller, self) end

fim

Vamos reimplementar o CmsController, mas desta vez herdaremos de ActionController::Metal


e incluiremos apenas os módulos necessários, o que reduz a sobrecarga em uma requisição:

templater/3_final/app/controllers/cms_controller.rb
classe CmsController < ActionController::Metal
incluir ActionController::Rendering incluir
AbstractController::Helpers
prepend_view_path ::SqlTemplate::[Link] helper CmsHelper

def respond
render template: params[:page] end

fim

módulo CmsHelper fim

Após essas alterações, nossos testes ainda devem passar, mostrando que nossa nova
implementação de controlador usando ActionController::Metal funciona conforme o esperado.

Se precisarmos de mais funcionalidades, adicionamos os módulos necessários. Por exemplo,


se quisermos adicionar layouts, incluímos o módulo AbstractController::Layouts , criamos um
layout no banco de dados com o caminho layouts/cms e especificamos o layout "cms" no
controlador. Experimente!

3.5 Concluindo
Neste capítulo, abordamos muitos tópicos. Analisamos a pilha de renderização do ActionView
e desenvolvemos um resolver que lê templates de um banco de dados com mecanismos de
expiração de cache. Em seguida, criamos um controlador para acessar dinamicamente as
páginas no resolver e o otimizamos transformando-o em um objeto ActionController::Metal .
Se você quiser ver mais exemplos de uso de resolvers, confira o código-fonte do Rails e
descubra como ele implementa o resolver de sistema de arquivos, que recupera templates do
sistema de arquivos.

3. [Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 3. Recuperando modelos de visualização de repositórios personalizados • 60

Por outro lado, se você já está familiarizado com os detalhes internos do Rails (como
resolvers e Metal) e ainda busca um desafio, pode aprender mais sobre hashes em Ruby
consultando o código-fonte do Rubinius. O Rubinius implementa a maior parte da linguagem
Ruby em Ruby, incluindo a classe Hash , então você pode aprender muito analisando seu
código-fonte.ÿ

No próximo capítulo, discutiremos manipuladores de templates como ERB, Builder e Haml.


Criaremos nosso próprio manipulador usando Markdown e ERB e o integraremos aos
geradores do Rails.

4. [Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Neste capítulo, veremos:


• A API template-handler do Rails
• Templates multipart com Action Mailer •
Geradores e railties do Rails

CAPÍTULO 4

Envio de e-mails em várias partes

Utilizando manipuladores de modelos

Para finalizar nossa análise da pilha de renderização do Rails, vamos examinar como o Rails
compila e renderiza templates. Até agora, vimos que a responsabilidade de um controller é
normalizar as opções de renderização e enviá-las para o renderizador de visualização. Com base
nessas opções, o renderizador de visualização solicita ao contexto de busca (lookup context) que
procure um template específico nos resolvers disponíveis, levando em consideração também os
valores de localidade e formato que o contexto de busca contém.

Como vimos em "Escrevendo o Código", na página 43, o resolver retorna instâncias de


ActionView::Template e, no momento em que essas instâncias são inicializadas, precisamos
passar um objeto chamado handler como argumento. Cada extensão, como .erb ou .haml, tem
seu próprio manipulador de modelo:

ActionView::Template.registered_template_handler("erb")
#=> #<ActionView::Template::Handlers::ERB:0x007fc722516490>

A responsabilidade do manipulador de modelos na pilha de renderização é compilar um modelo


em código-fonte Ruby. Esse código-fonte é executado dentro do contexto da visualização e deve
retornar o modelo renderizado como uma string. A Figura 8, Objetos envolvidos na pilha de
renderização, na página 62, resume esse processo.

Para entender como um manipulador de modelos funciona, vamos criar um manipulador de


modelos para resolver um problema específico. Embora a base dos e-mails atuais tenha sido
criada em 1970 e a versão 4 da especificação HTML date de 1997, ainda não podemos confiar
no envio de e-mails HTML para todos, já que muitos clientes de e-mail não conseguem renderizá-
los corretamente.

Isso implica que, sempre que configurarmos um aplicativo para enviar um e-mail HTML, também
devemos enviar uma versão em texto simples do mesmo, criando um "e-mail multicanal".

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 4. Envio de e-mails multipartes usando manipuladores de modelos • 62

Figura 8 — Objetos envolvidos na pilha de renderização

e-mail "tipart". Se o destinatário do e-mail usar um cliente que não consegue ler HTML, ele exibirá a parte em
texto simples.

O Action Mailer facilita muito a criação de e-mails multipartes, mas com essa abordagem precisamos manter duas
versões da mesma mensagem. Não seria ótimo se tivéssemos um modelo que pudesse ser renderizado tanto em
texto simples quanto em HTML?

É aqui que entra o Markdown. O Markdown é uma linguagem de marcação leve, criada por John Gruber e Aaron
Swartz, que visa ser o mais fácil possível de ler e escrever.<sup>1</sup> A sintaxe do Markdown consiste
inteiramente em caracteres de pontuação e permite incorporar HTML personalizado sempre que necessário. Aqui
está um exemplo de texto em Markdown:

Bem-vindo
=======

Olá, José Valim!

Obrigado por escolher nosso produto. Antes de usá-lo, você precisa confirmar sua
conta acessando o seguinte link:

[Link]

1. [Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Explorando a API Template-Handler • 63

Lembre-se, você tem *7 dias* para confirmar. Para mais informações, você pode visitar nossa
[Pergunta Frequente][1] ou nossa [página de Suporte ao Cliente][2].

Cumprimentos,

A Equipe.

[1]: [Link] [2]: http://


[Link]/customer

De fato, é bastante legível! A melhor parte é que pode ser transformado em HTML, que é
renderizado como mostrado na figura a seguir.

Figura 9 — HTML gerado a partir de um modelo Markdown

Nosso manipulador de modelos usará os recursos do Markdown para gerar visualizações


em texto simples e HTML usando apenas um modelo. O único problema com o Markdown
é que ele não interpreta código Ruby. Para contornar isso, precisamos compilar nossos
modelos com ERB e, em seguida, convertê-los usando o compilador Markdown.

Ao final deste capítulo, vamos integrar os geradores do Rails e configurar o gerador de e-


mail para usar nosso novo manipulador de modelos por padrão.

4.1 Explorando a API Template-Handler


Para que um objeto esteja em conformidade com a API do manipulador, ele precisa
responder ao método `call()` . Este método recebe como argumento uma instância de
`ActionView::Template`, que apresentamos em "Escrevendo o Código", na página 43, e
deve retornar uma string contendo código Ruby válido. O código Ruby retornado pelo manipulador é:

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 4. Envio de e-mails multipartes usando manipuladores de modelos • 64

Em seguida, é compilado em um método, de modo que renderizar um modelo é tão simples quanto
invocar esse método compilado.

Antes de mergulharmos no nosso manipulador Markdown + ERB, vamos criar alguns manipuladores de
modelos para nos familiarizarmos com a API.

Manipulador de modelo Ruby

Nosso primeiro manipulador de modelos simplesmente permite código Ruby arbitrário como um modelo.
Isso significa que o seguinte modelo é válido:
""
corpo =
corpo << "Este é meu primeiro " corpo <<
content_tag(:b, "manipulador de modelo") corpo << "!" corpo

Para implementar isso, vamos criar um novo plug-in chamado handlers usando o plugin rails:

Novos manipuladores de plug-in do Rails

Em seguida, vamos escrever um teste de integração simples para o nosso manipulador de templates.
Nosso objetivo é renderizar um template fictício em test/dummy/app/views/handlers/rb_handler.[Link]:

handlers/1_first_handlers/test/dummy/app/views/handlers/rb_handler.[Link] corpo =
""
corpo <<
"Este é meu primeiro " corpo <<
content_tag(:b, "manipulador de modelo") corpo << "!" corpo

Nosso teste de integração precisará de rotas e um controlador para servir esse modelo, então vamos adicioná-
los:

handlers/1_first_handlers/test/dummy/config/[Link]
Dummy::[Link] do
obter "/handlers/:action", para: "handlers" fim

handlers/1_first_handlers/test/dummy/app/controllers/handlers_controller.rb classe
HandlersController < ApplicationController fim

Nosso teste de integração deve fazer uma requisição para a rota definida em /handlers/rb_handler e
verificar se nosso modelo foi renderizado corretamente:

handlers/1_first_handlers/test/integration/rendering_test.rb requer
"test_helper"

classe RenderingTest < ActionDispatch::IntegrationTest


teste ".rb template handler" faça obter "/handlers/
rb_handler"

[Link]
errata do relatório • discutir
Machine Translated by Google

Explorando a API Template-Handler • 65

esperado = "Este é meu primeiro <b>manipulador de modelo</b>!" assert_match


esperado, [Link] fim

fim

Ao executarmos o conjunto de testes, ele falha porque o Rails ainda não reconhece a extensão .rb nos
templates. Para registrar um novo manipulador de template, invocamos
ActionView::Template.register_template_handler() com dois argumentos: a extensão do template e o objeto
manipulador. Como o objeto manipulador é qualquer coisa que responda a call() e retorne uma String,
podemos implementar nosso manipulador simplesmente usando lambdas do Ruby:

handlers/1_first_handlers/lib/[Link]
requer "action_view/template"
ActionView::Template.register_template_handler :rb, lambda { |template|
[Link] } módulo Handlers

fim

Ao executarmos o conjunto de testes, o teste que acabamos de escrever passa. Nossa função lambda recebe
uma instância de `ActionView::Template` como argumento. Como nosso manipulador de modelo precisa
retornar uma string com código Ruby e nosso modelo no sistema de arquivos está escrito em Ruby, basta
retornar ` [Link]()`.

Como os símbolos Ruby implementam um método `to_proc()` e `:source.to_proc` é exatamente o mesmo que
`lambda { |arg| [Link] }`, podemos tornar nosso manipulador de modelo ainda mais curto:

ActionView::Template.register_template_handler :rb, :source.to_proc

Manipulador de modelo de string

Nosso manipulador de templates .rb é bastante simples, mas tem uso limitado. As views do Rails geralmente
contêm grandes blocos de conteúdo estático, e lidar com isso no código Ruby rapidamente se tornaria
complexo. Dito isso, vamos implementar outro manipulador de templates mais adequado para lidar com
conteúdo estático, mas que ainda nos permita incorporar código Ruby. Como as strings em Ruby suportam
interpolação, nosso próximo manipulador de templates será baseado em strings do Ruby. Vamos adicionar
um template de exemplo ao aplicativo de teste:

handlers/1_first_handlers/test/dummy/app/views/handlers/string_handler.[Link]
Parabéns! Você acabou de criar mais um #{@what}!

Nosso novo modelo usa interpolação de strings, e o código Ruby interpolado faz referência a uma variável de
instância chamada `@what`. Vamos definir uma nova ação com essa variável de instância em nosso
`HandlersController` para que nossos testes a utilizem como um fixture:

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 4. Envio de e-mails multipartes usando manipuladores de modelos • 66

handlers/1_first_handlers/test/dummy/app/controllers/handlers_controller.rb
classe HandlersController < ApplicationController
def string_handler @what =
"manipulador de modelo" end end

Agora vamos escrever um pequeno teste para isso em nosso conjunto de integrações:

handlers/1_first_handlers/test/integration/rendering_test.rb teste
".string template handler" faça obter "/handlers/
string_handler" esperado = "Parabéns! Você
acabou de criar outro manipulador de modelo!" assert_match esperado, [Link] fim

Para que nosso novo teste seja aprovado, vamos implementar este novo manipulador de modelo, mais uma vez em
lib/[Link]:

handlers/1_first_handlers/lib/[Link]
ActionView::Template.register_template_handler :string, lambda { |template|
"%Q{#{[Link]}}" }

Execute o conjunto de testes e nosso novo teste será aprovado. Nosso manipulador de modelo retorna uma string
criada com o atalho Ruby %Q{}, que o Rails então compila para um método. Quando esse método é invocado, o
interpretador Ruby avalia a string e retorna o resultado interpolado.

Este manipulador de modelos funciona bem para casos simples, mas tem duas falhas principais: adicionar o
caractere } ao modelo causa erros de sintaxe, a menos que o caractere seja escapado, e o suporte a blocos é
limitado porque precisa ser envolvido em toda a sintaxe de interpolação. Isso significa que ambos os exemplos a
seguir são inválidos:

Isso causa um erro de sintaxe.

#{[Link] faça}
Isso não funciona como em ERB e é inválido.
#{fim}

Portanto, é hora de analisarmos manipuladores de modelos mais robustos.

4.2 Criando um manipulador de modelos com Markdown + ERB


Diversas gems conseguem compilar a sintaxe Markdown para HTML. Para o nosso manipulador de templates,
vamos usar o RDiscount,2 , que é um wrapper em Ruby para a biblioteca de compilação Markdown rápida chamada
Discount, escrita em C.

2. [Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Criando um manipulador de modelos com Markdown + ERB • 67

Manipulador de modelos Markdown

Podemos criar um manipulador de modelos que compila para Markdown com apenas algumas linhas de
código. Vamos primeiro adicionar mais um teste ao nosso conjunto:

handlers/1_first_handlers/test/integration/rendering_test.rb teste
".md template handler" faça obter "/handlers/
rdiscount" esperado = "<p>RDiscount é
<em>legal</em> e <strong>rápido</strong>!</p>" assert_match esperado, [Link] fim

E então vamos escrever nosso modelo no sistema de arquivos:

handlers/1_first_handlers/test/dummy/app/views/handlers/[Link] RDiscount
é *legal* e **rápido**!

Observe que nosso modelo usa .md como extensão para Markdown. Vamos registrá-lo no Rails:

handlers/1_first_handlers/lib/[Link]
requer "rdiscount"
ActionView::Template.register_template_handler :md, lambda { |template|
"[Link](#{[Link]}).to_html" }

Como nosso manipulador de modelos depende do RDiscount, vamos adicioná-lo como uma dependência ao
nosso plug-in e executar o comando `bundle install` logo em seguida:

handlers/1_first_handlers/[Link]
s.add_dependency "rdiscount", "~> 2.0.0"

Ao executarmos o conjunto de testes, nosso novo teste é aprovado. Nosso manipulador de modelos Markdown
funciona perfeitamente, mas não permite incorporar código Ruby, o que limita bastante seu uso.

Para contornar essa limitação, poderíamos usar a mesma técnica que usamos em nosso manipulador de
modelo .string , mas ela também tem limitações. Portanto, usaremos ERB para incorporar código Ruby em
nosso modelo Markdown e criaremos um novo manipulador de modelo chamado .merb.

Manipulador de modelo MERB

Primeiro, vamos adicionar um exemplo do nosso novo manipulador de modelos ao sistema de arquivos. Este
exemplo deve estar dentro do nosso aplicativo de teste e o utilizaremos em nossos testes:

handlers/1_first_handlers/test/dummy/app/views/handlers/[Link] O
manipulador de modelos MERB é **<%= %w(cool fast).to_sentence %>**!

Em seguida, vamos escrever um teste que renderize esse modelo e verifique a saída desejada:

[Link]
errata do relatório • discutir
Machine Translated by Google

Capítulo 4. Envio de e-mails multipartes usando manipuladores de modelos • 68

handlers/1_first_handlers/test/integration/rendering_test.rb teste
".merb template handler" faça
obter "/handlers/merb" esperado
= "<p>O manipulador de modelos MERB é <strong>legal e rápido</strong>!</p>" assert_match esperado, [Link]
fim

Desta vez, para implementar nosso manipulador de templates, não usaremos uma função
lambda. Em vez disso, vamos criar um módulo que responde a `call()`, permitindo-nos dividir
nossa implementação em métodos menores. Para compilar para ERB, usaremos o manipulador
ERB que acompanha o Rails, o qual podemos obter usando o método
`ActionView::Template.registered_template_handler()` , como fizemos em " Escrevendo o
Código", na página 43. Aqui está nosso manipulador de templates .merb :

handlers/1_first_handlers/lib/[Link]
Módulo de manipuladores
MERB

def self.erb_handler
@@erb_handler ||= ActionView::Template.registered_template_handler(:erb) end

def [Link](template)
compiled_source = erb_handler.call(template)
"[Link](begin;#{compiled_source};end).to_html" fim

fim
fim

ActionView::Template.register_template_handler :merb, Handlers::MERB

O manipulador ERB compila o modelo e, como qualquer outro manipulador de modelos, retorna uma string com
código Ruby válido. O resultado desse código Ruby é uma string contendo sintaxe Markdown, que é então convertida
em HTML usando o RDiscount.

Por fim, observe como envolvemos o código retornado pelo ERB em uma cláusula begin/end
embutida . Precisamos fazer isso em linha, ou as linhas de rastreamento de pilha ficarão
incorretas. Por exemplo, imagine o seguinte modelo:

<% nil.this_method_does_not_exist! %>

Este modelo gerará um erro ao ser renderizado. No entanto, considere estas duas maneiras
de compilar o modelo:

[Link](begin
nil.this_method_does_not_exist! end).to_html

[Link](begin;nil.this_method_does_not_exist!;end).to_html

[Link]
errata do relatório • discutir
Machine Translated by Google

Criando um manipulador de modelos com Markdown + ERB • 69

No primeiro exemplo, como introduzimos novas linhas no modelo compilado, o rastreamento da exceção
indicaria que o erro ocorreu na segunda linha do modelo, o que seria enganoso. Observe que também
precisamos usar ` begin` e `end` para envolver o código; caso contrário, nosso manipulador geraria código
Ruby inválido quando o modelo contivesse mais de uma expressão Ruby. Vamos verificar isso testando o
seguinte código de exemplo no irb:

puts(a=1;b=a+1) # => gera erro de sintaxe


puts(begin;a=1;b=a+1;end) # => imprime 2 corretamente

A última linha da nossa implementação registra o novo manipulador, fazendo com que todos os testes sejam
aprovados. Nosso manipulador de modelos .merb já está implementado, mas ainda não renderiza modelos
de texto simples e HTML conforme descrito no início deste capítulo — ele renderiza apenas os últimos.
Precisamos fazer algumas alterações no nosso manipulador de modelos para gerar resultados diferentes
dependendo do formato do modelo.

E-mails em várias partes

Usaremos e-mails multipart no Action Mailer para demonstrar o comportamento que queremos adicionar ao
nosso manipulador de modelos. Vamos criar um serviço de envio de e-mail dentro do nosso aplicativo de teste
para ser usado em nossos testes:

handlers/2_final/test/dummy/app/mailers/[Link] classe
Notificador < ActionMailer::Base
def contato(destinatário)
@recipient = destinatário

mail(to: @recipient, from: "[Link]@[Link]") do |format|


[Link]
[Link]
fim
fim
fim

Este código deve parecer familiar; assim como o `respond_to()` em seus controladores, você pode passar um
bloco para `mail()` para especificar quais modelos renderizar. No entanto, em controladores, o Rails escolhe
apenas um modelo para renderizar, enquanto em e-mails o bloco especifica vários modelos que são usados
para criar um único e-mail multipart.

Nosso e-mail tem duas partes, uma em texto simples e outra em HTML. Como ambas as partes usarão o
mesmo modelo, vamos criar um modelo dentro do nosso aplicativo de teste, mas sem adicionar um formato
ao nome do arquivo:

handlers/2_final/test/dummy/app/views/notifier/
[Link] Modelos duplos **são incríveis**!

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 4. Envio de e-mails multipartes usando manipuladores de modelos • 70

E vamos escrever um teste para isso usando este programa de envio de e-mail e esta visualização:

handlers/2_final/test/integration/rendering_test.rb teste
"modelo duplo com .merb" faça email =
[Link]("you@[Link]") assert_equal 2, [Link]
assert_equal "multipart/alternative", email.mime_type

assert_equal "text/plain", [Link][0].mime_type assert_equal "Dual templates


**rock**!", [Link][0].[Link]

assert_equal "text/html", [Link][1].mime_type assert_equal "<p>Dual


templates <strong>rock</strong>!</p>",
[Link][1].[Link]
fim

O teste verifica se nosso e-mail possui duas partes. Como a parte em texto simples é uma
representação alternativa da parte em HTML, o e-mail deve ter um tipo MIME igual a multipart/
alternative, que é definido automaticamente pelo Action Mailer.
Em seguida, o teste verifica o tipo MIME e o corpo de cada parte.
A ordem das partes também é importante; se as partes fossem invertidas, a maioria dos clientes
simplesmente ignoraria a parte HTML, exibindo apenas o texto simples.

Ao executarmos este teste, ele falha porque nossa parte `text/plain` contém código HTML, e não
apenas texto simples. Isso é esperado, já que nosso manipulador de modelo sempre retorna
código HTML. Para que o teste seja aprovado, precisaremos alterar ligeiramente a implementação
de ` Handlers::[Link]()` para considerar ` [Link]`.

handlers/2_final/lib/[Link] def
[Link](template)
compiled_source = erb_handler.call(template) if [Link]?
(:html)
"[Link](begin;#{compiled_source};end).to_html" else

fonte_compilada fim fim

Analisamos o arquivo `[Link]` e verificamos se ele inclui o formato `:html` . Se sim,


renderizamos o modelo como HTML; caso contrário, retornamos o código compilado pelo ERB,
resultando em um modelo de texto simples escrito em sintaxe Markdown. Isso nos permite enviar
um e-mail com partes em texto simples e em HTML usando apenas um modelo!

Com essa última alteração, nosso manipulador de modelos faz exatamente o que planejamos no
início deste capítulo. Antes de criarmos geradores para nosso novo manipulador de modelos,
vamos discutir como `[Link]` é configurado.

[Link]
errata do relatório • discutir
Machine Translated by Google

Personalizando geradores de trilhos • 71

Pesquisa de formatos

Em "Escrevendo o Código", na página 43, discutimos que o resolvedor é responsável por fornecer a opção
`:format` aos modelos. O resolvedor realiza a seguinte busca para decidir qual formato usar:

1. Se o modelo encontrado tiver um formato válido, ele será usado. Em modelos colocados no sistema de
arquivos, o formato é especificado no nome do arquivo do modelo, como em [Link].

2. Se o modelo encontrado não especificar um formato, o resolvedor perguntará ao


O manipulador de modelos verifica se ele possui um formato padrão.

3. Se o manipulador de modelos não tiver um formato preferencial, o resolvedor deverá retornar


Os mesmos formatos usados na pesquisa.

Como nosso modelo [Link] não especifica um formato, o resolvedor tenta recuperar o formato padrão
do nosso manipulador de modelos Handlers::MERB . Esse formato padrão é recuperado por meio de
Handlers::MERB.default_format(), mas como nosso manipulador de modelos não responde a default_format(),
a segunda etapa também é ignorada. A última opção do resolvedor é retornar os formatos usados na
pesquisa.
Como estamos usando os métodos [Link] e [Link] , eles definem automaticamente os formatos na
pesquisa para texto simples e HTML, respectivamente.

Por exemplo, se definíssemos Handlers::MERB.default_format() em nossa implementação para retornar :text


ou :html, nosso último teste falharia. Nosso resolvedor nunca chegaria à terceira etapa e sempre retornaria
um formato específico na segunda etapa.

4.3 Personalizando os Geradores de Trilhos


Com nosso manipulador de templates em mãos e renderizando e-mails multipart, o passo final é criar um
gerador para nosso plug-in. Nosso gerador se integrará ao gerador de e-mails do Rails e o configurará para
criar templates .merb em vez de .erb .

Os geradores do Rails fornecem pontos de extensão que permitem que outros geradores estendam e
personalizem o código gerado. Uma rápida olhada no gerador de e-mail no código-fonte do Rails revela os
pontos de extensão que ele oferece:

rails/railties/lib/rails/generators/rails/mailer/mailer_generator.rb módulo
Rails
geradores de módulo
classe MailerGenerator < NamedBase
source_root File.expand_path("../templates", __FILE__) argumento :ações, tipo: :array,

padrão: [], banner: "método método"


verificar_colisão_de_classe def
criar_arquivo_de_e-mail modelo
"[Link]",

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 4. Envio de e-mails multipartes usando manipuladores de modelos • 72

[Link]("app/mailers", class_path, "#{file_name}.rb")


fim
hook_for :template_engine, :test_framework end end end

Embora ainda não tenhamos abordado toda a API de Geradores, podemos ver que seu
comportamento principal é copiar um modelo de e-mail para app/mailers, o que é
implementado no método create_mailer_file() . Observe que o gerador de e-mails não
menciona nada sobre o mecanismo de modelos ou a estrutura de testes; ele fornece apenas
pontos de integração (hooks). Isso permite que outras bibliotecas, como Haml e RSpec, se
integrem ao gerador de e-mails, personalizando o processo de geração.

A API Active Model e o desacoplamento nos geradores do Rails fornecem abstrações


importantes que nos permitem substituir os padrões do Rails por nossas próprias convenções.
Discutimos o primeiro no Capítulo 2, " Construindo Modelos com o Active Model", na página
17, e agora vamos explorar o segundo.

A estrutura de um gerador

Para entender como um gerador funciona, vamos analisar mais detalhadamente o


Rails::Generators::MailerGenerator mostrado no código da página 71. O gerador de e-mail
herda de Rails::Generators::NamedBase. Todos os geradores que herdam dele esperam que
um argumento chamado NAME seja fornecido quando o gerador é invocado a partir da linha de comando.
Podemos verificar os argumentos e opções que o gerador de e-mail espera executando o
seguinte comando dentro de uma aplicação Rails:

$ rails g mailer --ajuda


Uso: rails
generate mailer NOME [método método] [opções]

Opções: -e,
[--template-engine=NOME] # Mecanismo de modelo a ser invocado
# Padrão: erb
-t, [--test-framework=NOME] # Framework de teste a ser invocado
# Padrão: unidade_de_teste

Voltando ao nosso código gerador — a classe Rails::Generators::MailerGenerator também


define :actions como um argumento, na linha 6. Como um valor padrão foi fornecido (um
array vazio), essas ações são opcionais e aparecem entre colchetes na mensagem de ajuda
anterior.

Em seguida, invocamos o método `class_collisions_check()` , que verifica se o NOME


fornecido ao gerador já não está definido em nossa aplicação. Isso é útil, pois gera um erro
se tentarmos definir um serviço de envio de e-mail chamado, por exemplo, `Object`.

[Link]
errata do relatório • discutir
Machine Translated by Google

Personalizando geradores de trilhos • 73

Nas linhas seguintes, definimos o método create_mailer_file() , reproduzido aqui para sua conveniência:

def create_mailer_file template


"[Link]", [Link]("app/
mailers", class_path, "#{file_name}.rb")
fim

Os geradores do Rails funcionam invocando todos os métodos públicos na sequência em que são definidos.
Essa construção é interessante porque se integra bem com herança: se você precisar estender o gerador de
e-mail para realizar tarefas extras, basta herdar dele e definir mais métodos públicos. Para pular uma tarefa,
basta remover a definição de um método. Sempre que o novo gerador for invocado, ele executará os métodos
herdados e, em seguida, os novos métodos públicos definidos. Assim como nos controladores do Rails, você
pode expor ou executar ações acidentalmente ao deixar um método declarado como público.

O método `create_mailer_file()` invoca três métodos: `template()`, `class_path()` e `file_name()`. O primeiro é


um auxiliar definido em Thor,3 que é a base para os geradores do Rails, e os outros são definidos por
`Rails::Generators::NamedBase`.

Thor possui um módulo chamado Thor::Actions, que contém vários métodos para auxiliar na geração de

tarefas. Um deles é o método template() , já discutido , que aceita dois argumentos: um arquivo de origem e
um arquivo de destino.

O método `template() ` lê o arquivo de origem no sistema de arquivos, executa o código Ruby incorporado
usando ERB e, em seguida, copia o resultado para o destino especificado. Todos os templates ERB no Thor
são avaliados no contexto do gerador, o que significa que as variáveis de instância definidas no seu gerador
também estão disponíveis nos seus templates, bem como em métodos protegidos/privados.

Os valores retornados pelos outros dois métodos, `class_path()` e `file_name()`, são inferidos do `NAME`
fornecido como argumento. Para ver todos os métodos definidos e o que eles retornam, vamos dar uma
olhada no arquivo `named_base_test.rb` no código-fonte do Rails:

rails/railties/test/generators/named_base_test.rb
def test_named_generator_attributes g =
generator ['admin/foo'] assert_name
g, 'admin/foo', :name assert_name g,
%w(admin), :class_path assert_name g,
'Admin::Foo', :class_name assert_name g, 'admin/
foo', :file_path assert_name g, 'foo', :file_name
assert_name g, 'Foo', :human_name

3. [Link]

[Link]
errata do relatório • discutir
Machine Translated by Google

Capítulo 4. Envio de e-mails multipartes usando manipuladores de modelos • 74

assert_name g, 'foo', :singular_name assert_name g, 'foos', :plural_name


assert_name g, '[Link]', :i18n_scope assert_name g,
'admin_foos', :table_name end

Este teste afirma que, quando admin/foo é fornecido como NAME, como em rails g mailer admin/foo,
podemos acessar todos esses métodos e cada um deles retornará o respectivo valor fornecido na asserção.

Por fim, o gerador de e-mails fornece dois ganchos: um para o mecanismo de modelos e outro para a
estrutura de testes. Esses ganchos se tornam opções que também podem ser fornecidas pela linha de
comando. Resumindo, o gerador anterior aceita uma variedade de argumentos e opções e pode ser
invocado da seguinte forma:

$ rails g mailer Notificador de boas-vindas contato --test-framework=rspec

Ganchos de geradores

Já sabemos que os geradores do Rails fornecem hooks. No entanto, quando solicitamos o uso do ERB
como mecanismo de templates, como o gerador de mailers sabe como encontrá-lo e utilizá-lo? Os hooks
dos geradores funcionam graças a um conjunto de convenções. Quando você escolhe um mecanismo de

templates chamado `:erb`, o `Rails::Generators::MailerGenerator` tentará carregar um dos três geradores a


seguir:

• Rails::Generators::ErbGenerator
• Erb::Generators::MailerGenerator
• Gerador de Herança

E como todos os geradores devem estar no $LOAD_PATH, dentro da pasta rails/generators ou da pasta
generators , encontrar esses geradores é tão simples quanto tentar importar os seguintes arquivos:

• (rails/)generators/rails/erb/erb_generator •
(rails/)generators/rails/erb_generator •
(rails/)generators/erb/mailer/mailer_generator •
(rails/)generators/erb/mailer_generator •
(rails/)generators/erb/erb_generator •
(rails/)generators/erb_generator

Se um desses geradores for encontrado, ele será invocado com os mesmos argumentos de linha de
comando fornecidos ao gerador de e-mail. Nesse caso, o gerador encontrado é Erb::Generators::MailerGenerator,
que discutiremos a seguir.

[Link]
errata do relatório • discutir
Machine Translated by Google

Personalizando geradores de trilhos • 75

O Rails expõe três hooks para

mecanismos de templates : um para o controlador, um para o servidor de e-mail e um para os geradores de


scaffolds. Os dois primeiros geram arquivos somente se determinadas ações forem fornecidas na linha de
comando, como em `rails g mailer Notifier welcome contact` ou `rails g controller Info about contact`. Para cada
ação fornecida, o mecanismo de templates deve criar um template correspondente.

Por outro lado, o gancho de estrutura cria todas as visualizações usadas na estrutura: index,
edit, show, new e a parcial _form .

A implementação de Erb::Generators::ControllerGenerator no Rails é a seguinte:

rails/railties/lib/rails/generators/erb/controller/controller_generator.rb Requer o
módulo "rails/generators/erb" Erb

geradores de módulo
classe ControllerGenerator < Base
argumento: ações, tipo: : array,
padrão: [], banner: "ação ação"

def copiar_arquivos_de_visualização

base_path = [Link]("app/views", class_path, file_name) empty_directory base_path


[Link] do |action| @action = action
@path = [Link](base_path,

filename_with_extensions(action)) template filename_with_extensions(:view), @path end

fim
fim
fim
fim

O único método que ainda não discutimos é filename_with_extensions(), definido em Erb::Generators::Base:

rails/railties/lib/rails/generators/[Link]
require "rails/generators/named_base"

módulo Erb
geradores de módulo
classe Base < Rails::Generators::NamedBase
formato de
definição
protegido : :html
fim
manipulador de definição

:erb
fim

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 4. Envio de e-mails multipartes usando manipuladores de modelos • 76

def nome_do_arquivo_com_extensões(nome)
[nome, formato, manipulador].[Link](".") fim

fim
fim
fim

O Erb::Generators::ControllerGenerator cria um arquivo de visualização em app/views usando o formato e o


manipulador configurados para cada ação fornecida. O modelo usado para criar essas visualizações no código-
fonte do Rails é semelhante a este:

rails/railties/lib/rails/generators/erb/controller/templates/[Link]
<h1><%= class_name %>#<%= @action %></h1> <p>Encontre-
me em <%= @path %></p>

Isso, para o controlador `admin/foo bar` do Rails, gera a seguinte saída no arquivo `app/views/admin/foo/
[Link]`:

<h1>Admin::Foo#bar</h1>
<p>Encontre-me em app/views/admin/foo/bar</p>

A classe Erb::Generators::MailerGenerator simplesmente herda do gerador de controlador anterior e altera o


formato padrão para :text, reutilizando a mesma lógica:

rails/railties/lib/rails/generators/erb/mailer/mailer_generator.rb require
"rails/generators/erb/controller/controller_generator"

módulo Erb
módulo Generators classe
MailerGenerator < ControllerGenerator protegido

formato def
:texto
fim
fim
fim
fim

E o modelo criado para os e-mails enviados é assim:

rails/railties/lib/rails/generators/erb/mailer/templates/[Link] <%=
class_name %>#<%= @action %>

<%%= @greeting %>, encontre-me em app/views/<%= @path %>

Se observarmos a estrutura de diretórios do gerador ERB no código-fonte do Rails, no diretório railties/lib ,


podemos ver facilmente quais modelos estão disponíveis, como na Figura 10, Estrutura dos geradores ERB,
na página 77.

[Link]
errata do relatório • discutir
Machine Translated by Google

Personalizando geradores de trilhos • 77

Figura 10 — Estrutura para geradores ERB

Portanto, se quisermos substituir completamente os geradores ERB, precisamos criar esses


geradores e templates. E como os geradores do Rails funcionam bem com herança, podemos
fazer isso herdando do respectivo gerador ERB e sobrescrevendo alguns métodos de
configuração.

Criando nosso primeiro gerador


Para implementar nosso gerador de mailers .merb , basta herdar de Erb::Generators::MailerGenerator
e sobrescrever os métodos format() e handler() definidos em Erb::Generators::Base. Nossa
implementação do gerador se parece com isto:

handlers/2_final/lib/generators/merb/mailer/mailer_generator.rb require
"rails/generators/erb/mailer/mailer_generator"

módulo Merb
módulo Geradores
classe MailerGenerator < Erb::Generators::MailerGenerator
source_root File.expand_path("../templates", __FILE__)

formato def
protegido
nulo # Nossos modelos não têm formato fim

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 4. Envio de e-mails multipartes usando manipuladores de modelos • 78

manipulador de definição

:merb
fim
fim
fim
fim

Precisamos invocar um método chamado source_root() no nível da classe para informar ao Rails
que os modelos para o nosso gerador podem ser encontrados em lib/generators/merb/mailer/templates.

Como escolhemos `nil` como formato e `:merb` como manipulador, vamos criar nosso modelo `
[Link]` com o seguinte conteúdo:

handlers/2_final/lib/generators/merb/mailer/templates/[Link] <%=
class_name %>#<%= @action %>

<%%= @greeting %>, encontre-me em app/views/<%= @path %>

E é isso. Nosso modelo tem o mesmo conteúdo do gerador ERB, mas podemos modificá-lo para
incluir Markdown por padrão. Para testar o gerador, vamos acessar o aplicativo de teste dentro do
nosso plug-in em test/dummy e executar o seguinte comando:

$ rails g mailer Mailer contato bem-vindo --template-engine=merb

Esse comando cria um programa de e-mail chamado Mailer com dois modelos, [Link] e
[Link]. O gerador é executado, mostrando a seguinte saída:

crie app/mailers/[Link] invoque merb

criar app/views/mailer app/


criar views/mailer/[Link] app/views/mailer/
criar [Link]

Você também pode configurar seu aplicativo em test/dummy/config/[Link] para usar o gerador
merb por padrão, adicionando a seguinte linha:

[Link] template_engine: :merb

No entanto, talvez você não queira adicionar essa linha a cada nova aplicação que iniciar.
Seria ótimo se pudéssemos definir esse valor como padrão dentro do nosso plug-in e não sempre
no aplicativo. O Rails nos permite fazer isso com um Rails::Railtie.

4.4 Extensão dos trilhos com abraçadeiras


Um Rails::Railtie (pronuncia-se "Rails Rail-tie") permite que você se conecte à inicialização do Rails
e configure alguns valores padrão. Essas ferramentas permitem que frameworks como o Active
Record informem ao Rails como ele deve ser inicializado e configurado, fornecendo um railtie.

[Link] errata do relatório • discutir


Machine Translated by Google

Extensão de trilhos com abraçadeiras • 79

Você deve incluir um dormente ferroviário em seu plug-in somente se pelo menos uma das seguintes condições for
verdadeira:

• Seu plug-in precisa executar uma determinada tarefa durante ou após a aplicação Rails.
O cátion é inicializado.

• Seu plug-in precisa alterar um valor de configuração — por exemplo, definir


um gerador.
• Seu plugin deve fornecer tarefas e geradores Rake em locais não padrão (o local padrão para as tarefas
é lib/tasks e para os geradores é lib/generators ou lib/rails/generators ). • Seu plugin deseja executar
código personalizado sempre que o console
do Rails ou o executor do Rails for iniciado.

• Você deseja que seu plug-in forneça opções de configuração para o aplicativo, como config.my_plugin.key
= :value.

Vejamos um trecho do ActiveRecord::Railtie no código-fonte do Rails que contém alguns exemplos desses
cenários:

rails/activerecord/lib/active_record/[Link]
módulo ActiveRecord classe
Railtie < Rails::Railtie
config.active_record = ActiveSupport::[Link]
config.app_generators.orm :active_record, migration: true, timestamps: true

config.app_middleware.insert_after "::ActionDispatch::Callbacks",
"ActiveRecord::QueryCache"
config.eager_load_namespaces << ActiveRecord rake_tasks do require
"active_record/base"
load "active_record/railties/[Link]"
end

corredor faz
require "active_record/base" end initializer

"active_record.initialize_timezone" do ActiveSupport.on_load(:active_record) do
self.time_zone_aware_attributes = true self.default_timezone
= :utc end

fim
inicializador "active_record.migration_error" faça
se config.active_record.delete(:migration_error) == :page_load
config.app_middleware.insert_after "::ActionDispatch::Callbacks",
"ActiveRecord::Migration::CheckPending"
fim
fim
fim
fim

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 4. Envio de e-mails multipartes usando manipuladores de modelos • 80

Após esses exemplos, estamos prontos para criar nosso primeiro railtie e configurar o gerador de e-mail
para usar nosso novo manipulador de modelos por padrão:

handlers/2_final/lib/handlers/[Link]
manipuladores de módulos
classe Railtie < Rails::Railtie
config.app_generators.mailer template_engine: :merb end end

Como nosso railtie precisa ser carregado quando nosso plug-in é carregado, precisamos adicionar um
`require` em `lib/[Link]`:

handlers/2_final/lib/[Link]
exigem "manipuladores/dormentes"

E é só isso! Vamos para o aplicativo de teste em test/dummy e invocar o auxiliar do gerador novamente
com rails g mailer --help. Observe que o mecanismo de templates padrão mudou para merb. Portanto,
não precisamos passá-lo como opção ao invocá-lo!

Todos os principais geradores do Rails, como model, controller e scaffold, dependem de hooks. Como
acabamos de ver, isso nos permite adaptá-los ao nosso fluxo de trabalho e às nossas ferramentas
preferidas.

É importante ressaltar que esse mecanismo só funciona se o nosso `Railtie` for carregado antes da
inicialização da nossa aplicação. Por isso, em uma aplicação Rails recém-criada, importamos as
dependências antes de definirmos a aplicação Rails. Você pode verificar se as dependências são
importadas logo no início abrindo o arquivo `config/[Link]` em qualquer aplicação Rails e
observando que a linha `[Link]` vem antes da herança de `Rails::Application`.

Além disso, embora os plugins possam alterar as configurações padrão do Rails, a aplicação ainda tem
a palavra final sobre isso. Por exemplo, alteramos o Rails para usar nosso mecanismo de templates
`:merb` no gerador de e-mails por padrão. No entanto, se os desenvolvedores quiserem redefinir esse
valor para `:erb`, basta fazê-lo na definição da aplicação em `config/[Link]`.

4.5 Concluindo
Neste capítulo, concluímos nossa discussão sobre a pilha de renderização do Rails criando alguns
manipuladores de templates. Nosso manipulador de templates principal, com a extensão .merb, combina
Markdown com ERB, permitindo renderizar tanto o HTML quanto o texto simples de um e-mail usando
apenas um template.

Ao final do capítulo, criamos um gerador e personalizamos o Rails para usar nosso novo manipulador de
templates por padrão. Há muito mais para descobrir no...

[Link] errata do relatório • discutir


Machine Translated by Google

Concluindo • 81

API de Geradores. Além dos métodos vistos neste capítulo, o Thor::Actions define
copy_file(), inject_into_file(), create_file(), run() e alguns outros. Adicionalmente, o Rails
possui um módulo chamado Rails::Generators::Actions que fornece métodos específicos
do Rails, como gem(), environment(), route() e muitos outros. O Rails também fornece
um recurso de teste para geradores chamado Rails::Generators::TestCase, que é útil
ao testar nossos geradores. Aqui está um exemplo de como o Rails usa
Rails::Generators::TestCase para testar seu próprio gerador de e-mail:
rails/railties/test/generators/mailer_generator_test.rb require
"generators/generators_test_helper" require "rails/generators/mailer/
mailer_generator"

classe MailerGeneratorTest < Rails::Generators::TestCase


argumentos %w(notificar foo bar)

def test_mailer_skeleton_is_created run_generator


assert_file "app/
mailers/[Link]" do |mailer|
assert_match(/class Notifier < ActionMailer::Base/, mailer) assert_match(/default from:
"from@[Link]"/, mailer) end

fim

def test_mailer_with_i18n_helper run_generator


assert_file "app/
mailers/[Link]" do |mailer|
assert_match(/en\.notifier\.foo\.subject/, mailer) assert_match(/
en\.notifier\.bar\.subject/, mailer) fim

fim

def test_invokes_default_test_framework run_generator


assert_file "test/
mailers/notifier_test.rb" do |test|
assert_match(/class NotifierTest < ActionMailer::TestCase/, test) assert_match(/test "foo"/, test)
assert_match(/test "bar"/, test) end

fim
fim

Não deixe de explorar todas as ferramentas disponíveis! Em seguida, construiremos um mecanismo Rails que utiliza as funcionalidades

de streaming do Rails para enviar atualizações ao navegador conforme as alterações ocorrem em nosso sistema.

[Link] errata do relatório • discutir


Machine Translated by Google

Neste capítulo, veremos: •


Engines do Rails •
Funcionalidade de transmissão ao vivo do
Rails • Threads e filas em Ruby •
Carregamento antecipado de código no Rails

CAPÍTULO 5

Transmissão assíncrona de

eventos do servidor para clientes

Nos capítulos anteriores, analisamos a fundo a pilha de renderização do Rails. Você aprendeu
que, quando uma requisição chega a um controlador, este reúne as informações necessárias
para renderizar um template. O template é obtido de um dos resolvers, compilado, renderizado
completamente e incorporado a um layout.
Ao final desse processo, você terá uma string Ruby representando esse modelo.
A string é definida como a resposta HTTP e enviada de volta ao cliente.

Essa abordagem funciona bem para a grande maioria das aplicações. No entanto, em alguns casos, precisamos enviar nossa

resposta em partes menores. Às vezes, essas partes menores podem ser infinitas; continuamos enviando partes para o cliente até

que a conexão entre o servidor e o cliente seja fechada.

Sempre que enviamos uma resposta em partes, dizemos que o servidor está transmitindo dados
para o cliente em fluxo contínuo. Como o Rails foi construído com o cenário mais tradicional de
requisição-resposta em mente, o suporte a streaming foi adicionado e aprimorado ao longo do
tempo, e vamos explorá-lo neste capítulo.

Para explorar como funciona o streaming, vamos escrever um plugin para Rails que envia dados
para o navegador sempre que uma de nossas folhas de estilo for alterada. O navegador usará
essas informações para recarregar as folhas de estilo da página atual, permitindo que os
desenvolvedores vejam as alterações na página HTML à medida que modificam seus arquivos
de recursos, sem a necessidade de atualizar manualmente a página no navegador.

Como este plug-in terá seu próprio controlador, recursos, rotas e muito mais, vamos aproveitar o
poder dos engines do Rails para adicionar essas funcionalidades como se fizessem parte de
uma aplicação Rails, mas agrupá-las em uma gem para compartilhar entre diferentes projetos.

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 5. Transmissão assíncrona de eventos do servidor para clientes • 84

5.1 Extensão dos trilhos com motores


Os engines do Rails permitem que nosso plug-in tenha seus próprios controllers, models, helpers, views,
assets e rotas, assim como em uma aplicação Rails normal. Vamos gerar um plug-in chamado ` live_assets`
usando o gerador de plug-ins do Rails. Mas desta vez, vamos passar a flag `--full` , que irá gerar diretórios
para models, controllers, rotas e muito mais:

$ rails plug-in new live_assets --full

Além dos arquivos que o gerador normalmente cria para nós, a opção --full também gera estes arquivos:

• Um diretório de aplicativos com controladores, modelos e outros elementos, como visto em um aplicativo padrão.
aplicativo

• Um arquivo config/[Link] para rotas

• Um arquivo lib/live_assets/[Link] declarando nosso mecanismo.

• Um arquivo test/integration/navigation_test.rb vazio ao qual podemos adicionar nossa integração-


testes de ção

O arquivo mais importante aqui é o lib/live_assets/[Link], então vamos analisá-lo mais de perto:

live_assets/1_live/lib/live_assets/[Link]
módulo LiveAssets
classe Engine < ::Rails::Engine fim

fim

Para criar um mecanismo, precisamos herdar de Rails::Engine e garantir que nosso novo mecanismo seja
carregado o mais rápido possível. O gerador que executamos já fez isso para nós, adicionando esta linha em
lib/live_assets.rb:

live_assets/1_live/lib/live_assets.rb
requer "live_assets/engine"

módulo LiveAssets
fim

Criar um Rails::Engine é bastante semelhante a criar um Rails::Railtie. Isso porque um Rails::Engine nada
mais é do que um Rails::Railtie com alguns inicializadores padrão e a API Paths, que veremos a seguir.

Caminhos

Um Rails::Engine não possui caminhos fixos. Isso significa que não precisamos colocar nossos modelos ou
controladores em `app/`; podemos colocá-los em qualquer lugar.

[Link] errata do relatório • discutir


Machine Translated by Google

Extensão de trilhos com motores • 85

escolha. Por exemplo, podemos configurar nosso mecanismo para carregar nossos controladores de
lib/controllers em vez de app/controllers, da seguinte forma:

módulo LiveAssets
classe Engine < Rails::Engine
caminhos["app/controllers"] = ["lib/controllers"]
fim
fim

Também podemos fazer com que o Rails carregue nossos controladores tanto de app/controllers quanto de
lib/controladores:

módulo LiveAssets
classe Engine < Rails::Engine
caminhos["app/controllers"] << "lib/controllers"
fim
fim

Esses caminhos têm a mesma semântica que em uma aplicação Rails: se você tiver
um controlador chamado LiveAssetsController dentro de app/controllers/live_assets_controller.rb ou
lib/controllers/live_assets_controller.rb, o controlador será carregado automaticamente
Quando você precisar. Não precisa ser explicitamente exigido.

Por enquanto, vamos seguir o caminho convencional e conectar nossos controles.


app/controllers, portanto, não aplique as alterações anteriores. Podemos verificar todos os caminhos
personalizáveis de um mecanismo inspecionando o código-fonte do Rails:

rails/railties/lib/rails/engine/[Link]
caminhos de definição

@caminhos ||= início


caminhos = Rails::Paths::[Link](@root)

[Link] "app", eager_load: true, glob: "*"


[Link] "app/assets", glob: "*"
[Link] "app/controllers", eager_load: true
[Link] "app/helpers", eager_load: true
[Link] "app/models", eager_load: true
[Link] "app/mailers", eager_load: true
[Link] "app/views"

[Link] "app/controllers/concerns", eager_load: true


[Link] "app/models/concerns", eager_load: true

[Link] "lib", carregar_caminho: verdadeiro

[Link] "lib/assets", glob: "*"


[Link] "lib/tasks", glob: "**/*.rake"

[Link] "config"
[Link] "config/environments", glob: "#{[Link]}.rb"
[Link] "config/initializers", glob: "**/*.rb"

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 5. Transmissão assíncrona de eventos do servidor para clientes • 86

[Link] "config/locales", [Link] glob: "*.{rb,yml}"


"config/[Link]"

[Link] "db"
[Link] "db/migrate" [Link]
"db/[Link]"

[Link] "vendedor", load_path: true glob: "*"


[Link] "vendedor/ativos",

caminhos fim fim

O trecho anterior mostra que o mecanismo também especifica quais caminhos devem ser carregados
antecipadamente e quais não devem, além de listar caminhos para localidades, migrações e muito
mais. No entanto, declarar um caminho não é suficiente; algo precisa ser feito com ele. É aí que
entram os inicializadores.

Inicializadores

Um motor possui diversos inicializadores responsáveis por inicializar o motor.


Esses inicializadores são de nível relativamente baixo e não devem ser confundidos com os disponíveis
no arquivo `config/initializers` da sua aplicação. Vejamos um exemplo:

rails/railties/lib/rails/[Link]
inicializador :adicionar_caminhos_de_visualização faça
views = paths["app/views"].existent unless [Link]?

ActiveSupport.on_load(:action_controller){ prepend_view_path(views) }
ActiveSupport.on_load(:action_mailer){ prepend_view_path(views) } end

fim

Este inicializador é responsável por adicionar as views do nosso engine, geralmente definidas em
`app/views`, a `ActionController::Base` e `ActionMailer::Base` assim que forem carregadas, permitindo
que uma aplicação Rails utilize os templates definidos em um engine. Para ver todos os inicializadores
definidos em um `Rails::Engine`, podemos iniciar um novo console Rails em `test/dummy` com `rails
console` e digitar o seguinte:

Rails::[Link](&:name) # =>

[:set_load_path, :set_autoload_paths, :add_routing_paths, :add_locales, :add_view_paths, :load_environment_config, :append_assets_path, :prepend_he

Trabalhar com um motor de jogo é muito semelhante a trabalhar com uma aplicação Rails. Como já
sabemos como construir aplicações, implementar nosso plug-in de streaming deve parecer familiar.

[Link] errata do relatório • discutir


Machine Translated by Google

Transmissão ao vivo • 87

5.2 Transmissão ao vivo


Para ver como funciona o streaming, vamos criar um controlador chamado LiveAssetsController em app/
controllers/live_assets_controller.rb que inclui a funcionalidade ActionController::Live e transmite “hello
world” continuamente:

live_assets/1_live/app/controllers/live_assets_controller.rb classe
LiveAssetsController < ActionController::Base
incluir ActionController::Live

def hello while


true
[Link] "Olá Mundo\n" sleep 1 end rescue
IOError

[Link] fim

fim

Nosso controlador fornece uma ação chamada hello() que transmite "Olá Mundo" a cada segundo. Se, por
algum motivo, a conexão entre o servidor e o cliente for interrompida, [Link] falhará com
um erro IOError, que precisamos tratar antes de fechar corretamente nosso fluxo.

Também precisamos de uma rota para a ação hello():

live_assets/1_live/config/[Link]
[Link] do get "/
live_assets/:action", to: "live_assets" end

Estamos quase prontos para testar nosso endpoint de streaming. No entanto, como um engine do Rails não
pode ser executado sozinho, precisamos iniciá-lo através da aplicação em `test/dummy`. Além disso, a
funcionalidade de streaming não funciona no Webrick, o servidor que acompanha o Ruby e que o Rails usa
por padrão; o Webrick armazenaria nossa resposta em buffer antes de enviá-la ao cliente e, como nossa
resposta é infinita, nunca veríamos nada. Por esse motivo, vamos adicionar o Puma ao nosso gemspec
como uma dependência de desenvolvimento:1

ativos_vivos/1_vivo/ativos_vivos.gemspec
s.add_development_dependency "puma"

Finalmente, vamos entrar no diretório test/dummy e executar o comando rails s. Agora, o Rails inicia o
Puma em vez do WEBrick:

1. [Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 5. Transmissão assíncrona de eventos do servidor para clientes • 88

=> Inicializando o Puma


=> Aplicativo Rails 4.0.0 iniciando em modo de desenvolvimento em [Link] => Use -d para desanexar

=> Ctrl-C para desligar o servidor

A maioria dos navegadores também tentará armazenar em buffer a resposta do streaming, e pode levar algum
tempo até que eles decidam nos mostrar algo. Portanto, para testar se nosso endpoint de streaming realmente
funciona, usaremos o cURL, que funciona pela linha de comando.
Vamos experimentar o curl :

$ curl -v localhost:3000/live_assets/hello > GET /live_assets/hello


HTTP/1.1 > User-Agent: curl/7.24.0 (x86_64-apple-
darwin12.0)
> Host: localhost:3000
> Aceitar: */*
>
< HTTP/1.1 200 OK < X-
Frame-Options: SAMEORIGIN < X-XSS-
Protection: 1; mode=block < X-Content-Type-Options:
nosniff < X-UA-Compatible: chrome=1 < Cache-
Control: no-cache

< Content-Type: text/html; charset=utf-8 < X-Request-Id: f21f8c0d-


d496-4bfa-944c-cd01b44b87ee < X-Runtime: 0.003120

< Transfer-Encoding: chunked


<
Olá, mundo!
Olá, mundo!

A cada segundo, você verá uma nova linha "Olá Mundo" aparecer na tela. Isso significa que nosso endpoint de
streaming está funcionando. Pressione CTRL+C no teclado para interrompê-lo, pois estamos prontos para prosseguir
para exemplos mais complexos!

Eventos enviados pelo servidor

Os desenvolvedores sempre precisaram receber atualizações do servidor no navegador. Por muito tempo, o polling
foi a técnica mais comum para resolver esse problema. No polling, o navegador faz requisições frequentes ao
servidor, solicitando novos dados. Caso não haja novas informações disponíveis, o servidor retorna uma resposta
vazia e o navegador inicia uma nova requisição. Dependendo da frequência, o navegador acaba enviando muitas
requisições ao servidor, gerando uma sobrecarga considerável.

2. [Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Transmissão ao vivo • 89

Com o tempo, surgiram técnicas como o long polling . Nelas, o navegador envia requisições ao servidor
periodicamente e, se não houver atualizações disponíveis, o servidor aguarda um certo período antes de
enviar uma resposta vazia. Embora apresente um desempenho melhor do que o polling tradicional, sofre
com problemas de compatibilidade entre navegadores. Além disso, muitos proxies e servidores encerram
a conexão se não houver comunicação por um tempo, tornando essa abordagem ineficaz.

Para atender às necessidades de atualização dos desenvolvedores, a especificação HTML5 inclui duas
novas APIs: Server Sent Events (SSE) e WebSockets. O WebSockets permite que o cliente e o servidor
troquem informações pela mesma conexão, mas, por ser um protocolo novo, pode exigir alterações na sua
infraestrutura de implantação para suportá-lo. Por outro lado, o Server Sent Events é um canal de
comunicação unidirecional do servidor para o cliente e pode ser usado com qualquer servidor web capaz
de transmitir respostas. Por esses motivos, o SSE é a ferramenta que escolheremos para este capítulo.

O princípio fundamental do SSE é o formato de fluxo de eventos; aqui está um exemplo de resposta de fluxo
de eventos para uma solicitação HTTP:

< HTTP/1.1 200 OK <


Content-Type: text/event-stream
<

< evento: algum_canal < dados:


{"olá":"mundo"}
<

< evento: outro_canal < dados:


{"outro":"mensagem"}

As mensagens são delimitadas por duas novas linhas. Cada mensagem pode ter um evento e dados
associados. Neste caso, os dados são um payload JSON (JavaScript Object Notation) , mas poderiam ser
qualquer texto. Este é o formato que precisamos receber do servidor ao transmitir dados. Vamos criar uma
nova ação chamada `sse` em nosso `LiveAssetsController` que transmite um evento `reloadCSS` a cada
segundo:

live_assets/1_live/app/controllers/live_assets_controller.rb def sse

[Link]["Cache-Control"] = "no-cache" [Link]["Content-


Type"] = "text/event-stream"

embora seja verdade

[Link] "evento: recarregarCSS\ndata: {}\n\n" sleep 1 end

resgatar IOError
[Link] fim

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 5. Transmissão assíncrona de eventos do servidor para clientes • 90

É semelhante à nossa primeira ação, exceto que agora precisamos definir o tipo de conteúdo de resposta correto e

desativar qualquer cache. Com o servidor pronto, vamos implementar o lado do cliente com JavaScript:

live_assets/1_live/app/assets/javascripts/live_assets/[Link]
[Link] = função() {
// 1. Conecte-se ao nosso fluxo de eventos var source
= new EventSource('/live_assets/sse');

// 2. Este retorno de chamada será acionado em cada evento reloadCSS.


[Link]('reloadCSS', function(e) {

// 3. Carregar todas as entradas CSS var


sheets = [Link]("[rel=stylesheet]"); var forEach = [Link];

// 4. Para cada entrada, clone-a, adicione-a ao // documento e remova a


original após [Link](sheets, function(sheet){ var clone =
[Link](); [Link]('load', function()
{ [Link](sheet); });
[Link](clone); });

}); };

Nosso arquivo JavaScript se conecta ao nosso novo endpoint e, a cada evento `reloadCSS` , recarrega todas as
folhas de estilo da página. Nosso arquivo de recursos foi definido em `app/assets/live_assets/[Link]`; essa
estrutura é necessária porque, por padrão, o Rails pré-compila apenas arquivos de recursos que correspondem a
`application.*`. Como são os únicos arquivos pré-compilados, esses arquivos geralmente incluem todos os outros
arquivos existentes no projeto. É por isso que são frequentemente chamados de manifestos.

Por fim, vamos criar uma função auxiliar que facilitará o carregamento de nossos recursos pelos aplicativos:

live_assets/1_live/app/helpers/live_assets_helper.rb
módulo LiveAssetsHelper def
live_assets
javascript_include_tag "live_assets/application" end end

Com nosso mecanismo de Eventos Enviados pelo Servidor pronto, vamos testá-lo. Acesse nosso aplicativo de teste/
simulação e crie um controlador e uma rota:

[Link]
errata do relatório • discutir
Machine Translated by Google

Transmissão ao vivo • 91

live_assets/1_live/test/dummy/app/controllers/home_controller.rb class
HomeController < ApplicationController def index

render text: "Olá", layout: true end end

live_assets/1_live/test/dummy/config/[Link]
Dummy::[Link] do
raiz para: "home#index"
fim

Alteraremos nosso layout para incluir os recursos do motor gráfico, mas apenas em desenvolvimento:

live_assets/1_live/test/dummy/app/views/layouts/[Link] <!DOCTYPE
html>
<html>

<head>
<title>Dummy</title> <%=
stylesheet_link_tag "application", media: "all" %> <%= javascript_include_tag "application"
%> <%= live_assets if [Link]? %> <%= csrf_meta_tags
%> </head>

<body>
<%= yield %> </
body>

</html>

Reinicie o aplicativo de teste e acesse localhost:3000 pelo navegador. Se o seu navegador tiver um painel de
rede que mostre todas as solicitações HTTP enviadas, você pode esperar que cada folha de estilo seja
recarregada a cada segundo, mas não é isso que acontece, como mostrado na Figura 11, Solicitação pendente
de [Link] no painel de rede do Google Chrome, na página 92.

Embora o Puma seja um servidor web com suporte a threads, o Rails permite que apenas uma thread seja
executada por vez. Vamos contornar esse problema alterando o aplicativo de exemplo para permitir concorrência:

live_assets/1_live/test/dummy/config/[Link]
config.allow_concurrency = true

Como o navegador está conectado ao servidor web, aguardando que o servidor aceite as solicitações,
precisamos fechar o navegador antes de reiniciar o servidor web.
Feche o navegador, reinicie o servidor e abra novamente localhost:3000; finalmente poderemos ver os arquivos
de folha de estilo recarregando a cada segundo. Para verificar se nosso estilo está correto, siga estes passos:

[Link]
errata do relatório • discutir
Machine Translated by Google

Capítulo 5. Transmissão assíncrona de eventos do servidor para clientes • 92

Figura 11 — Solicitação pendente de [Link] no painel de rede do Google Chrome

Após a atualização das folhas de estilo, podemos editar o arquivo test/dummy/app/assets/stylesheets/


[Link] e observar que as alterações são aplicadas em tempo real, sem precisar atualizar a página. Por
exemplo, tente definir a cor do texto para vermelho:

corpo { cor: vermelho; }

Como você pode ver, nosso streaming de eventos enviados pelo servidor funciona! No entanto, podemos fazer
algumas melhorias. Primeiro, queremos recarregar as folhas de estilo somente quando houver alterações no
sistema de arquivos — e não a cada segundo. Monitorar essas alterações deve ser eficiente. Se tivermos cinco
páginas abertas, não queremos consultar nosso sistema de arquivos para cada página aberta; idealmente,
teríamos uma entidade principal de escuta do sistema de arquivos à qual cada solicitação pudesse se inscrever.

O segundo problema em nosso código até agora é que não escrevemos nenhum teste.
Essa funcionalidade é particularmente difícil de testar porque estamos transmitindo uma quantidade infinita de
dados, então, em vez de testá-la diretamente do controlador, precisamos dividir todos os componentes existentes
em partes menores e testáveis.

Finalmente, agora que habilitamos o config.allow_concurrency, precisamos entender como essa configuração
afetará a implantação de aplicativos que dependem de streaming. Portanto, não saia ainda: temos muito o que
fazer!

5.3 Notificações do sistema de arquivos com threads

Por padrão, uma aplicação Rails é gerada com três diretórios de recursos: `app/assets`, `lib/assets` e `vendor/assets`.

Nossos recursos devem ser distribuídos entre esses diretórios da mesma forma que distribuímos nosso código: o diretório

`app` deve conter os recursos diretamente relacionados à nossa aplicação, o diretório `lib` deve conter os recursos

necessários para a execução da aplicação, e o diretório `vendor/assets` deve conter os recursos necessários para a execução da aplicação.

[Link]
errata do relatório • discutir
Machine Translated by Google

Notificações do sistema de arquivos com threads • 93

Componentes JavaScript ou folhas de estilo isolados que seriam úteis além da nossa aplicação, e
o diretório vendor deve conter arquivos de terceiros.

Gostaríamos de monitorar as alterações no sistema de arquivos em cada um desses diretórios. Uma opção é verificar
manualmente a data e hora de modificação de cada arquivo nesses diretórios a cada segundo ou menos. Isso é

conhecido como sondagem do sistema de arquivos. A sondagem pode ser um bom ponto de partida, mas, à medida
que o número de ativos aumenta, pode se tornar muito intensiva em termos de CPU.

Felizmente, a maioria dos sistemas operacionais oferece um mecanismo de notificação para


alterações no sistema de arquivos. Basta passar para o sistema operacional todos os diretórios que
desejamos monitorar e, se um arquivo for adicionado, removido ou alterado, nosso código será
notificado. A gem listen expõe todos os principais mecanismos de notificação do sistema operacional
em uma única API fácil de usar.

Além disso, considerando nossa necessidade de ter uma entidade principal monitorando o sistema
de arquivos à qual nossas requisições possam se inscrever, vamos encapsular toda a funcionalidade
de escuta em uma thread, que será executada simultaneamente com nossas requisições. Vamos
abrir o arquivo lib/live_assets.rb e implementá-la:

live_assets/2_listener/lib/live_assets.rb
require "live_assets/engine" require "thread"
require "listen" module
LiveAssets

mattr_reader :subscribers @@subscribers


= []
# Inscrever-se em todos os eventos publicados. def
[Link](subscriber) subscribers <<
subscriber
fim
# Cancelar a inscrição de um assinante existente. def
[Link](subscriber)
[Link](subscriber) end

# Inicie um ouvinte para os seguintes diretórios.


# Sempre que ocorrer uma alteração, publique o evento correspondente para
todos os assinantes disponíveis.
def self.start_listener(event, directories)
[Link] faça
[Link](*directories, latency: 0.5) do |_modified, _added, _removed|
[Link] { |s| s << event } end

fim
fim
fim

3. [Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 5. Transmissão assíncrona de eventos do servidor para clientes • 94

Nosso código fornece um mecanismo para iniciar ouvintes dentro de uma thread. Esses
ouvintes monitoram um conjunto específico de diretórios e, sempre que ocorre uma alteração,
enviam o evento registrado para cada um dos assinantes. Como estamos usando a gem
listen , vamos adicioná-la também ao gemspec:

live_assets/2_listener/live_assets.gemspec
s.add_dependency "listen"

Embora não possamos escrever testes de integração para uma ação que transmite
atualizações do sistema de arquivos infinitamente, nossa funcionalidade de ouvinte está
desacoplada do sistema de streaming, permitindo-nos testá-la isoladamente. Vamos escrever
um teste que inicia um ouvinte e verifica se um evento será enviado ao nosso assinante
sempre que ocorrer uma alteração no diretório test/tmp :

live_assets/2_listener/test/live_assets_test.rb
require "test_helper" require
"fileutils"

classe LiveAssetsTest < ActiveSupport::TestCase


setup do
FileUtils.mkdir_p "test/tmp" end

desmontar fazer
FileUtils.rm_rf "test/tmp" fim

teste "pode se inscrever em eventos de ouvinte" faça


# Criar um ouvinte
l = LiveAssets.start_listener(:reload, ["test/tmp"])
# Nosso assinante é um simples array subscriber = []

[Link](assinante)

iniciar
enquanto [Link]?
# Acionar alterações em um arquivo até recebermos um evento [Link]("test/
tmp/sample", [Link](20)) end

# Verifique se recebemos o evento


assert_includes subscriber, :reload
garantir

# Limpar
[Link](assinante) [Link]

fim
fim
fim

[Link]
errata do relatório • discutir
Machine Translated by Google

Notificações do sistema de arquivos com threads • 95

Excelente! Parece que nosso listener está funcionando como esperado. Ao executar o conjunto
de testes, você pode receber alguns avisos da gem listen . Isso ocorre porque ela usa polling
do sistema de arquivos, a menos que você instale uma gem específica para o seu sistema
operacional que utilize notificações do sistema de arquivos. Sinta-se à vontade para adicionar
essa gem ao seu Gemfile (e não ao gemspec, já que não é uma dependência estrita do nosso
plugin).

Por fim, precisamos garantir que um ouvinte que monitore os diretórios de recursos seja iniciado
sempre que nossa aplicação for inicializada e envie um ` :reloadCSS` sempre que houver uma
alteração. Vamos escrever um teste:

live_assets/2_listener/test/live_assets_test.rb
teste "pode se inscrever em eventos reloadCSS existentes" faça assinante = []

[Link](assinante)

iniciar
enquanto [Link]?
[Link]("test/dummy/app/assets/stylesheets/[Link]") end

assert_includes subscriber, :reloadCSS


garantir

[Link](assinante) fim fim

Nosso teste pressupõe que o ouvinte já esteja disponível no momento da execução do teste.
Para que o teste seja aprovado, vamos definir um inicializador dentro do nosso mecanismo,
semelhante aos que vimos anteriormente neste capítulo, que inicia o ouvinte, passando todos
os diretórios de recursos como argumentos:

live_assets/2_listener/lib/live_assets/[Link]
módulo LiveAssets
class Engine < ::Rails::Engine initializer
"live_assets.start_listener" do |app| paths = [Link]["app/assets"].existent +
[Link]["lib/assets"].existent + [Link]["vendor/assets"].existent

caminhos = [Link] { |p| p =~ /stylesheets/ }

se [Link]
LiveAssets.start_listener :reloadCSS, caminhos fim

fim
fim
fim

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 5. Transmissão assíncrona de eventos do servidor para clientes • 96

Observe que iniciamos o listener somente se nossos recursos estiverem sendo compilados
dinamicamente; isso evita iniciar o listener em produção, onde os recursos geralmente são pré-
compilados e a configuração de compilação está definida como falsa.

Agora que nosso ouvinte é iniciado por padrão e está pronto para enviar eventos aos assinantes, sempre
que uma nova solicitação for feita para /live_assets/ sse, precisamos criar um novo assinante, adicioná-
lo à lista de assinantes e aguardar até que um novo evento seja enviado a ele. Assim que o evento
chegar, transmitimos um evento enviado pelo servidor para o navegador, conforme mostrado na figura a
seguir.

Figura 12 — Visualização da pilha de notificações do sistema de arquivos

A parte complicada desse esquema é a espera: queremos que cada requisição fique ociosa até que um evento chegue.
Verificar a ocorrência de um novo evento em um loop, como fizemos no teste, não é uma opção, pois causaria um pico
no uso da CPU. Poderíamos contornar isso fazendo com que a requisição aguardasse um período específico, como

meio segundo, e então verificando a ocorrência de um evento, mas isso também não é o ideal. O ideal seria aguardar
o tempo necessário e despertar automaticamente assim que um evento chegasse.

Ruby já vem com uma solução perfeita em sua Biblioteca Padrão: a classe Queue .
Vamos dar uma olhada.

[Link]
errata do relatório • discutir
Machine Translated by Google

Notificações do sistema de arquivos com threads • 97

Threads e filas
Uma fila é uma estrutura de dados do tipo FIFO (primeiro a entrar, primeiro a sair). Podemos tornar
uma implementação de fila acessível a qualquer código Ruby exigindo uma thread, e ela fornece uma
API muito simples:

requer "thread" q =
[Link]

t = [Link] do
enquanto último = [Link]
sleep(1) # simula custos puts último fim

fim

q << :foo
sleep(1)
$[Link]

Este código cria uma nova fila e uma nova thread. Dentro da thread, há um loop que chama o método
`Queue#pop()`. Se não houver nenhum item na fila, a thread ficará bloqueada até que um item seja
adicionado à fila. Nas três últimas linhas, adicionamos um símbolo à fila, o que despertará a thread.
Após um segundo, se enviarmos o conteúdo para `$stdout`, veremos "foo" impresso.

Isso significa que as filas são a estrutura perfeita para usarmos como assinantes! Se a fila estiver vazia,
a solicitação ficará em espera até que um novo evento chegue; então, transmitiremos esse novo evento
e voltaremos a ficar em espera. Vamos criar uma classe chamada LiveAssets::SSESubscriber que
receberá esses eventos e os emitirá no formato de fluxo de eventos enviados pelo servidor, como no
teste a seguir:

live_assets/2_listener/test/live_assets/subscriber_test.rb
require "test_helper" require
"thread" class
LiveAssets::SubscriberTest < ActiveSupport::TestCase test "yields server sent events from the
queue" do # Vamos iniciar nossa fila com alguns eventos queue = [Link]
queue << :reloadCSS queue << :ping queue << nil

# E crie um assinante em cima disso subscriber =


LiveAssets::[Link](queue) stream = [] [Link] do |msg|
stream << msg
end

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 5. Transmissão assíncrona de eventos do servidor para clientes • 98

assert_equal 2, [Link] assert_includes


stream, "event: reloadCSS\ndata: {}\n\n" assert_includes stream, "event: ping\ndata: {}\n\n"
end end

Nosso teste cria uma fila, a passa para um assinante e, em seguida, consome todos os eventos
emitidos pelo assinante. Observe que adicionamos `nil` à fila como forma de sinalizar que não temos
mais eventos para consumir. Vamos implementar o assinante:

live_assets/2_listener/lib/live_assets/sse_subscriber.rb requer
o módulo "thread" do
LiveAssets
class SSESubscriber def
initialize(queue = [Link]) @queue = queue

[Link](@queue) end

def each
while event = @[Link] yield "event:
#{event}\ndata: {}\n\n" end

fim

fechar def
[Link](@queue) fim

fim
fim

e carregá-lo automaticamente:

live_assets/2_listener/lib/live_assets.rb
módulo LiveAssets

autoload :SSESubscriber, "live_assets/sse_subscriber" fim

Por fim, vamos reescrever nossa ação live_assets#sse para utilizar nosso novo assinante:

live_assets/2_listener/app/controllers/live_assets_controller.rb def sse

[Link]["Cache-Control"] = "no-cache" [Link]["Content-


Type"] = "text/event-stream"

sse = LiveAssets::[Link]

[Link] { |msg| [Link] msg }


resgatar IOError
[Link]
[Link] fim

[Link]
errata do relatório • discutir
Machine Translated by Google

Notificações do sistema de arquivos com threads • 99

Mais uma vez, reinicie o servidor Puma em execução dentro de test/dummy e verifique se os fluxos
de eventos chegam somente depois que você editar app/assets/stylesheets/[Link], refletindo
alterações imediatas na página. Desta vez, vamos aumentar o tamanho da fonte:

corpo { tamanho da fonte: 32px; }

Estamos quase terminando nossa implementação; há apenas um último problema a resolver. Caso
não haja alterações em uma folha de estilo por algum tempo, podemos ficar um longo período sem
transmitir dados para o navegador. Isso pode fazer com que o navegador, o servidor ou até mesmo
um proxy intermediário feche a conexão.
conexão.

Temporizador

Para garantir que a conexão não seja fechada devido a longos períodos de inatividade, precisamos
de um temporizador cuja única responsabilidade seja enviar um evento de ping aos assinantes a
cada dez segundos. Vamos começar escrevendo um teste:

live_assets/3_final/test/live_assets_test.rb
teste "recebe notificações do temporizador" faça # Crie um
temporizador
l = LiveAssets.start_timer(:ping, 0.5)

# Nosso assinante é um simples array subscriber = []

[Link](assinante)

início
# Aguarde até recebermos um evento
verdadeiro enquanto [Link]?
assert_includes subscriber, :ping
garantir

# Limpar
[Link](assinante) fim fim

Nosso temporizador também será executado em sua própria thread e enviará eventos aos assinantes
de forma síncrona:

live_assets/3_final/lib/live_assets.rb def
self.start_timer(event, time)
[Link] do while
true
[Link] { |s| s << event } sleep(time) end end

fim

[Link]
errata do relatório • discutir
Machine Translated by Google

Capítulo 5. Transmissão assíncrona de eventos do servidor para clientes • 100

Essa implementação é suficiente para que nosso teste seja aprovado! Finalmente, vamos adicionar
ao nosso mecanismo outro inicializador responsável por iniciar o temporizador:

live_assets/3_final/lib/live_assets/[Link]
inicializador "live_assets.start_timer" faça |app| se [Link]

LiveAssets.start_timer :ping, 10 fim fim

Reinicie o servidor web Puma; agora, os eventos de ping devem ser enviados a cada dez
segundos. Não registramos nenhum retorno de chamada para esses eventos no lado do JavaScript,
mas poderíamos fazê-lo se quiséssemos. O objeto EventSource do JavaScript também emite
eventos de abertura e fechamento quando a conexão é aberta e fechada. A Rede de
Desenvolvedores da Mozilla tem mais informações sobre eventos enviados pelo servidor que você
pode explorar.

Ao longo da nossa implementação, um dos detalhes que deixamos passar despercebido foi a
necessidade de definir `config.allow_concurrency` como `true`. Agora, com a implementação em
tempo real concluída, temos a oportunidade perfeita para discutir esse assunto.

5.4 Técnicas de Carregamento de Código

Para entender por que precisamos ativar explicitamente o allow_concurrency, precisamos analisar
os mecanismos disponíveis no Ruby e no Rails para carregar código.

A forma mais comum de carregar código é o método require() do Ruby :

requer "ativos_vivos"

Algumas bibliotecas funcionam bem apenas com o uso de `require`, mas à medida que crescem,
algumas delas tendem a depender de técnicas de autoload para evitar carregar todos os seus
arquivos de uma só vez. O autoload é particularmente importante em plugins do Rails porque
ajuda a manter o tempo de inicialização da aplicação baixo em ambientes de desenvolvimento e
teste, já que carregamos os módulos somente quando precisamos deles.

Técnicas de carregamento automático

Neste capítulo, utilizamos o recurso de carregamento automático (autoload) do Ruby com a classe
LiveAssets::SSESubscriber :

módulo LiveAssets
autoload :SSESubscriber, "live_assets/sse_subscriber" fim

4. [Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Técnicas de Carregamento de Código • 101

Agora, na primeira vez que LiveAssets::SSESubscriber for acessado, ele será carregado automaticamente.
Os plugins e aplicações do Rails possuem outra técnica de carregamento de código, que é o autoload do
Rails. Por exemplo, nosso LiveAssetsController é carregado automaticamente quando precisamos dele pela
primeira vez. Mas esse caso não é tratado pelo Ruby, e sim pelo ActiveSupport::Dependencies, que vem
com o Rails.

O problema com as abordagens em Ruby e Rails é que o carregamento de código em Ruby não é atômico
— não ocorre em uma única etapa. Por exemplo, se você tiver uma requisição em andamento na Thread A
e essa thread começar a carregar o LiveAssetsController, a classe LiveAssetsController poderá estar visível
na Thread B em outra requisição antes que a Thread A tenha terminado de carregar o arquivo app/controllers/
live_assets_controller.rb . Nesse cenário, a Thread B terá uma implementação parcial do controlador, que,
por exemplo, pode conter apenas a ação hello() (e não a sse() ), levando a uma falha.

Embora algumas implementações do Ruby estejam trabalhando para tornar o autoload do Ruby thread-safe (para que o cenário

descrito anteriormente não ocorra), o autoload do Rails não é thread-safe. Para contornar esse problema, sempre que o Rails precisa

carregar código automaticamente, ele permite que apenas uma thread seja executada por padrão, o que significa que ele só pode

atender a uma requisição por vez. É por isso que não conseguimos servir recursos ao mesmo tempo em que a conexão de eventos

enviados pelo servidor (Server-Sent Events - SSE) é estabelecida.

estava aberto. Para contornar essa limitação, definimos config.allow_concurrency como verdadeiro , por
nossa conta e risco.

O que isso significa para a produção? Precisamos permitir explicitamente a concorrência em produção?
Quais são as nossas opções para implantar esta aplicação?

Técnicas de carregamento antecipado

Em produção, o Rails carrega seu código antecipadamente : todos os seus modelos, controladores, helpers
e outros elementos são carregados na inicialização. Como todo o código do Rails é carregado de uma vez e
não há recarregamento de código, o carregamento automático é desativado. E quando não há carregamento
automático, podemos executar nossa aplicação Rails com segurança com `config.allow_concurrency` definido
como `true`, o que o Rails faz por padrão.

No entanto, o Rails só carrega antecipadamente o código definido dentro do diretório do aplicativo . Se


dependermos do autoload do Ruby, precisamos carregar o código antecipadamente nós mesmos! Caso
contrário, podemos estar carregando código no meio de uma requisição. Isso é o que poderia acontecer com
o LiveAssets::SSESubscriber. Imagine o seguinte:
cenário:

Na primeira requisição para /live_assets/ sse, o Ruby começará a carregar o LiveAssets::SSESubscriber. Se


muitas requisições ocorrerem simultaneamente para este endpoint, a primeira requisição pode não ter
terminado de carregar o subscriber, resultando no seguinte problema:

[Link]
errata do relatório • discutir
Machine Translated by Google

Capítulo 5. Transmissão assíncrona de eventos do servidor para clientes • 102

A solicitação para visualizar uma definição parcial do assinante requer que o LiveAssets::SSESubscriber
seja carregado antecipadamente (eager load) durante a inicialização da aplicação Rails. Como essa
é uma necessidade comum em plugins do Rails e no próprio código-fonte do Rails, o Rails oferece
algumas facilidades para isso.

A primeira conveniência é a opção de configuração `config.eager_load_namespaces` , disponível em


qualquer railtie ou engine, que mantém uma lista de namespaces para carregamento antecipado.
Vamos adicionar `LiveAssets` a essa lista na definição da nossa engine:

live_assets/3_final/lib/live_assets/[Link]
config.eager_load_namespaces << LiveAssets

Agora o Rails chamará `LiveAssets.eager_load!` para carregar explicitamente nosso código em


produção. No entanto, ainda não implementamos o método `eager_load!()` . Vamos defini-lo com a
ajuda de `ActiveSupport::Autoload`.

live_assets/3_final/lib/live_assets.rb
módulo LiveAssets
estender ActiveSupport::Autoload

eager_autoload do
autoload :SSESubscriber
fim
fim

Ao estender nosso módulo com ActiveSupport::Autoload, obtemos automaticamente um método


LiveAssets.eager_load! que carrega antecipadamente tudo o que está definido dentro do bloco
eager_autoload() . Não precisamos mais passar um caminho para autoload(); o Rails faz o possível
para adivinhar com base no nome da constante.

Isso é tudo o que precisamos fazer para que o Rails carregue o restante do nosso código
antecipadamente. Lembre-se: usaremos essa técnica sempre que tivermos código que o Rails não
carrega automaticamente, geralmente configurado por meio de autoloads do Ruby. Podemos abrir um
console no diretório `test/dummy` para verificar todos os namespaces que o Rails carrega antecipadamente:

[Link].eager_load_namespaces # =>
[ Suporte Ativo, Despacho de Ações, Modelo Ativo, Visualização de Ações,
ActionController, ActiveRecord, ActionMailer, LiveAssets::Engine,
LiveAssets, Dummy::Application ]

Lembre-se de que o carregamento antecipado não é benéfico apenas para servidores web com
suporte a threads, como o Puma, mas também para servidores baseados em forks, como o Unicorn.ÿ
O Unicorn opera tirando um snapshot da nossa aplicação Rails logo após a inicialização. Ao carregar
o código antecipadamente, garantimos que esse snapshot contenha todo o código carregado de
antemão, sem a necessidade de recarregar o código automaticamente a cada requisição.

5. [Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Técnicas de Carregamento de Código • 103

Portanto, a decisão sobre qual servidor web usar quando se trata de streaming (e, para
requisições de longa duração, geralmente se resume à capacidade do servidor web de lidar
com muitas conexões simultâneas) é crucial. Por exemplo, o Unicorn funciona iniciando um
conjunto de processos de servidor web, e cada servidor web consegue lidar com apenas uma
requisição por vez (o modelo multiprocesso de thread única). Se um servidor web estiver
transmitindo dados em streaming ou recebendo um arquivo grande, ele não conseguirá atender
a outras requisições, mesmo que os dados sejam transmitidos pela conexão apenas a cada
dez segundos! Por outro lado, o servidor web Puma, que usamos neste capítulo, consegue
lidar com outras requisições mesmo durante o streaming.

Infelizmente, não existe uma solução mágica, e a melhor opção em termos de implantação é
comparar os diferentes servidores web disponíveis. Servidores web multithread como o Puma
podem lidar com múltiplas requisições, assim como servidores orientados a eventos como o
Thin; no entanto, até a versão 1.5, o Thin ainda não suporta streaming.ÿ Servidores como o
Passenger e o Rainbows! permitem combinar diferentes estilos de concorrência, oferecendo
uma opção de implantação híbrida multithread e multiprocesso.ÿ,ÿ Para ampliar ainda mais as
opções, você pode obter resultados ainda melhores implantando em plataformas como JRuby
e Rubinius.ÿ,¹ÿ

Diferentes plataformas oferecem aos desenvolvedores diferentes garantias de segurança de


threads. Por exemplo, operações com arrays em JRuby não têm garantia de segurança, o que
representa um problema em nosso plug-in. O array `[Link]` é uma estrutura
de dados global, e pode acontecer de duas requisições tentarem se inscrever exatamente ao
mesmo tempo, corrompendo nosso array. Dito isso, precisamos encapsular nossas operações
de inscrição em um mutex, uma estrutura que garante que apenas uma thread possa executar
um trecho de código específico em um dado momento.

live_assets/3_final/lib/live_assets.rb
@@mutex = [Link] def
[Link](subscriber) @@[Link]
do
assinantes << assinante
fim
fim
def [Link](subscriber) @@[Link]
do [Link](subscriber)
end end

6. [Link]
7. [Link]
8. [Link]
9. [Link]
10. [Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 5. Transmissão assíncrona de eventos do servidor para clientes • 104

Ao executar os testes novamente, nosso conjunto de testes deve permanecer verde e nosso código
agora também é thread-safe no JRuby! É importante ter isso em mente: sempre que o estado global é
manipulado no meio de uma requisição, precisamos verificar qual garantia de thread-safety cada
implementação do Ruby nos oferece e agir de acordo. Somente escrevendo código thread-safe podemos
contar com uma variedade de opções de implantação disponíveis.

5.5 Concluindo
Neste capítulo, utilizamos as funcionalidades de transmissão ao vivo do Rails para enviar eventos do
servidor para o navegador. Nossa implementação tinha uma entidade central que monitorava as
alterações no sistema de arquivos, as quais eram enviadas para um grupo de assinantes. Utilizamos
threads e filas nativas do Ruby para controlar o fluxo de informações em todo o sistema.

Os tópicos abordados neste capítulo servem de base para apresentar técnicas de carregamento de
código no Rails e como essas técnicas afetam a implantação de nossas aplicações. Abordamos
superficialmente a segurança de threads e as sutilezas envolvidas com diferentes servidores web e
implementações do Ruby.

A seguir, veremos como encapsular o comportamento de nossos controladores em um objeto chamado


` ActionController::Responder` e personalizá-lo para atender às nossas necessidades! Depois,
discutiremos os geradores do Rails e aprenderemos outras maneiras de personalizá-los.

[Link]
errata do relatório • discutir
Machine Translated by Google

Neste capítulo, veremos


• Respondedores do Rails e o método respond_with() •
Personalização de templates de geradores do Rails

CAPÍTULO 6

Escrevendo controladores DRY com respondedores

O gerador de scaffolds do Rails é uma ótima ferramenta para nos ajudar a prototipar uma nova aplicação.
Sua flexibilidade nos permite trocar o mecanismo de templates padrão, a estrutura de testes e o mapeador
objeto-relacional (ORM) pelas nossas opções favoritas, garantindo produtividade independentemente das
ferramentas escolhidas. O único problema com o scaffolding é que os controladores gerados ainda são
um pouco verbosos, e acabamos com muito comportamento duplicado em diferentes controladores. Por
exemplo, aqui estão as ações `create()` e `destroy()` semelhantes ao que seria gerado pelo gerador de
scaffolding quando invocado com um atributo `name`:

class UsersController < ApplicationController def create

@user = [Link](user_params) respond_to


do |format| if @[Link]

[Link] { redirect_to @user, notice: 'Usuário criado com sucesso.' } [Link] { render action: 'show', status: :created,
location: @user } else

[Link] { render action: 'new' } [Link] { render json:


@[Link], status: :unprocessable_entity } end

fim
fim

def destroy @user


= [Link](params[:id]) @[Link] respond_to
do |format| [Link]
{ redirect_to users_url } [Link]
{ head :no_content } end end

privado

[Link]
errata do relatório • discutir
Machine Translated by Google

Capítulo 6. Escrevendo controladores DRY com respondedores • 106

def user_params
[Link](:user).permit(:name) end

fim

Todos esses blocos respond_to() são muito semelhantes de um controlador para outro.
Para resolver esse problema, o Rails fornece um método chamado `respond_with()`, que usa um `
ActionController::Responder` para abstrair a forma como nossos controladores respondem. Usando essa nova
interface de programação de aplicativos (API), essas ações são reduzidas ao seguinte:

classe UsersController < ApplicationController


responder_para :html, :json

def criar

@user = [Link](user_params) flash[:notice]


= 'Usuário criado com sucesso.' if @[Link] respond_with(@user) end

def destroy @user


= [Link](params[:id]) @[Link]
respond_with(@user)
end

privado

def user_params
[Link](:user).permit(:name) end

fim

No início do código, declaramos a quais formatos nosso controlador responde e delegamos todo o trabalho
pesado para `respond_with()`. Poderíamos reescrever todas as nossas ações usando essa API mais limpa.

Neste capítulo, abordaremos como os respondedores funcionam, como personalizá-los para lidar
automaticamente com o cache HTTP e mensagens flash e, finalmente, como personalizar o gerador de
scaffold para usar respond_with() por padrão.

6.1 Compreendendo os Socorristas


Para entender os conceitos por trás dos respondedores, precisamos compreender as três variáveis que afetam
a forma como os controladores respondem: tipo de requisição, verbo HTTP e status do recurso.

[Link]
errata do relatório • discutir
Machine Translated by Google

Entendendo os Socorristas • 107

Solicitações de navegação e API


Um controlador criado pelo gerador de scaffolds responde a dois formatos por padrão: HTML e JSON (JavaScript

Object Notation). O gerador de scaffolds utiliza esses dois formatos porque eles representam dois tipos de requisições:
navegacionais e de API. As primeiras são processadas por um navegador e contêm formatos como HTML e MOBILE,
enquanto as segundas são utilizadas por máquinas e representam formatos como XML e JSON.

def índice
@users = [Link] respond_to
do |format| [Link] #
[Link] [Link] { render json:
@users } end

fim

Vamos analisar esta ação index() , comum a muitas aplicações Rails. O formato HTML não recebe nenhum bloco,
então ele renderiza um template, e o formato JSON renderiza a representação JSON do recurso com render json:
@users.

Isso significa que o comportamento dos controladores depende do tipo de requisição. Consequentemente, para
abstrair o funcionamento dos controladores, os responsáveis pela resposta devem levar em consideração o tipo de
requisição.

Verbo HTTP

As ações `show()` e `new()` em um controlador Rails respondem de forma semelhante a `index()`, renderizando um

modelo ou uma representação do objeto solicitado. E o que todas essas ações têm em comum? O verbo HTTP.

As ações restantes, como `create()` e `destroy()`, são acionadas pelos verbos `POST` e `DELETE`, respectivamente, e
respondem de maneira diferente, redirecionando para locais distintos, retornando códigos de status e cabeçalhos

HTTP diferentes. Em outras palavras, o verbo HTTP é outra variável que afeta a forma como um controlador responde.

Considerando o número de resultados possíveis para uma requisição, vamos analisar atentamente o controlador de

estrutura básica gerado pelo Rails e criar uma tabela que represente como ele responde dependendo do tipo de
requisição e do verbo HTTP.

Por padrão, todas as requisições GET, geralmente tratadas por ações como `index()`, `show()` e `new()`, renderizam

um modelo para as requisições de navegação. Se tivermos uma requisição de API, podemos renderizar um modelo
(como um modelo ` .jbuilder` ) ou renderizar a representação do recurso (por exemplo, chamando `to_json() ` nele). A

Tabela 1, Comportamento de Recursos do Scaffold para GET, na página 108, resume essas informações.

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 6. Escrevendo controladores DRY com respondedores • 108

Solicitação de navegação
PEGAR Solicitação de API renderizar modelo renderizar modelo ou renderizar recurso.to_format
PUBLICAR

COLOCAR

EXCLUIR

Tabela 1 — Comportamento do recurso de estrutura para GET

Até agora, sabemos como um controlador estruturado responde ao método GET em ambos os tipos de requisição.

Agora, vamos analisar os outros verbos HTTP e preencher toda a tabela.

Status do recurso
Se analisarmos a ação create() , que representa uma requisição POST, perceberemos que ela possui duas ramificações:

uma se o recurso for salvo com sucesso e outra se não for. Cada uma dessas ramificações responde de uma maneira

diferente:

def criar

@user = [Link](user_params)

respond_to do |format| if @[Link]


[Link]
{ redirect_to @user, notice: 'Usuário criado com sucesso.' } [Link] { render action: 'show', status: :created, location:
@user } else

[Link] { render action: "new" } [Link] { render


json: @[Link], status: :unprocessable_entity } end

fim
fim

O estado do recurso determina como o controlador de estrutura básica responde.

Neste caso, redirecionamos se o recurso for criado com sucesso, mas exibimos uma página com erros se a criação

falhar. Também podemos observar esse padrão na ação `update()` , que é invocada pelas requisições `PATCH` e `PUT`.

Embora a ação `destroy()` gerada pelo scaffolding não pareça depender do status do recurso, eventualmente

precisaremos alterá-la para lidar com casos em que ` [Link]` retorne `false`. Por exemplo, imagine uma

configuração em que um grupo tenha vários gerentes. Como um grupo precisa ter pelo menos um gerente, implementamos

um callback ` before_destroy()` que verifica essa condição sempre que tentamos remover um gerente. Se a condição

não for atendida, tanto o callback quanto o método `destroy() ` retornam `false`. Esse novo cenário precisa ser tratado

no controlador, geralmente alterando a ação `destroy()` para exibir uma mensagem flash e redirecionar para a página do
grupo. Em outras palavras, mesmo

[Link]
errata do relatório • discutir
Machine Translated by Google

Explorando ActionController::Responder • 109

Embora a ação destroy() gerada pelo scaffold não dependa do estado do recurso, as solicitações DELETE podem
depender.

Dito isso, o controlador precisa saber o status do recurso para responder às solicitações POST, PUT e DELETE.
Nossa tabela foi modificada para representar esse novo cenário e preenchida adequadamente para cada tipo de
solicitação, verbo HTTP e
estado do recurso:

Modelo de API
PEGAR renderização de navegação renderizar modelo ou recurso.para_ formato

POST bem-sucedido redirecionar para recurso renderizar modelo ou recurso.para_ formato

Falha na renderização POST: novo render [Link]

COLOCAR Redirecionamento bem-sucedido para o cabeçalho do recurso: sem conteúdo

COLOCAR Falha na renderização: editar render [Link]

EXCLUIR Sucesso redirect_to cabeçalho da coleção :no_content

Falha no redirecionamento para a coleção DELETE ao renderizar o recurso [Link]

Tabela 2 — Comportamento dos Recursos de Andaimes

Sempre que você invoca `respond_with()` em seus controladores, ele chama a classe `ActionController::Responder` ,
que nada mais é do que toda esta tabela escrita em código Ruby. Vamos explorar como `ActionController::Responder`
é implementado e como podemos modificá-lo para se comportar de uma maneira personalizada.

6.2 Explorando ActionController::Responder


Qualquer coisa que responda a `call()`, que aceita três argumentos, pode ser um respondedor. Os três argumentos
fornecidos a `call()` são o controlador atual, o recurso (ou um recurso aninhado ou um array de recursos) e um hash
de opções. Todas as opções fornecidas a `respond_with()` são encaminhadas ao respondedor como o terceiro
argumento.

ActionController::Responder implementa o método call() em uma única linha de código, como podemos ver no código-
fonte do Rails:

rails/actionpack/lib/action_controller/metal/[Link] def
[Link](*args) new(*args).respond
end

O método call() encaminha esses três argumentos para a inicialização do ActionController::Responder e, em seguida,
chama respond():

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 6. Escrevendo controladores DRY com respondedores • 110

rails/actionpack/lib/action_controller/metal/[Link] #
Ponto de entrada principal para o respondedor responsável # por
encaminhar para o formato correto. def respond method =
"to_#{format}"
respond_to?(method) ? send(method) :
to_format end

# O formato HTML não renderiza o recurso, # ele sempre tenta


renderizar um modelo. def to_html default_render rescue

ActionView::MissingTemplate
=> e
comportamento_de_navegação(e)
fim

# to_js simplesmente tenta renderizar um modelo.


# Se nenhum modelo for encontrado, gera um erro. def to_js
default_render
end

# Todos os outros formatos seguem o procedimento abaixo. Primeiro, # tentamos renderizar


um modelo. Se o modelo não estiver disponível, # verificamos se o recurso responde a :to_format e
o exibimos. def to_format if get? || !has_errors? || response_overridden? default_render else

exibir_erros fim

resgate ActionView::MissingTemplate => e api_behavior(e) fim

O método respond() verifica se o respondedor lida com o formato da requisição atual. Se sim, ele chama o
método específico para esse formato; caso contrário, chama to_format(). Como ActionController::Responder
define apenas to_html() e to_js(), somente as requisições HTML e JavaScript (JS) têm um comportamento
personalizado, e todas as outras recorrem ao caso to_format() .

Ao analisar as implementações de `to_html()` e `to_format()` , vemos que a primeira responde com


`navigational_behavior()` e a segunda com `api_behavior()`. Se adicionarmos um novo formato de navegação
a um aplicativo, como `MOBILE`, o servidor o tratará como um formato de API, e não como um formato de
navegação.
Felizmente, devido ao funcionamento dos respondedores, podemos fazer com que as solicitações MOBILE
usem o comportamento de navegação simplesmente atribuindo um alias ao método :to_mobile para :to_html
em um inicializador.

[Link] errata do relatório • discutir


Machine Translated by Google

Explorando ActionController::Responder • 111

ActionController::Responder.class_eval do alias :to_iphone :to_html


end

Além disso, observe que um respondedor sempre invoca o método default_render() antes de recorrer à API ou
ao comportamento de navegação:

rails/actionpack/lib/action_controller/metal/[Link] def
to_html
default_render rescue
ActionView::MissingTemplate => e
comportamento_de_navegação(e)
fim

default_render() simplesmente tenta renderizar um modelo caso nenhum tenha sido renderizado ainda
(performed?() deve retornar falso) e, caso o modelo não seja encontrado, gera uma exceção
ActionView::MissingTemplate, que é tratada corretamente, permitindo que o comportamento dos respondedores
entre em ação.

Eis como o Rails implementa os métodos navigational_behavior() e api_behavior() :

rails/actionpack/lib/action_controller/metal/[Link]
AÇÕES_PADRÃO_PARA_VERBOS =
{ post: :novo,
patch: :editar,
put: :editar
}
# Este é o comportamento comum para formatos associados
# com navegação, como :html, :iphone e assim por diante. def
navigation_behavior(error) if get? raise error

elsif has_errors? && default_action render :action =>


default_action else

redirecionar_para_local_de_navegação fim

fim

# Este é o comportamento comum para formatos associados


# com APIs, como :xml e :json. def api_behavior(error)
raise error unless resourceful? if
get? display resource elsif post? display

resource, :status
=> :created, :location
=> api_location else

cabeçalho: sem conteúdo


fim
fim

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 6. Escrevendo controladores DRY com respondedores • 112

com certeza, engenhoso?


recurso.responder_a?("a_#{formato}") fim

def has_errors?
resource.respond_to?(:errors) && ![Link]? end

def resource_location options[:location]


|| resources end

alias :navigation_location :resource_location


alias :api_location :resource_location

# Exibir é apenas um atalho para renderizar um recurso com o formato atual. #

# Exibir @usuário, status: :ok #

Para requisições XML, é equivalente a:


#

# renderizar xml: @user, status: :ok #

# As opções enviadas pelo usuário também são utilizadas: #

# respond_with(@user, status: :created) # display(@user, status: :ok) #

# Resultados em:
#

# renderizar xml: @user, status: :created #

def display(resource, given_options={}) [Link]


given_options.merge!(options).merge!(format => resource) end

A implementação de navigational_behavior() mapeia diretamente para a tabela em Status do


Recurso, na página 108. Para uma requisição GET, ela gera um erro de modelo ausente,
porque a única opção para requisições GET é renderizar um modelo, o que já tentamos sem
sucesso.

Para outros verbos HTTP, o comportamento de navegação verifica se o recurso contém erros.
Caso positivo e uma ação padrão seja fornecida, a ação padrão especificada pelo hash
DEFAULT_ACTIONS_FOR_VERBS é executada . Por fim, se o recurso não contiver erros, o
redirecionamento é feito para o recurso correto, que é o comportamento esperado em caso de sucesso.
casos.

A implementação de `api_behavior()` segue um caminho diferente. Ela usa o método `display()` ,


que combina as opções fornecidas a `respond_with()` e adiciona um formato antes de chamar
`render`. Em outras palavras, quando chamamos `respond_with()` assim:

[Link]
errata do relatório • discutir
Machine Translated by Google

Explorando ActionController::Responder • 113

respond_with @user, status: :created

Em solicitações GET para formato JSON, o controlador responde da seguinte forma:

render json: @user, status: :created

É importante perceber que os respondedores do Rails não chamam ` @user.to_json`. Eles simplesmente
delegam essa responsabilidade ao método `render()` e, consequentemente, ao renderizador `:json` , como
vimos na Seção 1.2, "Escrevendo o Renderizador", na página 5. Isso é importante porque é possível adicionar
novos renderizadores, e eles funcionam nos respondedores sem a necessidade de adicionar nenhuma outra
linha de código.

Por fim, a última personalização disponível nos respondedores pode ser feita em nosso próprio controlador.
Imagine que temos um respondedor que funciona perfeitamente em todos os casos, exceto para uma ação e
formato específicos em que queremos que ele se comporte de maneira diferente. Podemos personalizar o
respondedor para esse cenário usando a mesma API de bloco que em `respond_to()`:

def índice
@users = [Link]
respond_with(@users) do |format|
[Link] { render json: @users.to_json(some_specific_option: true) } end end

E tudo isso funciona porque respond_with() encaminha o bloco fornecido a [Link] para o respondedor
quando o formato da requisição é JSON. O default_render() visto nos trechos de código anteriores do
respondedor chama esse bloco sempre que ele estiver disponível.

A grande vantagem de usar `ActionController::Responder` é que ele centraliza o comportamento da nossa


aplicação por formato. Dito isso, se quisermos alterar o comportamento de todos os controladores de uma só
vez, basta criar nosso próprio respondedor e configurar o Rails para usá-lo, como mostrado aqui:

[Link] = MyAppResponder

Além disso, podemos definir respostas personalizadas para controladores específicos em nossa aplicação:

class UsersController < ApplicationController [Link] =


MyCustomUsersResponder end

Vamos criar um respondedor com algum comportamento extra e configurar o Rails para usá-lo.
isto.

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 6. Escrevendo controladores DRY com respondedores • 114

6.3 O Respondedor Instantâneo

O controlador gerado automaticamente usa mensagens flash nas ações `create()` e `update()` .
Essas mensagens são bastante semelhantes em diferentes controladores. Não seria interessante
se pudéssemos definir essas mensagens flash por padrão nos respondedores, mas ainda
assim fornecer uma API amigável para alterá-las?

Vamos implementar essa funcionalidade usando o framework de internacionalização (I18n)


para que possamos facilmente buscar mensagens flash em arquivos YAML, configurar valores
padrão e possibilitar a tradução dessas mensagens no futuro. Vamos usar o plugin do Rails
para criar um novo projeto chamado responders:

plugin rails novos respondedores

Vamos começar escrevendo alguns testes que acessam as ações create(), update() e destroy()
e garantem que uma mensagem flash esteja sendo exibida para o cliente:

responders/1_flash/test/responders/flash_test.rb
require "test_helper" class
FlashTest < ActionController::TestCase
testes UsersController

teste "define mensagem de aviso na criação bem-sucedida" faça


post :create, user: { name: "John Doe" } assert_equal "Usuário
criado com sucesso.", flash[:notice] end

teste "define mensagem de aviso em atualização bem-sucedida" faça


usuário = [Link]!(name: "John Doe") put :update, id:
[Link], user: { name: "Outro John Doe" } assert_equal "Usuário atualizado com sucesso.",
flash[:notice] end

teste "define mensagem de aviso após destruição bem-sucedida" faça usuário =


Usuá[Link]!(nome: "John Doe") exclua :destruir, id:
usuá[Link] assert_equal "Usuário foi destruído
com sucesso.", flash[:aviso] fim

fim

O teste depende da existência de um UsersController, que podemos definir invocando o gerador


de scaffold dentro da aplicação dummy em test/dummy.
Ao invocar o gerador, vamos ignorar os arquivos de teste, garantindo que eles não entrem em
conflito com nossos testes de plug-in:

$ rails g scaffold Nome de usuário:string --no-test-framework

Em seguida, execute as migrações e configure nosso banco de dados de teste:

$ rake db:migrate db:test:clone

[Link] errata do relatório • discutir


Machine Translated by Google

O Flash Responder • 115

Observe, no entanto, que o gerador de scaffolds não utiliza a API de resposta.


Vamos alterar o controlador gerado para usar `respond_with()` e, em seguida, remover todas as
mensagens flash, pois nossos respondedores as configurarão automaticamente. Este é o nosso
`UsersController` após essas alterações:

responders/1_flash/test/dummy/app/controllers/users_controller.rb classe
UsersController < ApplicationController
respond_to :html, :json
before_action :set_user, only: [:show, :edit, :update, :destroy] def index

@users = [Link]
respond_with(@users) end

mostrar def

respond_with(@user) fim

def novo
@user = [Link]
respond_with(@user) end

def editar
fim

def criar

@user = [Link](user_params) @[Link]

respond_with(@user) end

def update
@[Link](user_params)
respond_with(@user) end

def destroy
@[Link]
respond_with(@user) end

private #
Use callbacks para compartilhar configurações ou restrições comuns entre ações. def set_user @user =
[Link](params[:id])
end

# Permitir apenas parâmetros confiáveis da "lista branca". def user_params

[Link](:user).permit(:name) end end

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 6. Escrevendo controladores DRY com respondedores • 116

Todas as ações neste controlador devem invocar `respond_with()`, com exceção da ação
`edit()` . Isso ocorre porque a ação `edit()` é usada apenas por requisições de navegação,
já que sua principal responsabilidade é exibir o formulário para editar o recurso.

Ao executarmos o conjunto de testes, ele falha com a seguinte mensagem:

1) Falha:
test_sets_notice_message_on_successful_creation(FlashTest): Esperado: "Usuário criado com
sucesso."
Real: nenhum

A falha era esperada, pois ainda não implementamos nossos mecanismos de resposta.
Como desenvolveremos duas extensões de resposta neste capítulo, vamos escrever cada
extensão como um módulo, permitindo que os desenvolvedores incluam essa funcionalidade
onde desejarem. Nosso primeiro módulo se chama Responders::Flash e busca mensagens
flash por meio do framework I18n.

Imagine uma requisição com parâmetros válidos na ação `create()` do ` UserController`.


Quando `respond_with()` é chamado e nenhuma mensagem flash está definida, o
respondedor deve tentar encontrar uma mensagem I18n no namespace e ação do
controlador, que neste caso é "[Link]". Se uma mensagem I18n for
encontrada, o respondedor deve defini-la em `flash[:notice]`, e ela será exibida corretamente
na próxima requisição.

Alternativamente, se a solicitação em UsersController#create não tiver parâmetros válidos


(ou seja, o usuário criado é inválido), o respondedor deve procurar uma mensagem em
"[Link]" e definir flash[:alert] em vez disso.

Com esses requisitos em mente, vamos escrever nosso módulo Responders::Flash :

responders/1_flash/lib/responders/[Link]
Módulo de resposta Módulo
Flash
def to_html
set_flash_message! unless get? super end
private
def

set_flash_message! status =
has_errors? ? :alert : :notice return if [Link][status].present?

mensagem = i18n_lookup(status)
[Link][status] = mensagem se [Link]? fim

def i18n_lookup(status)
namespace = controller.controller_path.gsub("/", ".") ação
= controlador.nome_da_ação

[Link] errata do relatório • discutir


Machine Translated by Google

O Flash Responder • 117

lookup = [namespace, action, status].join(".").to_sym default = ["actions", action,


status].join(".").to_sym I18n.t(lookup, scope: :flash, default: default,

nome_do_recurso: [Link].nome_do_modelo.humano)
fim
fim
fim

Nosso módulo sobrescreve o comportamento de to_html() para definir mensagens flash para solicitações que
não sejam GET e, em seguida, chama super, permitindo que o comportamento do respondedor e outras
extensões entrem em ação.

Além de configurar mensagens flash com base no namespace do controlador, nossa implementação fornece um
namespace "actions" como uma opção :default para I18n.t. Isso permite que o I18n recorra a
"[Link]" caso uma mensagem não seja encontrada em "[Link]".

Esse mecanismo de fallback nos permite fornecer mensagens padrão para toda a aplicação, evitando repetições
em cada controlador. Vamos definir as mensagens padrão do scaffold em nosso plug-in criando um arquivo
YAML com o seguinte conteúdo:

responders/1_flash/lib/responders/locales/[Link]
em:
clarão:
ações:
criar:
aviso: "%{resource_name} foi criado com sucesso." alerta: "" atualização: aviso: "%

{resource_name} foi atualizado com sucesso." alerta: ""

destroy:
aviso: "%{resource_name} foi destruído com sucesso." alerta: "%{resource_name} não
pôde ser destruído."

Agora, qualquer controlador usará as mensagens flash configuradas neste arquivo YAML, a menos que
definamos uma chave específica para que o controlador personalize sua mensagem.
Para alcançar isso, usamos a interpolação I18n, que nos permite usar %{resource_name} em nossas
mensagens, e ele será substituído pelo nome humano do recurso fornecido a :resource_name quando I18n.t
for invocado.

Para finalmente passarmos nos testes, precisamos disponibilizar o Responders::Flash .


Em vez de modificar o respondedor padrão do Rails, vamos herdar dele e adicionar nossas próprias
personalizações. Definiremos nossos novos respondedores como os padrões e adicionaremos nosso arquivo
YAML padrão com traduções ao caminho de carregamento do I18n:

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 6. Escrevendo controladores DRY com respondedores • 118

responders/1_flash/lib/[Link]
require "action_controller" require
"responders/flash" module Responders

class AppResponder < ActionController::Responder include Flash

fim
fim

ActionController::[Link] = Responders::AppResponder require "active_support/i18n"

I18n.load_path << File.expand_path("../responders/locales/[Link]", __FILE__)

Execute o conjunto de testes e você verá que nosso respondedor é acionado corretamente e está usando
as mensagens flash padrão no arquivo YAML! Como nossos testes verificam apenas mensagens de
"aviso", vamos escrever um teste adicional para verificar se as mensagens de "alerta" serão exibidas em
caso de falhas:

responders/1_flash/test/responders/flash_test.rb teste
"define mensagens de alerta do escopo do controlador" faça
início
[Link].store_translations :en,
flash: { usuários: { destruir: { alerta: "Não é possível destruir!" } } }

usuário = [Link]!(name: "Indestrutível") delete :destroy, id:


[Link] assert_equal "Não pode ser destruído!",
flash[:alert]
garantir

[Link]!
fim
fim

O teste cria um recurso e tenta destruí-lo, mas falha, exibindo uma mensagem de que o recurso não pode
ser destruído. Como fizemos em " Como criar uma API compatível com o modelo ativo", na página 21,
estamos usando a API I18n para armazenar traduções dinamicamente para o cenário de falha.

Para que o teste seja aprovado, vamos adicionar uma função de retorno de chamada ` before_destroy() `
que adiciona mensagens de erro a ` @[Link]` e retorna `false` se o nome de usuário for "Indestrutível":

responders/1_flash/test/dummy/app/models/[Link]
classe Usuário < ActiveRecord::Base
before_destroy do if name
== "Indestrutível"
[Link](:base, "é indestrutível") falso

fim
fim
fim

[Link]
errata do relatório • discutir
Machine Translated by Google

Respondedor de cache HTTP • 119

Precisamos anexar erros ao modelo para sinalizar ao servidor que algo deu errado. Com essa alteração final,
nossos testes passam novamente! Existem outros recursos que poderíamos adicionar ao nosso servidor
Flash, mas vamos em frente e torná-lo um cidadão HTTP melhor.

6.4 Respondedor de Cache HTTP

O Rails adotou o REST desde a versão 1.2 e, desde então, o desenvolvimento de APIs tornou-se cada vez
mais fácil. No entanto, à medida que sua aplicação cresce, você pode precisar se concentrar mais na
implementação da sua API e encontrar maneiras de otimizar o número de requisições que ela pode processar.

Ao expor uma API, é comum que um cliente solicite um recurso ao servidor várias vezes, recebendo sempre
a mesma resposta, visto que o recurso solicitado não foi alterado. Nesses casos, o servidor perde tempo
renderizando o mesmo recurso repetidamente, e o cliente analisa a mesma resposta apenas para descobrir
que nada mudou.

Felizmente, a especificação HTTP 1.1 possui uma seção dedicada ao cache. O problema anterior poderia ser
facilmente resolvido se o servidor adicionasse um cabeçalho Last-Modified à resposta com um timestamp
representando a última modificação do recurso. Para requisições subsequentes, o cliente deveria adicionar
um cabeçalho If-Modified-Since com esse timestamp e, se o recurso não tiver sido alterado, o servidor deveria
retornar um status 304 Not Modified e não precisaria renderizar o recurso.

recurso novamente. Ao receber um status 304, o cliente sabe que nada mudou. A Figura 13, Interação do
cliente e do servidor com o cache HTTP, na página 120, ilustra esse cenário.

Como de costume, vamos começar nossa implementação escrevendo testes. Há pelo menos três cenários a
serem considerados:

• Quando If-Modified-Since não é fornecido, nosso controlador responde normalmente, mas


Adiciona um cabeçalho Last-Modified .

• Quando o parâmetro If-Modified-Since é fornecido e está atualizado, nosso controlador responde com o
status 304 e um corpo em branco.

• Quando If-Modified-Since é fornecido e não é atualizado, nosso controlador responde


normalmente, mas adiciona um novo cabeçalho Last-Modified .

Para escrever esses testes, precisamos modificar alguns cabeçalhos de requisição e verificar se alguns
cabeçalhos de resposta estão sendo definidos corretamente. Vamos usar novamente o UsersController
existente, disponível no aplicativo de exemplo, para dar suporte aos nossos testes:

[Link]
errata do relatório • discutir
Machine Translated by Google

Capítulo 6. Escrevendo Controladores DRY com Respondedores • 120

Figura 13 — Interação do cliente e do servidor com o cache HTTP

responders/2_http_cache/test/responders/http_cache_test.rb require
"test_helper" class HttpCacheTest
< ActionController::TestCase tests UsersController

setup do
@[Link] = "application/json"
ActionController::Base.perform_caching = true

Usuá[Link](nome: "Primeiro", atualizado_em: [Link](2009))


Usuá[Link](nome: "Segundo", atualizado_em: [Link](2008)) fim

teste "responde com última modificação usando o carimbo de data/hora mais recente" faça
obter :index
assert_equal [Link](2009).httpdate, @[Link]["Last-Modified"] assert_match '"name":"First"',
@[Link] assert_equal 200, @[Link] end

[Link] errata do relatório • discutir


Machine Translated by Google

Respondedor de cache HTTP • 121

teste "responde com não modificado se a solicitação ainda estiver recente" faça
@[Link]["HTTP_IF_MODIFIED_SINCE"] = [Link](2009, 6).httpdate get :index assert_equal 304,
@[Link]
assert @[Link]? end

teste "responde com a última modificação se a solicitação não for recente" faça
@[Link]["HTTP_IF_MODIFIED_SINCE"] = [Link](2008, 6).httpdate get :index assert_equal

[Link](2009).httpdate, @[Link]["Last-Modified"] assert_match '"name":"First"', @[Link]


assert_equal 200, @[Link] end

fim

O Rails fornece diversas funções auxiliares além da especificação de cache HTTP, e nós as
utilizaremos para criar um novo módulo chamado Responders::HttpCache que adiciona
automaticamente a funcionalidade de cache HTTP a todas as requisições GET:

responders/2_http_cache/lib/responders/http_cache.rb módulo
Responders
módulo HttpCache
delegado :resposta, para: :controlador def to_format
retornar se
do_http_cache? && do_http_cache! super fim

privado

def do_http_cache!
response.last_modified ||= max_timestamp if max_timestamp head :not_modified if fresh =
[Link]?(response) fresh

fim

# Percorra todos os recursos e encontre o último atualizado. def max_timestamp

@max_timestamp ||= [Link] do |resource|


resource.updated_at.try(:utc) if resource.respond_to?(:updated_at)
[Link].máximo
fim
# Acione o cache somente se for uma requisição GET e # o cache estiver habilitado.
def do_http_cache? get? &&

ActionController::Base.perform_caching end

fim
fim

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 6. Escrevendo Controladores DRY com Respondedores • 122

Nossa implementação basicamente percorre todos os recursos fornecidos e os recupera.


o carimbo de data/hora da última atualização. Em seguida, atualizamos o objeto de resposta.
e se a solicitação for recente (isto é, se o recurso não foi modificado entre
solicitações), retornamos um status 304 para o cliente e não renderizamos nenhum recurso,
já que to_format() retorna antes de chamar super.

Antes de executar nossos novos testes, vamos importar o repositório `responders/http_cache` e incluir o seguinte:
Responders::HttpCache em nosso AppResponder, modificando o início do arquivo lib/[Link]
arquivo:

responders/2_http_cache/lib/[Link]
exigem "respondentes/flash"
requer "responders/http_cache"

módulo Responders
classe AppResponder < ActionController::Responder
inclui Flash
incluir HttpCache
fim
fim

E pronto! Nosso conjunto de testes está passando novamente! Extraímos o flash e o HTTP.
A responsabilidade pelo cache era dos nossos controladores, e agora isso é gerenciado automaticamente.
por nosso respondente!

6.5 maneiras adicionais de personalizar geradores


Agora que entendemos como os socorristas trabalham e como adaptá-los à nossa realidade,
conforme as necessidades, podemos ter confiança em usá-los cada vez mais em nossos controladores. O único
problema é que o gerador de scaffolds usa respond_to() por padrão.
e não respond_with().

Por outro lado, em Ganchos dos Geradores, na página 74, discutimos como
Personalize os geradores e deve haver um gancho para personalizar o controlador.
gerado no scaffold, certo? Vamos dar uma olhada na saída do scaffold:

invocar registro ativo


criar db/migrate/20130415031520_create_users.rb
criar app/models/[Link]
invocar rota de recurso
rota recursos: usuários

invocar scaffold_controller
Crie o arquivo app/controllers/users_controller.rb
invocar erva
criar aplicativo/visualizações/usuários
criar app/views/users/[Link]
criar app/views/users/[Link]
criar app/views/users/[Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Mais maneiras de personalizar geradores • 123

criar app/views/users/[Link]
criar app/views/users/_form.[Link]
invocar ajudante
criar app/helpers/users_helper.rb
invocar ativos
invocar js
criar app/assets/javascripts/[Link]
invocar CSS
criar app/assets/stylesheets/[Link]
invocar css
criar app/assets/stylesheets/[Link]

Cada invocação na saída é um gancho que podemos sobrescrever. Isso significa que podemos
de fato, substitua o gerador scaffold_controller por outro que se adeque ao nosso caso.
precisa.

No entanto, não é assim que resolveremos esse problema. Em vez disso, vamos usar outra abordagem.
O recurso Rails-generator permite personalizar modelos sem a necessidade de usar um gerador.
ganchos.

Caminho de origem dos geradores

Considere a seguinte linha em um gerador do Rails:

copiar_arquivo "[Link]", "app/controller/#{file_name}_controller.rb"

Ele simplesmente copia o arquivo [Link] da fonte dos geradores para o local especificado.
destino, que para um UsersController seria app/controllers/users_controller.rb.

No entanto, um gerador pode ter mais de uma fonte! Antes de copiar um arquivo
Para um determinado local, o gerador procura esse arquivo de origem em vários locais, chamados
caminhos de origem. O método de classe `source_root()` que especificamos em Criação
Nosso primeiro gerador, na página 77, é na verdade o último lugar onde um gerador é mencionado.
procura um modelo.

Esse comportamento já está integrado ao Thor,¹ mas o Rails o encapsula de forma elegante por meio de
Adicionando automaticamente o diretório lib/templates dentro do seu aplicativo a todos os
Os caminhos de origem dos geradores. Isso significa o Rails::Generators::ScaffoldControllerGenerator.
O script usado no scaffold sempre tentará encontrar um modelo em lib/templates/rails/scaf-fold_controller
antes de usar o fornecido pelo Rails.

Ao analisarmos a implementação de Rails::Generators::ScaffoldControllerGenerator


No código-fonte do Rails, podemos ver facilmente a lógica que copia o controlador.
modelo:

1. [Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 6. Escrevendo Controladores DRY com Respondedores • 124

rails/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb módulo
Rails
geradores de módulo
classe ScaffoldControllerGenerator < NamedBase def
criar_arquivos_de_controlador
template "[Link]", [Link]("app/controllers", class_path, "#{controller_file_name}_controller.rb")

fim
fim
fim
fim

Ele usa um modelo chamado [Link], que está disponível em railties/lib/rails/generators/rails/


scaffold_controller/templates/[Link]. De acordo com os caminhos de origem, se colocarmos um
arquivo em lib/templates/rails/scaffold_controller/[Link] dentro de nossa aplicação, o Rails usará este
arquivo da aplicação em vez do arquivo que vem com o Rails!

Você pode testar isso facilmente criando uma nova aplicação Rails, colocando um arquivo vazio em `lib/
templates/rails/scaffold_controller/[Link]` dentro da sua aplicação e executando o comando `scaffold`.
Quando você verificar o controlador criado pelo scaffold, ele estará vazio!

Vamos usar esse recurso incrível para personalizar o scaffold para usar respond_with() por padrão.

Usando respond_with por padrão


Para usar `respond_with()` por padrão no scaffold, vamos colocar um modelo em `lib/templates` da nossa
aplicação. No entanto, para evitar fazer isso em cada nova aplicação, criaremos um gerador que copia um
arquivo para o local correto.

Vamos chamar esse gerador de Responders::Generators::InstallGenerator e implementá-lo da seguinte


forma:

responders/3_final/lib/generators/responders/install/install_generator.rb módulo
Responders
geradores de módulo
classe InstallGenerator < Rails::Generators::Base
source_root File.expand_path("../templates", __FILE__) def copy_template_file copy_file
"[Link]",

"lib/templates/rails/scaffold_controller/[Link]"
fim
fim
fim
fim

Aqui está o modelo que nosso gerador utiliza:

[Link]
errata do relatório • discutir
Machine Translated by Google

Mais maneiras de personalizar geradores • 125

responders/3_final/lib/generators/responders/install/templates/[Link] <%
module_namespacing do -%>

classe <%= controller_class_name %>Controlador < ApplicationController


before_action :set_<%= singular_table_name %>, only:
[:show, :edit, :update, :destroy]

# GET <%= route_url %> def index

@<%= plural_table_name %> = <%= orm_class.all(class_name) %> respond_with(@<%=


plural_table_name %>) end

# GET <%= route_url %>/1 def show

respond_with(@<%= singular_table_name %>) fim

# GET <%= route_url %>/novo def novo

@<%= singular_table_name %> = <%= orm_class.build(class_name) %> respond_with(@<%=


singular_table_name %>) end

# GET <%= route_url %>/1/edit def editar fim

# POST <%= route_url %> def criar

@<%= singular_table_name %> = <%= orm_class.build(class_name, "#{singular_table_name}


_params") %>
@<%= orm_instance.save %>
respond_with(@<%= singular_table_name %>) end

# PATCH/PUT <%= route_url %>/1 def update


@<%=
orm_instance.update("#{singular_table_name}_params") %> respond_with(@<%= singular_table_name
%>) end

# DELETE <%= route_url %>/1 def destroy


@<%=
orm_instance.destroy %> respond_with(@<%=
singular_table_name %>) end

privado

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 6. Escrevendo controladores DRY com respondedores • 126

# Use callbacks para compartilhar configurações ou restrições comuns entre ações. def set_<%= singular_table_name
%>
@<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %> fim

# Permitir apenas parâmetros confiáveis da "lista branca". def <%= "#{singular_table_name}


_params" %> <%- if attributes_names.empty? -%> params[<%=
":#{singular_table_name}" %>] <%- else -%>

[Link](<%= ":#{singular_table_name}" %>). permit(<%=


attributes_names.map { |name| ":#{name}" }.join(', ') %>) <%- end -%>

fim
fim
<% fim -%>

O modelo anterior é baseado no que vem com o Rails, mas substitui todas as chamadas de
`respond_to()` por `respond_with()`. Ele também usa vários métodos que já discutimos,
exceto `orm_class()` e `orm_instance()`, que abordaremos em breve.

Para testar o novo gerador, basta acessar o aplicativo de demonstração e invocá-lo.


isto:

$ rails g responders:instalar

Agora, ao criar qualquer novo recurso, ele usará o modelo que acabamos de instalar! Isso
significa que o scaffold do Rails é flexível não apenas para extensões do Rails como Haml
ou RSpec, mas também para desenvolvedores de aplicativos, pois eles podem personalizar
o scaffold para se adequar ao seu fluxo de trabalho, estrutura do aplicativo e marcação.

Geradores e agnosticismo de ORM


Já discutimos o Active Model e seu papel no agnosticismo de ORMs. Também falamos
sobre os hooks de geradores, que permitem que outros ORMs se integrem aos geradores
de modelos e scaffolds. Sempre que usamos o gerador de scaffolds, essas duas funções
se cruzam e o Rails fornece uma API personalizada para permitir que os ORMs customizem
o código gerado.

Os controladores do Rails são responsáveis por interagir com o modelo e passar os objetos desejados para a
visualização. Em outras palavras, os controladores devem interagir com o ORM atual e recuperar as
informações necessárias dele. O controlador gerado pelo scaffolding deve mudar dependendo do ORM
utilizado. O Rails resolve esse problema criando um objeto responsável por informar ao gerador de scaffolding
como a interação com o ORM deve ocorrer. A implementação básica desse objeto está disponível no código-
fonte do Rails e se parece com isto:

[Link]
errata do relatório • discutir
Machine Translated by Google

Mais maneiras de personalizar geradores • 127

rails/railties/lib/rails/generators/active_model.rb
módulo Rails
geradores de módulo
classe ActiveModel

attr_reader :nome def


initialize(nome)
@name = nome fim

# Obter índice
def [Link](klass) "#{klass}.all"
end

# GET show
# OBTER edição
# Atualização PATCH/PUT #
Destruição DELETE def
[Link](klass, params=nil) "#{klass}.find(#{params})"
end

# RECEBA novo
# POSTagem criar
def [Link](klass, params=nil) if params

"#{klass}.new(#{params})" else "#{klass}.new"


end

fim

# POSTagem criar
salvar def
"#{name}.save" fim #

Atualização PATCH/PUT def


update(params=nil)
"#{name}.update(#{params})" fim

# POSTagem criar
# Atualização PATCH/PUT
def errors
"#{name}.errors" end

# DELETE destroy def


destroy
"#{name}.destroy" end

fim
fim
fim

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 6. Escrevendo controladores DRY com respondedores • 128

A função `orm_class() ` aponta para `Rails::Generators::ActiveModel`, e `orm_instance() ` aponta para uma instância

dessa mesma classe. Portanto, sempre que invocamos `orm_class.all("User")` no template, isso invoca

`Rails::Generators::[Link]("User")` e retorna `[Link]`, que é o comportamento normal do Active Record.

A função `orm_instance() ` comporta-se de forma semelhante, exceto que não precisamos passar o nome do recurso

como argumento, uma vez que já o fizemos na inicialização. Por esse motivo, `orm_instance.save` retorna com sucesso

`[Link]` para o Active Record.

Toda a interação entre o controlador e o ORM é especificada em `Rails::Generators::ActiveModel`. O agnosticismo

decorre do fato de que qualquer ORM pode fornecer sua própria implementação dessa classe. Basta definirmos uma

classe `ActiveModel` dentro do namespace do gerador do ORM.

Por exemplo, o DataMapper possui uma sintaxe diferente para encontrar e atualizar registros. Portanto, ele precisa herdar

de Rails::Generators::ActiveModel e implementar a nova API:

módulo DataMapper
geradores de módulo
classe ActiveModel < ::Rails::Generators::ActiveModel
def [Link](klass, params=nil) "#{klass}.get(#{params})"
end

fim
fim
fim

Os geradores de estrutura, juntamente com a API Active Model, possibilitam o agnosticismo no Rails, permitindo que os

desenvolvedores escolham as ferramentas que melhor se adaptam ao seu fluxo de trabalho.

6.6 Concluindo
Neste capítulo, analisamos os mecanismos de resposta para entender como funcionam e como personalizá-los. Como

prova de conceito, desenvolvemos duas extensões para mecanismos de resposta: uma para lidar com mensagens flash

e outra para lidar com o cache HTTP.

Há muito mais que poderíamos delegar aos servidores de resposta. Na camada HTTP, poderíamos usar o cabeçalho de

requisição `If-Unmodified-Since` para fornecer requisições PUT condicionais, nas quais o recurso é atualizado somente

se não tiver sido modificado após a data especificada; caso contrário, retornamos um status 409 Conflito . A Figura 14,

Interação entre cliente e servidor com requisições HTTP condicionais, na página 129, ilustra esse cenário.

[Link]
errata do relatório • discutir
Machine Translated by Google

Concluindo • 129

Figura 14 — Interação entre cliente e servidor com solicitações condicionais HTTP

Também demos outra olhada nos geradores do Rails e aprendemos mais sobre o agnosticismo de
ORM.

Se você deseja incorporar os respondedores e o respond_with() ao seu fluxo de trabalho, pode


experimentar a gem Responders da Plataformatec,² que implementa ambas as extensões
desenvolvidas neste capítulo e contribui com alguns recursos extras, como a capacidade de alterar
os respondedores para redirecionar para a ação index() em vez da ação show() quando um usuário
é criado ou atualizado.

Em seguida, vamos nos conectar à API de Notificações do Rails para armazenar todas as consultas
que o Rails envia ao banco de dados e usar um mecanismo montável do Rails para expor essas
consultas por meio de uma interface web isolada do nosso aplicativo!

2. [Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Neste capítulo, veremos:


• API ActiveSupport::Notifications
• Engines isoladas e montáveis do Rails •
Rack, stacks de middleware e middleware personalizado

CAPÍTULO 7

Gerenciando eventos de aplicativos

com mecanismos montáveis

Desde os primórdios do Ruby on Rails, as pessoas se perguntam o que acontece dentro de suas
aplicações. Quantas consultas foram realizadas nesta requisição? Quanto tempo essa requisição levou?

Para abordar essa preocupação comum, alguns projetos de código aberto (como Foot-notes e Bullet1,2)
e serviços (como Scout e RPM3,4 da New Relic) foram
construído. Já que todas essas ferramentas diferentes eram necessárias para extrair essas informações de

O Rails evoluiu para fornecer uma maneira centralizada de publicar e assinar eventos que ocorrem dentro
de um aplicativo com a interface de programação de aplicativos (API) ActiveSupport::Notifications .

Neste capítulo, usaremos essa API para nos inscrevermos em todas as ações processadas por nossa
aplicação e armazená-las em um banco de dados MongoDB. Em seguida, usaremos um mecanismo Rails
para criar um conjunto de rotas, controladores e visualizações para navegar pelos dados armazenados.
Esse mecanismo pode então ser compartilhado entre aplicações Rails e montado em endpoints específicos.

7.1 Motores Montáveis e Isolados


No Capítulo 5, " Transmitindo Eventos do Servidor para Clientes de Forma Assíncrona", na página 83,
criamos um mecanismo Rails que transmite dados para nossa aplicação. Além de fornecer um controlador,
o mecanismo adicionou novas rotas à nossa aplicação e métodos auxiliares como `live_assets`. De certa
forma, esse mecanismo estava estendendo diretamente o...

1. [Link] 2.
[Link] 3.
[Link] 4.
[Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 7. Gerenciando eventos de aplicativos com mecanismos montáveis • 132

Nossa aplicação Rails com seus próprios componentes. No entanto, esse comportamento nem
sempre é desejável.

Tomemos como exemplo o plug-in que construiremos neste capítulo. Ele fornecerá seus próprios
modelos, controladores e visualizações. À medida que o plug-in cresce, o número de rotas começará
a se multiplicar, assim como o número de métodos auxiliares. Se o nosso plug-in implementar um
método auxiliar `show_paginated_results()` e uma aplicação Rails usar o nosso plug-in, não
queremos que o nosso método auxiliar esteja disponível dentro da aplicação Rails, já que ele é
interno ao nosso plug-in. Pior ainda, se a aplicação tiver o seu próprio método auxiliar
`show_paginated_results()` , ele sobrescreverá o método auxiliar definido no nosso plug-in, o que
pode levar a falhas!

O Rails resolve esses problemas fornecendo engines montáveis e isoladas. Uma engine montável
usa seu próprio roteador em vez de adicionar rotas diretamente ao roteador da aplicação. Uma
engine isolada é construída dentro de seu próprio namespace, com seus próprios modelos,
controladores, visualizações, recursos e helpers. Vamos gerar nossa primeira engine montável com
o comando do plugin Rails , passando `--mountable` como opção:

$ rails plugin new mongo_metrics --mountable

A opção --mountable gera um mecanismo montável e isolado. Podemos observar isso verificando
alguns arquivos diferentes. Primeiro, vamos abrir o arquivo config/[Link] do plug-in:

mongo_metrics/1_engine/config/[Link]
MongoMetrics::[Link] do end

Observe como as rotas são desenhadas através do mecanismo. Compare-as com o arquivo config/
[Link] gerado no rake , conforme descrito na Seção 5.1, "Extending Rails with Engines", na
página 84, que são desenhadas diretamente na aplicação:

live_assets/1_live/config/[Link]
[Link] do get "/
live_assets/:action", to: "live_assets" end

Como as rotas não são mais adicionadas ao aplicativo, o mecanismo precisa ser montado
explicitamente no roteador do aplicativo, o que é feito automaticamente pelo comando do plugin
Rails no aplicativo de teste/fictício :

mongo_metrics/1_engine/test/dummy/config/[Link]
[Link] do mount
MongoMetrics::Engine => "/mongo_metrics" end

[Link] errata do relatório • discutir


Machine Translated by Google

Armazenando notificações no banco de dados • 133

Isso é tudo o que é necessário para tornar nosso mecanismo montável. Para ter um mecanismo isolado,
precisamos declará-lo explicitamente como isolado e escolher um namespace. A opção `--mountable` gera
automaticamente uma definição de mecanismo com `MongoMetrics` como o namespace isolado.

mongo_metrics/1_engine/lib/mongo_metrics/[Link]
módulo MongoMetrics classe
Engine < ::Rails::Engine
isolate_namespace MongoMetrics fim

fim

Como estamos declarando um namespace isolado, todos os nossos controladores, modelos e helpers
devem ser definidos dentro desse namespace, garantindo que fiquem isolados do restante da aplicação.
Assim, os arquivos helper definidos no engine não serão incluídos automaticamente pela aplicação e vice-
versa. Isso também proporciona muitas conveniências — por exemplo, se estivéssemos usando o Active
Record, ele prefixaria todos os nomes de tabelas de modelos com `mongo_metrics_` e garantiria que o
Rails gerasse modelos, controladores e helpers com namespaces.

Além dessas alterações, o comando do plugin Rails com a opção --mountable gera alguns arquivos extras,
como manifestos de ativos e um MongoMetrics::ApplicationController em app/controllers/mongo_metrics/
application_controller.rb, tornando nosso mecanismo mais parecido com a aparência de um aplicativo Rails
totalmente novo.

Com o nosso plug-in configurado, é hora de explorar a API ActiveSupport::Notifications do Rails e começar
a armazenar essas notificações no banco de dados.

7.2 Armazenando notificações no banco de dados


Antes de implementarmos a lógica para armazenar notificações no banco de dados, vamos dar uma olhada
na API de Notificações.

A API de Notificações

A API de Notificações consiste em apenas dois métodos: instrument() e subscribe().


O primeiro é chamado quando queremos instrumentar e publicar um evento, e para o processamento do
Controlador de Ações, ele se parece com isto:

ActiveSupport::[Link]("process_action.action_controller", format: :html, path: "/", action: "index") do

process_action("index") fim

O primeiro argumento é o nome do evento publicado, que neste caso é process_action.action_controller, e


o segundo é um hash com informações sobre

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 7. Gerenciando eventos de aplicativos com mecanismos montáveis • 134

O evento, chamado payload. Para se inscrever nessas notificações, basta passar o nome do
evento e um bloco para subscribe(), como segue:

evento = "process_action.action_controller"
ActiveSupport::[Link](event) do |*args|
# faça alguma coisa para
terminar

onde args é uma matriz com cinco itens:

• nome: Uma string com o nome do evento

• started_at: Um objeto Time que representa quando o evento começou.

• ended_at: Um objeto Time que representa quando o evento terminou

• instrumenter_id: Uma string contendo o ID exclusivo do objeto instrumentado.


o evento

• payload: Um Hash com as informações fornecidas como payload para o instrumento()


método

E isso é tudo o que precisamos saber. Em seguida, vamos dar uma olhada no banco de dados onde...
Vamos armazenar as notificações.

Usando o MongoDB

O MongoDB é um banco de dados rápido e orientado a documentos, perfeitamente adequado


para armazenar notificações, já que se trata de dados de alto volume e baixo valor. Você
pode ler mais sobre o MongoDB em seu site,ÿ que também inclui instruções de instalação
para diferentes sistemas operacionais.

Atualmente, existem vários mapeadores objeto-relacionais para interagir com o MongoDB,


e usaremos o Mongoid neste projeto.ÿ Não abordaremos as instruções de instalação,
portanto, se você não tiver o MongoDB instalado, faça isso agora! Depois que o MongoDB
estiver instalado e em execução, vamos adicionar o Mongoid como uma dependência ao
nosso plugin:

mongo_metrics/1_engine/mongo_metrics.gemspec
s.add_dependency "mongoid", "~> 4.0.0"

Vamos também gerar a configuração do Mongoid dentro de nossa aplicação de teste/fictícia :

$ rails g mongoid:config

e exigem o mongoid assim que carregamos nosso plug-in de métricas:

5. [Link] 6.
[Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Armazenando notificações no banco de dados • 135

mongo_metrics/1_engine/lib/mongo_metrics.rb
require "mongoid" require
"mongo_metrics/engine"

fim do módulo MongoMetrics

Com o Mongoid configurado, vamos criar nosso primeiro modelo, devidamente nomeado em app/models/
mongo_metrics/[Link]:

mongo_metrics/1_engine/app/models/mongo_metrics/[Link] módulo
MongoMetrics classe Métrica

incluir Mongoid::Document fim

fim

Antes de escrever qualquer lógica que armazene documentos no MongoDB, vamos escrever um teste em
test/mongo_metrics_test.rb. O teste instrumenta um evento com o nome process_action.action_controller e
verifica se uma métrica foi armazenada no MongoDB com todos os campos relevantes:

mongo_metrics/1_engine/test/mongo_metrics_test.rb requer
"test_helper"

class MongoMetricsTest < ActiveSupport::TestCase setup


{ MongoMetrics::Metric.delete_all }

teste "a notificação process_action é salva no banco de dados mongo" faça evento =
"process_action.action_controller" payload = { "path" => "/" }

ActiveSupport::[Link] evento, carga útil do


sleep(0.001) # simular fim do trabalho

métrica = MongoMetrics::[Link] assert_equal 1,


MongoMetrics::[Link] assert_equal event, mé[Link]
assert_equal "/", mé[Link]["path"]

afirmar mé[Link]ção
assert metric.instrumenter_id assert
metric.started_at assert metric.created_at
end

fim

Ao executarmos o teste, ele falha porque ainda não estamos armazenando nada:

[Link]
errata do relatório • discutir
Machine Translated by Google

Capítulo 7. Gerenciando eventos de aplicativos com mecanismos montáveis • 136

1) Falha:
test_process_action_notification_is_saved_in_the_mongo_database(MongoMetricsTest)
Esperado: 1
Real: 0

Para que o teste seja aprovado, primeiro vamos nos inscrever no ActiveSupport::Notifications no final do
arquivo lib/mongo_metrics.rb:

mongo_metrics/2_metrics/lib/mongo_metrics.rb
requer "suporte_ativo/notificações"

Módulo MongoMetrics
EVENTO = "process_action.action_controller"
ActiveSupport::[Link] EVENT do |*args| MongoMetrics::[Link]!(args)
end end

Nosso gancho de notificação simplesmente chama o método store!() em nosso MongoMetrics::Met-ric, que
será responsável por analisar os argumentos e criar um registro no banco de dados, da seguinte forma:

mongo_metrics/2_metrics/app/models/mongo_metrics/[Link] módulo
MongoMetrics classe Métrica

incluir Mongoid::Documento

campo :nome, tipo: String campo :duração,


tipo: Inteiro campo :id_instrumentador, tipo: String
campo :carga_útil, tipo: Hash campo :iniciado_em, tipo:
Data e Hora campo :criado_em, tipo: Data
e Hora

def [Link]!(args) métrica = nova

mé[Link](args)
mé[Link]!
fim

def parse(args)
[Link] = args[0] =
self.started_at args[1] =
[Link] (args[2] - args[1]) * 1000000 self.instruments_id =
args[3] [Link] = args[4] self.created_at =
[Link] end

fim
fim

[Link] errata do relatório • discutir


Machine Translated by Google

Armazenando notificações no banco de dados • 137

Após essa alteração, nosso conjunto de testes está funcionando corretamente! Para ver como nosso plug-in
se comporta fora do ambiente de teste, vamos criar um controlador ` HomeController` com três ações dentro

de `test/dummy` e, em seguida, executar o aplicativo fictício:

$ rails g controller Home foo bar baz $ rails s

Agora, faça algumas requisições para /home/foo, /home/bar e /home/baz para gerar alguns dados. Quando
terminar, inicie uma nova sessão do console com o comando `rails console` e veja todas as notificações
criadas por essas requisições digitando `MongoMetrics::[Link].to_a` na linha de comando.

Embora nosso sistema de assinatura funcione como esperado, não seria interessante se tivéssemos uma
página onde pudéssemos visualizar essas notificações em vez de usar o console do Rails?
Vamos aproveitar o poder dos engines do Rails mais uma vez!

A página de notificações
Para criar nossa página de notificações, vamos criar um controlador, uma visualização e rotas dentro do nosso
mecanismo. Começaremos com o controlador:

mongo_metrics/2_metrics/app/controllers/mongo_metrics/metrics_controller.rb módulo
MongoMetrics classe
MetricsController < ApplicationController
responder_para :html, :json

def índice
@metrics = [Link]
respond_with(@metrics) end

def destroy
@metric = [Link](params[:id]) @[Link]
respond_with(@metric)
end

fim
fim

Nosso controlador possui duas ações: index() e destroy(). Para a primeira, precisamos criar uma view:

mongo_metrics/2_metrics/app/views/mongo_metrics/metrics/
[Link] <h1>Métricas de listagem</h1>

<table>
<tr>
<th>Nome</th>
<th>Duração</th> <th>Início
em</th>

[Link]
errata do relatório • discutir
Machine Translated by Google

Capítulo 7. Gerenciando eventos de aplicativos com mecanismos montáveis • 138

<th>Carga útil</th> <th></


th> </tr>

<%= content_tag_for :tr, @metrics do |metric| %>


<td><%= [Link] %></td> <td><%=
[Link] / 1000 %>ms</td> <td><%= time_ago_in_words
metric.started_at %> atrás</td> <td>

<ul>
<% [Link] do |k, v| %>
<li><%= [Link] %>: <%= v %></li> <% end %> </ul>
</td> <td><%=
link_to
'Destruir',
metric_path(metric), method: :delete, data: { confirm: 'Tem certeza?' } %></
td> <% end %>

</table>

Para que os links "Destruir" funcionem corretamente no navegador, precisamos adicionar a gem jquery-
rails como uma dependência:

mongo_metrics/2_metrics/mongo_metrics.gemspec
s.add_dependency "jquery-rails", "~> 3.0.1"

Em seguida, inclua a dependência no início do arquivo lib/mongo_metrics.rb, como fizemos para o


mongoid:

mongo_metrics/2_metrics/lib/mongo_metrics.rb
requer "jquery-rails"

Em seguida, inclua tanto o jQuery quanto o jquery_ujs no arquivo de manifesto JavaScript:

mongo_metrics/2_metrics/app/assets/javascripts/mongo_metrics/[Link] //=
requer jquery //= requer
jquery_ujs //= requer_tree .

Por fim, vamos adicionar algumas rotas em config/[Link]:

mongo_metrics/2_metrics/config/[Link]
MongoMetrics::[Link] do root to:
"metrics#index" resources :metrics,
only: [:index, :destroy] end

Observe como declaramos as rotas sem nos preocuparmos com namespaces. Como nosso mecanismo
é montável e isolado, o Rails nos poupa o trabalho de especificar o namespace em cada rota. Além
disso, em nossa view, simplesmente chamamos `metric_path()` e o Rails automaticamente buscou
essa rota no roteador do mecanismo.

[Link]
errata do relatório • discutir
Machine Translated by Google

Armazenando notificações no banco de dados • 139

e não no roteador de aplicação. Mesmo que a aplicação tivesse uma rota nomeada em
`metric_path()` , as rotas não entrariam em conflito!

No entanto, isso levanta uma questão: e se quisermos acessar uma rota de aplicação a partir
do mecanismo, ou uma rota do mecanismo a partir da aplicação?

Para ilustrar o problema, vamos escrever testes de integração. Nossos testes precisam acessar
algumas páginas do aplicativo de teste e, em seguida, percorrer as páginas do nosso plug-in
para garantir que as notificações estejam sendo exibidas corretamente:

mongo_metrics/2_metrics/test/integration/navigation_test.rb require
"test_helper" class NavigationTest
< ActionDispatch::IntegrationTest
setup { MongoMetrics::Metric.delete_all }

teste "pode visualizar notificações "

obter main_app.home_foo_path obter


main_app.home_bar_path obter
main_app.home_baz_path

obter mongo_metrics.root_path assert_match


"Caminho: /home/foo", [Link] assert_match "Caminho: /home/bar",
[Link] assert_match "Caminho: /home/baz", [Link] fim

teste "pode destruir notificações" faça obter


main_app.home_foo_path métrica =
MongoMetrics::[Link] excluir
mongo_metrics.metric_path(métrica) assert_empty
MongoMetrics::[Link](id: [Link]) fim

fim

Nossos novos testes devem passar de imediato. Observe que, sempre que queremos acessar
uma página do aplicativo, usamos o proxy `main_app()` e o proxy `mongo_metrics()` para as
páginas do mecanismo montáveis. O aplicativo Rails estará sempre acessível através de
`main_app()`; o nome do proxy do mecanismo pode ser encontrado executando `rake routes`
no aplicativo de teste:
Padrão URI de verbo prefixado Controlador#Ação

home_foo GET /home/foo(.:format) home#foo home_bar GET /home/


bar(.:format) home#bar home_baz GET /home/baz(.:format)
home#baz mongo_metrics /mongo_metrics
MongoMetrics::Engine

Rotas para MongoMetrics::Engine: raiz GET / /


metrics(.:format) mongo_metrics/metrics#index mongo_metrics/
métricas OBTER metrics#index
métrica DELETE /metrics/:id(.:format) mongo_metrics/metrics#destroy

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 7. Gerenciando eventos de aplicativos com mecanismos montáveis • 140

O nome do proxy está na coluna Prefixo. Observe que o comando `rake routes` também
exibe as rotas do mecanismo montado! Se desejar, você também pode iniciar o aplicativo de
teste e acessar a página de notificações no seu navegador.

Agora que estamos armazenando e exibindo corretamente as informações do MongoDB,


podemos ver que, a cada acesso às páginas do nosso mecanismo, os dados também são
armazenados no MongoDB. Seria impraticável desativar o armazenamento de métricas
sempre que acessássemos o próprio plugin. Para resolver esse problema, vamos implementar
um mecanismo para silenciar as notificações em locais selecionados. Para isso, precisamos
entender como o Rails se integra ao Rack.

7.3 Trilhos e Rack


Citando a documentação do Rack:7

Rack oferece uma interface minimalista, modular e adaptável para o desenvolvimento de


aplicações web em Ruby. Ao encapsular requisições e respostas HTTP da maneira mais
simples possível, ele unifica e simplifica a API de servidores web, frameworks web e
softwares intermediários (o chamado middleware) em uma única chamada de método.

Aplicações Rails precisam de um servidor web para interagir através do protocolo HTTP. E
desde os primórdios, a comunidade Rails tem visto uma enorme variedade de servidores
web disponíveis para implantar aplicações.

Inicialmente, o Rails era responsável por fornecer um adaptador para cada servidor web que
suportava: um para o Mongrel, outro para o WEBrick, outro para o Thin e assim por diante.
Da mesma forma, outros frameworks precisavam fornecer adaptadores diferentes para os
mesmos servidores web, já que possuíam APIs diferentes da do Rails.

Isso rapidamente se mostrou uma duplicação de esforços e, no início de 2007, o Rack foi
lançado com o objetivo de unificar as APIs usadas por servidores web e frameworks web. Ao
seguir a API do Rack, um framework web poderia usar adaptadores de servidor web do Rack
em vez de fornecer os seus próprios, eliminando a duplicação de esforços que existe na
comunidade Ruby.

Embora o Rails 2.2 já oferecesse uma interface Rack simples, o Rails adotou o Rack e sua
API de forma mais abrangente na versão 2.3. No entanto, a verdadeira revolução do Rack
ocorreu no Rails 3, quando diversas partes do Rails se tornaram endpoints Rack, e era
possível facilmente integrar diferentes aplicações Rack no mesmo processo. Por exemplo,
podemos facilmente integrar uma aplicação Sinatra dentro do roteador do Rails, de forma
semelhante a como integramos um engine dentro da aplicação fictícia neste capítulo.

7. [Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Trilhos e suportes • 141

Olá, Rack!
A especificação Rack descreve claramente a API que os aplicativos Rack usam para se comunicar com um
servidor web e entre si:8

Uma aplicação Rack é qualquer objeto Ruby que responde a uma chamada. Ela recebe
exatamente um argumento, o ambiente, e retorna um array com exatamente três valores: o
status, os cabeçalhos e o corpo da requisição.

A API minimalista do Rack nos permite escrever uma aplicação web simples com apenas algumas linhas de
código:

exigir a classe 'rack'


HelloRack
def call(env)
[200, { 'Content-Type' => 'text/html' }, ['Olá Rack!']] fim fim

execute [Link]

Ao criar o arquivo [Link] anterior em um diretório e executar o comando rackup dentro desse mesmo
diretório, o Rack inicia um servidor web e invoca nossa aplicação HelloRack em cada requisição. Quando
você abre um navegador e digita http:// localhost:9292/, você verá “Hello Rack!” como resposta.

Todas as aplicações Rails são distribuídas com um arquivo [Link] , como podemos ver na aplicação de
exemplo dentro de test/dummy, com o seguinte conteúdo:

# Este arquivo é usado por servidores baseados em Rack para iniciar a aplicação. require ::File.expand_path('../
config/environment', __FILE__) run [Link]

Toda aplicação Rails também é uma aplicação Rack: ela implementa o método `call()` , que recebe o
ambiente e retorna um array com três elementos. Por padrão, sua implementação envia a requisição para o
roteador da aplicação (aquele definido em `config/[Link]`), que encaminha a requisição para outra
aplicação Rack se alguma rota corresponder. Vamos explorar mais a fundo.

Entendendo o Roteador do Rails


O roteador Rails pode encaminhar as chamadas para qualquer aplicação Rack:

[Link] do match "/hello", to:


lambda { |env| [200, { "Content-
Type" => "text/plain" }, ["World"]] }
fim

8. [Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 7. Gerenciando eventos de aplicativos com mecanismos montáveis • 142

Ao adicionarmos essa rota a qualquer aplicação Rails e digitarmos /hello no navegador, recebemos “World”
como resposta do servidor. Na verdade, quando temos uma rota como esta:

[Link] do
correspondência "/hello", para: "posts#index" fim

Antes de uma requisição, o Rails converte automaticamente `controller#action` em um aplicativo Rack. Você
pode recuperar qualquer ação de um controlador como um aplicativo Rack simplesmente fazendo o seguinte:

[Link](:index)
[Link](:index).responds_to?(:call) # => true

Sempre que você chama os métodos ` get()`, `post()`, `put()`, `delete()`, `resources()` ou
`resource()` na linguagem específica de domínio do roteador, esses métodos invocam o
método `match()` . O único método com semântica diferente é `mount()`, usado no
aplicativo fictício para montar nosso mecanismo.

O método `match()` funciona comparando a rota completa. Se pensarmos em termos de expressões regulares,
quando dizemos `match "/mongo_metrics"`, ele só encontra correspondências para caminhos como `%r"\A/
mongo_metrics\z"` (a string de consulta não é considerada nas correspondências). No entanto, ao montar um
mecanismo ou qualquer outro aplicativo Rack, não comparamos solicitações apenas para `/mongo_metrics`,
mas também para `/mongo_metrics/metrics`, `/mongo_metrics/other` e assim por diante. A expressão regular
equivalente seria `%r"\A/mongo_metrics",` sem o caractere âncora `\z` .

Você deve ter notado que há algo mais acontecendo. Para acessar o mecanismo montado, enviamos
solicitações para /mongo_metrics/metrics, mas o roteador do mecanismo só encontra correspondência em /
metrics, ignorando o prefixo /mongo_metrics . Por quê?

Sempre que uma requisição atinge um servidor Rack, o servidor obtém o caminho da requisição, armazena-o
no hash de ambiente como env["PATH_INFO"] e o passa para a aplicação Rack subjacente. A montagem
funciona porque, ao despachar para um engine montado, o Rails remove /mongo_metrics de env["PATH_INFO"],
de modo que o engine vê apenas /metrics (como se um navegador estivesse acessando /metrics diretamente
no engine).

Esse mecanismo funciona não apenas com engines, mas com qualquer aplicação Rack, visto que isso está
descrito na própria especificação do Rack. A especificação também determina que o Rails deve definir
`env["SCRIPT_NAME"] = "/mongo_metrics"` antes de chamar a engine montada. Isso informa à engine que
ela está montada em um ponto específico, permitindo que ela ainda gere URLs completas.

[Link]
errata do relatório • discutir
Machine Translated by Google

Pilhas de Middleware • 143

Resumindo, temos uma aplicação Rails, que é uma aplicação Rack, que invoca nosso roteador, que é outra
aplicação Rack, que finalmente encaminha para outra aplicação Rack — um controlador e uma ação, um
mecanismo ou até mesmo uma aplicação Sinatra. São aplicações Rack do começo ao fim! E para tornar as
coisas ainda mais interessantes, o Rack oferece o conceito de middleware, que nos permite adicionar código
personalizado entre essas aplicações Rack, dando-nos ainda mais flexibilidade. Vamos explorar isso a seguir.

7.4 Pilhas de Middleware


Embora os adaptadores de servidor web e a API de aplicativos Rack tenham revolucionado a forma como os
frameworks web Ruby são desenvolvidos, você provavelmente está mais familiarizado com outro termo
relacionado ao Rack: middleware.

Um middleware envolve uma aplicação Rack. Ele nos permite manipular tanto a requisição enviada à aplicação
quanto a resposta retornada por ela. Ao empilhar vários middlewares antes de uma aplicação, criamos uma
pilha de middlewares. Qualquer requisição para uma ação em um controlador Rails passa por três pilhas de
middlewares, como mostra a figura a seguir.

Figura 15 — As camadas de middleware envolvidas em uma requisição para uma ação em um controlador Rails

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 7. Gerenciando eventos de aplicativos com mecanismos montáveis • 144

O Rails oculta a primeira dessas três camadas de middleware. Ele fica entre a web e o servidor web.

servidor e o objeto Rails::Application e contém apenas dois componentes de middleware:

• Rails::Rack::LogTailer: Analisa o arquivo de log e o imprime no console. •


Rails::Rack::Debugger: Requer e habilita o depurador.

Após passar por essa pilha de middleware, a requisição atinge um Rails::Application (por exemplo, nosso
Dummy::Application dentro de test/dummy), que nada mais é do que outra pilha de middleware com o
roteador no final.

A pilha de middlewares do Rails::Application é a mais conhecida no Rails. Você pode adicionar ou remover
middlewares dessa pilha através do arquivo `[Link]`, disponível em `config/[Link]`.
Para visualizar todos os middlewares disponíveis, é necessário executar o comando `rake middleware` na
linha de comando, a partir da raiz da aplicação.

Para nossa aplicação fictícia dentro de test/dummy, o middleware rake retorna o seguinte:

use ActionDispatch::Static use Rack::Lock


use
#<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007fed3d5eddf0> use Rack::Runtime

use Rack::MethodOverride
use ActionDispatch::RequestId use
Rails::Rack::Logger use
ActionDispatch::ShowExceptions use
ActionDispatch::DebugExceptions use
ActionDispatch::RemoteIp use
ActionDispatch::Reloader use
ActionDispatch::Callbacks use
ActiveRecord::Migration::CheckPending use
ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache
use ActionDispatch::Cookies use
ActionDispatch::Session::EncryptedCookieStore
use ActionDispatch::Flash use ActionDispatch::ParamsParser use Rack::Head

use Rack::ConditionalGet
use Rack::ETag use
Rack::Mongoid::Middleware::IdentityMap execute
Dummy::[Link]

A pilha de middleware varia de acordo com suas dependências e seu ambiente Rails. Por exemplo,
`Rack::Mongoid::Middleware::IdentityMap` é uma dependência do Mongoid, enquanto
`ActionDispatch::Reloader` geralmente está disponível apenas em ambientes de desenvolvimento.
Vejamos a lista de middleware:

[Link]
errata do relatório • discutir
Machine Translated by Google

Pilhas de Middleware • 145

• ActionDispatch::Static: Serve ativos públicos em desenvolvimento.

• Rack::Lock: Envolve a aplicação em um Mutex para que apenas uma thread possa acessá-la por vez.
Quando definimos config.allow_concurrency como true na Seção 5.3, Notificações do Sistema de
Arquivos com Threads, na página 92, instruímos o Rails a remover este middleware.

• ActiveSupport::Cache::Strategy::LocalCache: Utiliza um armazenamento em cache na memória para


Fornecer um cache local durante as requisições.

• Rack::Runtime: Mede o tempo da requisição e o retorna como um X-Runtime


cabeçalho.

• Rack::MethodOverride: Verifica solicitações POST e as converte em PUT ou


EXCLUIR se _method estiver presente nos parâmetros.

• ActionDispatch::RequestId: Define as informações de ID da solicitação, que são exibidas nos registros.

• Rails::Rack::Logger: Registra cada requisição

• ActionDispatch::DebugExceptions e ActionDispatch::ShowExceptions: São responsáveis por exibir páginas


de erro úteis em ambiente de desenvolvimento e renderizar páginas de status em ambiente de produção
a partir do diretório público.

• ActionDispatch::RemoteIp: Lida com a verificação de falsificação de IP.

• ActionDispatch::Callbacks e ActionDispatch::Reloader: Execute os callbacks to_prepare — uma vez na


inicialização do aplicativo em produção e antes de cada solicitação em desenvolvimento (como I18n e
recarregamento de rotas).

Os três middlewares seguintes estão todos relacionados ao Active Record, seguidos pelos middlewares
responsáveis pelo gerenciamento de cookies, sessões e mensagens flash. Os últimos middlewares são os
seguintes:

• ActionDispatch::ParamsParser: Analisa os parâmetros fornecidos na solicitação, tanto


a partir de uma string de consulta ou no corpo da requisição POST

• Rack::Head: Converte solicitações HEAD em solicitações GET

• Rack::ConditionalGet: Retorna um código de status 304 caso os cabeçalhos de cache HTTP apropriados
correspondam.

• Rack::ETag: Calcula o resumo do corpo da resposta usando o MD5.


algoritmo e o define como o cabeçalho ETag da resposta HTTP

A última etapa na pilha é o roteador de aplicativos, que é mais um aplicativo Rack. Se o roteador encaminhar a
solicitação para uma ação específica em um controlador, ela também passará por outra pilha de middleware, já
que cada controlador tem sua própria pilha de middleware. Podemos adicionar um middleware a um controlador
da seguinte forma:

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 7. Gerenciando eventos de aplicativos com mecanismos montáveis • 146

classe UsersController < ApplicationController


usar MyMiddleware usar
AnotherMiddleware
fim

A pilha de middleware do controlador é invocada antes de quaisquer filtros e antes do processamento da ação. Como
o Rails oferece muitas opções para conectar um middleware, um middleware parece ser uma boa escolha para
implementar o mecanismo que desativa nosso armazenamento de métricas, então vamos escrever nosso primeiro
middleware!

Construindo o Middleware de

Silenciamento Antes de escrever nosso middleware, precisamos garantir que o MongoMetrics forneça um método
para silenciar notificações para um bloco de código específico. Vamos chamar esse método de `mute!()` e fornecer
um método `mute?() ` que retorne `true` sempre que as notificações forem silenciadas.
Vamos escrever um teste:

mongo_metrics/3_final/test/mongo_metrics_test.rb teste
'pode ignorar notificações quando especificado' faça [Link]! faça
assert [Link]? evento
= "process_action.action_controller"

ActiveSupport::[Link] evento do
dormir(0.001) # simular trabalho fim fim

assert ![Link]? assert_equal


0, MongoMetrics::[Link] end

Para que nosso teste seja aprovado, vamos implementar esses dois métodos e alterar o bloco passado para o método
`subscribe` para respeitar a condição de silenciamento:

mongo_metrics/3_final/lib/mongo_metrics.rb
require "mongoid" require
"jquery-rails" require "mongo_metrics/
engine" require "active_support/notifications"
module MongoMetrics

EVENTO = "process_action.action_controller"
ActiveSupport::[Link] EVENT do |*args| MongoMetrics::[Link]!(args)
unless mute? end

def [Link]!
[Link]["sql_metrics.mute"] = true yield

garantir

[Link]["sql_metrics.mute"] = false end

[Link] errata do relatório • discutir


Machine Translated by Google

Pilhas de Middleware • 147

def [Link]?

[Link]["sql_metrics.mute"] || false end

fim

Observe que usamos variáveis de thread para garantir que silenciar uma requisição em uma thread não afete outras
threads em um ambiente multithread. Além disso, precisamos envolver a chamada `yield()` em um bloco `ensure` ,

permitindo que o status de silenciamento seja revertido mesmo se ocorrer uma exceção durante a execução do bloco.
Vamos escrever um teste para isso também:

mongo_metrics/3_final/test/mongo_metrics_test.rb teste
'não vaza estado de silenciamento em caso de falhas' faça
[Link]! faça assert
[Link]? levante "oops" fim
resgate nil

assert ![Link]? end

Com nossa implementação de `mute!()` pronta, podemos escrever o middleware `MuteMiddleware`. Para garantir que

nosso middleware funcione como esperado, vamos escrever um teste de integração que verifique se o acesso ao
nosso controlador de métricas não gera nenhum evento:

mongo_metrics/3_final/test/integration/navigation_test.rb teste
"não registra ações do mecanismo" faça obter
mongo_metrics.root_path assert 0,
MongoMetrics::[Link] fim

Qualquer middleware Rack é inicializado com a aplicação ou com o middleware que deve ser chamado em seguida na
pilha. Sempre que nosso middleware MuteMiddleware é

Uma vez invocado, basta invocar a aplicação subjacente dentro de um bloco `mute!() `, silenciando efetivamente tudo
o que acontece a jusante. Vamos implementar isso:

mongo_metrics/3_final/lib/mongo_metrics/mute_middleware.rb módulo
MongoMetrics
classe MuteMiddleware
def initialize(app) @app = app
end

def call(env)
[Link]! { @[Link](env) } fim

fim
fim

[Link]
errata do relatório • discutir
Machine Translated by Google

Capítulo 7. Gerenciando eventos de aplicativos com mecanismos montáveis • 148

Como uma requisição típica do Rails passa por três camadas de middleware diferentes, precisamos avaliar o
melhor lugar para usar nosso middleware:

• A camada de interconexão entre o servidor web e a aplicação Rails está inacessível.


Portanto, está fora de questão.

• Uma pilha dentro da aplicação que termina com o roteador não é apropriada, pois adicionar nosso
middleware a essa pilha silenciaria todas as requisições.

• Uma pilha dentro de cada controlador é a nossa melhor opção, pois podemos adicioná-la.
diretamente para MongoMetrics::ApplicationController.

No entanto, quando uma solicitação é direcionada a um mecanismo, existe outra pilha de middleware entre o
roteador de aplicação e o roteador do mecanismo; trata-se da própria pilha de middleware do mecanismo,
conforme mostra a figura a seguir.

Figura 16 — Todas as camadas de middleware envolvidas em uma requisição que inclui um mecanismo Rails.

Essa pilha de middleware é semelhante à pilha de uma aplicação Rails, mas vazia por padrão. Se adicionarmos
nosso middleware à pilha do mecanismo, qualquer requisição que chegue ao nosso plug-in de métricas,
independentemente do controlador, será automaticamente silenciada. Parece útil; vamos adicionar nosso
middleware a essa pilha:

[Link]
errata do relatório • discutir
Machine Translated by Google

Pilhas de Middleware • 149

mongo_metrics/3_final/lib/mongo_metrics/[Link] require
"mongo_metrics/mute_middleware"

módulo MongoMetrics classe


Engine < ::Rails::Engine
isolate_namespace MongoMetrics
[Link] MuteMiddleware end

fim

Podemos configurar a pilha de middleware do mecanismo por meio de `[Link]`. Se


estivéssemos dentro de uma aplicação Rails, também usaríamos `[Link]` .
`[Link]` sempre aponta para a pilha de middleware do mecanismo ou da aplicação atual.
Se quiséssemos modificar a pilha de middleware da aplicação a partir do mecanismo, poderíamos
acessá-la através de config.app_middleware.

Após essas alterações, nossos testes voltarão a passar. Usando middleware, podemos desativar
facilmente o armazenamento de métricas para seções específicas de nossos plug-ins e aplicativos.
Se quisermos desativá-lo para um controlador específico em um aplicativo, ou mesmo para ações
específicas, podemos fazer isso:

classe AdminController < ApplicationController


# Você também pode usar as opções :only e :except. # use
MuteControllerMiddleware, only: :index use MuteControllerMiddleware
end

Sempre que uma aplicação Rack está envolvida, é trivial adicionar uma camada intermediária em
torno dela, e o Rails faz isso frequentemente para nos fornecer diferentes pontos de extensão para
nossas aplicações.

Reduzindo a pilha de middleware


Quando executamos o middleware do Rake na aplicação de teste, ele continha vários middlewares
relacionados ao Active Record. Na verdade, nosso plugin tem uma dependência da gem Rails , que
por sua vez depende do Active Record e de outras gems, como o Action Mailer, que não estamos
usando. Vamos dividir nossa dependência do Rails no gemspec em duas menores:

mongo_metrics/3_final/mongo_metrics.gemspec
s.add_dependency "railties", "~> 4.0.0" s.add_dependency
"sprockets-rails", "~> 2.0.0"

A gem `rails` é uma "meta gem". Ela não vem com nenhum código; em vez disso, simplesmente
contém a pilha padrão do Rails como dependência. É por isso que ela depende de Active Record,
Action Mailer, Sprockets e outros. No nosso caso, substituímos a gem `rails` pela gem `railties` , que
é a espinha dorsal do Rails, contendo os processos de inicialização, railties, definições de engine e
de aplicação, bem como o...

[Link]
errata do relatório • discutir
Machine Translated by Google

Capítulo 7. Gerenciando eventos de aplicativos com mecanismos montáveis • 150

O sprockets-rails nos permite disponibilizar nossos recursos. A gem railties já é independente do


actionpack (para rotas, controladores e visualizações) e do activesupport (extensões Ruby), então não
precisamos adicioná-los explicitamente.

Após essas alterações, nosso aplicativo não inicializará mais, pois nossos arquivos de configuração
foram gerados esperando que algumas das dependências removidas estivessem presentes. Portanto,
certifique-se de remover quaisquer entradas `config.active_record` e `config.action_mailer` dos
arquivos de ambiente em `test/dummy/config/environments`.
Verifique também seus arquivos de teste para garantir que não haja chamadas relacionadas a fixtures,
como fixtures :all, no arquivo test/integration/navigation_test.rb.

Se você executar `rake middleware` novamente, o middleware Active Record não estará mais em
nossa pilha. O processo que acabamos de concluir é semelhante à forma como reduziríamos as
dependências em uma aplicação Rails. O passo principal é substituir a gem `rails` no Gemfile por
dependências diretas e, em seguida, limpar os arquivos de configuração da aplicação. Podemos até
remover alguns componentes, como Sprockets e Active Record, ao gerar a aplicação ou o plugin;
basta passar `--skip-sprockets` ou `--skip-active-record` ao invocar `rails new` ou `rails plugin new`.

7.5 Streaming com Rack


Embora pudéssemos aprimorar nosso aplicativo de métricas para exibir gráficos e relatórios mais
atraentes, forneceremos, em vez disso, uma maneira de exportar os dados armazenados no banco
de dados para que possam ser renderizados em um serviço ou ferramenta de terceiros.

A abordagem padrão para enviar dados do servidor para o cliente no Rails é usar o método
`send_data()` . No entanto, essa abordagem exige que geremos antecipadamente toda a string que
queremos enviar, o que pode levar tempo e exigir muita memória à medida que armazenamos mais
métricas em nosso banco de dados. Para resolver esse problema, vamos usar o streaming de dados,
o que nos permite enviar dados para o cliente em partes, sem alocar uma grande quantidade de
memória.

No Capítulo 5, Transmitindo Eventos do Servidor para Clientes Assincronamente, na página 83,


usamos os recursos de transmissão ao vivo do Rails para transmitir dados do servidor. Agora, vamos
nos basear exclusivamente na flexibilidade da especificação Rack para implementar esse recurso.
Nosso formato de streaming preferido é o CSV porque um cliente pode consumir os dados linha por
linha e o Ruby possui ferramentas integradas para converter dados em CSV.

Streaming Redux
A especificação Rack define que um corpo de resposta válido é qualquer objeto Ruby que responda
ao método `each()`. É por isso que usamos comumente arrays em aplicações Rack para armazenar
o corpo da resposta:

[Link]
errata do relatório • discutir
Machine Translated by Google

Streaming com Rack • 151

classe HelloRack
def call(env)
[200, { 'Content-Type' => 'text/html' }, ['Olá Rack!']] fim fim

O servidor web Rack percorrerá o corpo da resposta, usando o método `each()` , e exibirá os dados
retornados. Como o array responde ao método `each()`, ele retorna uma única vez com o valor “Hello Rack!” .

Este contrato de API significa que podemos implementar streaming no Rack simplesmente criando
um corpo de resposta personalizado que implementa um mecanismo de iteração personalizado
através do método `each()`. Veja como podemos implementar streaming usando apenas o Rack:

requer 'rack'

classe StreamingRack def


chamar(env)
[200, { 'Content-Type' => 'text/html' }, self] fim

def each
while true yield
'Olá Rack!\n' end end

fim
execute [Link]

Escreva o código anterior em um arquivo [Link] e execute o comando `rackup -s puma` para
iniciar o aplicativo Rack. Vamos usar o cURL novamente pela linha de comando e observar o
fluxo de dados através de:9

$ curl -v localhost:9292/

E pronto! Transmitir dados em fluxo contínuo com o Rack é bastante acessível. Pressione CTRL+C
para fechar o aplicativo Rack e o processo cURL.

Transmissor CSV

Vamos começar nossa implementação do CSV-streamer escrevendo um teste que acessa o


caminho das métricas com a extensão .csv e obtém o documento CSV de volta do servidor:

mongo_metrics/3_final/test/integration/navigation_test.rb teste
"exporta dados para csv" faça obter
main_app.home_foo_path obter
mongo_metrics.metrics_path(formato: :csv) assert_match
"process_action.action_controller," [Link] fim

9. [Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 7. Gerenciando eventos de aplicativos com mecanismos montáveis • 152

Como a rota que lida com a solicitação CSV já está definida, basta alterar o controlador
`MongoMetrics::MetricsController` para responder a solicitações CSV na ação `index`. Nosso
método `index()` já utiliza `respond_with()`, portanto, o seguinte deve ser suficiente para obter
uma resposta CSV:

mongo_metrics/3_final/app/controllers/mongo_metrics/metrics_controller.rb módulo
MongoMetrics classe
MetricsController < ApplicationController
respond_to :html, :json respond_to :csv,
only: :index

No entanto, lembre-se da discussão da Seção 6.2, Explorando ActionController::Responder, na


página 109; o Rails primeiro tentará renderizar um template, que não existe, e então recorrerá à
chamada de renderizador de CSV: @metrics. O problema aqui é que o Rails não implementa
um renderizador de CSV, então precisamos escrever um nós mesmos. Felizmente, discutimos
como escrever renderizadores no Capítulo 1, Criando Nosso Próprio Renderizador, na página 1.

Queremos que nosso renderizador de CSV envie um arquivo de volta para o usuário, transmitindo
cada bloco aos poucos. O renderizador também deve definir o corpo da resposta como um
objeto personalizado, que chamaremos de CSVStreamer. Vamos implementá-lo:

mongo_metrics/3_final/lib/mongo_metrics/csv_streamer.rb módulo
MongoMetrics
ActionController::[Link] :csv do |model, options|
cabeçalhos = [Link]
cabeçalhos["Content-Disposition"] =
%(attachment; filename="#{controller_name}.csv") headers["Cache-Control"] =
"no-cache" [Link] "Content-Length" self.content_type
||= Mime::CSV self.response_body =
[Link](model) end class CSVStreamer

def initialize(scope) @scope =


scope end

def cada

@[Link] do |record| yield


record.to_csv end

fim
fim
fim

Primeiro, definimos o cabeçalho Content-Disposition para informar ao navegador que estamos


enviando um anexo. Em seguida, desativamos o cache e removemos o cabeçalho Content-
Length, notificando o servidor Rack subjacente de que desejamos transmitir os dados. Finalmente,

[Link] errata do relatório • discutir


Machine Translated by Google

Streaming com Rack • 153

Atribuímos o tipo de conteúdo apropriado e definimos o corpo da resposta para o nosso objeto
CSVStreamer personalizado.

Observe que nosso objeto streamer chama `to_csv()` em cada registro de métrica, portanto, também
precisamos implementar `to_csv()` . Como o Ruby fornece uma biblioteca chamada `csv` para gerar
e consumir arquivos CSV em sua biblioteca padrão, vamos importá-la e usá-la para converter um
array com informações de métricas em CSV:

mongo_metrics/3_final/app/models/mongo_metrics/[Link] require
"csv" def to_csv
[name,
started_at, duration, instrumenter_id, created_at].to_csv end

O último passo para que nosso teste seja aprovado é importar o arquivo `mongo_metrics/csv_streamer`,
onde definimos nosso renderizador CSV personalizado, tornando-o disponível para nossos controladores:

mongo_metrics/3_final/lib/mongo_metrics.rb
require "mongo_metrics/csv_streamer"

Nosso novo teste foi aprovado! Embora a implementação do streaming de CSV com Rack seja um
pouco diferente de como faríamos usando ActionController::Live, ela sofre das mesmas limitações e
requer o mesmo cuidado na implantação. Conseguimos testar nosso endpoint porque não estávamos
fazendo streaming infinito, ao contrário do que acontecia no projeto do Capítulo 5, Streaming de
Eventos do Servidor para Clientes Assincronamente, na página 83.

Então, qual opção devemos escolher para implementar streaming: ActionController::Live ou Rack
streaming? Em geral, o streaming ao vivo é preferível. Como o Rack streaming depende da API
básica do Rack, um servidor web não sabe antecipadamente que tipo de resposta enviará — longa
(streaming) ou curta (normal) — o que dificulta a otimização desses cenários pelos servidores web.
O streaming ao vivo, por outro lado, é uma abstração do Rails que pode evoluir de forma transparente
a partir do código do desenvolvedor, à medida que o Rack aprimora a funcionalidade de streaming
subjacente. De qualquer forma, o streaming com Rack pode ser conveniente para pequenas
aplicações Rack que não têm acesso a todas as facilidades que o Rails oferece.

Essa funcionalidade específica poderia até ser implementada sem streaming, simplesmente usando
uma das muitas ferramentas de processamento em segundo plano disponíveis para o Rails, como
Delayed Job e Resque, para gerar o arquivo CSV em segundo plano e enviar um e-mail assim que
estiver pronto.10,11

10. [Link]
11. [Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 7. Gerenciando eventos de aplicativos com mecanismos montáveis • 154

7.6 Concluindo
Neste capítulo, desenvolvemos um mecanismo Rails que escuta todas as ações
processadas por um aplicativo e as armazena no MongoDB. Podemos visualizar essas
notificações acessando `/mongo_metrics` no navegador.

Nossa implementação foi construída sobre um mecanismo montável e isolado, o que nos
permite desenvolver nossos recursos isoladamente de um aplicativo Rails, garantindo que
não teremos conflitos nos locais mais prováveis, como rotas e helpers.
Também exploramos o Rack criando um middleware que nos permite silenciar o
armazenamento de métricas em áreas específicas do nosso plug-in e usando um corpo
Rack personalizado para transmitir dados.

Ainda há muito a ser feito em nosso plug-in no que diz respeito à visualização. Poderíamos,
por exemplo, permitir que o desenvolvedor classificasse as métricas por duração e
fornecesse gráficos. Ainda mais interessante, poderíamos usar as técnicas de streaming
que abordamos até agora para transmitir eventos para a página de visualização à medida
que acontecem.

Em seguida, vamos criar um aplicativo Rails que nos permita traduzir mensagens I18n
por meio de um aplicativo Sinatra autenticado com o Devise, uma biblioteca de
autenticação popular.

[Link]
errata do relatório • discutir
Machine Translated by Google

Neste capítulo, veremos


• O framework I18n • O
framework web Sinatra • O roteador
Rails • As gems
Devise (para autenticação) e Capybara (para testes de integração)

CAPÍTULO 8

Aplicativos de tradução

Utilizando back-ends de chave-valor

O framework de internacionalização (I18n) adicionado no Rails 2.2 desempenhou um papel


fundamental no aumento da adoção do Rails em todo o mundo. Embora seja fácil
disponibilizar um aplicativo em diferentes idiomas, o maior desafio é manter esses dados de
tradução. Algumas empresas contam com uma equipe de tradutores, enquanto outras optam
por uma abordagem colaborativa, permitindo que seus próprios usuários traduzam o
aplicativo web. Em ambos os casos, é comum desenvolver uma interface web para auxiliar
no processo de tradução.

Por padrão, o I18n armazena dados de tradução em arquivos YAML, que podem ser difíceis
de manipular pela interface web. De fato, usar YAML exigiria um mecanismo para instruir
todos os servidores a sincronizar e recarregar os arquivos YAML assim que fossem
atualizados. Como você pode imaginar, tal solução poderia se tornar complexa rapidamente.

Felizmente, o framework I18n oferece diferentes back-ends que nos permitem armazenar
traduções em locais que não sejam arquivos YAML. Isso facilita bastante a manipulação da
tabela de traduções por meio de uma interface web e a atualização das traduções do site
sob demanda. Não há necessidade de sincronizar arquivos YAML entre servidores web. Por
outro lado, recuperar as traduções do banco de dados em vez de um hash na memória tem
um grande impacto no desempenho.

Um banco de dados chave-valor é uma solução que atende tanto aos requisitos de
simplicidade quanto de desempenho. Neste capítulo, armazenaremos traduções em um
banco de dados Redis e usaremos um backend chave-valor para recuperá-las. Além disso, construiremos

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 8. Traduzindo Aplicativos Usando Back-Ends de Chave-Valor • 156

Uma aplicação Sinatra simples para expor uma interface web que permita ler, criar e atualizar essas
traduções em tempo real.1

Diferentemente dos capítulos anteriores, desenvolveremos toda essa funcionalidade como uma
aplicação Rails em vez de um plug-in. Após estudar e analisar railties e engines, agora podemos
construir aplicações Rails com uma perspectiva diferente.

8.1 Revisitando Rails::Application


Nos capítulos anteriores, discutimos o Rails::Engine e como ele exibe diversos comportamentos
semelhantes a uma aplicação Rails. Ao analisarmos o código-fonte do Rails, encontramos o seguinte:

módulo Rails
classe Application < Engine # ...

fim
fim

A classe Rails::Application herda de Rails::Engine! Isso significa que uma aplicação pode fazer tudo
o que um engine faz, além de ter alguns comportamentos específicos:

• Um aplicativo é responsável por toda a inicialização (por exemplo, carregar o Active Support,
configurar os caminhos de carregamento e configurar o registrador de logs).

• Um aplicativo possui seu próprio roteador e pilha de middleware (como discutimos).


(na Seção 7.4, Pilhas de Middleware, na página 143).

• Um aplicativo deve carregar e inicializar todos os plug-ins.

• Um aplicativo é responsável por recarregar o código e as rotas entre


pedidos se eles mudaram.

• Um aplicativo é responsável por carregar tarefas e geradores quando


apropriado.

Para analisar essas responsabilidades mais detalhadamente, vamos começar desenvolvendo o nosso
Aplicativo de tradução:

novo tradutor de trilhos

Quando estudamos o aplicativo fictício no Capítulo 1, Criando Nosso Próprio Renderizador,


na página 1, discutimos as responsabilidades dos arquivos config/[Link], config/
[Link] e config/[Link] . Em particular, o arquivo boot é responsável por
definir nossos caminhos de carregamento, o arquivo application define nosso aplicativo
Rails e o arquivo environment, finalmente, inicializa o aplicativo chamando o método initialize!() :

1. [Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Revisitando Rails::Application • 157

translator/1_app/config/[Link]
# Carregar a aplicação Rails. require
File.expand_path('../application', __FILE__)

# Inicialize a aplicação Rails.


Tradutor::[Link]!

Em Inicializadores, na página 86, mostramos como os mecanismos fornecem um conjunto de inicializadores


que controlam a inicialização do mecanismo. Não é surpresa que um aplicativo Rails também forneça esses
inicializadores:

módulo Tradutor
classe Application < Rails::Application
inicializador "translator.say_hello" faça puts "olá na
inicialização" fim

fim
fim

Para ver todos os inicializadores disponíveis em uma aplicação Rails, abra um console Rails em nossa
aplicação recém-gerada e digite o seguinte:

[Link](&:name)

A diferença aqui é que a aplicação contém não apenas seus próprios inicializadores, mas também os
inicializadores definidos em todos os railties e engines. Inicializar uma aplicação Rails é simplesmente uma
questão de executar esses inicializadores um por um.

Todo o resto em uma aplicação Rails é construído em torno dos arquivos boot, application e environment. Se
abrirmos o Rakefile, veremos o seguinte:

tradutor/1_app/Rakefile
require File.expand_path('../config/application', __FILE__)
Tradutor::Aplicativo.carregar_tarefas

Primeiro, o arquivo de configuração da aplicação é necessário, definindo a aplicação Rails. Em seguida, a


função `load_tasks()` é invocada, carregando todas as tarefas Rake fornecidas pela aplicação, pelos plugins
e pelo próprio Rails. Observe que não exigimos o arquivo de ambiente em nenhum momento. Isso permite que
os comandos básicos do Rake sejam executados rapidamente, já que eles não inicializam a aplicação; apenas
a definem.

No entanto, muitas tarefas exigem que a aplicação seja inicializada. Por exemplo, `rake db:migrate` só funciona se o banco de

dados estiver configurado. É por isso que o Rails fornece uma tarefa Rake chamada `:environment`; essa tarefa simplesmente

requer o arquivo `config/[Link]` para inicializar a aplicação. Sempre que você precisar acessar o banco de dados ou

qualquer classe da sua aplicação em uma tarefa Rake, você precisa depender da tarefa `:environment` .

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 8. Traduzindo Aplicativos Usando Back-Ends de Chave-Valor • 158

Por fim, vamos analisar o arquivo [Link] na raiz da nossa aplicação. Ele requer o
arquivo de ambiente, inicializando efetivamente a aplicação, e executa a aplicação Rails
atual como uma aplicação Rack:
translator/1_app/[Link]
# Este arquivo é usado pelos servidores baseados em Rack para iniciar o aplicativo.

require ::File.expand_path('../config/environment', __FILE__) run [Link]

O processo de inicialização da aplicação é dividido em vários arquivos, mas apenas


porque precisamos nos conectar a diferentes pontos. O arquivo [Link] precisa de
todo o ambiente de antemão, enquanto o Rakefile o carrega em etapas. No entanto,
nada nos impede de mesclar todos esses arquivos em uma única aplicação Rails!

O aplicativo Rails de arquivo único


Agrupar uma aplicação Rails em um único arquivo nos ajuda a entender como configurar
e inicializar o Rails. Vamos analisar um exemplo de aplicação Rails em um único arquivo
e discuti-lo a seguir. Crie um diretório vazio e adicione um arquivo `[Link]` com o
seguinte conteúdo:

tradutor/[Link]
# Simplesmente usaremos o rubygems para definir nossos caminhos de
carregamento require "rubygems"
# Requer nossas dependências require
"rails" require
"active_support/railtie" require "action_dispatch/
railtie" require "action_controller/railtie"

class SingleFile < Rails::Application # Configurar o


ambiente de produção config.eager_load = true
config.cache_classes = true

# É necessária uma chave base para que nosso aplicativo seja


inicializado. config.secret_key_base = "pa34u13hsleuowi1aisejkez12u39201pluaep2ejlkwhkj"

# Defina uma rota básica


[Link] do root
to: lambda { |env| [200, { "Content-
Type" => "text/plain" }, ["Hello world"]]

} fim
fim

[Link]! execute
[Link]

[Link]
errata do relatório • discutir
Machine Translated by Google

I18n Extremidades e Partes Traseiras • 159

Podemos inicializar esta aplicação executando o comando `rackup` no mesmo diretório do arquivo ` [Link]`.
Abra seu navegador em `localhost:9292` e você deverá ver a mensagem "Olá, mundo!".

Uma aplicação Rails de arquivo único não difere muito de uma aplicação Rails comum. Ela
configura o caminho de carregamento, neste caso usando o RubyGems em vez do Bundler. Em
seguida, carrega todas as dependências uma a uma, em vez de usar ` require "rails/all"`,
geralmente encontrado no início do arquivo `config/[Link]`. Finalmente, define, inicializa e
executa a aplicação.

Note que o Rails exige que definamos algumas opções de configuração, como `
consig.secret_key_base`. Tudo isso já nos é familiar; o único método novo usado neste arquivo é
`[Link]`.

Em geral, o único método que os desenvolvedores Rails acessam no roteador é o draw(),


encontrado no arquivo config/[Link] :

Translator::[Link] do
# ...
fim

O método `draw()` foi projetado para funcionar com recarregamento de código. Sempre que um
arquivo de rotas é alterado, todas as rotas desenhadas anteriormente são apagadas e
redesenhadas do zero, recarregando todas as entradas do arquivo `config/[Link]` na aplicação
e nos plugins. No entanto, em alguns casos, algumas rotas podem ser definidas durante a
inicialização ou em um arquivo que nunca é recarregado. Para esses cenários, o Rails oferece os
métodos `[Link]` e `[Link]` para definir rotas persistentes.

Por exemplo, se você usar [Link] para definir rotas dentro de config/[Link], que não
é recarregado em desenvolvimento, assim que suas rotas forem recarregadas porque você alterou
algo em config/[Link], as rotas definidas no aplicativo serão efetivamente perdidas.

Uma requisição para esta aplicação Rails de arquivo único funciona como em qualquer outra
aplicação Rails. O servidor web invoca o método `SingleFile#call()` , passando por uma pilha de
middleware que termina com o roteador. No nosso caso, o roteador simplesmente encontra uma
correspondência na ação raiz para uma aplicação Rack personalizada.

Agora que entendemos as responsabilidades do aplicativo e como ele é construído sobre railties
e engines, é hora de voltar ao aplicativo Translator e criar nosso back-end de tradução usando a
API I18n.

8.2 I18n Extremidades Traseiras e Extensões

Sempre que invocamos [Link]() (também conhecido como I18n.t()) ou [Link]()


(também conhecido como I18n.l()) em nossa aplicação, estamos delegando esses métodos para o I18n.

[Link]
errata do relatório • discutir
Machine Translated by Google

Capítulo 8. Traduzindo Aplicações Usando Back-Ends de Chave-Valor • 160

O backend é armazenado em `[Link]()`. Ao substituir esse backend, você pode modificar completamente
o funcionamento da biblioteca I18n. O framework I18n é fornecido com três backends diferentes:

• I18n::Backend::Simple: Mantém as traduções em um hash na memória.


A partir de arquivos YAML; este é o backend padrão.

• I18n::Backend::KeyValue: Usa qualquer armazenamento de chave-valor como backend, desde que


está em conformidade com uma API mínima.

• I18n::Backend::Chain: Permite encadear vários back-ends; em outras palavras, se uma tradução não for
encontrada em um back-end, ela será procurada no próximo back-end da cadeia.

O Rails depende de muitos recursos fornecidos pelo I18n. Por exemplo, em nosso aplicativo de tradução,
podemos ver a seguinte linha em config/environments/[Link]:

[Link] = true

Sempre que essa opção de configuração estiver definida como verdadeira, o Rails configura o framework I18n
para incluir a funcionalidade de fallback no backend atual, permitindo que qualquer pesquisa recorra à localidade
padrão caso uma tradução não seja encontrada na localidade atual. Se você estiver usando o I18n fora de uma
aplicação Rails, também poderá usar o comportamento de fallback com uma única linha de código:

[Link](:include, I18n::Backend::Fallbacks)

Outra funcionalidade de internacionalização (I18n) utilizada pelo Rails é o suporte à transliteração. A


transliteração que acompanha o Rails permite substituir caracteres latinos acentuados por seus correspondentes
não acentuados, como mostrado aqui:

[Link]("obrigado " ) # => ÿ"obrigado"


ÿ

Se precisar transliterar caracteres hebraicos, cirílicos, chineses ou outros, você pode adicionar novas regras de
transliteração sob demanda. Lembre-se de que as regras de fallback e as transliterações não são um recurso
do backend, mas sim uma das várias extensões listadas aqui, fornecidas pela biblioteca I18n:

• I18n::Backend::Cache: Usa um armazenamento em cache na frente de I18n.t para armazenar traduções.

resultados; ou seja, a sequência de caracteres após a pesquisa, interpolação e pluralização.

• I18n::Backend::Cascade: Encadeia pesquisas removendo escopos aninhados da chave de pesquisa; em


outras palavras, se :"[Link]" não for encontrado, ele automaticamente pesquisa por :"[Link]".

• I18n::Backend::Fallbacks: Fornece alternativas de localização, recorrendo à localização padrão.


especificar a localidade caso não seja possível encontrar uma tradução na localidade atual.

[Link]
errata do relatório • discutir
Machine Translated by Google

I18n Extremidades e Partes Traseiras • 161

• I18n::Backend::Gettext: Oferece suporte a arquivos gettext e .po .

• I18n::Backend::InterpolationCompiler: Compila chaves de interpolação (como %{model}) em dados de


tradução para acelerar o desempenho.

• I18n::Backend::Memoize: Armazena em memória os resultados da pesquisa; ao contrário de I18n::Back-


end::Cache, utiliza um hash na memória e é útil se você estiver usando o backend de chave-valor.

• I18n::Backend::Metadata: Adiciona metadados (como contagem de pluralização e


valores de interpolação) para resultados de tradução.

• I18n::Backend::Pluralization: Adiciona suporte a regras de pluralização em


:"[Link]".

• I18n::Backend::Transliterator: Adiciona suporte a regras de transliteração (conforme discutido anteriormente)


em :"[Link]".

A biblioteca I18n fornece diversos back-ends e extensões para diferentes áreas, como melhorar o desempenho
ou adicionar mais flexibilidade para linguagens com necessidades específicas, como pluralização personalizada.
Neste capítulo, usaremos apenas dois deles: I18n::Backend::KeyValue e I18n::Backend::Memoize.

O backend de chave-valor para I18n pode aceitar qualquer objeto como armazenamento, desde que esteja em
conformidade com a seguinte API:

• @store[]: Um método para ler um valor a partir de uma chave •


@store[]=: Um método para definir um valor a partir de uma chave
• @[Link]: Um método para recuperar todas as chaves armazenadas

Como fornecer uma API compatível é trivial, quase todos os armazenamentos de chave-valor podem ser
usados com este backend. Neste capítulo, vamos usar o Redis, pois ele está geralmente disponível e é
amplamente utilizado em produção.²

Após o Redis estar instalado e em execução, vamos integrá-lo à nossa aplicação Rails adicionando a gem `
redis` , uma biblioteca cliente em Ruby puro para o Redis, ao nosso Gemfile:

tradutor/1_app/Gemfile gem
'redis', '~> 3.0.3'

Em seguida, instale a gem adicionada:

instalação do pacote

Agora, vamos abrir um console do Rails com o comando `rails console` e verificar se o Redis está em
conformidade com a API esperada pelo I18n:

2. [Link] 3.
[Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 8. Traduzindo Aplicativos Usando Back-Ends de Chave-Valor • 162

db = [Link]
db["foo"] = "bar" db["foo"] #
=> bar [Link] # => ["foo"]

Voltando à nossa configuração de internacionalização (I18n), vamos criar um arquivo chamado lib/
[Link], que será responsável por configurar uma instância do Redis apontando para o banco
de dados apropriado (o banco de dados é referenciado como um inteiro no Redis). Vamos também
criar um backend personalizado de chave-valor que inclui o módulo I18n::Backend::Memoize para
armazenar em cache as consultas e usa o armazenamento Redis na inicialização:

translator/1_app/lib/[Link]
módulo Tradutor
BANCOS DE DADOS
= { "desenvolvimento" => 0,
"teste" => 1,
"produção" => 2
}

def [Link]
@store ||= [Link](db: DATABASES[[Link].to_s]) end

classe Backend < I18n::Backend::KeyValue incluir


I18n::Backend::Memoize

def inicializar
super([Link]) fim

fim
fim

Em seguida, vamos configurar o framework I18n para usar nosso novo back-end em
Tradutor::Aplicativo:

translator/1_app/config/[Link]
módulo Tradutor
classe Application < Rails::Application
# Configurar o backend do tradutor para I18n
require "translator"
[Link] = Translator::[Link]

Ao contrário do backend I18n padrão, o backend chave-valor não carrega as traduções de arquivos
YAML antes de cada requisição, mas sim sob demanda (pois seria lento). Dito isso, para armazenar
todas as traduções padrão em nosso armazenamento Redis, basta executar o seguinte comando
em um terminal:

$ rails runner "[Link].load_translations"

[Link] errata do relatório • discutir


Machine Translated by Google

Trilhos e Sinatra • 163

Ao iniciarmos o console do Rails novamente, podemos acessar todas as novas traduções armazenadas em nosso
armazenamento Redis:

db = [Link]
[Link]
db["[Link]"] # => "não pode estar em branco"
db["[Link]"] # => "{\"format\":{\"delimiter\":\"\"}}"

Observe que o armazenamento de chave-valor codifica automaticamente os valores para a Notação de Objetos
JavaScript (JSON).

8.3 Trilhos e Sinatra


Com as traduções devidamente armazenadas, agora podemos escrever nosso aplicativo de tradução usando
Sinatra. Sinatra é uma linguagem de domínio específico (DSL) para criar rapidamente aplicativos web em Ruby
com o mínimo de esforço. O "Olá, mundo!" consiste em apenas algumas linhas de código:

# [Link]
require 'sinatra' get '/' do
'Olá mundo!' end

Não acessaremos o aplicativo Sinatra diretamente, mas o integraremos ao nosso aplicativo Rails. Isso nos permite
reutilizar toda a estrutura que já temos no ecossistema Rails, como testes, sessões, autenticação e assim por
diante. Antes de desenvolvermos nosso aplicativo Sinatra, vamos escrever um teste de integração novamente
usando o Capybara para tornar nossos testes mais robustos e legíveis. Primeiro, vamos definir
`ActiveSupport::IntegrationCase` dentro do nosso arquivo `test/test_helper.rb`, que inclui a DSL do Capybara:

translator/1_app/test/test_helper.rb
requer "capivara" requer
"capivara/rails"
# Define um caso de teste básico para usar com a classe Capybara
ActiveSupport::IntegrationCase < ActiveSupport::TestCase
include Capybara::DSL include
[Link].url_helpers end

Nossa definição de caso de teste é exatamente a mesma que usamos na Seção 2.2, Testes de Integração com
Capybara, na página 28. Agora, adicione o Capybara ao Gemfile:

tradutor/1_app/Gemfile
grupo :test do gem
'capivara', '~> 2.0.0' end

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 8. Traduzindo Aplicativos Usando Back-Ends de Chave-Valor • 164

Nosso teste tenta localizar uma data usando a localidade polonesa, mas falhará porque não temos dados de
tradução para essa localidade. Em seguida, devemos acessar a URL de tradução /translator/en/pl, o que
significa que queremos traduzir mensagens do inglês para o polonês, preencher o campo de tradução
apropriado e armazenar essa nova tradução. Depois disso, podemos afirmar que nossa tradução foi
armazenada com sucesso, pois conseguimos localizar uma data. A implementação é a seguinte:

translator/1_app/test/integration/translator_app_test.rb requer
"test_helper"

classe TranslatorAppTest < ActiveSupport::IntegrationCase


# Configurar o armazenamento e o carregamento das traduções padrão
setup { [Link]! }

teste "pode traduzir mensagens de uma determinada localidade para outra" faça
assert_raise I18n::MissingTranslationData do I18n.l([Link](2010, 4,
17), locale: :pl) end

visite "/translator/en/pl" preencha


"[Link]", com: %{"%d-%m-%Y"} clique no botão "Salvar traduções"

assert_match "Traduções armazenadas com sucesso!", [Link] assert_equal "17-04-2010",


I18n.l([Link](2010, 4, 17), locale: :pl) end end

Nossa configuração de teste invoca um método chamado [Link]!(). Este método será responsável
por remover todas as chaves do banco de dados e recarregar os dados de tradução. Vamos implementá-lo a
seguir:

translator/1_app/lib/[Link] def
[Link]!
[Link]
[Link].load_translations fim

Nossos testes estão prontos para serem executados com `rake test`, mas falham porque nossa aplicação
Sinatra ainda não foi compilada. Portanto, vamos adicionar o Sinatra e o Haml ao Gemfile do nosso projeto (e
instalar essas novas dependências com `bundle install`):

tradutor/1_app/Gemfile gem
'sinatra', '~> 1.4.2', require: 'sinatra/base' gem 'haml', '~> 4.0.2'

Nossa aplicação Sinatra deve definir uma rota como /:from/:to, que, ao ser acessada, renderiza uma
visualização com todos os dados de tradução disponíveis na localidade :from .

[Link] errata do relatório • discutir


Machine Translated by Google

Trilhos e Sinatra • 165

Pronto para ser traduzido para a localidade :to . Nossa primeira iteração de código para o aplicativo Sinatra é mostrada aqui:

translator/1_app/lib/translator/[Link]
módulo Tradutor
classe App < Sinatra::Base
definir :environment, [Link]
habilitar :inline_templates

obter "/:de/:para" faça |de, para|


exibir_traduções(de, para) fim

protegido

# Armazena os idiomas de origem e destino em variáveis e recupera # todas as chaves


disponíveis para tradução. def exhibit_translations(from, to)

@from, @to, @keys = from, to, available_keys(from) haml :index

fim

# Obtenha todas as chaves para uma localidade. Remova a localidade da chave e ordene-as.
# Se uma chave for nomeada "[Link]", este método a retornará como "[Link]". def available_keys(locale) keys =
[Link]("#{locale}.*") range
= [Link]([Link] + 1, -1) [Link] { |k| [Link](range) }.sort! end

# Obtém o valor no repositório de tradução para uma determinada localidade. Este método # decodifica os valores e
verifica se são um hash, pois não queremos subárvores # disponíveis para tradução, já que elas são gerenciadas
automaticamente pelo I18n. def locale_value(locale, key)

valor = [Link]["#{locale}.#{key}"] valor se valor && !


ActiveSupport::[Link](valor).is_a?(Hash) fim

fim
fim
__FIM__

@@ índice !!!

%html
%head
%title
Translator::App %body
%h2=
"De #{@from} para #{@to}"

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 8. Traduzindo Aplicativos Usando Back-Ends de Chave-Valor • 166

%p(style="color:green")= @message

- se @[Link]?
Nenhuma tradução disponível para #{@from} - else

%form(method="post" action="") - @[Link] do |


key| - from_value =
locale_value(@from, key) - next unless from_value - to_value =
locale_value(@to, key) || from_value
%p %label(for=key) %small= key = from_value %br

%input(id=key name=key type="text" value=to_value size="120")


%p
%input(type="submit" value="Armazenar traduções")

Há alguns pontos a serem discutidos nesta implementação. Primeiro, observe que


encaminhamos explicitamente o ambiente Rails para o ambiente da aplicação Sinatra. Em
seguida, definimos a rota `/:from/:to` disponível através do método de requisição `GET`. Se
uma rota corresponder, o Sinatra retornará ambos os parâmetros para o bloco, que será
executado. O bloco simplesmente invoca `exhibit_translations()`, que atribui esses parâmetros
a variáveis de instância, obtém todas as chaves de localidade disponíveis para tradução e
renderiza o template de índice.

Neste caso, optamos por usar Haml como marcação de modelo para a página inicial.4 O
modelo consiste em apenas algumas linhas de código e foi definido no mesmo arquivo da
aplicação por meio do recurso de modelos embutidos do Sinatra, que habilitamos no início da
aplicação. No entanto, é importante observar que os modelos são avaliados no mesmo
contexto da aplicação. Isso significa que qualquer método definido em nossa aplicação Sinatra
também está disponível no modelo, assim como as variáveis de instância da aplicação. Essa
abordagem é diferente da do Rails, porque os modelos do Rails não são avaliados no mesmo
contexto dos controladores, mas sim em um contexto de visualização específico. Portanto, o
Rails precisa copiar todas as variáveis de instância dos controladores para as visualizações
nos bastidores, como vimos na Seção 1.3, Compreendendo a Pilha de Renderização do Rails,
na página 9, e os métodos do controlador devem ser chamados explicitamente como
[Link]().

Por fim, observe que nosso modelo chama o método `locale_value()` . Esse método recebe
uma localidade e uma chave e retorna o valor armazenado no Redis. Esse método deve

4. Haml significa HTML Abstraction Markup Language (Linguagem de Marcação de Abstração HTML), e você pode encontrar alguns exemplos.

em [Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Trilhos e Sinatra • 167

Também lida com hashes, que são criados e armazenados por padrão pelo framework I18n, para permitir que você
recupere subárvores de back-ends.

Em I18n, sempre que você armazena uma tradução { "[Link]" => "baz" }, ela decompõe a chave "[Link]" e
armazena { "foo" => { "bar" => "baz"} } como a tradução. Isso permite que você recupere a tradução específica com
I18n.t("[Link]") #=> "bar" ou um hash de subárvore com I18n.t("foo") #=> { "bar" => "baz" }. Dito isso, se
mostrarmos hashes em nossa interface Sinatra, várias traduções seriam duplicadas porque apareceriam no hash

de subárvore na chave foo ou na chave completa [Link].

Antes de testarmos nossa aplicação Sinatra, vamos carregá-la automaticamente a partir de lib/[Link],
como mostrado aqui:

translator/1_app/lib/[Link]
autoload :App, "translator/app"

E, finalmente, vamos instalá-lo no roteador em "translator":

translator/1_app/config/[Link]
Translator::[Link] do
montar Translator::App, em: "/translator" fim

Vamos verificar se agora funciona, iniciando o servidor usando o comando `rails server` como de costume e
acessando `/translator/en/pl` no navegador. Obtemos uma página de tradução semelhante à da figura a seguir.

Figura 17 — Aplicativo de tradução

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 8. Traduzindo Aplicativos Usando Back-Ends de Chave-Valor • 168

Esta página configura automaticamente uma página para traduzir uma mensagem do inglês para o
polonês, mas não clique no botão "Enviar" ainda; ainda não implementamos o comportamento
POST. Aliás, quando executamos os testes novamente, eles falham por esse motivo. Clicar no
botão nos testes de integração retorna a mensagem "Nenhuma rota corresponde".
erro:

1) Erro:
test_can_translate_messages_from_a_given_locale_to_another(TranslatorAppTest)
ActionController::RoutingError: Nenhuma rota corresponde a [POST] "/translator/en/pl"

Para que o teste seja aprovado, vamos adicionar uma nova rota ao Sinatra para solicitações POST.
Essa nova rota deve armazenar a tradução no backend I18n, passando a localidade de destino e
as traduções decodificadas de JSON para Ruby; chamar o método save() no armazenamento
Redis, forçando o despejo dos dados no sistema de arquivos; e exibir a página de tradução
novamente:

translator/2_final/lib/translator/[Link] post
"/:from/:to" do |from, to|
[Link].store_translations para, decoded_translations, escape: false [Link]

@message = "Traduções armazenadas com sucesso!" exhibit_translations(from,


to) end protected # Obtém todas as traduções

enviadas pelo
formulário e decodifica # seus valores JSON para verificar a validade. def decoded_translations
translations = [Link]("from", "to") [Link] do |
key, value|

translations[key] = ActiveSupport::[Link](value) rescue nil end end

Observe que definimos ` :escape` como `false` ao armazenar traduções para que o I18n possa
gerar subárvores corretamente. Por padrão, se você fornecer uma tradução como `{ "[Link]" =>
"baz" }`, o I18n a tratará como uma única chave, escapada como `{ "foo\[Link]" => "baz" }`.
Quando armazenada dessa forma, não podemos recuperar sua subárvore como `I18n.t("foo").`. No
entanto, se desativarmos o escape, o I18n dividirá a chave, convertendo-a em `{ "foo" => { "bar" =>
"baz" } }` e permitindo que a recuperemos como `I18n.t("foo")` ou `I18n.t("[Link]").`

Fique à vontade para reiniciar o servidor e traduzir todos os dados de qualquer idioma para outro!
Observe que optei por representar os dados em formato JSON na interface, pois podemos
representar facilmente arrays, strings, números ou valores booleanos.

Neste momento, todos os nossos testes estão passando! Nosso aplicativo de tradução está quase
pronto; agora é hora de adicionar autenticação com o Devise e melhorar a robustez dos nossos
testes com o Capybara!

[Link] errata do relatório • discutir


Machine Translated by Google

Levando a experiência a um novo patamar com Devise e Capybara • 169

8.4 Levando a experiência a um novo patamar com Devise e Capybara

Se alguma de nossas aplicações fornecer uma interface para traduções, devemos garantir que essa interface seja
protegida por senha e que possamos testar adequadamente sua funcionalidade. Nesta seção, vamos analisar o

Devise,5 uma solução de autenticação completa baseada em Rack, e examinar mais detalhadamente como podemos
usar o Capybara para testar aplicações Rack.6

Adicionando autenticação entre aplicações: O Devise é uma

solução interessante para autenticação porque fornece, em pouquíssimas linhas de código, uma pilha completa de
autenticação, com login, cadastro, recuperação de senha e muito mais. Ele usa o Warden para mover o tratamento de
autenticação para a pilha de middleware, permitindo que qualquer aplicação, seja ela Sinatra ou um controlador Rails,

use as mesmas regras de autenticação.

Para adicionar o Devise ao nosso aplicativo de tradução, primeiro precisamos adicioná-lo ao nosso Gemfile e executar
o comando `bundle install` para instalá-lo:

tradutor/2_final/Gemfile gem
'devise', '~> 3.0.0'

Com a gem instalada em nossa máquina, precisamos invocar o gerador devise:install :

$ rails g devise:install

O gerador copia para nossa aplicação um arquivo de localização e um inicializador com diversas opções de
configuração. Ao final, ele também imprime alguns passos que precisamos executar manualmente.

O primeiro passo é configurar o Action Mailer para desenvolvimento:

translator/2_final/config/environments/[Link]
config.action_mailer.default_url_options = { host: 'localhost:3000' }

Em seguida, adicionamos mensagens flash ao nosso layout:

translator/2_final/app/views/layouts/[Link] <p
class="notice"><%= notice %></p> <p class="alert"><%=
alert %></p>

E, finalmente, adicionamos uma rota raiz:

translator/2_final/config/[Link] raiz
para: "home#index"

5. [Link] 6.
[Link] 7.
[Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 8. Traduzindo Aplicações Usando Back-Ends de Chave-Valor • 170

Como nossa rota raiz aponta para um HomeController, vamos implementar esse controlador.
Por enquanto, a ação de indexação apenas gera um link para o aplicativo Sinatra montado:

translator/2_final/app/controllers/home_controller.rb class
HomeController < ApplicationController def index render inline:

"<%= link_to 'Traduzir do inglês para o polonês', '/translator/en/pl' %>"


fim
fim

Com a configuração concluída, estamos prontos para criar nosso primeiro modelo Devise, chamado Admin:

$ rails g devise Admin

Em seguida, execute a migração adicionada pelo gerador:

$ bundle exec rake db:migrate

Neste ponto, não fizemos nenhuma alteração significativa em nossa aplicação, mas se executarmos nossos
testes de integração, eles falharão. Isso ocorre porque os fixtures gerados para o Admin precisam ser
preenchidos corretamente. No entanto, como não precisaremos desses fixtures por enquanto, vamos
excluir o arquivo de fixtures em test/fixtures/[Link] para que nossos testes passem novamente.

Para ver como o Devise funciona com nossa aplicação, fique à vontade para iniciar um novo servidor,
acessar /admins/sign_up, criar uma nova conta de administrador e fazer login. Você também pode acessar /
admins/edit se quiser alterar sua conta (embora seja recomendável desativar essa opção de cadastro antes
de implantar o aplicativo).

O Devise oferece diversas funções auxiliares para restringir o acesso a controladores do Rails. Como
criamos um modelo chamado Admin, podemos usar `authenticate_admin!()` como um filtro `before`, e a
requisição só prosseguirá se um modelo admin estiver autenticado:

class PostsController < ApplicationController before_filter :authenticate_admin!


end

No entanto, queremos adicionar autenticação ao nosso aplicativo Sinatra, algo que o Devise não inclui em
sua biblioteca auxiliar. Felizmente, isso ainda é trivial de se fazer com o Devise graças ao Warden. Sempre
que invocamos `authenticate_admin!()` em um controlador Rails, o seguinte código é executado:

env["warden"].authenticate!(scope: "admin")

O objeto `env["warden"] ` é um middleware proxy do Warden criado, e o Devise adiciona esse middleware
à pilha de middlewares do Rails por meio de um ` Rails::Engine`. Como esse middleware é executado antes
que a requisição chegue ao roteador, o proxy

[Link] errata do relatório • discutir


Machine Translated by Google

Levando a experiência a um novo patamar com Devise e Capybara • 171

O objeto também está disponível no Sinatra, e podemos facilmente adicionar autenticação a ele.
Translator::App em um retorno de chamada "before":

translator/2_final/lib/translator/[Link]
antes de fazer
env["warden"].authenticate!(scope: "admin") end

Em resumo, nossa solicitação passa por nossa aplicação e pilha de middleware, conforme mostra a figura a
seguir.

Figura 18 — A pilha de middleware e os aplicativos Rack envolvidos em uma solicitação para um


aplicativo Rails com Sinatra, Warden e Devise.

Agora, quando você solicita o aplicativo Sinatra sem um administrador conectado, o filtro `before` lançará
um erro. O middleware Warden captura esse erro.

Utilizando o operador throw/ catch do Ruby , o Warden redireciona você para a página de login dentro do
Devise. Após o login, o código anterior simplesmente retorna o administrador atual da sessão, prosseguindo
com a requisição Sinatra.

Embora essa abordagem nos permita usar o mesmo mecanismo de autenticação em diferentes aplicações
Rack, ela apresenta um problema: exige que alteremos a aplicação Sinatra adicionando um filtro `before`.
Dito isso, se estivermos usando uma aplicação Sinatra de terceiros, como a fornecida na gem Resque,8 não
poderemos modificá-la.

8. [Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Capítulo 8. Traduzindo Aplicações Usando Back-Ends de Chave-Valor • 172

Neste caso, poderíamos garantir a autenticação no nível do roteador sem precisar alterar o aplicativo Sinatra,

como mostrado aqui:

autenticar "admin" faça


montar Translator::App, em: "/translator" fim

O Devise adiciona o método authenticate(), mostrado anteriormente, ao roteador do Rails, e simplesmente


usa a API de restrições do roteador para garantir que a função "admin" esteja autenticada.

Vamos verificar a implementação do método no código-fonte do Devise:

def authenticate(scope) constraint =


lambda do |request| [Link]["warden"].authenticate!
(:scope => scope) end

restrições(restrição) façam ceder fim

fim

Independentemente de escolhermos um filtro "before" ou uma restrição de roteador para exigir autenticação
em nossa aplicação Sinatra, podemos verificar se o backend do tradutor está seguro executando novamente
nosso conjunto de testes e observando se ele falha.

1) Erro:
test_can_translate_messages_from_a_given_locale_to_another(TranslatorAppTest)
Capybara::ElementNotFound: Não foi possível encontrar o campo "[Link]"

O teste não consegue encontrar o rótulo "[Link]" fornecido para fill_in() porque está exibindo a
página /admin/sign_in em vez da página de traduções. Para corrigir isso, vamos autenticar um administrador
em nosso teste de integração usando um gancho de configuração:

translator/2_final/test/integration/translator_app_test.rb
configuração { login(admin) }

def admin
@admin ||= [Link]!( email:
"admin_#{[Link]}@[Link]", password: "12345678"

) fim

def sign_in(admin) visit "/


admins/sign_in" fill_in "Email", fill_in
"Password", with: com: [Link]
[Link] click_button "Sign in" end

[Link] errata do relatório • discutir


Machine Translated by Google

Levando a experiência a um novo patamar com Devise e Capybara • 173

Com esse código implementado, vamos executar nosso conjunto de testes novamente e ver se ele passa!
Observe que optamos por fazer o login manual do administrador preenchendo o formulário, em vez de usar uma
gambiarra que modifica a sessão ou passa um cookie. Aliás, mesmo que quiséssemos modificar a sessão ou um
cookie, o Capybara não nos permitiria fazer isso — por um bom motivo, como veremos a seguir.

Adicionando testes de compatibilidade entre navegadores

Sempre que usamos o Capybara ao longo do livro, criamos nosso próprio caso de teste chamado
ActiveSupport::IntegrationCase em vez de usar ActionController::IntegrationTest:

# Define um caso de teste básico para usar com a classe Capybara


ActiveSupport::IntegrationCase < ActiveSupport::TestCase
include Capybara include
[Link].url_helpers end

Ao escrever testes de integração do Rails usando `ActionController::IntegrationTest`, temos acesso completo aos
objetos brutos de requisição e resposta, o que nos permite verificar e manipular cookies, sessões, cabeçalhos e
assim por diante. O Capybara, por outro lado, possui uma API muito fechada que não expõe esses recursos. Dito
isso, se simplesmente incluíssemos o Capybara em `ActionController::IntegrationTest`, seríamos tentados a
acessar e manipular esses objetos, o que levaria a problemas tanto conceituais quanto práticos.

problemas.

Vamos discutir um pouco as questões conceituais. O Capybara foi projetado para nos permitir escrever testes de
integração a partir da perspectiva do usuário final. Por exemplo, imagine que estamos criando um site de
comércio eletrônico que armazena no rodapé os últimos cinco produtos visualizados. Se nossa implementação
simplesmente armazena esses IDs de produto na sessão, um teste de integração ingênuo apenas verificaria se,
após acessar a página de um produto, o ID do produto foi adicionado à sessão.

O problema com esse tipo de teste é que o usuário do e-commerce não se importa se

Algo foi armazenado na sessão. O usuário apenas deseja visualizar os últimos produtos visitados no rodapé e poder
clicar neles, algo que não verificamos em nossos testes.

Além disso, o fato de armazenarmos essas informações na sessão é um detalhe de implementação. Se, em
algum momento, decidirmos manter esses dados em um cookie, nosso teste ingênuo falhará, mas deveria passar,
já que a interface do usuário não foi alterada. Esse é um sintoma comum em testes muito acoplados à sua
implementação.

Por esse motivo, o Capybara esconde todos esses componentes internos de você, o que é ótimo, considerando
que uma das características mais importantes do Capybara é justamente essa:

[Link]
errata do relatório • discutir
Machine Translated by Google

Capítulo 8. Traduzindo Aplicações Usando Back-Ends de Chave-Valor • 174

Ele suporta diferentes drivers. Os drivers do Capybara manipulam um navegador, que então acessa nosso
aplicativo por meio de um servidor web, como mostra a figura a seguir.

Figura 19 — Rastreamento de chamadas ao usar o Capybara com um driver Selenium e o Firefox

Alguns drivers, como o Selenium, usam navegadores comuns (Firefox, Internet Explorer e Chrome), enquanto
outros interagem com um navegador sem interface gráfica, como o PhantomJS.

Como você pode imaginar, cada navegador compatível com o Selenium precisa expor uma API limitada.
Alguns podem permitir o acesso a cookies; outros, não. Alguns navegadores headless podem oferecer controle
total sobre os cabeçalhos de requisição, mas outros não. Para permitir que você troque de drivers e
navegadores sem precisar reescrever grande parte dos seus testes de integração, o Capybara se concentra
no conjunto comum de recursos suportados pela maioria deles.

Por padrão, o Capybara usa o driver de teste Rack, que ignora todo o navegador e acessa o aplicativo Rack
diretamente. Isso é muito conveniente em termos de desempenho, mas também muito limitado. Por exemplo,
qualquer recurso que dependa de JavaScript não pode ser testado com o driver padrão. Felizmente, podemos
facilmente alterar nossos aplicativos para usar outro driver. Vamos experimentar o Selenium em nosso
aplicativo adicionando as seguintes linhas ao nosso auxiliar de teste:9

translator/2_final/test/test_helper.rb
requer "selenium-webdriver"

# Pode ser :chrome, :firefox ou :ie


Selenium::[Link] :firefox
Capivara.default_driver = :selenium

class ActiveSupport::TestCase # Desativar


fixtures transacionais para testes de integração self.use_transactional_fixtures = false end

9. [Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Concluindo • 175

O Selenium usa navegadores reais para testar nossa aplicação. Por padrão, ele usa o Firefox 10 , que você
precisa ter instalado antes de executar os testes novamente. Depois de instalá-lo, execute nossos testes de
integração e observe que o Selenium inicia o Firefox automaticamente e o direciona para o nosso site! No final,
nossos testes ainda são aprovados!

Como o Selenium precisa acessar um servidor web para cada requisição, o Capybara inicia um automaticamente.
Como o Capybara inicia esse novo servidor web em uma thread, a conexão com o banco de dados usada nos
testes não é a mesma que o servidor usa em cada requisição. Dito isso, se usarmos fixtures transacionais para

envolver cada teste em uma transação de banco de dados, os dados criados nos testes não estarão disponíveis
no banco de dados.
o servidor, visto que os dados transacionais não são compartilhados entre conexões de banco de dados

até que seja confirmado. É por isso que precisamos desativar os fixtures transacionais em nosso arquivo test/
test_helper.rb , mesmo que isso reduza o desempenho.

Outra consequência de desabilitar fixtures transacionais é que os dados armazenados em nosso banco de
dados não são limpos entre os testes, e isso certamente atrapalhará à medida que adicionarmos novos testes
à nossa suíte. Felizmente, algumas soluções fazem todo esse trabalho para nós. Uma que se destaca é o
Database Cleaner,¹¹ pois oferece suporte a diferentes mapeadores objeto-relacionais e bancos de dados.

8.5 Concluindo
Neste capítulo, criamos mais uma aplicação Rails e aproveitamos a oportunidade para descrever melhor como
as aplicações Rails são estruturadas e projetadas. Mais uma vez, vimos a importância da especificação Rack
e como ela facilita a integração entre diferentes frameworks (como Rails e Sinatra) sem complicações. Também
falamos mais sobre Internacionalização (I18n), seus back-ends e suas extensões. Além disso, encontramos
um bom exemplo de uso de um banco de dados chave-valor simples como o Redis.

Por fim, discutimos duas ferramentas muito utilizadas na comunidade Rails: Devise e Capybara. Recomendo
que você dê o próximo passo e explore-as um pouco mais — não apenas usando-as, mas também analisando
o código-fonte. Você perceberá como o Devise utiliza o `ActionController::Metal`, como vimos em "Brincando
com Metal", na página 57, para definir um controlador básico, e aprenderá como o Capybara utiliza aplicações
Rack e handlers para iniciar servidores automaticamente.

Isso conclui nossa exploração do Rails. As ferramentas que discutimos (o conjunto de renderização, railties,
engines, generators, Active Model e assim por diante) são poderosas não apenas

10. [Link]
11. [Link]

[Link]
errata do relatório • discutir
Machine Translated by Google

Capítulo 8. Traduzindo Aplicações Usando Back-Ends de Chave-Valor • 176

para o desenvolvimento de extensões e aplicações Rails, mas também para o desenvolvimento do


próprio Rails.

Ao desenvolver sua próxima aplicação web ou ao dar manutenção a uma já existente, lembre-se
de todas as ferramentas disponíveis para tornar seu código mais limpo. Você pode usar
respondedores para tornar seu controlador mais conciso e usar geradores para manter você e sua
equipe produtivos. Agora você pode analisar o código-fonte de outras extensões do Rails, enviar
alterações e depurar problemas com muito mais facilidade.

Finalmente, você entende melhor o Rails. Você pode explorar outras áreas do código-fonte; estudar
outros módulos de Action Controller e Active Model; verificar outras implementações de geradores;
ou ler o código-fonte de railties, engines e aplicações em detalhes! O Rails também possui guias
detalhados sobre como contribuir para o Rails, então, se você ainda não o fez, esta é a hora de
propor melhorias ou corrigir alguns bugs que possam estar te incomodando.

Espero que este livro tenha lhe ensinado novas maneiras de aprimorar seu código Ruby e seus
aplicativos Rails. Acima de tudo, espero que você tenha se divertido.

—José Valim

12. [Link]

[Link] errata do relatório • discutir


Machine Translated by Google

Índice

Plug-in Mail Form, 27 envio de retornos de


SÍMBOLOS %
emails multipartes com manipuladores chamada, 35 conformidade,
{resource_name}, 117 /:from/:to, de modelos, 62, 69–71 21–26 criação de modelo para Mail
164 <%% ... %>, Plug-in de formulário, 17–28

escapando tags ERB com, 48 ActionController::Base, 57, 86 geradores e ag-nosticismo ORM,


128
ActionController::Instrumentação, 13–
14 Testes de integração de
} caractere, escapando, 66
formulários de e-mail, 28–32
ActionController::Metal, 55–59, 175
DÍGITOS Plugin de formulário de e-mail, 17–
ActionController::Renderers, 12 37 validadores, 32–35
status 304
ActionController::Rendering, 12 Registro ativo
Respondedores de cache HTTP,
middleware ActionController::Responder, 106, 109– compartilhamento de
116 comportamento, 17 middleware, 145, 149
119, 122 , 145
ActionController::Streaming, 13 Recurso ativo, compartilhamento de
Status 409, 128
ActionDispatch::Callbacks, 145 comportamento, 17
UM ActiveModel::AttributeMethods, 19–
ActionDispatch::DebugExceptions, 145
21
:ausência, 33
validadores de ausência, 33–35 ActiveModel::Callbacks, 35
ActionDispatch::ParamsParser, 145
Validador de Ausência, 33 ActiveModel::Conversion, 22–26
ActionDispatch::Reloader, 145
Controlador abstrato, na pilha de ActionDispatch::RemoteIp, 145 ActiveModel::Dirty, 37

renderização do Rails, 9–14 ActiveModel::Lint::Tests, 21, 36


ActionDispatch::RequestId, 145
AbstractController::Layouts, 12 ActiveModel::Model, 36
ActionDispatch::ShowExceptions, 145
AbstractController::Rendering, 9 ActiveModel::Name, 24
ActionDispatch::Static, 145
ActiveModel::Naming, 24
ActionMailer::Base, engines, 86 :actions
ActiveModel::Serialization, 37
argumento para geradores, 72
namespace ActiveModel::Translation, 24
AbstractController::Rendering#_normalize_options(), suporte para transliteração de 11 acentos, 160
"actions" , 117 ActiveModel::Validações, 25–26
Visualizações do mecanismo
ActionView::LookupContext, 39
ActiveModel::Validations::PresenceVal-
do Controlador de Ações, 86
classe idator , 32
Classe Metal , 55–59, 175 na Classe ActionView::MissingTemplate ,
111 ActiveRecord::Railtie, 79
pilha de renderização Rails,
9–14 ActionView::Renderer, 11 Suporte Ativo::Carregamento Automático, 102

respondentes, 106, 109–116 ActionView::Resolver, 41–42 ActiveSupport::Cache::Strategy::Local-


Cache, 145
ActionView::Template, 43, 63–66,
Action Mailer sobre, 68 ActiveSupport::IntegrationCase, 29, 163,
9 Modelo ativo sobre, 173
autenticação com De-vise, 169
17 métodos ActiveSupport::IntegrationTest, 173
mecanismos,
de atributos, 19–21
86

[Link]
Machine Translated by Google

Índice • 178

ActiveSupport::Notificações, 131, carregamento automático cached(), 43


136 Atributos para formulário de e-mail
variável @cached , 49 cache
Administração, 170–173 plug-in, 18
de cache
Sinatra, 167
after_deliver(), 35 de resolução expirando, 48, 53–55
técnicas, 100-104
after_save, 54
Respondedores de cache HTTP,
agnosticismo, 72, 126–128 B 119–122
allow_concurrency, 100 Testes de back-
Framework I18n, 160
api_behavior(), 110–112 app/ end em diferentes navegadores middleware, 145 métricas
com Capybara, 169–175
assets, 92, 96 app/ de streaming, 152 modelos, 42,

mailers, 72 app/ 48–49, 51–


extensões, 160–163 55
views Classe Rails::Application , 156–
chamar()
engines, 86 159
generators, 76 view Aplicações em rack, 141
Biblioteca Sinatra, 163–168
paths, 40 respondentes, 109
traduzindo com, 155–175
manipuladores de modelos, 63,
append_view_path(), 27 Classe básica , motores, 86 68, 70
[Link], 4 Constante base , 19 callbacks
argumentos, pilha de normalização e antes do filtro, 171 destruindo recursos, 108 mensagens
renderização, 11 operações flash, 118
antes_deliver(), 35
de array em JRuby e segurança de Plugin de formulário de e-mail,
antes_destroy(), 108, 118
threads, 103 notificações do 35 middlewares, 145, 170
antes_type_cast, 21
sistema de eventos de servidor de streaming,
cláusula begin/end , 68 100
arquivos de arquivos de ativos
com threads, 92–99 streaming atributo body , armazenamentos resolvedores de modelo, 54
de modelo, 41
de eventos enviados pelo Classe de retornos de chamada , 35, 145
servidor, 90 Booleanos, chaves hash, 51 tempo Capivara
streaming assíncrono de inicialização, carregamento sobre, 173
concorrência, 100–104 automático, 100 testes entre navegadores, 169–
estendendo o Rails com engines, 175 testes
[Link], 4,
84–86 notificações de integração, 28–32, 56, 163, 173
156 inicialização de aplicativo
do sistema de arquivos com
fictício, 3 aplicativos Rails, 156
threads, 92–99 controlador Plugin de formulário de e-mail, 28–32
inicialização, 156 buffering,
de transmissão ao vivo, 87–92 Rack e, 175
eventos streaming de eventos, resolvedores de modelos, 56
do servidor, 83–104 88
aplicativos de tradução, 163
atomicidade no carregamento de código, Bala, 131 Classe Cascade , I18n::Backend, 160
pacote de instalação, 3
101 attr_accessor(), pegar/arremessar, 171
18 attribute_before_type_cast(), 21 atualização do pacote, 3 Classe Chain , I18n::Backend, 160

attribute_method_affix(), 21 Empacotador, 3 transliteração de caracteres

attribute_method_suffix(), 20 suporte, 160


Métodos de Atributo, 19–21
C Suporte à transliteração de caracteres
Extensões C/C++ e Windows, x
atributos chineses, 160

listagem, 28 class_attribute(), 28
cURL
Plug-in de formulário de e-mail, class_collisions_check(), 72
dados de streaming, 151
18–21, 26, 32, 34
eventos de streaming, 88 class_path(), 73
método attributes() , 18, 28
Classe de cache , I18n::Backend, 160 clear_attribute(), 20
atributos_nomes(), 28
chaves de cache, 48 clear_cache(), 49, 53
autenticar(), 172
respondedores de cache, HTTP, clear_email(), 19
authenticate_admin!(), 170 119–122 clear_name(), 19
autenticação com Devise,
cache_classes(), 55 limpeza do cache do resolvedor, 49, 53–
169–173, 175 55
atributo cache_key , resolvedor de
Carregamento automático, 102
modelo, 42, 48, 52 eventos próximos , 100
autoload(), 18
cache_template_loading(), 55

[Link]
Machine Translated by Google

Índice • 179

CmsController, servindo modelos com autenticação de personalização, geradores, 71–80, 122–128


Metal, 55–59 técnicas de controladores com De-vise, 170
desativação
carregamento de código, 100–104, Suporte para transliteração de caracteres
159 do armazenamento de métricas, 149 cirílicos, 160

conformidade, Modelo Ativo, 21–


26 visualizações do D
mecanismo, 86 Limpador de banco de dados, 175
compliance_test.rb, 21 mecanismos, 83 variáveis de
gerenciamento
notificações de instância, 166 contexto de
de bancos de dados com mount-
sistema de arquivos de concorrência, pesquisa, 39 pilha de middleware,
93 motores capazes, 131–153
145,
silenciando notificações, 146–
streaming de eventos enviados 148 manipulação de modelo, 22
149
pelo servidor, 91, 100–104 Padrão MVC, 1 página
Configuração da API de notificações,
solicitações condicionais, 128 de notificações, 137
133–137
ConditionalGet, 145 Agnosticismo ORM, 126–128
página de notificações, 137–
config.action_mailer, 150 140
Plug-in de renderização de PDF, 7,
config.active_record, 150 persistência, 25 pilha
15
de renderização, 39
config.app_middleware , 149 persistidos?(), 25
configuração do resolvedor, 41–
config.eager_load_namespaces, 102 aplicações Rack, 142 tipos de
55 recuperando modelos de
requisição e, 107 modelos de
[Link] , 149 visualização de,
serviço com Metal, 55–59
[Link], 144 39–59 servindo modelos com
Metal, 55–59
[Link], 141, 158 config/ eventos do servidor de streaming,
métricas de streaming com Rack,
[Link], 4, 156 config/[Link], 87–92
150–153 DataMapper,
4, 156 config/[Link], ganchos do mecanismo de
ix, 128 rótulo
157 config/[Link], 156
modelos, 75 caminhos "[Link]" , 172
config/[Link], 132 configurando
de visualização, 40 escrita com Exceções de depuração, 145
concorrência, 100, 102 respondedores, 105–129
depurador, 3, 144
aplicativos convenção sobre configuração, ix
desacoplamento em geradores, 72
fictícios para teste, 4 pilha de
DEFAULT_ACTIONS_FOR_VERBS
middleware do mecanismo, Conversão, Modelo Ativo, 22–26
hash,
cookies, 145, 173
112 :opção padrão, 117
149 copy_file(), generators, 80 create()
default_format(), 71
Estrutura I18n, 160, flash
default_render(), 111, 113
162 respondedores, 114–
119 Tarefa atrasada, 153
Aplicações Rails, 156 railties para
middleware, 150 resolvedores de geradores de andaimes, 105 delete(), Aplicativos Rack, 142

create_file(), geradores, 80 Geradores de solicitações


templates, 48–55 ambientes de teste, DELETE e agnosticismo ORM, 126
create_mailer_file(), 72–73 testes
5 middleware, 145
entre navegadores com comportamento de
constantes, carregamento automático,
Conceber e Capivara, 169–175
navegação, 112
19 contato(), 27
[Link], 78
proteção contra falsificação de respond_with() como padrão em
tipos de conteúdo, configuração no solicitação entre sites, 57 controladores gerados por
renderizador de PDF, 8 scaffolding, 124

Disposição de conteúdo, 152 Honeypot do validador de ausência de respondentes e, 107–109 deliver(),

Comprimento do conteúdo: 152 CSS, 33, 35 26, 35 configuração


contexto plug-in de streaming de folhas de
de dependências, 4
estilo, 83, 89–104
pesquisa, 39, 52 gems, 3 middleware,
visualização, 11, 39 CSV, dados de streaming com, 150– 144, 149
153 aplicações Rails de arquivo
controlador#ação, 142
biblioteca csv , 153 único, 159
controlador.método(), 166

ControladorGerador, 75–78

[Link]
Machine Translated by Google

Índice • 180

destroy() Plug-in de formulário de e-mail, 17–37 ERB


respondedores de flash, 114– Validadores de formulários de e-mail, 32– personalizando geradores, 71–80
119 35
controlador de notificações, 137 Envio de e-mails multipartes com escapando tags,
manipuladores de modelos, manipulador de modelo 48 , 66–71
recursos, 108 61–80 Erb::Generators::Base, 75
geradores de scaffold, 105 usando manipuladores de modelos, Erb::Generators::ControllerGenerator, 75–78
controladores de escrita com 63–66
respondedores, 107 Atributo de e-mail , plug-in de
Erb::Generators::MailerGenerator 74– ,
atributo de detalhes , 42, 52 formulário de e-mail, 18– 80
Elaborar, autenticação com, 169–173, 19, 26 e-mail?
Páginas de erro, middleware, 145 erros(),
175 (), 20 variável ended_at , 133
validações do modelo ativo, 25
dispositivo:instalar, 169
endpoints
DevKit, x CSV streamer, 153
caractere de
diretórios e notificações do sistema de streaming de eventos enviados
escape }, 66
arquivos com threads, 92–99 pelo servidor, 87, 90
Etiquetas ERB, 48
Classe de locomotivas , 84–86,
ETag, 145
Classe suja , 37 156
retornos de chamada
desativando o armazenamento de métricas, 149 locomotivas com
avaliados(), 35 formato de fluxo de eventos, 89
carregamento rápido, 102 trilhos extensíveis com, 84–
Biblioteca de descontos, 66
86 servidores com eventos, 103
display(), 112 isolados, 131, 133 eventos
div_for(@post), 22 gerenciando eventos com concorrência, 100–104
dom_class(), 22 motores montáveis, estendendo o Rails com engines,
131–153 84–86 notificações
dom_id(), 22
middleware, 143–150 pilha de do sistema de arquivos com
Princípio "Não se repita", ix, 176, veja
middleware, 148 threads, 92–99 controlador
também respondentes
Configuração da API de notificações, de transmissão ao vivo,
133–137 87–92
draw(), 159
página de notificações, 137– Gerenciando com o Mount-
motoristas e Capivara, 173 140 motores capazes, 131–153
Princípio DRY, ix, 176, veja também Integração em rack, 140–143 Configuração da API de notificações,
aplicação fictícia de 133–137
Aplicações Rails e, página de notificações, 137–140
respondentes para testes, 2–3
156
métricas de streaming com streaming assíncrono, 83–104
Rack, 150–153
E métricas de
ganchos de modelo, 75–77 streaming com
cada(), 150
Exemplo de aplicação de tradução de Prateleira, 150–153
Classe EachValidator , 34
inglês para polonês, 164– EventSource, 100
carregamento antecipado, 101 168
:escape, 168
carregamento_automático_antecipado(), garantir, 147
exhibit_translations(), 166
102 carregamento_antecipado!
env["PATH_INFO"], 142
cache do resolvedor expirando, 48, 53–
(), 102 editar(), respondedores de flash, 116 env["guarda"], 170 55
email
ambiente
sobre manipuladores de modelos,
Configurando testes, 5 F
61–63
geradores, 80 Classe de reserva , 160
construindo manipuladores de modelos- equipes de
inicializando em aplicações Rails,
leitores com Markdown +
157 pilha de resposta rápida de contingência, 117
ERB, 66–71
middleware e, 144 Framework I18n, 160
criando modelo para o Correio
:arquivo, render(),
Plug-in de formulário, 17–28 Sinatra e, 166
geradores de personalização, 1 nome_do_arquivo(),
71–80 Método environment() , geradores, 80
73 nome_do_arquivo_com_extensões(),
Retornos de chamada do formulário de e-mail, 35
75 notificações do sistema de arquivos
: tarefa de ambiente, 157
Testes de integração de com threads, 92–99
formulários de e-mail, 28–32 eql?(), 49

[Link]
Machine Translated by Google

Índice • 181

fill_in(), 172 /:de/:para, 164 plug-in de manipuladores , 64–66


find_all() --completo, 84 HandlersController, 65
sobre, 41–44 chaves hash(), 49
de cache, 48, 51, 54 G
hashes
find_templates(), 42, 45 gem(), geradores, instalação
locale_value(), 166
middleware de 80 gems, x pesquisas em Ruby, 49–51,
de fixtures e, 150 transacional, Gemfile, 2–3 60
175 mensagens flash Classe principal , 145
dependências de gems, 3 Requisições HEAD, middleware,
autenticação com De- Gemfile, 2 145
vise, 169 constantes de carregamento, 19 cabeçalhos
callbacks, 118 plug-in
geradores, veja também personalização respondedores de cache HTTP,
de formulário de e-mail, 31 do gerador de 119
middleware, 145
scaffold, 122–128 personalização Plugin de formulário de e-mail, 27
resolvedores de modelo, 57
para e-mails multipartes, 71–80 Plug-in de renderização de PDF,
respondedores flash, 114–119 desacoplamento de pilha de renderização 7 ,
"[Link]", 116 entrada, 72 método headers() 13 , plug-in de
Elaborar, 169
"[Link]", 116 formulário de e-mail, 27
estendendo-se com dormentes de trilho,
flash[:alert], 116 78–80
navegadores sem interface gráfica, 174

flash[:notice], 116 Suporte para transliteração de caracteres


ganchos, 71, 74–77, 122
Disco Voador, 16 Agnosticismo ORM, 126– hebraicos, 160 hello(), 87

Notas de rodapé, 131 128


Rails::Application e, 156 caminhos Exemplos de "Olá, mundo!"
form_for(@model), 25
de origem, 123 estruturas, Sinatra, 163
format(), gerador de e-mail, 77 72–74
aplicações Rails de arquivo único,
atributo de formato
API de geradores, 72–80 159 eventos
Conformidade com a API do Active
Geradores::InstalarGerador, 124 obter(), de servidor de streaming, 87–92
Model, 23
repositórios de modelos, 41, 47, Aplicativos Rack, 142
HelloRack, 141, 151
52 Requisições GET
condicionais, 128 hide_action(), 57
:format
manipuladores de modelos de geradores e agnosticismo ORM, 126 ocultando
validador de ausência, 35 ações
chave, 70 armazenamentos
Respondedores de cache HTTP, em solucionadores de modelo, 57
de modelos, 44
119–122
[Link],
middleware, 145 Controlador residencial
113 formatos padrão em geradores
comportamento de navegação, autenticação com De-vise, 170
de scaffold, 112
107 fluxo de eventos, 89
respond_with() como padrão em Plug-in de renderização de PDF, 7,
gerador de e-mail, 77 Post, 23 controladores com scaffolding, 15
render(), 124
plug-in de armazenamento de
1
respondedores e, 107– 109, 112 notificações, 137
respondedores, 110, 113
manipuladores de modelos, 70 honeypot, spam, 33–35 hooks
Aplicativo de tradução Sinatra, 166
armazenamentos de modelos, 41, callbacks
44, 47, 52 em formulário de e-mail
Classe Gettext , 161
formulários plug-in, 35
caminho dourado, ix geradores, 71, 74–77,
Criando um modelo para o Mail
grupos, destruindo e, 108 122
Plug-in de formulário, 17–28
Retornos de chamada do formulário de e-mail, 35 Gruber, John, 62 notificações, 136 dormentes
ferroviários e, 80
Testes de integração de
formulários de e-mail, 28–32 H mecanismos de modelos, 75–77

Plug-in de formulário de e-mail, 17–37 Marcação Haml, 164, 166 manipulador aplicativos de tradução, 172
Validadores de formulários de e-mail, 32–
(), gerador de mailer, resolvedor de modelo
35
de atributo de
/:de, 164
manipulador 77 , 44
armazenamentos de modelo, 41, 47

[Link]
Machine Translated by Google

Índice • 182

HTML, veja também multipart Classe Rails::Application , 156–159 testes de integração com
e-mails; tradução de aplicativos Capivara,
ções Biblioteca Sinatra, 163–168 aproximadamente 173
Comportamento personalizado armazenamentos de Plugin de formulário de e-mail, 28–
em respondedores, modelos, 44 tradução, 155– 32 resolvedores de modelos,
110 formato padrão em geradores 175 [Link](), 159 56 aplicativos de tradução, 163
de scaffold, 107 IntegrationCase, 29, 163, 173
I18n.l(), 159
Markdown, 62
[Link](), 159 Teste de integração, 173
PDFs de, 6, 16
I18n.t(), 159 Internacionalização, veja o quadro I18n.
Marcação de abstração HTML
Linguagem, veja marcação [Link](), 159
Compilador de interpolação,
Haml : atributo identificador , re-solucionador de
modelo, 44 161 invocar, ganchos e, 123
formato html , 70
IDs Erro de E/S, eventos do servidor
cabeçalhos de cache HTTP intermediários- Métodos do modelo ativo, 22 eventos de streaming, 87

ware, 145 em notificações. Verificação de falsificação de IP,


respondedores de cache, 119– 122 API, 133
145 mecanismos isolados, 131, 133
middleware, 145

pedidos condicionais, 128 resolvedor de modelos, 44 J


Marcação Haml, cabeçalhos 164 e Cabeçalho If-Modified-Since , 119 Testes de
166 e pilha de renderização, 13 Cabeçalho If-Unmodified-Since , 128 compatibilidade entre navegadores
modelos de em
serviço com JavaScript, 174 comportamentos
controlador de notificações de índice,
Metal, 57 personalizados em
137
verbo e respondentes, 106– respondedores, 110 controladores
Plug-in de renderização de PDF, 7
109, 112 de
respondedores, 129
Módulo HttpCache , 121 notificações, 138 eventos enviados
geradores de scaffold, 107 métricas
humano(), 24 pelo servidor em streaming, 90, 100
de streaming, 152 resolvedores
de modelos, 47 Notações de Objetos em JavaScript,
EU ver JSON
controlador de
Classe I18n::Backend::Cache , 160 notificações do método index() , jQuery, 138
Classe I18n::Backend::Cascade , 160 137 jQuery-Rails, 138
Classe I18n::Backend::Chain , 160 respondedores, 129 jQuery_ujs, 138
geradores de scaffold, 107 métricas
Classe I18n::Backend::Fallbacks , 160 JRuby, 103 :js,
de streaming, 152 resolvedores
Classe I18n::Backend::Gettext , 161 render(), 1
de modelo, 47 herança,
Classe I18n::Backend::InterpolationCompiler , JSON
geradores, 73,
161 77 formato padrão no scaffold

Classe I18n::Backend::KeyValue , 160 geradores, 107


inicializar(), 31
Classe I18n::Backend::Memoize , armazenamentos de chave-
inicializar!(), 156
161–162 valor, 163 renderização,
inicializadores 1, 5 solicitações e padrão MVC,
Classe I18n::Backend::Metadata , 161
motores, 86, 95, 100 1
Classe I18n::Backend::Pluralization , Plugin de formulário de e-mail, 31 respondedores, 113
161
Aplicações Rails, 156 Aplicativo tradutor de Sinatra, 168
I18n::Backend:: Classe simples, 160 inject_into_file(), geradores, 80 instalação,
Classe I18n::Backend::Transliterator , x :json, render(), 1, 5
161
InstallGenerator, 124
Framework I18n, cerca K
instância(), SqlTemplate, 53
de 155 back- armazena cerca de 155
variáveis de instância
ends e extensões. chaves-valor.
Sinatra e, 166 ver Extremidades traseiras e extensões
ções, 160–163 testes
contexto, 11 I18n, 160–163
entre navegadores
instrumento(), 133 Classe Rails::Application , 156–159
com Capivara, 169–175
eventos de instrumentação, pilha de
respondedor de flash, 114–119 renderização, variável Biblioteca Sinatra, 163–168
Plugin de formulário de e-mail, 24 instrument_id 13–14 , 133

[Link]
Machine Translated by Google

Índice • 183

traduzindo com, 155–175 usando atributo locals , re-solucionador de métricas

Devise e Capy-bara, 169–175 modelo, 42, 49 silenciando notificações,


Classe de fechadura , 145
146–149

chaves, veja também armazenamentos de página de notificações, 137–140


Classe Logger , 145
chave-valor
logs, middleware e, 144–
Métodos do modelo ativo, 22 chaves armazenando configuração na API
145
de notificações, 133–137
de cache, 42, 48 pesquisas
Classe LogTailer , 144 streaming, 150–153
de hash em Ruby, 49
long polling, 89 middleware
Classe KeyValue , 160
Classe LookupContext , 39 definido, 143
condição de silenciamento, 146–149
pesquisas
em cascata, 160 pilhas, 143–146, 148–149, 169
Palavra-chave lambda L , 65
Cabeçalho Last-Modified , 119 contexto de pesquisa, 39, 52
memorização, 161 migrações, repositórios de modelos, 41,
Transliteração de caracteres latinos-
47
suporte de ção, 160 looping, streaming com Rack,
151 Tipos MIME, 8, 70
Layouts, pilha de renderização, pasta
Classe MissingTemplate , 111
lib 12 , 2, 4 M
Classe do modelo , Modelo Ativo, 36
lib/assets, 92, 96 mail(), 69
@ variável do modelo, 21
lib/live_assets.rb, 93 lib/ Plugin de formulário de e-mail, 17–37
modelo-visão-controlador (MVC)
live_assets/[Link], 84 lib/ callbacks, 35
padrão, 1, 17
mail_form/[Link], 18 lib/ criação de modelo para, 17–28 testes
model_name(), 24
de integração, 28–32 validadores,
pdf_renderer.rb, 8 lib/
32–35 callbacks
[Link], 162 de modelos, 35
@mail_form, 27
link_to(), pilha de renderização, 11 gem criando modelo para Mail
mail_form/[Link], 18
listen , 93 ouvintes, Plug-in de formulário, 17–28
mail_form_test.rb, 26
notificações, 93 atributos de listagem, Testes de integração de
MailerGenerator, 72–78 formulários de e-mail, 28–32
28 plug-in live_assets , 84–104
MailForm::Base, 17 Plug-in de formulário de e-mail, 17–37

MailForm::Notifier, 27, 40 Padrão MVC, 1, 17


concorrência, 100–104 estendendo nomenclatura,
MailForm::Validadores, 34
o Rails com engines, 84–86 24 persistência, 25
notificações do main_app(), 139 validadores, 32–35
sistema de arquivos com threads, manifestos, 90, 133 mongo_metrics(), 139
92–99 controlador de
Markdown, manipulador de modelos, O MongoDB
transmissão ao vivo, 87–92
62, 66–71, 78
gerencia eventos com
correspondência(), 142 mecanismos montáveis.
live_assets#sse, 98 132–153
Extensão .md , 67
LiveAssets.eager_load!, 102 silenciando notificações, 146–
MD5, 145
LiveAssets::SSESubscriber, 97, 100 149
memcached, 55
LiveAssetsController, 87–92 Configuração da API de notificações,
Classe Memorize , 161–162 página de
load_tasks(), 157
MERB notificações 134–137 , 137–
carregando 140
Personalização de geradores, 71–
atributos para o Formulário de E-mail
80 métricas de streaming com
plug-in, 18 Prateleira, 150–153
manipulador de modelo, 66–71
carregamento automático,
_merge_attributes(), 32 Mongoide, 134
100 ansioso, 101
Tarefas de ancinho, 157 Classe de metadados , 161 O MongoMetrics
Sinatra, 167 Metal gerencia eventos com

aplicações Rails de arquivo único, Crie e, 175 modelos de mecanismos montáveis.


159 132–153
apresentação com,
55–59 silenciando notificações, 146–
Classe LocalCache , 145
149
atributo de localidade , método_ausente(), 20
Configuração da API de notificações,
armazenamentos de modelos, 41, 47, 52 Classe MethodOverride , 145 134–137
valor_locale(), 166 metric_path(), 138

[Link]
Machine Translated by Google

Índice • 184

página de notificações, 137– variável nome em Notificações Página de notificações


140 API, 133 da API de notificações, 137–
métricas de streaming com nome?(), 20
Prateleira, 150–153 Configuração 140 , 133–137
named_base_test.rb, 73
MongoMetrics::MetricsController, 152 Classe Notificadora , Formulário de E-mail, 27,
Classe NamedBase , 72
montar(), 142 40
namespaces
--montável, 132 "ações" namespace em O
motores montáveis sobre, I18n, 117 de
Object#hash(), 49–52
131 carregamento rápido, 102
mapeadores objeto-relacionais, veja
gerenciamento de eventos com, motores isolados, 133 motores
ORMs
131–153 montáveis e,
middleware, 143–150 138 recursos online, xii, 176 eventos
namespaces e, 138 nosso abertos , 100 opções
Configuração da API de Modelo ativo, 24 render(),
notificações, 133–137 convenções, 2 1, 6
página de notificações, 137–140 eventos em Notificações render_to_string(), 15
API, lista pilha de renderização, 11–12,
Integração em rack, 140– 133 nomes de atributos, 15
143 28 respondentes e, 109 objetos
métricas de streaming com resolvedor de modelos, 42 de opções , 6
Prateleira, 150–153
Classe de nomenclatura , Modelo Ativo,
cláusulas de ordem na busca de
Mozilla, eventos enviados pelo servidor,
24 modelos, 45
100
respondedores de navegação, orm_class(), 126, 128
emails multipartes sobre 110–112 geradores com estrutura de suporte,
manipuladores de modelos, 61–63 orm_instance(), 126, 128
107
ORMs
comportamento_navegacional(),
construção de manipuladores de modelos Modelo ativo, 17, 22
respondedores, 110–112
leitores com Markdown + agnosticismo, 72, 126–128
ERB, 66–71 Implementação de hash aninhado,
51 sobrecarga, pilha de renderização,
Personalização de geradores, 71– 13–14
80 novo()
sobrescrevendo, na pilha de renderização,
Envio com manipuladores de SqlTemplate, 53
12
modelos, 61–80 usando escrevendo controladores com
respondedores, 107
manipuladores de modelos, P
63–66 Nova Relíquia, 131
ParâmetrosParser, 145
multipart/alternativo, 70 apelido, validador de ausência honeypot,
resolvedor de modelo
Correio de Internet Multiuso 33, 35 pilha de
de atributo parcial , 42, 49
Tipos de extensões (MIME), 8, 70 renderização de
armazenamentos de modelo, 41,
normalização, 11, 15 resolvedor
47
servidores multithread, 103 de modelo, 43 _normalize_args(),
renderização de parciais para,
silenciar!(), 146–149 11 _normalize_options(), 22–23 resolvedor de modelo, 42, 49
silenciar?(), 146 11–12, 15 _normalize_render(), 11 armazenamentos de modelo, 41, 47

MuteMiddleware, 146–149 mutex, notificações notificações do Passageiros, 103

103, 145 silenciamento sistema de O PATCH solicita


arquivos com threads, 92–99 comportamento de navegação,
de notificações, 140, 146–149
gerenciando eventos com 112

mecanismos montáveis, respondedores e 108 atributos


Padrão MVC, 1, 17
de caminho , armazena modelos,
131–153 41
N
silenciamento, 140, 146–149
Argumento NAME para geradores,
página, 137–140
72 mecanismos de caminhos,
armazenando configuração na API
atributo nome 84–86 fonte do gerador, 123
de Notificações, 133–137
renderização para parcial, 22–
Plugin de formulário de e-mail, métricas de streaming com 23
resolvedor de modelos 18–19 , 42, 49 Prateleira, 150–153
Aplicações Rails de arquivo único,
Classe Nome , Modelo Ativo, 24 159

[Link]
Machine Translated by Google

Índice • 185

lojas de modelos, 41, 44, atributo de prefixo , solucionador de Rack::Runtime, 145


47 modelo, 42, 49 rackup, 141
vista, 40 atributos de prefixo rackup -s puma, 151
variável de carga útil , 133 Métodos do modelo ativo, 19
Trilhos
Plug-in de renderização de PDF, 1–16 resolvedor de modelo, 42, 49 :
que se estendem com motores,
criação, 1–5 chave de presença, 33 84–86
Pilha de renderização Rails, 9– Validador de Presença, 32 importância de, ix
15
Biblioteca Prince XML, 16 Integração em rack, 140–
escrita, 5–9 143
_process_options(), 11–12
renderizador de pdf, 2– pilha de renderização, 9–14
proxies recursos, xii, 176 aplicativos
16 criação, 2–5
Métodos do modelo ativo, 22
Pilha de renderização Rails, 9– de arquivo único,
15 middlewares, 170 rotas 158
de notificação, 139
escrita, 5–9 versões, x, 12, 140 gem
gema de alavanca , 3
pdf_renderer.gemspec, 2 rails , pilha de middleware,
Puma 149
pdf_renderer_test.rb, 5
com carregamento
testes de Roteador de trilhos, Aplicações em
antecipado, 102 eventos de servidor de streaming,
rack, 141
desempenho em diferentes navegadores, 87
174 Servidor Rails, testes, 8
put(), Aplicações Rack, 142
pesquisa de hash, 51–53 Rails::Application, 144, 156–159
método send_data() , 150 Requisições PUT
Trilhos::Motor, 84–86, 156
condicionais, 128
realizado?(), 111
Rails::Generators::Actions, 80
geradores e agnosticismo ORM,
persistido?(), 25
126 middleware, 145 Rails::Generators::ActiveModel, 128
temporizador de ping para streaming comportamento de Rails::Generators::MailerGenerator, 72–
de eventos enviados pelo servidor, 99
navegação, 112 78
Plataformatec, 37, 129
Rails::Generators::NamedBase, 72
Classe de pluralização , 161 persistência, 25
Rails::Generators::ScaffoldController-
respond_with() como padrão em
arquivos .po , Gerador, 123
controladores com scaffolding,
161 Exemplo de aplicação de 124 Rails::Generators::TestCase, 80
tradução para polonês, 164–168
respondedores e, 107–109 Rails::Rack::Debugger, 144
polling, 88, 93, 95 post(),
Trilhos::Rack::Logger, 145
Aplicações Rack, 142 Classe Post , Q Trilhos::Rack::LogTailer, 144
Métodos do Active Model, 22 Objeto Atalho %Q{} (Ruby), 66
Trilhos::Amarração de trilho, 84
@post Classe de fila , notificações do sistema
Instalador de trilhos, x
de arquivos, 96–99
ferrovias
Métodos do modelo ativo, 22 Fila#pop(), 97
render(), 6 carregamento antecipado,
expiração
102 motores e, 84
Geradores de do cache de filas, 55
geradores de extensão com,
requisições POST e ag-nosticismo notificações do sistema de
78–80 inicialização
ORM, 126 arquivos, 96–99
em aplicações Rails, 157 pilha de
Plugin de formulário de e-mail,
middleware, 149
17 middlewares, 145 R
comportamentos de navegação, Rack
112 Gem Railties , pilha de middleware,
Capivara e integração com
149
persistência, 25 Rails, 175
respond_with() como padrão em Middleware Arco-íris, 103
controladores com scaffolding, 140–143 , métricas de streaming rake -T, 3
124 143–150 com, middleware rake, 144, 149–150
respondentes e, 107–109 150–153
rotas de rastelo, 139
Aplicativo tradutor de Sinatra, Rack::ConditionalGet, 145
168 Tarefas de rastelo, carregamento, 157
Rack::ETag, 145
Arquivo de ancinhos, 2–3
post_path(@post), 22
Rack::Cabeçalho, 145
Camarão, 1, 6–9, 14 Extensão .rb , 65
Rack::Lock, 145
RDiscount, 66
Rack::MethodOverride, 145

[Link]
Machine Translated by Google

Índice • 186

Redis tipo de solicitação, respondentes, 106 Módulo Responders::HttpCache ,


reimplementando cache com, 55 121
Classe RequestId , 145
armazenando respondedores/http_cache, 122
Classe Resolver , Action View, 41–
dados de tradução, 155, 161, 168 42 Responders::Flash, 114–119
gem redis , 161
API Resolve, 41–44 Responders::Generators::InstallGener-ator,

Resolver#clear_cache(), 53 124
register_template_handler(), 65, 68
resolvers, configuração de cabeçalhos de resposta, plug-in de
registered_template_handler(), 44 renderização de
modelos, 48–55 formatos,
registrando, manipuladores de modelo, 71 modelos de PDF, 7 [Link],
44, 65, 67
serviço com 87 Resque, 153, 171
expressões regulares em aplicações Rack, Metal, 55–59
REST, 119
142 reloadCSS, 89 configuração, 41–48
route(), geradores, 80 roteadores
método resource() , Aplicativos Rack, 142
Classe Reloader , 145 status do recurso,
autenticação com De-vise, pilha de
middleware respondedores e, 108
middleware
de recarregamento, 145 172 , mecanismos montáveis 144–
em Rails::Application, 156 [Link], 108 % 145, 148, 159 , 132,
aplicações Rails de arquivo único, {nome_do_recurso},
159 folhas de 138
117 :nome_do_recurso, 117
estilo com plug-in de streaming, 89– Aplicações em rack, 140–141
recursos
92
Verbo HTTP e, 106, 109, 112
Classe RemoteIp , 145 Aplicações Rails de arquivo único,
render() 159
Aplicações Rack, 142 eventos
adicionando opções, 1 rotas
enviados pelo servidor, 100
modificando para PDFs, 1– 13, 15 motores montáveis, 132 página de
métodos resources() , Aplicações Rack,
notificações, 137– 140 plug-in de
142
sobrecarga, 13
recursos, Rails online, xii,
sobrescrita, 12 renderização de PDF, 7 aplicativos
176
parciais, 23 pilha Rack, 141 aplicativos Rails de
de renderização, 9 respond(), 109 arquivo único, 159 sticky, 159
respondedores, 112 respond_to(), 57, 106, 122 resolvedor de

renderizar csv: @metrics, 152 respond_with() modelo, 56

api_behavior(), 112 [Link], 159


renderizar json: @users, 107
controllers, 106, 109, 115 customizing [Link], 159
render(:new), 11
generators, 122–126 RPM, 131
render(ação: "novo"), 11

render(partial: action_name), 11 Gema Responders, 129 métricas


Rubinius, 60, 103
render(partial: true), 11 de streaming, 152
_render_template(), 11 respondentes
Carregamento automático do Ruby, 100
render_to_body(), 11 sobre, 106 call(),
Conversão de dados CSV, 150 gems
109–113
render_to_string(), 9, 13–15 e carregamento de constantes,
personalizando geradores, 122–128
renderizadores 19 pesquisas
respondentes
CSV, 152 de hash, 49–51, 60 objetos e
flash, 114–119
definidos, 5 aplicações Rack, 141
Plug-in de renderização de PDF, 1–
Respondedores de cache HTTP,
16 Atalho %Q{} , manipulador
119–122
caminhos de visualização, de modelo 66 , run() 64–66 ,
Verbo HTTP e, 106–109, 112
40 pilhas de renderização, veja também mid- navegação, geradores, 80
utensílios domésticos
110–112 tipo de requisição, Classe de tempo de execução , 145
Rails, manipulador 106 status do recurso, 108 runtime, middleware, 145
de modelos 9–14 , 61 modelos, escrevendo controladores DRY
11, 15, 39, 53, 61 com, 105–129 S
SampleMail, 17–37
Cabeçalhos de requisição, teste Gema dos respondentes, 129 retornos de chamada, 35
Capybara, 174

[Link]
Machine Translated by Google

Índice • 187

Criando um modelo para o Mail --skip-active-record, 150 -- métricas com Rack, pilha de
Plug-in de formulário, 17–28 skip-sprockets, 150
Testes de integração de renderização 150–153 , 13
sleeping, 96
formulários de e- eventos do servidor, 83–104
mail, 28–32 validadores, 32–35 source attribute, template re-solver, 44
strings
source paths,
salvar(), 168 chaves hash, 51
123 :source.to_proc, 65
duplicação do gerador de renderização para, 9, 13–15
source_root(), 78, 123 manipulador de modelo, 65, 68
andaimes em, 105 formatos
padrão, 107 spam, absence validators, plug-in de streaming de folhas de estilo,
Agnosticismo de ORM, 126–128 33– 83, 89–104
35 subscribe(), 133
respond_to() como padrão, 122 inscritos
verificação de falsificação,
ganchos do mecanismo de
145 trilhos de notificações do sistema de
modelo, 75
arquivos,
engrenagens, 149
resolvedor de modelo, 41, 46
configuração do 96–100 segurança de
Controlador de andaimeGerador, 123
SqlTemplate::Resolver , 48–55 threads, 103 subárvores, recuperação de
Controladores com andaimes back-ends, 166, 168
servindo modelos
enviam mensagens flash, formatos com Metal, 55–59 métodos de atributo de sufixo, 20 super
114–119 padrão, 107
configuração, 41–48 palavra-chave, 117, 122
Verbo HTTP, 107–109 status
do recurso, 108 respond_with() SqlTemplate::[Link](), 53 Swartz, Aaron, 62
como padrão, 124–126 SqlTemplate::[Link](), 53 sse, 89
Assinante SSE, 97, 100
T
Escoteiro, 131 pilhas tags, escapando ERB, 48

Selênio, 174 notificações do sistema de arquivos, Classe de modelo , Visualização de


96 ação, 43, 63–
enviar_dados(), 8, 150
middleware, 143–146, 148–149, 66 ganchos do mecanismo de modelo, 75–
Classe de serialização , 37
169
77 manipuladores de
eventos enviados pelo servidor, 83–104
Renderização Rails, 9–14, 39, modelo sobre, 61–
concorrência, 100–104 53, 61 variável
63 personalizando geradores, 71–
estendendo o Rails com engines,
started_at , 133 80
84–86 notificações
Classe estática , 145 Markdown e ERB, 66–
do sistema de arquivos com 71
threads, 92–99 controlador :status
registrando, 65 pilha
de transmissão ao vivo, pilha de renderização, 11
87–92 de renderização, 61
recursos, 108
recuperando modelos de
middleware rotas fixas, 159 loja!(), 136 armazenamentos personalizados, 41
de sessões, 145
Ruby, 64–66
modificando, 173 pilha
@[Link], 161 enviando e-mails multipartes
de com, 61–80
@store[], 161
renderização show() , 11 strings, 65, 68
@store[]=, 161
escrevendo controladores com usando, 63–66
respondedores, 107 armazenamento de modelos, 39–59
método template() , 73
pilha de renderização, 39
Mostrar exceções, 145
configuração do resolvedor, 41– opção :template , render(), 1
login, consulte autenticação configurando resolvedores
55 servindo com Metal, 55–59
com Devise
armazenamento de dados de de modelo, 48–55 formatos,
Classe simples , I18n::Backend, 160 71 servindo
tradução, veja armazenamentos
Sinatra, traduzindo aplicações, 155, 163– modelos com
de chave-
168 Metal, 55–59 em
valor, concorrência de streaming,
preparação, 41–48
aplicações Rails de arquivo único, 100–104
158 API Template-Handler, 63–66
definido, 83 estendendo o Rails
SingleFile#call(), 159 com engines, 84– [Link], 70

Módulo singleton , 53 86 notificações do sistema de [Link](), 65


arquivos com threads, 92–99
resolvedores singleton, 53 templater, 41–48
controlador de streaming ao vivo,
singular(), 24 87–92

[Link]
Machine Translated by Google

Índice • 188

modelos, veja também manipuladores de arremesso/recepção, 171 V


modelos; re-modelo temporizadores, transmissão de eventos válido?(), 25–26
solucionadores
enviados pelo servidor, 99
validar_cada(), 34
armazenamento em cache, 42, 48–49, 51–
carimbos de data/hora, respondedores valida(), 25, 32–35
55
de cache HTTP, 119,
Personalização de geradores, 71– valida_formato_de(), 25
80 122 /:para localidade, 164
valida_inclusão_de(), 25
Marcação de Haml, 166 to_csv(), 153
valida_presença_de(), 32
Plugin de formulário de e-mail, to_format(), 110, 122
valida_com(), 32
27 modelos ausentes e to_html(), 110,
Classe Validações , Modelo Ativo, 25–26
default_render(), 111
117 :to_iphone,
Renderizador de PDF, 15
render(), 1 110 to_js(), ausência de

pilha de renderização, 11, 15, 39, 110 to_key(), 22–23 validadores, 33–35
53, 61 to_model(), 22 Plugin de formulário de e-mail, 32–35
configuração do resolvedor, 41– valores, consulte fornecedores/ativos de
to_param(), 22–23
55 recuperando de armazenamentos armazenamento de chave-valor , 92, 96
to_partial_path(), 22–23
personalizados, 39–
versões
59 servindo modelos com to_prepare, 145
Trilhos, x, 12, 140
Metal, 55–59 to_proc(), 65
Gems Ruby, 3
usando respond_with() como fixtures transacionais, 175 aplicações
padrão, 124–126 contextos de visualização, pilha de
de tradução renderização, 11, 39
visualizar caminhos, Extremidades traseiras e extensões
40 testes, criação, 2 caminhos de
I18n, 160–163
ambiente de teste, configuração, Plugin de formulário de e-mail, 24 visualização, 40 pilha
5 Classe Rails::Application , 156–159 de renderização de modelos
de visualização, 39 configuração
test/compliance_test.rb, 21 test/
Biblioteca Sinatra, 163–168 usando do resolvedor, 41–55 recuperando
fixtures/sample_mail.rb, 18 test/ de armazenamentos
Devise e Capybara, 169–175
mail_form_test.rb, 26 test/ usando back-ends de personalizados, 39–59 servindo com
pdf_renderer_test.rb, 5 test/ chave-valor, 155–175 Metal, 55–59
test_helper.rb, 5 view_assigns(), 11
test_helper.rb, 5 Tradução, Modelo Ativo, 24 view_context, 11, 39 view_renderer, 11
Classe TestCase , geradores, 80 testes, [Link]!(), 164 vistas

veja também Capybara suporte à transliteração, 160– mecanismos, 86


ActiveModel::Lint::Tests, 21, 161 chaves de modelo, 22
36 Classe de transliterador , 161 manipulação de modelo, 22 padrão
eventos de streaming, 92, 94 MVC, 1 página de
:texto EM notificações, 137 plug-in de
render(), 1 Unicórnio, 102 renderização de PDF, 7 persistido?
(), 25 pilha de
manipulador de modelo, 71 update(), 108, 114–
renderização, 11, 39
texto/simples, 70
119 :updated_at key, templates
Chave :virtual_path , armazenamentos
Fino, 103 stores, 44, 55
de modelos, 44
Thor, 73, 80, 123 url_for(), 25

Módulo Thor::Ações , 73, 80 URLs, modelos, 22, 25 EM

Classe de thread , 97 @user.to_json, 113 Diretor, 169–171

@[Link], 118 WEBrick, eventos de servidor de


carregamento automático de threads Controlador de usuários streaming, 87
e, 101 testes entre navegadores socorristas de emergência, 114– API WebSockets, 89
com Capybara, 175 119 [Link], 78
notificações do sistema de respondedores de cache HTTP,
@qual variável, manipulador de modelo de
arquivos com, 92–99 119–122
string, 65
silenciando notificações, 147 filas e, lojas de modelos, 47
97–99 eventos enviados Instalação do Windows, x

pelo servidor em streaming, 91

[Link]
Machine Translated by Google

Índice • 189

X E
Cabeçalho X-Runtime, middleware, 145
Respondedor flash YAML, 114–119
armazenando dados de tradução,
155, 160

yield(), 147

[Link]
Machine Translated by Google

Dê um toque de diversão ao funcional.

Elixir traz de volta a "diversão" à programação funcional, em cima do ambiente robusto, testado em batalha e de
nível industrial do Erlang.

Você quer explorar a programação funcional, mas se sente


desanimado pela atmosfera acadêmica (fale-me mais uma vez sobre
mônadas). Você sabe que precisa de aplicações concorrentes, mas
também sabe que é quase impossível implementá-las corretamente.
Conheça o Elixir, uma linguagem funcional e concorrente construída
sobre a robusta máquina virtual Erlang. A sintaxe pragmática do
Elixir e o suporte integrado à metaprogramação tornarão você
produtivo e manterão seu interesse a longo prazo. Este livro é uma
introdução ao Elixir para programadores experientes.

Dave Thomas
(240 páginas) ISBN: 9781937785581. US$ 36
[Link]

Um jogo multiusuário, um site, um aplicativo em nuvem ou um banco


de dados em rede pode ter milhares de usuários simultaneamente.

interagindo simultaneamente. Você precisa de uma ferramenta


poderosa e robusta, de nível industrial, para lidar com os problemas
realmente complexos inerentes a ambientes paralelos e concorrentes.
Você precisa de Erlang. Nesta segunda edição do best-seller
Programando em Erlang, você aprenderá a escrever programas
paralelos que escalam sem esforço em sistemas multicore.

Joe Armstrong
(548 páginas) ISBN: 9781937785536. $42 http://
[Link]/book/jaerlang2

[Link]
Machine Translated by Google

A Alegria da Matemática e a Programação Saudável

Redescubra a alegria e a estranheza fascinante da matemática pura e aprenda a adotar uma abordagem mais saudável
para a programação.

A matemática é linda — e pode ser divertida e emocionante.

Além de teórico, este livro é prático e instigante. Boa Matemática é o


seu guia para alguns dos tópicos mais fascinantes de dois mil anos de
matemática: das frações egípcias às máquinas de Turing; do verdadeiro
significado dos números às árvores de provas, simetria de grupos e
computação mecânica. Se você já se perguntou o que existe além das
demonstrações que você se esforçou para completar nas aulas de
geometria do ensino médio, ou quais são os limites do computador na
sua mesa, este livro é para você.

Mark C. Chu-Carroll
(282 páginas) ISBN: 9781937785338. US$ 34
[Link]

Para continuar fazendo o que você ama, é preciso manter seus próprios
sistemas, não apenas aqueles para os quais você escreve código.
Exercícios regulares e uma alimentação adequada ajudam você a
aprender, lembrar, se concentrar e ser criativo — habilidades essenciais
para desempenhar bem o seu trabalho. Aprenda a mudar seus hábitos
de trabalho, domine exercícios que tornam o trabalho no computador mais
confortável e desenvolva um plano para se manter em forma, saudável
e com a mente afiada por muitos anos.

Este livro destina-se apenas a servir como um guia informativo para


aqueles que desejam saber mais sobre questões de saúde. De forma
alguma este livro pretende substituir, contradizer ou entrar em conflito
com o aconselhamento fornecido pelo seu profissional de saúde,
incluindo médicos, enfermeiros, assistentes médicos, nutricionistas e
outros profissionais licenciados.

Joe Kutner

(254 páginas) ISBN: 9781937785314. $36 http://


[Link]/book/jkthp

[Link]
Machine Translated by Google

A Web Moderna

Fique por dentro das técnicas mais recentes de HTML, CSS e JavaScript.

HTML5 e CSS3 são mais do que apenas palavras da moda — são a base
das aplicações web atuais.
Este livro coloca você a par dos elementos HTML5 e dos recursos CSS3 que
você pode usar agora mesmo em seus projetos atuais, com soluções
retrocompatíveis 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.

recursos, incluindo animações CSS, IndexedDB e validações do lado do


cliente.

Brian P. Hogan
(300 páginas) ISBN: 9781937785598. $38 http://
[Link]/book/bhh52e

Com o advento do HTML5, do MVC para front-end e do [Link], o

JavaScript tornou-se onipresente — e continua sendo uma linguagem complexa.


Este livro lhe dará uma base sólida para gerenciar tarefas assíncronas sem perder
a cabeça em meio a uma teia de callbacks. É um guia prático para as técnicas
mais essenciais para lidar com comportamento assíncrono, incluindo PubSub,
modelos orientados a eventos e Promises. Com esses truques na manga, você
estará mais bem preparado para gerenciar a complexidade de grandes

aplicativos web e entregar código responsivo.

Trevor Burnham

(104 páginas) ISBN: 9781937785277. US$ 17 http://


[Link]/book/tbajs

[Link]
Machine Translated by Google

Explore os testes e o Cucumber


Explore as águas desconhecidas dos testes exploratórios e aprofunde-se no Cucumber.

Descubra surpresas, riscos e bugs potencialmente graves com


testes exploratórios. Em vez de planejar todos os testes com
antecedência, os exploradores criam e executam experimentos
pequenos e rápidos, usando o que aprenderam com o
experimento anterior para orientar o próximo. Aprenda as
habilidades essenciais de um explorador experiente, incluindo
como analisar software para descobrir pontos-chave de
vulnerabilidade, como criar experimentos rapidamente, como
aprimorar suas habilidades de observação e como concentrar seus esforços.

Elisabeth Hendrickson
(160 páginas) ISBN: 9781937785024. $29 http://
[Link]/book/ehxta

Seus clientes querem um software robusto, livre de bugs e que


faça exatamente o que eles esperam. No entanto, nem sempre
conseguem articular suas ideias com clareza suficiente para que
você as transforme em código. O livro "The Cucumber Book"
aborda o cerne do problema: a comunicação entre as pessoas. O
Cucumber resolve o problema; é uma ferramenta de teste,
comunicação e levantamento de requisitos — tudo em um só lugar.

Matt Wynne e Aslak Hellesøy (336


páginas) ISBN: 9781934356807. $ 30 http://
[Link]/book/hwcuc

[Link]
Machine Translated by Google

A Estante Pragmática
A Pragmatic Bookshelf apresenta livros escritos por desenvolvedores para desenvolvedores. Os títulos seguem o estilo
consagrado do Pragmatic Programmer e continuam a receber prêmios e críticas excelentes. À medida que o desenvolvimento
se torna cada vez mais complexo, os Pragmatic Programmers estarão presentes com mais títulos e produtos para ajudá-lo a se
manter no topo do seu jogo.

Visite-nos online
Página inicial deste livro:
[Link]
jvrails2 Código-fonte deste livro, erratas e outros recursos. Venha nos dar seu feedback também!

Cadastre-se para receber


atualizações : http://
[Link]/updates Seja notificado quando atualizações e novos livros estiverem disponíveis.
Junte-se à comunidade :
[Link]
Leia nossos blogs, participe de nossas discussões online, inscreva-se em nossa lista de e-mails, interaja
com nossa wiki e beneficie-se da experiência de outros Programadores Pragmáticos.

Novidades e Destaques
[Link]
Confira os últimos desenvolvimentos pragmáticos, novos títulos e outras ofertas.

Compre o livro
Se você gostou deste e-book, talvez queira adquirir uma cópia impressa. Ela está disponível para compra
em nossa loja: [Link]

Contate-nos
Pedidos online: [Link]

Atendimento ao Cliente: support@[Link]

Direitos internacionais: translations@[Link]

Uso acadêmico: academic@[Link]

Escreva para nós: [Link]

Ou ligue para: +1 800-699-7764

[Link]

Você também pode gostar