Rails: Como o bloco respond_to funciona?

210

Estou examinando o guia Introdução ao Rails e fiquei confuso com a seção 6.7. Depois de gerar um andaime, encontro o seguinte bloco gerado automaticamente no meu controlador:

def index
  @posts = Post.all

  respond_to do |format|
    format.html  # index.html.erb
    format.json  { render :json => @posts }
  end
end

Eu gostaria de entender como o bloco respond_to realmente funciona. Que tipo de variável é formato? Os métodos .html e .json do objeto de formato? A documentação para

ActionController::MimeResponds::ClassMethods::respond_to

não responde a pergunta.

Cole
fonte
Seria bom se eu pudesse ligar para a documentação para ActionController :: MimeResponds :: ClassMethods :: respond_to mas api.rubyonrails.org não parece como hiperlinks diretos ...
Cole
respond_to aceita o final da chamada (por exemplo, blah.html, blah.json, etc) e corresponde à visualização especificada. Outras respostas podem ser XML, CSV e muito mais, dependendo do aplicativo.
21912 Scott
5
Como "corresponde à exibição especificada?"
Cole
Não acho que a extensão (xml, html, etc) seja mapeada para uma visualização. Se você escolher a renderização padrão ( format.html- sem argumento), ele usará convenções (com base no URL e no verbo HTTP) para escolher uma exibição (que se espera seja HTML). O respondedor (formato) é instruído aqui para renderizar URLs que terminam em .json serializando para json, em vez de usar visualizações e convenções.
Craig Celeste

Respostas:

188

Eu sou novo no Ruby e fiquei preso nesse mesmo código. As partes nas quais eu desliguei foram um pouco mais fundamentais do que algumas das respostas que encontrei aqui. Isso pode ou não ajudar alguém.

  • respond_toé um método na superclasse ActionController.
  • é preciso um bloco, que é como um delegado. O bloco é de doaté end, com |format|como argumento para o bloco.
  • respond_to executa seu bloco, passando um Respondente para o formatargumento.

http://api.rubyonrails.org/v4.1/classes/ActionController/Responder.html

  • A Respondernão contém um método para .htmlou .json, mas nós chamamos esses métodos de qualquer maneira! Esta parte me jogou para um loop.
  • Ruby tem um recurso chamado method_missing. Se você chamar um método que não existe (como jsonou html), Ruby chama o method_missingmétodo.

http://ruby-metaprogramming.rubylearning.com/html/ruby_metaprogramming_2.html

  • A Responderclasse usa isso method_missingcomo um tipo de registro. Quando chamamos 'json', dizemos para responder a solicitações com a extensão .json serializando para json. Precisamos chamar htmlsem argumentos para instruí-lo a lidar com solicitações .html da maneira padrão (usando convenções e visualizações).

Poderia ser escrito assim (usando pseudocódigo do tipo JS):

// get an instance to a responder from the base class
var format = get_responder()

// register html to render in the default way
// (by way of the views and conventions)
format.register('html')

// register json as well. the argument to .json is the second
// argument to method_missing ('json' is the first), which contains
// optional ways to configure the response. In this case, serialize as json.
format.register('json', renderOptions)

Esta parte me confundiu. Ainda acho isso pouco intuitivo. Ruby parece usar bastante essa técnica. A classe inteira ( responder) se torna a implementação do método. Para alavancar method_missing, precisamos de uma instância da classe, por isso somos obrigados a passar um retorno de chamada para o qual eles passam o objeto semelhante ao método. Para alguém que codifica em idiomas C há 20 anos, isso é muito retroativo e pouco intuitivo para mim. Não que isso seja ruim! Mas é algo que muitas pessoas com esse tipo de experiência precisam entender, e acho que pode ser o que o OP estava procurando.

ps note que no RoR 4.2 respond_tofoi extraído na gema dos respondedores .

Craig Celeste
fonte
Obrigado Craig, esse link também tinha uma tonelada de informações úteis, eu não percebi o quanto é possível method_missing, considerando que você pode passar argumentos e um bloco!
Aditya MP
2
Melhor resposta para explicar o uso de method_missing () como um mecanismo de registro na classe Responder! Eu também estava muito confuso com esse código.
Alan Evangelista
1
Os geradores de andaimes do Rails 6 parecem produzir código respond_tonos controladores, sem a gema dos respondedores presente no Gemfile. Talvez o pouco sobre respond_toser extraído na gema de resposta tenha sido alterado?
Qasim
106

Este é um bloco de código Ruby que tira proveito de um método auxiliar do Rails. Se você ainda não conhece os blocos, os verá bastante em Ruby.

respond_toé um método auxiliar do Rails anexado à classe Controller (ou melhor, sua superclasse). Ele está referenciando a resposta que será enviada para o View (que está indo para o navegador).

O bloco no seu exemplo está formatando dados - passando um paramater 'format' no bloco - a ser enviado do controlador para a visualização sempre que um navegador faz uma solicitação de dados html ou json.

Se você estiver em sua máquina local e tiver seu andaime Post configurado, poderá acessar http://localhost:3000/postse verá todas as suas postagens no formato html. Mas, se você digitar isto http://localhost:3000/posts.json:, verá todas as suas postagens em um objeto json enviado do servidor.

Isso é muito útil para criar aplicativos pesados ​​em javascript que precisam passar o json para frente e para trás a partir do servidor. Se você quisesse, poderia criar facilmente uma json api no seu back-end de trilhos e passar apenas uma visualização - como a visualização de índice do seu controlador Post. Em seguida, você pode usar uma biblioteca javascript como Jquery ou Backbone (ou ambos) para manipular dados e criar sua própria interface. Essas são chamadas de interfaces de usuário assíncronas e estão se tornando realmente populares (o Gmail é um). Eles são muito rápidos e proporcionam ao usuário final uma experiência mais semelhante à da área de trabalho na web. Obviamente, essa é apenas uma vantagem de formatar seus dados.

O jeito do Rails 3 de escrever isso seria o seguinte:

    class PostsController < ApplicationController
      # GET /posts
      # GET /posts.xml


      respond_to :html, :xml, :json

      def index
        @posts = Post.all

        respond_with(@posts)
      end

#
# All your other REST methods
#

end

Ao colocar respond_to :html, :xml, :jsonno topo da classe, você pode declarar todos os formatos que deseja que seu controlador envie para seus pontos de vista.

Em seguida, no método do controlador, tudo o que você precisa fazer é responder_com (@whatever_object_you_have)

Apenas simplifica seu código um pouco mais do que o que o Rails gera automaticamente.

Se você quer saber sobre o funcionamento interno deste ...

Pelo que entendi, o Rails introspecta os objetos para determinar qual será o formato real. O valor das variáveis ​​'format' é baseado nessa introspecção. O Rails pode fazer muito com um pouco de informação. Você ficaria surpreso com o quão longe um simples @post ou: post vai.

Por exemplo, se eu tivesse um arquivo parcial _user.html.erb parecido com este:

_user.html.erb

<li>    
    <%= link_to user.name, user %>
</li>

Então, isso sozinho na minha exibição de índice informaria ao Rails que ele precisava encontrar os 'usuários' parciais e iterar por todos os objetos 'usuários':

index.html.erb

 <ul class="users">
   <%= render @users %>     
 </ul>

permitiria ao Rails saber que precisava encontrar o 'usuário' parcial e iterar através de todos os objetos 'usuários':

Você pode achar útil este post do blog: http://archives.ryandaigle.com/articles/2009/8/6/what-s-new-in-edge-rails-cleaner-restful-controllers-w-respond_with

Você também pode ler a fonte: https://github.com/rails/rails

PhillipKregg
fonte
1
Ótima dica no caminho rails3. Ainda estou tentando chegar ao final do bloco respond_to e qual é o argumento do bloco | formato | é passado.
Cole
4
Boa resposta, mas não diz nada específico sobre a variável de formato que está sendo passada para o bloco. No exemplo dado, há format.html e format.json - esses dois são passados ​​para respond_to e respondem_to decidem o que fazer com eles?
Anthony
quando foi respond_toe respond_withintroduzido? Estou usando rails 2.3.5 e estou recebendoNoMethodError (undefined method respond_to)
abbood
10

Pelo que sei, respon_to é um método anexado ao ActionController, para que você possa usá-lo em todos os controladores, porque todos eles herdam do ActionController. Aqui está o método respond_to do Rails:

def respond_to(&block)
  responder = Responder.new(self)
  block.call(responder)
  responder.respond
end

Você está passando um bloco , como eu mostro aqui:

respond_to <<**BEGINNING OF THE BLOCK**>> do |format|
  format.html
  format.xml  { render :xml => @whatever }
end <<**END OF THE BLOCK**>>

O formato | parte é o argumento que o bloco está esperando; portanto, dentro do método respond_to, podemos usá-lo. Quão?

Bem, se você perceber que passamos o bloco com um prefixo & no método respond_to, e fazemos isso para tratá-lo como um Proc. Como o argumento tem o ".xml", ".html", podemos usá-lo como métodos a serem chamados.

O que basicamente fazemos na classe respond_to é chamar métodos ".html, .xml, .json" para uma instância de uma classe Respondente.

Nobita
fonte
1
A fonte de respond_to na API do docs é diferente da fonte que você incluiu e estava me excitando. Seu snippet deixa mais claro que o argumento do bloco de formato está sendo passado para um objeto Respondente. A documentação do Respondente parece responder à pergunta, lendo isso agora.
Cole
7

Eu gostaria de entender como o bloco respond_to realmente funciona. Que tipo de variável é formato? Os métodos .html e .json do objeto de formato?

Para entender o que formaté, você pode primeiro procurar a fonte respond_to, mas rapidamente descobrirá que realmente precisa ver o código de retrieve_response_from_mimes .

A partir daqui, você verá que o bloco que foi passado para respond_to(no seu código) é realmente chamado e passado com uma instância do Collector (que dentro do bloco é referenciada como format). O Collector basicamente gera métodos (acredito na inicialização do Rails) com base no que os tipos mime conhecem o rails.

Portanto, sim, os métodos .htmle .jsonsão definidos (em tempo de execução) na formatclasse Collector (aka ).

rnicholson
fonte
2

A metaprogramação por trás do registro do respondedor (consulte a resposta do Parched Squid) também permite que você faça coisas bacanas como esta:

def index
  @posts = Post.all

  respond_to do |format|
    format.html  # index.html.erb
    format.json  { render :json => @posts }
    format.csv   { render :csv => @posts }
    format.js
  end
end

A linha csv fará com que to_csv seja chamado em cada postagem quando você visitar /posts.csv. Isso facilita a exportação de dados como CSV (ou qualquer outro formato) do seu site de trilhos.

A linha js fará com que um arquivo javascript /posts.js (ou /posts.js.coffee) seja renderizado / executado. Descobri que essa é uma maneira leve de criar um site habilitado para Ajax usando pop-ups da UI do jQuery.

Catharz
fonte
1

Que tipo de variável é formato?

De um POV java, o formato é uma implementação de uma interface anônima. Essa interface possui um método nomeado para cada tipo MIME. Quando você invoca um desses métodos (passando um bloco para ele), se o Rails achar que o usuário deseja esse tipo de conteúdo, ele invocará seu bloco.

A reviravolta, é claro, é que esse objeto de cola anônimo não implementa realmente uma interface - ele captura as chamadas do método dinamicamente e funciona se é o nome de um tipo mime que ele conhece.

Pessoalmente, acho estranho: o bloco que você passa é executado . Faria mais sentido passar um hash de rótulos e blocos de formato. Mas - é assim que é feito no RoR, ao que parece.

PaulMurrayCbr
fonte
1

Isso está um pouco desatualizado, por Ryan Bigg faz um ótimo trabalho explicando isso aqui:

http://ryanbigg.com/2009/04/how-rails-works-2-mime-types-respond_to

De fato, pode ser um pouco mais detalhado do que você estava procurando. Acontece que há muita coisa acontecendo nos bastidores, incluindo a necessidade de entender como os tipos MIME são carregados.

idStar
fonte
0

"Formato" é o seu tipo de resposta. Pode ser json ou html, por exemplo. É o formato da saída que seu visitante receberá.

Rafael Nascimento
fonte
0

Há mais uma coisa que você deve estar ciente - MIME.

Se você precisar usar um tipo MIME e não for suportado por padrão, poderá registrar seus próprios manipuladores em config / initializers / mime_types.rb:

Mime::Type.register "text/markdown", :markdown


fonte