Ruby e as variáveis de classe

Recentemente percebi algo muito peculiar no comportamento do Ruby quando trata-se de variáveis de classe. Sabemos que:

class Foo
  @@bar = 'baz'
	
  def self.bar
    @@bar
  end
end

p Foo.bar #baz

Porém, certa vez, aconteceu um caso peculiar.

class User < ActiveRecord::Base
	@@default_user_locale = :en

	def default_user_locale
		 # …
	end
end

p User.first.default_user_locale

Isso, conforme o esperado, deveria retornar en, mas acredite, retornou outra coisa.

O fato

Até não ter ideia do que ocorrera, resolvi pesquisar. Encontrei pessoas afirmando que: "@@default_user_locale era uma palavra reservada do Rails" =S
Pode ser qualquer coisa, menos isto, afinal estamos falando de uma classe!

Daí, lendo o livro Eloquent Ruby, me deparei com o tal problema. Não é culpa do Rails (ou POG do Valim), e sim, um comportamento maluco do Ruby. O dito cujo, trata variáveis de classe como variáveis globais. Logo, se você possuir uma herança, onde em ambas classes a constante for definida, o valor da última classe a ser carregada definirá o valor da variável da classe.

Em termos práticos:

foo.rb

class Foo
	@@size = :small

	def self.size
	 @@size
	end
end

bar.rb

class Bar < Foo
	@@size = :large
	
	def self.size
	 @@size
	end	
end

run.rb

require 'foo'
require 'bar'

p Bar.size # => small

A solução

A sugestão para sanar este problema, de acordo com o autor do livro é utilizar variáveis de instância com métodos de acesso estáticos. Desta forma, o valor nunca será sobreescrito caso uma classe pai tenha definido a mesma variável internamente. Ficaria assim:

class Foo
	@size = :small

	def self.size
		@size
	end
end

Fica o alerta para evitar uma noite perdida ;)

Hélio Costa

Desenvolvedor de software há um tempinho. Interessou-se por Object-Oriented Design e Test-first antes do Eddy Merckx ser removido na organização da UCI Road World Championships.

Sao Paulo, Brazil