Por ser uma linguagem orientada a objetos, Ruby possui variáveis de instância e de classe. As primeiras se referem a cada instância de uma determinada classe e as segundas à própria classe:

class Funcionario
  @@dias_de_ferias = 30

  def salario=(valor)
    @salario = valor
  end

  def salario
    @salario
  end
end

Como esperado, cada instância da classe Funcionario possui uma instância da variável @salario, e a variável @@dias_de_ferias possui uma única instância:

Funcionario.class_variable_get(:@@dias_de_ferias)  # 30

funcionario1 = Funcionario.new
funcionario1.salario = 2000
funcionario1.salario  # 2000

funcionario2 = Funcionario.new
funcionario2.salario = 2500
funcionario2.salario  # 2500

Até aqui nada de novo. Porém, um tipo de variável menos conhecida e usada é a variável de instância de classe.

Como tudo em Ruby é um objeto, todas as classes (tanto as classes padrão do Ruby quanto as criadas pelo usuário) são objetos - instâncias da classe Class:

String.class  # Class

Funcionario.class  # Funcionario

E, como são objetos, as classes também podem ter variáveis de instância. Para definí-las e acessá-las, basta prefixar o nome da variável com [email protected], da mesma forma como é feito com variáveis de instância, porém no escopo de classe (ou num método de classe):

class Funcionario
  @bonus = 1000

  def self.atualiza_bonus
    @bonus = 2000
  end
end

O comportamento de variáveis de instância de classe é semelhante às variáveis de classe, com uma diferença: quando usamos herança, as variáveis de classe são compartilhadas entre todas as classes da hierarquia, e as variáveis de instância de classe tem uma instância para cada classe:

class Funcionario
  @@dias_de_ferias = 30
  @bonus = 1000
end

class Gerente < Funcionario
  @bonus = 5000
end


Funcionario.class_variable_get(:@@dias_de_ferias)  # 30
Funcionario.instance_variable_get(:@bonus)  # 1000

Gerente.class_variable_get(:@@dias_de_ferias)  # 30
Gerente.instance_variable_get(:@bonus)  # 5000

Gerente.class_variable_set(:@@dias_de_ferias, 45)
Gerente.class_variable_get(:@@dias_de_ferias)  # 45
Funcionario.class_variable_get(:@@dias_de_ferias)  # 45

No exemplo acima, a variável de classe @@dias_de_ferias é compartilhada entre as classes Funcionario e Gerente. Por isso, ao alterar o valor desta variável na subclasse, o valor na superclasse também mudou. Para confirmar que a instância é a mesma, basta verificar que o object_id da variável em ambas as classes:

Funcionario.class_variable_get(:@@dias_de_ferias).object_id  # 70139308064800
Gerente.class_variable_get(:@@dias_de_ferias).object_id  # 70139308064800

No caso da variável de instância de classe @bonus, há uma instância para a classe Funcionario e outra para a classe Gerente:

Funcionario.instance_variable_get(:@bonus).object_id  # 70139307998300
Gerente.instance_variable_get(:@bonus).object_id  # 70139308064780

Na última semana aconteceu a última edição do FISL, o Fórum Internacional de Software Livre. No segundo dia do evento, fiz uma palestra sobre Design Patterns em Ruby, a qual foi baseada neste post. Todas as palestras do evento foram transmitidas ao vivo, e os vídeos já estão disponíveis.

Disponibilizei o vídeo da minha apresentação no Vimeo e os slides no Slideshare.

UPDATE: atualizei os slides após apresentar esta mesma palestra no evento RS on Rails.


Ao criar um novo projeto Rails, o generator cria uma estrutura padrão de diretórios. Dentro de app, ele cria os diretórios models, controllers, views e helpers. Os três primeiros tem papéis bem definidos, mas mesmo assim há uma certa confusão quando surge algum arquivo “fora do padrão”.

Numa aplicação típica, um model geralmente estende a classe ActiveRecord::Base ou inclui um módulo, como Mongoid::Document, no caso do Mongoid, por exemplo, para mapear a estrutura do banco de dados. Além disso, o model contém as regras de negócio associadas a ele. O controller tem a responsabilidade de mapear a ação atual numa view - por exemplo, ao submeter um formulário para criação de um novo objeto, um controller típico renderiza uma view exibindo uma mensagem de sucesso, ou renderiza a mesma view do formulário com as mensagens de erro, caso haja algum. Já a view é responsável por exibir os dados correspondentes à página atual.

Essa estrutura básica funciona bem numa aplicação simples. O problema é quando a view começa a conter muita lógica. Por exemplo, uma view para exibir dados de um usuário poderia ser simples assim:

@usuario.nome

Porém, se o conteúdo muda dependendo do tipo de usuário (ex: usuário comum e admin), precisamos de um if dentro da view:

<% if @usuario.admin? %>
  admin
<% else %>
  @usuario.nome
<% end %>

Quando mais diferenças houver, mais complexa fica a view. Consequentemente, fica mais difícil de gerenciar. Além disso, normalmente fazemos testes unitários para o model e o controller, e testamos a view somente com testes de aceitação, que são muito mais lentos (é preciso carregar todo o ambiente, Rails, banco de dados, e dependendo do teste, abrir um browser). Fica impraticável testar todos os fluxos de uma view cheia de if’s usando testes de aceitação.

Uma solução comum no mundo Rails é usar os helpers. No exemplo acima, eu poderia ter o seguinte helper:

class UsuarioHelper
  def titulo_usuario(usuario)
    usuario.admin? ? "admin" : usuario.nome
  end
end

Isso deixa o código da view mais simples:

titulo_usuario(@usuario)

E, além disso, posso testar a lógica num teste unitário do helper. Mas essa solução também tem problemas: o helper não está associado diretamente ao objeto em questão. Isso ficou claro no exemplo acima, onde precisei passar o usuário como parâmetro para o método do helper. Isso se repetiria para cada método.

Uma boa solução para este caso é utilizar o design pattern Decorator. Para isso, criamos uma classe que recebe o model como parâmetro no construtor e implementa todos os métodos necessários para lógicas de visualização (ou seja, que não estão associados ao negócio e não devem ficar no model). Um exemplo de decorator é o seguinte:

class UsuarioDecorator
  attr_reader :usuario

  def initialize(usuario)
    @usuario = usuario
  end

  def titulo
    @usuario.admin? ? "admin" : @usuario.nome
  end
end

Outra opção é criar um decorator que também implementa o padrão Delegation. Neste caso, quando é chamado um método que não existe, o Decorator delega a chamada para o model. Segue um exemplo de implementação:

module Decorator
  attr_reader :model

  def initialize(model)
    @model = model
  end

  def method_missing(meth, *args)
    if @model.respond_to?(meth)
      @model.send(meth, *args)
    else
      super
    end
  end

  def respond_to?(meth)
    @model.respond_to?(meth)
  end

  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def decorate(object)
      if object.is_a? Enumerable
        object.map {|obj| self.new(obj)}
      else
        self.new(object)
      end
    end
  end
end


class UsuarioDecorator
  include Decorator

  def titulo
    @model.admin? ? "admin" : @model.nome
  end
end

Desta forma, temos uma classe que recebe o model no construtor ou no método de classe decorate. A implementação do método titulo_usuario no Decorator ficou muito mais simples. Para utilizá-la, basta decorar o model no controller:

class UsuariosController
  def show
    @usuario = UsuarioDecorator.decorate(Usuario.find(params[:id]))
  end
end

A implementação da view fica assim:

@usuario.titulo

Em casos mais simples, o Decorator atende bem. Mas e quando temos uma página mais complexa, envolvendo diversos objetos? Precisaríamos criar um Decorator para cada model, e lembrar de decorar cada objeto na criação, assim como fizemos com o usuário no exemplo anterior. Outro problema é que podemos ter visualizações diferentes de um objeto em cada tela da aplicação. Como tratar este caso? Poderíamos criar métodos diferentes no Decorator, mas com o tempo o Decorator poderia virar um monstro. Outra opção é criar vários Decorators para aquele model, onde cada um se aplica a uma página. Ou podemos usar um outro padrão, o Presenter.

O Presenter é um padrão também conhecido por outros nomes, como View Object, mas na comunidade Ruby o nome Presenter se popularizou com um post de Jay Fields. O Presenter é muito parecido com o Decorator, mas envolve vários objetos. O contexto do Presenter é uma página específica da aplicação, e recebe como parâmetro todos os objetos necessários à exibição daquela página. Desta forma, toda a lógica de apresentação fica numa única classe. Segue um exemplo de uso do Presenter:

class PedidoPresenter
  def initialize(usuario, pedidos)
    @usuario = usuario
    @pedidos = pedidos
  end

  def titulo
    "Usuário #{@usuario.nome} - #{@pedidos.size} pedidos"
  end

  def links
    @pedidos.map { |pedido| link_to pedido.nome, pedido_url(pedido) }
  end
end

Ainda há uma outra opção além do Presenter, que foi apresentada no livro Objects on Rails. É o padrão Exhibit. A diferença em relação ao Presenter é que, enquanto o Presenter disponibiliza métodos para serem chamados pela view (como no exemplo acima), o Exhibit é responsável pela renderização. Para isso, ele precisa receber um contexto:

class Exhibit
  def initialize(obj, context)
    @obj = obj
    @context = context
  end

  def render_header
    @context.render :partial => "header", :locals => {:obj => @obj}
  end
end

Este contexto pode ser o view_context do controller:

class Controller
  def show
    @usuario = Exhibit.new(Usuario.find(params[:id]), view_context)
  end
end

Outra maneira de instanciar o Exhibit é através de um helper, como mostrado no livro The Rails View:

class Helper
  def exhibit
    Exhibit.new(Usuario.find(params[:id]), self)
  end
end

São muitos padrões que tem a mesma função: encapsular a lógica de visualização num único local, que seja facilmente testável. E qual é a melhor opção entre os três? A resposta depende da situação. Não adianta querer encontrar um padrão perfeito para todos os casos. Na minha opinião, o Decorator funciona bem em páginas mais simples, que envolvem apenas um model. Quando a página é mais complexa e envolve vários models, o Presenter e o Exhibit são mais adequados. E a diferença entre os dois é uma questão de gosto.

Links relacionados:


Um dos temas mais atuais no desenvolvimento web é a otimização de sites. A motivação para reduzir o tempo de carregamento das páginas vem não só de estudos que mostram que quando maior o tempo de carregamento, maior o número de usuários que abandonam o site, mas também do fato de que o Google considera o tempo de resposta na criação do PageRank.

Um dos itens mais importantes ao otimizar um site, de acordo com Steve Souders, é diminuir o número de requests. Um recurso muito útil para isso é a criação de sprites, ou seja, um único arquivo contendo várias imagens que são utilizadas no site. Nos locais que fazem referência a estas imagens, são definidos a largura, altura e offset do sprite. Desta forma, é feito um único request para obter todas as imagens. O uso de sprites é muito comum em grandes sites como Twitter, Facebook, Google e Yahoo.

A criação de um sprite manualmente é uma tarefa bem trabalhosa. É necessário criar uma imagem utilizando uma ferramenta como o Photoshop, por exemplo, e colar cada imagem uma abaixo (ou ao lado) da outra, deixando alguns pixels entre imagens. Sempre que se quiser adicionar uma nova imagem, será necessário abrir o sprite novamente no Photoshop e repetir o processo para inserir a nova imagem.

Para simplificar esta tarefa, é possível utilizar uma ferramenta para geração automática de sprites. Uma das melhores ferramentas para isso é o Compass. Após instalar e configurar, basta colocar as imagens num diretório e o sprite será gerado automaticamente. A instalação num projeto Rails é extremamente simples, e está bem explicada no help do Compass.

A configuração básica do Compass para geração de sprites segue o padrão abaixo:

$imagens-spacing: 2px
$imagens-sprite-dimensions: true
@import "imagens/*.png"

No exemplo acima, todas as propriedades são configuradas com o prefixo imagens. O sprite é configurado com espaçamento de 2 pixels entre cada imagem, para evitar sobreposição no limite entre as imagens. A segunda linha habilita a inclusão das dimensões das imagens no CSS gerado, o que é útil para manter fixo o tamanho ocupado pela imagem enquanto ela é carregada. A terceira linha informa quais imagens serão adicionadas. Neste caso, são todas as imagens com extensão png que estão no diretório imagens. É importante lembrar que o nome deste diretório deve ser igual ao prefixo utilizado nas propriedades.

Além de gerar o sprite, o Compass cria classes CSS para referenciar cada imagem. Os nomes das classes começam com o prefixo utilizado acima, seguido por hífen e o nome da imagem sem extensão. Por exemplo, para uma imagem chamada excluir.png, a classe teria o nome imagens-excluir.

O uso as imagens do sprite no seu CSS pode ser feito de duas formas: usando diretamente as classes criadas pelo Compass (como imagens-excluir, no exemplo anterior) ou utilizando um mixin do Compass no seu arquivo Sass:

.minha-classe { @include imagens-sprite(excluir); }

Ao utilizar uma destas configurações, a imagem será configurada como background do elemento.

Para criar um segundo sprite, para a parte administrativa da aplicação, por exemplo, é necessário utilizar um prefixo diferente, como no exemplo abaixo:

$imagens-admin-spacing: 2px
$imagens-admin-sprite-dimensions: true
@import "admin/imagens-admin/*.png"

Neste exemplo, as imagens do sprite estão no diretório admin/imagens-admin, e o prefixo segue o nome do último diretório (imagens-admin). Isso significa que, no exemplo acima, não seria possível manter o sprite do admin em admin/imagens, pois haveria conflito de nomes com o outro sprite.

Os sprites gerados pelo Compass são arquivos png que tem como nome o prefixo utilizado na configuração seguido por um hash (ex: imagens-b03bdb7a79370e7ff107e7b37fe7df6e.png). Quando o sprite é modificado (em ambiente de desenvolvimento o Compass verifica automaticamente a cada request se alguma imagem foi adicionada ou removida, e em produção é necessário executar um rake task para isso), o Compass gera um novo hash para o nome do arquivo. Isto é feito para evitar que o sprite seja cacheado pelo browser. Se isso acontecesse, o browser não buscaria o sprite atualizado, mantendo o arquivo anterior.

Os exemplos descritos acima descrevem apenas as configurações básicas para geração de sprites. A documentação de sprites traz mais detalhes sobre as opções de configuração. Além disso, o Compass tem muitas outras funcionalidades. Vale a pena pesquisar a referência no site do Compass para mais detalhes.

UPDATE: outra configuração útil para os sprites é o layout das imagens. Por padrão, o layout dos sprites é vertical, ou seja, cada imagem é colocada abaixo da anterior. Porém, o Compass também permite definir layout horizontal, diagonal ou smart. Neste último, a disposição das imagens é feita de acordo com o tamanho de cada uma, e o resultado é uma imagem menor do que com o layout padrão. No meu projeto, ao trocar o layout vertical pelo smart, o sprite ficou cerca de 10% menor. No primeiro exemplo deste post, a configuração do layout ficaria assim:

$imagens-layout: smart

Ao utilizar o scaffold do Rails, ele criará todos os métodos necessários no controller. Depois que o usuário preenche os dados do formulário e envia, é executado o método create do controller. Este método faz algo semelhante ao código abaixo (no exemplo é um CRUD de usuário):

@usuario = Usuario.new(params[:usuario])

Na forma em que o Rails cria o formulário, os atributos do usuário são passados ao controller como um hash. O valor do params acima é algo parecido com isso:

{ "authenticity_token" => "xI1Cy+LvUZzg6FR/1Y/JHcaHVPRyWsHmRII8BhMOr0E=",
 "utf8" => "?",
 "action" => "create",
 "controller" => "usuarios",
 "usuario" => { "nome" => "Novo usuario", "email" => "[email protected]" } }

Além da definição do token de segurança, codificação em utf-8, nome do controller e da action que será executada, o parâmetro usuario contém todos os atributos que foram preenchidos no formulário. Desta forma, é muito simples atribuir os parâmetros preenchidos a um objeto Usuario, seja criando um novo (@usuario = Usuario.new(params[:usuario])) ou editando (@usuario.update_params(params[:usuario])). O Rails chama isso de mass assignment.

O problema desta abordagem é que o usuário poderia facilmente inserir novos parâmetros neste hash, simplesmente adicionando tags input hidden no formulário (usando o Firebug, por exemplo):

<input type="hidden" name="usuario[admin]" id="usuario_admin" value="true" />

Adicionando o código acima, o novo usuário criado receberia o valor true no atributo admin, o que representa uma falha grave na segurança da aplicação.

O Rails oferece um mecanismo para garantir a segurança nestes casos, usando os métodos attr_protected e attr_accessible do ActiveModel. O primeiro permite definir atributos que não podem ser alterados através de mass assignment:

class Usuario
  attr_protected :admin
end

E o attr_accessible é uma forma mais segura: somente os atributos passados para este método poderão ser alterados com mass assignment. Os demais ficam protegidos:

class Usuario
  attr_accessible :nome, :email
end

Obviamente estes dois métodos não podem ser usados simultaneamente, pois um exclui o outro.

Se você quiser atualizar um objeto com mass assignment ignorando a segurança fornecida pelo attr_accessible e pelo attr_protected, basta utilizar o parâmetro without_protection (somente no Rails 3.1):

Usuario.create(
  {:nome => "Novo usuario", :email =>"[email protected]", :admin => true},
  :without_protection => true)

No exemplo acima, o usuário criado terá o atributo admin igual a true, mesmo que tenha sido usado o método attr_protected ou o attr_accessible para evitar a alteração deste atributo.

Outra opção interessante para evitar a alteração de atributos indesejados é o método attr_readonly. Os atributos passados para este método só poderão ser definidos na criação do objeto, e não poderão ser alterados depois. Porém, este método faz parte do ActiveRecord::Base, e não do ActiveModel, ou seja, ele não estará disponível se você usar outro ORM. Há uma issue aberta no Github solicitando que este método seja movido para o ActiveModel.

Link relacionado: Recebendo dados do usuário: attr_accessible e attr_protected