FIX: no duplicates when a user is member of multiple invited groups

FIX: no duplicates when a user is member of multiple invited groups

diff --git a/app/models/discourse_post_event/event.rb b/app/models/discourse_post_event/event.rb
index c64e7cf..d5cc189 100644
--- a/app/models/discourse_post_event/event.rb
+++ b/app/models/discourse_post_event/event.rb
@@ -161,8 +161,8 @@ module DiscoursePostEvent
 
     def notify_missing_invitees!
       if self.private?
-        self.missing_group_users.each do |group_user|
-          create_notification!(group_user.user, self.post)
+        self.missing_users.each do |user|
+          create_notification!(user, self.post)
         end
       end
     end
@@ -219,11 +219,9 @@ module DiscoursePostEvent
         # so we create a dummy invitee object with only what's needed for serializer
         going =
           going +
-            GroupUser.includes(:group, :user).where(
-              'groups.name' => self.raw_invitees
-            ).where.not('users.id' => going.pluck(:user_id)).limit(
-              limit - going.count
-            ).map { |gu| Invitee.new(user: gu.user, post_id: self.id) }
+          missing_users(going.pluck(:user_id))
+            .limit(limit - going.count)
+            .map { |user| Invitee.new(user: user, post_id: self.id) }
       end
 
       going
@@ -288,11 +286,12 @@ module DiscoursePostEvent
       end
     end
 
-    def missing_group_users
-      GroupUser
-        .joins(:group, :user)
+    def missing_users(excluded_ids = self.invitees.select(:user_id))
+      User
+        .joins(:groups)
         .where('groups.name' => self.raw_invitees)
-        .where.not('users.id' => self.invitees.select(:user_id))
+        .where.not(id: excluded_ids)
+        .distinct
     end
 
     def update_with_params!(params)
diff --git a/app/serializers/discourse_post_event/event_serializer.rb b/app/serializers/discourse_post_event/event_serializer.rb
index 9128fb0..37b1cc1 100644
--- a/app/serializers/discourse_post_event/event_serializer.rb
+++ b/app/serializers/discourse_post_event/event_serializer.rb
@@ -96,7 +96,7 @@ module DiscoursePostEvent
       # when a group is private we know the list of possible users
       # even if an invitee has not been created yet
       if object.private?
-        unanswered += object.missing_group_users.count
+        unanswered += object.missing_users.count
       end
 
       {
diff --git a/spec/models/discourse_post_event/event_spec.rb b/spec/models/discourse_post_event/event_spec.rb
index 34ca640..05c45e7 100644
--- a/spec/models/discourse_post_event/event_spec.rb
+++ b/spec/models/discourse_post_event/event_spec.rb
@@ -5,6 +5,7 @@ require_relative '../../fabricators/event_fabricator'
 
 describe DiscoursePostEvent::Event do
   Event ||= DiscoursePostEvent::Event
+  Invitee ||= DiscoursePostEvent::Invitee
   Field ||= DiscoursePostEvent::TOPIC_POST_EVENT_STARTS_AT
 
   before do
@@ -461,4 +462,38 @@ describe DiscoursePostEvent::Event do
       end
     end
   end
+
+  context '#missing_users' do
+    let!(:post_1) { Fabricate(:post) }
+    let!(:user_1) { Fabricate(:user) }
+    let!(:user_2) { Fabricate(:user) }
+    let!(:user_3) { Fabricate(:user) }
+    let!(:group_1) {
+      Fabricate(:group).tap do |g|
+        g.add(user_1)
+        g.add(user_2)
+        g.add(user_3)
+        g.save!
+      end
+    }
+    let!(:group_2) {
+      Fabricate(:group).tap do |g|
+        g.add(user_2)
+        g.save!
+      end
+    }
+    let!(:event_1) { Fabricate(:event, post: post_1, status: Event.statuses[:private], raw_invitees: [group_1.name, group_2.name]) }
+
+    before do
+      Invitee.create_attendance!(user_3.id, post_1.id, :going)
+    end
+
+    it 'doesn’t return already attending user' do
+      expect(event_1.missing_users.pluck(:id)).to_not include(user_3.id)
+    end
+
+    it 'return users from groups with no duplicates' do
+      expect(event_1.missing_users.pluck(:id)).to match_array([user_1.id, user_2.id])
+    end
+  end
 end

GitHub sha: 84a051fb