Monday, June 14, 2010

Create A Rails 3 beta With Authlogic, Declarative Authorization a

Create A Rails 3 beta With Authlogic, Declarative Authorization and Cucumber

I have updated my auth_with_roles app to use rails 3.0.7. You can find the code at https://github.com/johnivanoff/auth_with_roles/tree/rails_3_0_7

This was done on a windows wachine so forgive the C:\
And it's a crappy first draft.

My test aren't the best and need refactoring but this should get you started.
I plan to update this when rails 3 is released. I would also like to intergate Factory Girl.



This project uses:

Let's get going.
C:\web> rails auth_with_roles
C:\web> cd auth_with_roles
C:\web\auth_with_roles> git init
C:\web\auth_with_roles> git add .
C:\web\auth_with_roles> git commit -m "auth_with_roles Scaffold"

Let's add the gems for cucumber.
open /Gemfile add . . .
# Use Authlogic for security
gem "authlogic", :git => "git://github.com/odorcicd/authlogic.git", :branch => "rails3"
# using for role assignments
gem "declarative_authorization", :git => "git://github.com/stffn/declarative_authorization.git"

group :test do
gem 'capybara'
gem 'database_cleaner'
gem 'cucumber-rails'
gem 'cucumber', '0.7.2'
gem 'rspec-rails', '2.0.0.beta.8'
gem 'spork'
gem 'launchy' # So you can do Then show me the page
end


run
C:\web\auth_with_roles> bundle install

bootstrap your Rails app, for rspec:
C:\web\auth_with_roles> rails g rspec:install

Finally, bootstrap your Rails app, for cucumber:
C:\web\auth_with_roles> rails generate cucumber:skeleton --rspec --capybara

create the authlogic base.
C:\web\auth_with_roles> rails g cucumber:feature user username:string email:string password:string
C:\web\auth_with_roles> rails g scaffold user username:string email:string password:string

create_user migration
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.string :username
t.string :email
t.string :crypted_password
t.string :password_salt
t.string :persistence_token
t.timestamps
end
add_index :users, :username
add_index :users, :persistence_token
end
def self.down
drop_table :users
end
end

run
C:\web\auth_with_roles>rake db:migrate

Create a UserSessionsController
C:\web\auth_with_roles> rails g authlogic:session user_session
C:\web\auth_with_roles> rails g controller user_sessions new create destroy

fix up the sign up form /app/views/users/_form.html.erb
<%= form_for(@user) do |f| %>
<% if @user.errors.any? %>
<div id="errorExplanation">
<h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>
<ul>
<% @user.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :username %><br />
<%= f.text_field :username %>
</div>
<div class="field">
<%= f.label :email %><br />
<%= f.text_field :email %>
</div>
<div class="field">
<%= f.label :password %><br />
<%= f.password_field :password %>
</div>
<div class="field">
<%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>


fix the user index fix /app/views/users/index.html.erb
<h1>Listing users</h1>
<table>
<tr>
<th>Username</th>
<th>Email</th>
<th></th>
<th></th>
<th></th>
</tr>
<% @users.each do |user| %>
<tr>
<td><%= user.username %></td>
<td><%= user.email %></td>
<td><%= link_to 'Show', user %></td>
<td><%= link_to 'Edit', edit_user_path(user) %></td>
<td><%= link_to 'Destroy', user, :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
</table>
<br />
<%= link_to 'New User', new_user_path %>


the user show /app/views/users/show.html.erb
<p class="notice"><%= notice %></p>
<p>
<b>Username:</b>
<%= @user.username %>
</p>
<p>
<b>Email:</b>
<%= @user.email %>
</p>
<%= link_to 'Edit', edit_user_path(@user) %> |
<%= link_to 'Back', users_path %>



fix up the login form /app/views/user_sessions/new.html.erb
<%= form_for @user_session, :url => login_path do |f| %>
<% if @user_session.errors.any? %>
<div id="errorExplanation">
<h2><%= pluralize(@user_session.errors.count, "error") %> prohibited this asset_type from being saved:</h2>
<ul>
<% @user_session.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= f.label :username %><br />
<%= f.text_field :username %>
</p>
<p>
<%= f.label :password %><br />
<%= f.password_field :password %>
</p>
<p><%= f.submit "Log in" %></p>
<% end %>


add the routes /config/routes.rb
AuthWithRoles::Application.routes.draw do |map|
resources :users
controller :user_sessions do
get 'login' => :new
post 'login' => :create
delete 'logout' => :destroy
end

match 'register', :to => 'users#new', :as => "register"
end



Modify the application controller /apps/controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
layout 'application'
helper :all
helper_method :current_user_session, :current_user
#filter_parameter_logging :password, :password_confirmation
before_filter :set_current_user
protected
def set_current_user
Authorization.current_user = current_user
end
private
def current_user_session
return @current_user_session if defined?(@current_user_session)
@current_user_session = UserSession.find
end
def current_user
return @current_user if defined?(@current_user)
@current_user = current_user_session && current_user_session.record
end
def require_user
unless current_user
store_location
flash[:notice] = "You must be logged in to access this page"
redirect_to login_url
return false
end
end
def require_no_user
if current_user
store_location
flash[:notice] = "You must be logged out to access this page"
redirect_to account_url
return false
end
end
def store_location
session[:return_to] = request.fullpath
end
def redirect_back_or_default(default)
redirect_to(session[:return_to] || default)
session[:return_to] = nil
end
end



user_sessions_controller.rb /apps/controllers/user_sessions_controller.rb
class UserSessionsController < ApplicationController
def new
@user_session = UserSession.new
end
def create
@user_session = UserSession.new(params[:user_session])
if @user_session.save
flash[:notice] = "Successfully logged in."
redirect_to users_path
else
render :action => 'new'
end
end
def destroy
current_user_session.destroy
flash[:notice] = "Successfully logged out."
redirect_back_or_default login_url
end
end


Modify the user controller /apps/controllers/users_controller.rb
class UsersController < ApplicationController
#before_filter :require_no_user, :only => [:new, :create]
#before_filter :require_user, :only => [:show, :edit, :update]
# GET /users
# GET /users.xml
def index
@users = User.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @users }
end
end
# GET /users/1
# GET /users/1.xml
def show
@user = User.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @user }
end
end
# GET /users/new
# GET /users/new.xml
def new
@user = User.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @user }
end
end
# GET /users/1/edit
def edit
@user = User.find(params[:id])
end
# POST /users
# POST /users.xml
def create
@user = User.new(params[:user])
respond_to do |format|
if @user.save
format.html { redirect_to(@user, :notice => 'Registration successful.') }
format.xml { render :xml => @user, :status => :created, :location => @user }
else
format.html { render :action => "new" }
format.xml { render :xml => @user.errors, :status => :unprocessable_entity }
end
end
end
# PUT /users/1
# PUT /users/1.xml
def update
@user = User.find(params[:id])
respond_to do |format|
if @user.update_attributes(params[:user])
format.html { redirect_to(@user, :notice => 'User was successfully updated.') }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @user.errors, :status => :unprocessable_entity }
end
end
end
# DELETE /users/1
# DELETE /users/1.xml
def destroy
@user = User.find(params[:id])
@user.destroy
respond_to do |format|
format.html { redirect_to(users_url) }
format.xml { head :ok }
end
end
end



modify the user model /app/model/user.rb
class User < ActiveRecord::Base
acts_as_authentic
end


we'll need to modify the cucumber test /features/manage_users.feature
Feature: Manage users
In order to [goal]
[stakeholder]
wants [behaviour]
Scenario: Register new user
Given the following roles:
|name|
|admin|
And I am on the new user page
When I fill in "Username" with "username 1"
And I fill in "Email" with "example@example.com"
And I fill in "Password" with "secret"
And I fill in "Password confirmation" with "secret"
And I check "admin"
And I press "Create"
Then I should see "username 1"
And I should see "example@example.com"
And I should see "admin"
Scenario: Delete user
Given the following users:
|username |email|password|password_confirmation|role_ids|
|username 1|email1@example.com|secret|secret|1|
|username 2|email2@example.com|secret|secret|2|
|username 3|email3@example.com|secret|secret|2|
|username 4|email4@example.com|secret|secret|2|
When I delete the 3rd user
Then I should see the following users:
|Username |Email|
|username 1|email1@example.com|
|username 2|email2@example.com|
|username 4|email4@example.com|



run
C:\web\auth_with_roles> rake cucumber
...........

2 scenarios (2 passed)
11 steps (11 passed)

run
C:\web\auth_with_roles> rake spec

Well we have some test to correct.

let modify the user session spec in /spec/controllers/user_sessions_controller_spec.rb
require 'spec_helper'
describe UserSessionsController do
def mock_usersession(stubs={})
@mock_usersession ||= mock_model(UserSession, stubs).as_null_object
end
describe "GET 'new'" do
it "should be successful" do
get 'new'
response.should be_success
end
end
describe "GET 'create'" do
it "should be successful" do
get 'create'
response.should be_success
end
end
describe "DELETE destroy" do
it "destroys the current session" do
UserSession.should_receive(:find).with("37") { mock_usersession }
mock_usersession.should_receive(:destroy)
delete :destroy, :id => "37"
end
it "redirects to the login page" do
UserSession.stub(:find) { mock_usersession(:destroy => true) }
delete :destroy
response.should redirect_to(login_url)
end
end
end

Now modify the user's index spec /spec/views/users/index.html.erb_spec.rb
require 'spec_helper'
describe "users/index.html.erb" do
before(:each) do
assign(:users, [
stub_model(User,
:username => "MyUsername",
:email => "MyEmail",
:password => "MyPassword"
),
stub_model(User,
:username => "MyUsername",
:email => "MyEmail",
:password => "MyPassword"
)
])
end
it "renders a list of users" do
render
response.should have_selector("tr>td", :content => "MyUsername".to_s, :count => 2)
response.should have_selector("tr>td", :content => "MyEmail".to_s, :count => 2)
end
end


run
C:\web\auth_with_roles> rake spec

...................*.....*


Finished in 2.34 seconds
26 examples, 0 failures, 2 pending

Pending:
User add some examples to (or delete) ./spec/models/user_spec.rb (Not Yet Implemented)
# ./spec/models/user_spec.rb:4
user_sessions/create.html.erb add some examples to (or delete) ./spec/views/user_sessions/create.html.erb_spec.rb (Not Yet Implemented)
# ./spec/views/user_sessions/create.html.erb_spec.rb:4

All tests are passing, although they are not complete.

Let's get the login spec working /spec/views/new.html.erb_spec
require 'spec_helper'
describe "user_sessions/new.html.erb" do
before(:each) do
assign(:user_session, stub_model(User,
:new_record? => true,
:username => "MyString",
:password => "MyString"
))
end
it "renders login form" do
render
response.should have_selector("form", :action => login_path, :method => "post") do |form|
form.should have_selector("input#user_username", :name => "user[username]")
form.should have_selector("input#user_password", :name => "user[password]")
end
end
end

Let's get the User model working /spec/models/user_spec.rb
require 'spec_helper'
describe User do
before(:each) do
@user = User.new(:username => "Jimmy",
:email => "jimmy@example.com",
:password => "secret",
:password_confirmation => "secret")
end
it "is valid with valid attributes" do
@user.should be_valid
end
it "is not valid without a username" do
@user.username = nil
@user.should_not be_valid
end
it "is not valid without a unique username" do
@user2 = User.create( :username => "Jimmy",
:email => "johnny@example.com",
:password => "secret",
:password_confirmation => "secret")
@user.should_not be_valid
end
it "is not valid without an email" do
@user.email = nil
@user.should_not be_valid
end
it "is not valid without a unique email" do
@user2 = User.create( :username => "Johnny",
:email => "jimmy@example.com",
:password => "secret",
:password_confirmation => "secret")
@user.should_not be_valid
end
it "is not valid without a password"
it "is not valid without matching password and password confirmation" do
@user.password = "wrong"
@user.should_not be_valid
end
it "is not valid without a password confirmation" do
@user.password_confirmation = nil
@user.should_not be_valid
end
end



Now create the Roles scaffold

C:\web\auth_with_roles> rails g cucumber:feature role name:string
C:\web\auth_with_roles> rails g scaffold role name:string

create the join table
C:\web\auth_with_roles> rails g model assignment user_id:integer role_id:integer
C:\web\auth_with_roles> rake db:migrate

update the models to

in /app/models/assignment.rb
class Assignment < ActiveRecord::Base
belongs_to :user
belongs_to :role
end

in /app/models/role.rb
class Role < ActiveRecord::Base
has_many :assignments
has_many :users, :through => :assignments
end

in /app/models/user.rb
class User < ActiveRecord::Base
acts_as_authentic
has_many :assignments
has_many :roles, :through => :assignments
def role_symbols
roles.map do |role|
role.name.underscore.to_sym
end
end
end


run some tests
run
C:\web\auth_with_roles> rake cucumber
..................

4 scenarios (4 passed)
18 steps (18 passed)
0m6.688s

run
C:\web\auth_with_roles> rake spec
..................................**.....*.............


Finished in 6.66 seconds
55 examples, 0 failures, 3 pending

Pending:
Assignment add some examples to (or delete) ./spec/models/assignment_spec.rb (Not Yet Implemented)
# ./spec/models/assignment_spec.rb:4
Role add some examples to (or delete) ./spec/models/role_spec.rb (Not Yet Implemented)
# ./spec/models/role_spec.rb:4
User is not valid without a password (Not Yet Implemented)
# ./spec/models/user_spec.rb:37

Let's add some tests for the Role model. /spec/models/role_spec.rb
require 'spec_helper'
describe User do
before(:each) do
@role = Role.new(:name => "Jimmy")
end
it "is valid with valid attributes" do
@role.should be_valid
end
it "is not valid without a name" do
@role.name = nil
@role.should_not be_valid
end
it "is not valid without a unique name"
end

run
C:\web\auth_with_roles> rake spec
..................................*.F*.....*.............

1) Role is not valid without a name
Failure/Error: @role.should_not be_valid
expected valid? to return false, got true
# ./spec/models/role_spec.rb:14
# ./spec/controllers/roles_controller_spec.rb:3


Finished in 4.59 seconds
57 examples, 1 failures, 3 pending

Pending:
Assignment add some examples to (or delete) ./spec/models/assignment_spec.rb (Not Yet Implemented)
# ./spec/models/assignment_spec.rb:4
Role is not valid without a unique name (Not Yet Implemented)
# ./spec/models/role_spec.rb:17
User is not valid without a password (Not Yet Implemented)
# ./spec/models/user_spec.rb:37


We need to add vilidation to the Role model /app/models/role.rb
class Role < ActiveRecord::Base
has_many :assignments
has_many :users, :through => :assignments
validates :name, :presence => true
end


run
C:\web\auth_with_roles> rake spec
..................................*..*.....*.............


Finished in 4.75 seconds
57 examples, 0 failures, 3 pending

Pending:
Assignment add some examples to (or delete) ./spec/models/assignment_spec.rb (Not Yet Implemented)
# ./spec/models/assignment_spec.rb:4
Role is not valid without a unique name (Not Yet Implemented)
# ./spec/models/role_spec.rb:17
User is not valid without a password (Not Yet Implemented)
# ./spec/models/user_spec.rb:37

Passed.
Let's add finish the tests for the Role model. /spec/models/role_spec.rb
require 'spec_helper'
describe User do
before(:each) do
@role = Role.new(:name => "Jimmy")
end
it "is valid with valid attributes" do
@role.should be_valid
end
it "is not valid without a name" do
@role.name = nil
@role.should_not be_valid
end
it "is not valid without a unique name" do
@role2 = ROle.create( :username => "Jimmy") @role.should_not be_valid
end
end

run
C:\web\auth_with_roles> rake spec
..................................*..F.....*.............

1) Role is not valid without a unique name
Failure/Error: @role.should_not be_valid
expected valid? to return false, got true
# ./spec/models/role_spec.rb:19
# ./spec/controllers/roles_controller_spec.rb:3


Finished in 4.63 seconds
57 examples, 1 failures, 2 pending

Pending:
Assignment add some examples to (or delete) ./spec/models/assignment_spec.rb (Not Yet Implemented)
# ./spec/models/assignment_spec.rb:4
User is not valid without a password (Not Yet Implemented)
# ./spec/models/user_spec.rb:37


We need to add vilidation to the Role model /app/models/role.rb
class Role < ActiveRecord::Base
has_many :assignments
has_many :users, :through => :assignments
validates :name, :presence => true
validates :name, :uniqueness => true
end


run
C:\web\auth_with_roles> rake spec
..................................*........*.............


Finished in 6.27 seconds
57 examples, 0 failures, 2 pending

Pending:
Assignment add some examples to (or delete) ./spec/models/assignment_spec.rb (Not Yet Implemented)
# ./spec/models/assignment_spec.rb:4
User is not valid without a password (Not Yet Implemented)
# ./spec/models/user_spec.rb:37

We need to lock down the roles controller to admin only

In the application controller /app/controllers/application_controller.rb add
class ApplicationController < ActionController::Base
protect_from_forgery
layout 'application'
helper :all
helper_method :current_user_session, :current_user
#filter_parameter_logging :password, :password_confirmation
before_filter :set_current_user
protected
def set_current_user
Authorization.current_user = current_user
end
def permission_denied
flash[:error] = "Sorry, you are not allowed to access that page."
redirect_to '/'
end
private
def current_user_session
return @current_user_session if defined?(@current_user_session)
@current_user_session = UserSession.find
end
def current_user
return @current_user if defined?(@current_user)
@current_user = current_user_session && current_user_session.record
end
def require_user
unless current_user
store_location
flash[:notice] = "You must be logged in to access this page"
redirect_to login_url
return false
end
end
def require_no_user
if current_user
store_location
flash[:notice] = "You must be logged out to access this page"
redirect_to account_url
return false
end
end
def store_location
session[:return_to] = request.fullpath
end
def redirect_back_or_default(default)
redirect_to(session[:return_to] || default)
session[:return_to] = nil
end
end

In the role controller /app/controllers/roles_controller.rb add
class RolesController < ApplicationController
filter_resource_access
.....................................
end

In the user controller /app/controllers/users_controller.rb add
class RolesController < ApplicationController
filter_resource_access
.....................................
end


run
C:\web\auth_with_roles> rake cucumber
.F--.F-...........

(::) failed steps (::)

cannot fill in, no text field, text area or password field with id, name, or label 'Name' found (Capybara::ElementNotFound)
./features/step_definitions/web_steps.rb:41
./features/step_definitions/web_steps.rb:14:in `with_scope'
./features/step_definitions/web_steps.rb:40:in `/^(?:|I )fill in "([^\"]*)" with "([^\"]*)"(?: within "([^\"]*)")?$/'
features\manage_roles.feature:8:in `When I fill in "Name" with "name 1"'

scope '//table//*[position() = 4 and self::tr]' not found on page (Capybara::ElementNotFound)
./features/step_definitions/role_steps.rb:7:in `/^I delete the (\d+)(?:st|nd|rd|th) role$/'
features\manage_roles.feature:42:in `When I delete the 3rd role'

Failing Scenarios:
cucumber features\manage_roles.feature:6 # Scenario: Register new role
cucumber features\manage_roles.feature:35 # Scenario: Delete role

4 scenarios (2 failed, 2 passed)
18 steps (2 failed, 3 skipped, 13 passed)
0m7.813s
rake aborted!
Command failed with status (1): [C:/Ruby/bin/ruby.exe -I "C:/Documents and ...]

Well we got redirected to the home page. We'll have to log in.
in /featues/manage_roles.feature add
Feature: Manage roles
In order to manage roles
as an Admin
I want to create and edit roles.
Scenario: Register new role
Given the following roles:
|name|
|admin|
And the following users:
|username |email|password|password_confirmation|role_ids|
|don |email1@example.com|secret|secret|1|
And I am logged in as "don" with password "secret"
Given I am on the new role page
When I fill in "Name" with "name 1"
And I press "Create"
Then I should see "name 1"
Scenario: Delete role
Given the following roles:
|name |
|admin |
|name 2|
|name 3|
|name 4|
And the following users:
|username |email |password|password_confirmation|role_ids|
|don |email1@example.com|secret |secret |1 |
And I am logged in as "don" with password "secret"
When I delete the 3rd role
Then I should see the following roles:
|Name |
|admin |
|name 2|
|name 4|

run
C:\web\auth_with_roles> rake cucumber
..U----..U--...........

4 scenarios (2 undefined, 2 passed)
23 steps (6 skipped, 2 undefined, 15 passed)
0m5.781s

You can implement step definitions for undefined steps with these snippets:

Given /^I am logged in as "([^\"]*)" with password "([^\"]*)"$/ do |arg1, arg2|
pending # express the regexp above with the code you wish you had
end

in /featues/step_definitions/user_steps.rb add
Given /^I am logged in as "([^\"]*)" with password "([^\"]*)"$/ do |username, password|
unless username.blank?
visit login_url
fill_in "Username", :with => username
fill_in "Password", :with => password
click_button "Log in"
end
end
run
C:\web\auth_with_roles> rake cucumber
.......................

4 scenarios (4 passed)
23 steps (23 passed)
0m10.063s

Let's update the users feature so we can select a role for a new users and admin can show edit or destroy. everyone can see a list of the users
In /features/manage_users.feature
Feature: Manage users
In order to manage Users
As an Admin
I want to manage users
@focus
Scenario: Register new user
Given the following roles:
|name|
|admin|
And I am on the new user page
When I fill in "Username" with "username 1"
And I fill in "Email" with "example@example.com"
And I fill in "Password" with "secret"
And I fill in "Password confirmation" with "secret"
And I check "admin"
And I press "Create"
Then I should see "username 1"
And I should see "example@example.com"
And I should see "admin"
Scenario: Delete user
Given the following users:
|username |email|password|password_confirmation|
|username 1|email1@example.com|secret|secret|
|username 2|email2@example.com|secret|secret|
|username 3|email3@example.com|secret|secret|
|username 4|email4@example.com|secret|secret|
When I delete the 3rd user
Then I should see the following users:
|Username |Email|
|username 1|email1@example.com|
|username 2|email2@example.com|
|username 4|email4@example.com|
Scenario: Admin sees all user's edit links
Given the following roles:
|name|
|admin|
|guest|
And the following users:
|username |email|password|password_confirmation|role_ids|
|don |email1@example.com|secret|secret|1|
|username 2|email2@example.com|secret|secret|2|
|username 3|email3@example.com|secret|secret|2|
|username 4|email4@example.com|secret|secret|2|
And I am logged in as "don" with password "secret"
Then I should see the following users:
|Username |Email|Show|Edit|Destroy|
|don |email1@example.com|Show|Edit|Destroy|
|username 2|email2@example.com|Show|Edit|Destroy|
|username 3|email3@example.com|Show|Edit|Destroy|
|username 4|email4@example.com|Show|Edit|Destroy|

you might have noticed I added an @focus tag. We'll be running one scenario at a time now. we'll be able to focus on it.
run
C:\web\auth_with_roles> cucumber --tags @focus
......F----

(::) failed steps (::)

cannot check field, no checkbox with id, name, or label 'admin' found (Capybara::ElementNotFound)
./features/step_definitions/web_steps.rb:78
./features/step_definitions/web_steps.rb:14:in `with_scope'
./features/step_definitions/web_steps.rb:77:in `/^(?:|I )check "([^\"]*)"(?: within "([^\"]*)")?$/'
features\manage_users.feature:16:in `And I check "admin"'

Failing Scenarios:
cucumber features\manage_users.feature:6 # Scenario: Register new user

1 scenario (1 failed)
11 steps (1 failed, 4 skipped, 6 passed)
0m7.453s


We Update the _form view so we can add roles to users /app/views/users/_form.html.erb
<%= form_for(@user) do |f| %>
<% if @user.errors.any? %>
<div id="errorExplanation">
<h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>
<ul>
<% @user.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :username %><br />
<%= f.text_field :username %>
</div>
<div class="field">
<%= f.label :email %><br />
<%= f.text_field :email %>
</div>
<div class="field">
<%= f.label :password %><br />
<%= f.password_field :password %>
</div>
<div class="field">
<%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation %>
</div>
<p>
<%= f.label :roles %><br />
<% for role in Role.all %>
<%= check_box_tag "user[role_ids][]", role.id, @user.roles.include?(role) %>
<label for="user_role_ids_"><%= role.name %></label>
<% end %>
</p>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
run
C:\web\auth_with_roles> cucumber --tags @focus
..........F

(::) failed steps (::)

<false> is not true. (Test::Unit::AssertionFailedError)
C:/Ruby/lib/ruby/1.8/test/unit/assertions.rb:48:in `assert_block'
C:/Ruby/lib/ruby/1.8/test/unit/assertions.rb:500:in `_wrap_assertion'
C:/Ruby/lib/ruby/1.8/test/unit/assertions.rb:46:in `assert_block'
C:/Ruby/lib/ruby/1.8/test/unit/assertions.rb:63:in `assert'
C:/Ruby/lib/ruby/1.8/test/unit/assertions.rb:495:in `_wrap_assertion'
C:/Ruby/lib/ruby/1.8/test/unit/assertions.rb:61:in `assert'
./features/step_definitions/web_steps.rb:112
./features/step_definitions/web_steps.rb:14:in `with_scope'
./features/step_definitions/web_steps.rb:108:in `/^(?:|I )should see "([^\"]*)"(?: within "([^\"]*)")?$/'
features\manage_users.feature:20:in `And I should see "admin"'

Failing Scenarios:
cucumber features\manage_users.feature:6 # Scenario: Register new user

1 scenario (1 failed)
11 steps (1 failed, 10 passed)
0m7.531s


We'll also update the show page so we can see the user's roles /app/views/users/show.html.erb
<p class="notice"><%= notice %></p>
<p>
<b>Username:</b>
<%= @user.username %>
</p>
<p>
<b>Email:</b>
<%= @user.email %>
</p>
<p><%= pluralize(@user.roles.size, 'Role') %></p>
<ul>
<% @user.roles.each do |role| %>
<li><%= role.name %></li>
<% end %>
</ul>
<%= link_to 'Edit', edit_user_path(@user) %> |
<%= link_to 'Back', users_path %>

run
C:\web\auth_with_roles> cucumber --tags @focus
...........

1 scenario (1 passed)
11 steps (11 passed)
0m7.828s

It passes.
Now we want only the admin to 'Show", "Edit', and 'Destroy' users.
in /features/manage_users.feature add
@focus
Scenario: Admin sees all user's edit links
Given the following roles:
|name|
|admin|
|guest|
And the following users:
|username |email|password|password_confirmation|role_ids|
|don |email1@example.com|secret|secret|1|
|username 2|email2@example.com|secret|secret|2|
|username 3|email3@example.com|secret|secret|2|
|username 4|email4@example.com|secret|secret|2|
And I am logged in as "don" with password "secret"
Then I should see the following users:
|Username |Email|Show|Edit|Destroy|
|don |email1@example.com|Show|Edit|Destroy|
|username 2|email2@example.com|Show|Edit|Destroy|
|username 3|email3@example.com|Show|Edit|Destroy|
|username 4|email4@example.com|Show|Edit|Destroy|
And I should see "New user"

Remove the previous @focus from feature
run
C:\web\auth_with_roles> cucumber --tags @focus
......F

(::) failed steps (::)

Tables were not identical (Cucumber::Ast::Table::Different)
./features/step_definitions/user_steps.rb:13:in `/^I should see the following users:$/'
features\manage_users.feature:73:in `Then I should see the following users:'

Failing Scenarios:
cucumber features\manage_users.feature:61 # Scenario: Admin sees all user's edit links

2 scenarios (1 failed, 1 passed)
7 steps (1 failed, 6 passed)

we need to modify the index view /app/views/users/index.html.erb
<h1>Listing users</h1>
<table>
<tr>
<th>Username</th>
<th>Email</th>
<th>Show</th>
<th>Edit</th>
<th>Destroy</th>
</tr>
<% @users.each do |user| %>
<tr>
<td><%= user.username %></td>
<td><%= user.email %></td>
<td><%= link_to 'Show', user %></td>
<td><%= link_to 'Edit', edit_user_path(user) %></td>
<td><%= link_to 'Destroy', user, :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
</table>
<br />

<%= link_to 'New User', new_user_path %>

run
C:\web\auth_with_roles> cucumber --tags @focus
.....

1 scenario (1 passed)
5 steps (5 passed)
0m7.828s

We will need to fix the delete user scenario. remember to move the @focus tag to it.
Scenario: Delete user
Given the following roles:
|name|
|admin|
|guest|
And the following users:
|username |email|password|password_confirmation|role_ids|
|don |email1@example.com|secret|secret|1|
|username 2|email2@example.com|secret|secret|2|
|username 3|email3@example.com|secret|secret|2|
|username 4|email4@example.com|secret|secret|2|
And I am logged in as "don" with password "secret"
When I delete the 3rd user
Then I should see the following users:
|Username |Email|
|don |email1@example.com|
|username 2|email2@example.com|
|username 4|email4@example.com|


run
C:\web\auth_with_roles> cucumber --tags @focus
.....

1 scenario (1 passed)
5 steps (5 passed)
0m7.828s

Let's add a scenario so that users can see everyone but do not see the show edit or destroy and the New user link.
@focus
Scenario: guest sees all user's and Show links, but no edit or destroy links
Given the following roles:
|name|
|admin|
|guest|
And the following users:
|username |email|password|password_confirmation|role_ids|
|don |email1@example.com|secret|secret|1|
|jimmy |email2@example.com|secret|secret|2|
|username 3|email3@example.com|secret|secret|2|
|username 4|email4@example.com|secret|secret|2|
And I am logged in as "jimmy" with password "secret"
Then I should see the following users:
|Username |Email |Show|Edit|Destroy|
|don |email1@example.com|Show| | |
|jimmy |email2@example.com|Show| | |
|username 3|email3@example.com|Show| | |
|username 4|email4@example.com|Show| | |
And I should not see "New User"



run
C:\web\auth_with_roles> cucumber --tags @focus
...F-

(::) failed steps (::)

Tables were not identical (Cucumber::Ast::Table::Different)
./features/step_definitions/user_steps.rb:13:in `/^I should see the following users:$/'
features\manage_users.feature:97:in `Then I should see the following users:'

Failing Scenarios:
cucumber features\manage_users.feature:85 # Scenario: guest sees all user's and Show links, but no edit or destroy links

1 scenario (1 failed)
5 steps (1 failed, 1 skipped, 3 passed)
0m14.359s

Let's updae the index page /app/views/users/index.html.erb
<h1>Listing users</h1>
<table>
<tr>
<th>Username</th>
<th>Email</th>
<th>Show</th>
<th>Edit</th>
<th>Destroy</th>
</tr>
<% @users.each do |user| %>
<tr>
<td><%= user.username %></td>
<td><%= user.email %></td>
<td>
<% if permitted_to? :show, @user %>
<%= link_to 'Show', user %>
<% end %>
</td>
<td>
<% if permitted_to? :edit, @user %>
<%= link_to 'Edit', edit_user_path(user) %>
<% end %>
</td>
<td>
<% if permitted_to? :destroy, @user %>
<%= link_to 'Destroy', user, :confirm => 'Are you sure?', :method => :delete %>
<% end %>
</td>
</tr>
<% end %>
</table>
<br />
<% if permitted_to? :create, @user %>
<%= link_to 'New User', new_user_path %>
<% end %>

run
C:\web\auth_with_roles> cucumber --tags @focus
.....

1 scenario (1 passed)
5 steps (5 passed)
0m7.828s

Let's test all the cucumber tests
C:\web\auth_with_roles> rake cucumber
......................................

6 scenarios (6 passed)
38 steps (38 passed)
0m18.016s

Now only an admin can create, edit or delete roles.
You'll need to lock down the Users so only an admin can create, edit or destroy users, or what ever you decide.

Cheers,
John

Source code on git hub.

Thanks to
railscasts Authlogic
and the documentation for the gems I used.