Variáveis @/@@ e self em Ruby

Artigos - 17/Dez/2020 - por André Kanamura

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é">] 

Referências

Foto de perfil do autor
André Kanamura

Dev na Campus Code