params.expect onde antes geravam require e permit. A nova forma é mais curta. Ela também corrige um problema de ordem. Esse problema gera exceções evitáveis em aplicações Rails desde que os strong parameters existem.
O que este tutorial cobre
- A exceção que
requireseguido depermitgera com uma entrada adulterada. - Por que inverter a ordem corrige o problema, e por que
expecté melhor do que fazer isso você mesmo. - Como declarar um parâmetro como array para que ele não possa chegar como hash.
Requisitos
O problema com require seguido de permit
A forma conhecida é:require(:post) retorna o que estiver naquela chave e garante apenas que ela esteja presente. permit é então chamado sobre o resultado.
Isso funciona bem enquanto post é um hash. Não funciona quando alguém envia ?post=hello. require retorna sem problemas a string "hello", permit é chamado sobre uma String, e a requisição falha com:
NoMethodError é o resultado errado. Os parâmetros estavam malformados, o que é um erro do cliente, e os strong parameters existem exatamente para rejeitar entradas malformadas. Em vez disso, é o código que deveria higienizar a entrada que quebra. Bots que sondam uma aplicação encontram isso rapidamente, e é por isso que esses erros aparecem em volume no monitoramento de erros.
Por que inverter a ordem corrige o problema
Chamepermit primeiro, e o formato é verificado antes que qualquer coisa seja extraída:
permit avalia post em relação a um formato declarado: “um hash que pode conter title e body”. Uma string não corresponde a isso, então é descartada em vez de repassada. require então gera ActionController::ParameterMissing, que o Rails já trata como um 400.
O que o expect faz
params.expect é essa ordem, já embutida:
ParameterMissing em vez de NoMethodError, sem que você precise lembrar em qual ordem escrever os dois métodos.
Declarando arrays
O segundo problema queexpect corrige não pode ser expresso com permit de forma alguma.
Considere categorias aninhadas. Com permit, enviar post[categories][name]=foo produz um único hash, quando seu código espera um array deles. Os parâmetros são permitidos, os tipos estão errados, e a falha aparece mais tarde, em outro lugar.
expect permite exigir um array, usando um segundo par de colchetes:
Migrando
expect é opt-in, e require e permit não estão descontinuados. Praticamente toda aplicação Rails existente os utiliza, então eles vão continuar existindo por muito tempo.
- No Rails 8: migre seus controllers para
expect. O próprio Rails migrou seus controllers internos, incluindo o Action Mailbox, na mesma mudança. A alteração é mecânica. Substituarequireepermitporexpect, e adicione um segundo par de colchetes em torno de tudo que deveria ser um array. - Ainda não no Rails 8: escreva
permitantes derequire. Você ganha o benefício da ordem sem precisar fazer o upgrade.
O que isso muda no seu monitoramento de erros
Este é um dos poucos refactorings com um efeito diretamente visível na sua lista de issues.NoMethodError: undefined method 'permit' for an instance of String é ruído. Ele vem de quem estiver sondando sua aplicação, não dos seus usuários. Ele não carrega nenhuma informação depois da primeira ocorrência, e disputa atenção com regressões reais. Migrar para expect transforma esses erros em ParameterMissing, que o Rails responde com um 400 sem propagar pela sua aplicação.
Duas coisas valem a pena fazer junto com a mudança:
- Acompanhe a issue no monitoramento de erros para confirmar que ela para depois do deploy. Adicione um marcador de deploy para que o antes e depois fique visível no gráfico. Os parâmetros da requisição em cada issue mostram o que foi enviado. Você pode usar isso para diferenciar uma sondagem de um formulário que sua própria aplicação envia.
- Se ainda restar algum volume, ignore o erro em vez de deixá-lo parado na sua lista. Ignore-o porque você já estabeleceu que se trata de ruído do lado do cliente, não porque ele é frequente.
Transcrição
Transcrito do vídeo e levemente editado. As legendas automáticas entenderam errado vários nomes de produtos e de APIs, e esses foram corrigidos.Ler a transcrição
Ler a transcrição
0:02 Olá, pessoal, neste episódio vamos falar sobre o novíssimo
params.expect no Rails 8, que você talvez já tenha ou ainda não tenha adotado. Você vai ver isso nos seus scaffolds, onde aparece params.expect para o ID, e também params.expect para o nome do seu model, como post com title e body. E isso resolve alguns problemas da forma antiga de fazer isso. Então vamos dar uma olhada em alguns desses problemas. A forma antiga de fazer isso seria dizer require post, permit, os atributos title e body, certo?0:35 Se entrarmos na nossa aplicação Rails, aqui eu peguei a nova action e estou apenas exibindo os parâmetros que são permitidos ali. Agora vemos que params.expect está gerando um erro dizendo que não há post. Agora vamos usar params.require. Isso também vai dizer que é inválido porque não há post nos parâmetros. Então vamos adicionar post, e normalmente fazemos algo assim, onde um atributo como title fica entre colchetes ao lado de post, para que ele saiba colocar isso dentro de post e transformar isso em um hash.1:10 No entanto, frequentemente temos usuários maliciosos, bots ou qualquer coisa do tipo bisbilhotando e alterando coisas. E se dissermos post igual a hello, sendo uma string em vez de um hash, nossos parâmetros vão explodir com esse erro de método inexistente, em vez de realmente higienizar esses parâmetros como deveria ser. Então esse é um problema em que temos params.require post. Isso vai nos dar, vamos ampliar isso um pouco, isso vai nos retornar a string hello, e obviamente permit não é um método nessa string.1:42 Ele precisa estar em um objeto de parâmetros. Então a forma de corrigir isso é na verdade inverter a ordem. Podemos fazer isso dizendo params.permit post com title e body. E também dizemos a ele para fazer esse require de post. Para testar isso, vamos ver se esse require falha com a mensagem de erro correta, o erro de parâmetro ausente.2:13 E isso acontece porque quando params.permit olha para isso, ele vai avaliar post e ver que, ah, isso precisa ser um hash em que title é opcional dentro dele. E isso é uma string, não um hash. Então não devemos permitir esse parâmetro. E aí está, obtemos um item vazio aqui, o que então nos permite exigir post. Mas todos os nossos scaffolds e controllers fizeram o oposto desde que os strong parameters existem: eles fizeram o require primeiro.2:47 E esse é um dos problemas que params.expect resolve para nós. Se executarmos isso, vamos obter automaticamente esse método de parâmetro ausente, ou parâmetro post ausente, com o erro de parâmetro ausente, exatamente como vemos aqui de forma padrão. Mas não precisamos especificar esse require duas vezes. Ele vai simplesmente cuidar de toda essa parte para nós, o que é ótimo. Então isso simplifica um pouco as coisas e torna isso um pouco mais seguro, de forma que pessoas adulterando parâmetros vão receber os erros corretos de requisições inválidas.3:29 Então isso vai higienizar as coisas de forma um pouco mais tranquila. Mas o que acontece quando temos algo como um item aninhado, por exemplo categories, e categories poderia ter um name dentro dele? Se fizermos isso e dissermos post com, digamos, title e post categories como foo, isso vai constatar que categories não era um hash, era apenas uma string. Então isso não é permitido nesses parâmetros.4:01 Mas se dissermos categories name foo, isso vai definir categories para esses parâmetros permitidos filhos. Mas isso está errado, porque queremos um array em vez de um único item. E então nossos parâmetros ainda são algo que pode ser um pouco adulterado aqui, porque precisamos daqueles colchetes vazios internos para dizer ao Rack e ao Action Dispatch que isso precisa ser interpretado como um array em vez de um objeto, como um hash. Então isso é algo que não é realmente possível fazer no params.permit, mas podemos fazer isso aqui. E a forma de fazer isso é dizer categories e usar colchetes duplos, de forma que o primeiro colchete diga, olha, isso vai ser um array, e depois os atributos permitidos dentro desse array para cada um dos objetos.4:59 Então, se atualizarmos isso agora, vamos obter a coisa correta, com colchetes para a categoria. Então obtemos um array. Se tentássemos adulterar isso e atribuir categories a um hash em vez disso, isso vai dizer que não é permitido, e resolver isso completamente. E você vai ver que, se reativarmos a forma antiga de fazer isso, categories ainda vai ser definido, mas para um único item em vez de um array, o que não é o que queremos. Isso não está sendo validado e higienizado corretamente aqui. Então params.expect é uma ótima melhoria só para tornar isso mais consistente e menos adulterável.5:41 Então precisamos fazer isso corretamente para obter esses tipos. Precisa ser um hash, um array ou um valor individual, e podemos dizer isso a ele, e ele vai impor isso como deveria. Agora uma coisa que quero destacar antes de terminarmos é que params.expect é opt-in, os controllers da sua aplicação Rails existente não precisam usar isso. permit e require provavelmente vão continuar existindo por muito tempo, porque praticamente todas as aplicações Rails os utilizam atualmente, e seria uma mudança muito grande exigir que todos migrassem para expect. E, embora permit e require continuem funcionando, params.expect é realmente a forma recomendada de fazer isso, só para manter as coisas um pouco mais seguras nas suas aplicações Rails. Então, se você puder atualizar para o Rails 8, definitivamente atualize seus controllers para a nova sintaxe, e se você ainda não puder atualizar para o Rails 8, use permit e depois require nessa ordem, para que você ganhe um pouco mais desse benefício de ordem de execução ao permitir esses parâmetros.6:46 Então, quando pessoas fazem esses post igual a name ou foo maliciosos, ou qualquer coisa do tipo, e enviam uma string em vez de um hash ou array, você vai estar protegido por isso, e vai ter menos erros no seu monitoramento de erros, e isso vai ser tratado automaticamente para essas situações, o que é ótimo. Então eu recomendo fortemente fazer pelo menos isso, mas se você puder chegar ao Rails 8, cuide de todos esses métodos de parâmetros e garanta que use params.expect em todos os lugares que puder. Se você quiser ver alguns detalhes internos disso, Martin Emde, que trabalha no RubyGems e no Bundler, criou esse PR para o Rails em abril, que finalmente foi mesclado em setembro, e há muita discussão boa sobre como isso deveria funcionar, e os idas e vindas sobre os vários pontos, porque essa é uma funcionalidade bem central do Rails que está sendo alterada nesse momento. Então é um PR bem grande aqui, e ele atualiza toda a Action Mailbox e outros controllers internos dentro do Rails para também usar params.expect, e tudo isso é bem simples, na verdade.7:59 É basicamente adicionar expect, remover require e permit, e depois envolver quaisquer desses arrays com um colchete extra para lidar com isso. Mas dê uma olhada nesse PR, é realmente uma ótima discussão se você quiser ver o que acontece em uma funcionalidade central como essa pela equipe principal do Rails. Então é isso, params.expect eu acho que vai ser uma ótima melhoria, só protegendo nosso código um pouco mais, e vamos nos beneficiar disso com menos desses problemas de método permit indefinido sempre que alguém estiver bisbilhotando onde não deveria. Então é isso para este episódio, espero que tenham gostado, e nos falamos no próximo. Paz!Tutoriais relacionados
Sobre este tutorial
Este tutorial resume um screencast da GoRails sobreparams.expect no Rails 8, de Chris Oliver. O screencast é a obra original. A GoRails o publica, junto com o resto da série, em gorails.com.