Skip to content

Commit

Permalink
Introducing Registerable module, allowing users to sign up.
Browse files Browse the repository at this point in the history
  • Loading branch information
carlosantoniodasilva committed Feb 4, 2010
1 parent 4de1e43 commit 6b837cb
Show file tree
Hide file tree
Showing 17 changed files with 173 additions and 22 deletions.
5 changes: 3 additions & 2 deletions README.rdoc
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ Devise is a flexible authentication solution for Rails based on Warden. It:
* Allows you to have multiple roles (or models/scopes) signed in at the same time;
* Is based on a modularity concept: use just what you really need.

Right now it's composed of nine modules:
Right now it's composed of ten modules:

* Authenticatable: responsible for encrypting password and validating authenticity of a user while signing in.
* Confirmable: responsible for verifying whether an account is already confirmed to sign in, and to send emails with confirmation instructions.
* Recoverable: takes care of reseting the user password and send reset instructions.
* Registerable: handles signing up users through a registration process.
* Rememberable: manages generating and clearing token for remember the user from a saved cookie.
* Trackable: tracks sign in count, timestamps and ip.
* Validatable: creates all needed validations for email and password. It's totally optional, so you're able to to customize validations by yourself.
Expand Down Expand Up @@ -170,7 +171,7 @@ Since devise is an engine, it has all default views inside the gem. They are goo

ruby script/generate devise_views

By default Devise will use the same views for all roles you have. But what if you need so different views to each of them? Devise also has an easy way to accomplish it: just setup config.scoped_views to true inside "config/initializers/devise.rb".
By default Devise will use the same views for all roles you have. But what if you need so different views to each of them? Devise also has an easy way to accomplish it: just setup config.scoped_views to true inside "config/initializers/devise.rb".

After doing so you will be able to have views based on the scope like 'sessions/users/new' and 'sessions/admin/new'. If no view is found within the scope, Devise will fallback to the default view.

Expand Down
22 changes: 22 additions & 0 deletions app/controllers/registrations_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
class RegistrationsController < ApplicationController
include Devise::Controllers::InternalHelpers
include Devise::Controllers::Common

# POST /resource/registration
def create
self.resource = resource_class.new(params[resource_name])

if resource.save
# Attempt to sign the resource in. When there is no other thing blocking
# the resource (ie confirmations), then the resource will be signed in,
# otherwise the specific message is shown and the resource will be
# redirected to the sign in page.
sign_in(resource_name, resource)
# At this time the resource has signed in and no hook has signed it out.
set_flash_message :notice, :signed_up
sign_in_and_redirect(resource_name, resource, true)
else
render_with_scope :new
end
end
end
19 changes: 19 additions & 0 deletions app/views/registrations/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<h2>Sign up</h2>

<%- if devise_mapping.registerable? %>
<% form_for resource_name, resource, :url => registration_path(resource_name) do |f| -%>
<%= f.error_messages %>
<p><%= f.label :email %></p>
<p><%= f.text_field :email %></p>

<p><%= f.label :password %></p>
<p><%= f.password_field :password %></p>

<p><%= f.label :password_confirmation %></p>
<p><%= f.password_field :password_confirmation %></p>

<p><%= f.submit "Register" %></p>
<% end -%>
<% end%>
<%= render :partial => "shared/devise_links" %>
6 changes: 5 additions & 1 deletion app/views/shared/_devise_links.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
<%= link_to t('devise.sessions.link'), new_session_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
<%= link_to t('devise.registrations.link'), new_registration_path(resource_name) %><br />
<% end -%>
<%- if devise_mapping.recoverable? && controller_name != 'passwords' %>
<%= link_to t('devise.passwords.link'), new_password_path(resource_name) %><br />
<% end -%>
Expand All @@ -12,4 +16,4 @@
<%- if devise_mapping.lockable? && controller_name != 'unlocks' %>
<%= link_to t('devise.unlocks.link'), new_unlock_path(resource_name) %><br />
<% end -%>
<% end -%>
8 changes: 5 additions & 3 deletions lib/devise.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,16 @@ module Orm
autoload :MongoMapper, 'devise/orm/mongo_mapper'
end

ALL = [:authenticatable, :activatable, :confirmable, :recoverable,
:rememberable, :validatable, :trackable, :timeoutable, :lockable, :token_authenticatable]
ALL = [:authenticatable, :activatable, :confirmable, :lockable, :recoverable,
:registerable, :rememberable, :timeoutable, :token_authenticatable,
:trackable, :validatable]

# Maps controller names to devise modules
CONTROLLERS = {
:sessions => [:authenticatable, :token_authenticatable],
:passwords => [:recoverable],
:confirmations => [:confirmable],
:registrations => [:registerable],
:unlocks => [:lockable]
}

Expand Down Expand Up @@ -231,4 +233,4 @@ def add_module(module_name, options = {})
end

require 'devise/mapping'
require 'devise/rails'
require 'devise/rails'
2 changes: 1 addition & 1 deletion lib/devise/controllers/url_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ module Controllers
# Those helpers are added to your ApplicationController.
module UrlHelpers

[:session, :password, :confirmation, :unlock].each do |module_name|
[:session, :password, :confirmation, :registration, :unlock].each do |module_name|
[:path, :url].each do |path_or_url|
actions = [ nil, :new_ ]
actions << :edit_ if module_name == :password
Expand Down
4 changes: 4 additions & 0 deletions lib/devise/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ en:
link: "Didn't receive confirmation instructions?"
send_instructions: 'You will receive an email with instructions about how to confirm your account in a few minutes.'
confirmed: 'Your account was successfully confirmed. You are now signed in.'
registrations:
link: 'Sign up'
signed_up: 'You have signed up successfully.'
unlocks:
link: "Didn't receive unlock instructions?"
send_instructions: 'You will receive an email with instructions about how to unlock your account in a few minutes.'
Expand All @@ -27,3 +30,4 @@ en:
confirmation_instructions: 'Confirmation instructions'
reset_password_instructions: 'Reset password instructions'
unlock_instructions: 'Unlock Instructions'

2 changes: 1 addition & 1 deletion lib/devise/mapping.rb
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def #{m}?
# Configure default path names, allowing the user overwrite defaults by
# passing a hash in :path_names.
def setup_path_names
[:sign_in, :sign_out, :password, :confirmation].each do |path_name|
[:sign_in, :sign_out, :password, :confirmation, :registration, :unlock].each do |path_name|
@path_names[path_name] ||= path_name.to_s
end
end
Expand Down
8 changes: 8 additions & 0 deletions lib/devise/models/registerable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module Devise
module Models
# Registerable is responsible for everything related to registering a new
# resource (ie account or sign up).
module Registerable
end
end
end
11 changes: 7 additions & 4 deletions lib/devise/rails/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,6 @@ def authenticatable(routes, mapping)
end
end

def recoverable(routes, mapping)
routes.resource :password, :only => [:new, :create, :edit, :update], :as => mapping.path_names[:password]
end

def confirmable(routes, mapping)
routes.resource :confirmation, :only => [:new, :create, :show], :as => mapping.path_names[:confirmation]
end
Expand All @@ -117,6 +113,13 @@ def lockable(routes, mapping)
routes.resource :unlock, :only => [:new, :create, :show], :as => mapping.path_names[:unlock]
end

def recoverable(routes, mapping)
routes.resource :password, :only => [:new, :create, :edit, :update], :as => mapping.path_names[:password]
end

def registerable(routes, mapping)
routes.resource :registration, :only => [:new, :create], :as => mapping.path_names[:registration]
end
end
end
end
58 changes: 58 additions & 0 deletions test/integration/registerable_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
require 'test/test_helper'

class RegistrationTest < ActionController::IntegrationTest

test 'a guest admin should be able to sign in successfully' do
visit new_admin_session_path
click_link 'Sign up'

assert_template 'registrations/new'

fill_in 'email', :with => 'new_user@test.com'
fill_in 'password', :with => 'new_user123'
fill_in 'password confirmation', :with => 'new_user123'
click_button 'Register'

assert_contain 'You have signed up successfully.'
assert warden.authenticated?(:admin)

admin = Admin.last
assert_equal admin.email, 'new_user@test.com'
end

test 'a guest user should be able to sign up successfully and be blocked by confirmation' do
visit new_user_session_path
click_link 'Sign up'

assert_template 'registrations/new'

fill_in 'email', :with => 'new_user@test.com'
fill_in 'password', :with => 'new_user123'
fill_in 'password confirmation', :with => 'new_user123'
click_button 'Register'

follow_redirect!

assert_contain 'You have to confirm your account before continuing.'
assert_not warden.authenticated?(:user)

user = User.last
assert_equal user.email, 'new_user@test.com'
end

test 'a guest user cannot sign up with invalid information' do
visit new_user_session_path
click_link 'Sign up'

fill_in 'email', :with => 'invalid_email'
fill_in 'password', :with => 'new_user123'
fill_in 'password confirmation', :with => 'new_user321'
click_button 'Register'

assert_template 'registrations/new'
assert_have_selector '#errorExplanation'
assert_contain "Email is invalid"
assert_contain "Password doesn't match confirmation"
assert_nil User.first
end
end
12 changes: 8 additions & 4 deletions test/mapping_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ class MappingTest < ActiveSupport::TestCase
assert_equal 'sign_out', mapping.path_names[:sign_out]
assert_equal 'password', mapping.path_names[:password]
assert_equal 'confirmation', mapping.path_names[:confirmation]
assert_equal 'registration', mapping.path_names[:registration]
assert_equal 'unlock', mapping.path_names[:unlock]
end

test 'allow custom path names to be given' do
Expand All @@ -75,6 +77,8 @@ class MappingTest < ActiveSupport::TestCase
assert_equal 'logout', mapping.path_names[:sign_out]
assert_equal 'secret', mapping.path_names[:password]
assert_equal 'verification', mapping.path_names[:confirmation]
assert_equal 'sign_up', mapping.path_names[:registration]
assert_equal 'unblock', mapping.path_names[:unlock]
end

test 'has an empty path as default path prefix' do
Expand All @@ -86,7 +90,7 @@ class MappingTest < ActiveSupport::TestCase
mapping = Devise.mappings[:manager]
assert_equal '/:locale/', mapping.path_prefix
end

test 'retrieve as from the proper position' do
assert_equal 1, Devise.mappings[:user].as_position
assert_equal 2, Devise.mappings[:manager].as_position
Expand All @@ -96,13 +100,13 @@ class MappingTest < ActiveSupport::TestCase
assert_equal '/users', Devise.mappings[:user].raw_path
assert_equal '/:locale/accounts', Devise.mappings[:manager].raw_path
end

test 'raw path ignores the relative_url_root' do
swap ActionController::Base, :relative_url_root => "/abc" do
assert_equal '/users', Devise.mappings[:user].raw_path
end
end

test 'parsed path is returned' do
begin
Devise.default_url_options {{ :locale => I18n.locale }}
Expand All @@ -112,7 +116,7 @@ class MappingTest < ActiveSupport::TestCase
Devise.default_url_options {{ }}
end
end

test 'parsed path adds in the relative_url_root' do
swap ActionController::Base, :relative_url_root => '/abc' do
assert_equal '/abc/users', Devise.mappings[:user].parsed_path
Expand Down
2 changes: 1 addition & 1 deletion test/models_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def assert_include_modules(klass, *modules)
end

test 'add modules cherry pick' do
assert_include_modules Admin, :authenticatable, :timeoutable
assert_include_modules Admin, :authenticatable, :registerable, :timeoutable
end

test 'set a default value for stretches' do
Expand Down
2 changes: 1 addition & 1 deletion test/rails_app/app/active_record/admin.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class Admin < ActiveRecord::Base
devise :authenticatable, :timeoutable
devise :authenticatable, :registerable, :timeoutable

def self.find_for_authentication(conditions)
last(:conditions => conditions)
Expand Down
6 changes: 4 additions & 2 deletions test/rails_app/app/active_record/user.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
class User < ActiveRecord::Base
devise :authenticatable, :confirmable, :recoverable, :rememberable, :trackable,
:validatable, :timeoutable, :lockable, :token_authenticatable
devise :authenticatable, :confirmable, :lockable, :recoverable,
:registerable, :rememberable, :timeoutable, :token_authenticatable,
:trackable, :validatable

attr_accessible :username, :email, :password, :password_confirmation
end
5 changes: 3 additions & 2 deletions test/rails_app/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
map.devise_for :admin, :as => 'admin_area'
map.devise_for :accounts, :scope => 'manager', :path_prefix => ':locale',
:class_name => "User", :requirements => { :extra => 'value' }, :path_names => {
:sign_in => 'login', :sign_out => 'logout', :password => 'secret',
:confirmation => 'verification', :unlock => 'unblock'
:sign_in => 'login', :sign_out => 'logout',
:password => 'secret', :confirmation => 'verification',
:unlock => 'unblock', :registration => 'sign_up'
}

map.resources :users, :only => [:index], :member => { :expire => :get }
Expand Down
23 changes: 23 additions & 0 deletions test/routes_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,26 @@ class MapRoutingTest < ActionController::TestCase
assert_recognizes({:controller => 'passwords', :action => 'update'}, {:path => 'users/password', :method => :put})
end

test 'map new user unlock' do
assert_recognizes({:controller => 'unlocks', :action => 'new'}, 'users/unlock/new')
end

test 'map create user unlock' do
assert_recognizes({:controller => 'unlocks', :action => 'create'}, {:path => 'users/unlock', :method => :post})
end

test 'map show user unlock' do
assert_recognizes({:controller => 'unlocks', :action => 'show'}, {:path => 'users/unlock', :method => :get})
end

test 'map new user registration' do
assert_recognizes({:controller => 'registrations', :action => 'new'}, 'users/registration/new')
end

test 'map create user registration' do
assert_recognizes({:controller => 'registrations', :action => 'create'}, {:path => 'users/registration', :method => :post})
end

test 'map admin session with :as option' do
assert_recognizes({:controller => 'sessions', :action => 'new'}, {:path => 'admin_area/sign_in', :method => :get})
end
Expand Down Expand Up @@ -72,4 +92,7 @@ class MapRoutingTest < ActionController::TestCase
assert_recognizes({:controller => 'unlocks', :action => 'new', :locale => 'en', :extra => 'value'}, '/en/accounts/unblock/new')
end

test 'map account with custom path name for registration' do
assert_recognizes({:controller => 'registrations', :action => 'new', :locale => 'en', :extra => 'value'}, '/en/accounts/sign_up/new')
end
end

1 comment on commit 6b837cb

@grimen
Copy link
Contributor

@grimen grimen commented on 6b837cb Feb 8, 2010

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YES! =D

Please sign in to comment.