much-rails-pub-sub 0.0.1.pre → 0.1.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
  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