#224 Controllers in Rails 3
- Download:
- source codeProject Files in Zip (95.2 KB)
- mp4Full Size H.264 Video (19.7 MB)
- m4vSmaller H.264 Video (12.4 MB)
- webmFull Size VP8 Video (30.9 MB)
- ogvFull Size Theora Video (28.7 MB)
Enquanto as principais mudanças nos controllers do Rails 3 aconteceram nos "bastidores", existem algumas diferenças para os desenvolvedores. Este episódio vai cobrir algumas das novas funcionalidades que foram adicionadas.
Parâmetros no Log
A primeira mudança que vamos mostrar é no ApplicationController
. Em uma aplicação Rails 2, você pode filtrar certos parâmetros para não aparecerem em texto simples no arquivo de log, usando o método filter_parameter_logging
, como mostrado aqui:
<p>Esse método foi substituído (deprecated) no Rails 3 e deve ser removido da sua aplicação. A filtragem de parâmetros está configurada agora no arquivo <code>/config/application.rb</code>. O código que configura os parâmetros que devem ser filtrados está no final do arquivo.</p> ``` /config/application.rb # Configure sensitive parameters which will be filtered from the log file. config.filter_parameters += [:password]
Essa linha é adicionada por padrão nas novas aplicações Rails 3. É fácil esquecer de adicionar filtragem em uma aplicação, então é bom ter essa funcionalidade como padrão. Isso irá, da forma que está, filtrar somente o parâmetro de senha. Será necessário alterar se outro parâmetro, como um número de cartão de crédito, precisar ser filtrado também.
Modos de Redirecionamento
Em seguida vamos dar uma olhada no ProductsController
de uma aplicação de loja virtual que tenho escrito. E em como redirecionamentos são tratados quando uma action, como create
ou update
, é chamada em um controller típico Rails, com os sete métodos habituais RESTful. Se olharmos o código do método create
, veremos que depois de validado, o Product
foi salvo, a action define uma mensagem flash
e então redireciona para a action show
do produto recém-criado.
def create @product = Product.new(params[:product]) if @product.save flash[:notice] = "Successfully created product." redirect_to @product else render :action => 'new' end end
Esse comportamento é bastante comum em um controller e no Rails 3 podemos combinar essas duas linhas de modo que a mensagem flash
torna-se um parâmetro do método redirect_to
.
def create @product = Product.new(params[:product]) if @product.save redirect_to @product, :notice => "Successfully created product." else render :action => 'new' end end
As mensagens :notice
ou :alert
vão funcionar aqui, mas qualquer outro tipo de mensagem flash
terá que ser incorporada em um :flash
hash. Esse recurso também foi adicionado ao Rails 2.3.6, por isso, se você estiver usando a versão mais recente do Rails 2, então você pode usar esse atalho também.
O método redirect_to
acima tem uma instância de um model como parâmetro. Passar um objeto do model Product
, como @product
, é um atalho para o uso do método product_path(@product)
. Isso é traduzido para o path da action show
desse produto. Se quiséssemos redirecionar para a página de edição, em vez disso, poderíamos usar edit_product_path(@product)
. Mas existe um outro atalho que podemos usar. Em vez de escrever:
redirect_to edit_product_path(@product)
Poderíamos escrever:
redirect_to [:edit, @product]
Isso será traduzido para o mesmo path da action edit do produto acima, mas com um pouco menos de código. Como o :notice
, essa opção acima também funciona na última versão do Rails 2. Esse atalho funciona com recursos aninhados também. Então se um produto pertence a uma categoria, ele pode ser redirecionado com:
redirect_to [@category, @product]
Essa técnica de usar array funciona em qualquer lugar que você precisa gerar uma URL e pode ser usado no código das views em um método link_to
, bem como nos controllers.
Cookies Permanentes
O próximo recurso novo que vamos demonstrar são os cookies permanentes. Digamos que quando um usuário cria um novo produto, queremos armazenar o id
do produto em um cookie. Podemos usar o método cookies
para fazer isso:
def create @product = Product.new(params[:product]) if @product.save cookies[:last_product_id] = @product.id redirect_to @product, :notice => "Successfully created product." else render :action => 'new' end end
Esse código cria um cookie de sessão que durará apenas enquanto o usuário mantém a sua janela do navegador aberta. Para criar um cookie com uma data de validade explícita no Rails 2, teríamos que transformar o valor em um hash e definir um tempo de validade explícito assim:
cookies[:last_product_id] = { :value => @product_id, :expires => 1.month.from_now }
No Rails 3 (no Rails 2.3.6 e acima), podemos usar o novo método cookies.permanent
para criar um cookie com um tempo de vida longo.
cookies.permanent[:last_product_id] = @product.id
Nós também podemos criar cookies assinados dessa forma. Por enquanto não entramos em detalhes sobre isso aqui, mas há um ótimo post no blog do Pratik Naik sobre esse assunto, que merece leitura. Essencialmente podemos usar o método cookies.permanent.signed
para gerar um cookie criptografado e lê-lo novamente mais tarde. Note que vamos precisar obter o cookie_verifier_secret
em nossa aplicação para isso funcionar.
Usando respond_with
A última coisa que vamos mostrar neste episódio é provavelmente a mais significativa mudança nos controller do Rails 3: respond_with
. Nós vamos demonstrar isso adicionando um serviço REST sobre uma interface XML para o nosso ProductsController
. Isso significa que cada uma das actions do controller terá que devolver uma resposta XML, além de sua resposta padrão HTML.
Em uma aplicação Rails 2, nós implementaríamos isso usando respond_to
em cada action, assim:
def index @products = Product.all respond_to do |format| format.html format.xml { render :xml => @products } end end
Dentro do bloco respond_to
especificamos cada um dos formatos que a action responde, renderizando a versão XML do array de produtos para uma requisição XML. Essa abordagem pode tornar-se incômoda se for necessário fazer isso para cada action de um controller.
No Rails 3, podemos substituir esse código por uma chamada ao método respond_with
, passando o objeto apropriado, nesse caso, o array de produtos. Para o respond_with
funcionar também precisamos usar o método de classe respond_to
para especificar quais tipos MIME o controller deve responder e assim o respond_with
sabe o que responder com em cada action.
class ProductsController < ApplicationController respond_to :html, :xml def index @products = Product.all respond_with @products end end # Other methods end
Agora que mudamos o nosso ProductsController
para usar respond_with
, vamos ver se as alterações funcionaram. Se visitarmos a página index de produtos em um navegador, vamos ver a action index com o HTML renderizado como nós esperamos.
E se acrescentarmos .xml
à URL, vamos obter a resposta XML.
Como o método respond_with
funciona? Para uma simples requisição GET, como index
, ele procura, de acordo com o MIME type apropriado, pelo arquivo da view correspondente, como o arquivo .html.erb
, e renderiza. Se o respond_with
não conseguir encontrar o arquivo da view, então ele vai tentar chamar um método como o to_xml
ou o to_json
para deixar o objeto que foi passado no formato adequado.
Sabendo disso, podemos atualizar os métodos show
e new
no ProductsController
para usar o respond_with
.
def show @product = Product.find(params[:id]) respond_with @product end def new @product = Product.new respond_with @product end
A action create
é mais complicada, pois ela tem alguns códigos HTML específicos, como por exemplo, as chamadas para redirect_to
e render
. Nós não queremos usar esses códigos em uma resposta XML. Podemos contornar o problema removendo-os do método e adicionando uma chamada para respond_with
, deixando o método parecido com este:
def create @product = Product.new(params[:product]) if @product.save cookies[:last_product_id] = @product.id flash[:notice] = "Successfully created product." end respond_with(@product) end
Nós podemos fazer isso porque o respond_with
irá automaticamente redirecionar ou renderizar, dependendo se o objeto passado para ele é válido. Podemos agora acrescentar respond_with
aos outros métodos do controller, removendo qualquer código HTML específico. O código do nosso controller será parecido com este:
class ProductsController < ApplicationController respond_to :html, :xml def index @products = Product.all respond_with @products end def show @product = Product.find(params[:id]) respond_with @product end def new @product = Product.new respond_with @product end def create @product = Product.new(params[:product]) if @product.save cookies[:last_product_id] = @product.id flash[:notice] = "Successfully created product." end respond_with(@product) end def edit @product = Product.find(params[:id]) respond_with(@product) end def update @product = Product.find(params[:id]) if @product.update_attributes(params[:product]) flash[:notice] = "Successfully updated product." end respond_with(@product) end def destroy @product = Product.find(params[:id]) @product.destroy flash[:notice] = "Successfully destroyed product." respond_with(@product) end end
O método respond_with
pode ser um pouco confuso, por isso aqui vai um resumo.
Para uma requisição GET, respond_with
procurará primeiro por uma view que responde ao formato específico. Se encontrar irá renderizá-la; caso contrário ele irá chamar to_xml
(ou qualquer outro formato que foi solicitado) no objeto que foi passado para ele.
Para qualquer outro tipo de solicitação, respond_with
primeiro verifica se o objeto passado tem algum erro. Se isso acontecer, então a view apropriada será renderizada (por exemplo, new
para a action create
e edit
para a action update
). Se não houver nenhum erro, então ele irá redirecionar para a página específica do objeto (ou seja, a action show
).
Nós podemos testar isso editando um de nossos produtos.
Ao clicar no botão "Update Product" a action update
é chamada e respond_with
irá executar um redirecionamento para a página do produto.
Da mesma forma, se clicarmos no link "destroy", seremos redirecionados para a página index.
Se o comportamento padrão do respond_with
não satisfizer certas necessidades da nossa aplicação, podemos personalizá-lo para determinadas actions. Como um exemplo, digamos que quando atualizarmos um produto, nós queremos o redirecionamento para a action index
em vez do padrão que é a action show
. Nesse caso, podemos passar a opção de location
para o respond_with
para que ele redirecione para uma action diferente. Para redirecionar para a página index de produtos em vez de um produto específico da página, podemos alterar a action update
da seguinte forma:
def update @product = Product.find(params[:id]) if @product.update_attributes(params[:product]) flash[:notice] = "Successfully updated product." end respond_with(@product, :location => products_url) end
Também podemos substituir formatos específicos passando um bloco para o respond_with
, como faríamos com o respond_to
. Portanto, se queremos apenas rederizar algum texto para o formato XML da action edit, podemos fazê-lo.
def edit @product = Product.find(params[:id]) respond_with(@product) do |format| format.xml { render :text => "I'm XML!" } end end
Quando visitarmos a versão XML da página de edição, vamos ver essa string renderizada.
Finalmente, se precisamos de uma completa personalização, podemos passar uma classe responder personalizada. Dessa forma, o controller irá entregar tudo para a classe e o comportamento da resposta será como gostaríamos.
respond_with(@product, :responder => MyResponder)
Para um exemplo de criação de uma classe responder, dê uma olhada no Responder class do Rails 3. O código é bastante fácil de ler, está bem documentado e você terá uma ideia melhor de como funciona uma resposta.
Por esse episódio, é isso. Como você pode ver o respond_with
é uma maneira muito conveniente para lidar com vários formatos. Mas se você estiver personalizando-o muito, poderia ser mais fácil deixar tudo como está e não usá-lo. Alternativamente, se você tem um monte de duplicação, você pode criar sua classe de resposta personalizada.