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 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
Let's get the User model working /spec/models/user_spec.rb
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
run
We need to add vilidation to the Role model /app/models/role.rb
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
run
It passes.
<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 %>
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.
Source code on git hub.
Thanks to
railscasts Authlogic
railscasts declarative-authorization
and the documentation for the gems I used.