Na última semana aconteceu o evento Abril Pro Ruby 2014, em Porto de Galinhas. O evento foi excelente, muito bem organizado e com várias palestras e workshops de excelente nível técnico.

No dia de palestras, fiz uma apresentação sobre Arquitetura Distribuída, onde mostrei uma pequena parte da arquitetura do projeto Globotv.

Os vídeos das apresentações foram disponibilizados no Youtube. Seguem abaixo os links para cada apresentação e workshop. Alguns estão faltando, mas assim que conseguir, eu atualizo os links do post.

Apresentações:

Workshops:


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