Chamando Services Object sem instância in Ruby

Thiago Lima
4 min readAug 16, 2023

--

Quando chamamos o método público de uma classe em Ruby, precisamos antes instanciá-la com o método construtor .new . Por exemplo, se precisamos chamar os métodos .price e .typeda classe Product a gente faria assim:

product = Product.new(params)
puts product.price
puts product.type

A partir da instancia de produto, eu posso acessar todos os seus métodos públicos.

Quando criamos uma classe Service Object, estamos criando um trabalhador, um funcionário que vai executar uma ação. É natural chamarmos essa classe com se fosse alguém que vai fazer algo. Exemplo: BookDestroyer, PluginConstructor, Finalizator.

Nosso service trabalhando!
Nosso Service trabalhando

O serviço tem um algorítimo com uma série de execuções que entrega um trabalho. Vamos ao exemplo do ProductCreator .

ProductCreator.new(params).call

Note que instanciamos a classe e já chamamos seu único método publico .call . É normal chamamos assim o método único de uma classe Service. Não é obrigatório, apenas uma convenção já praticada pelos desenvolvedores. Isso acontece porque o service só fará uma ação, que é a de executar o que a classe propõe. Desta forma seria interessante que não precisássemos instanciar a classe, para economizar espaço.

ProductCreator.call(params)

Convenhamos, é mais simples, fácil de ler e suave.

Você deve estar pensando, mas para fazer isso é só criar um método de class, assim não precisamos de instancias. Está certo em pensar assim, porém vamos fazer algo mais bem elaborado. Se a gente criasse apenas um método de classe, não estaríamos aproveitando todo potencial do paradigma orientado a objeto. Seria o mesmo que criarmos apenas um module e colocarmos uma função call, não é a nossa proposta.

Modelando um Serviço

class NameCreator
def initialize(nome)
@nome = nome
end

def call
puts "Nome: #{@nome}"
end
end

NameCreator.new('Thiago').call

Aqui temos uma classe que recebe um nome e imprime uma string contendo o nome passado no argumento. Se você executar este código no IRB ele irá retornar isto:

Nome: Thiago
=> nil

Chamada direta

Vamos adicionar à nossa classe um método de classe chamado call e dentro dela vamos instanciar a própria classe.

class NameCreator
def self.call(nome)
new(nome).call
end

def initialize(nome)
@nome = nome
end

def call
puts "Nome: #{@nome}"
end
end

NameCreator.call('Thiago')

Note que não deixamos de instanciar a classe, apenas é instanciada automaticamente pelo método de classe. Assim quando chamamos o método de classe .call ele estará criando a instancia e chamando o método call da instancia. new(nome).call

Se testar este código no IRB o resultado será o mesmo do anterior.

Reaproveitando o método call

Como nosso intuito é usar esse padrão em todas os nossos serviços, vamos extrai o método de classe .call para uma classe abstrata.

class ApplicationSevices
def self.call(name)
new(name).call
end
end

Ainda temos um problema! O parâmetro name só se aplica à classe NameCreator . Precisamos poder passar qualquer parâmetro no método de classe. Neste caso podemos passar um splat operator *args .

class ApplicationSevices
def self.call(*args)
new(*args).call
end
end

O splat operator vai cria um array de parâmetros. Isso vai resolver o problema da diferença de parâmetros de cadas classe.

A partir do Ruby 2.7, podemos contar com uma nova sugar syntax chamada de triple-dots.

class ApplicationSevices
def self.call(...)
new(...).call
end
end

Agora nossa classe abstrata está pronta.

Herdando a nossa classe abstrata

class ApplicationSevices
def self.call(...)
new(...).call
end
end

class NameCreator < ApplicationSevices
def initialize(nome)
@nome = nome
end

def call
puts "Nome: #{@nome}"
end
end

NameCreator.call('Thiago')

Agora você pode herdar em todos os seus Services Object.

Outro caminho

Outra forma de fazer, caso não queira trabalhar com herança, seria estendendo um modulo com o método call.

module Called
def call(...)
new(...).call
end
end

class NameCreator
extend Called

def initialize(nome)
@nome = nome
end

def call
puts "Nome: #{@nome}"
end
end

NameCreator.call('Thiago')

--

--

Thiago Lima

Hello! I’m Thiago Lima, I’m maried, I have a son named Isaac. I’m software engineer and I programming in Ruby on Rails.