dispatch-rider 1.6.2 → 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/README.md +55 -0
- data/dispatch-rider.gemspec +3 -0
- data/lib/dispatch-rider.rb +1 -0
- data/lib/dispatch-rider/publisher/base.rb +14 -1
- data/lib/dispatch-rider/scheduled_job.rb +59 -0
- data/lib/dispatch-rider/scheduled_job/migration.rb +15 -0
- data/lib/dispatch-rider/version.rb +1 -1
- data/spec/lib/dispatch-rider/scheduled_job_spec.rb +73 -0
- data/spec/spec_helper.rb +18 -0
- metadata +34 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ceff36f00f29c392c12751a5b9be98ded76af731
|
4
|
+
data.tar.gz: 4252fb5a29899475e8dd465cd9d9073a81c8bd4e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3f1f68c73b69964a68b0222fc02319e0f954d380af74a255d5eb69c15ca48c3c6374a0d14f29a81f41f7be664edf43cf785f223d8b2f4b1b0f853246242011bb
|
7
|
+
data.tar.gz: cf671844773968e6930f9e2ff3245903e658c04bbf40f2c41eac2a01b7ca6b7f2ce8c4dad9c6a77042163897800ec777c2ec4620c302303b7814788e0e7c179c
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -448,6 +448,61 @@ DispatchRider.config do |config|
|
|
448
448
|
config.error_handler = DispatchRider::AirbrakeErrorHandler
|
449
449
|
end
|
450
450
|
```
|
451
|
+
## Scheduling Job
|
452
|
+
|
453
|
+
Scheduled jobs currently require `ActiveRecord`. Support for other ORM will be added in the future depending on demand.
|
454
|
+
|
455
|
+
1. Add and run the DB migration below:
|
456
|
+
```ruby
|
457
|
+
class CreateScheduledJobsTable < ActiveRecord::Migration
|
458
|
+
include DispatchRider::ScheduledJob::Migration
|
459
|
+
|
460
|
+
def change
|
461
|
+
create_scheduled_jobs_table
|
462
|
+
end
|
463
|
+
end
|
464
|
+
```
|
465
|
+
|
466
|
+
2. Schedule jobs:
|
467
|
+
```ruby
|
468
|
+
# Use `#publish_later`
|
469
|
+
class NewsPublisher < DispatchRider::Publisher::Base
|
470
|
+
destinations :sns_message_queue
|
471
|
+
subject "read_news"
|
472
|
+
|
473
|
+
def self.midnight_publish(news)
|
474
|
+
new.publish_later("headlines" => news.headlines, at: Date.tomorrow.midnight)
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
# Or create a scheduled job manually
|
479
|
+
DispatchRider::ScheduledJob.create! scheduled_at: Date.tomorrow.midnight,
|
480
|
+
destinations: [:sns_message_queue],
|
481
|
+
message: {
|
482
|
+
subject: "read_news",
|
483
|
+
body: { "headlines" => news.headlines }
|
484
|
+
}
|
485
|
+
```
|
486
|
+
|
487
|
+
3. Run scheduled publishing.
|
488
|
+
```ruby
|
489
|
+
# Run once
|
490
|
+
|
491
|
+
# Ideally run on a cron. Where the cron is responsible for the publishing
|
492
|
+
# frequency. Any jobs due at the time this is run by the cron will be
|
493
|
+
# published.
|
494
|
+
DispatchRider::ScheduledJob.publish_due_jobs
|
495
|
+
|
496
|
+
# Loop. Warning: Loops are blocking. Run this on a separate thread if it's not
|
497
|
+
# the sole purpose of the app.
|
498
|
+
|
499
|
+
# publish every minute
|
500
|
+
DispatchRider::ScheduledJob.publish_due_jobs every: 1.minute
|
501
|
+
|
502
|
+
# publish every half an hour
|
503
|
+
DispatchRider::ScheduledJob.publish_due_jobs every: 30.minutes
|
504
|
+
```
|
505
|
+
|
451
506
|
## Deployment
|
452
507
|
|
453
508
|
In order to deploy a new version of the gem into the wild ...
|
data/dispatch-rider.gemspec
CHANGED
@@ -41,6 +41,9 @@ Gem::Specification.new do |gem|
|
|
41
41
|
|
42
42
|
gem.add_runtime_dependency 'activesupport', ">= 3.2.0"
|
43
43
|
gem.add_runtime_dependency 'activemodel', ">= 3.2.0"
|
44
|
+
gem.add_runtime_dependency "activerecord", ">= 3.2.0"
|
44
45
|
gem.add_runtime_dependency 'daemons', "~> 1.2"
|
45
46
|
gem.add_runtime_dependency 'retries', "~> 0.0", ">= 0.0.5"
|
47
|
+
|
48
|
+
gem.add_development_dependency "sqlite3"
|
46
49
|
end
|
data/lib/dispatch-rider.rb
CHANGED
@@ -30,10 +30,19 @@ module DispatchRider
|
|
30
30
|
|
31
31
|
# @param [Hash] body
|
32
32
|
def publish(body)
|
33
|
-
|
33
|
+
validate_body(body)
|
34
34
|
publisher.publish(destinations: destinations, message: { subject: subject, body: body })
|
35
35
|
end
|
36
36
|
|
37
|
+
# @param [Hash] body
|
38
|
+
# @param [Time] at
|
39
|
+
def publish_later(body, at:)
|
40
|
+
validate_body(body)
|
41
|
+
DispatchRider::ScheduledJob.create! scheduled_at: at,
|
42
|
+
destinations: destinations,
|
43
|
+
message: { subject: subject, body: body }
|
44
|
+
end
|
45
|
+
|
37
46
|
private
|
38
47
|
|
39
48
|
def publisher
|
@@ -47,5 +56,9 @@ module DispatchRider
|
|
47
56
|
def subject
|
48
57
|
self.class.instance_variable_get(:@subject)
|
49
58
|
end
|
59
|
+
|
60
|
+
def validate_body(body)
|
61
|
+
raise ArgumentError, 'body should be a hash' unless body.is_a?(Hash)
|
62
|
+
end
|
50
63
|
end
|
51
64
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "active_record"
|
2
|
+
|
3
|
+
# @note: Later this could be pulled out to its own gem and included depending on what ORM the user
|
4
|
+
# prefers. Something like:
|
5
|
+
# @example
|
6
|
+
# gem "dispatch_rider-active_record"
|
7
|
+
# gem "dispatch_rider-rom" # Ruby Object Mapper
|
8
|
+
# gem "dispatch_rider-mongo_mapper"
|
9
|
+
# gem "dispatch_rider-ohm"
|
10
|
+
module DispatchRider
|
11
|
+
class ScheduledJob < ActiveRecord::Base
|
12
|
+
class << self
|
13
|
+
def publisher
|
14
|
+
@publisher ||= Publisher.new
|
15
|
+
end
|
16
|
+
|
17
|
+
# @param [ActiveSupport::Duration] every
|
18
|
+
# @example: DispatchRider::ScheduledJob.publish_due_jobs every 1.minute
|
19
|
+
def publish_due_jobs(every: nil)
|
20
|
+
loop {
|
21
|
+
claim_stub = get_new_claim_stub
|
22
|
+
due.unclaimed.update_all claim_stub
|
23
|
+
due.claimed_by(claim_stub[:claim_id]).find_each(&:publish)
|
24
|
+
every ? sleep(every) : break # until the next loop
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def get_new_claim_stub
|
31
|
+
{ claim_id: SecureRandom.uuid, claim_expires_at: 30.minutes.from_now }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
serialize :destinations
|
36
|
+
serialize :message
|
37
|
+
|
38
|
+
validates :scheduled_at,
|
39
|
+
:destinations,
|
40
|
+
:message,
|
41
|
+
presence: true
|
42
|
+
|
43
|
+
scope :due, -> (time = Time.now) { where "scheduled_at <= ?", time }
|
44
|
+
scope :claimed_by, -> (claim_id) { where(claim_id: claim_id).where "claim_expires_at > ?", Time.now }
|
45
|
+
scope :unclaimed, -> { where "claim_expires_at IS NULL OR claim_expires_at <= ?", Time.now }
|
46
|
+
|
47
|
+
def publish
|
48
|
+
publisher.publish(destinations: destinations, message: message)
|
49
|
+
|
50
|
+
destroy # once published
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
delegate :publisher, to: :"self.class"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
require_relative "scheduled_job/migration"
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module DispatchRider
|
2
|
+
module ScheduledJob::Migration
|
3
|
+
def create_scheduled_jobs_table
|
4
|
+
create_table :scheduled_jobs do |t|
|
5
|
+
t.datetime :scheduled_at
|
6
|
+
t.text :destinations
|
7
|
+
t.text :message
|
8
|
+
t.string :claim_id
|
9
|
+
t.datetime :claim_expires_at
|
10
|
+
|
11
|
+
t.index :scheduled_at
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe DispatchRider::ScheduledJob do
|
4
|
+
let(:message_details) {
|
5
|
+
{
|
6
|
+
destinations: [:allied_forces],
|
7
|
+
message: {
|
8
|
+
subject: :war_update,
|
9
|
+
body: { enigma_machine: :broken }
|
10
|
+
}
|
11
|
+
}
|
12
|
+
}
|
13
|
+
|
14
|
+
let!(:due_job) { described_class.create! message_details.merge(scheduled_at: 1.minute.ago) }
|
15
|
+
let!(:later_job) { described_class.create! message_details.merge(scheduled_at: 30.minutes.since) }
|
16
|
+
|
17
|
+
describe ".due" do
|
18
|
+
describe "due now" do
|
19
|
+
subject(:due_jobs) { described_class.due }
|
20
|
+
|
21
|
+
it { expect(due_jobs).to include due_job }
|
22
|
+
it { expect(due_jobs).to_not include later_job }
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "due tomorrow" do
|
26
|
+
subject(:due_jobs) { described_class.due Date.tomorrow.midnight }
|
27
|
+
|
28
|
+
it { expect(due_jobs).to include due_job }
|
29
|
+
it { expect(due_jobs).to include later_job }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe ".publish_due_jobs" do
|
34
|
+
example {
|
35
|
+
expect(described_class.publisher).to receive(:publish).once.with destinations: [:allied_forces],
|
36
|
+
message: {
|
37
|
+
subject: :war_update,
|
38
|
+
body: { enigma_machine: :broken }
|
39
|
+
}
|
40
|
+
|
41
|
+
2.times { described_class.publish_due_jobs }
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "#destinations serialization" do
|
46
|
+
subject { described_class.find(due_job.id).destinations }
|
47
|
+
|
48
|
+
it { is_expected.to eq [:allied_forces] }
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "#message serialization" do
|
52
|
+
subject { described_class.find(due_job.id).message }
|
53
|
+
|
54
|
+
it {
|
55
|
+
is_expected.to eq subject: :war_update,
|
56
|
+
body: { enigma_machine: :broken }
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "#publish" do
|
61
|
+
subject(:job) { described_class.find(due_job.id) }
|
62
|
+
|
63
|
+
example {
|
64
|
+
expect(described_class.publisher).to receive(:publish).with destinations: [:allied_forces],
|
65
|
+
message: {
|
66
|
+
subject: :war_update,
|
67
|
+
body: { enigma_machine: :broken }
|
68
|
+
}
|
69
|
+
|
70
|
+
job.publish
|
71
|
+
}
|
72
|
+
end
|
73
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -6,6 +6,8 @@ Bundler.require
|
|
6
6
|
require 'aws'
|
7
7
|
require 'rake'
|
8
8
|
require 'tempfile'
|
9
|
+
require "sqlite3"
|
10
|
+
|
9
11
|
|
10
12
|
require 'dispatch-rider'
|
11
13
|
Dir['./spec/support/**/*.rb'].each { |fn| require(fn) }
|
@@ -25,10 +27,26 @@ RSpec.configure do |config|
|
|
25
27
|
|
26
28
|
config.include IntegrationSupport
|
27
29
|
|
30
|
+
config.before(:suite) do
|
31
|
+
FileUtils.mkdir_p "tmp"
|
32
|
+
FileUtils.rm_f "tmp/lite.db"
|
33
|
+
FileUtils.rm_rf "spectmp"
|
34
|
+
SQLite3::Database.new "tmp/lite.db"
|
35
|
+
ActiveRecord::Base.establish_connection adapter: :sqlite3, database: File.dirname(__FILE__) + "tmp/lite.db"
|
36
|
+
ActiveRecord::Schema.define(version: 1) do
|
37
|
+
extend DispatchRider::ScheduledJob::Migration
|
38
|
+
create_scheduled_jobs_table
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
28
42
|
config.before do
|
29
43
|
DispatchRider.config.logger = NullLogger.new
|
30
44
|
end
|
31
45
|
|
46
|
+
config.after do
|
47
|
+
DispatchRider::ScheduledJob.destroy_all
|
48
|
+
end
|
49
|
+
|
32
50
|
config.include FactoryGirl::Syntax::Methods
|
33
51
|
end
|
34
52
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dispatch-rider
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Suman Mukherjee
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2015-
|
14
|
+
date: 2015-07-17 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: activesupport
|
@@ -41,6 +41,20 @@ dependencies:
|
|
41
41
|
- - ">="
|
42
42
|
- !ruby/object:Gem::Version
|
43
43
|
version: 3.2.0
|
44
|
+
- !ruby/object:Gem::Dependency
|
45
|
+
name: activerecord
|
46
|
+
requirement: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: 3.2.0
|
51
|
+
type: :runtime
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: 3.2.0
|
44
58
|
- !ruby/object:Gem::Dependency
|
45
59
|
name: daemons
|
46
60
|
requirement: !ruby/object:Gem::Requirement
|
@@ -75,6 +89,20 @@ dependencies:
|
|
75
89
|
- - ">="
|
76
90
|
- !ruby/object:Gem::Version
|
77
91
|
version: 0.0.5
|
92
|
+
- !ruby/object:Gem::Dependency
|
93
|
+
name: sqlite3
|
94
|
+
requirement: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
type: :development
|
100
|
+
prerelease: false
|
101
|
+
version_requirements: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
78
106
|
description: "\n Messaging system based on the reactor pattern.\n\n You can
|
79
107
|
publish messages to a queue and then a demultiplexer\n runs an event loop which
|
80
108
|
pops items from the queue and hands\n it over to a dispatcher.\n\n The dispatcher
|
@@ -159,6 +187,8 @@ files:
|
|
159
187
|
- lib/dispatch-rider/registrars/queue_service.rb
|
160
188
|
- lib/dispatch-rider/registrars/sns_channel.rb
|
161
189
|
- lib/dispatch-rider/runner.rb
|
190
|
+
- lib/dispatch-rider/scheduled_job.rb
|
191
|
+
- lib/dispatch-rider/scheduled_job/migration.rb
|
162
192
|
- lib/dispatch-rider/subscriber.rb
|
163
193
|
- lib/dispatch-rider/version.rb
|
164
194
|
- lib/generators/dispatch_rider/install/USAGE
|
@@ -216,6 +246,7 @@ files:
|
|
216
246
|
- spec/lib/dispatch-rider/registrars/sns_channel_spec.rb
|
217
247
|
- spec/lib/dispatch-rider/registrars_spec.rb
|
218
248
|
- spec/lib/dispatch-rider/runner_spec.rb
|
249
|
+
- spec/lib/dispatch-rider/scheduled_job_spec.rb
|
219
250
|
- spec/lib/dispatch-rider/subscriber_spec.rb
|
220
251
|
- spec/lib/dispatch-rider_spec.rb
|
221
252
|
- spec/spec_helper.rb
|
@@ -295,6 +326,7 @@ test_files:
|
|
295
326
|
- spec/lib/dispatch-rider/registrars/sns_channel_spec.rb
|
296
327
|
- spec/lib/dispatch-rider/registrars_spec.rb
|
297
328
|
- spec/lib/dispatch-rider/runner_spec.rb
|
329
|
+
- spec/lib/dispatch-rider/scheduled_job_spec.rb
|
298
330
|
- spec/lib/dispatch-rider/subscriber_spec.rb
|
299
331
|
- spec/lib/dispatch-rider_spec.rb
|
300
332
|
- spec/spec_helper.rb
|