devise_invitable 1.0.0 → 1.0.2
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 +11 -10
- 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/controllers/registrations.rb +40 -0
- data/lib/devise_invitable/controllers/registrations.rb~ +43 -0
- data/lib/devise_invitable/controllers/url_helpers.rb +1 -1
- data/lib/devise_invitable/model.rb +55 -10
- data/lib/devise_invitable/model.rb~ +57 -18
- data/lib/devise_invitable/rails.rb +3 -3
- data/lib/devise_invitable/version.rb +1 -1
- data/lib/devise_invitable.rb +8 -6
- data/lib/devise_invitable.rb~ +68 -0
- data/lib/generators/devise_invitable/install_generator.rb +3 -1
- data/lib/generators/devise_invitable/install_generator.rb~ +54 -0
- metadata +24 -18
data/README.rdoc
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
= DeviseInvitable
|
|
2
|
+
{<img src="http://travis-ci.org/scambra/devise_invitable.png"/>}[http://travis-ci.org/scambra/devise_invitable]
|
|
2
3
|
|
|
3
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.
|
|
4
5
|
|
|
5
6
|
DeviseInvitable currently only support Rails 3, if you want to use it with Rails 2.3 you must install version {0.2.3}[http://rubygems.org/gems/devise_invitable/versions/0.2.3]
|
|
6
7
|
|
|
7
|
-
== Installation
|
|
8
|
+
== Installation
|
|
8
9
|
|
|
9
10
|
Install DeviseInvitable gem, it will also install dependencies (such as devise and warden):
|
|
10
11
|
|
|
@@ -12,8 +13,8 @@ Install DeviseInvitable gem, it will also install dependencies (such as devise a
|
|
|
12
13
|
|
|
13
14
|
Add DeviseInvitable to your Gemfile (and Devise if you weren't using them):
|
|
14
15
|
|
|
15
|
-
gem 'devise', '
|
|
16
|
-
gem 'devise_invitable', '~> 0.
|
|
16
|
+
gem 'devise', '>= 2.0.0'
|
|
17
|
+
gem 'devise_invitable', '~> 1.0.0'
|
|
17
18
|
|
|
18
19
|
=== Automatic installation
|
|
19
20
|
|
|
@@ -50,7 +51,7 @@ Add t.invitable to your Devise model migration:
|
|
|
50
51
|
t.string :invited_by_type
|
|
51
52
|
...
|
|
52
53
|
end
|
|
53
|
-
add_index :users, :invitation_token
|
|
54
|
+
add_index :users, :invitation_token, :unique => true
|
|
54
55
|
|
|
55
56
|
or for a model that already exists, define a migration to add DeviseInvitable to your model:
|
|
56
57
|
|
|
@@ -65,9 +66,9 @@ or for a model that already exists, define a migration to add DeviseInvitable to
|
|
|
65
66
|
end
|
|
66
67
|
|
|
67
68
|
# Allow null encrypted_password
|
|
68
|
-
|
|
69
|
+
change_column :users, :encrypted_password, :string, :null => true
|
|
69
70
|
# Allow null password_salt (add it if you are using Devise's encryptable module)
|
|
70
|
-
|
|
71
|
+
change_column :users, :password_salt, :string, :null => true
|
|
71
72
|
|
|
72
73
|
== Model configuration
|
|
73
74
|
|
|
@@ -89,7 +90,7 @@ or directly as parameters to the <tt>devise</tt> method:
|
|
|
89
90
|
|
|
90
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.
|
|
91
92
|
|
|
92
|
-
* 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}.
|
|
93
94
|
|
|
94
95
|
* validate_on_invite: force a record to be valid before being actually invited.
|
|
95
96
|
|
|
@@ -165,7 +166,7 @@ A callback event is fired before and after an invitation is accepted (User#accep
|
|
|
165
166
|
after_invitation_accepted :email_invited_by
|
|
166
167
|
|
|
167
168
|
def email_invited_by
|
|
168
|
-
# ...
|
|
169
|
+
# ...
|
|
169
170
|
end
|
|
170
171
|
|
|
171
172
|
The callbacks support all options and arguments available to the standard callbacks provided by AR.
|
|
@@ -262,7 +263,7 @@ http://github.com/scambra/devise_invitable/contributors
|
|
|
262
263
|
Special thanks to rymai[http://github.com/rymai] for the Rails 3 support, his fork was a great help.
|
|
263
264
|
|
|
264
265
|
== Note on Patches/Pull Requests
|
|
265
|
-
|
|
266
|
+
|
|
266
267
|
* Fork the project.
|
|
267
268
|
* Make your feature addition or bug fix.
|
|
268
269
|
* Add tests for it. This is important so I don't break it in a future version unintentionally.
|
|
@@ -271,4 +272,4 @@ Special thanks to rymai[http://github.com/rymai] for the Rails 3 support, his fo
|
|
|
271
272
|
|
|
272
273
|
== Copyright
|
|
273
274
|
|
|
274
|
-
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 />
|
|
@@ -0,0 +1,40 @@
|
|
|
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
|
+
hash = params[resource_name]
|
|
10
|
+
if hash && hash[:email]
|
|
11
|
+
resource = resource_class.where(:email => hash[:email], :encrypted_password => '').first
|
|
12
|
+
if resource
|
|
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]
|
|
17
|
+
resource.destroy
|
|
18
|
+
end
|
|
19
|
+
end
|
|
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
|
|
25
|
+
yield
|
|
26
|
+
reset_invitation_info if resource_invitable
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def reset_invitation_info
|
|
30
|
+
# Restore info about the last invitation (for later reference)
|
|
31
|
+
# Reset the invitation_info only, if invited_by_id is still nil at this stage:
|
|
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]
|
|
37
|
+
resource.save!
|
|
38
|
+
end
|
|
39
|
+
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,26 +39,43 @@ 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
|
|
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
|
+
|
|
59
79
|
if save(:validate => false)
|
|
60
80
|
self.invited_by.decrement_invitation_limit! if !was_invited and self.invited_by.present?
|
|
61
81
|
deliver_invitation unless @skip_invitation
|
|
@@ -66,18 +86,25 @@ module Devise
|
|
|
66
86
|
# invited, we need to calculate if the invitation time has not expired
|
|
67
87
|
# for this user, in other words, if the invitation is still valid.
|
|
68
88
|
def valid_invitation?
|
|
69
|
-
|
|
89
|
+
invited_to_sign_up? && invitation_period_valid?
|
|
70
90
|
end
|
|
71
91
|
|
|
72
92
|
# Only verify password when is not invited
|
|
73
93
|
def valid_password?(password)
|
|
74
|
-
super unless
|
|
94
|
+
super unless invited_to_sign_up?
|
|
75
95
|
end
|
|
76
96
|
|
|
77
97
|
def reset_password!(new_password, new_password_confirmation)
|
|
78
98
|
super
|
|
79
99
|
accept_invitation!
|
|
80
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
|
|
81
108
|
|
|
82
109
|
protected
|
|
83
110
|
# Overriding the method in Devise's :validatable module so password is not required on inviting
|
|
@@ -120,6 +147,16 @@ module Devise
|
|
|
120
147
|
end
|
|
121
148
|
|
|
122
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
|
+
|
|
123
160
|
# Attempt to find a user by it's email. If a record is not found, create a new
|
|
124
161
|
# user and send invitation to it. If user is found, returns the user with an
|
|
125
162
|
# email already exists error.
|
|
@@ -127,16 +164,24 @@ module Devise
|
|
|
127
164
|
# resend_invitation is set to false
|
|
128
165
|
# Attributes must contain the user email, other attributes will be set in the record
|
|
129
166
|
def _invite(attributes={}, invited_by=nil, &block)
|
|
130
|
-
|
|
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)
|
|
131
174
|
invitable.assign_attributes(attributes, :as => inviter_role(invited_by))
|
|
132
175
|
invitable.invited_by = invited_by
|
|
133
176
|
|
|
134
177
|
invitable.skip_password = true
|
|
135
178
|
invitable.valid? if self.validate_on_invite
|
|
136
179
|
if invitable.new_record?
|
|
137
|
-
invitable.errors.clear if !self.validate_on_invite and invitable.
|
|
138
|
-
|
|
139
|
-
|
|
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
|
|
140
185
|
end
|
|
141
186
|
|
|
142
187
|
if invitable.errors.empty?
|
|
@@ -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,26 +39,43 @@ 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
|
|
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
|
+
|
|
59
79
|
if save(:validate => false)
|
|
60
80
|
self.invited_by.decrement_invitation_limit! if !was_invited and self.invited_by.present?
|
|
61
81
|
deliver_invitation unless @skip_invitation
|
|
@@ -66,19 +86,26 @@ module Devise
|
|
|
66
86
|
# invited, we need to calculate if the invitation time has not expired
|
|
67
87
|
# for this user, in other words, if the invitation is still valid.
|
|
68
88
|
def valid_invitation?
|
|
69
|
-
|
|
89
|
+
invited_to_sign_up? && invitation_period_valid?
|
|
70
90
|
end
|
|
71
91
|
|
|
72
92
|
# Only verify password when is not invited
|
|
73
93
|
def valid_password?(password)
|
|
74
|
-
super unless
|
|
94
|
+
super unless invited_to_sign_up?
|
|
75
95
|
end
|
|
76
|
-
|
|
96
|
+
|
|
77
97
|
def reset_password!(new_password, new_password_confirmation)
|
|
78
98
|
super
|
|
79
99
|
accept_invitation!
|
|
80
100
|
end
|
|
81
|
-
|
|
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
|
+
|
|
82
109
|
protected
|
|
83
110
|
# Overriding the method in Devise's :validatable module so password is not required on inviting
|
|
84
111
|
def password_required?
|
|
@@ -90,12 +117,6 @@ module Devise
|
|
|
90
117
|
::Devise.mailer.invitation_instructions(self).deliver
|
|
91
118
|
end
|
|
92
119
|
|
|
93
|
-
# Clear invitation token when reset password token is cleared too
|
|
94
|
-
def clear_reset_password_token
|
|
95
|
-
self.invitation_token = nil if invited?
|
|
96
|
-
super
|
|
97
|
-
end
|
|
98
|
-
|
|
99
120
|
# Checks if the invitation for the user is within the limit time.
|
|
100
121
|
# We do this by calculating if the difference between today and the
|
|
101
122
|
# invitation sent date does not exceed the invite for time configured.
|
|
@@ -126,6 +147,16 @@ module Devise
|
|
|
126
147
|
end
|
|
127
148
|
|
|
128
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
|
+
|
|
129
160
|
# Attempt to find a user by it's email. If a record is not found, create a new
|
|
130
161
|
# user and send invitation to it. If user is found, returns the user with an
|
|
131
162
|
# email already exists error.
|
|
@@ -133,16 +164,24 @@ module Devise
|
|
|
133
164
|
# resend_invitation is set to false
|
|
134
165
|
# Attributes must contain the user email, other attributes will be set in the record
|
|
135
166
|
def _invite(attributes={}, invited_by=nil, &block)
|
|
136
|
-
|
|
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)
|
|
137
174
|
invitable.assign_attributes(attributes, :as => inviter_role(invited_by))
|
|
138
175
|
invitable.invited_by = invited_by
|
|
139
176
|
|
|
140
177
|
invitable.skip_password = true
|
|
141
178
|
invitable.valid? if self.validate_on_invite
|
|
142
179
|
if invitable.new_record?
|
|
143
|
-
invitable.errors.clear if !self.validate_on_invite and invitable.
|
|
144
|
-
|
|
145
|
-
|
|
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
|
|
146
185
|
end
|
|
147
186
|
|
|
148
187
|
if invitable.errors.empty?
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
module DeviseInvitable
|
|
2
2
|
class Engine < ::Rails::Engine
|
|
3
|
-
|
|
3
|
+
|
|
4
4
|
ActiveSupport.on_load(:action_controller) { include DeviseInvitable::Controllers::UrlHelpers }
|
|
5
5
|
ActiveSupport.on_load(:action_view) { include DeviseInvitable::Controllers::UrlHelpers }
|
|
6
|
-
|
|
6
|
+
|
|
7
7
|
# We use to_prepare instead of after_initialize here because Devise is a Rails engine; its
|
|
8
8
|
# mailer is reloaded like the rest of the user's app. Got to make sure that our mailer methods
|
|
9
9
|
# are included each time Devise::Mailer is (re)loaded.
|
|
10
10
|
config.to_prepare do
|
|
11
11
|
require 'devise/mailer'
|
|
12
12
|
Devise::Mailer.send :include, DeviseInvitable::Mailer
|
|
13
|
+
Devise::RegistrationsController.send :include, DeviseInvitable::Controllers::Registrations
|
|
13
14
|
end
|
|
14
|
-
|
|
15
15
|
end
|
|
16
16
|
end
|
data/lib/devise_invitable.rb
CHANGED
|
@@ -7,11 +7,12 @@ end
|
|
|
7
7
|
require 'devise_invitable/mailer'
|
|
8
8
|
require 'devise_invitable/routes'
|
|
9
9
|
require 'devise_invitable/controllers/url_helpers'
|
|
10
|
+
require 'devise_invitable/controllers/registrations'
|
|
10
11
|
require 'devise_invitable/controllers/helpers'
|
|
11
12
|
require 'devise_invitable/rails'
|
|
12
13
|
|
|
13
14
|
module Devise
|
|
14
|
-
# Public: Validity period of the invitation token (default: 0). If
|
|
15
|
+
# Public: Validity period of the invitation token (default: 0). If
|
|
15
16
|
# invite_for is 0 or nil, the invitation will never expire.
|
|
16
17
|
# Set invite_for in the Devise configuration file (in config/initializers/devise.rb).
|
|
17
18
|
#
|
|
@@ -19,7 +20,7 @@ module Devise
|
|
|
19
20
|
mattr_accessor :invite_for
|
|
20
21
|
@@invite_for = 0
|
|
21
22
|
|
|
22
|
-
# Public: Flag that force a record to be valid before being actually invited
|
|
23
|
+
# Public: Flag that force a record to be valid before being actually invited
|
|
23
24
|
# (default: false).
|
|
24
25
|
#
|
|
25
26
|
# Examples (in config/initializers/devise.rb)
|
|
@@ -35,14 +36,15 @@ module Devise
|
|
|
35
36
|
# config.invitation_limit = nil
|
|
36
37
|
mattr_accessor :invitation_limit
|
|
37
38
|
@@invitation_limit = nil
|
|
38
|
-
|
|
39
|
-
# Public: The key to be used to check existing users when sending an invitation
|
|
39
|
+
|
|
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.
|
|
40
42
|
#
|
|
41
43
|
# Examples (in config/initializers/devise.rb)
|
|
42
44
|
#
|
|
43
|
-
# config.invite_key = :email
|
|
45
|
+
# config.invite_key = {:email => /\A[^@]+@[^@]+\z/}
|
|
44
46
|
mattr_accessor :invite_key
|
|
45
|
-
@@invite_key = :email
|
|
47
|
+
@@invite_key = {:email => Devise.email_regexp}
|
|
46
48
|
|
|
47
49
|
# Public: Resend invitation if user with invited status is invited again
|
|
48
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
|
|
@@ -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
|
|
@@ -25,12 +25,12 @@ dependencies:
|
|
|
25
25
|
requirements:
|
|
26
26
|
- - ~>
|
|
27
27
|
- !ruby/object:Gem::Version
|
|
28
|
-
hash:
|
|
28
|
+
hash: 19
|
|
29
29
|
segments:
|
|
30
30
|
- 1
|
|
31
|
+
- 1
|
|
31
32
|
- 0
|
|
32
|
-
|
|
33
|
-
version: 1.0.7
|
|
33
|
+
version: 1.1.0
|
|
34
34
|
type: :development
|
|
35
35
|
version_requirements: *id001
|
|
36
36
|
- !ruby/object:Gem::Dependency
|
|
@@ -54,7 +54,7 @@ dependencies:
|
|
|
54
54
|
requirement: &id003 !ruby/object:Gem::Requirement
|
|
55
55
|
none: false
|
|
56
56
|
requirements:
|
|
57
|
-
- -
|
|
57
|
+
- - ">="
|
|
58
58
|
- !ruby/object:Gem::Version
|
|
59
59
|
hash: 15
|
|
60
60
|
segments:
|
|
@@ -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
84
|
- lib/devise_invitable.rb
|
|
83
|
-
- lib/devise_invitable
|
|
85
|
+
- lib/devise_invitable.rb~
|
|
86
|
+
- lib/devise_invitable/routes.rb
|
|
84
87
|
- lib/devise_invitable/model.rb
|
|
85
88
|
- lib/devise_invitable/rails.rb
|
|
86
|
-
- lib/devise_invitable/
|
|
87
|
-
- lib/devise_invitable/controllers/helpers.rb
|
|
88
|
-
- lib/devise_invitable/controllers/url_helpers.rb
|
|
89
|
+
- lib/devise_invitable/mailer.rb
|
|
89
90
|
- lib/devise_invitable/version.rb
|
|
90
|
-
- lib/devise_invitable/inviter.rb
|
|
91
91
|
- lib/devise_invitable/model.rb~
|
|
92
|
-
- lib/
|
|
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
|
|
98
|
+
- lib/generators/active_record/devise_invitable_generator.rb
|
|
99
|
+
- lib/generators/mongoid/devise_invitable_generator.rb
|
|
100
|
+
- lib/generators/devise_invitable/install_generator.rb
|
|
94
101
|
- lib/generators/devise_invitable/views_generator.rb
|
|
95
102
|
- lib/generators/devise_invitable/devise_invitable_generator.rb
|
|
96
|
-
- lib/generators/devise_invitable/install_generator.rb
|
|
97
|
-
- lib/generators/mongoid/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
|