devise_invitable 1.0.1 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of devise_invitable might be problematic. Click here for more details.
- data/README.rdoc +3 -3
- data/app/controllers/devise/invitations_controller.rb~ +48 -0
- data/app/views/devise/invitations/new.html.erb +4 -2
- data/app/views/devise/invitations/new.html.erb~ +14 -0
- data/lib/devise_invitable.rb +4 -3
- data/lib/devise_invitable.rb~ +68 -0
- data/lib/devise_invitable/controllers/registrations.rb +21 -17
- data/lib/devise_invitable/controllers/registrations.rb~ +43 -0
- data/lib/devise_invitable/model.rb +50 -10
- data/lib/devise_invitable/model.rb~ +249 -0
- data/lib/devise_invitable/version.rb +1 -1
- data/lib/generators/devise_invitable/install_generator.rb +3 -1
- data/lib/generators/devise_invitable/install_generator.rb~ +54 -0
- metadata +23 -17
data/README.rdoc
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
= DeviseInvitable
|
2
|
-
{<img src="
|
2
|
+
{<img src="http://travis-ci.org/scambra/devise_invitable.png"/>}[http://travis-ci.org/scambra/devise_invitable]
|
3
3
|
|
4
4
|
It adds support to devise[http://github.com/plataformatec/devise] for send invitations by email (it requires to be authenticated) and accept the invitation setting the password.
|
5
5
|
|
@@ -90,7 +90,7 @@ or directly as parameters to the <tt>devise</tt> method:
|
|
90
90
|
|
91
91
|
* invitation_limit: The number of invitations users can send. The default value of nil means users can send as many invites as they want. A setting of 0 means they can't send invitations. A setting n > 0 means they can send n invitations.
|
92
92
|
|
93
|
-
* invite_key: The key to be used to check existing users when sending an invitation.
|
93
|
+
* invite_key: The key to be used to check existing users when sending an invitation. You can use multiple keys. This value must be a hash with the invite key as hash keys, and regexp to validate format as values. If you don't to validate the key you can set nil as validation format. The default value is looking for users by email and validating with Devise.email_regexp {:email => Devise.email_regexp}.
|
94
94
|
|
95
95
|
* validate_on_invite: force a record to be valid before being actually invited.
|
96
96
|
|
@@ -272,4 +272,4 @@ Special thanks to rymai[http://github.com/rymai] for the Rails 3 support, his fo
|
|
272
272
|
|
273
273
|
== Copyright
|
274
274
|
|
275
|
-
Copyright (c)
|
275
|
+
Copyright (c) 2012 Sergio Cambra. See LICENSE for details.
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class Devise::InvitationsController < ApplicationController
|
2
|
+
include Devise::Controllers::InternalHelpers
|
3
|
+
|
4
|
+
before_filter :authenticate_inviter!, :only => [:new, :create]
|
5
|
+
before_filter :require_no_authentication, :only => [:edit, :update]
|
6
|
+
helper_method :after_sign_in_path_for
|
7
|
+
|
8
|
+
# GET /resource/invitation/new
|
9
|
+
def new
|
10
|
+
build_resource
|
11
|
+
render_with_scope :new
|
12
|
+
end
|
13
|
+
|
14
|
+
# POST /resource/invitation
|
15
|
+
def create
|
16
|
+
self.resource = resource_class.invite!(params[resource_name])
|
17
|
+
|
18
|
+
if resource.errors.empty?
|
19
|
+
puts params.inspect
|
20
|
+
set_flash_message :notice, :send_instructions, :email => self.resource.email
|
21
|
+
redirect_to after_sign_in_path_for(resource_name)
|
22
|
+
else
|
23
|
+
render_with_scope :new
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# GET /resource/invitation/accept?invitation_token=abcdef
|
28
|
+
def edit
|
29
|
+
if params[:invitation_token] && self.resource = resource_class.first(:conditions => { :invitation_token => params[:invitation_token] })
|
30
|
+
render_with_scope :edit
|
31
|
+
else
|
32
|
+
set_flash_message(:alert, :invitation_token_invalid)
|
33
|
+
redirect_to after_sign_out_path_for(resource_name)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# PUT /resource/invitation
|
38
|
+
def update
|
39
|
+
self.resource = resource_class.accept_invitation!(params[resource_name])
|
40
|
+
|
41
|
+
if resource.errors.empty?
|
42
|
+
set_flash_message :notice, :updated
|
43
|
+
sign_in_and_redirect(resource_name, resource)
|
44
|
+
else
|
45
|
+
render_with_scope :edit
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -3,8 +3,10 @@
|
|
3
3
|
<%= form_for resource, :as => resource_name, :url => invitation_path(resource_name), :html => {:method => :post} do |f| %>
|
4
4
|
<%= devise_error_messages! %>
|
5
5
|
|
6
|
-
|
7
|
-
|
6
|
+
<% resource.class.invite_key_fields.each do |field| -%>
|
7
|
+
<p><%= f.label field %><br />
|
8
|
+
<%= f.text_field field %></p>
|
9
|
+
<% end -%>
|
8
10
|
|
9
11
|
<p><%= f.submit "Send an invitation" %></p>
|
10
12
|
<% end %>
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<h2>Send invitation</h2>
|
2
|
+
|
3
|
+
<%= form_for resource, :as => resource_name, :url => invitation_path(resource_name), :html => {:method => :post} do |f| %>
|
4
|
+
<%= devise_error_messages! %>
|
5
|
+
|
6
|
+
<% Array(resource.class.invite_key).each do |field| -%>
|
7
|
+
<p><%= f.label field %><br />
|
8
|
+
<%= f.text_field field %></p>
|
9
|
+
<% end -%>
|
10
|
+
|
11
|
+
<p><%= f.submit "Send an invitation" %></p>
|
12
|
+
<% end %>
|
13
|
+
|
14
|
+
<%= link_to "Home", after_sign_in_path_for(resource_name) %><br />
|
data/lib/devise_invitable.rb
CHANGED
@@ -37,13 +37,14 @@ module Devise
|
|
37
37
|
mattr_accessor :invitation_limit
|
38
38
|
@@invitation_limit = nil
|
39
39
|
|
40
|
-
# Public: The key to be used to check existing users when sending an invitation
|
40
|
+
# Public: The key to be used to check existing users when sending an invitation,
|
41
|
+
# and the regexp used to test it when validate_on_invite is not set.
|
41
42
|
#
|
42
43
|
# Examples (in config/initializers/devise.rb)
|
43
44
|
#
|
44
|
-
# config.invite_key = :email
|
45
|
+
# config.invite_key = {:email => /\A[^@]+@[^@]+\z/}
|
45
46
|
mattr_accessor :invite_key
|
46
|
-
@@invite_key = :email
|
47
|
+
@@invite_key = {:email => Devise.email_regexp}
|
47
48
|
|
48
49
|
# Public: Resend invitation if user with invited status is invited again
|
49
50
|
# (default: true)
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'devise'
|
2
|
+
|
3
|
+
module DeviseInvitable
|
4
|
+
autoload :Inviter, 'devise_invitable/inviter'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'devise_invitable/mailer'
|
8
|
+
require 'devise_invitable/routes'
|
9
|
+
require 'devise_invitable/controllers/url_helpers'
|
10
|
+
require 'devise_invitable/controllers/registrations'
|
11
|
+
require 'devise_invitable/controllers/helpers'
|
12
|
+
require 'devise_invitable/rails'
|
13
|
+
|
14
|
+
module Devise
|
15
|
+
# Public: Validity period of the invitation token (default: 0). If
|
16
|
+
# invite_for is 0 or nil, the invitation will never expire.
|
17
|
+
# Set invite_for in the Devise configuration file (in config/initializers/devise.rb).
|
18
|
+
#
|
19
|
+
# config.invite_for = 2.weeks # => The invitation token will be valid 2 weeks
|
20
|
+
mattr_accessor :invite_for
|
21
|
+
@@invite_for = 0
|
22
|
+
|
23
|
+
# Public: Flag that force a record to be valid before being actually invited
|
24
|
+
# (default: false).
|
25
|
+
#
|
26
|
+
# Examples (in config/initializers/devise.rb)
|
27
|
+
#
|
28
|
+
# config.validate_on_invite = true
|
29
|
+
mattr_accessor :validate_on_invite
|
30
|
+
@@validate_on_invite = false
|
31
|
+
|
32
|
+
# Public: number of invitations the user is allowed to send
|
33
|
+
#
|
34
|
+
# Examples (in config/initializers/devise.rb)
|
35
|
+
#
|
36
|
+
# config.invitation_limit = nil
|
37
|
+
mattr_accessor :invitation_limit
|
38
|
+
@@invitation_limit = nil
|
39
|
+
|
40
|
+
# Public: The key to be used to check existing users when sending an invitation.
|
41
|
+
# It can be an array.
|
42
|
+
#
|
43
|
+
# Examples (in config/initializers/devise.rb)
|
44
|
+
#
|
45
|
+
# config.invite_key = :email
|
46
|
+
mattr_accessor :invite_key
|
47
|
+
@@invite_key = :email
|
48
|
+
|
49
|
+
# Public: The regexp to be used to test invite key when sending an invitation.
|
50
|
+
# It must be a hash indexed by invite_key
|
51
|
+
#
|
52
|
+
# Examples (in config/initializers/devise.rb)
|
53
|
+
#
|
54
|
+
# config.invite_key = :email
|
55
|
+
mattr_accessor :invite_key_validation_regexp
|
56
|
+
@@invite_key_validation_regexp = {:email => Devise.email_regexp}
|
57
|
+
|
58
|
+
# Public: Resend invitation if user with invited status is invited again
|
59
|
+
# (default: true)
|
60
|
+
#
|
61
|
+
# Example (in config/initializers/devise.rb)
|
62
|
+
#
|
63
|
+
# config.resend_invitation = false
|
64
|
+
mattr_accessor :resend_invitation
|
65
|
+
@@resend_invitation = true
|
66
|
+
end
|
67
|
+
|
68
|
+
Devise.add_module :invitable, :controller => :invitations, :model => 'devise_invitable/model', :route => :invitation
|
@@ -1,36 +1,40 @@
|
|
1
1
|
module DeviseInvitable::Controllers::Registrations
|
2
2
|
def self.included(controller)
|
3
|
-
controller.send :around_filter, :
|
3
|
+
controller.send :around_filter, :keep_invitation_info, :only => :create
|
4
4
|
end
|
5
5
|
|
6
6
|
protected
|
7
7
|
|
8
8
|
def destroy_if_previously_invited
|
9
|
-
invitation_info = {}
|
10
|
-
|
11
9
|
hash = params[resource_name]
|
12
10
|
if hash && hash[:email]
|
13
|
-
resource = resource_class.
|
11
|
+
resource = resource_class.where(:email => hash[:email], :encrypted_password => '').first
|
14
12
|
if resource
|
15
|
-
invitation_info
|
16
|
-
invitation_info[:
|
17
|
-
invitation_info[:
|
13
|
+
@invitation_info = {}
|
14
|
+
@invitation_info[:invitation_sent_at] = resource[:invitation_sent_at]
|
15
|
+
@invitation_info[:invited_by_id] = resource[:invited_by_id]
|
16
|
+
@invitation_info[:invited_by_type] = resource[:invited_by_type]
|
18
17
|
resource.destroy
|
19
18
|
end
|
20
19
|
end
|
21
|
-
|
22
|
-
|
20
|
+
end
|
21
|
+
|
22
|
+
def keep_invitation_info
|
23
|
+
resource_invitable = resource_class.devise_modules.include?(:invitable)
|
24
|
+
destroy_if_previously_invited if resource_invitable
|
23
25
|
yield
|
24
|
-
|
25
|
-
|
26
|
+
reset_invitation_info if resource_invitable
|
27
|
+
end
|
28
|
+
|
29
|
+
def reset_invitation_info
|
26
30
|
# Restore info about the last invitation (for later reference)
|
27
31
|
# Reset the invitation_info only, if invited_by_id is still nil at this stage:
|
28
|
-
resource = resource_class.
|
29
|
-
if resource
|
30
|
-
resource[:invitation_sent_at] = invitation_info[:invitation_sent_at]
|
31
|
-
resource[:invited_by_id] = invitation_info[:invited_by_id]
|
32
|
-
resource[:invited_by_type] = invitation_info[:invited_by_type]
|
32
|
+
resource = resource_class.where(:email => params[resource_name][:email], :invited_by_id => nil).first
|
33
|
+
if resource && @invitation_info
|
34
|
+
resource[:invitation_sent_at] = @invitation_info[:invitation_sent_at]
|
35
|
+
resource[:invited_by_id] = @invitation_info[:invited_by_id]
|
36
|
+
resource[:invited_by_type] = @invitation_info[:invited_by_type]
|
33
37
|
resource.save!
|
34
38
|
end
|
35
39
|
end
|
36
|
-
end
|
40
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module DeviseInvitable::Controllers::Registrations
|
2
|
+
def self.included(controller)
|
3
|
+
controller.send :around_filter, :keep_invitation_info, :only => :create
|
4
|
+
end
|
5
|
+
|
6
|
+
protected
|
7
|
+
|
8
|
+
def destroy_if_previously_invited
|
9
|
+
@invitation_info = {}
|
10
|
+
|
11
|
+
hash = params[resource_name]
|
12
|
+
if hash && hash[:email]
|
13
|
+
resource = resource_class.where(:email => hash[:email], :encrypted_password => '').first
|
14
|
+
if resource
|
15
|
+
@invitation_info[:invitation_sent_at] = resource[:invitation_sent_at]
|
16
|
+
@invitation_info[:invited_by_id] = resource[:invited_by_id]
|
17
|
+
@invitation_info[:invited_by_type] = resource[:invited_by_type]
|
18
|
+
resource.destroy
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def keep_invitation_info
|
24
|
+
resource_invitable = resource_class.devise_modules.include?(:invitable)
|
25
|
+
destroy_if_previously_invited if resource_invitable
|
26
|
+
yield
|
27
|
+
reset_invitation_info if resource_invitable
|
28
|
+
end
|
29
|
+
|
30
|
+
def reset_invitation_info
|
31
|
+
puts @invitation_info.inspect
|
32
|
+
# Restore info about the last invitation (for later reference)
|
33
|
+
# Reset the invitation_info only, if invited_by_id is still nil at this stage:
|
34
|
+
resource = resource_class.where(:email => params[resource_name][:email], :invited_by_id => nil).first
|
35
|
+
puts resource.inspect
|
36
|
+
if resource
|
37
|
+
resource[:invitation_sent_at] = @invitation_info[:invitation_sent_at]
|
38
|
+
resource[:invited_by_id] = @invitation_info[:invited_by_id]
|
39
|
+
resource[:invited_by_type] = @invitation_info[:invited_by_type]
|
40
|
+
resource.save!
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'active_support/deprecation'
|
2
|
+
|
1
3
|
module Devise
|
2
4
|
module Models
|
3
5
|
# Invitable is responsible for sending invitation emails.
|
@@ -13,7 +15,7 @@ module Devise
|
|
13
15
|
#
|
14
16
|
# Examples:
|
15
17
|
#
|
16
|
-
# User.find(1).
|
18
|
+
# User.find(1).invited_to_sign_up? # => true/false
|
17
19
|
# User.invite!(:email => 'someone@example.com') # => send invitation
|
18
20
|
# User.accept_invitation!(:invitation_token => '...') # => accept invitation with a token
|
19
21
|
# User.find(1).accept_invitation! # => accept invitation
|
@@ -22,6 +24,7 @@ module Devise
|
|
22
24
|
extend ActiveSupport::Concern
|
23
25
|
|
24
26
|
attr_accessor :skip_invitation
|
27
|
+
attr_accessor :completing_invite
|
25
28
|
|
26
29
|
included do
|
27
30
|
include ::DeviseInvitable::Inviter
|
@@ -36,23 +39,35 @@ module Devise
|
|
36
39
|
# Accept an invitation by clearing invitation token and and setting invitation_accepted_at
|
37
40
|
# Confirms it if model is confirmable
|
38
41
|
def accept_invitation!
|
39
|
-
|
42
|
+
self.completing_invite = true
|
43
|
+
if self.invited_to_sign_up? && self.valid?
|
40
44
|
run_callbacks :invitation_accepted do
|
41
45
|
self.invitation_token = nil
|
42
46
|
self.invitation_accepted_at = Time.now.utc if respond_to? :"invitation_accepted_at="
|
47
|
+
self.completing_invite = false
|
43
48
|
self.save(:validate => false)
|
44
49
|
end
|
45
50
|
end
|
46
51
|
end
|
47
52
|
|
53
|
+
# Verifies whether a user has accepted an invite, was never invited, or is in the process of accepting an invitation, or not
|
54
|
+
def accepting_or_not_invited?
|
55
|
+
!!completing_invite || !invited_to_sign_up?
|
56
|
+
end
|
57
|
+
|
48
58
|
# Verifies whether a user has been invited or not
|
49
|
-
def
|
59
|
+
def invited_to_sign_up?
|
50
60
|
persisted? && invitation_token.present?
|
51
61
|
end
|
62
|
+
|
63
|
+
def invited?
|
64
|
+
invited_to_sign_up?
|
65
|
+
end
|
66
|
+
deprecate :invited?
|
52
67
|
|
53
68
|
# Reset invitation token and send invitation again
|
54
69
|
def invite!
|
55
|
-
was_invited =
|
70
|
+
was_invited = invited_to_sign_up?
|
56
71
|
self.skip_confirmation! if self.new_record? && self.respond_to?(:skip_confirmation!)
|
57
72
|
generate_invitation_token if self.invitation_token.nil?
|
58
73
|
self.invitation_sent_at = Time.now.utc
|
@@ -71,18 +86,25 @@ module Devise
|
|
71
86
|
# invited, we need to calculate if the invitation time has not expired
|
72
87
|
# for this user, in other words, if the invitation is still valid.
|
73
88
|
def valid_invitation?
|
74
|
-
|
89
|
+
invited_to_sign_up? && invitation_period_valid?
|
75
90
|
end
|
76
91
|
|
77
92
|
# Only verify password when is not invited
|
78
93
|
def valid_password?(password)
|
79
|
-
super unless
|
94
|
+
super unless invited_to_sign_up?
|
80
95
|
end
|
81
96
|
|
82
97
|
def reset_password!(new_password, new_password_confirmation)
|
83
98
|
super
|
84
99
|
accept_invitation!
|
85
100
|
end
|
101
|
+
|
102
|
+
def invite_key_valid?
|
103
|
+
return true unless self.class.invite_key.is_a? Hash # FIXME: remove this line when deprecation is removed
|
104
|
+
self.class.invite_key.all? do |key, regexp|
|
105
|
+
regexp.nil? || self.send(key).try(:match, regexp)
|
106
|
+
end
|
107
|
+
end
|
86
108
|
|
87
109
|
protected
|
88
110
|
# Overriding the method in Devise's :validatable module so password is not required on inviting
|
@@ -125,6 +147,16 @@ module Devise
|
|
125
147
|
end
|
126
148
|
|
127
149
|
module ClassMethods
|
150
|
+
# Return fields to invite
|
151
|
+
def invite_key_fields
|
152
|
+
if invite_key.is_a? Hash
|
153
|
+
invite_key.keys
|
154
|
+
else
|
155
|
+
ActiveSupport::Deprecation.warn("invite_key should be a hash like {#{invite_key.inspect} => /.../}")
|
156
|
+
Array(invite_key)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
128
160
|
# Attempt to find a user by it's email. If a record is not found, create a new
|
129
161
|
# user and send invitation to it. If user is found, returns the user with an
|
130
162
|
# email already exists error.
|
@@ -132,16 +164,24 @@ module Devise
|
|
132
164
|
# resend_invitation is set to false
|
133
165
|
# Attributes must contain the user email, other attributes will be set in the record
|
134
166
|
def _invite(attributes={}, invited_by=nil, &block)
|
135
|
-
|
167
|
+
invite_key_array = invite_key_fields
|
168
|
+
attributes_hash = {}
|
169
|
+
invite_key_array.each do |k,v|
|
170
|
+
attributes_hash[k] = attributes.delete(k)
|
171
|
+
end
|
172
|
+
|
173
|
+
invitable = find_or_initialize_with_errors(invite_key_array, attributes_hash)
|
136
174
|
invitable.assign_attributes(attributes, :as => inviter_role(invited_by))
|
137
175
|
invitable.invited_by = invited_by
|
138
176
|
|
139
177
|
invitable.skip_password = true
|
140
178
|
invitable.valid? if self.validate_on_invite
|
141
179
|
if invitable.new_record?
|
142
|
-
invitable.errors.clear if !self.validate_on_invite and invitable.
|
143
|
-
|
144
|
-
|
180
|
+
invitable.errors.clear if !self.validate_on_invite and invitable.invite_key_valid?
|
181
|
+
elsif !invitable.invited_to_sign_up? || !self.resend_invitation
|
182
|
+
invite_key_array.each do |key|
|
183
|
+
invitable.errors.add(key, :taken)
|
184
|
+
end
|
145
185
|
end
|
146
186
|
|
147
187
|
if invitable.errors.empty?
|
@@ -0,0 +1,249 @@
|
|
1
|
+
require 'active_support/deprecation'
|
2
|
+
|
3
|
+
module Devise
|
4
|
+
module Models
|
5
|
+
# Invitable is responsible for sending invitation emails.
|
6
|
+
# When an invitation is sent to an email address, an account is created for it.
|
7
|
+
# Invitation email contains a link allowing the user to accept the invitation
|
8
|
+
# by setting a password (as reset password from Devise's recoverable module).
|
9
|
+
#
|
10
|
+
# Configuration:
|
11
|
+
#
|
12
|
+
# invite_for: The period the generated invitation token is valid, after
|
13
|
+
# this period, the invited resource won't be able to accept the invitation.
|
14
|
+
# When invite_for is 0 (the default), the invitation won't expire.
|
15
|
+
#
|
16
|
+
# Examples:
|
17
|
+
#
|
18
|
+
# User.find(1).invited_to_sign_up? # => true/false
|
19
|
+
# User.invite!(:email => 'someone@example.com') # => send invitation
|
20
|
+
# User.accept_invitation!(:invitation_token => '...') # => accept invitation with a token
|
21
|
+
# User.find(1).accept_invitation! # => accept invitation
|
22
|
+
# User.find(1).invite! # => reset invitation status and send invitation again
|
23
|
+
module Invitable
|
24
|
+
extend ActiveSupport::Concern
|
25
|
+
|
26
|
+
attr_accessor :skip_invitation
|
27
|
+
attr_accessor :completing_invite
|
28
|
+
|
29
|
+
included do
|
30
|
+
include ::DeviseInvitable::Inviter
|
31
|
+
belongs_to :invited_by, :polymorphic => true
|
32
|
+
|
33
|
+
include ActiveSupport::Callbacks
|
34
|
+
define_callbacks :invitation_accepted
|
35
|
+
|
36
|
+
attr_writer :skip_password
|
37
|
+
end
|
38
|
+
|
39
|
+
# Accept an invitation by clearing invitation token and and setting invitation_accepted_at
|
40
|
+
# Confirms it if model is confirmable
|
41
|
+
def accept_invitation!
|
42
|
+
self.completing_invite = true
|
43
|
+
if self.invited_to_sign_up? && self.valid?
|
44
|
+
run_callbacks :invitation_accepted do
|
45
|
+
self.invitation_token = nil
|
46
|
+
self.invitation_accepted_at = Time.now.utc if respond_to? :"invitation_accepted_at="
|
47
|
+
self.completing_invite = false
|
48
|
+
self.save(:validate => false)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Verifies whether a user has accepted an invite, was never invited, or is in the process of accepting an invitation, or not
|
54
|
+
def accepting_or_not_invited?
|
55
|
+
!!completing_invite || !invited_to_sign_up?
|
56
|
+
end
|
57
|
+
|
58
|
+
# Verifies whether a user has been invited or not
|
59
|
+
def invited_to_sign_up?
|
60
|
+
persisted? && invitation_token.present?
|
61
|
+
end
|
62
|
+
|
63
|
+
def invited?
|
64
|
+
invited_to_sign_up?
|
65
|
+
end
|
66
|
+
deprecate :invited?
|
67
|
+
|
68
|
+
# Reset invitation token and send invitation again
|
69
|
+
def invite!
|
70
|
+
was_invited = invited_to_sign_up?
|
71
|
+
self.skip_confirmation! if self.new_record? && self.respond_to?(:skip_confirmation!)
|
72
|
+
generate_invitation_token if self.invitation_token.nil?
|
73
|
+
self.invitation_sent_at = Time.now.utc
|
74
|
+
|
75
|
+
# Call these before_validate methods since we aren't validating on save
|
76
|
+
self.downcase_keys if self.new_record? && self.respond_to?(:downcase_keys)
|
77
|
+
self.strip_whitespace if self.new_record? && self.respond_to?(:strip_whitespace)
|
78
|
+
|
79
|
+
if save(:validate => false)
|
80
|
+
self.invited_by.decrement_invitation_limit! if !was_invited and self.invited_by.present?
|
81
|
+
deliver_invitation unless @skip_invitation
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Verify whether a invitation is active or not. If the user has been
|
86
|
+
# invited, we need to calculate if the invitation time has not expired
|
87
|
+
# for this user, in other words, if the invitation is still valid.
|
88
|
+
def valid_invitation?
|
89
|
+
invited_to_sign_up? && invitation_period_valid?
|
90
|
+
end
|
91
|
+
|
92
|
+
# Only verify password when is not invited
|
93
|
+
def valid_password?(password)
|
94
|
+
super unless invited_to_sign_up?
|
95
|
+
end
|
96
|
+
|
97
|
+
def reset_password!(new_password, new_password_confirmation)
|
98
|
+
super
|
99
|
+
accept_invitation!
|
100
|
+
end
|
101
|
+
|
102
|
+
def invite_key_valid?
|
103
|
+
return true unless self.class.invite_key.is_a? Hash # FIXME: remove this line when deprecation is removed
|
104
|
+
self.class.invite_key.all? do |key, regexp|
|
105
|
+
regexp.nil? || self.send(key).try(:match, regexp)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
protected
|
110
|
+
# Overriding the method in Devise's :validatable module so password is not required on inviting
|
111
|
+
def password_required?
|
112
|
+
!@skip_password && super
|
113
|
+
end
|
114
|
+
|
115
|
+
# Deliver the invitation email
|
116
|
+
def deliver_invitation
|
117
|
+
::Devise.mailer.invitation_instructions(self).deliver
|
118
|
+
end
|
119
|
+
|
120
|
+
# Checks if the invitation for the user is within the limit time.
|
121
|
+
# We do this by calculating if the difference between today and the
|
122
|
+
# invitation sent date does not exceed the invite for time configured.
|
123
|
+
# Invite_for is a model configuration, must always be an integer value.
|
124
|
+
#
|
125
|
+
# Example:
|
126
|
+
#
|
127
|
+
# # invite_for = 1.day and invitation_sent_at = today
|
128
|
+
# invitation_period_valid? # returns true
|
129
|
+
#
|
130
|
+
# # invite_for = 5.days and invitation_sent_at = 4.days.ago
|
131
|
+
# invitation_period_valid? # returns true
|
132
|
+
#
|
133
|
+
# # invite_for = 5.days and invitation_sent_at = 5.days.ago
|
134
|
+
# invitation_period_valid? # returns false
|
135
|
+
#
|
136
|
+
# # invite_for = nil
|
137
|
+
# invitation_period_valid? # will always return true
|
138
|
+
#
|
139
|
+
def invitation_period_valid?
|
140
|
+
invitation_sent_at && (self.class.invite_for.to_i.zero? || invitation_sent_at.utc >= self.class.invite_for.ago)
|
141
|
+
end
|
142
|
+
|
143
|
+
# Generates a new random token for invitation, and stores the time
|
144
|
+
# this token is being generated
|
145
|
+
def generate_invitation_token
|
146
|
+
self.invitation_token = self.class.invitation_token
|
147
|
+
end
|
148
|
+
|
149
|
+
module ClassMethods
|
150
|
+
# Return fields to invite
|
151
|
+
def invite_key_fields
|
152
|
+
if invite_key.is_a? Hash
|
153
|
+
invite_key.keys
|
154
|
+
else
|
155
|
+
ActiveSupport::Deprecation.warn("invite_key should be a hash like {#{invite_key.inspect} => /.../}")
|
156
|
+
Array(invite_key)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Attempt to find a user by it's email. If a record is not found, create a new
|
161
|
+
# user and send invitation to it. If user is found, returns the user with an
|
162
|
+
# email already exists error.
|
163
|
+
# If user is found and still have pending invitation, email is resend unless
|
164
|
+
# resend_invitation is set to false
|
165
|
+
# Attributes must contain the user email, other attributes will be set in the record
|
166
|
+
def _invite(attributes={}, invited_by=nil, &block)
|
167
|
+
invite_key_array = invite_key_fields
|
168
|
+
attributes_hash = {}
|
169
|
+
invite_key_array.each do |k,v|
|
170
|
+
attributes_hash[k] = attributes.delete(k)
|
171
|
+
end
|
172
|
+
|
173
|
+
invitable = find_or_initialize_with_errors(invite_key_array, attributes_hash)
|
174
|
+
invitable.assign_attributes(attributes, :as => inviter_role(invited_by))
|
175
|
+
invitable.invited_by = invited_by
|
176
|
+
|
177
|
+
invitable.skip_password = true
|
178
|
+
invitable.valid? if self.validate_on_invite
|
179
|
+
if invitable.new_record?
|
180
|
+
invitable.errors.clear if !self.validate_on_invite and invitable.invite_key_valid?
|
181
|
+
elsif !invitable.invited_to_sign_up? || !self.resend_invitation
|
182
|
+
invite_key_fields.each do |key|
|
183
|
+
invitable.errors.add(key, :taken)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
if invitable.errors.empty?
|
188
|
+
yield invitable if block_given?
|
189
|
+
mail = invitable.invite!
|
190
|
+
end
|
191
|
+
[invitable, mail]
|
192
|
+
end
|
193
|
+
|
194
|
+
# Override this method if the invitable is using Mass Assignment Security
|
195
|
+
# and the inviter has a non-default role.
|
196
|
+
def inviter_role(inviter)
|
197
|
+
:default
|
198
|
+
end
|
199
|
+
|
200
|
+
def invite!(attributes={}, invited_by=nil, &block)
|
201
|
+
invitable, mail = _invite(attributes, invited_by, &block)
|
202
|
+
invitable
|
203
|
+
end
|
204
|
+
|
205
|
+
def invite_mail!(attributes={}, invited_by=nil, &block)
|
206
|
+
invitable, mail = _invite(attributes, invited_by, &block)
|
207
|
+
mail
|
208
|
+
end
|
209
|
+
|
210
|
+
# Attempt to find a user by it's invitation_token to set it's password.
|
211
|
+
# If a user is found, reset it's password and automatically try saving
|
212
|
+
# the record. If not user is found, returns a new user containing an
|
213
|
+
# error in invitation_token attribute.
|
214
|
+
# Attributes must contain invitation_token, password and confirmation
|
215
|
+
def accept_invitation!(attributes={})
|
216
|
+
invitable = find_or_initialize_with_error_by(:invitation_token, attributes.delete(:invitation_token))
|
217
|
+
invitable.errors.add(:invitation_token, :invalid) if invitable.invitation_token && invitable.persisted? && !invitable.valid_invitation?
|
218
|
+
if invitable.errors.empty?
|
219
|
+
invitable.attributes = attributes
|
220
|
+
invitable.accept_invitation!
|
221
|
+
end
|
222
|
+
invitable
|
223
|
+
end
|
224
|
+
|
225
|
+
# Generate a token checking if one does not already exist in the database.
|
226
|
+
def invitation_token
|
227
|
+
generate_token(:invitation_token)
|
228
|
+
end
|
229
|
+
|
230
|
+
# Callback convenience methods
|
231
|
+
def before_invitation_accepted(*args, &blk)
|
232
|
+
set_callback(:invitation_accepted, :before, *args, &blk)
|
233
|
+
end
|
234
|
+
|
235
|
+
def after_invitation_accepted(*args, &blk)
|
236
|
+
set_callback(:invitation_accepted, :after, *args, &blk)
|
237
|
+
end
|
238
|
+
|
239
|
+
|
240
|
+
Devise::Models.config(self, :invite_for)
|
241
|
+
Devise::Models.config(self, :validate_on_invite)
|
242
|
+
Devise::Models.config(self, :invitation_limit)
|
243
|
+
Devise::Models.config(self, :invite_key)
|
244
|
+
Devise::Models.config(self, :resend_invitation)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
@@ -32,7 +32,9 @@ module DeviseInvitable
|
|
32
32
|
# config.invitation_limit = 5
|
33
33
|
|
34
34
|
# The key to be used to check existing users when sending an invitation
|
35
|
-
#
|
35
|
+
# and the regexp used to test it when validate_on_invite is not set.
|
36
|
+
# config.invite_key = {:email => /\A[^@]+@[^@]+\z/}
|
37
|
+
# config.invite_key = {:email => /\A[^@]+@[^@]+\z/, :username => nil}
|
36
38
|
|
37
39
|
# Flag that force a record to be valid before being actually invited
|
38
40
|
# Default: false
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module DeviseInvitable
|
2
|
+
module Generators
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
4
|
+
source_root File.expand_path("../../templates", __FILE__)
|
5
|
+
desc "Add DeviseInvitable config variables to the Devise initializer and copy DeviseInvitable locale files to your application."
|
6
|
+
|
7
|
+
# def devise_install
|
8
|
+
# invoke "devise:install"
|
9
|
+
# end
|
10
|
+
|
11
|
+
def add_config_options_to_initializer
|
12
|
+
devise_initializer_path = "config/initializers/devise.rb"
|
13
|
+
if File.exist?(devise_initializer_path)
|
14
|
+
old_content = File.read(devise_initializer_path)
|
15
|
+
|
16
|
+
if old_content.match(Regexp.new(/^\s# ==> Configuration for :invitable\n/))
|
17
|
+
false
|
18
|
+
else
|
19
|
+
inject_into_file(devise_initializer_path, :before => " # ==> Configuration for :confirmable\n") do
|
20
|
+
<<-CONTENT
|
21
|
+
# ==> Configuration for :invitable
|
22
|
+
# The period the generated invitation token is valid, after
|
23
|
+
# this period, the invited resource won't be able to accept the invitation.
|
24
|
+
# When invite_for is 0 (the default), the invitation won't expire.
|
25
|
+
# config.invite_for = 2.weeks
|
26
|
+
|
27
|
+
# Number of invitations users can send.
|
28
|
+
# If invitation_limit is nil, users can send unlimited invitations.
|
29
|
+
# If invitation_limit is 0, users can't send invitations.
|
30
|
+
# If invitation_limit n > 0, users can send n invitations.
|
31
|
+
# Default: nil
|
32
|
+
# config.invitation_limit = 5
|
33
|
+
|
34
|
+
# The key to be used to check existing users when sending an invitation.
|
35
|
+
# config.invite_key = {:email => /\A[^@]+@[^@]+\z/}
|
36
|
+
# config.invite_key = {:email => /\A[^@]+@[^@]+\z/, :username => nil}
|
37
|
+
|
38
|
+
# Flag that force a record to be valid before being actually invited
|
39
|
+
# Default: false
|
40
|
+
# config.validate_on_invite = true
|
41
|
+
|
42
|
+
CONTENT
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def copy_locale
|
49
|
+
copy_file "../../../config/locales/en.yml", "config/locales/devise_invitable.en.yml"
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
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: 19
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 1.0.
|
9
|
+
- 2
|
10
|
+
version: 1.0.2
|
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: 2012-
|
18
|
+
date: 2012-05-26 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: bundler
|
@@ -74,27 +74,33 @@ extensions: []
|
|
74
74
|
extra_rdoc_files: []
|
75
75
|
|
76
76
|
files:
|
77
|
-
- app/
|
77
|
+
- app/views/devise/mailer/invitation_instructions.html.erb
|
78
|
+
- app/views/devise/invitations/new.html.erb~
|
78
79
|
- app/views/devise/invitations/edit.html.erb
|
79
80
|
- app/views/devise/invitations/new.html.erb
|
80
|
-
- app/
|
81
|
+
- app/controllers/devise/invitations_controller.rb
|
82
|
+
- app/controllers/devise/invitations_controller.rb~
|
81
83
|
- config/locales/en.yml
|
82
|
-
- lib/devise_invitable
|
83
|
-
- lib/devise_invitable
|
84
|
-
- lib/devise_invitable/
|
85
|
-
- lib/devise_invitable/inviter.rb
|
86
|
-
- lib/devise_invitable/mailer.rb
|
84
|
+
- lib/devise_invitable.rb
|
85
|
+
- lib/devise_invitable.rb~
|
86
|
+
- lib/devise_invitable/routes.rb
|
87
87
|
- lib/devise_invitable/model.rb
|
88
88
|
- lib/devise_invitable/rails.rb
|
89
|
-
- lib/devise_invitable/
|
89
|
+
- lib/devise_invitable/mailer.rb
|
90
90
|
- lib/devise_invitable/version.rb
|
91
|
-
- lib/devise_invitable.rb
|
92
|
-
- lib/
|
91
|
+
- lib/devise_invitable/model.rb~
|
92
|
+
- lib/devise_invitable/inviter.rb
|
93
|
+
- lib/devise_invitable/controllers/url_helpers.rb
|
94
|
+
- lib/devise_invitable/controllers/helpers.rb
|
95
|
+
- lib/devise_invitable/controllers/registrations.rb~
|
96
|
+
- lib/devise_invitable/controllers/registrations.rb
|
93
97
|
- lib/generators/active_record/templates/migration.rb
|
94
|
-
- lib/generators/
|
98
|
+
- lib/generators/active_record/devise_invitable_generator.rb
|
99
|
+
- lib/generators/mongoid/devise_invitable_generator.rb
|
95
100
|
- lib/generators/devise_invitable/install_generator.rb
|
96
101
|
- lib/generators/devise_invitable/views_generator.rb
|
97
|
-
- lib/generators/
|
102
|
+
- lib/generators/devise_invitable/devise_invitable_generator.rb
|
103
|
+
- lib/generators/devise_invitable/install_generator.rb~
|
98
104
|
- LICENSE
|
99
105
|
- README.rdoc
|
100
106
|
homepage: https://github.com/scambra/devise_invitable
|
@@ -132,7 +138,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
132
138
|
requirements: []
|
133
139
|
|
134
140
|
rubyforge_project:
|
135
|
-
rubygems_version: 1.8.
|
141
|
+
rubygems_version: 1.8.10
|
136
142
|
signing_key:
|
137
143
|
specification_version: 3
|
138
144
|
summary: An invitation strategy for Devise
|