again, we will start from TDD!!!
1. since both edit and update need the same authentication, we can put their test together:
describe "authentication of edit/update pages" do
before(:each) do
@user = Factory(:user)
end
describe "for not signed-in users" do
it "should redirect to sign in page" do
get :edit, :id => @user
response.should redirect_to signin_path
end
it "should deny access to update" do
put :update, :id => @user, :user => {}
response.should redirect_to signin_path
end
end
end
2. we will add before_filter to user controller to make this test pass:
class UsersController < ApplicationController
before_filter :authenticate, :only => [:edit, :update]
.
private
def authenticate
deny_access unless signed_in?
end
end
we still need to define the deny_access method, since it is kind of authentication, I'll put it into session helper:
def deny_access
redirect_to signin_path, :notice => "please sign in first."
end
note, this line of code is equivalent with two
flash[:notice] = ""
redirect_to signin_path
you can also use:
redirect_to signin_path, :alert => "fdsfdsfsdf"
but you can't use :success or :error in this contruction.
3. except need of user to sign in, we still need to make sure current user can't edit other user info.
start from TDD again!!!
describe UsersController do
render_views
.
.
.
describe "authentication of edit/update pages" do
.
.
.
describe "for signed-in users" do
before(:each) do
wrong_user = Factory(:user, :email => "user@example.net")
test_sign_in(wrong_user)
end
it "should require matching users for 'edit'" do
get :edit, :id => @user
response.should redirect_to(root_path)
end
it "should require matching users for 'update'" do
put :update, :id => @user, :user => {}
response.should redirect_to(root_path)
end
end
end
end
4. now to make the test pass, we need to add a new before filter to user controller.
class UsersController < ApplicationController
before_filter :authenticate, :only => [:edit, :update]
before_filter :correct_user, :only => [:edit, :update]
.
.
.
def edit
@title = "Edit user"
end
.
.
.
private
def authenticate
deny_access unless signed_in?
end
def correct_user
@user = User.find(params[:id])
redirect_to(root_path) unless current_user?(@user)
end
end
module SessionsHelper
.
.
.
def current_user?(user)
user == current_user
end
def deny_access
redirect_to signin_path, :notice => "Please sign in to access this page."
end
private
.
.
.
end
now we have make our site very safe.
5. now we are doing some useful thing:
if a unsigned in user try to visit a protected page, he is redirected to the sign in page, then after he sign in, he is always redirected to the profile page, what we want is to redirect the user to the page he was trying to visit.
this is a very good work flow to be tested by the integration test!
so let's write a integration test for this flow first.
require 'spec_helper'
describe "FriendlyForwardings" do
it "should forward to the requested page after signin" do
user = Factory(:user)
visit edit_user_path(user)
# The test automatically follows the redirect to the signin page.
fill_in :email, :with => user.email
fill_in :password, :with => user.password
click_button
# The test follows the redirect again, this time to users/edit.
response.should render_template('users/edit')
end
end
you may wondering, why I use
should render_template()
instead of
should redirect_to()
because, in integration test, it will follow the redirect, so response.should redirect_to will not work.
6. next, we will do the implementation to make the test pass.
how do we do this?
a. since http is stateless, we have to use session to store the requested url in last request, then get it from session in the new request.(the things in session will expire when browser close.)
b. we will use the request object to get the url.
module SessionsHelper
.
.
.
def deny_access
store_location
redirect_to signin_path, :notice => "Please sign in to access this page."
end
def redirect_back_or(default)
redirect_to(session[:return_to] || default)
clear_return_to
end
private
.
.
.
def store_location
session[:return_to] = request.fullpath
end
def clear_return_to
session[:return_to] = nil
end
end