diff --git a/app/jobs/notifications/new_badge_achievement_job.rb b/app/jobs/notifications/new_badge_achievement_job.rb new file mode 100644 index 000000000..eb1ed472a --- /dev/null +++ b/app/jobs/notifications/new_badge_achievement_job.rb @@ -0,0 +1,12 @@ +module Notifications + class NewBadgeAchievementJob < ApplicationJob + queue_as :send_new_badge_achievement_notification + + def perform(badge_achievement_id, service = NewBadgeAchievement::Send) + badge_achievement = BadgeAchievement.find_by(id: badge_achievement_id) + return unless badge_achievement + + service.call(badge_achievement) + end + end +end diff --git a/app/jobs/notifications/new_comment_job.rb b/app/jobs/notifications/new_comment_job.rb index 5ca633083..a7e6dcfbf 100644 --- a/app/jobs/notifications/new_comment_job.rb +++ b/app/jobs/notifications/new_comment_job.rb @@ -1,6 +1,7 @@ module Notifications class NewCommentJob < ApplicationJob queue_as :send_new_comment_notification + def perform(comment_id, service = NewComment::Send) comment = Comment.find_by(id: comment_id) service.call(comment) if comment diff --git a/app/jobs/notifications/new_reaction_job.rb b/app/jobs/notifications/new_reaction_job.rb index 9739c89ca..51dd5fbf3 100644 --- a/app/jobs/notifications/new_reaction_job.rb +++ b/app/jobs/notifications/new_reaction_job.rb @@ -1,6 +1,7 @@ module Notifications class NewReactionJob < ApplicationJob queue_as :send_new_reaction_notification + # @param reaction_data [Hash] # * :reactable_id [Integer] - article or comment id # * :reactable_type [String] - "Article" or "Comment" diff --git a/app/models/badge_achievement.rb b/app/models/badge_achievement.rb index 429bd2599..e402a6df8 100644 --- a/app/models/badge_achievement.rb +++ b/app/models/badge_achievement.rb @@ -25,7 +25,7 @@ class BadgeAchievement < ApplicationRecord private def notify_recipient - Notification.send_new_badge_notification(self) + Notification.send_new_badge_achievement_notification(self) end def send_email_notification diff --git a/app/models/notification.rb b/app/models/notification.rb index 32e3a7bd5..6d068aefc 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -19,6 +19,8 @@ class Notification < ApplicationRecord end def send_new_follower_notification_without_delay(follow, is_read = false) + return unless Follow.need_new_follower_notification_for?(follow.followable_type) + follow_data = follow.attributes.slice("follower_id", "followable_id", "followable_type").symbolize_keys Notifications::NewFollowerJob.perform_now(follow_data, is_read) end @@ -60,28 +62,20 @@ class Notification < ApplicationRecord Notifications::NewCommentJob.perform_now(comment.id) end - def send_new_badge_notification(badge_achievement) - json_data = { - user: user_data(badge_achievement.user), - badge_achievement: { - badge_id: badge_achievement.badge_id, - rewarding_context_message: badge_achievement.rewarding_context_message, - badge: { - title: badge_achievement.badge.title, - description: badge_achievement.badge.description, - badge_image_url: badge_achievement.badge.badge_image_url - } - } - } - Notification.create( - user_id: badge_achievement.user.id, - notifiable_id: badge_achievement.id, - notifiable_type: "BadgeAchievement", - action: nil, - json_data: json_data, - ) + def send_new_badge_achievement_notification(badge_achievement) + Notifications::NewBadgeAchievementJob.perform_later(badge_achievement.id) + end + # NOTE: this alias is temporary until the transition to ActiveJob is completed + # and all old DelayedJob jobs are processed by the queue workers. + # It can be removed after pre-existing jobs are done + alias send_new_badge_notification send_new_badge_achievement_notification + + # NOTE: this method is temporary until the transition to ActiveJob is completed + # and all old DelayedJob jobs are processed by the queue workers. + # It can be removed after pre-existing jobs are done + def send_new_badge_notification_without_delay(badge_achievement) + Notifications::NewBadgeAchievementJob.perform_now(badge_achievement.id) end - handle_asynchronously :send_new_badge_notification def send_reaction_notification(reaction, receiver) return if reaction.skip_notification_for?(receiver) diff --git a/app/services/notifications/new_badge_achievement/send.rb b/app/services/notifications/new_badge_achievement/send.rb new file mode 100644 index 000000000..cb294eabd --- /dev/null +++ b/app/services/notifications/new_badge_achievement/send.rb @@ -0,0 +1,45 @@ +# send notifications about the new badge achievement +module Notifications + module NewBadgeAchievement + class Send + def initialize(badge_achievement) + @badge_achievement = badge_achievement + end + + delegate :user_data, to: Notifications + + def self.call(*args) + new(*args).call + end + + def call + Notification.create( + user_id: badge_achievement.user.id, + notifiable_id: badge_achievement.id, + notifiable_type: "BadgeAchievement", + action: nil, + json_data: json_data, + ) + end + + private + + attr_reader :badge_achievement + + def json_data + { + user: user_data(badge_achievement.user), + badge_achievement: { + badge_id: badge_achievement.badge_id, + rewarding_context_message: badge_achievement.rewarding_context_message, + badge: { + title: badge_achievement.badge.title, + description: badge_achievement.badge.description, + badge_image_url: badge_achievement.badge.badge_image_url + } + } + } + end + end + end +end diff --git a/spec/jobs/notifications/new_badge_achievement_job_spec.rb b/spec/jobs/notifications/new_badge_achievement_job_spec.rb new file mode 100644 index 000000000..7dd3aeee4 --- /dev/null +++ b/spec/jobs/notifications/new_badge_achievement_job_spec.rb @@ -0,0 +1,24 @@ +require "rails_helper" + +RSpec.describe Notifications::NewBadgeAchievementJob, type: :job do + include_examples "#enqueues_job", "send_new_badge_achievement_notification", 5 + + describe "#perform_now" do + let(:new_badge_service) { double } + + before do + allow(new_badge_service).to receive(:call) + end + + it "calls the service" do + badge_achievement = create(:badge_achievement) + described_class.perform_now(badge_achievement.id, new_badge_service) + expect(new_badge_service).to have_received(:call).with(badge_achievement).once + end + + it "doesn't call a service if a nonexistent badge achievement is passed" do + described_class.perform_now(9999, new_badge_service) + expect(new_badge_service).not_to have_received(:call) + end + end +end diff --git a/spec/models/notification_spec.rb b/spec/models/notification_spec.rb index e9d791b88..3783f00b1 100644 --- a/spec/models/notification_spec.rb +++ b/spec/models/notification_spec.rb @@ -7,6 +7,7 @@ RSpec.describe Notification, type: :model do let(:organization) { create(:organization) } let(:article) { create(:article, user_id: user.id, page_views_count: 4000, positive_reactions_count: 70) } let(:follow_instance) { user.follow(user2) } + let(:badge_achievement) { create(:badge_achievement) } describe "when trying to #send_new_follower_notification after following a tag" do let(:tag) { create(:tag) } @@ -425,4 +426,28 @@ RSpec.describe Notification, type: :model do expect(notification.aggregated?).to eq false end end + + describe "#send_new_badge_achievement_notification" do + it "enqueues a new badge achievement job" do + assert_enqueued_with(job: Notifications::NewBadgeAchievementJob, args: [badge_achievement.id]) do + Notification.send_new_badge_achievement_notification(badge_achievement) + end + end + end + + describe "#send_new_badge_notification (deprecated)" do + it "enqueues a new badge achievement job" do + assert_enqueued_with(job: Notifications::NewBadgeAchievementJob, args: [badge_achievement.id]) do + Notification.send_new_badge_notification(badge_achievement) + end + end + end + + describe "#send_new_badge_notification_without_delay (deprecated)" do + it "creates a notification" do + expect do + Notification.send_new_badge_notification_without_delay(badge_achievement) + end.to change(Notification, :count).by(1) + end + end end diff --git a/spec/requests/notifications_spec.rb b/spec/requests/notifications_spec.rb index 4ceab0595..834e1756a 100644 --- a/spec/requests/notifications_spec.rb +++ b/spec/requests/notifications_spec.rb @@ -221,7 +221,9 @@ RSpec.describe "NotificationsIndex", type: :request do sign_in user badge = create(:badge) badge_achievement = create(:badge_achievement, user: user, badge: badge) - Notification.send_new_badge_notification_without_delay(badge_achievement) + perform_enqueued_jobs do + Notification.send_new_badge_achievement_notification(badge_achievement) + end get "/notifications" end diff --git a/spec/services/notifications/new_badge_achievement/send_spec.rb b/spec/services/notifications/new_badge_achievement/send_spec.rb new file mode 100644 index 000000000..ada0922c0 --- /dev/null +++ b/spec/services/notifications/new_badge_achievement/send_spec.rb @@ -0,0 +1,48 @@ +require "rails_helper" + +RSpec.describe Notifications::NewBadgeAchievement::Send, type: :service do + let(:badge_achievement) { create(:badge_achievement) } + + def expected_json_data + { + user: Notifications.user_data(badge_achievement.user), + badge_achievement: { + badge_id: badge_achievement.badge_id, + rewarding_context_message: badge_achievement.rewarding_context_message, + badge: { + title: badge_achievement.badge.title, + description: badge_achievement.badge.description, + badge_image_url: badge_achievement.badge.badge_image_url + } + } + }.to_json + end + + it "creates a notification" do + expect do + described_class.call(badge_achievement) + end.to change(Notification, :count).by(1) + end + + it "creates a notification for the badge achievement user" do + notification = described_class.call(badge_achievement) + expect(notification.user).to eq(badge_achievement.user) + end + + it "creates a notification for the badge achievement" do + notification = described_class.call(badge_achievement) + expect(notification.notifiable_id).to eq(badge_achievement.id) + expect(notification.notifiable_type).to eq("BadgeAchievement") + end + + it "creates a notification with no action" do + notification = described_class.call(badge_achievement) + expect(notification.action).to be(nil) + end + + it "creates a notification with the proper json data" do + notification = described_class.call(badge_achievement) + json_data = notification.json_data.to_json + expect(JSON.parse(json_data)).to eq(JSON.parse(expected_json_data)) + end +end