devise_invitable 1.1.6 → 1.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +31 -0
- data/app/controllers/devise/invitations_controller.rb +20 -9
- data/app/controllers/devise_invitable/registrations_controller.rb +1 -1
- data/config/locales/en.yml +1 -0
- data/lib/devise_invitable/model.rb +21 -8
- data/lib/devise_invitable/routes.rb +1 -0
- data/lib/devise_invitable/version.rb +1 -1
- data/lib/generators/active_record/templates/migration.rb +1 -1
- metadata +19 -18
data/README.rdoc
CHANGED
|
@@ -32,12 +32,14 @@ Replace MODEL by the class name you want to add DeviseInvitable, like User, Admi
|
|
|
32
32
|
|
|
33
33
|
Follow the walkthrough for Devise and after it's done, follow this walkthrough.
|
|
34
34
|
|
|
35
|
+
== Devise Configuration
|
|
35
36
|
Add :invitable to the <tt>devise</tt> call in your model (we’re assuming here you already have a User model with some Devise modules):
|
|
36
37
|
|
|
37
38
|
class User < ActiveRecord::Base
|
|
38
39
|
devise :database_authenticatable, :confirmable, :invitable
|
|
39
40
|
end
|
|
40
41
|
|
|
42
|
+
== ActiveRecord Migration
|
|
41
43
|
Add t.invitable to your Devise model migration:
|
|
42
44
|
|
|
43
45
|
create_table :users do
|
|
@@ -67,6 +69,25 @@ or for a model that already exists, define a migration to add DeviseInvitable to
|
|
|
67
69
|
|
|
68
70
|
# Allow null encrypted_password
|
|
69
71
|
change_column :users, :encrypted_password, :string, :null => true
|
|
72
|
+
# Allow null password_salt (add it if you are using Devise's encryptable module)
|
|
73
|
+
change_column :users, :password_salt, :string, :null => true
|
|
74
|
+
|
|
75
|
+
== Mongoid Field Definitions
|
|
76
|
+
If you are using Mongoid, define the following fields and indexes within your invitable model:
|
|
77
|
+
|
|
78
|
+
field :invitation_token, type: String
|
|
79
|
+
field :invitation_sent_at, type: Time
|
|
80
|
+
field :invitation_accepted_at, type: Time
|
|
81
|
+
field :invitation_limit, type: Integer
|
|
82
|
+
|
|
83
|
+
index( {invitation_token: 1}, {:background => true} )
|
|
84
|
+
index( {invitation_by_id: 1}, {:background => true} )
|
|
85
|
+
|
|
86
|
+
You do not need to define a belongs_to relationship, as DeviseInvitable does this on your behalf:
|
|
87
|
+
belongs_to :invited_by, :polymorphic => true
|
|
88
|
+
|
|
89
|
+
Remember to create indexes within the MongoDB database after deploying your changes.
|
|
90
|
+
rake db:mongoid:create_indexes
|
|
70
91
|
|
|
71
92
|
== Model configuration
|
|
72
93
|
|
|
@@ -148,11 +169,21 @@ If you want to create the invitation but not send it, you can set <tt>skip_invit
|
|
|
148
169
|
end
|
|
149
170
|
# => the record will be created, but the invitation email will not be sent
|
|
150
171
|
|
|
172
|
+
When skip_invitation is used, you must also then set the invitation_sent_at field when the user is sent their
|
|
173
|
+
token. Failure to do so will yield "Invalid invitation token" errors when the user attempts to accept the invite.
|
|
174
|
+
You can set it like so:
|
|
175
|
+
|
|
176
|
+
user.invitation_sent_at = Time.now.utc
|
|
177
|
+
user.save!
|
|
178
|
+
|
|
151
179
|
You can add :skip_invitation to attributes hash if skip_invitation is added to attr_accessible.
|
|
152
180
|
|
|
153
181
|
User.invite!(:email => "new_user@example.com", :name => "John Doe", :skip_invitation => true)
|
|
154
182
|
# => the record will be created, but the invitation email will not be sent
|
|
155
183
|
|
|
184
|
+
Skip_invitation skips sending the email, but sets invitation_token, so invited_to_sign_up? on the
|
|
185
|
+
resulting user returns true.
|
|
186
|
+
|
|
156
187
|
You can send an invitation to an existing user if your workflow creates them separately:
|
|
157
188
|
|
|
158
189
|
user = User.find(42)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
class Devise::InvitationsController < DeviseController
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
prepend_before_filter :authenticate_inviter!, :only => [:new, :create]
|
|
4
|
+
prepend_before_filter :has_invitations_left?, :only => [:create]
|
|
5
|
+
prepend_before_filter :require_no_authentication, :only => [:edit, :update, :destroy]
|
|
6
|
+
prepend_before_filter :resource_from_invitation_token, :only => [:edit, :destroy]
|
|
6
7
|
helper_method :after_sign_in_path_for
|
|
7
8
|
|
|
8
9
|
# GET /resource/invitation/new
|
|
@@ -25,12 +26,7 @@ class Devise::InvitationsController < DeviseController
|
|
|
25
26
|
|
|
26
27
|
# GET /resource/invitation/accept?invitation_token=abcdef
|
|
27
28
|
def edit
|
|
28
|
-
|
|
29
|
-
render :edit
|
|
30
|
-
else
|
|
31
|
-
set_flash_message(:alert, :invitation_token_invalid)
|
|
32
|
-
redirect_to after_sign_out_path_for(resource_name)
|
|
33
|
-
end
|
|
29
|
+
render :edit
|
|
34
30
|
end
|
|
35
31
|
|
|
36
32
|
# PUT /resource/invitation
|
|
@@ -46,6 +42,13 @@ class Devise::InvitationsController < DeviseController
|
|
|
46
42
|
respond_with_navigational(resource){ render :edit }
|
|
47
43
|
end
|
|
48
44
|
end
|
|
45
|
+
|
|
46
|
+
# GET /resource/invitation/remove?invitation_token=abcdef
|
|
47
|
+
def destroy
|
|
48
|
+
resource.destroy
|
|
49
|
+
set_flash_message :notice, :invitation_removed
|
|
50
|
+
redirect_to after_sign_out_path_for(resource_name)
|
|
51
|
+
end
|
|
49
52
|
|
|
50
53
|
protected
|
|
51
54
|
def current_inviter
|
|
@@ -59,5 +62,13 @@ class Devise::InvitationsController < DeviseController
|
|
|
59
62
|
respond_with_navigational(resource) { render :new }
|
|
60
63
|
end
|
|
61
64
|
end
|
|
65
|
+
|
|
66
|
+
def resource_from_invitation_token
|
|
67
|
+
unless params[:invitation_token] && self.resource = resource_class.to_adapter.find_first(params.slice(:invitation_token))
|
|
68
|
+
set_flash_message(:alert, :invitation_token_invalid)
|
|
69
|
+
redirect_to after_sign_out_path_for(resource_name)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
62
73
|
end
|
|
63
74
|
|
|
@@ -7,7 +7,7 @@ class DeviseInvitable::RegistrationsController < Devise::RegistrationsController
|
|
|
7
7
|
self.resource = resource_class.where(:email => hash[:email], :encrypted_password => '').first
|
|
8
8
|
if self.resource
|
|
9
9
|
self.resource.attributes = hash
|
|
10
|
-
self.resource.accept_invitation
|
|
10
|
+
self.resource.accept_invitation
|
|
11
11
|
end
|
|
12
12
|
end
|
|
13
13
|
self.resource ||= super
|
data/config/locales/en.yml
CHANGED
|
@@ -5,6 +5,7 @@ en:
|
|
|
5
5
|
invitation_token_invalid: 'The invitation token provided is not valid!'
|
|
6
6
|
updated: 'Your password was set successfully. You are now signed in.'
|
|
7
7
|
no_invitations_remaining: "No invitations remaining"
|
|
8
|
+
invitation_removed: 'Your invitation was removed.'
|
|
8
9
|
new:
|
|
9
10
|
header: "Send invitation"
|
|
10
11
|
submit_button: "Send an invitation"
|
|
@@ -36,13 +36,17 @@ module Devise
|
|
|
36
36
|
|
|
37
37
|
include ActiveSupport::Callbacks
|
|
38
38
|
define_callbacks :invitation_accepted
|
|
39
|
+
before_update :generate_confirmation_token, :if => :confirmation_required_for_invited?
|
|
40
|
+
after_update :send_on_create_confirmation_instructions, :if => :confirmation_required_for_invited?
|
|
39
41
|
|
|
40
42
|
attr_writer :skip_password
|
|
41
43
|
|
|
42
|
-
scope :
|
|
44
|
+
scope :active, lambda { where(:invitation_token => nil) }
|
|
43
45
|
if defined?(Mongoid) && self < Mongoid::Document
|
|
46
|
+
scope :invitation_not_accepted, lambda { where(:invitation_accepted_at => nil, :invitation_token.ne => nil) }
|
|
44
47
|
scope :invitation_accepted, lambda { where(:invitation_accepted_at.ne => nil) }
|
|
45
48
|
else
|
|
49
|
+
scope :invitation_not_accepted, lambda { where(arel_table[:invitation_token].not_eq(nil)).where(:invitation_accepted_at => nil) }
|
|
46
50
|
scope :invitation_accepted, lambda { where(arel_table[:invitation_accepted_at].not_eq(nil)) }
|
|
47
51
|
|
|
48
52
|
[:before_invitation_accepted, :after_invitation_accepted].each do |callback_method|
|
|
@@ -71,12 +75,17 @@ module Devise
|
|
|
71
75
|
end
|
|
72
76
|
|
|
73
77
|
# Accept an invitation by clearing invitation token and and setting invitation_accepted_at
|
|
74
|
-
|
|
78
|
+
def accept_invitation
|
|
79
|
+
self.invitation_accepted_at = Time.now.utc
|
|
80
|
+
self.invitation_token = nil
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Accept an invitation by clearing invitation token and and setting invitation_accepted_at
|
|
84
|
+
# Saves the model and confirms it if model is confirmable, running invitation_accepted callbacks
|
|
75
85
|
def accept_invitation!
|
|
76
86
|
if self.invited_to_sign_up? && self.valid?
|
|
77
|
-
self.invitation_accepted_at = Time.now.utc
|
|
78
87
|
run_callbacks :invitation_accepted do
|
|
79
|
-
self.
|
|
88
|
+
self.accept_invitation
|
|
80
89
|
self.confirmed_at = self.invitation_accepted_at if self.respond_to?(:confirmed_at)
|
|
81
90
|
self.save(:validate => false)
|
|
82
91
|
end
|
|
@@ -115,17 +124,17 @@ module Devise
|
|
|
115
124
|
|
|
116
125
|
# Required to workaround confirmable model's confirmation_required? method
|
|
117
126
|
# being implemented to check for non-nil value of confirmed_at
|
|
118
|
-
if self.new_record? && self.respond_to?(:confirmation_required
|
|
127
|
+
if self.new_record? && self.respond_to?(:confirmation_required?, true)
|
|
119
128
|
def self.confirmation_required?; false; end
|
|
120
129
|
end
|
|
121
130
|
|
|
122
131
|
generate_invitation_token if self.invitation_token.nil?
|
|
123
|
-
self.invitation_sent_at = Time.now.utc
|
|
132
|
+
self.invitation_sent_at = Time.now.utc unless @skip_invitation
|
|
124
133
|
self.invited_by = invited_by if invited_by
|
|
125
134
|
|
|
126
135
|
# Call these before_validate methods since we aren't validating on save
|
|
127
|
-
self.downcase_keys if self.new_record? && self.respond_to?(:downcase_keys)
|
|
128
|
-
self.strip_whitespace if self.new_record? && self.respond_to?(:strip_whitespace)
|
|
136
|
+
self.downcase_keys if self.new_record? && self.respond_to?(:downcase_keys, true)
|
|
137
|
+
self.strip_whitespace if self.new_record? && self.respond_to?(:strip_whitespace, true)
|
|
129
138
|
|
|
130
139
|
if save(:validate => false)
|
|
131
140
|
self.invited_by.decrement_invitation_limit! if !was_invited and self.invited_by.present?
|
|
@@ -163,6 +172,10 @@ module Devise
|
|
|
163
172
|
!@skip_password && super
|
|
164
173
|
end
|
|
165
174
|
|
|
175
|
+
def confirmation_required_for_invited?
|
|
176
|
+
respond_to?(:confirmation_required?, true) && confirmation_required? && invitation_accepted?
|
|
177
|
+
end
|
|
178
|
+
|
|
166
179
|
# Deliver the invitation email
|
|
167
180
|
def deliver_invitation
|
|
168
181
|
send_devise_notification(:invitation_instructions)
|
|
@@ -6,6 +6,7 @@ module ActionDispatch::Routing
|
|
|
6
6
|
resource :invitation, :only => [:new, :create, :update],
|
|
7
7
|
:path => mapping.path_names[:invitation], :controller => controllers[:invitations] do
|
|
8
8
|
get :edit, :path => mapping.path_names[:accept], :as => :accept
|
|
9
|
+
get :destroy, :path => mapping.path_names[:remove], :as => :remove
|
|
9
10
|
end
|
|
10
11
|
end
|
|
11
12
|
|
|
@@ -6,7 +6,7 @@ class DeviseInvitableAddTo<%= table_name.camelize %> < ActiveRecord::Migration
|
|
|
6
6
|
t.datetime :invitation_accepted_at
|
|
7
7
|
t.integer :invitation_limit
|
|
8
8
|
t.references :invited_by, :polymorphic => true
|
|
9
|
-
t.index :invitation_token # for invitable
|
|
9
|
+
t.index :invitation_token, :unique => true # for invitable
|
|
10
10
|
t.index :invited_by_id
|
|
11
11
|
end
|
|
12
12
|
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: devise_invitable
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
hash:
|
|
4
|
+
hash: 3
|
|
5
5
|
prerelease:
|
|
6
6
|
segments:
|
|
7
7
|
- 1
|
|
8
8
|
- 1
|
|
9
|
-
-
|
|
10
|
-
version: 1.1.
|
|
9
|
+
- 8
|
|
10
|
+
version: 1.1.8
|
|
11
11
|
platform: ruby
|
|
12
12
|
authors:
|
|
13
13
|
- Sergio Cambra
|
|
@@ -15,7 +15,7 @@ autorequire:
|
|
|
15
15
|
bindir: bin
|
|
16
16
|
cert_chain: []
|
|
17
17
|
|
|
18
|
-
date: 2013-02
|
|
18
|
+
date: 2013-05-02 00:00:00 Z
|
|
19
19
|
dependencies:
|
|
20
20
|
- !ruby/object:Gem::Dependency
|
|
21
21
|
name: bundler
|
|
@@ -34,23 +34,22 @@ dependencies:
|
|
|
34
34
|
type: :development
|
|
35
35
|
version_requirements: *id001
|
|
36
36
|
- !ruby/object:Gem::Dependency
|
|
37
|
-
name:
|
|
37
|
+
name: railties
|
|
38
38
|
prerelease: false
|
|
39
39
|
requirement: &id002 !ruby/object:Gem::Requirement
|
|
40
40
|
none: false
|
|
41
41
|
requirements:
|
|
42
|
-
- -
|
|
42
|
+
- - ~>
|
|
43
43
|
- !ruby/object:Gem::Version
|
|
44
|
-
hash:
|
|
44
|
+
hash: 7
|
|
45
45
|
segments:
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
|
|
49
|
-
version: 2.1.2
|
|
46
|
+
- 3
|
|
47
|
+
- 0
|
|
48
|
+
version: "3.0"
|
|
50
49
|
type: :runtime
|
|
51
50
|
version_requirements: *id002
|
|
52
51
|
- !ruby/object:Gem::Dependency
|
|
53
|
-
name:
|
|
52
|
+
name: actionmailer
|
|
54
53
|
prerelease: false
|
|
55
54
|
requirement: &id003 !ruby/object:Gem::Requirement
|
|
56
55
|
none: false
|
|
@@ -65,18 +64,19 @@ dependencies:
|
|
|
65
64
|
type: :runtime
|
|
66
65
|
version_requirements: *id003
|
|
67
66
|
- !ruby/object:Gem::Dependency
|
|
68
|
-
name:
|
|
67
|
+
name: devise
|
|
69
68
|
prerelease: false
|
|
70
69
|
requirement: &id004 !ruby/object:Gem::Requirement
|
|
71
70
|
none: false
|
|
72
71
|
requirements:
|
|
73
|
-
- -
|
|
72
|
+
- - ">="
|
|
74
73
|
- !ruby/object:Gem::Version
|
|
75
|
-
hash:
|
|
74
|
+
hash: 15
|
|
76
75
|
segments:
|
|
77
|
-
-
|
|
78
|
-
-
|
|
79
|
-
|
|
76
|
+
- 2
|
|
77
|
+
- 1
|
|
78
|
+
- 2
|
|
79
|
+
version: 2.1.2
|
|
80
80
|
type: :runtime
|
|
81
81
|
version_requirements: *id004
|
|
82
82
|
description: It adds support for send invitations by email (it requires to be authenticated) and accept the invitation by setting a password.
|
|
@@ -162,3 +162,4 @@ specification_version: 3
|
|
|
162
162
|
summary: An invitation strategy for Devise
|
|
163
163
|
test_files: []
|
|
164
164
|
|
|
165
|
+
has_rdoc:
|