Nesse post vou mostrar como usar o method_missing do ruby para implementar uma fluent interface em um cliente de um webservice REST. Vamos supor um webservice imaginário com recursos que podem ser acessados nas seguintes URLs:
/recurso/id
/recurso/doTipo/algumTipo
Queremos que um cliente ruby para esse webservice forneça a seguinte fluent interface para acesso às URLs acima:
WebService.new.recurso(1).get WebService.new.recurso.do_tipo(algumTipo).get
Uma maneira mais tradicional de se implementar esse cliente seria algo como:
require 'net/http'
require 'uri'
class WebService
def initialize
@url = String.new
end
def recurso(id = nil)
@url << '/recurso'
@url << "/#{id}" unless id.nil?
self
end
def do_tipo(tipo)
@url << "/doTipo/#{tipo}"
self
end
def get
response = Net::HTTP.get_response(URI.parse("http://localhost:8080#{@url}"))
response.value
response.body
end
end
Repare que nos métodos recurso e do_tipo é retornado self, possibilitando o encadeamento dos métodos na fluent interface. No método get, response.value sobe uma exceção se o retorno do webservice for qualquer coisa diferente de 200 e response.body retorna o corpo do response.
Utilizando o method_missing a implementação fica bem mais simples e elegante:
require 'net/http'
require 'uri'
class WebService
def initialize
@url = String.new
end
def get
response = Net::HTTP.get_response(URI.parse("http://localhost:8080#{@url}"))
response.value
response.body
end
def method_missing(metodo, *args)
@url << "/#{camel_case(metodo)}"
@url << "/#{args[0]}" if args.length > 0
self
end
private
def camel_case(metodo)
primeira = true
metodo.to_s.split('_').map {|palavra|
if primeira
primeira = false
palavra
else
palavra.capitalize
end
}.join
end
end
Uma característica interessante da implementação com method_missing é que ela serve para qualquer URL de qualquer recurso que já exista ou que venha a ser criada (obviamente isso só vale se houver a padronização entre a URL e a fluent interface).
Posted by Guilherme Cirne