domingo, 17 de outubro de 2010

Rails 3, RSpec 2, Devise a MongoDB

1

Estive estudando o Rails 3 (Atualmente 3.0.1 para o rails), e também estudando um pouco de NoSQL, com o MongoDB, ainda pretendo me dedicar um pouco para aprender sobre o Redis, mas por enquanto estou vendo o Rails. Gosto de usar para autenticação o Devise, um projeto brasileiro, que se tornou bem famoso pelo mundo Rails. Para ORM gostei do Mongoid, e ainda mais pelo devise ter suporte ao mongoid ao invez do ActiveRecord.

Iniciando

Sobre o mongoid não há muito o que falar, a documentação presente no site é completa, e funciona muito bem, vale lembrar que arquitetura de um banco relacional é bem diferente de um NoSQL, e algumas definições já padrão na nossa cabeça devem ser desfeitas.

O RSpec-Rails vem configurado para rodar sobre o ActiveRecord e quando vc executar um rails g rspec:install ira gerar toda a estrutura padrão do rspec, incluindo a pasta spec que contem os testes. Primeiramente vamos editar o arquivo spec/spec_helper.rb

RSpec.configure do |config|
  require 'database_cleaner'
  
  config.before(:suite) do
    DatabaseCleaner.strategy = :truncation
    DatabaseCleaner.orm = "mongoid"
  end

  config.before(:each) do
    DatabaseCleaner.clean
  end
  config.mock_with :rspec
  config.include Devise::TestHelpers, :type => :controller # Essa linha adiciona os helpers do Devise para o RSpec.
end

A adição da gem DatabaseCleaner (que deve estar declarada no Gemfile), serve para limpar os resultados dos testes apos a execução, como podem reparar o uso de fixtures é desabilidado para o Mongo, somente para o ActiveRecord, sem o uso de fixtures os testes são mantidos na base, o que não é interessante.

Testando os controllers

Criei um modelo simples com o devise, apesar da documentação do devise indicar a criação de um modelo Admin para esses casos, preferia adotar o uso de um flag para poder indicar se o usuário é um administrador, acho isso um tanto mais simples.
Adicionei também no application_controller.rb um metodo filtro que indica se o usuário é um adminsitrador

protected
  def admin_user!    
    if authenticate_user!      
      unless current_user.admin        
        flash[:warning] = "Acesso Negado"
        redirect_to root_url
      end
    end
  end
reparem que usei o filtro authenticated_user! que é do devise pra indicar que o usuário está logado no sistema.
meu controller admin_controller.rb
class AdminController < ApplicationController
  before_filter :admin_user!
  
  def index
  end

end
é um controller bem simples, mas para garantirmos a segurança do aplicativo, e se algum dia nosso filtro precisar ser ajustado precisamos garantir que o acesso a esse controller seja restrito sempre, e quem alterar deve ter noção da quebra do teste. Em alguns dos meus testes, normalmente em fixtures, eu pego um hash que representa o codigo BCrypt da senha (padrão do Devise), mas como aqui no mongo não tem fixutes, parti pra outro ponto.
require 'spec_helper'

describe AdminController do
    
  describe "GET 'index'" do    
    def do_action_with_user(admin)
      @user = User.create!(:name => "Test", :email => "test@test.com", 
        :password => "testet",
        :password_confirmation => "testet",
        :admin => admin)        
      sign_in @user
      get 'index'
    end
    #1
    it "should not be successful when no user was logged in" do
      get 'index'
      flash[:alert].should_not be_nil
      response.should_not be_success
    end
    #2
    it "should not be successful when no admin user was logged in" do
      do_action_with_user(false)      
      flash[:warning].should_not be_nil      
      response.should_not be_success
    end
    #3
    it "should be successful when admin user was logged in" do
      do_action_with_user(true)
      response.should be_success
    end
  end

end
bem simples, um metodo dry pra fazer o registro do usuario e usar o metodo auxiliar do devise para o login (sign_in):
  • o teste 1 é um teste para acesso sem login, no caso uma chamada a action index do controller admin sem um usuário logado.
  • teste 2 teste para um usuário que não é administrador, repare que testo se o flash esta preenchido
  • teste 3 testa a ação de sucesso, no caso o acesso de um usuário administrador
Inicialmente esse teste se fez suficiente para dizer que agora a classe está segura.

Outras notas

Tive alguns problemas no começo do uso do RSpec 2 em views, e vou listar aqui alguns deles:

assign :name, "Teste"
ao invez de
assigns[:name] = "Teste"
Outra mudança é que em views você não deve mais usar o response, mas usar o rendered. Também deixou de existir o metodo have_tag, para verificar o conteudo de uma tag, veja abaixo:
#RSpec 3
assert_select('p', "Teste")
#RSpec 2
response.have_tag 'p', "Teste"


Espero que isso seja util pra mais alguem, pelo menos eu não vou mais esquecer :D

1 comentários:

Unknown disse...

Muito útil. Ajudou bastante. Valew!

 
Design by ThemeShift | Bloggerized by Lasantha - Free Blogger Templates | Best Web Hosting