devise_invitable 1.0.0 → 1.1.0

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 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 for Rails ~> 3.0 and Devise ~> 1.2
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', '~> 2.0.0'
16
- gem 'devise_invitable', '~> 0.7.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
- change_column_null :users, :encrypted_password, true
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
- change_column_null :users, :password_salt, true
71
+ change_column :users, :password_salt, :string, :null => true
71
72
 
72
73
  == Model configuration
73
74
 
@@ -89,12 +90,14 @@ 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. The key must be an unique field. The default value is looking for users by email.
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
 
96
97
  * resend_invitation: resend invitation if user with invited status is invited again. Enabled by default.
97
98
 
99
+ * invited_by_class_name: The class name of the inviting model. If this is nil, polymorphic association is used.
100
+
98
101
  For more details, see <tt>config/initializers/devise.rb</tt> (after you invoked the "devise_invitable:install" generator described above).
99
102
 
100
103
  == Configuring views
@@ -152,6 +155,15 @@ You can add :skip_invitation to attributes hash if skip_invitation is added to a
152
155
  User.invite!(:email => "new_user@example.com", :name => "John Doe", :skip_invitation => true)
153
156
  # => the record will be created, but the invitation email will not be sent
154
157
 
158
+ You can send an invitation to an existing user if your workflow creates them separately:
159
+
160
+ user = User.find(42)
161
+ user.invite!(current_user) # current user is optional to set the invited_by attribute
162
+
163
+ You can also set <tt>invited_by</tt> when using the <tt>invite!</tt> class method:
164
+
165
+ User.invite!({:email => "new_user@example.com"}, current_user) # current_user will be set as invited_by
166
+
155
167
  === Accept an invitation
156
168
 
157
169
  To accept an invitation with a token use the <tt>accept_invitation!</tt> class method. <tt>:invitation_token</tt> must be present in the parameters hash. You can also include other attributes in the hash.
@@ -165,11 +177,18 @@ A callback event is fired before and after an invitation is accepted (User#accep
165
177
  after_invitation_accepted :email_invited_by
166
178
 
167
179
  def email_invited_by
168
- # ...
180
+ # ...
169
181
  end
170
182
 
171
183
  The callbacks support all options and arguments available to the standard callbacks provided by AR.
172
184
 
185
+ === Scopes
186
+
187
+ A pair of scopes to find those users that have accepted, and those that have not accepted, invitations are defined:
188
+
189
+ User.invitation_accepted # => returns all Users for whom the invitation_accepted_at attribute is not nil
190
+ User.invitation_not_accepted # => returns all Users for whom the invitation_accepted_at attribute is nil
191
+
173
192
  == Integration in a Rails application
174
193
 
175
194
  Since the invitations controller take care of all the creation/acceptation of an invitation, in most cases you wouldn't call the <tt>invite!</tt> and <tt>accept_invitation!</tt> methods directly.
@@ -262,7 +281,7 @@ http://github.com/scambra/devise_invitable/contributors
262
281
  Special thanks to rymai[http://github.com/rymai] for the Rails 3 support, his fork was a great help.
263
282
 
264
283
  == Note on Patches/Pull Requests
265
-
284
+
266
285
  * Fork the project.
267
286
  * Make your feature addition or bug fix.
268
287
  * Add tests for it. This is important so I don't break it in a future version unintentionally.
@@ -271,4 +290,4 @@ Special thanks to rymai[http://github.com/rymai] for the Rails 3 support, his fo
271
290
 
272
291
  == Copyright
273
292
 
274
- Copyright (c) 2009 Sergio Cambra. See LICENSE for details.
293
+ Copyright (c) 2012 Sergio Cambra. See LICENSE for details.
@@ -13,7 +13,7 @@ class Devise::InvitationsController < DeviseController
13
13
 
14
14
  # POST /resource/invitation
15
15
  def create
16
- self.resource = resource_class.invite!(params[resource_name], current_inviter)
16
+ self.resource = resource_class.invite!(resource_params, current_inviter)
17
17
 
18
18
  if resource.errors.empty?
19
19
  set_flash_message :notice, :send_instructions, :email => self.resource.email
@@ -35,7 +35,7 @@ class Devise::InvitationsController < DeviseController
35
35
 
36
36
  # PUT /resource/invitation
37
37
  def update
38
- self.resource = resource_class.accept_invitation!(params[resource_name])
38
+ self.resource = resource_class.accept_invitation!(resource_params)
39
39
 
40
40
  if resource.errors.empty?
41
41
  set_flash_message :notice, :updated
@@ -1,4 +1,4 @@
1
- <h2>Set your password</h2>
1
+ <h2><%= t 'devise.invitations.edit.header' %></h2>
2
2
 
3
3
  <%= form_for resource, :as => resource_name, :url => invitation_path(resource_name), :html => { :method => :put } do |f| %>
4
4
  <%= devise_error_messages! %>
@@ -10,5 +10,5 @@
10
10
  <p><%= f.label :password_confirmation %><br />
11
11
  <%= f.password_field :password_confirmation %></p>
12
12
 
13
- <p><%= f.submit "Set my password" %></p>
13
+ <p><%= f.submit t("devise.invitations.edit.submit_button") %></p>
14
14
  <% end %>
@@ -1,12 +1,12 @@
1
- <h2>Send invitation</h2>
1
+ <h2><%= t "devise.invitations.new.header" %></h2>
2
2
 
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
- <p><%= f.label :email %><br />
7
- <%= f.text_field :email %></p>
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
- <p><%= f.submit "Send an invitation" %></p>
11
+ <p><%= f.submit t("devise.invitations.new.submit_button") %></p>
10
12
  <% end %>
11
-
12
- <%= link_to "Home", after_sign_in_path_for(resource_name) %><br />
@@ -5,6 +5,12 @@ 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
+ new:
9
+ header: "Send invitation"
10
+ submit_button: "Send an invitation"
11
+ edit:
12
+ header: "Set your password"
13
+ submit_button: "Set my password"
8
14
  mailer:
9
15
  invitation_instructions:
10
16
  subject: 'Invitation instructions'
@@ -0,0 +1,39 @@
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 = Hash[resource.invitation_fields.map {|field|
14
+ [field, resource[field]]
15
+ }]
16
+ resource.destroy
17
+ end
18
+ end
19
+ end
20
+
21
+ def keep_invitation_info
22
+ resource_invitable = resource_class.devise_modules.include?(:invitable)
23
+ destroy_if_previously_invited if resource_invitable
24
+ yield
25
+ reset_invitation_info if resource_invitable
26
+ end
27
+
28
+ def reset_invitation_info
29
+ # Restore info about the last invitation (for later reference)
30
+ # Reset the invitation_info only, if invited_by_id is still nil at this stage:
31
+ resource = resource_class.where(:email => params[resource_name][:email], :invited_by_id => nil).first
32
+ if resource && @invitation_info
33
+ resource.invitation_fields.each do |field|
34
+ resource[field] = @invitation_info[field]
35
+ end
36
+ resource.save!
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,36 @@
1
+ module DeviseInvitable::Controllers::Registrations
2
+ def self.included(controller)
3
+ controller.send :around_filter, :destroy_if_previously_invited, :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 resource_class.modules.include?(:invitable) && 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
+
22
+ # execute the action (create)
23
+ yield
24
+ # Note that the after_filter is executed at THIS position !
25
+
26
+ # Restore info about the last invitation (for later reference)
27
+ # Reset the invitation_info only, if invited_by_id is still nil at this stage:
28
+ resource = resource_class.where(:email => hash[:email], :invited_by_id => nil).first
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]
33
+ resource.save!
34
+ end
35
+ end
36
+ end
@@ -13,7 +13,7 @@ module DeviseInvitable
13
13
  else
14
14
  resource.class.name.underscore
15
15
  end
16
-
16
+
17
17
  send("#{action}\#{resource}_invitation_#{path_or_url}", *args)
18
18
  end
19
19
  URL_HELPERS
@@ -1,6 +1,6 @@
1
1
  module DeviseInvitable
2
2
  module Mailer
3
-
3
+
4
4
  # Deliver an invitation email
5
5
  def invitation_instructions(record)
6
6
  devise_mail(record, :invitation_instructions)
@@ -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).invited? # => true/false
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,40 +24,102 @@ 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
- include ::DeviseInvitable::Inviter
28
- belongs_to :invited_by, :polymorphic => true
29
-
30
+ include ::DeviseInvitable::Inviter
31
+ if Devise.invited_by_class_name
32
+ belongs_to :invited_by, :class_name => Devise.invited_by_class_name
33
+ else
34
+ belongs_to :invited_by, :polymorphic => true
35
+ end
36
+
30
37
  include ActiveSupport::Callbacks
31
38
  define_callbacks :invitation_accepted
32
-
39
+
33
40
  attr_writer :skip_password
41
+
42
+ scope :invitation_not_accepted, where(:invitation_accepted_at => nil)
43
+ if defined?(Mongoid) && self < Mongoid::Document
44
+ scope :invitation_accepted, where(:invitation_accepted_at.ne => nil)
45
+ else
46
+ scope :invitation_accepted, where(arel_table[:invitation_accepted_at].not_eq(nil))
47
+
48
+ [:before_invitation_accepted, :after_invitation_accepted].each do |callback_method|
49
+ send callback_method do
50
+ notify_observers callback_method
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ def self.required_fields(klass)
57
+ fields = [:invitation_token, :invitation_sent_at, :invitation_accepted_at,
58
+ :invitation_limit, :invited_by_id, :invited_by_type]
59
+ if Devise.invited_by_class_name
60
+ fields -= :invited_by_type
61
+ end
62
+ fields
63
+ end
64
+
65
+ def invitation_fields
66
+ fields = [:invitation_sent_at, :invited_by_id, :invited_by_type]
67
+ if Devise.invited_by_class_name
68
+ fields -= :invited_by_type
69
+ end
70
+ fields
34
71
  end
35
72
 
36
73
  # Accept an invitation by clearing invitation token and and setting invitation_accepted_at
37
74
  # Confirms it if model is confirmable
38
75
  def accept_invitation!
39
- if self.invited? && self.valid?
76
+ self.invitation_accepted_at = Time.now.utc
77
+ if self.invited_to_sign_up? && self.valid?
40
78
  run_callbacks :invitation_accepted do
41
79
  self.invitation_token = nil
42
- self.invitation_accepted_at = Time.now.utc if respond_to? :"invitation_accepted_at="
43
80
  self.save(:validate => false)
44
81
  end
45
82
  end
46
83
  end
47
84
 
85
+ # Verifies whether a user has accepted an invitation (or is accepting it), or was never invited
86
+ def accepting_or_not_invited?
87
+ ActiveSupport::Deprecation.warn "accepting_or_not_invited? is deprecated and will be removed from DeviseInvitable 1.1.0 (use accepted_or_not_invited? instead)"
88
+ accepted_or_not_invited?
89
+ end
90
+
48
91
  # Verifies whether a user has been invited or not
49
- def invited?
92
+ def invited_to_sign_up?
50
93
  persisted? && invitation_token.present?
51
94
  end
52
95
 
96
+ # Verifies whether a user accepted an invitation (or is accepting it)
97
+ def invitation_accepted?
98
+ invitation_accepted_at.present?
99
+ end
100
+
101
+ # Verifies whether a user has accepted an invitation (or is accepting it), or was never invited
102
+ def accepted_or_not_invited?
103
+ invitation_accepted? || !invited_to_sign_up?
104
+ end
105
+
106
+ def invited?
107
+ ActiveSupport::Deprecation.warn "invited? is deprecated and will be removed from DeviseInvitable 1.1.0 (use invited_to_sign_up? instead)"
108
+ invited_to_sign_up?
109
+ end
110
+
53
111
  # Reset invitation token and send invitation again
54
- def invite!
55
- was_invited = invited?
112
+ def invite!(invited_by = nil)
113
+ was_invited = invited_to_sign_up?
56
114
  self.skip_confirmation! if self.new_record? && self.respond_to?(:skip_confirmation!)
57
115
  generate_invitation_token if self.invitation_token.nil?
58
116
  self.invitation_sent_at = Time.now.utc
117
+ self.invited_by = invited_by if invited_by
118
+
119
+ # Call these before_validate methods since we aren't validating on save
120
+ self.downcase_keys if self.new_record? && self.respond_to?(:downcase_keys)
121
+ self.strip_whitespace if self.new_record? && self.respond_to?(:strip_whitespace)
122
+
59
123
  if save(:validate => false)
60
124
  self.invited_by.decrement_invitation_limit! if !was_invited and self.invited_by.present?
61
125
  deliver_invitation unless @skip_invitation
@@ -66,19 +130,26 @@ module Devise
66
130
  # invited, we need to calculate if the invitation time has not expired
67
131
  # for this user, in other words, if the invitation is still valid.
68
132
  def valid_invitation?
69
- invited? && invitation_period_valid?
133
+ invited_to_sign_up? && invitation_period_valid?
70
134
  end
71
135
 
72
136
  # Only verify password when is not invited
73
137
  def valid_password?(password)
74
- super unless invited?
138
+ super unless invited_to_sign_up?
75
139
  end
76
-
140
+
77
141
  def reset_password!(new_password, new_password_confirmation)
78
142
  super
79
143
  accept_invitation!
80
144
  end
81
145
 
146
+ def invite_key_valid?
147
+ return true unless self.class.invite_key.is_a? Hash # FIXME: remove this line when deprecation is removed
148
+ self.class.invite_key.all? do |key, regexp|
149
+ regexp.nil? || self.send(key).try(:match, regexp)
150
+ end
151
+ end
152
+
82
153
  protected
83
154
  # Overriding the method in Devise's :validatable module so password is not required on inviting
84
155
  def password_required?
@@ -87,7 +158,7 @@ module Devise
87
158
 
88
159
  # Deliver the invitation email
89
160
  def deliver_invitation
90
- ::Devise.mailer.invitation_instructions(self).deliver
161
+ send_devise_notification(:invitation_instructions)
91
162
  end
92
163
 
93
164
  # Checks if the invitation for the user is within the limit time.
@@ -120,6 +191,16 @@ module Devise
120
191
  end
121
192
 
122
193
  module ClassMethods
194
+ # Return fields to invite
195
+ def invite_key_fields
196
+ if invite_key.is_a? Hash
197
+ invite_key.keys
198
+ else
199
+ ActiveSupport::Deprecation.warn("invite_key should be a hash like {#{invite_key.inspect} => /.../}")
200
+ Array(invite_key)
201
+ end
202
+ end
203
+
123
204
  # Attempt to find a user by it's email. If a record is not found, create a new
124
205
  # user and send invitation to it. If user is found, returns the user with an
125
206
  # email already exists error.
@@ -127,16 +208,24 @@ module Devise
127
208
  # resend_invitation is set to false
128
209
  # Attributes must contain the user email, other attributes will be set in the record
129
210
  def _invite(attributes={}, invited_by=nil, &block)
130
- invitable = find_or_initialize_with_error_by(invite_key, attributes.delete(invite_key))
211
+ invite_key_array = invite_key_fields
212
+ attributes_hash = {}
213
+ invite_key_array.each do |k,v|
214
+ attributes_hash[k] = attributes.delete(k)
215
+ end
216
+
217
+ invitable = find_or_initialize_with_errors(invite_key_array, attributes_hash)
131
218
  invitable.assign_attributes(attributes, :as => inviter_role(invited_by))
132
219
  invitable.invited_by = invited_by
133
220
 
134
221
  invitable.skip_password = true
135
222
  invitable.valid? if self.validate_on_invite
136
223
  if invitable.new_record?
137
- invitable.errors.clear if !self.validate_on_invite and invitable.email.try(:match, Devise.email_regexp)
138
- else
139
- invitable.errors.add(invite_key, :taken) unless invitable.invited? && self.resend_invitation
224
+ invitable.errors.clear if !self.validate_on_invite and invitable.invite_key_valid?
225
+ elsif !invitable.invited_to_sign_up? || !self.resend_invitation
226
+ invite_key_array.each do |key|
227
+ invitable.errors.add(key, :taken)
228
+ end
140
229
  end
141
230
 
142
231
  if invitable.errors.empty?
@@ -145,7 +234,7 @@ module Devise
145
234
  end
146
235
  [invitable, mail]
147
236
  end
148
-
237
+
149
238
  # Override this method if the invitable is using Mass Assignment Security
150
239
  # and the inviter has a non-default role.
151
240
  def inviter_role(inviter)
@@ -181,16 +270,16 @@ module Devise
181
270
  def invitation_token
182
271
  generate_token(:invitation_token)
183
272
  end
184
-
273
+
185
274
  # Callback convenience methods
186
275
  def before_invitation_accepted(*args, &blk)
187
276
  set_callback(:invitation_accepted, :before, *args, &blk)
188
277
  end
189
-
278
+
190
279
  def after_invitation_accepted(*args, &blk)
191
280
  set_callback(:invitation_accepted, :after, *args, &blk)
192
281
  end
193
-
282
+
194
283
 
195
284
  Devise::Models.config(self, :invite_for)
196
285
  Devise::Models.config(self, :validate_on_invite)
@@ -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).invited? # => true/false
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
- if self.invited? && self.valid?
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 invited?
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 = 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,19 @@ 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
- invited? && invitation_period_valid?
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 invited?
94
+ super unless invited_to_sign_up?
75
95
  end
76
- =begin
96
+
77
97
  def reset_password!(new_password, new_password_confirmation)
78
98
  super
79
99
  accept_invitation!
80
100
  end
81
- =end
101
+
82
102
  protected
83
103
  # Overriding the method in Devise's :validatable module so password is not required on inviting
84
104
  def password_required?
@@ -90,12 +110,6 @@ module Devise
90
110
  ::Devise.mailer.invitation_instructions(self).deliver
91
111
  end
92
112
 
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
113
  # Checks if the invitation for the user is within the limit time.
100
114
  # We do this by calculating if the difference between today and the
101
115
  # invitation sent date does not exceed the invite for time configured.
@@ -142,7 +156,7 @@ module Devise
142
156
  if invitable.new_record?
143
157
  invitable.errors.clear if !self.validate_on_invite and invitable.email.try(:match, Devise.email_regexp)
144
158
  else
145
- invitable.errors.add(invite_key, :taken) unless invitable.invited? && self.resend_invitation
159
+ invitable.errors.add(invite_key, :taken) unless invitable.invited_to_sign_up? && self.resend_invitation
146
160
  end
147
161
 
148
162
  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
@@ -1,6 +1,6 @@
1
1
  module ActionDispatch::Routing
2
2
  class Mapper
3
-
3
+
4
4
  protected
5
5
  def devise_invitation(mapping, controllers)
6
6
  resource :invitation, :only => [:new, :create, :update],
@@ -8,6 +8,6 @@ module ActionDispatch::Routing
8
8
  get :edit, :path => mapping.path_names[:accept], :as => :accept
9
9
  end
10
10
  end
11
-
11
+
12
12
  end
13
13
  end
@@ -1,3 +1,3 @@
1
1
  module DeviseInvitable
2
- VERSION = '1.0.0'
2
+ VERSION = '1.1.0'
3
3
  end
@@ -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)
@@ -52,6 +54,11 @@ module Devise
52
54
  # config.resend_invitation = false
53
55
  mattr_accessor :resend_invitation
54
56
  @@resend_invitation = true
57
+
58
+ # Public: The class name of the inviting model. If this is nil,
59
+ # the #invited_by association is declared to be polymorphic. (default: nil)
60
+ mattr_accessor :invited_by_class_name
61
+ @@invited_by_class_name = nil
55
62
  end
56
63
 
57
64
  Devise.add_module :invitable, :controller => :invitations, :model => 'devise_invitable/model', :route => :invitation
@@ -3,16 +3,16 @@ module DeviseInvitable
3
3
  class InstallGenerator < Rails::Generators::Base
4
4
  source_root File.expand_path("../../templates", __FILE__)
5
5
  desc "Add DeviseInvitable config variables to the Devise initializer and copy DeviseInvitable locale files to your application."
6
-
6
+
7
7
  # def devise_install
8
8
  # invoke "devise:install"
9
9
  # end
10
-
10
+
11
11
  def add_config_options_to_initializer
12
12
  devise_initializer_path = "config/initializers/devise.rb"
13
13
  if File.exist?(devise_initializer_path)
14
14
  old_content = File.read(devise_initializer_path)
15
-
15
+
16
16
  if old_content.match(Regexp.new(/^\s# ==> Configuration for :invitable\n/))
17
17
  false
18
18
  else
@@ -23,18 +23,20 @@ module DeviseInvitable
23
23
  # this period, the invited resource won't be able to accept the invitation.
24
24
  # When invite_for is 0 (the default), the invitation won't expire.
25
25
  # config.invite_for = 2.weeks
26
-
26
+
27
27
  # Number of invitations users can send.
28
28
  # If invitation_limit is nil, users can send unlimited invitations.
29
29
  # If invitation_limit is 0, users can't send invitations.
30
30
  # If invitation_limit n > 0, users can send n invitations.
31
31
  # Default: nil
32
32
  # config.invitation_limit = 5
33
-
33
+
34
34
  # The key to be used to check existing users when sending an invitation
35
- # config.invite_key = :email
36
-
37
- # Flag that force a record to be valid before being actually invited
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}
38
+
39
+ # Flag that force a record to be valid before being actually invited
38
40
  # Default: false
39
41
  # config.validate_on_invite = true
40
42
 
@@ -43,11 +45,11 @@ CONTENT
43
45
  end
44
46
  end
45
47
  end
46
-
48
+
47
49
  def copy_locale
48
50
  copy_file "../../../config/locales/en.yml", "config/locales/devise_invitable.en.yml"
49
51
  end
50
-
52
+
51
53
  end
52
54
  end
53
55
  end
@@ -0,0 +1,11 @@
1
+ <h2><%= t 'devise.invitations.edit.header' %></h2>
2
+
3
+ <%= simple_form_for resource, :as => resource_name, :url => invitation_path(resource_name), :html => { :method => :put } do |f| %>
4
+ <%= devise_error_messages! %>
5
+ <%= f.hidden_field :invitation_token %>
6
+
7
+ <%= f.input :password %>
8
+ <%= f.input :password_confirmation %>
9
+
10
+ <%= f.submit t("devise.invitations.edit.submit_button") %>
11
+ <% end %>
@@ -0,0 +1,11 @@
1
+ <h2><%= t "devise.invitations.new.header" %></h2>
2
+
3
+ <%= simple_form_for resource, :as => resource_name, :url => invitation_path(resource_name), :html => {:method => :post} do |f| %>
4
+ <%= devise_error_messages! %>
5
+
6
+ <% resource.class.invite_key_fields.each do |field| -%>
7
+ <%= f.input field %>
8
+ <% end -%>
9
+
10
+ <%= f.submit t("devise.invitations.new.submit_button") %>
11
+ <% end %>
@@ -2,18 +2,40 @@ require 'generators/devise/views_generator'
2
2
 
3
3
  module DeviseInvitable
4
4
  module Generators
5
- class ViewsGenerator < Rails::Generators::Base
6
- desc 'Copies all DeviseInvitable views to your application.'
5
+ class InvitationViewsGenerator < Rails::Generators::Base
6
+ include ::Devise::Generators::ViewPathTemplates
7
+
8
+ def copy_views
9
+ view_directory :invitations
10
+ end
11
+ end
12
+
13
+ class SimpleFormForGenerator < InvitationViewsGenerator
14
+ source_root File.expand_path("../templates/simple_form_for", __FILE__)
15
+ end
7
16
 
8
- argument :scope, :required => false, :default => nil,
9
- :desc => "The scope to copy views to"
17
+ class FormForGenerator < InvitationViewsGenerator
18
+ source_root File.expand_path("../../../../app/views/devise", __FILE__)
19
+ end
10
20
 
21
+ class MailerViewsGenerator < Rails::Generators::Base
11
22
  include ::Devise::Generators::ViewPathTemplates
12
23
  source_root File.expand_path("../../../../app/views/devise", __FILE__)
24
+ desc "Copies Devise mail erb views to your application."
25
+ hide!
26
+
13
27
  def copy_views
14
- view_directory :invitations
15
28
  view_directory :mailer
16
29
  end
17
30
  end
31
+
32
+ class ViewsGenerator < Rails::Generators::Base
33
+ desc 'Copies all DeviseInvitable views to your application.'
34
+ argument :scope, :required => false, :default => nil, :desc => "The scope to copy views to"
35
+
36
+ invoke MailerViewsGenerator
37
+
38
+ hook_for :form_builder, :aliases => "-b", :desc => "Form builder to be used", :default => defined?(SimpleForm) ? "simple_form_for" : "form_for"
39
+ end
18
40
  end
19
41
  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: 23
4
+ hash: 19
5
5
  prerelease:
6
6
  segments:
7
7
  - 1
8
+ - 1
8
9
  - 0
9
- - 0
10
- version: 1.0.0
10
+ version: 1.1.0
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-01-31 00:00:00 Z
18
+ date: 2012-08-20 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: bundler
@@ -23,18 +23,18 @@ dependencies:
23
23
  requirement: &id001 !ruby/object:Gem::Requirement
24
24
  none: false
25
25
  requirements:
26
- - - ~>
26
+ - - ">="
27
27
  - !ruby/object:Gem::Version
28
- hash: 25
28
+ hash: 19
29
29
  segments:
30
30
  - 1
31
+ - 1
31
32
  - 0
32
- - 7
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
37
- name: rails
37
+ name: railties
38
38
  prerelease: false
39
39
  requirement: &id002 !ruby/object:Gem::Requirement
40
40
  none: false
@@ -49,21 +49,36 @@ dependencies:
49
49
  type: :runtime
50
50
  version_requirements: *id002
51
51
  - !ruby/object:Gem::Dependency
52
- name: devise
52
+ name: actionmailer
53
53
  prerelease: false
54
54
  requirement: &id003 !ruby/object:Gem::Requirement
55
55
  none: false
56
56
  requirements:
57
57
  - - ~>
58
58
  - !ruby/object:Gem::Version
59
- hash: 15
59
+ hash: 7
60
60
  segments:
61
- - 2
62
- - 0
61
+ - 3
63
62
  - 0
64
- version: 2.0.0
63
+ version: "3.0"
65
64
  type: :runtime
66
65
  version_requirements: *id003
66
+ - !ruby/object:Gem::Dependency
67
+ name: devise
68
+ prerelease: false
69
+ requirement: &id004 !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ hash: 11
75
+ segments:
76
+ - 2
77
+ - 1
78
+ - 0
79
+ version: 2.1.0
80
+ type: :runtime
81
+ version_requirements: *id004
67
82
  description: It adds support for send invitations by email (it requires to be authenticated) and accept the invitation by setting a password.
68
83
  email:
69
84
  - sergio@entrecables.com
@@ -84,9 +99,11 @@ files:
84
99
  - lib/devise_invitable/model.rb
85
100
  - lib/devise_invitable/rails.rb
86
101
  - lib/devise_invitable/routes.rb
102
+ - lib/devise_invitable/version.rb
87
103
  - lib/devise_invitable/controllers/helpers.rb
88
104
  - lib/devise_invitable/controllers/url_helpers.rb
89
- - lib/devise_invitable/version.rb
105
+ - lib/devise_invitable/controllers/registrations.rb
106
+ - lib/devise_invitable/controllers/registrations.rb~
90
107
  - lib/devise_invitable/inviter.rb
91
108
  - lib/devise_invitable/model.rb~
92
109
  - lib/generators/active_record/devise_invitable_generator.rb
@@ -94,6 +111,8 @@ files:
94
111
  - lib/generators/devise_invitable/views_generator.rb
95
112
  - lib/generators/devise_invitable/devise_invitable_generator.rb
96
113
  - lib/generators/devise_invitable/install_generator.rb
114
+ - lib/generators/devise_invitable/templates/simple_form_for/invitations/edit.html.erb
115
+ - lib/generators/devise_invitable/templates/simple_form_for/invitations/new.html.erb
97
116
  - lib/generators/mongoid/devise_invitable_generator.rb
98
117
  - LICENSE
99
118
  - README.rdoc
@@ -132,7 +151,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
132
151
  requirements: []
133
152
 
134
153
  rubyforge_project:
135
- rubygems_version: 1.8.10
154
+ rubygems_version: 1.8.23
136
155
  signing_key:
137
156
  specification_version: 3
138
157
  summary: An invitation strategy for Devise