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 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