Uma dúvida bastante comum quando começamos a estudar Ruby é a diferença entre @
, @@
e self
. Numa primeira análise, pode parecer que eles podem ser trocados indiscriminadamente e ainda atingem os mesmos resultados, mas essa não é a realidade. Podemos implementar o código de forma que eles retornem a mesma coisa, mas suas funções são totalmente distintas.
self
Você já deve saber que, em Ruby, tudo é objeto, inclusive as classes. O self
é uma referência ao objeto que recebe a chamada do método no contexto atual, vamos considerar o código:
nome = "André"
nome.length
# => 5
No exemplo acima, a variável nome
é o objeto no qual está sendo chamado o método length
. Quando queremos criar métodos de classe, utilizamos o self
em sua implementação:
class Exemplo
def self.um_metodo_de_classe
# código que faz alguma coisa
end
end
E esse método seria chamado dessa maneira: Exemplo.metodo_de_classe
. No contexto da implementação desse método, self
é a própria classe. Mas ele pode ser diferente dependendo de onde ele é chamado. Por exemplo, quando é chamado diretamente no IRB, temos:
puts self
#=> main
Já quando chamamos self
dentro de uma classe:
class Exemplo
puts self
end
#=> Exemplo
Note como dentro do contexto da classe, self
é a própria classe Exemplo
. Quando chamado no IRB, o self
é a main
.
Se quiser ver um pouco mais sobre o self
, recomendo esse artigo do Nando Vieira.
@ e @@
Resumidamente, usamos @
para definir variáveis de instância e @@
para variáveis de classe. Variáveis de instância ficam limitadas ao escopo da instância, enquanto as variáveis de classe estão disponíveis na classe inteira. Vamos considerar a seguinte classe Exemplo
:
class Exemplo
@@variavel_de_classe = "Variável da classe"
def initialize(variavel_de_instancia)
@variavel_de_instancia = variavel_de_instancia
end
Temos @@variavel_de_classe
e @variavel_de_instancia
, sendo a variável de instância definida no método construtor da classe. Vamos agora implementar alguns métodos para fazer alguns testes:
def variavel_de_instancia
@variavel_de_instancia
end
def self.variavel_de_instancia
@variavel_de_instancia
end
def variavel_de_classe
@@variavel_de_classe
end
def self.variavel_de_classe
@@variavel_de_classe
end
Vamos rodar esse classe no IRB e fazer alguns testes:
x = Exemplo.new("Instância 1")
x.variavel_de_classe
# => "Variável da classe"
x.variavel_de_instancia
# => "Instância 1"
Exemplo.variavel_de_classe
# => "Variável da classe"
Exemplo.variavel_de_instancia
# => nil
As variáveis ficam disponíveis ou indisponíveis dependendo da forma como foram chamadas. Até aqui, nenhuma novidade. Vamos implementar mais um método na classe:
def faz_algo
self.variavel_de_instancia
end
Agora no IRB podemos fazer:
x = Exemplo.new("Instancia 1")
x.faz_algo
# => "Instancia 1"
Assim acessamos a variável de instância por meio do método variavel_de_instância
. Mas em Ruby, nem sempre é necessário usar self
explicitamente, o método poderia ser
def faz_algo
variavel_de_instancia
end
e o código funcionaria da mesma maneira. O Ruby entende que variavel_de_instancia
não foi passada como argumento, nem definida dentro do método, então procura por um método de instância com o mesmo nome.
Uma coisa interessante que podemos fazer com esse conhecimento é utilizar o método class
para acessar a um método de classe. Considere, por exemplo, uma classe Usuario
:
class Usuario
@@todos_usuarios = []
def initialize(nome)
@nome = nome
self.class.todos << self
end
def self.todos
@@todos_usuarios
end
Sempre que uma instância de Usuario
é inicializada, ela se insere na variável de classe @@todos_usuarios
por meio do método de classe todos
.
user = Usuario.new('André')
Usuario.todos
# => [#<Usuario:0x00007fe5503b1d10 @nome="André">]