Guia Avançado para Rails 4 Applications
Guia Avançado para Rails 4 Applications
[Link]
Machine Translated by Google
[Link]
Machine Translated by Google
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
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,
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
[Link]
Machine Translated by Google
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]
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.
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
[Link]
Machine Translated by Google
Conteúdo • vi
Í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!
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.
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.
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
trilhos -v
E você pode usar o comando `gem install` para obter a versão mais adequada:
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
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.
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]
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.
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.
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
Prefácio • xii
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.
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]
CAPÍTULO 1
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:
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]
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!
Criar
[Link]
Criar arquivo Rakefile
criar pdf_renderer.gemspec criar MIT-LICENSE
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]
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
3. [Link]
pdf_renderer/1_prawn/test/dummy/config/[Link] #
Configure as gems listadas no Gemfile.
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__)
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 $
Escrevendo o Renderizador • 5
# Executando testes:
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.
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?
fim
[Link]
errata do relatório • discutir
Machine Translated by Google
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.
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.
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:
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]
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
pdf_renderer/1_prawn/test/dummy/app/views/home/
[Link] Este modelo foi renderizado com o Prawn.
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"
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...
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
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.
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.
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
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.
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:
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.
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:
fim
Ao retornar um hash vazio, garantimos que nenhuma das ações no passe do controlador seja
atribuída à 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.
• 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
• ActionController::Instrumentation: Sobrecarrega o método render() para que ele possa realizar medições.
Quanto tempo foi gasto na pilha de renderização?
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.
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.
Voltando à nossa implementação de renderização, agora entendemos o que acontece quando adicionamos
a seguinte linha aos nossos controladores:
[Link]
errata do relatório • discutir
Machine Translated by Google
O fato de nosso renderizador depender de render_to_string() nos permite usar também as seguintes
opções:
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/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
[Link]
errata do relatório • discutir
Machine Translated by Google
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]/
CAPÍTULO 2
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!
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
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.
mail_form/1_attributes/test/mail_form_test.rb require
"test_helper" require "fixtures/
sample_mail"
fim
Ao executar o conjunto de testes com `rake test`, ele falha porque `MailForm::Base` ainda não está definido.
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
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.
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
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] = 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_'
[Link]
errata do relatório • discutir
Machine Translated by Google
def [Link](*names)
attr_accessor(*names)
define_attribute_methods(names) end protected
enviar(atributo).presentar? fim
fim
fim
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.
2. [Link]
mail_form/4_am_compliance/test/compliance_test.rb require
'test_helper' require 'fixtures/
sample_mail'
def setup
@model = [Link] end
fim
Ao executar o comando `rake test`, obtemos várias falhas, todas com o seguinte motivo:
fim
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
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.
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:
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
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.
[Link]
errata do relatório • discutir
Machine Translated by Google
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):
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
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" } }
[Link]!
fim
fim
[Link]
errata do relatório • discutir
Machine Translated by Google
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:
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:
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
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.
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
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.
mail_form/5_delivery/lib/mail_form/[Link]
entrega definida
se válida?
MailForm::[Link](self).deliver else false
fim
fim
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"
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
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)
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.
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"
mail_form/5_delivery/test/integration/navigation_test.rb requer
"test_helper"
click_button "Entregar"
3. [Link]
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
mail_form/5_delivery/test/dummy/config/[Link]
Dummy::[Link] do
recursos :contact_forms, somente: :criar raiz para:
"contact_forms#new" fim
mail_form/5_delivery/test/dummy/app/controllers/contact_forms_controller.rb classe
ContactFormsController < ApplicationController
def novo
def criar
@contact_form = [Link](params[:contact_form])
if @contact_form.deliver redirect_to
root_url, notice: "Sua mensagem foi enviada com sucesso." else
mail_form/5_delivery/test/dummy/app/views/contact_forms/[Link] <h1>Novo
Formulário de Contato </h1>
[Link]
errata do relatório • discutir
Machine Translated by Google
class="field">
<%= [Link] :email %><br /> <%=
f.text_field :email %> </div> <div
class="field">
<%= [Link] :message %><br /> <%=
f.text_field :message %> </div> <div
mail_form/5_delivery/test/dummy/app/models/contact_form.rb classe
ContactForm <MailForm::Base
atributos :nome, :email, :mensagem
cabeçalhos de definição
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
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.
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
valida_presença_de :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
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
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
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.
mail_form/6_final/lib/mail_form/[Link]
incluir MailForm::Validators
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 `
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
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
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.
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
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
fim
def inicializar(params={})
[Link] do |attr, value|
self.public_send("#{attr}=", value) end if params end
Concluindo • 37
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]
CAPÍTULO 3
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.
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.
[Link]
errata do relatório • discutir
Machine Translated by Google
é 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.
Em seguida, vamos definir o modelo que armazenará os templates no banco de dados usando o
Gerador de estrutura Rails:
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 :
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
Para uma requisição HTML na ação index() de um UsersController, esses argumentos são exatamente
como mostrado aqui:
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.
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
fim
fim
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.
Escrevendo o código
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 .
templater/1_resolver/test/models/sql_template_test.rb
requer 'test_helper'
# 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?
formato: "html",
localidade: "en",
manipulador: "erb",
parcial: falso)
fim
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.
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)
}
::[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
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]("/")
"_#{caminho}"
fim
fim
fim
fim
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:
Configurando um SqlResolver • 47
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.
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.
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.
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
O cache de resolvedores
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]
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
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.
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
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
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.
bar = "barra"
array = [foo, bar] hash = {a:
foo, b: bar}
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:
[Link]
errata do relatório • discutir
Machine Translated by Google
`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]
}
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:
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
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.
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
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]
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] }
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.
• 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.
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.
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
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"
templater/2_improving/Gemfile
grupo :test do gem
'capivara', '~> 2.0.0' end
templater/2_improving/test/integration/cms_test.rb requer
'test_helper'
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"
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!
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?
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:
rails/actionpack/lib/action_controller/[Link]
módulo ActionController
classe Base < Metal
resumo!
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
Concluindo • 59
fim
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
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.
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]
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.ÿ
4. [Link]
CAPÍTULO 4
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.
ActionView::Template.registered_template_handler("erb")
#=> #<ActionView::Template::Handlers::ERB:0x007fc722516490>
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".
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
=======
Obrigado por escolher nosso produto. Antes de usá-lo, você precisa confirmar sua
conta acessando o seguinte link:
[Link]
1. [Link]
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.
De fato, é bastante legível! A melhor parte é que pode ser transformado em HTML, que é
renderizado como mostrado na figura a seguir.
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.
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:
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"
[Link]
errata do relatório • discutir
Machine Translated by Google
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:
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:
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:
#{[Link] faça}
Isso não funciona como em ERB e é inválido.
#{fim}
2. [Link]
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
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.
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
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
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:
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
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:
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.
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
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**!
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
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
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
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].
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.
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,
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 estrutura de um gerador
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
[Link]
errata do relatório • discutir
Machine Translated by Google
Nas linhas seguintes, definimos o método create_mailer_file() , reproduzido aqui para sua conveniência:
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.
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
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:
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
• 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
Por outro lado, o gancho de estrutura cria todas as visualizações usadas na estrutura: index,
edit, show, new e a parcial _form .
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
fim
fim
fim
fim
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
def nome_do_arquivo_com_extensões(nome)
[nome, formato, manipulador].[Link](".") fim
fim
fim
fim
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>
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
rails/railties/lib/rails/generators/erb/mailer/templates/[Link] <%=
class_name %>#<%= @action %>
[Link]
errata do relatório • discutir
Machine Translated by Google
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
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 %>
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:
Esse comando cria um programa de e-mail chamado Mailer com dois modelos, [Link] e
[Link]. O gerador é executado, mostrando a seguinte saída:
Você também pode configurar seu aplicativo em test/dummy/config/[Link] para usar o gerador
merb por padrão, adicionando a seguinte linha:
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.
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.
• 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
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...
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"
fim
fim
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.
CAPÍTULO 5
Transmissão assíncrona de
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é
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.
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
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.
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.
rails/railties/lib/rails/engine/[Link]
caminhos de definição
[Link] "config"
[Link] "config/environments", glob: "#{[Link]}.rb"
[Link] "config/initializers", glob: "**/*.rb"
[Link] "db"
[Link] "db/migrate" [Link]
"db/[Link]"
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
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) # =>
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.
Transmissão ao vivo • 87
live_assets/1_live/app/controllers/live_assets_controller.rb classe
LiveAssetsController < ActionController::Base
incluir ActionController::Live
[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.
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]
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 :
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!
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]
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:
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:
resgatar IOError
[Link] fim
É 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');
}); };
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
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
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!
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
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.
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
fim
fim
fim
3. [Link]
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"
desmontar fazer
FileUtils.rm_rf "test/tmp" fim
[Link](assinante)
iniciar
enquanto [Link]?
# Acionar alterações em um arquivo até recebermos um evento [Link]("test/
tmp/sample", [Link](20)) end
# Limpar
[Link](assinante) [Link]
fim
fim
fim
[Link]
errata do relatório • discutir
Machine Translated by Google
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
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
se [Link]
LiveAssets.start_listener :reloadCSS, caminhos fim
fim
fim
fim
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.
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
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
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
Por fim, vamos reescrever nossa ação live_assets#sse para utilizar nosso novo assinante:
sse = LiveAssets::[Link]
[Link]
errata do relatório • discutir
Machine Translated by Google
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:
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)
[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
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]
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.
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.
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.
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]
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
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?
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.
[Link]
errata do relatório • discutir
Machine Translated by Google
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.
live_assets/3_final/lib/live_assets/[Link]
config.eager_load_namespaces << LiveAssets
live_assets/3_final/lib/live_assets.rb
módulo LiveAssets
estender ActiveSupport::Autoload
eager_autoload do
autoload :SSESubscriber
fim
fim
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]
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.ÿ,¹ÿ
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]
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.
[Link]
errata do relatório • discutir
Machine Translated by Google
CAPÍTULO 6
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`:
[Link] { redirect_to @user, notice: 'Usuário criado com sucesso.' } [Link] { render action: 'show', status: :created,
location: @user } else
fim
fim
privado
[Link]
errata do relatório • discutir
Machine Translated by Google
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:
def criar
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.
[Link]
errata do relatório • discutir
Machine Translated by Google
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.
Solicitação de navegação
PEGAR Solicitação de API renderizar modelo renderizar modelo ou renderizar recurso.to_format
PUBLICAR
COLOCAR
EXCLUIR
Até agora, sabemos como um controlador estruturado responde ao método GET em ambos os tipos de requisição.
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)
fim
fim
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
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
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.
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():
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
ActionView::MissingTemplate
=> e
comportamento_de_navegação(e)
fim
exibir_erros 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() .
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.
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
redirecionar_para_local_de_navegação fim
fim
resource, :status
=> :created, :location
=> api_location else
def has_errors?
resource.respond_to?(:errors) && ![Link]? end
# Resultados em:
#
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.
[Link]
errata do relatório • discutir
Machine Translated by Google
É 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.
[Link] = MyAppResponder
Além disso, podemos definir respostas personalizadas para controladores específicos em nossa aplicação:
Vamos criar um respondedor com algum comportamento extra e configurar o Rails para usá-lo.
isto.
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 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
fim
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
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
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.
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.
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
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: "%
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.
responders/1_flash/lib/[Link]
require "action_controller" require
"responders/flash" module Responders
fim
fim
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!" } } }
[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
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.
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 o parâmetro If-Modified-Since é fornecido e está atualizado, nosso controlador responde com o
status 304 e um corpo em branco.
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
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
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
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
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
ActionController::Base.perform_caching end
fim
fim
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!
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 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]
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.
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.
1. [Link]
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
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.
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
[Link]
errata do relatório • discutir
Machine Translated by Google
responders/3_final/lib/generators/responders/install/templates/[Link] <%
module_namespacing do -%>
privado
# 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
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.
$ 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.
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
rails/railties/lib/rails/generators/active_model.rb
módulo Rails
geradores de módulo
classe ActiveModel
# 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
fim
# POSTagem criar
salvar def
"#{name}.save" fim #
# POSTagem criar
# Atualização PATCH/PUT
def errors
"#{name}.errors" end
fim
fim
fim
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
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
decorre do fato de que qualquer ORM pode fornecer sua própria implementação dessa classe. Basta definirmos uma
Por exemplo, o DataMapper possui uma sintaxe diferente para encontrar e atualizar registros. Portanto, ele precisa herdar
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
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
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
Também demos outra olhada nos geradores do Rails e aprendemos mais sobre o agnosticismo de
ORM.
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]
CAPÍTULO 7
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.
1. [Link] 2.
[Link] 3.
[Link] 4.
[Link]
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:
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
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.
A API de Notificações
process_action("index") fim
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
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
mongo_metrics/1_engine/mongo_metrics.gemspec
s.add_dependency "mongoid", "~> 4.0.0"
$ rails g mongoid:config
5. [Link] 6.
[Link]
mongo_metrics/1_engine/lib/mongo_metrics.rb
require "mongoid" require
"mongo_metrics/engine"
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
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"
teste "a notificação process_action é salva no banco de dados mongo" faça evento =
"process_action.action_controller" payload = { "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
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
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
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
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
<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"
mongo_metrics/2_metrics/lib/mongo_metrics.rb
requer "jquery-rails"
mongo_metrics/2_metrics/app/assets/javascripts/mongo_metrics/[Link] //=
requer jquery //= requer
jquery_ujs //= requer_tree .
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
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 }
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
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.
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]
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:
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.
8. [Link]
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
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.
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
O Rails oculta a primeira dessas três camadas de middleware. Ele fica entre a web e o servidor web.
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 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
• 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.
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:
• Rack::ConditionalGet: Retorna um código de status 304 caso os cabeçalhos de cache HTTP apropriados
correspondam.
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:
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
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
def [Link]?
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
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
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:
• 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
mongo_metrics/3_final/lib/mongo_metrics/[Link] require
"mongo_metrics/mute_middleware"
fim
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:
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.
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
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`.
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.
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
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'
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
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]
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
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 cada
fim
fim
fim
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]
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
CAPÍTULO 8
Aplicativos 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
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.
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).
Para analisar essas responsabilidades mais detalhadamente, vamos começar desenvolvendo o nosso
Aplicativo de tradução:
1. [Link]
translator/1_app/config/[Link]
# Carregar a aplicação Rails. require
File.expand_path('../application', __FILE__)
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
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` .
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.
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"
} fim
fim
[Link]! execute
[Link]
[Link]
errata do relatório • discutir
Machine Translated by Google
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]`.
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.
[Link]
errata do relatório • discutir
Machine Translated by Google
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::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)
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:
[Link]
errata do relatório • discutir
Machine Translated by Google
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:
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'
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]
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
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:
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).
# [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
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"
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
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 .
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
protegido
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)
fim
fim
__FIM__
@@ índice !!!
%html
%head
%title
Translator::App %body
%h2=
"De #{@from} para #{@to}"
%p(style="color:green")= @message
- se @[Link]?
Nenhuma tradução disponível para #{@from} - else
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]
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
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"
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.
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]
enviadas pelo
formulário e decodifica # seus valores JSON para verificar a validade. def decoded_translations
translations = [Link]("from", "to") [Link] do |
key, value|
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!
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
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,
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'
$ 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.
translator/2_final/config/environments/[Link]
config.action_mailer.default_url_options = { host: 'localhost:3000' }
translator/2_final/app/views/layouts/[Link] <p
class="notice"><%= notice %></p> <p class="alert"><%=
alert %></p>
translator/2_final/config/[Link] raiz
para: "home#index"
5. [Link] 6.
[Link] 7.
[Link]
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:
Com a configuração concluída, estamos prontos para criar nosso primeiro modelo Devise, chamado Admin:
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:
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
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.
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]
Neste caso, poderíamos garantir a autenticação no nível do roteador sem precisar alterar o aplicativo Sinatra,
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
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.
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:
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
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.
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"
9. [Link]
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
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]
Índice
[Link]
Machine Translated by Google
Índice • 178
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
ControladorGerador, 75–78
[Link]
Machine Translated by Google
Índice • 180
[Link]
Machine Translated by Google
Índice • 181
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
[Link]
Machine Translated by Google
Índice • 183
[Link]
Machine Translated by Google
Índice • 184
[Link]
Machine Translated by Google
Índice • 185
[Link]
Machine Translated by Google
Índice • 186
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
[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
[Link]
Machine Translated by Google
Índice • 188
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
[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
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.
Dave Thomas
(240 páginas) ISBN: 9781937785581. US$ 36
[Link]
Joe Armstrong
(548 páginas) ISBN: 9781937785536. $42 http://
[Link]/book/jaerlang2
[Link]
Machine Translated by Google
Redescubra a alegria e a estranheza fascinante da matemática pura e aprenda a adotar uma abordagem mais saudável
para a programação.
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.
Joe Kutner
[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.
Brian P. Hogan
(300 páginas) ISBN: 9781937785598. $38 http://
[Link]/book/bhh52e
Trevor Burnham
[Link]
Machine Translated by Google
Elisabeth Hendrickson
(160 páginas) ISBN: 9781937785024. $29 http://
[Link]/book/ehxta
[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!
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]
[Link]