much-rails-pub-sub 0.0.1.pre → 0.1.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
  SHA256:
3
- metadata.gz: 2c027ffbe66fe37a8a92be4aa13ed0a1f78ee49f189c92c36b13ff08919ed2dc
4
- data.tar.gz: 99715b804b37c91b44a1492273412c316c3b0e12a2ad6be419c6e87bb628dfda
3
+ metadata.gz: 5a6ed6456d7161fa397059ffd159886698af32247762c621915cd8aef58a76d8
4
+ data.tar.gz: 7ba0b6851e960c63439d6434d27de90768fcf47570eadd99df5548721e124587
5
5
  SHA512:
6
- metadata.gz: 8bfefb07e7e97de36bf32c08430ccf7a695b8904ff05c8e28279ba0cc3866f6ef6111c91f00c12b38122f4d819e8b62c020ba4ba8b59b0129d2b9828d4c18d8e
7
- data.tar.gz: d2b4784118517d796316af6c258a90ad20ddd9a609ad0253366bb0f1a4e056e15a6b408d940223d4244e7f138e66850306799d6c628ef5ec213f0956b1cdce6a
6
+ metadata.gz: 2761c88d2e0a4caa1751a52adc5ca0919318c3debd9c9bbd440976c1b0792512b6377149913dbcc5f1c332efe236a3705988092f5528b0aac21333763ef129dc
7
+ data.tar.gz: 9a056a060618c2e3043c9937883f896474dd4b1044555fb98cd907dc1f96dd3f0574b44aa80963c4ab01b747ed832ed273797092ae81af383d11797ef949e240
data/README.md CHANGED
@@ -2,9 +2,129 @@
2
2
 
3
3
  A Pub/Sub API/framework for MuchRails using ActiveJob.
4
4
 
5
+ ## Setup
6
+
7
+ ### Add an ActiveJob to publish events
8
+
9
+ In e.g. `app/jobs/pub_sub_publish_job.rb`:
10
+
11
+ ```ruby
12
+ class PubSubPublishJob < ApplicationJob
13
+ include MuchRailsPubSub::PublishJobBehaviors
14
+
15
+ # add any additional desired ActiveJob configurations
16
+ queue_as :critical
17
+ end
18
+ ```
19
+
20
+ ### Add a config file for event subscriptions
21
+
22
+ Create an empty config file named e.g. `config/pub_sub.rb`. This file will hold the configured event subscriptions.
23
+
24
+ ### Add an initializer
25
+
26
+ This will configure the PubSubPublishJob and load subscriptions in `config/pub_sub.rb`. In e.g. `config/initializers/pub_sub.rb`:
27
+
28
+ ```ruby
29
+ MuchRailsPubSub.configure do |config|
30
+ config.publish_job = PubSubPublishJob
31
+ config.logger = Rails.logger
32
+ end
33
+
34
+ # `MuchRailsPubSub` needs to load subscriptions after the Rails app has
35
+ # been initialized. This allows initialization callbacks to configure
36
+ # `ActiveJob` before pub/sub job classes are required and evaluated by
37
+ # loading subscriptions.
38
+ Rails.application.config.after_initialize do
39
+ MuchRailsPubSub.load_subscriptions(Rails.root.join("config/pub_sub.rb"))
40
+ end
41
+ ```
42
+
5
43
  ## Usage
6
44
 
7
- TODO: Write code samples and usage instructions here
45
+ ### Add an event handler job
46
+
47
+ In e.g. `app/jobs/events/thing/create_v1_job.rb`:
48
+
49
+ ```ruby
50
+ class Events::Thing::CreatedV1Job < ApplicationJob
51
+ def perform(params)
52
+ puts "do something when a Thing is created ..."
53
+ puts "params: #{params.inspect}"
54
+ end
55
+ end
56
+ ```
57
+
58
+ ### Subscribe the event handler job to an event
59
+
60
+ In e.g. `config/pub_sub.rb`:
61
+
62
+ ```ruby
63
+ MuchRailsPubSub.subscribe "thing.created.v1",
64
+ job: Events::Thing::CreatedV1Job
65
+ ```
66
+
67
+ ### Publish the event in your code
68
+
69
+ E.g.:
70
+
71
+ ```ruby
72
+ MuchRailsPubSub.publish("thing.created.v1", key: "value")
73
+ ```
74
+
75
+ In the Rails logger:
76
+
77
+ ```
78
+ Enqueued PubSubPublishJob (Job ID: fc834ce6-2f2d-4953-bb83-e9a272bf2a08) to Sidekiq(critical) with arguments: {"event_id"=>"5aaa5129-69b5-46fe-bff3-2e60c9749d62", "event_name"=>"thing.created.v1", "event_params"=>{:key=>"value"}}
79
+ 2021-06-16T13:14:15.116Z pid=31539 tid=mxn INFO: [MuchRailsPubSub] Published "thing.created.v1":
80
+ ID: "5aaa5129-69b5-46fe-bff3-2e60c9749d62"
81
+ PARAMS: {:key=>"value"}
82
+
83
+ 2021-06-16T13:14:15.117Z pid=31902 tid=e62 class=PubSubPublishJob jid=1e73eeee286bebe1f57a0e97 INFO: start
84
+ 2021-06-16T13:14:15.126Z pid=31902 tid=e0y class=Events::Thing::CreatedV1Job jid=f22958c1fa80a4aee91b2731 INFO: start
85
+ 2021-06-16T13:14:15.127Z pid=31902 tid=e62 class=PubSubPublishJob jid=1e73eeee286bebe1f57a0e97 INFO: [MuchRailsPubSub] Dispatched 1 subscription job(s) for "thing.created.v1" (5aaa5129-69b5-46fe-bff3-2e60c9749d62):
86
+ - Events::Thing::CreatedV1Job
87
+ 2021-06-16T13:14:15.127Z pid=31902 tid=e62 class=PubSubPublishJob jid=1e73eeee286bebe1f57a0e97 elapsed=0.01 INFO: done
88
+ do something when a Thing is created ...
89
+ params: {:key=>"value"}
90
+ 2021-06-16T13:14:15.129Z pid=31902 tid=e0y class=Events::Thing::CreatedV1Job jid=f22958c1fa80a4aee91b2731 elapsed=0.002 INFO: done
91
+ ```
92
+
93
+ ## Testing
94
+
95
+ ### Event Handler Jobs
96
+
97
+ These are just ActiveJobs; test them like you would test any other ActiveJob.
98
+
99
+ ### Event publishes
100
+
101
+ MuchRailsPubSub comes with a `TestPublisher` and `TestingPublishedEvents` classes that produce the same side-effects of publishing an event without _actually_ publishing the event. You can then test for these side-effects, in e.g. unit tests, to verify event publishes are happening as expected.
102
+
103
+ This example assumes you are using [Assert](https://github.com/redding/assert) as your test framework. However, this can be adapted to whatever framework you use.
104
+
105
+ In e.g. `test/helper.rb`
106
+
107
+ ```ruby
108
+ require "test/support/fake_logger"
109
+ require "much-rails-pub-sub"
110
+
111
+ MuchRailsPubSub.setup_test_publishing
112
+ MuchRailsPubSub.config.logger = FakeLogger.new
113
+
114
+ Assert::Context.teardown do
115
+ MuchRailsPubSub.published_events.clear
116
+ end
117
+ ```
118
+
119
+ In a test:
120
+
121
+ ```ruby
122
+ MuchRailsPubSub.publish("thing.created.v1", key: "value")
123
+
124
+ latest_event = MuchRailsPubSub.published_events.last
125
+ assert_that(latest_event.name).equals("thing.created.v1")
126
+ assert_that(latest_event.params).equals(key: "value")
127
+ ```
8
128
 
9
129
  ## Installation
10
130
 
@@ -1,6 +1,158 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "much-rails"
3
4
  require "much-rails-pub-sub/version"
5
+ require "much-rails-pub-sub/active_job_publisher"
6
+ require "much-rails-pub-sub/publish_job_behaviors"
7
+ require "much-rails-pub-sub/subscription"
4
8
 
5
9
  module MuchRailsPubSub
10
+ include MuchRails::Config
11
+
12
+ add_config
13
+
14
+ def self.publish(event_name, **event_params)
15
+ config
16
+ .publisher_class
17
+ .call(event_name, event_params: event_params)
18
+ .tap do |event|
19
+ published_events << event
20
+ config.logger.info(
21
+ "[MuchRailsPubSub] Published #{event.name.inspect}:\n"\
22
+ " ID: #{event.id.inspect}\n"\
23
+ " PARAMS: #{event.params.inspect}",
24
+ )
25
+ end
26
+ end
27
+
28
+ def self.subscribe(event_name, job:)
29
+ subscriptions <<
30
+ MuchRailsPubSub::Subscription.new(
31
+ event_name,
32
+ job_class: config.constantize_job(job, type: :subscription),
33
+ )
34
+ end
35
+
36
+ def self.published_events
37
+ config.published_events
38
+ end
39
+
40
+ def self.subscriptions
41
+ config.subscriptions
42
+ end
43
+
44
+ def self.load_subscriptions(subscriptions_file_path)
45
+ config.constantize_publish_job_class
46
+ # Use `Kernel.load` so we can stub and test this.
47
+ Kernel.load(subscriptions_file_path)
48
+ end
49
+
50
+ def self.setup_test_publishing
51
+ require "much-rails-pub-sub/test_publisher"
52
+
53
+ config.published_events =
54
+ MuchRailsPubSub::Config::TestingPublishedEvents.new
55
+ config.publisher_class = MuchRailsPubSub::TestPublisher
56
+ end
57
+
58
+ class Config
59
+ attr_reader :publish_job_class
60
+ attr_accessor :publish_job, :published_events, :publisher_class, :logger
61
+
62
+ def initialize
63
+ @published_events = DefaultPublishedEvents.new
64
+ @publisher_class = MuchRailsPubSub::ActiveJobPublisher
65
+ end
66
+
67
+ def constantize_publish_job_class
68
+ @publish_job_class =
69
+ constantize_job(publish_job, type: :publish).tap do |job_class|
70
+ unless publish_job_class?(job_class)
71
+ raise(
72
+ TypeError,
73
+ "Publish job classes must mixin MuchRailsPubSub::PublishJob. "\
74
+ "The given job class, #{job_class.inspect}, does not.",
75
+ )
76
+ end
77
+ end
78
+ end
79
+
80
+ def constantize_job(value, type:)
81
+ begin
82
+ value.to_s.constantize
83
+ rescue NameError
84
+ raise TypeError, "Unknown #{type} job class: #{value.inspect}."
85
+ end
86
+ end
87
+
88
+ def subscriptions
89
+ @subscriptions ||= Subscriptions.new
90
+ end
91
+
92
+ def publish_job_class?(job_class)
93
+ !!(job_class < MuchRailsPubSub::PublishJobBehaviors)
94
+ end
95
+
96
+ class PublishedEvents < ::Array
97
+ def <<(value)
98
+ super
99
+
100
+ value
101
+ end
102
+ end
103
+
104
+ class DefaultPublishedEvents < PublishedEvents
105
+ def <<(value)
106
+ value
107
+ end
108
+ end
109
+
110
+ TestingPublishedEvents = Class.new(PublishedEvents)
111
+
112
+ class Subscriptions
113
+ def initialize
114
+ @subscriptions = Hash.new{ |hash, key| hash[key] = ::Set.new }
115
+ end
116
+
117
+ def for_event(event_name)
118
+ @subscriptions[normalize_event_name(event_name)].to_a
119
+ end
120
+
121
+ def <<(subscription)
122
+ @subscriptions[normalize_event_name(subscription.event_name)] <<
123
+ subscription
124
+ end
125
+
126
+ def dispatch(publish_params)
127
+ event_id = publish_params["event_id"]
128
+ event_name = publish_params["event_name"]
129
+ event_params = publish_params["event_params"]
130
+ subscriptions = for_event(event_name)
131
+ subscription_log_details =
132
+ subscriptions
133
+ .map{ |subscription|
134
+ " - #{subscription.job_class.inspect}"
135
+ }
136
+ .join("\n")
137
+
138
+ subscriptions.each{ |subscription| subscription.call(event_params) }
139
+
140
+ logger.info(
141
+ "[MuchRailsPubSub] Dispatched #{subscriptions.size} subscription "\
142
+ "job(s) for #{event_name.inspect} (#{event_id}):\n"\
143
+ "#{subscription_log_details}",
144
+ )
145
+ end
146
+
147
+ private
148
+
149
+ def normalize_event_name(name)
150
+ name.to_s.downcase
151
+ end
152
+
153
+ def logger
154
+ MuchRailsPubSub.config.logger
155
+ end
156
+ end
157
+ end
6
158
  end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "much-rails-pub-sub/publisher"
4
+
5
+ class MuchRailsPubSub::ActiveJobPublisher < MuchRailsPubSub::Publisher
6
+ def on_call
7
+ @publshed_job_id ||=
8
+ MuchRailsPubSub.config.publish_job_class.perform_later(publish_params)
9
+
10
+ event
11
+ end
12
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "much-rails-pub-sub"
4
+
5
+ MuchRailsPubSub::Event =
6
+ Struct.new(:id, :name, :params) do
7
+ def initialize(name, params:)
8
+ super(SecureRandom.uuid, name, params)
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "much-rails-pub-sub"
4
+
5
+ module MuchRailsPubSub::PublishJobBehaviors
6
+ include MuchRails::Mixin
7
+
8
+ mixin_instance_methods do
9
+ def perform(publish_params)
10
+ MuchRailsPubSub.subscriptions.dispatch(publish_params)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "much-rails-pub-sub"
4
+ require "much-rails-pub-sub/event"
5
+
6
+ class MuchRailsPubSub::Publisher
7
+ include MuchRails::CallMethod
8
+
9
+ attr_reader :event
10
+
11
+ def initialize(event_name, event_params:)
12
+ @event = MuchRailsPubSub::Event.new(event_name, params: event_params)
13
+ end
14
+
15
+ def on_call
16
+ raise NotImplementedError
17
+ end
18
+
19
+ def event_id
20
+ event.id
21
+ end
22
+
23
+ def event_name
24
+ event.name
25
+ end
26
+
27
+ def event_params
28
+ event.params
29
+ end
30
+
31
+ private
32
+
33
+ def publish_params
34
+ {
35
+ "event_id" => event_id,
36
+ "event_name" => event_name,
37
+ "event_params" => event_params,
38
+ }
39
+ end
40
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "much-rails-pub-sub"
4
+
5
+ class MuchRailsPubSub::Subscription
6
+ attr_reader :event_name, :job_class
7
+
8
+ def initialize(event_name, job_class:)
9
+ @event_name = event_name
10
+ @job_class = job_class
11
+
12
+ unless job_class.respond_to?(:perform_later)
13
+ raise(
14
+ ArgumentError,
15
+ "Invalid job class #{job_class.inspect}: it does not respond to "\
16
+ "the :perform_later method.",
17
+ )
18
+ end
19
+ end
20
+
21
+ def call(params)
22
+ job_class.perform_later(params)
23
+ end
24
+
25
+ def hash
26
+ job_class.hash
27
+ end
28
+
29
+ def eql?(other)
30
+ job_class.eql?(other.job_class)
31
+ end
32
+
33
+ def ==(other)
34
+ if other.is_a?(self.class)
35
+ event_name == other.event_name && job_class == other.job_class
36
+ else
37
+ super
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "much-rails-pub-sub/publisher"
4
+
5
+ class MuchRailsPubSub::TestPublisher < MuchRailsPubSub::Publisher
6
+ def on_call
7
+ event
8
+ end
9
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MuchRailsPubSub
4
- VERSION = "0.0.1.pre"
4
+ VERSION = "0.1.0"
5
5
  end
data/test/helper.rb CHANGED
@@ -7,3 +7,11 @@ $LOAD_PATH.unshift(File.expand_path("../..", __FILE__))
7
7
  require "pry"
8
8
 
9
9
  require "test/support/factory"
10
+ require "test/support/publish_job"
11
+ require "much-rails-pub-sub"
12
+
13
+ MuchRailsPubSub.configure do |config|
14
+ config.publish_job = "PublishJob"
15
+ end
16
+
17
+ MuchRailsPubSub.load_subscriptions("test/support/pub_sub.rb")
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ApplicationJob
4
+ def self.perform_later(*)
5
+ end
6
+ end
@@ -4,4 +4,8 @@ require "assert/factory"
4
4
 
5
5
  module Factory
6
6
  extend Assert::Factory
7
+
8
+ def self.uuid
9
+ SecureRandom.uuid
10
+ end
7
11
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ class FakeLogger
4
+ UNKNOWN = ::Logger::UNKNOWN # 5
5
+ WARN = ::Logger::WARN # 2
6
+ DEBUG = ::Logger::DEBUG # 0
7
+
8
+ attr_reader :info_calls, :warning_calls, :error_calls, :debug_calls
9
+ attr_accessor :level
10
+
11
+ def initialize(level: DEBUG)
12
+ @level = level
13
+ @info_calls = []
14
+ @warning_calls = []
15
+ @error_calls = []
16
+ @debug_calls = []
17
+ end
18
+
19
+ def info(*args)
20
+ @info_calls << args
21
+ end
22
+
23
+ def warning(*args)
24
+ @warning_calls << args
25
+ end
26
+
27
+ def error(*args)
28
+ @error_calls << args
29
+ end
30
+
31
+ def debug(*args)
32
+ @debug_calls << args
33
+ end
34
+ end
File without changes
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test/support/application_job"
4
+ require "much-rails-pub-sub"
5
+
6
+ class PublishJob < ApplicationJob
7
+ include MuchRailsPubSub::PublishJobBehaviors
8
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "assert"
4
+ require "much-rails-pub-sub/active_job_publisher"
5
+
6
+ class MuchRailsPubSub::ActiveJobPublisher
7
+ class UnitTests < Assert::Context
8
+ desc "MuchRailsPubSub::ActiveJobPublisher"
9
+ subject{ unit_class }
10
+
11
+ setup do
12
+ Assert.stub(MuchRailsPubSub.config, :publish_job_class) do
13
+ fake_publish_job_class
14
+ end
15
+ end
16
+
17
+ let(:unit_class){ MuchRailsPubSub::ActiveJobPublisher }
18
+
19
+ let(:event_name){ "something_happened_v1" }
20
+ let(:event_params){ { some: "thing" } }
21
+ let(:fake_publish_job_class){ FakeJobClass.new }
22
+
23
+ should "be configured as expected" do
24
+ assert_that(subject < MuchRailsPubSub::Publisher).is_true
25
+ end
26
+
27
+ should "call #perform_later on the configured publish job class" do
28
+ event = subject.call(event_name, event_params: event_params)
29
+ assert_that(event).is_a?(MuchRailsPubSub::Event)
30
+
31
+ assert_that(fake_publish_job_class.perform_calls.size).equals(1)
32
+ assert_that(fake_publish_job_class.perform_calls.last.args)
33
+ .equals([
34
+ "event_id" => event.id,
35
+ "event_name" => event.name,
36
+ "event_params" => event.params,
37
+ ])
38
+ end
39
+ end
40
+
41
+ class FakeJobClass
42
+ attr_reader :perform_calls
43
+
44
+ def initialize
45
+ @perform_calls = []
46
+ end
47
+
48
+ def perform_later(*args)
49
+ @perform_calls << Assert::StubCall.new(*args)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "assert"
4
+ require "much-rails-pub-sub/event"
5
+
6
+ class MuchRailsPubSub::Event
7
+ class UnitTests < Assert::Context
8
+ desc "MuchRailsPubSub::Event"
9
+ subject{ unit_class }
10
+
11
+ let(:unit_class){ MuchRailsPubSub::Event }
12
+
13
+ let(:event_id){ Factory.uuid }
14
+ let(:event_name){ "something_happened_v1" }
15
+ let(:event_params){ { some: "thing" } }
16
+ end
17
+
18
+ class InitTests < UnitTests
19
+ desc "when init"
20
+ subject{ unit_class.new(event_name, params: event_params) }
21
+
22
+ setup do
23
+ event_id
24
+ Assert.stub(SecureRandom, :uuid){ event_id }
25
+ end
26
+
27
+ should have_imeths :id, :name, :params
28
+
29
+ should "know its attributes" do
30
+ assert_that(subject.id).equals(event_id)
31
+ assert_that(subject.name).equals(event_name)
32
+ assert_that(subject.params).equals(event_params)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,290 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "assert"
4
+ require "much-rails-pub-sub"
5
+
6
+ require "test/support/fake_logger"
7
+ require "test/support/application_job"
8
+
9
+ module MuchRailsPubSub
10
+ class UnitTests < Assert::Context
11
+ desc "MuchRailsPubSub"
12
+ subject{ unit_class }
13
+
14
+ let(:unit_class){ MuchRailsPubSub }
15
+
16
+ let(:event_name){ "something_happened_v1" }
17
+ let(:event_params){ { some: "thing" } }
18
+
19
+ let(:job_value){ "MuchRailsPubSub::TestPublishJob" }
20
+ let(:job_class){ TestPublishJob }
21
+
22
+ let(:logger1){ FakeLogger.new }
23
+
24
+ should have_imeths :config
25
+ should have_imeths :publish, :subscribe
26
+ should have_imeths :published_events, :subscriptions
27
+ should have_imeths :load_subscriptions, :setup_test_publishing
28
+
29
+ should "be configured as expected" do
30
+ assert_that(subject).includes(MuchRails::Config)
31
+ end
32
+
33
+ should "know its attributes" do
34
+ assert_that(subject.config).is_a?(unit_class::Config)
35
+ end
36
+ end
37
+
38
+ class PublishMethodTests < UnitTests
39
+ desc ".publish"
40
+
41
+ setup do
42
+ Assert.stub(unit_class.config, :logger){ logger1 }
43
+
44
+ @publisher_calls = []
45
+ Assert.stub_tap_on_call(
46
+ unit_class.config.publisher_class,
47
+ :call,
48
+ ) do |_, call|
49
+ @publisher_calls << call
50
+ end
51
+
52
+ Assert.stub(unit_class.config, :published_events){ published_events1 }
53
+ end
54
+ end
55
+
56
+ class DefaultPublishedEventsPublishTests < PublishMethodTests
57
+ desc "when using the DefaultPublishedEvents"
58
+
59
+ let(:published_events1){ unit_class::Config::DefaultPublishedEvents.new }
60
+
61
+ should "call the Publish service without storing the published event" do
62
+ event = subject.publish(event_name, **event_params)
63
+ assert_that(event).is_a?(MuchRailsPubSub::Event)
64
+
65
+ assert_that(@publisher_calls.size).equals(1)
66
+ assert_that(@publisher_calls.last.pargs).equals([event_name])
67
+ assert_that(@publisher_calls.last.kargs)
68
+ .equals(event_params: event_params)
69
+ assert_that(subject.published_events).is_empty
70
+ end
71
+ end
72
+
73
+ class TestingPublishedEventsPublishTests < PublishMethodTests
74
+ desc "when using the TestingPublishedEvents"
75
+
76
+ let(:published_events1){ unit_class::Config::TestingPublishedEvents.new }
77
+
78
+ should "call the Publish service and store the published event" do
79
+ event = subject.publish(event_name, **event_params)
80
+ assert_that(event).is_a?(MuchRailsPubSub::Event)
81
+
82
+ assert_that(@publisher_calls.size).equals(1)
83
+ assert_that(@publisher_calls.last.pargs).equals([event_name])
84
+ assert_that(@publisher_calls.last.kargs)
85
+ .equals(event_params: event_params)
86
+ assert_that(subject.published_events.last).is(event)
87
+ end
88
+ end
89
+
90
+ class SubscribeMethodTests < UnitTests
91
+ desc ".subscribe"
92
+
93
+ setup do
94
+ Assert.stub(unit_class.config, :subscriptions){ subscriptions1 }
95
+ end
96
+
97
+ let(:subscriptions1){ unit_class::Config::Subscriptions.new }
98
+
99
+ should "push a subscription on to the configured subscriptions" do
100
+ subject.subscribe(event_name, job: job_value)
101
+
102
+ assert_that(subscriptions1.for_event(event_name).size).equals(1)
103
+ assert_that(subscriptions1.for_event(event_name).last)
104
+ .equals(unit_class::Subscription.new(event_name, job_class: job_class))
105
+ end
106
+ end
107
+
108
+ class LoadSubscriptionsTests < UnitTests
109
+ desc ".load_subscriptions"
110
+
111
+ setup do
112
+ Assert.stub_on_call(
113
+ subject.config,
114
+ :constantize_publish_job_class,
115
+ ) do |call|
116
+ @constantize_publish_job_class_call = call
117
+ end
118
+ Assert.stub_on_call(Kernel, :load){ |call| @load_call = call }
119
+ end
120
+
121
+ let(:subscriptions_file_path){ Factory.file_path }
122
+
123
+ should "constantize the publish job class and "\
124
+ "load the subscriptions file path" do
125
+ subject.load_subscriptions(subscriptions_file_path)
126
+ assert_that(@constantize_publish_job_class_call).is_not_nil
127
+ assert_that(@load_call.args).equals([subscriptions_file_path])
128
+ end
129
+ end
130
+
131
+ class ConfigTests < UnitTests
132
+ desc "Config"
133
+ subject{ config_class }
134
+
135
+ let(:config_class){ unit_class::Config }
136
+ end
137
+
138
+ class ConfigInitTests < ConfigTests
139
+ desc "when init"
140
+ subject{ config_class.new }
141
+
142
+ let(:type_value){ :some_type_value }
143
+
144
+ should have_readers :publish_job_class
145
+ should have_accessors :publish_job, :published_events, :publisher_class
146
+ should have_accessors :logger
147
+
148
+ should "know how to constantize its publish job class" do
149
+ subject.publish_job = job_value
150
+ assert_that(subject.publish_job_class).is_nil
151
+
152
+ subject.constantize_publish_job_class
153
+ assert_that(subject.publish_job_class).equals(job_class)
154
+ end
155
+
156
+ should "complain if it can't constantize the publish job class" do
157
+ ex =
158
+ assert_that{ subject.constantize_publish_job_class }.raises(TypeError)
159
+ assert_that(ex.message)
160
+ .equals("Unknown publish job class: nil.")
161
+
162
+ subject.publish_job = "MuchRailsPubSub::UnknownPublishJob"
163
+ ex =
164
+ assert_that{ subject.constantize_publish_job_class }.raises(TypeError)
165
+ assert_that(ex.message)
166
+ .equals("Unknown publish job class: #{subject.publish_job.inspect}.")
167
+
168
+ subject.publish_job = "MuchRailsPubSub::TestNonPublishJob"
169
+ ex =
170
+ assert_that{ subject.constantize_publish_job_class }.raises(TypeError)
171
+ assert_that(ex.message)
172
+ .equals(
173
+ "Publish job classes must mixin MuchRailsPubSub::PublishJob. "\
174
+ "The given job class, MuchRailsPubSub::TestNonPublishJob, does not.",
175
+ )
176
+ end
177
+
178
+ should "constantize valid job class values" do
179
+ assert_that(subject.constantize_job(job_value, type: type_value))
180
+ .equals(job_class)
181
+ end
182
+
183
+ should "complain when constantizing invalid job class values" do
184
+ ex =
185
+ assert_that{
186
+ subject.constantize_job("SomeUnknownInvalidClass", type: type_value)
187
+ }.raises(TypeError)
188
+ assert_that(ex.message).includes("Unknown #{type_value} job class: ")
189
+ end
190
+
191
+ should "know if a given job class is a publish job class or not" do
192
+ assert_that(subject.publish_job_class?(TestPublishJob)).is_true
193
+ assert_that(subject.publish_job_class?(TestNonPublishJob)).is_false
194
+ end
195
+ end
196
+
197
+ class SubscriptionsTests < ConfigTests
198
+ desc "Subscriptions"
199
+ subject{ subscriptions_class }
200
+
201
+ let(:subscriptions_class){ config_class::Subscriptions }
202
+ end
203
+
204
+ class SubscriptionsInitSetupTests < SubscriptionsTests
205
+ desc "when init"
206
+ subject{ subscriptions_class.new }
207
+
208
+ setup{ subject << subscription1 }
209
+
210
+ let(:fake_job_class){ FakeJobClass.new }
211
+ let(:publish_params) do
212
+ {
213
+ "event_name" => event_name,
214
+ "event_params" => event_params,
215
+ }
216
+ end
217
+ end
218
+
219
+ class SubscriptionsInitTests < SubscriptionsInitSetupTests
220
+ let(:subscription1) do
221
+ MuchRailsPubSub::Subscription.new(event_name, job_class: fake_job_class)
222
+ end
223
+
224
+ should have_imeths :for_event, :<<, :dispatch
225
+
226
+ should "know which subscriptions are configured for an event name" do
227
+ assert_that(subject.for_event(event_name).size).equals(1)
228
+ assert_that(subject.for_event(event_name).last).equals(subscription1)
229
+ end
230
+
231
+ should "not add duplicate subscriptions" do
232
+ subject << subscription1
233
+ subject << subscription1
234
+
235
+ assert_that(subject.for_event(event_name).size).equals(1)
236
+ end
237
+ end
238
+
239
+ class DispatchSubscriptionsTests < SubscriptionsInitSetupTests
240
+ desc "when init and dispacting subscriptions"
241
+
242
+ setup do
243
+ Assert.stub(MuchRailsPubSub.config, :logger){ logger1 }
244
+ end
245
+
246
+ let(:subscription1) do
247
+ FakeSubscription.new(event_name, job_class: fake_job_class)
248
+ end
249
+
250
+ should "dispatch by calling each subscription for the event name" do
251
+ subject.dispatch(publish_params)
252
+
253
+ subject.for_event(event_name).each do |subscription|
254
+ assert_that(subscription.calls.size).equals(1)
255
+ assert_that(subscription.calls.last.args).equals([event_params])
256
+ end
257
+ end
258
+ end
259
+
260
+ class FakeSubscription < MuchRailsPubSub::Subscription
261
+ attr_reader :calls
262
+ def initialize(*args)
263
+ super
264
+ @calls = []
265
+ end
266
+
267
+ def call(*args)
268
+ @calls << Assert::StubCall.new(*args)
269
+ end
270
+ end
271
+
272
+ class FakeJobClass
273
+ attr_reader :perform_calls
274
+
275
+ def initialize
276
+ @perform_calls = []
277
+ end
278
+
279
+ def perform_later(*args)
280
+ @perform_calls << Assert::StubCall.new(*args)
281
+ end
282
+ end
283
+
284
+ class TestPublishJob < ApplicationJob
285
+ include MuchRailsPubSub::PublishJobBehaviors
286
+ end
287
+
288
+ class TestNonPublishJob < ApplicationJob
289
+ end
290
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "assert"
4
+ require "much-rails-pub-sub/publish_job_behaviors"
5
+
6
+ require "test/support/application_job"
7
+
8
+ module MuchRailsPubSub::PublishJobBehaviors
9
+ class UnitTests < Assert::Context
10
+ desc "MuchRailsPubSub::Publish::JobBehaviors"
11
+ subject{ unit_module }
12
+
13
+ let(:unit_module){ MuchRailsPubSub::PublishJobBehaviors }
14
+
15
+ let(:event_name){ "something_happened_v1" }
16
+ let(:event_params){ { some: "thing" } }
17
+
18
+ should "be configured as expected" do
19
+ assert_that(subject).includes(MuchRails::Mixin)
20
+ end
21
+ end
22
+
23
+ class ReceiverTests < UnitTests
24
+ desc "receiver"
25
+ subject{ receiver_class }
26
+
27
+ let(:receiver_class) do
28
+ Class.new(ApplicationJob).tap{ |c| c.include unit_module }
29
+ end
30
+ end
31
+
32
+ class ReceiverInitTests < ReceiverTests
33
+ desc "when init"
34
+ subject{ receiver_class.new }
35
+
36
+ setup do
37
+ @subscriptions_dispatch_calls = []
38
+ Assert.stub_on_call(MuchRailsPubSub.subscriptions, :dispatch) do |call|
39
+ @subscriptions_dispatch_calls << call
40
+ end
41
+ end
42
+
43
+ let(:publish_params) do
44
+ {
45
+ "event_name" => event_name,
46
+ "event_params" => event_params,
47
+ }
48
+ end
49
+
50
+ should have_imeth :perform
51
+
52
+ should "dispatch the subscriptions for the given event name with params" do
53
+ subject.perform(publish_params)
54
+
55
+ assert_that(@subscriptions_dispatch_calls.size).equals(1)
56
+ assert_that(@subscriptions_dispatch_calls.last.args)
57
+ .equals([publish_params])
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "assert"
4
+ require "much-rails-pub-sub/publisher"
5
+
6
+ class MuchRailsPubSub::Publisher
7
+ class UnitTests < Assert::Context
8
+ desc "MuchRailsPubSub::Publisher"
9
+ subject{ unit_class }
10
+
11
+ let(:unit_class){ MuchRailsPubSub::Publisher }
12
+
13
+ let(:event_name){ "something_happened_v1" }
14
+ let(:event_params){ { some: "thing" } }
15
+
16
+ should "be configured as expected" do
17
+ assert_that(subject).includes(MuchRails::CallMethod)
18
+ end
19
+
20
+ should "not implement its on call method" do
21
+ assert_that{
22
+ subject.call(event_name, event_params: event_params)
23
+ }.raises(NotImplementedError)
24
+ end
25
+ end
26
+
27
+ class InitTests < UnitTests
28
+ desc "when init"
29
+ subject{ unit_class.new(event_name, event_params: event_params) }
30
+
31
+ should have_readers :event
32
+ should have_imeths :event_id, :event_name, :event_params
33
+
34
+ should "know its attributes" do
35
+ assert_that(subject.event).is_a?(MuchRailsPubSub::Event)
36
+ assert_that(subject.event.name).equals(event_name)
37
+ assert_that(subject.event.params).equals(event_params)
38
+
39
+ assert_that(subject.event_id).equals(subject.event.id)
40
+ assert_that(subject.event_name).equals(subject.event.name)
41
+ assert_that(subject.event_params).equals(subject.event.params)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "assert"
4
+ require "much-rails-pub-sub/subscription"
5
+
6
+ class MuchRailsPubSub::Subscription
7
+ class UnitTests < Assert::Context
8
+ desc "MuchRailsPubSub::Subscription"
9
+ subject{ unit_class }
10
+
11
+ let(:unit_class){ MuchRailsPubSub::Subscription }
12
+
13
+ let(:event_name){ "something_happened_v1" }
14
+ let(:event_params){ { some: "thing" } }
15
+
16
+ should "complain if initialized with an invalid job class" do
17
+ assert_that{ subject.new(event_name, job_class: Class.new) }
18
+ .raises(ArgumentError)
19
+ end
20
+ end
21
+
22
+ class InitTests < UnitTests
23
+ desc "when init"
24
+ subject{ unit_class.new(event_name, job_class: fake_job_class) }
25
+
26
+ let(:fake_job_class){ FakeJobClass.new }
27
+
28
+ should have_readers :event_name, :job_class
29
+
30
+ should "call #perform_later on the configured subscription job class" do
31
+ subject.call(event_params)
32
+
33
+ assert_that(fake_job_class.perform_calls.size).equals(1)
34
+ assert_that(fake_job_class.perform_calls.last.args)
35
+ .equals([event_params])
36
+ end
37
+ end
38
+
39
+ class FakeJobClass
40
+ attr_reader :perform_calls
41
+
42
+ def initialize
43
+ @perform_calls = []
44
+ end
45
+
46
+ def perform_later(*args)
47
+ @perform_calls << Assert::StubCall.new(*args)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "assert"
4
+ require "much-rails-pub-sub/test_publisher"
5
+
6
+ class MuchRailsPubSub::TestPublisher
7
+ class UnitTests < Assert::Context
8
+ desc "MuchRailsPubSub::TestPublisher"
9
+ subject{ unit_class }
10
+
11
+ setup do
12
+ Assert.stub(MuchRailsPubSub.config, :publish_job_class) do
13
+ fake_publish_job_class
14
+ end
15
+ end
16
+
17
+ let(:unit_class){ MuchRailsPubSub::TestPublisher }
18
+
19
+ let(:event_name){ "something_happened_v1" }
20
+ let(:event_params){ { some: "thing" } }
21
+ let(:fake_publish_job_class){ FakeJobClass.new }
22
+
23
+ should "be configured as expected" do
24
+ assert_that(subject < MuchRailsPubSub::Publisher).is_true
25
+ end
26
+
27
+ should "doesnn't call #perform_later on the configured publish job class" do
28
+ event = subject.call(event_name, event_params: event_params)
29
+ assert_that(event).is_a?(MuchRailsPubSub::Event)
30
+
31
+ assert_that(fake_publish_job_class.perform_calls.size).equals(0)
32
+ end
33
+ end
34
+
35
+ class FakeJobClass
36
+ attr_reader :perform_calls
37
+
38
+ def initialize
39
+ @perform_calls = []
40
+ end
41
+
42
+ def perform_later(*args)
43
+ @perform_calls << Assert::StubCall.new(*args)
44
+ end
45
+ end
46
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: much-rails-pub-sub
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1.pre
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kelly Redding
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-06-15 00:00:00.000000000 Z
12
+ date: 2021-06-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: much-style-guide
@@ -65,13 +65,29 @@ files:
65
65
  - LICENSE
66
66
  - README.md
67
67
  - lib/much-rails-pub-sub.rb
68
+ - lib/much-rails-pub-sub/active_job_publisher.rb
69
+ - lib/much-rails-pub-sub/event.rb
70
+ - lib/much-rails-pub-sub/publish_job_behaviors.rb
71
+ - lib/much-rails-pub-sub/publisher.rb
72
+ - lib/much-rails-pub-sub/subscription.rb
73
+ - lib/much-rails-pub-sub/test_publisher.rb
68
74
  - lib/much-rails-pub-sub/version.rb
69
75
  - log/.keep
70
76
  - much-rails-pub-sub.gemspec
71
77
  - test/helper.rb
78
+ - test/support/application_job.rb
72
79
  - test/support/factory.rb
80
+ - test/support/fake_logger.rb
81
+ - test/support/pub_sub.rb
82
+ - test/support/publish_job.rb
73
83
  - test/system/.keep
74
- - test/unit/.keep
84
+ - test/unit/active_job_publisher_tests.rb
85
+ - test/unit/event_tests.rb
86
+ - test/unit/much-rails-pub-sub_tests.rb
87
+ - test/unit/publish_job_behaviors_tests.rb
88
+ - test/unit/publisher_tests.rb
89
+ - test/unit/subscription_tests.rb
90
+ - test/unit/test_publisher_tests.rb
75
91
  - tmp/.keep
76
92
  homepage: https://github.com/redding/much-rails-pub-sub
77
93
  licenses:
@@ -88,9 +104,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
88
104
  version: '2.5'
89
105
  required_rubygems_version: !ruby/object:Gem::Requirement
90
106
  requirements:
91
- - - ">"
107
+ - - ">="
92
108
  - !ruby/object:Gem::Version
93
- version: 1.3.1
109
+ version: '0'
94
110
  requirements: []
95
111
  rubygems_version: 3.1.6
96
112
  signing_key:
@@ -98,6 +114,16 @@ specification_version: 4
98
114
  summary: A Pub/Sub API/framework for MuchRails using ActiveJob
99
115
  test_files:
100
116
  - test/helper.rb
117
+ - test/support/application_job.rb
101
118
  - test/support/factory.rb
119
+ - test/support/fake_logger.rb
120
+ - test/support/pub_sub.rb
121
+ - test/support/publish_job.rb
102
122
  - test/system/.keep
103
- - test/unit/.keep
123
+ - test/unit/active_job_publisher_tests.rb
124
+ - test/unit/event_tests.rb
125
+ - test/unit/much-rails-pub-sub_tests.rb
126
+ - test/unit/publish_job_behaviors_tests.rb
127
+ - test/unit/publisher_tests.rb
128
+ - test/unit/subscription_tests.rb
129
+ - test/unit/test_publisher_tests.rb