Ultimamente, Rails tem se popularizado, e com ele o famoso (e já antigo) MVC. Porém, como todas as coisas, a prática acabou sobrepondo a teoria, então achei que seria interessante falar um pouco sobre MVC, já que parece-me que algumas confusões começaram a acontecer. Esse post é imensamente baseado nesse post do Martin Fowler sobre arquiteturas GUI, então seria interessante ler ele também.

Enfim, vamos lá: no Rails, quando fazemos um “scaffold”, é criada uma combinação de coisas para nós: uma “migration”, que cria uma tabela no banco. Um “model”, que basicamente é o mapeamento dessa tabela para um objeto. Um “controller”, que faz a busca do registro certo e repassa para a renderização da tela. Por exemplo, a ação “edit”:

  def edit
    @foo = Foo.find(params[:id])
    render :action => 'edit' #Isso é redundante, mas deixa explícito um aspecto importante.
  end

Além disso, há uma série de boas-práticas, tais como não concentrar código de regra de negócio no controller, não colocar lógica nas views, enfim. Porém, essas “regra gerais” pecam em um ponto:

Rails não é 100% MVC…

Bom, explicando mais detalhadamente: digamos que todas as telas de cadastro precisem, além dos dados gerais do objeto, um “select” que contenha dados de outra model:

  <p>
    <%= f.label :bars %><br />
    <%= f.select :bar_id, Bar.all.map { |b| [b.name, b.id] } %>
  </p>

Pelas regras do Rails, isso é lógica na view, e deveria ser movido para outro lugar. Um helper, um controller, enfim. Mas o que é exatamente um helper? O lugar certo do código é realmente em um helper? E se esse código dependesse do registro atual, algo como:

  <p>
    <%= f.label :bars %><br />
    <% 
       bars = if @foo.is_admin?
         Bar.all.map { |b| [b.name, b.id] }
       else
         Bar.only_active.map { |b| [b.name, b.id] }
       end 
    %>
    <%= f.select :bar_id, bars %>
  </p>

E agora, isso deve ficar na view, num helper (eu, particularmente, acho estranho colocar lógica num helper quando ele depende de uma variável de instância que veio de um controller), ou no próprio controller?

Bom, a resposta é simples: esse é um código DA VIEW. Só que, a view NÃO É só o HTML (ou, no nosso caso, o ERB, HAML, enfim). No Rails, controllers fazem um papel que é responsabilidade da view, que basicamente é buscar os registros e instanciar os modelos que são necessários para a view existir. Se for usado render :update, :inline, :text ou mesmo “render_to_string”, só estamos ainda mais expondo esse método da view no controller.

Então, o que verdadeiramente é o “V” e o “C”?

Pelo MVC, através de observers, a view é notificada de alterações no “model”, e reage de acordo com a alteração. Logo, se o model foi salvo, a view reage identificando se esse salvamento foi bem sucedido, ou se foi mal-sucedido e em cada caso decide o que fazer. A view é também “independente” do controller, isso é, uma view contém em si TODAS as informações necessárias para ser exibida. Porém, sabemos que a web é “stateless”, logo não podemos guardar o estado do model e da view e “encaixar” observers nesse modelo… mas, poderíamos sim deixar o rails mais “MVC”:

#app/controllers/foos_controller.rb
class FoosController < ApplicationController
  def edit
    render :FooView, :edit #Até poderia ser redundante, também
  end
  #...
end

#app/views/foo_view.rb
class FooView < ActiveView::Base #imaginando uma abordagem...
  def edit
    @foo = Foo.find(params[:id])
    erb :edit
  end

def bars_collection
    Bar.all.map { |b| [b.name, b.id] }
  end
  private :bars_collection
end

#app/views/foo_view/layouts/edit.html.erb
<% form_for(@foo) do |f| %>
  <p>
    <%= f.label :bars %><br />
    <%= f.select :bar_id, bars_collection %>
  </p>
<% end %>

BEM mais separado. Além disso, agora toda a lógica de renderização da view fica num lugar correto chamado “View”, não espalhados pelos controllers. Por fim, acaba-se a necessidade de “helpers”, pois temos uma abordagem “orientada a objetos” para views. Alguém afim de implementar isso?

Há uma última coisa a mencionar: “Models” não são apenas tabelas em banco de dados. Model é basicamente um “objeto de domínio”, ou seja, uma classe que concentra regras de negócio de um determinado tipo, que podem ou não estar ligados a dados mesmo. Ou seja, nada me impede de criar um model assim:

class TaxCalculator
  def initialize(salary)
    @salary = salary
  end

def ir
    @salary * 0.25 #Hipoteticamente, claro...
  end
end

E usá-lo em minha view. Recapitulando, então:

  • Models são objetos de domínio. Podem representar cálculos, dados, registros de banco de dados, coleções de registris em bancos de dados, etc.
  • Views são objetos que definem uma tela. Podem ter lógica de programação envolvida, desde que essa lógica se prenda apenas à exibição para o usuário. Reagem a mudanças na view, em casos onde a programação envolve mudança de estados (tal como GUI)
  • Controllers são objetos que reagem a interações do usuário (cliques em botões, etc) e roteiam essas ações para causar uma mudança de estados no Model que, consequentemente, informará a View através do mecanismo de “observers” que algo mudou, e quem decide como vai atuar naquela mudança é a View.

Ok, mas e no mundo “stateless” da web, aonde instâncias das views, controllers e models são “descartadas” depois de cada requisição, o que fazer? Nesse caso, cada framework tem sua abordagem. No caso do Rails, o Controller server como um “roteador”, que a partir de uma reação (GET, POST, PUT ou DELETE numa URL) identifica o que foi feito (através do método HTTP, da URL e dos parâmetros) e roteia para o caminho certo (redireciona para outro controller ou outra action, ou re-renderiza uma página).

Essa foi a abordagem Rails. Não está certa nem errada, mesmo a abordagem “MVC pura” não está certa nem errada. O que está errado é defender uma certa prática no MVC porque o Rails faz assim…


2 Comments

Leandro Maríngolo · 2011-12-13 at 15:56

Pelo que entendi, nessa abordagem de uma ActionView, em um projeto digamos grande seria necessário criar vários XXX.rb, o que acarretará em um esforço MUITO grande de manutenção.

O Helper talvez seja mais atrativo no que diz respeito a executar lógica pra view. Uma vez que o controller tem os objetos, basta ter métodos no helper que recebem esses objetos e fazem algo com eles.

    Maurício Szabo · 2011-12-14 at 16:21

    Na verdade, nessa abordagem só seria necessário 1 XXX.rb pra cada controller que quiser renderizar suas views. É uma divisão melhor de responsabilidades, a custo de mais arquivos no projeto.

    Na prática mesmo, se fosse pra ter uma divisão 100% de responsabilidades, várias outras coisas teriam que ser repensadas (validações fora dos mapeamentos, callbacks fora dos mapeamentos, e regras de negócio também fora dos mapeamentos). Isso dá um trabalho absurdo, e acho que não dá melhor manutenção (programadores Java que o digam).

    Só acho ruim o fato de que algumas pessoas aprendem Rails, acham que ele é “100% MVC”, e passam a usar ele como base pra defender teorias como “ah, mas a model só pode ter mapeamento” ou “cada tabela só pode ter, no máximo, uma model” ou algo parecido… aí a coisa complica consideravelmente, pq fundamentalmente, ela está pensando em MVC como algo quando na verdade, é outra coisa…

Comments are closed.