dispatch-rider 1.6.2 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 697069b0aff03bae879decd081323fe5c90441b1
4
- data.tar.gz: 25ae5e9c896c920864800e9dea6e7421cafe3aee
3
+ metadata.gz: ceff36f00f29c392c12751a5b9be98ded76af731
4
+ data.tar.gz: 4252fb5a29899475e8dd465cd9d9073a81c8bd4e
5
5
  SHA512:
6
- metadata.gz: a0496f4be0ffffa3bcf025abc4a92dd8fc42c621a667bf8c69841120a8472cf0c37f37663b2fc0226b59b7e01990219eb8eec528fcb851974f0848eb4f6af445
7
- data.tar.gz: 407419063fd8c30148d644156ddbaba8342250f9310dc6323d5affa259ec15dcf2ba9694893d209530f978ff7fb5cee5c21551b8d00652c71584fbcc47b8a3b8
6
+ metadata.gz: 3f1f68c73b69964a68b0222fc02319e0f954d380af74a255d5eb69c15ca48c3c6374a0d14f29a81f41f7be664edf43cf785f223d8b2f4b1b0f853246242011bb
7
+ data.tar.gz: cf671844773968e6930f9e2ff3245903e658c04bbf40f2c41eac2a01b7ca6b7f2ce8c4dad9c6a77042163897800ec777c2ec4620c302303b7814788e0e7c179c
data/.gitignore CHANGED
@@ -15,6 +15,7 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ spectmp
18
19
  log
19
20
  .rvmrc
20
21
  .ruby*
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 ...
@@ -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
@@ -41,4 +41,5 @@ require "dispatch-rider/demultiplexer"
41
41
  require "dispatch-rider/runner"
42
42
  require "dispatch-rider/publisher"
43
43
  require "dispatch-rider/subscriber"
44
+ require "dispatch-rider/scheduled_job"
44
45
  require "dispatch-rider/logging"
@@ -30,10 +30,19 @@ module DispatchRider
30
30
 
31
31
  # @param [Hash] body
32
32
  def publish(body)
33
- raise ArgumentError, 'body should be a hash' unless body.kind_of?(Hash)
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
@@ -1,4 +1,4 @@
1
1
  # This file specifies the current version of the gem.
2
2
  module DispatchRider
3
- VERSION = "1.6.2"
3
+ VERSION = "1.7.0"
4
4
  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.6.2
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-06-25 00:00:00.000000000 Z
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