Carlos Brando

Nome do Jogo

Rails Way: Variáveis de Instância, Controllers e Views

Mais uma sexta-feira e mais um artigo da série Rails Way, onde vou pouco a pouco mostrando a forma como os Rubistas mais experientes costumam programar no Ruby on Rails.

Programadores iniciantes rapidamente ficam fascinados com o Ruby e principalmente com o Rails, e por serem uma linguagem e framework com uma curva de aprendizado muito curta é comum que ao terminarem seu primeiro projeto em Rails, percam um pouco o interesse em entender como determinadas coisas funcionam, assimilando tudo que o Rails faz como pura mágica.

Isto é extremamente prejudicial ao programador iniciante e pode levá-lo a se tornar um programador “experiente” com pouco ou nenhum conhecimento real sobre o funcionamento interno da sua ferramenta de trabalho.

A ideia desta série de artigos é expor algumas boas práticas de desenvolvimento ao mesmo tempo em que tento explicar de uma forma simples como as engrenagens funcionam. Por exemplo, já se perguntou como um controller e uma view compartilham as mesmas variáveis de instância?

Por definição uma variável de instância só existe dentro de uma única instância de um objeto. Mas quando estamos falando de controllers e view, estamos falando de dois objetos diferentes que compartilham as mesmas variáveis.

# books_controller.rb
class BooksController < ApplicationController
  def index
    @books = Book.find(:all)
  end
end
<!-- index.html.erb -->
<% for book in @books %>
   <%= book.title %>
<% end %>

Nos exemplos acima estou definindo uma variável de instância @books dentro do controller, e eu simplesmente assumo que a variável também estará disponível na view.

Note que meu controller é uma extensão da classe ApplicationController que por sua vez é uma extensão da classe ActionController::Base e por isto é uma instância desta classe. Por outro lado a minha view é uma instância da classe ActionView::Base. Ou seja, meu controller é uma instância de um objeto enquanto minha view é uma instância de um outro objeto totalmente diferente.

Então como é possível compartilhar uma variável de instância quando temos duas instância de objetos diferentes? Será mágica? Não, a resposta é mais simples do que você imagina.

O Rails acrescenta à classe Object um método chamado copy_instance_variables_from que faz exatamente o que o nome diz, copia variáveis de instância de uma classe para outro. Você pode até mesmo brincar um pouco com este método no console. Abra o terminal e após digitar o comando irb, copie e cole o código abaixo e veja o resultado:

require 'rubygems'
require 'active_support'


class A
  attr_reader :x, :y
end

class B
  def initialize
    @x = 'valor copiado da classe B'
    @y = 11
  end
end


a = A.new
b = B.new

puts a.x # => nil
puts a.y # => nil

a.copy_instance_variables_from(b)

puts a.x # => "valor copiado da classe B"
puts a.y # => 11

Acabamos de copiar todas as variáveis de instância de um objeto para outro. É exatamente assim que acontece com controllers e views.

O que acontece por debaixo dos panos é que quando o método render do controller é executado, primeiro ele copia todas as variáveis de instância que você criou para dentro de apenas uma variável chamada @assigns, que funciona como um Hash. E ao criar uma uma nova view, ele faz uma copia desta variável (@assigns com todas as suas variáveis dentro) para o novo objeto. Simples não?

O inferno das variáveis

O que tenho visto muitas vezes é que alguns programadores tem abusado desta facilidade que o Rails oferece, criando inúmeras variáveis de instância no controller para recuperar todo tipo de informação na view.

Manter muitas variáveis complica o gerenciamento do código, e em alguns casos acabamos até mesmo fazendo chamadas repetidas à associações de um modelo sem se dar conta disso, causando um problema de performance em nosso projeto.

O ideal é que você compartilhe apenas uma variável de instância entre seu controller e sua view. Desta forma todas as chamadas para associações serão realizadas “on demand” evitando o consumo indevido de recursos e tornando a manutenção do código mais simples.

Isto não quer dizer que você não possa compartilhar outras variáveis quando necessário. Mas é importante que você analise a real necessidade de se fazer isto. Por exemplo, é mais prático criar um helper current_user para recuperar o usuário atual do que sempre armazená-lo em uma variável @current_user.

Se no primeiro exemplo você precisasse recuperar também todos os livros relacionados ao livro atual, talvez parecesse razoável criar uma segunda variável @related_books contendo estes livros. Mas se você colocar em prática a primeira dica desta série e acrescentar o método related_books ao modelo Book, você poderá usar @book.related_books na sua view. Evitando a criação desnecessária de mais uma variável e deixando seu código mais limpo e organizado.

Usem de bom senso.

Comments