Merge pull request #367 from coopdevs/feature/create-push-notifications-job
Push notifications background job for Post
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
class CreatePushNotificationsJob < ActiveJob::Base
|
||||
queue_as :default
|
||||
|
||||
def perform(event_id:)
|
||||
event = ::Event.find_by_id(event_id)
|
||||
|
||||
raise 'A valid Event must be provided' unless event
|
||||
|
||||
::PushNotifications::Creator.new(event: event).create!
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,6 @@
|
||||
class PushNotification < ActiveRecord::Base
|
||||
belongs_to :event, foreign_key: 'event_id'
|
||||
belongs_to :device_token, foreign_key: 'device_token_id'
|
||||
|
||||
validates :event, :device_token, presence: true
|
||||
end
|
||||
@@ -1,11 +0,0 @@
|
||||
module PushNotifications
|
||||
class PostNotification
|
||||
def title
|
||||
"Notification title"
|
||||
end
|
||||
|
||||
def body
|
||||
"Notification body"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -10,6 +10,7 @@ module Persister
|
||||
::ActiveRecord::Base.transaction do
|
||||
post.save!
|
||||
create_save_event!
|
||||
enqueue_push_notification_job!
|
||||
post
|
||||
end
|
||||
rescue ActiveRecord::RecordInvalid => _exception
|
||||
@@ -20,6 +21,7 @@ module Persister
|
||||
::ActiveRecord::Base.transaction do
|
||||
post.update_attributes!(params)
|
||||
create_update_event!
|
||||
enqueue_push_notification_job!
|
||||
post
|
||||
end
|
||||
rescue ActiveRecord::RecordInvalid => _exception
|
||||
@@ -28,12 +30,18 @@ module Persister
|
||||
|
||||
private
|
||||
|
||||
attr_accessor :event
|
||||
|
||||
def create_save_event!
|
||||
::Event.create! action: :created, post: post
|
||||
@event = ::Event.create! action: :created, post: post
|
||||
end
|
||||
|
||||
def create_update_event!
|
||||
::Event.create! action: :updated, post: post
|
||||
@event = ::Event.create! action: :updated, post: post
|
||||
end
|
||||
|
||||
def enqueue_push_notification_job!
|
||||
CreatePushNotificationsJob.perform_later(event_id: event.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
module PushNotifications
|
||||
class Creator
|
||||
# Given an Event it will create as many PushNotification resources
|
||||
# necessary as the resource associated to the Event will require.
|
||||
#
|
||||
# @param [Hash] event: <Event>
|
||||
def initialize(event:)
|
||||
@event = event
|
||||
end
|
||||
|
||||
def create!
|
||||
event_notifier = EventNotifierFactory.new(event: event).build
|
||||
event_notifier.device_tokens.each do |device_token|
|
||||
PushNotification.create!(event: event, device_token: device_token)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_accessor :event
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,26 @@
|
||||
module PushNotifications
|
||||
module EventNotifier
|
||||
class Post
|
||||
def initialize(event:)
|
||||
@event = event
|
||||
@post = event.post
|
||||
end
|
||||
|
||||
# Conditions for Post:
|
||||
#
|
||||
# We need to notify all the users that:
|
||||
# - are members of the Post's organization
|
||||
# - have a DeviceToken associated
|
||||
#
|
||||
# @return [<DeviceToken>]
|
||||
def device_tokens
|
||||
organization = post.organization
|
||||
DeviceToken.where(user_id: organization.user_ids)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_accessor :event, :post
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
module PushNotifications
|
||||
class EventNotifierFactory
|
||||
def initialize(event:)
|
||||
@event = event
|
||||
end
|
||||
|
||||
def build
|
||||
return EventNotifier::Post.new(event: event) if event.post_id
|
||||
|
||||
raise 'The resource associated to the Event is not supported'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_accessor :event
|
||||
end
|
||||
end
|
||||
@@ -44,4 +44,7 @@ Rails.application.configure do
|
||||
|
||||
# Avoid seeing all that stuff in tests
|
||||
config.log_level = :warn
|
||||
|
||||
# ActiveJob configuration
|
||||
config.active_job.queue_adapter = :test
|
||||
end
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
class CreatePushNotifications < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :push_notifications do |t|
|
||||
t.references :event, null: false
|
||||
t.references :device_token, null: false
|
||||
t.datetime :processed_at
|
||||
|
||||
t.timestamps null: false
|
||||
end
|
||||
|
||||
add_foreign_key :push_notifications, :events
|
||||
add_foreign_key :push_notifications, :device_tokens
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,11 @@
|
||||
class ChangeIndexOnEvents < ActiveRecord::Migration
|
||||
def change
|
||||
remove_index :events, :post_id
|
||||
remove_index :events, :member_id
|
||||
remove_index :events, :transfer_id
|
||||
|
||||
add_index :events, :post_id, where: 'post_id IS NOT NULL'
|
||||
add_index :events, :member_id, where: 'member_id IS NOT NULL'
|
||||
add_index :events, :transfer_id, where: 'transfer_id IS NOT NULL'
|
||||
end
|
||||
end
|
||||
+14
-4
@@ -11,7 +11,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20180525141138) do
|
||||
ActiveRecord::Schema.define(version: 20180529144243) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
@@ -85,9 +85,9 @@ ActiveRecord::Schema.define(version: 20180525141138) do
|
||||
t.datetime "updated_at"
|
||||
end
|
||||
|
||||
add_index "events", ["member_id"], name: "index_events_on_member_id", unique: true, where: "(member_id IS NOT NULL)", using: :btree
|
||||
add_index "events", ["post_id"], name: "index_events_on_post_id", unique: true, where: "(post_id IS NOT NULL)", using: :btree
|
||||
add_index "events", ["transfer_id"], name: "index_events_on_transfer_id", unique: true, where: "(transfer_id IS NOT NULL)", using: :btree
|
||||
add_index "events", ["member_id"], name: "index_events_on_member_id", where: "(member_id IS NOT NULL)", using: :btree
|
||||
add_index "events", ["post_id"], name: "index_events_on_post_id", where: "(post_id IS NOT NULL)", using: :btree
|
||||
add_index "events", ["transfer_id"], name: "index_events_on_transfer_id", where: "(transfer_id IS NOT NULL)", using: :btree
|
||||
|
||||
create_table "members", force: :cascade do |t|
|
||||
t.integer "user_id"
|
||||
@@ -158,6 +158,14 @@ ActiveRecord::Schema.define(version: 20180525141138) do
|
||||
add_index "posts", ["tags"], name: "index_posts_on_tags", using: :gin
|
||||
add_index "posts", ["user_id"], name: "index_posts_on_user_id", using: :btree
|
||||
|
||||
create_table "push_notifications", force: :cascade do |t|
|
||||
t.integer "event_id", null: false
|
||||
t.integer "device_token_id", null: false
|
||||
t.datetime "processed_at"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
create_table "transfers", force: :cascade do |t|
|
||||
t.integer "post_id"
|
||||
t.text "reason"
|
||||
@@ -215,4 +223,6 @@ ActiveRecord::Schema.define(version: 20180525141138) do
|
||||
add_foreign_key "events", "members", name: "events_member_id_fkey"
|
||||
add_foreign_key "events", "posts", name: "events_post_id_fkey"
|
||||
add_foreign_key "events", "transfers", name: "events_transfer_id_fkey"
|
||||
add_foreign_key "push_notifications", "device_tokens"
|
||||
add_foreign_key "push_notifications", "events"
|
||||
end
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
Fabricator(:device_token) do
|
||||
end
|
||||
@@ -0,0 +1,2 @@
|
||||
Fabricator(:event) do
|
||||
end
|
||||
@@ -0,0 +1,31 @@
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe CreatePushNotificationsJob, type: :job do
|
||||
describe '#perform' do
|
||||
context 'with an Event that doesn\'t exist' do
|
||||
let(:event_id) { nil }
|
||||
|
||||
it 'raises an error' do
|
||||
expect {
|
||||
described_class.new.perform(event_id: event_id)
|
||||
}.to raise_error 'A valid Event must be provided'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an Event that does exist' do
|
||||
let(:post) { Fabricate(:post) }
|
||||
let(:event) { Fabricate(:event, post: post, action: :created) }
|
||||
let(:event_id) { event.id }
|
||||
|
||||
it 'calls the PushNotification creator' do
|
||||
creator = instance_double(::PushNotifications::Creator)
|
||||
expect(::PushNotifications::Creator).to receive(:new)
|
||||
.with(event: event)
|
||||
.and_return(creator)
|
||||
expect(creator).to receive(:create!)
|
||||
|
||||
described_class.new.perform(event_id: event_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,16 @@
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe PushNotification do
|
||||
describe 'Validations' do
|
||||
it { is_expected.to validate_presence_of(:event) }
|
||||
it { is_expected.to validate_presence_of(:device_token) }
|
||||
end
|
||||
|
||||
describe 'Associations' do
|
||||
it { is_expected.to belong_to(:event).with_foreign_key('event_id') }
|
||||
it { is_expected.to belong_to(:device_token).with_foreign_key('device_token_id') }
|
||||
|
||||
it { is_expected.to have_db_column(:event_id) }
|
||||
it { is_expected.to have_db_column(:device_token_id) }
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Persister::PostPersister do
|
||||
RSpec.describe Persister::PostPersister do
|
||||
let(:organization) { Fabricate(:organization) }
|
||||
let(:user) { Fabricate(:user) }
|
||||
let(:category) { Fabricate(:category) }
|
||||
@@ -14,30 +14,57 @@ describe Persister::PostPersister do
|
||||
)
|
||||
end
|
||||
let(:persister) { ::Persister::PostPersister.new(post) }
|
||||
let(:event) { Fabricate.build(:event, id: 27) }
|
||||
|
||||
describe '#save' do
|
||||
before { persister.save }
|
||||
|
||||
it 'saves the post' do
|
||||
persister.save
|
||||
|
||||
expect(post).to be_persisted
|
||||
end
|
||||
|
||||
# TODO: write better expectation
|
||||
it 'creates an event' do
|
||||
expect(Event.where(post_id: post.id).first.action).to eq('created')
|
||||
expect(::Event).to receive(:create!).with(action: :created, post: post).and_return(event)
|
||||
|
||||
persister.save
|
||||
end
|
||||
|
||||
context 'background job' do
|
||||
before do
|
||||
allow(::Event).to receive(:create!).and_return(event)
|
||||
end
|
||||
|
||||
it 'enqueues a CreatePushNotificationsJob background job' do
|
||||
expect {
|
||||
persister.save
|
||||
}.to enqueue_job(CreatePushNotificationsJob).with(event_id: 27)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#update_attributes' do
|
||||
before { persister.update_attributes(title: 'New title') }
|
||||
|
||||
it 'updates the resource attributes' do
|
||||
persister.update_attributes(title: 'New title')
|
||||
|
||||
expect(post.title).to eq('New title')
|
||||
end
|
||||
|
||||
# TODO: write better expectation
|
||||
it 'creates an event' do
|
||||
expect(Event.where(post_id: post.id).first.action).to eq('updated')
|
||||
expect(::Event).to receive(:create!).with(action: :updated, post: post).and_return(event)
|
||||
|
||||
persister.update_attributes(title: 'New title')
|
||||
end
|
||||
|
||||
context 'background job' do
|
||||
before do
|
||||
allow(::Event).to receive(:create!).and_return(event)
|
||||
end
|
||||
|
||||
it 'enqueues a CreatePushNotificationsJob background job' do
|
||||
expect {
|
||||
persister.update_attributes(title: 'New title')
|
||||
}.to enqueue_job(CreatePushNotificationsJob).with(event_id: 27)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe PushNotifications::Creator do
|
||||
let(:user) { Fabricate(:user) }
|
||||
let!(:device_token) { Fabricate(:device_token, user: user, token: 'aloha') }
|
||||
let(:organization) { Fabricate(:organization) }
|
||||
let(:post) { Fabricate(:post, organization: organization, user: user) }
|
||||
let(:event) { Fabricate.build(:event, post: post, action: :created) }
|
||||
let(:creator) { described_class.new(event: event) }
|
||||
|
||||
before do
|
||||
organization.members.create(user: user)
|
||||
end
|
||||
|
||||
describe '#create!' do
|
||||
it 'creates as many PushNotification resources as needed' do
|
||||
expect(PushNotification).to receive(:create!).with(
|
||||
event: event,
|
||||
device_token: device_token
|
||||
).once
|
||||
|
||||
creator.create!
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,37 @@
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe ::PushNotifications::EventNotifier::Post do
|
||||
let(:user) { Fabricate.build(:user) }
|
||||
let(:organization) { Fabricate(:organization) }
|
||||
let(:post) { Fabricate(:post, organization: organization, user: user) }
|
||||
let(:event) { Fabricate.build(:event, post: post, action: :created) }
|
||||
|
||||
describe '#users' do
|
||||
context 'when a user pertains to the Post\'s organization' do
|
||||
before { organization.members.create(user: user) }
|
||||
|
||||
context 'and the user has a DeviceToken associated' do
|
||||
let!(:device_token) { Fabricate(:device_token, user: user, token: 'aloha') }
|
||||
|
||||
it 'returns the device token associated with the user' do
|
||||
expect(described_class.new(event: event).device_tokens).to eq([device_token])
|
||||
end
|
||||
end
|
||||
|
||||
context 'but the user has no DeviceToken associated' do
|
||||
it 'doesn\'t return the user' do
|
||||
expect(described_class.new(event: event).device_tokens).to eq([])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a user doesn\'t pertain to the Post\'s organization' do
|
||||
let(:other_user) { Fabricate(:user) }
|
||||
let!(:device_token) { Fabricate(:device_token, user: other_user, token: 'WAT') }
|
||||
|
||||
it 'doesn\'t return the user' do
|
||||
expect(described_class.new(event: event).device_tokens).to_not include(device_token)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,31 @@
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe PushNotifications::EventNotifierFactory do
|
||||
describe '#build' do
|
||||
let(:user) { Fabricate.build(:user) }
|
||||
let(:organization) { Fabricate(:organization) }
|
||||
let(:factory) { described_class.new(event: event) }
|
||||
|
||||
context 'when the given Event is associated to a Post' do
|
||||
let(:post) { Fabricate(:post, organization: organization, user: user) }
|
||||
let(:event) { Fabricate.build(:event, post: post, action: :created) }
|
||||
|
||||
it 'returns a Post notifier' do
|
||||
notifier = instance_double(::PushNotifications::EventNotifier::Post)
|
||||
allow(::PushNotifications::EventNotifier::Post).to receive(:new).and_return(notifier)
|
||||
|
||||
expect(factory.build).to be(notifier)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the given Event is associated to a resource not supported' do
|
||||
let(:event) { Fabricate.build(:event, action: :created) }
|
||||
|
||||
it 'raises an error' do
|
||||
expect {
|
||||
factory.build
|
||||
}.to raise_error('The resource associated to the Event is not supported')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user