Métodos que retornam mais de um valor em Ruby

Nas últimas semanas, a equipe que eu trabalho estava desenvolvendo um web service onde havia a necessidade de renderizar o retorno de uma lógica de negócio em representações XML. Digo representações (no plural), pois para um retorno com sucesso a representação seria uma e para retorno com erro a representação seria outra.
Por exemplo, um retorno com sucesso:
<natural-person> <name>Prodis</name> <cpf>01234567890</cpf> </natural-person>
E um retorno sem sucesso:
<error> <description>CPF inválido.</description> </error>
Como era um web service em REST, para sucesso retornamos o código de status HTTP “200 OK” e, dependendo do não sucesso da operação, o código de status HTTP da resposta poderia ser “400 Bad Request”, “404 Not Found” ou qualquer outro código 4xx ou 5xx que melhor se adequasse.
Mantendo um controller magro, a idéia era somente instanciar uma classe de negócio, chamar um método e renderizar o retorno. Algo como:
class NaturalPersonController < ActiveRecord::Controller
def index
business = SomeBusiness.new
natural_person = business.search_by_cpf params[:cpf]
# TODO: Renderizar pessoa física ou mensagem de erro
end
end
A partir daqui a equipe iniciou uma discussão sobre a melhor forma de se obter o(s) retorno(s) esperado(s). O controller precisava saber se a consulta havia sido feita com sucesso, para renderizar um objeto NaturalPerson retornando o código de status HTTP 200, ou se a consulta não tivesse sucesso, renderizar a mensagem de erro retornando um código de status HTTP 4xx adequado.
Como “bons programadores .NET e Java”, a primeira coisa que pensamos foi lançar uma exceção customizada caso a consulta não tivesse sucesso, capturar essa exceção no controller e, através das informações de descrição de erro e código de status HTTP contidas nessa exceção, renderizar o retorno adequado.
class NaturalPersonController < ActiveRecord::Controller
def index
business = SomeBusiness.new
begin
natural_person = business.search_by_cpf params[:cpf]
render natural_person, 200
rescue BusinessException => e
render e.error, e.status_code
end
end
end
A gente não tinha visto muito código Ruby utilizando begin rescue, então essa solução não nos pareceu muito “Ruby way”. Achamos melhor pedir a opinião de alguém com mais experiência em Ruby. Perguntamos ao Rafael Rosa, que nos disse que cada vez que lançamos uma exceção em Ruby “alguma coisa ruim acontece no servidor” e consequentemente a aplicação ficará mais lenta.
Ele indicou um post falando a respeito:
http://www.simonecarletti.com/blog/2010/01/how-slow-are-ruby-exceptions
Rafael Rosa nos sugeriu retornar um array de duas posições: uma com o código de status HTTP e outra com o objeto a ser renderizado.
class NaturalPersonController < ActiveRecord::Controller
def index
business = SomeBusiness.new
result = business.search_by_cpf params[:cpf]
render result[1], result[0]
end
end
Resolveu, mas o código não ficou muito intuitivo. A partir daí imaginamos algumas outras soluções.
Retornar um hash:
class NaturalPersonController < ActiveRecord::Controller
def index
business = SomeBusiness.new
result = business.search_by_cpf params[:cpf]
render result[:data], result[:status_code]
end
end
Criar uma classe de retorno:
class SomeBusinessResult attr_accessor :status_code, :data end
class NaturalPersonController < ActiveRecord::Controller
def index
business = SomeBusiness.new
result = business.search_by_cpf params[:cpf]
render result.data, result.status_code
end
end
Posteriormente, o Rafael Rosa também sugeriu essa última opção, mas criando uma Struct ao invés de uma classe.
Então eu sugeri o método search_by_cpf retornar dois valores. Todos da equipe me perguntaram: “Como assim retornar dois valores?”. Falei que em Ruby um método pode retornar vários valores, que vi isso no livro The Ruby Programming Language.

O código do controller ficou bem mais intuitivo:
class NaturalPersonController < ActiveRecord::Controller
def index
business = SomeBusiness.new
status_code, data = business.search_by_cpf params[:cpf]
render data, status_code
end
end
O método search_by_cpf está retornando tanto o código de status HTTP quanto os dados para serem renderizados:
class SomeBusiness
def search_by_cpf(cpf)
# Lógica de negócio aqui
return 200, natural_person
end
end
Note que mesmo a linha 4 sendo a última linha de instrução do método, o retorno de mais de um valor obrigatoriamente precisa utilizar o comando return.
Quando há mais de um valor de retorno para um método, os valores são colocados implicitamente dentro de uma array e essa array fica sendo o único retorno do método.
O mesmo resultado seria obtido dessa forma:
class Business
def search_by_cpf(cpf)
# Lógica de negócio aqui
[200, natural_person]
end
end
Quem está consumindo um método que retorna mais de um valor, pode utilizar o recurso de atribuição paralela do Ruby para distribuir os valores de retorno em variáveis distintas, como é o caso no nosso controller:
class NaturalPersonController < ActiveRecord::Controller
def index
business = SomeBusiness.new
status_code, data = business.search_by_cpf params[:cpf]
render data, status_code
end
end
O dinamismo do Ruby lhe oferece várias opções para você encontrar soluções para o mesmo problema ou questão. Cabe a você decidir qual melhor abordagem para seu tipo de problema. O interessante é você conhecer essas opções para facilitar a sua decisão.












Comentários