sexta-feira, 9 de novembro de 2012

Rails scope e default_scope (EDITADO)

Neste post pretendo demonstrar como é possível fazer uso de scope e default_scope na camada de modelo de aplicações em Rails de forma a centralizar regras de pesquisa sem violar outras camadas da aplicação e mantendo a simplicidade do código.

Partindo de um modelo Pessoa no qual temos os atributos nome, idade e ativo:

class Pessoa < ActiveRecord::Base
  attr_accessible :nome, :idade, :ativo
  
end

E um controlador que interage com este modelo e onde desejamos por algum motivo pesquisar as pessoas com idade acima de determinado valor:
class PessoaController < ApplicationController
  ...
  def pesquisar
    ...
    @pessoas = Pessoa.where("idade > ?", params[:idade])
                     .order(:nome)
    ...
    render :index
  end
  ...
end

O exemplo acima funcionaria perfeitamente, mas violaria o princípio das camadas, além de que, em qualquer outra parte da aplicação onde quiséssemos fazer o mesmo tipo de pesquisa, teríamos que replicar este código.

Muito mais conveniente seria poder implementar o controlador desta forma:
class PessoaController < ApplicationController
  ...
  def pesquisar
    ...
    @pessoas = Pessoa.com_idade_maior_que(params[:idade])
    ...
    render :index
  end
  ...
end

scope


Então já vimos como não fazer, agora vamos ver como essa última versão do nosso controlador poderia ser atendida pelo modelo fazendo uso do método scope que nos dá a facilidade de criar named queries no nosso modelo.

class Pessoa < ActiveRecord::Base
  attr_accessible :nome, :idade, :ativo
  
  scope :com_idade_maior_que, lambda {|id| where("idade > ?", id)
                                          .order(:nome) }
  scope :adultos, where("idade > 19").order(:nome)
end


Na linha 4 está a solução para nosso problema, não tão trivial por necessitar receber um bloco para tratar a parametrização. Mas na linha 6 adicionei um exemplo mais simples que também poderia ser utilizado em qualquer controlador com um simples Pessoa.adultos.

default_scope


Há casos também em que é útil sempre aplicar um determinado padrão em qualquer consulta efetuada a partir de um modelo, seja para aplicar um critério de pesquisa ou uma ordenação, sendo esta última mais comum ainda. Para simplificar também esta necessidade, o ActiveRecord oferece o método default_scope.

Suponhamos que todas as pesquisas à tabela de pessoas devam retornar as pessoas ordenadas por nome. Não queremos ter que sempre adicionar ao final da query um .order(:nome). A solução com default_scope fica conforme o exemplo abaixo:

class Pessoa < ActiveRecord::Base
  attr_accessible :nome, :idade, :ativo
  
  scope :com_idade_maior_que, lambda {|id| where("idade > ?", id) }
  scope :adultos, where("idade > 19")

  default_scope order(:nome)
end


No lugar do order(:nome) poderia ser utilizado também um where com qualquer critério, como, por exemplo, pessoas ativas onde o atributo ativo deve sempre ser true.

class Pessoa < ActiveRecord::Base
  attr_accessible :nome, :idade, :ativo
  
  scope :com_idade_maior_que, lambda {|id| where("idade > ?", id) }
  scope :adultos, where("idade > 19")

  default_scope where(ativo: true).order(:nome)
end


Observe que nos dois exemplos acima o order(:nome) foi retirado das named queries por já estar automaticamente incluído pelo default_scope.

Atenção ao default_scope


Existe um detalhe importantíssimo sobre o uso de default_scope. Os filtros definidos com este comando delimitam um escopo sobre o qual as queries serão realizadas e também definem um escopo para novos objetos.
Isso significa que além de todas as pesquisas de pessoas em nossa aplicação só retornarem pessoas ativas, também todas as novas pessoas cadastradas terão o atributo ativo igual a true por padrão conforme o default_scope definido.

Então com default_scope é necessário ter este cuidado. Se eventualmente você quiser ir além do escopo definido de forma a abranger todos os itens da tabela, utilize unscoped conforme os exemplos abaixo:

Pessoa.unscoped.all
Pessoa.unscoped.count
Pessoa.unscoped.adultos
Pessoa.unscoped.com_idade_maior_que(20)
...
...


Encontrei uma explanação muito boa sobre estes recursos nos livros Rails Recipes e Agile Web Development with Rails.

Até a próxima!

Nenhum comentário:

Postar um comentário