Skip to content

Push notifications background job for Post #367

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
May 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions app/jobs/create_push_notifications_job.rb
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions app/models/push_notification.rb
Original file line number Diff line number Diff line change
@@ -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
11 changes: 0 additions & 11 deletions app/models/push_notifications/post_notification.rb

This file was deleted.

12 changes: 10 additions & 2 deletions app/services/persister/post_persister.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ def save
::ActiveRecord::Base.transaction do
post.save!
create_save_event!
enqueue_push_notification_job!
post
end
rescue ActiveRecord::RecordInvalid => _exception
Expand All @@ -20,6 +21,7 @@ def update_attributes(params)
::ActiveRecord::Base.transaction do
post.update_attributes!(params)
create_update_event!
enqueue_push_notification_job!
post
end
rescue ActiveRecord::RecordInvalid => _exception
Expand All @@ -28,12 +30,18 @@ def update_attributes(params)

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
22 changes: 22 additions & 0 deletions app/services/push_notifications/creator.rb
Original file line number Diff line number Diff line change
@@ -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
26 changes: 26 additions & 0 deletions app/services/push_notifications/event_notifier/post.rb
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions app/services/push_notifications/event_notifier_factory.rb
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions config/environments/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,7 @@

# Avoid seeing all that stuff in tests
config.log_level = :warn

# ActiveJob configuration
config.active_job.queue_adapter = :test
end
14 changes: 14 additions & 0 deletions db/migrate/20180524143938_create_push_notifications.rb
Original file line number Diff line number Diff line change
@@ -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
11 changes: 11 additions & 0 deletions db/migrate/20180529144243_change_index_on_events.rb
Original file line number Diff line number Diff line change
@@ -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
18 changes: 14 additions & 4 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -85,9 +85,9 @@
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"
Expand Down Expand Up @@ -158,6 +158,14 @@
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"
Expand Down Expand Up @@ -215,4 +223,6 @@
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
2 changes: 2 additions & 0 deletions spec/fabricators/device_token_fabricator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fabricator(:device_token) do
end
2 changes: 2 additions & 0 deletions spec/fabricators/event_fabricator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fabricator(:event) do
end
31 changes: 31 additions & 0 deletions spec/jobs/create_push_notifications_job_spec.rb
Original file line number Diff line number Diff line change
@@ -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
16 changes: 16 additions & 0 deletions spec/models/push_notification_spec.rb
Original file line number Diff line number Diff line change
@@ -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
45 changes: 36 additions & 9 deletions spec/services/persister/post_persister_spec.rb
Original file line number Diff line number Diff line change
@@ -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) }
Expand All @@ -14,30 +14,57 @@
)
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
25 changes: 25 additions & 0 deletions spec/services/push_notifications/creator_spec.rb
Original file line number Diff line number Diff line change
@@ -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
Loading