diff --git a/README.rdoc b/README.rdoc index df916b12..716998ff 100644 --- a/README.rdoc +++ b/README.rdoc @@ -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. @@ -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. diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb new file mode 100644 index 00000000..0ba6094a --- /dev/null +++ b/app/controllers/registrations_controller.rb @@ -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 diff --git a/app/views/registrations/new.html.erb b/app/views/registrations/new.html.erb new file mode 100644 index 00000000..f8cbd331 --- /dev/null +++ b/app/views/registrations/new.html.erb @@ -0,0 +1,19 @@ +

Sign up

+ +<%- if devise_mapping.registerable? %> + <% form_for resource_name, resource, :url => registration_path(resource_name) do |f| -%> + <%= f.error_messages %> +

<%= f.label :email %>

+

<%= f.text_field :email %>

+ +

<%= f.label :password %>

+

<%= f.password_field :password %>

+ +

<%= f.label :password_confirmation %>

+

<%= f.password_field :password_confirmation %>

+ +

<%= f.submit "Register" %>

+ <% end -%> +<% end%> + +<%= render :partial => "shared/devise_links" %> diff --git a/app/views/shared/_devise_links.erb b/app/views/shared/_devise_links.erb index 22ff4de5..664b7dc2 100644 --- a/app/views/shared/_devise_links.erb +++ b/app/views/shared/_devise_links.erb @@ -2,6 +2,10 @@ <%= link_to t('devise.sessions.link'), new_session_path(resource_name) %>
<% end -%> +<%- if devise_mapping.registerable? && controller_name != 'registrations' %> + <%= link_to t('devise.registrations.link'), new_registration_path(resource_name) %>
+<% end -%> + <%- if devise_mapping.recoverable? && controller_name != 'passwords' %> <%= link_to t('devise.passwords.link'), new_password_path(resource_name) %>
<% end -%> @@ -12,4 +16,4 @@ <%- if devise_mapping.lockable? && controller_name != 'unlocks' %> <%= link_to t('devise.unlocks.link'), new_unlock_path(resource_name) %>
-<% end -%> \ No newline at end of file +<% end -%> diff --git a/lib/devise.rb b/lib/devise.rb index 7501c2ab..ce2f7de4 100644 --- a/lib/devise.rb +++ b/lib/devise.rb @@ -26,14 +26,16 @@ module Devise 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] } @@ -231,4 +233,4 @@ rescue end require 'devise/mapping' -require 'devise/rails' \ No newline at end of file +require 'devise/rails' diff --git a/lib/devise/controllers/url_helpers.rb b/lib/devise/controllers/url_helpers.rb index cac5dfdc..3fd845c3 100644 --- a/lib/devise/controllers/url_helpers.rb +++ b/lib/devise/controllers/url_helpers.rb @@ -19,7 +19,7 @@ module Devise # 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 diff --git a/lib/devise/locales/en.yml b/lib/devise/locales/en.yml index 6b2eb358..2c163a0a 100644 --- a/lib/devise/locales/en.yml +++ b/lib/devise/locales/en.yml @@ -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.' @@ -27,3 +30,4 @@ en: confirmation_instructions: 'Confirmation instructions' reset_password_instructions: 'Reset password instructions' unlock_instructions: 'Unlock Instructions' + diff --git a/lib/devise/mapping.rb b/lib/devise/mapping.rb index fe868411..b3a4486b 100644 --- a/lib/devise/mapping.rb +++ b/lib/devise/mapping.rb @@ -129,7 +129,7 @@ module Devise # 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 diff --git a/lib/devise/models/registerable.rb b/lib/devise/models/registerable.rb new file mode 100644 index 00000000..0f8b5542 --- /dev/null +++ b/lib/devise/models/registerable.rb @@ -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 diff --git a/lib/devise/rails/routes.rb b/lib/devise/rails/routes.rb index 2478635d..acf760b1 100644 --- a/lib/devise/rails/routes.rb +++ b/lib/devise/rails/routes.rb @@ -105,10 +105,6 @@ module ActionController::Routing 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 @@ -117,6 +113,13 @@ module ActionController::Routing 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 diff --git a/test/integration/registerable_test.rb b/test/integration/registerable_test.rb new file mode 100644 index 00000000..6a419b56 --- /dev/null +++ b/test/integration/registerable_test.rb @@ -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 diff --git a/test/mapping_test.rb b/test/mapping_test.rb index 8d882351..abfcb743 100644 --- a/test/mapping_test.rb +++ b/test/mapping_test.rb @@ -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 @@ -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 @@ -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 @@ -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 }} @@ -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 diff --git a/test/models_test.rb b/test/models_test.rb index 10f29295..9418606e 100644 --- a/test/models_test.rb +++ b/test/models_test.rb @@ -23,7 +23,7 @@ class ActiveRecordTest < ActiveSupport::TestCase 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 diff --git a/test/rails_app/app/active_record/admin.rb b/test/rails_app/app/active_record/admin.rb index 5f8d14f0..e4876042 100644 --- a/test/rails_app/app/active_record/admin.rb +++ b/test/rails_app/app/active_record/admin.rb @@ -1,5 +1,5 @@ class Admin < ActiveRecord::Base - devise :authenticatable, :timeoutable + devise :authenticatable, :registerable, :timeoutable def self.find_for_authentication(conditions) last(:conditions => conditions) diff --git a/test/rails_app/app/active_record/user.rb b/test/rails_app/app/active_record/user.rb index bf036c4e..d8c80189 100644 --- a/test/rails_app/app/active_record/user.rb +++ b/test/rails_app/app/active_record/user.rb @@ -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 diff --git a/test/rails_app/config/routes.rb b/test/rails_app/config/routes.rb index 33edd62b..cc7bea2e 100644 --- a/test/rails_app/config/routes.rb +++ b/test/rails_app/config/routes.rb @@ -3,8 +3,9 @@ ActionController::Routing::Routes.draw do |map| 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 } diff --git a/test/routes_test.rb b/test/routes_test.rb index 2e876ebd..27e37900 100644 --- a/test/routes_test.rb +++ b/test/routes_test.rb @@ -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 @@ -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