Skip to content

Commit ec0f47d

Browse files
authored
Merge pull request #367 from coopdevs/feature/create-push-notifications-job
Push notifications background job for Post
2 parents b31466a + a2d4b8c commit ec0f47d

19 files changed

+314
-26
lines changed
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
class CreatePushNotificationsJob < ActiveJob::Base
2+
queue_as :default
3+
4+
def perform(event_id:)
5+
event = ::Event.find_by_id(event_id)
6+
7+
raise 'A valid Event must be provided' unless event
8+
9+
::PushNotifications::Creator.new(event: event).create!
10+
end
11+
end

app/models/push_notification.rb

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class PushNotification < ActiveRecord::Base
2+
belongs_to :event, foreign_key: 'event_id'
3+
belongs_to :device_token, foreign_key: 'device_token_id'
4+
5+
validates :event, :device_token, presence: true
6+
end

app/models/push_notifications/post_notification.rb

-11
This file was deleted.

app/services/persister/post_persister.rb

+10-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ def save
1010
::ActiveRecord::Base.transaction do
1111
post.save!
1212
create_save_event!
13+
enqueue_push_notification_job!
1314
post
1415
end
1516
rescue ActiveRecord::RecordInvalid => _exception
@@ -20,6 +21,7 @@ def update_attributes(params)
2021
::ActiveRecord::Base.transaction do
2122
post.update_attributes!(params)
2223
create_update_event!
24+
enqueue_push_notification_job!
2325
post
2426
end
2527
rescue ActiveRecord::RecordInvalid => _exception
@@ -28,12 +30,18 @@ def update_attributes(params)
2830

2931
private
3032

33+
attr_accessor :event
34+
3135
def create_save_event!
32-
::Event.create! action: :created, post: post
36+
@event = ::Event.create! action: :created, post: post
3337
end
3438

3539
def create_update_event!
36-
::Event.create! action: :updated, post: post
40+
@event = ::Event.create! action: :updated, post: post
41+
end
42+
43+
def enqueue_push_notification_job!
44+
CreatePushNotificationsJob.perform_later(event_id: event.id)
3745
end
3846
end
3947
end
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
module PushNotifications
2+
class Creator
3+
# Given an Event it will create as many PushNotification resources
4+
# necessary as the resource associated to the Event will require.
5+
#
6+
# @param [Hash] event: <Event>
7+
def initialize(event:)
8+
@event = event
9+
end
10+
11+
def create!
12+
event_notifier = EventNotifierFactory.new(event: event).build
13+
event_notifier.device_tokens.each do |device_token|
14+
PushNotification.create!(event: event, device_token: device_token)
15+
end
16+
end
17+
18+
private
19+
20+
attr_accessor :event
21+
end
22+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
module PushNotifications
2+
module EventNotifier
3+
class Post
4+
def initialize(event:)
5+
@event = event
6+
@post = event.post
7+
end
8+
9+
# Conditions for Post:
10+
#
11+
# We need to notify all the users that:
12+
# - are members of the Post's organization
13+
# - have a DeviceToken associated
14+
#
15+
# @return [<DeviceToken>]
16+
def device_tokens
17+
organization = post.organization
18+
DeviceToken.where(user_id: organization.user_ids)
19+
end
20+
21+
private
22+
23+
attr_accessor :event, :post
24+
end
25+
end
26+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
module PushNotifications
2+
class EventNotifierFactory
3+
def initialize(event:)
4+
@event = event
5+
end
6+
7+
def build
8+
return EventNotifier::Post.new(event: event) if event.post_id
9+
10+
raise 'The resource associated to the Event is not supported'
11+
end
12+
13+
private
14+
15+
attr_accessor :event
16+
end
17+
end

config/environments/test.rb

+3
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,7 @@
4444

4545
# Avoid seeing all that stuff in tests
4646
config.log_level = :warn
47+
48+
# ActiveJob configuration
49+
config.active_job.queue_adapter = :test
4750
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
class CreatePushNotifications < ActiveRecord::Migration
2+
def change
3+
create_table :push_notifications do |t|
4+
t.references :event, null: false
5+
t.references :device_token, null: false
6+
t.datetime :processed_at
7+
8+
t.timestamps null: false
9+
end
10+
11+
add_foreign_key :push_notifications, :events
12+
add_foreign_key :push_notifications, :device_tokens
13+
end
14+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
class ChangeIndexOnEvents < ActiveRecord::Migration
2+
def change
3+
remove_index :events, :post_id
4+
remove_index :events, :member_id
5+
remove_index :events, :transfer_id
6+
7+
add_index :events, :post_id, where: 'post_id IS NOT NULL'
8+
add_index :events, :member_id, where: 'member_id IS NOT NULL'
9+
add_index :events, :transfer_id, where: 'transfer_id IS NOT NULL'
10+
end
11+
end

db/schema.rb

+14-4
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
#
1212
# It's strongly recommended that you check this file into your version control system.
1313

14-
ActiveRecord::Schema.define(version: 20180525141138) do
14+
ActiveRecord::Schema.define(version: 20180529144243) do
1515

1616
# These are extensions that must be enabled in order to support this database
1717
enable_extension "plpgsql"
@@ -85,9 +85,9 @@
8585
t.datetime "updated_at"
8686
end
8787

88-
add_index "events", ["member_id"], name: "index_events_on_member_id", unique: true, where: "(member_id IS NOT NULL)", using: :btree
89-
add_index "events", ["post_id"], name: "index_events_on_post_id", unique: true, where: "(post_id IS NOT NULL)", using: :btree
90-
add_index "events", ["transfer_id"], name: "index_events_on_transfer_id", unique: true, where: "(transfer_id IS NOT NULL)", using: :btree
88+
add_index "events", ["member_id"], name: "index_events_on_member_id", where: "(member_id IS NOT NULL)", using: :btree
89+
add_index "events", ["post_id"], name: "index_events_on_post_id", where: "(post_id IS NOT NULL)", using: :btree
90+
add_index "events", ["transfer_id"], name: "index_events_on_transfer_id", where: "(transfer_id IS NOT NULL)", using: :btree
9191

9292
create_table "members", force: :cascade do |t|
9393
t.integer "user_id"
@@ -158,6 +158,14 @@
158158
add_index "posts", ["tags"], name: "index_posts_on_tags", using: :gin
159159
add_index "posts", ["user_id"], name: "index_posts_on_user_id", using: :btree
160160

161+
create_table "push_notifications", force: :cascade do |t|
162+
t.integer "event_id", null: false
163+
t.integer "device_token_id", null: false
164+
t.datetime "processed_at"
165+
t.datetime "created_at", null: false
166+
t.datetime "updated_at", null: false
167+
end
168+
161169
create_table "transfers", force: :cascade do |t|
162170
t.integer "post_id"
163171
t.text "reason"
@@ -215,4 +223,6 @@
215223
add_foreign_key "events", "members", name: "events_member_id_fkey"
216224
add_foreign_key "events", "posts", name: "events_post_id_fkey"
217225
add_foreign_key "events", "transfers", name: "events_transfer_id_fkey"
226+
add_foreign_key "push_notifications", "device_tokens"
227+
add_foreign_key "push_notifications", "events"
218228
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fabricator(:device_token) do
2+
end

spec/fabricators/event_fabricator.rb

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fabricator(:event) do
2+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
require 'spec_helper'
2+
3+
RSpec.describe CreatePushNotificationsJob, type: :job do
4+
describe '#perform' do
5+
context 'with an Event that doesn\'t exist' do
6+
let(:event_id) { nil }
7+
8+
it 'raises an error' do
9+
expect {
10+
described_class.new.perform(event_id: event_id)
11+
}.to raise_error 'A valid Event must be provided'
12+
end
13+
end
14+
15+
context 'with an Event that does exist' do
16+
let(:post) { Fabricate(:post) }
17+
let(:event) { Fabricate(:event, post: post, action: :created) }
18+
let(:event_id) { event.id }
19+
20+
it 'calls the PushNotification creator' do
21+
creator = instance_double(::PushNotifications::Creator)
22+
expect(::PushNotifications::Creator).to receive(:new)
23+
.with(event: event)
24+
.and_return(creator)
25+
expect(creator).to receive(:create!)
26+
27+
described_class.new.perform(event_id: event_id)
28+
end
29+
end
30+
end
31+
end

spec/models/push_notification_spec.rb

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
require 'spec_helper'
2+
3+
RSpec.describe PushNotification do
4+
describe 'Validations' do
5+
it { is_expected.to validate_presence_of(:event) }
6+
it { is_expected.to validate_presence_of(:device_token) }
7+
end
8+
9+
describe 'Associations' do
10+
it { is_expected.to belong_to(:event).with_foreign_key('event_id') }
11+
it { is_expected.to belong_to(:device_token).with_foreign_key('device_token_id') }
12+
13+
it { is_expected.to have_db_column(:event_id) }
14+
it { is_expected.to have_db_column(:device_token_id) }
15+
end
16+
end
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
require 'spec_helper'
22

3-
describe Persister::PostPersister do
3+
RSpec.describe Persister::PostPersister do
44
let(:organization) { Fabricate(:organization) }
55
let(:user) { Fabricate(:user) }
66
let(:category) { Fabricate(:category) }
@@ -14,30 +14,57 @@
1414
)
1515
end
1616
let(:persister) { ::Persister::PostPersister.new(post) }
17+
let(:event) { Fabricate.build(:event, id: 27) }
1718

1819
describe '#save' do
19-
before { persister.save }
20-
2120
it 'saves the post' do
21+
persister.save
22+
2223
expect(post).to be_persisted
2324
end
2425

25-
# TODO: write better expectation
2626
it 'creates an event' do
27-
expect(Event.where(post_id: post.id).first.action).to eq('created')
27+
expect(::Event).to receive(:create!).with(action: :created, post: post).and_return(event)
28+
29+
persister.save
30+
end
31+
32+
context 'background job' do
33+
before do
34+
allow(::Event).to receive(:create!).and_return(event)
35+
end
36+
37+
it 'enqueues a CreatePushNotificationsJob background job' do
38+
expect {
39+
persister.save
40+
}.to enqueue_job(CreatePushNotificationsJob).with(event_id: 27)
41+
end
2842
end
2943
end
3044

3145
describe '#update_attributes' do
32-
before { persister.update_attributes(title: 'New title') }
33-
3446
it 'updates the resource attributes' do
47+
persister.update_attributes(title: 'New title')
48+
3549
expect(post.title).to eq('New title')
3650
end
3751

38-
# TODO: write better expectation
3952
it 'creates an event' do
40-
expect(Event.where(post_id: post.id).first.action).to eq('updated')
53+
expect(::Event).to receive(:create!).with(action: :updated, post: post).and_return(event)
54+
55+
persister.update_attributes(title: 'New title')
56+
end
57+
58+
context 'background job' do
59+
before do
60+
allow(::Event).to receive(:create!).and_return(event)
61+
end
62+
63+
it 'enqueues a CreatePushNotificationsJob background job' do
64+
expect {
65+
persister.update_attributes(title: 'New title')
66+
}.to enqueue_job(CreatePushNotificationsJob).with(event_id: 27)
67+
end
4168
end
4269
end
4370
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
require 'spec_helper'
2+
3+
RSpec.describe PushNotifications::Creator do
4+
let(:user) { Fabricate(:user) }
5+
let!(:device_token) { Fabricate(:device_token, user: user, token: 'aloha') }
6+
let(:organization) { Fabricate(:organization) }
7+
let(:post) { Fabricate(:post, organization: organization, user: user) }
8+
let(:event) { Fabricate.build(:event, post: post, action: :created) }
9+
let(:creator) { described_class.new(event: event) }
10+
11+
before do
12+
organization.members.create(user: user)
13+
end
14+
15+
describe '#create!' do
16+
it 'creates as many PushNotification resources as needed' do
17+
expect(PushNotification).to receive(:create!).with(
18+
event: event,
19+
device_token: device_token
20+
).once
21+
22+
creator.create!
23+
end
24+
end
25+
end

0 commit comments

Comments
 (0)