Skip to content

Commit

Permalink
Merge pull request #377 from coopdevs/feature/scheduled-jobs
Browse files Browse the repository at this point in the history
Scheduled job to send push notifications for Post resource
  • Loading branch information
enricostano authored Sep 11, 2018
2 parents dbf1a65 + bcb8684 commit 22a26c9
Show file tree
Hide file tree
Showing 21 changed files with 322 additions and 87 deletions.
6 changes: 5 additions & 1 deletion app/jobs/create_push_notifications_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ def perform(event_id:)

raise 'A valid Event must be provided' unless event

::PushNotifications::Creator.new(event: event).create!
if event.post_id
::PushNotifications::Creator::Post.new(event: event).create!
else
raise "You need to define a PushNotifications::Creator class for this event type ##{event.id}"
end
end
end
11 changes: 11 additions & 0 deletions app/jobs/send_push_notifications_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class SendPushNotificationsJob < ActiveJob::Base
queue_as :cron

def perform
push_notifications = PushNotification.where(processed_at: nil).limit(100)

::PushNotifications::Broadcast.new(
push_notifications: push_notifications
).send_notifications
end
end
4 changes: 3 additions & 1 deletion app/models/push_notification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@ 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
validates :event, :device_token, :title, presence: true

delegate :token, to: :device_token
end
58 changes: 58 additions & 0 deletions app/services/push_notifications/broadcast.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
module PushNotifications
class Broadcast
class PostError < ::StandardError; end

def initialize(push_notifications:)
@push_notifications = push_notifications
end

# https://docs.expo.io/versions/latest/guides/push-notifications.html
def send_notifications
return unless push_notifications.any?

response = client.post(
uri.request_uri,
notifications.to_json,
headers
)

unless response.is_a? Net::HTTPOK
raise PostError, "HTTP response: #{response.code}, #{response.body}"
end

now = Time.now.utc
push_notifications.update_all(processed_at: now, updated_at: now)
end

private

attr_reader :push_notifications

def notifications
push_notifications.map do |push_notification|
{
to: push_notification.token,
title: push_notification.title,
body: push_notification.body,
data: push_notification.data
}
end
end

def client
https = Net::HTTP.new(uri.host, uri.port)
https.use_ssl = true
https
end

def uri
URI('https://exp.host/--/api/v2/push/send')
end

def headers
{
"Content-Type" => "application/json"
}
end
end
end
22 changes: 0 additions & 22 deletions app/services/push_notifications/creator.rb

This file was deleted.

42 changes: 42 additions & 0 deletions app/services/push_notifications/creator/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
module PushNotifications
module Creator
class Base
# 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,
title: title,
body: body,
data: data
)
end
end

private

attr_accessor :event

def title
raise 'implement the private method `title`'
end

def body
raise 'implement the private method `body`'
end

def data
raise 'implement the private method `data`'
end
end
end
end
25 changes: 25 additions & 0 deletions app/services/push_notifications/creator/post.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module PushNotifications
module Creator
class Post < Base
private

def title
event.post.title
end

def body
event.post.description&.truncate(20) || 'No description'
end

def data
if event.post.class.to_s == 'Offer'
{ url: Rails.application.routes.url_helpers.offer_path(event.post) }
elsif event.post.class.to_s == 'Inquiry'
{ url: Rails.application.routes.url_helpers.inquiry_path(event.post) }
else
{}
end
end
end
end
end
30 changes: 0 additions & 30 deletions app/services/push_notifications/expo_sender_service.rb

This file was deleted.

19 changes: 0 additions & 19 deletions app/services/push_notifications/post_broadcaster_service.rb

This file was deleted.

4 changes: 4 additions & 0 deletions config/schedule.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
send_push_notifications_job:
cron: '*/5 * * * *'
class: 'SendPushNotificationsJob'
queue: cron
9 changes: 9 additions & 0 deletions db/migrate/20180604145622_add_title_to_push_notification.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class AddTitleToPushNotification < ActiveRecord::Migration
def up
add_column :push_notifications, :title, :string, null: false, default: ''
end

def down
remove_column :push_notifications, :title
end
end
9 changes: 9 additions & 0 deletions db/migrate/20180828160700_add_body_to_push_notification.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class AddBodyToPushNotification < ActiveRecord::Migration
def up
add_column :push_notifications, :body, :string, null: false, default: ''
end

def down
remove_column :push_notifications, :body
end
end
9 changes: 9 additions & 0 deletions db/migrate/20180831161349_add_data_to_push_notification.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class AddDataToPushNotification < ActiveRecord::Migration
def up
add_column :push_notifications, :data, :json, null: false, default: '{}'
end

def down
remove_column :push_notifications, :data
end
end
13 changes: 8 additions & 5 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: 20180530180546) do
ActiveRecord::Schema.define(version: 20180831161349) do

# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
Expand Down Expand Up @@ -159,11 +159,14 @@
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.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
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "title", default: "", null: false
t.string "body", default: "", null: false
t.json "data", default: {}, null: false
end

create_table "transfers", force: :cascade do |t|
Expand Down
1 change: 1 addition & 0 deletions spec/fabricators/device_token_fabricator.rb
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
Fabricator(:device_token) do
token 'token'
end
2 changes: 2 additions & 0 deletions spec/fabricators/push_notification_fabricator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fabricator(:push_notification) do
end
4 changes: 2 additions & 2 deletions spec/jobs/create_push_notifications_job_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
let(:event_id) { event.id }

it 'calls the PushNotification creator' do
creator = instance_double(::PushNotifications::Creator)
expect(::PushNotifications::Creator).to receive(:new)
creator = instance_double(::PushNotifications::Creator::Post)
expect(::PushNotifications::Creator::Post).to receive(:new)
.with(event: event)
.and_return(creator)
expect(creator).to receive(:create!)
Expand Down
43 changes: 43 additions & 0 deletions spec/jobs/send_push_notifications_job_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
require 'spec_helper'

RSpec.describe SendPushNotificationsJob, type: :job do
describe '#perform' do
let(:user) { Fabricate(:user) }
let(:device_token) { Fabricate(:device_token, user: user) }
let(:post) { Fabricate(:post) }
let(:event_created) { Fabricate(:event, post: post, action: :created) }
let(:event_updated) { Fabricate(:event, post: post, action: :updated) }
let(:push_notification) do
Fabricate(
:push_notification,
event: event_created,
device_token: device_token,
title: 'A new Post hase been created.'
)
end
let(:processed_push_notification) do
Fabricate(
:push_notification,
event: event_updated,
device_token: device_token,
title: 'A new Post hase been created.',
processed_at: Time.zone.now
)
end

before do
push_notification
processed_push_notification
end

it 'calls Broadcast to send the notifications' do
broadcast = instance_double(::PushNotifications::Broadcast)
expect(::PushNotifications::Broadcast).to receive(:new)
.with(push_notifications: [push_notification])
.and_return(broadcast)
expect(broadcast).to receive(:send_notifications)

described_class.new.perform
end
end
end
10 changes: 10 additions & 0 deletions spec/models/push_notification_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
describe 'Validations' do
it { is_expected.to validate_presence_of(:event) }
it { is_expected.to validate_presence_of(:device_token) }
it { is_expected.to validate_presence_of(:title) }
end

describe 'Associations' do
Expand All @@ -13,4 +14,13 @@
it { is_expected.to have_db_column(:event_id) }
it { is_expected.to have_db_column(:device_token_id) }
end

describe "#token" do
let(:device_token) { Fabricate.build(:device_token, token: 'token') }
let(:push_notification) { described_class.new(device_token: device_token) }

it 'returns the associated DeviceToken\'s token' do
expect(push_notification.token).to eq('token')
end
end
end
Loading

0 comments on commit 22a26c9

Please # to comment.