Skip to content
New issue

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

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

Already on GitHub? # to your account

Push notifications background job for Post #367

Merged
merged 9 commits into from
May 29, 2018
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