Metaprogramação em Ruby - Criando métodos de Classe
Nem falo nada sobre quanto tempo sem escrever nada, mas vou contar uma novidade rapidinha: Agora Gaveteiro virou NEI. Mas vamos ao que interessa nesse post.
Métodos de Classe? Bom, sabemos que isso não existe em Ruby. Na verdade são métodos singleton da classe, mas enfim, vamos abistrair essa parte.
Bom, então, digamos que você vai mapear um recurso de uma API e ela tem um campo/atributo type
, e essa pode retornar os valores ClientA.administrador, ClientA.manager e ClientA.buyer como valores para este atributo. Mas você quer ter um código mais limpo, algo que você possa perguntar ao seu objeto: resource.admin?
em vez de ficar espalhado pelo seu código algo como resource.type == "ClientA.administrador"
e etc. E o mesmo para criar uma instância desse objeto, teria que fazer algo como Resource.new(type: 'ClientA.administrador')
. Não seria melhor ter algo como Resource.new_admin(...)
?. Nesse caso, vou apresentar dois métodos para criar dinamicamente métodos. o define_method
serve para criar os métodos de instância e o define_singleton_method
para criar os “métodos de classe”.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Resource < OpenStruct
include ActiveModel::Validations
TYPES = {
"admin" => "ClientA.administrator",
"manager" => "ClientA.manager",
"buyer" => "ClientA.buyer"
}.freeze
validates :api_id, presence: true
validates :type, inclusion: TYPES.values
TYPES.each do |key, value|
# define "question" methods for each type (e.g.: def admin?; type == "ClientA.administrador"; end)
define_method "#{key}?" do
type == value
end
# define constructors for each type (e.g.: Resource.new_admin({...}))
self.define_singleton_method("new_#{key}".to_sym) do |**args|
new(args.merge(type: value))
end
end
end
Então agora, é só usar
O que achou?
PS1: Claro que ainda estamos limitados a conhecer todos os valores que virão no atributo
type
. Se quisermos que seja 100% dinâmico… (Será que rola outro post?)PS2: Deu para perceber que desde o último post, estou fazendo integrações com APIs.
Referências:
- Stackoverflow - How to dynamically define a class method which will refer to a local variable outside?
- Medium Ruby Inside <3 - Class Methods Are Singleton Methods