devise_invitable 3.0.3 → 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 +8 -1
- data/lib/devise_invitable/invitation.rb +7 -1
- data/lib/devise_invitable/models.rb +57 -12
- data/lib/devise_invitable/version.rb +1 -1
- data/test/models/invitable_test.rb +11 -1
- data/test/models/separate_invitations_test.rb +37 -0
- 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,11 @@
|
|
|
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
|
|
2
9
|
|
|
3
10
|
- Check invitation token uniqueness against the invitations store instead of
|
|
4
11
|
the resource table, which no longer has an `invitation_token` column after
|
|
@@ -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.
|
|
@@ -464,6 +508,7 @@ module Devise
|
|
|
464
508
|
superseded_reason: 'registered'
|
|
465
509
|
)
|
|
466
510
|
end
|
|
511
|
+
reset_devise_invitation_associations
|
|
467
512
|
self.registering_directly = false
|
|
468
513
|
end
|
|
469
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')
|
|
@@ -138,6 +138,7 @@ class SeparateInvitationsTest < ActiveSupport::TestCase
|
|
|
138
138
|
|
|
139
139
|
user = User.invite!(email: 'placeholder@example.com')
|
|
140
140
|
user.update!(confirmed_at: Time.now.utc)
|
|
141
|
+
user.pending_invitations.to_a
|
|
141
142
|
|
|
142
143
|
assert_predicate user, :invitation_pending?
|
|
143
144
|
refute_predicate user, :active_for_authentication?
|
|
@@ -149,6 +150,34 @@ class SeparateInvitationsTest < ActiveSupport::TestCase
|
|
|
149
150
|
assert_predicate user, :active_for_authentication?
|
|
150
151
|
end
|
|
151
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
|
+
|
|
152
181
|
test 'resource attributes raise when placeholder creation is disabled' do
|
|
153
182
|
User.create_resource_on_invitation = false
|
|
154
183
|
|
|
@@ -224,4 +253,12 @@ class SeparateInvitationsTest < ActiveSupport::TestCase
|
|
|
224
253
|
refute first.reload.attributes.key?('invitation_created_at')
|
|
225
254
|
refute second.reload.attributes.key?('invitation_created_at')
|
|
226
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
|
|
227
264
|
end
|