Ruby é muito similar a muitas outras linguagens orientadas a objeto. Podemos encontrar construções similares em linguagens não-dinâmicas como Java ou C#. Por outro lado, para começar a tocar nas possibilidades de Ruby é necessário investir tempo aprendendo o que chamamos de “Rubismos”. Um exemplo disso são Símbolos.
Isso é mais óbvio quando se começa a aprender Ruby através de Rails. Muito do poder de Rails vem do fato dele usar muitos rubismos. Vejamos um exemplo:
1 2 3 4 5 6 7 8 9 |
class Transact < ActiveRecord::Base validates_presence_of :when validates_presence_of :category, :account validates_presence_of :value validates_numericality_of :value belongs_to :category belongs_to :account end |
‘class’ é fácil, afinal, todas as grandes linguagens são ‘orientadas-a-objeto’. Mas o que são os “dois-pontos” por todo o código? Eles denotam Símbolos. Mais importante, esses dois-pontos representam inicializadores da classe Symbol.
Isso pode ser bem confuso considerando que o jeito normal de inicializar um objeto é:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
A chamada 'new' pede pelo método padrão 'initialize' definido dentro da classe. Acontece que esse método é privado na classe Symbol, a idéia sendo que todo símbolo deve ser instanciado com a notação de dois-pontos. Símbolos são usados como identificadores. Outras linguagens poderiam simplesmente usar Strings em vez de Símbolos. Em Ruby, ficaria parecido com isso: --- ruby class Transact < ActiveRecord::Base validates_presence_of "when" validates_presence_of "category", "account" validates_presence_of "value" validates_numericality_of "value" belongs_to "category" belongs_to "account" end |
Visualmente não ficou tão diferente: nos livramos dos dois-pontos e voltamos às confortáveis aspas. Parece a mesma coisa mas o comportamenteo é diferente. Como Símbolos em Ruby, Strings também tem um construtor especial. Em vez de fazer:
rubyString.new(“category”)
1 2 3 4 |
Apenas fazemos: --- ruby"category" |
Alguém poderia chamar esse tipo de atalho de “enfeite”, mas a linguagem seria bem chata sem eles. Usamos Strings o tempo todo, e seria muito doloroso instanciar novos Strings sem esse construtor especial: simplesmente escrevendo entre aspas.
O problema é, como Strings são fáceis de escrever, nós abusamos mais do que deveríamos. Existe um efeito colateral: cada nova construção instancia um novo objeto em memória, mesmo tendo o mesmo conteúdo. Por exemplo:
>> "category".object_id => 2953810 >> "category".object_id => 2951340
Aqui instanciamos 2 strings com o mesmo conteúdo. Cada objeto em memória tem um ID único de forma que cada string acima usa um pedaço separado de memória e tem IDs separados. Agora imagine que os mesmos strings acima apareçam centenas de vezes em diferentes lugares pelo seu projeto. Definitivamente estamos usando mais memória do que necessário.
Mas, isso não é um problema novo. Para isso, temos outra construção na maioria das linguagens chamada ‘constantes’, incluindo Ruby. Temos que planejar e pré-definir diversas constantes de ante-mão, de forma consciente. Então, nosso exemplo anterior, com uso de constantes ficaria assim:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Transact < ActiveRecord::Base ACCOUNT = "account" CATEGORY = "category" VALUE = "value" WHEN = "when" validates_presence_of WHEN validates_presence_of CATEGORY, ACCOUNT validates_presence_of VALUE validates_numericality_of VALUE belongs_to CATEGORY belongs_to ACCOUNT end |
Isso funciona, mas não é nem de perto mais legal. Primeiro, é preciso pré-definir tudo antecipadamente, seja na mesma classe ou em um módulo separado apenas para constantes. Segundo, o código é menos elegante, menos legível, portanto, de mais difícil manutenção.
Então, voltamos ao propósito de Símbolos: ser tão eficientes em consumo de memória quanto constantes mas tão agradáveis aos olhos como strings. Notação de aspas já é de Strings, palavras em maiúsculo para constantes, cifrão para variáveis globais e assim por diante. Então, dois-pontos foi um bom candidato.
Vejamos o que isso significa:
>> "string".object_id => 3001850 >> "string".object_id => 2999540 >> :string.object_id => 69618 >> :string.object_id => 69618
Como explicamos antes, os primeiros 2 strings têm o mesmo conteúdo e parecem similar, mas ocupam espaços diferentes de memória, permitindo duplicação desnecessária.
Os últimos 2 símbolos são exatamente a mesma coisa. Então posso chamar identificadores como símbolos por todo meu código sem me preocupar com duplicação em memória. Eles são fáceis de inicializar e fáceis de gerenciar.
Podemos também transformar um String em um Símbolo e vice-versa:
>> "string".to_sym => :string >> :symbol.to_s => "symbol"
Um lugar onde isso é usado muito bem é dentro do pacote ActiveSupport do Rails. Esse pacote foi feito para estender a linguagem Ruby e uma dessas extensões foi feita na comum classe Hash. Vejamos um exemplo:
>> params = { "id" => 1, "action" => "show" } => {"action"=>"show", "id"=>1} >> params["id"] => 1 >> params.symbolize_keys! => {:id=>1, :action=>"show"} >> params[:id] => 1
O primeiro comando linha instancia e popula um Hash (e temos mais uma notação especial de inicialização). O segundo comando pede pelo valor identificado pela chave “id”, que é uma string.
Em vez de fazer dessa maneira, podemos chamar o método symbolize_keys! para transformar todas as chaves string em chaves símbolo. Agora, no último comando podemos usar a notação mais comum em Rails de símbolos como chaves dentro de um Hash. Quando o Rails recebe um post de um formulário HTML, ele apenas recebe strings, portanto é seu trabalho converter tudo em objetos que façam sentido. Se você esteve no mundo Rails, já viu esse uso em controllers.
Então, isso é tuod que pode ser dito sobre Símbolos: construções muito simples que tornam o código mais legível e mais eficiente ao mesmo tempo, o que é compatível com a filosofia Ruby.