devise_invitable 3.0.2 → 3.0.4
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -1
- data/lib/devise_invitable/invitation.rb +7 -1
- data/lib/devise_invitable/models.rb +60 -13
- data/lib/devise_invitable/version.rb +1 -1
- data/test/models/invitable_test.rb +11 -1
- data/test/models/separate_invitations_test.rb +51 -0
- data/test/rails_app/app/models/user.rb +1 -7
- data/test/rails_app/db/migrate/20100401102949_create_tables.rb +0 -7
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1d7694e9f3f376c8f60a5d426144eebe986967f33b6ea016ca1f01c65fa19e32
|
|
4
|
+
data.tar.gz: 40faca7611e16510fe0d45f886e35f6622eb7d1cefe9016be0e93ba6d272e2e4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: aab0c7da6b34b49475014c55ace27804cff1c95b15012a3209c0cf7f7e42e6612647dbdeb0ddacd8fd6e26353b2617355f8c4e2f25a3dd8a2e4aaf9aa0b89d7e
|
|
7
|
+
data.tar.gz: 2d7873782c77d7c9076312eea7a57cb8cfc4e54874dbd4705e6133d1a5ed92f981291ec8bc7bd81cf4570836059c2b2aff6fffdf01b54f0056d67e62467a5a50
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
##
|
|
1
|
+
## 3.0.4
|
|
2
|
+
|
|
3
|
+
- Avoid using distinct which may fail in postgresql if the model has json column ([935](https://github.com/scambra/devise_invitable/issues/935))
|
|
4
|
+
- Use scoped preloaded invitations when checking whether an invitation is
|
|
5
|
+
pending, avoiding per-resource queries in listings
|
|
6
|
+
([#937](https://github.com/scambra/devise_invitable/issues/937))
|
|
7
|
+
|
|
8
|
+
## 3.0.3
|
|
9
|
+
|
|
10
|
+
- Check invitation token uniqueness against the invitations store instead of
|
|
11
|
+
the resource table, which no longer has an `invitation_token` column after
|
|
12
|
+
running `devise_invitable:upgrade` ([#933](https://github.com/scambra/devise_invitable/issues/933))
|
|
2
13
|
|
|
3
14
|
## 3.0.2
|
|
4
15
|
|
|
@@ -38,7 +38,13 @@ module DeviseInvitable
|
|
|
38
38
|
index({ invitable_type: 1, invite_key_digest: 1 })
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
invitable_options = { polymorphic: true, optional: true }
|
|
42
|
+
if defined?(Mongoid::Document) && !defined?(ActiveRecord::Base)
|
|
43
|
+
# Mongoid cannot infer a polymorphic inverse when the parent exposes both
|
|
44
|
+
# the complete and scoped invitation associations.
|
|
45
|
+
invitable_options[:inverse_of] = false
|
|
46
|
+
end
|
|
47
|
+
belongs_to :invitable, **invitable_options
|
|
42
48
|
invited_by_options = { polymorphic: true, optional: true, autosave: true }
|
|
43
49
|
if defined?(ActiveRecord::Base) && Devise.invited_by_counter_cache
|
|
44
50
|
invited_by_options[:counter_cache] = Devise.invited_by_counter_cache
|
|
@@ -35,11 +35,25 @@ module Devise
|
|
|
35
35
|
included do
|
|
36
36
|
include ::DeviseInvitable::Inviter
|
|
37
37
|
has_many :devise_invitations,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
as: :invitable,
|
|
39
|
+
class_name: 'DeviseInvitable::Invitation',
|
|
40
|
+
dependent: :nullify,
|
|
41
|
+
autosave: true
|
|
42
|
+
if defined?(Mongoid::Document) && self < Mongoid::Document
|
|
43
|
+
has_many :pending_invitations,
|
|
44
|
+
as: :invitable,
|
|
45
|
+
class_name: 'DeviseInvitable::Invitation',
|
|
46
|
+
foreign_key: :invitable_id,
|
|
47
|
+
inverse_of: false,
|
|
48
|
+
scope: -> { pending.provisioning }
|
|
49
|
+
else
|
|
50
|
+
has_many :pending_invitations,
|
|
51
|
+
-> { pending.provisioning },
|
|
52
|
+
as: :invitable,
|
|
53
|
+
class_name: 'DeviseInvitable::Invitation',
|
|
54
|
+
foreign_key: :invitable_id,
|
|
55
|
+
inverse_of: false
|
|
56
|
+
end
|
|
43
57
|
extend ActiveModel::Callbacks
|
|
44
58
|
define_model_callbacks :invitation_created
|
|
45
59
|
define_model_callbacks :invitation_accepted
|
|
@@ -68,14 +82,22 @@ module Devise
|
|
|
68
82
|
}
|
|
69
83
|
elsif defined?(ActiveRecord) && defined?(ActiveRecord::Base) && self < ActiveRecord::Base
|
|
70
84
|
scope :created_by_invite, lambda {
|
|
71
|
-
|
|
85
|
+
invitation_ids = DeviseInvitable::Invitation.provisioning
|
|
86
|
+
.where(invitable_type: devise_invitable_base_class.name)
|
|
87
|
+
.select(:invitable_id)
|
|
88
|
+
where(id: invitation_ids)
|
|
72
89
|
}
|
|
73
90
|
scope :invitation_not_accepted, lambda {
|
|
74
|
-
|
|
75
|
-
.
|
|
91
|
+
invitation_ids = DeviseInvitable::Invitation.pending.provisioning
|
|
92
|
+
.where(invitable_type: devise_invitable_base_class.name)
|
|
93
|
+
.select(:invitable_id)
|
|
94
|
+
where(id: invitation_ids)
|
|
76
95
|
}
|
|
77
96
|
scope :invitation_accepted, lambda {
|
|
78
|
-
|
|
97
|
+
invitation_ids = DeviseInvitable::Invitation.accepted
|
|
98
|
+
.where(invitable_type: devise_invitable_base_class.name)
|
|
99
|
+
.select(:invitable_id)
|
|
100
|
+
where(id: invitation_ids)
|
|
79
101
|
}
|
|
80
102
|
scope :no_active_invitation, lambda {
|
|
81
103
|
invitation_ids = DeviseInvitable::Invitation.pending
|
|
@@ -195,6 +217,7 @@ module Devise
|
|
|
195
217
|
current_invitation.invitation_token = nil
|
|
196
218
|
current_invitation.save!
|
|
197
219
|
supersede_other_invitations!(accepted_at)
|
|
220
|
+
reset_devise_invitation_associations
|
|
198
221
|
true
|
|
199
222
|
else
|
|
200
223
|
false
|
|
@@ -222,7 +245,12 @@ module Devise
|
|
|
222
245
|
def invitation_pending?
|
|
223
246
|
return false unless persisted?
|
|
224
247
|
|
|
225
|
-
|
|
248
|
+
invitations = pending_invitations
|
|
249
|
+
if devise_invitable_association_loaded?(invitations)
|
|
250
|
+
return invitations.any?
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
invitations.exists?
|
|
226
254
|
end
|
|
227
255
|
|
|
228
256
|
def invited_to_sign_up?
|
|
@@ -276,6 +304,7 @@ module Devise
|
|
|
276
304
|
invitation.resource_created_at = now if resource_was_new
|
|
277
305
|
end
|
|
278
306
|
invitation.save!
|
|
307
|
+
reset_devise_invitation_associations
|
|
279
308
|
invitation.invited_by.decrement_invitation_limit! if invitation.invited_by.present?
|
|
280
309
|
deliver_invitation(options) unless skip_invitation
|
|
281
310
|
end
|
|
@@ -371,24 +400,39 @@ module Devise
|
|
|
371
400
|
invitation = current_invitation
|
|
372
401
|
return unless invitation&.pending?
|
|
373
402
|
|
|
374
|
-
remove_placeholder = invitation.provisioning?
|
|
403
|
+
remove_placeholder = invitation.provisioning?
|
|
375
404
|
invitation.update!(revoked_at: Time.now.utc, invitation_token: nil)
|
|
405
|
+
reset_devise_invitation_associations
|
|
376
406
|
destroy if remove_placeholder
|
|
377
407
|
end
|
|
378
408
|
|
|
379
409
|
protected
|
|
380
410
|
|
|
411
|
+
def devise_invitable_association_loaded?(association)
|
|
412
|
+
return association.loaded? if association.respond_to?(:loaded?)
|
|
413
|
+
|
|
414
|
+
association._loaded?
|
|
415
|
+
end
|
|
416
|
+
|
|
381
417
|
def assign_invitation_attribute(attribute, value)
|
|
382
418
|
return unless current_invitation
|
|
383
419
|
|
|
384
420
|
current_invitation.public_send("#{attribute}=", value)
|
|
385
|
-
|
|
421
|
+
if current_invitation.persisted?
|
|
422
|
+
current_invitation.save!
|
|
423
|
+
reset_devise_invitation_associations
|
|
424
|
+
end
|
|
386
425
|
end
|
|
387
426
|
|
|
388
427
|
def block_from_invitation?
|
|
389
428
|
invitation_pending?
|
|
390
429
|
end
|
|
391
430
|
|
|
431
|
+
def reset_devise_invitation_associations
|
|
432
|
+
devise_invitations.reset
|
|
433
|
+
pending_invitations.reset
|
|
434
|
+
end
|
|
435
|
+
|
|
392
436
|
# Checks if the invitation for the user is within the limit time.
|
|
393
437
|
# We do this by calculating if the difference between today and the
|
|
394
438
|
# invitation sent date does not exceed the invite for time configured.
|
|
@@ -415,7 +459,9 @@ module Devise
|
|
|
415
459
|
# Generates a new random token for invitation, and stores the time
|
|
416
460
|
# this token is being generated
|
|
417
461
|
def generate_invitation_token
|
|
418
|
-
|
|
462
|
+
# Tokens live in the invitations store, so check uniqueness there
|
|
463
|
+
# instead of on the resource, which may no longer have the column
|
|
464
|
+
raw, enc = Devise.token_generator.generate(DeviseInvitable::Invitation, :invitation_token)
|
|
419
465
|
@raw_invitation_token = raw
|
|
420
466
|
current_invitation.invitation_token = enc
|
|
421
467
|
end
|
|
@@ -462,6 +508,7 @@ module Devise
|
|
|
462
508
|
superseded_reason: 'registered'
|
|
463
509
|
)
|
|
464
510
|
end
|
|
511
|
+
reset_devise_invitation_associations
|
|
465
512
|
self.registering_directly = false
|
|
466
513
|
end
|
|
467
514
|
|
|
@@ -88,7 +88,7 @@ class InvitableTest < ActiveSupport::TestCase
|
|
|
88
88
|
|
|
89
89
|
test 'should return the correct raw_invitation_token ' do
|
|
90
90
|
user = new_user
|
|
91
|
-
raw, enc = Devise.token_generator.generate(
|
|
91
|
+
raw, enc = Devise.token_generator.generate(DeviseInvitable::Invitation, :invitation_token)
|
|
92
92
|
# stub the generator so the tokens are the same
|
|
93
93
|
Devise.token_generator.stubs(:generate).returns([raw, enc])
|
|
94
94
|
user.invite!
|
|
@@ -839,6 +839,16 @@ class InvitableTest < ActiveSupport::TestCase
|
|
|
839
839
|
assert_equal 3, User.created_by_invite.count
|
|
840
840
|
end
|
|
841
841
|
|
|
842
|
+
test 'invitation scopes do not use distinct with json columns' do
|
|
843
|
+
skip unless defined?(ActiveRecord)
|
|
844
|
+
|
|
845
|
+
assert_equal :json, User.type_for_attribute('options').type
|
|
846
|
+
|
|
847
|
+
[:created_by_invite, :invitation_not_accepted, :invitation_accepted].each do |scope|
|
|
848
|
+
refute_match(/\bSELECT\s+DISTINCT\b/i, User.public_send(scope).to_sql)
|
|
849
|
+
end
|
|
850
|
+
end
|
|
851
|
+
|
|
842
852
|
test "should preserve return values of Devise::Recoverable#reset_password" do
|
|
843
853
|
user = new_user
|
|
844
854
|
retval = user.reset_password('anewpassword', 'anewpassword')
|
|
@@ -48,6 +48,20 @@ class SeparateInvitationsTest < ActiveSupport::TestCase
|
|
|
48
48
|
end
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
+
test 'invitation token uniqueness is checked against the invitations store' do
|
|
52
|
+
User.create_resource_on_invitation = false
|
|
53
|
+
Devise.stubs(:friendly_token).returns('taken-token', 'taken-token', 'free-token')
|
|
54
|
+
|
|
55
|
+
User.invite!(email: 'first@example.com')
|
|
56
|
+
User.invite!(email: 'second@example.com')
|
|
57
|
+
|
|
58
|
+
tokens = DeviseInvitable::Invitation.all.map(&:invitation_token)
|
|
59
|
+
assert_equal [
|
|
60
|
+
Devise.token_generator.digest(User, :invitation_token, 'taken-token'),
|
|
61
|
+
Devise.token_generator.digest(User, :invitation_token, 'free-token')
|
|
62
|
+
].sort, tokens.sort
|
|
63
|
+
end
|
|
64
|
+
|
|
51
65
|
test 'a failed later delivery remains unsent and can be retried' do
|
|
52
66
|
User.create_resource_on_invitation = false
|
|
53
67
|
invited = User.invite!(email: 'retry@example.com', skip_invitation: true)
|
|
@@ -124,6 +138,7 @@ class SeparateInvitationsTest < ActiveSupport::TestCase
|
|
|
124
138
|
|
|
125
139
|
user = User.invite!(email: 'placeholder@example.com')
|
|
126
140
|
user.update!(confirmed_at: Time.now.utc)
|
|
141
|
+
user.pending_invitations.to_a
|
|
127
142
|
|
|
128
143
|
assert_predicate user, :invitation_pending?
|
|
129
144
|
refute_predicate user, :active_for_authentication?
|
|
@@ -135,6 +150,34 @@ class SeparateInvitationsTest < ActiveSupport::TestCase
|
|
|
135
150
|
assert_predicate user, :active_for_authentication?
|
|
136
151
|
end
|
|
137
152
|
|
|
153
|
+
test 'invitation pending uses preloaded invitations' do
|
|
154
|
+
pending_user = User.invite!(email: 'pending@example.com')
|
|
155
|
+
active_user = User.create!(email: 'active@example.com', password: '123456789')
|
|
156
|
+
DeviseInvitable::Invitation.create!(
|
|
157
|
+
invitable: active_user,
|
|
158
|
+
invitation_accepted_at: Time.now.utc,
|
|
159
|
+
resource_created_at: Time.now.utc,
|
|
160
|
+
invite_key: { email: active_user.email }
|
|
161
|
+
)
|
|
162
|
+
users = User.includes(:pending_invitations).index_by(&:email)
|
|
163
|
+
|
|
164
|
+
users.each_value do |user|
|
|
165
|
+
assert association_loaded?(user.pending_invitations)
|
|
166
|
+
end
|
|
167
|
+
check_statuses = proc do
|
|
168
|
+
assert_equal 1, users[pending_user.email].pending_invitations.size
|
|
169
|
+
assert_empty users[active_user.email].pending_invitations
|
|
170
|
+
assert_predicate users[pending_user.email], :invitation_pending?
|
|
171
|
+
refute_predicate users[active_user.email], :invitation_pending?
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
if respond_to?(:assert_no_queries)
|
|
175
|
+
assert_no_queries(&check_statuses)
|
|
176
|
+
else
|
|
177
|
+
check_statuses.call
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
138
181
|
test 'resource attributes raise when placeholder creation is disabled' do
|
|
139
182
|
User.create_resource_on_invitation = false
|
|
140
183
|
|
|
@@ -210,4 +253,12 @@ class SeparateInvitationsTest < ActiveSupport::TestCase
|
|
|
210
253
|
refute first.reload.attributes.key?('invitation_created_at')
|
|
211
254
|
refute second.reload.attributes.key?('invitation_created_at')
|
|
212
255
|
end
|
|
256
|
+
|
|
257
|
+
private
|
|
258
|
+
|
|
259
|
+
def association_loaded?(association)
|
|
260
|
+
return association.loaded? if association.respond_to?(:loaded?)
|
|
261
|
+
|
|
262
|
+
association._loaded?
|
|
263
|
+
end
|
|
213
264
|
end
|
|
@@ -18,13 +18,7 @@ class User < PARENT_MODEL_CLASS
|
|
|
18
18
|
field :unconfirmed_email, type: String # Only if using reconfirmable
|
|
19
19
|
|
|
20
20
|
## Invitable
|
|
21
|
-
field :
|
|
22
|
-
field :invitation_created_at, type: Time
|
|
23
|
-
field :invitation_sent_at, type: Time
|
|
24
|
-
field :invitation_accepted_at, type: Time
|
|
25
|
-
field :invitation_limit, type: Integer
|
|
26
|
-
field :invited_by_id, type: Integer
|
|
27
|
-
field :invited_by_type, type: String
|
|
21
|
+
field :invitation_limit, type: Integer
|
|
28
22
|
|
|
29
23
|
field :username
|
|
30
24
|
field :profile_id
|
|
@@ -20,18 +20,11 @@ class CreateTables < (Rails.version < '5.1' ? ActiveRecord::Migration : ActiveRe
|
|
|
20
20
|
t.boolean :active
|
|
21
21
|
|
|
22
22
|
## Invitable
|
|
23
|
-
t.string :invitation_token, :limit => 60
|
|
24
|
-
t.datetime :invitation_created_at
|
|
25
|
-
t.datetime :invitation_sent_at
|
|
26
|
-
t.datetime :invitation_accepted_at
|
|
27
23
|
t.integer :invitation_limit
|
|
28
|
-
t.integer :invited_by_id
|
|
29
|
-
t.string :invited_by_type
|
|
30
24
|
t.integer :invitations_count, :default => 0
|
|
31
25
|
|
|
32
26
|
t.timestamps :null => false
|
|
33
27
|
end
|
|
34
|
-
add_index :users, :invitation_token, :unique => true
|
|
35
28
|
|
|
36
29
|
create_table :devise_invitations do |t|
|
|
37
30
|
t.string :invitation_token
|