Nil Object Pattern em Ruby on Rails

Tutoriais - 09/Out/2019 - por André Kanamura

O Nil (ou Null) Object Pattern é um padrão em programação Orientada a Objetos que descreve o uso de objetos nulos e o seu comportamento. Na realidade, o comportamento de objetos nulos é neutro e o Nil Object Pattern nos permite controlar como nossa aplicação responde a eles.

Esse padrão pode ser implementado em diversos contextos, mas aqui vamos descrevê-lo dentro de uma aplicação Ruby on Rails fictícia que simula o sistema interno de uma locadora de veículos.

Nessa aplicação utilizamos a gem Devise para gerenciar o sistema de login dos usuários. Existem usuários gerentes, com funções restritas, e administradores, que têm acesso a todas as funcionalidades do sistema. Além disso, visitantes não cadastrados podem visualizar os veículos disponíveis para locação.

Como as funcionalidades disponíveis são diferentes para cada tipo de usuário, é necessário realizar uma série de verificações dentro dos Controllers da aplicação para que sejam autorizadas apenas funcionalidades de acordo com quem está acessando o sistema. Por essa razão, métodos como current_user são utilizados frequentemente e, nesse ponto, nos deparamos com um problema: se não houver usuário logado, o método não sabe como responder.

Uma das vantagens de utilizar o Devise é que alguns métodos ficam disponíveis – como o current_user.admin?, que verifica se o usuário logado é admin, por exemplo. No entanto, dentro do cenário descrito aqui em que temos um visitante navegando no site, ou seja, não há um usuário logado, esse método não funciona corretamente.

Uma maneira elegante de resolver esse problema é utilizar o Nil Object Pattern. Na nossa aplicação, quando um usuário não logado utiliza o sistema, métodos que verificam a presença de um usuário logado retornam nulo (nil em Ruby), mas, nesse caso, nulo não é o mesmo que nenhum usuário.

Primeiramente, criamos uma classe NilUser:

class NilUser

end 

Agora, sobrescrevemos o método current_user dentro do Application Controller para que ele ainda responda da maneira tradicional, mas, na ausência de um usuário, ele deve instanciar um objeto NilUser:

class ApplicationController < ActionController
  def current_user
    super || NilUser.new
  end
end

Desta forma, toda vez que algum método falha com current_user, ele pode ser sobrescrito dentro da classe NilUser. Podemos, por exemplo, já implementar o método admin? para que ele retorne falso, afinal, um usuário não logado não é um administrador:

class NilUser
  def admin?
     false
  end
end

Podemos ir além e implementar um método email, que retorna "", e guest?, que retorna verdadeiro:

class NilUser
  def admin?
     false
  end

  def email
    ""
  end

  def guest?
    true
  end
end

Assim, temos controle sobre as respostas esperadas para todos cenários em que um usuário é nulo. Por essa razão, deixa de ser necessário verificar a todo momento se as variáveis são nulas. Além disso, todo o código que define esse comportamento fica dentro de uma única classe NillUser, em vez de ficar espalhado dentro dos Controllers e Views da aplicação.

Vídeos e sugestões de leitura:

Foto de perfil do autor
André Kanamura

Dev na Campus Code