Métodos que retornam mais de um valor em Ruby

em Tecnologia.

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.


Post original:
http://prodis.pro.br/2010/02/20/metodos-que-retornam-mais-de-um-valor-em-ruby

.

Você também pode gostar