reactor 0.19.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +25 -134
- data/lib/reactor.rb +14 -4
- data/lib/reactor/event.rb +6 -1
- data/lib/reactor/subscription.rb +12 -22
- data/lib/reactor/testing/stubs.rb +46 -0
- data/lib/reactor/version.rb +1 -1
- data/lib/reactor/workers/concerns/configuration.rb +3 -9
- data/spec/event_spec.rb +15 -6
- data/spec/models/concerns/publishable_spec.rb +0 -1
- data/spec/models/concerns/subscribable_spec.rb +12 -40
- data/spec/spec_helper.rb +1 -2
- data/spec/subscription_spec.rb +25 -0
- data/spec/support/shared_examples.rb +1 -11
- data/spec/workers/event_worker_spec.rb +6 -27
- data/spec/workers/mailer_worker_spec.rb +0 -2
- metadata +3 -17
- data/lib/reactor/controllers.rb +0 -2
- data/lib/reactor/controllers/concerns/actions/action_event.rb +0 -13
- data/lib/reactor/controllers/concerns/actions/create_event.rb +0 -18
- data/lib/reactor/controllers/concerns/actions/destroy_event.rb +0 -9
- data/lib/reactor/controllers/concerns/actions/edit_event.rb +0 -9
- data/lib/reactor/controllers/concerns/actions/index_event.rb +0 -9
- data/lib/reactor/controllers/concerns/actions/new_event.rb +0 -9
- data/lib/reactor/controllers/concerns/actions/show_event.rb +0 -9
- data/lib/reactor/controllers/concerns/actions/update_event.rb +0 -18
- data/lib/reactor/controllers/concerns/resource_actionable.rb +0 -55
- data/lib/reactor/testing.rb +0 -50
- data/spec/controllers/concerns/resource_actionable_spec.rb +0 -140
- data/spec/reactor_spec.rb +0 -59
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8bbbeb29447a373e5c2d652170a9d9f57d2251b7
|
4
|
+
data.tar.gz: 8034ad54e39372f66f0f13255265e2ac77ccf954
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2c1f561926d09bd84826533eab277b0cfb69bc2efe5c20a54b3f2e3ecfe217aeb52cff776d36e4c8174a5348d51e293323d2968f08cf23c7221cdfb57e5cb68e
|
7
|
+
data.tar.gz: 5d089b23eefc52622a1fc29db4dbf2c5f17392c46373836d7411517cadf39b15d8b17cc24dc3e5a5ec92e0508b998775f8a5dcaf667db85d8835e1119d4267b2
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -87,15 +87,7 @@ on_event :any_event do |event|
|
|
87
87
|
end
|
88
88
|
```
|
89
89
|
|
90
|
-
Static subscribers like these are automatically placed into Sidekiq and executed in the background
|
91
|
-
|
92
|
-
It's also possible to run a subscriber block in memory like so
|
93
|
-
|
94
|
-
```ruby
|
95
|
-
on_event :any_event, in_memory: true do |event|
|
96
|
-
event.target.do_something_about_it_and_make_the_user_wait!
|
97
|
-
end
|
98
|
-
```
|
90
|
+
Static subscribers like these are automatically placed into Sidekiq and executed in the background.
|
99
91
|
|
100
92
|
You may also have Sidekiq process a subscriber block on a specific queue or supply any other Sidekiq::Worker options accordingly.
|
101
93
|
|
@@ -104,132 +96,13 @@ on_event :event_with_ui_bound, sidekiq_options: { queue: 'highest_priority' } do
|
|
104
96
|
speedily_execute!
|
105
97
|
end
|
106
98
|
```
|
99
|
+
### Automatic Events
|
107
100
|
|
108
|
-
|
101
|
+
If you'd like to have events automatically fired for you around standard rails resource controller actions,
|
102
|
+
you may want to write your own downstream abstraction for it. We attempted this once and it seems unwise to presume your application would want the same thing.
|
109
103
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
```ruby
|
114
|
-
class PetsController < ApplicationController
|
115
|
-
include Reactor::ResourceActionable
|
116
|
-
actionable_resource :@pet
|
117
|
-
|
118
|
-
# GET /pets
|
119
|
-
# GET /pets.json
|
120
|
-
def index
|
121
|
-
@pets = current_user.pets
|
122
|
-
|
123
|
-
respond_to do |format|
|
124
|
-
format.html # index.html.erb
|
125
|
-
format.json { render json: @pets }
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
def show
|
130
|
-
@pet = current_user.pets.find(params[:id])
|
131
|
-
respond_to do |format|
|
132
|
-
format.html # index.html.erb
|
133
|
-
format.json { render json: @pet }
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
```
|
139
|
-
|
140
|
-
Now your index action (and any of the other RESTful actions in that controller) will fire a useful event for you to bind to and log.
|
141
|
-
|
142
|
-
*Important* Reactor::ResourceActionable has one major usage constraints:
|
143
|
-
|
144
|
-
Your controller *must* have a method called "action_event" with this signature.
|
145
|
-
```ruby
|
146
|
-
def action_event(name, options = {})
|
147
|
-
# Here's what ours looks like, but yours may look different.
|
148
|
-
actor = options[:actor] || current_user
|
149
|
-
actor.publish(name, options.merge(default_action_parameters))
|
150
|
-
#where default_action_parameters includes things like ip_address, referrer, user_agent
|
151
|
-
end
|
152
|
-
```
|
153
|
-
|
154
|
-
Once you write your own action_event to describe your event data model's base attributes, your ResourceActionable endpoints will now fire events that map like so (for the example above):
|
155
|
-
|
156
|
-
<dl>
|
157
|
-
<dt>index =></dt>
|
158
|
-
<dd>"pets_indexed"</dd>
|
159
|
-
</dl>
|
160
|
-
|
161
|
-
<dl>
|
162
|
-
<dt>show =></dt>
|
163
|
-
<dd>"pet_viewed", target: @pet</dd>
|
164
|
-
</dl>
|
165
|
-
|
166
|
-
<dl>
|
167
|
-
<dt>new =></dt>
|
168
|
-
<dd>"new_pet_form_viewed"</dd>
|
169
|
-
</dl>
|
170
|
-
|
171
|
-
<dl>
|
172
|
-
<dt>edit =></dt>
|
173
|
-
<dd> "edit_pet_form_viewed", target: @pet</dd>
|
174
|
-
</dl>
|
175
|
-
|
176
|
-
<dl>
|
177
|
-
<dt>create =></dt>
|
178
|
-
<dd> when valid => "pet_created", target: @pet, attributes: params[:pet]
|
179
|
-
<br />
|
180
|
-
when invalid => "pet_create_failed", errors: @pet.errors, attributes: params[:pet]</dd>
|
181
|
-
</dl>
|
182
|
-
|
183
|
-
<dl>
|
184
|
-
<dt>update =></dt>
|
185
|
-
<dd>
|
186
|
-
when valid => "pet_updated", target: @pet, changes: @pet.previous_changes.as_json
|
187
|
-
<br />
|
188
|
-
when invalid => "pet_update_failed", target: @pet,
|
189
|
-
errors: @pet.errors.as_json, attributes: params[:pet]
|
190
|
-
</dd>
|
191
|
-
</dl>
|
192
|
-
|
193
|
-
<dl>
|
194
|
-
<dt>destroy =></dt>
|
195
|
-
<dd>"pet_destroyed", last_snapshot: @pet.as_jsont</dd>
|
196
|
-
</dl>
|
197
|
-
|
198
|
-
|
199
|
-
##### What for?
|
200
|
-
|
201
|
-
If you're obsessive about data like us, you'll have written a '*' subscriber that logs every event fired in the system. With information-dense resource information logged for each action a user performs, it will be trivial for a data analyst to determine patterns in user activity. For example, with the above data being logged for the pet resource, we can easily
|
202
|
-
* determine which form field validations are constantly being hit by users
|
203
|
-
* see if there are any fields that are consistently ignored on that form until later
|
204
|
-
* recover data from the last_snapshot of a destroyed record
|
205
|
-
* write a small conversion funnel analysis to see who never makes it back to a record to update it
|
206
|
-
* bind arbitrary logic anywhere in the codebase (see next example) to that specific request without worrying about the logic being run during the request (all listeners are run in the background by Sidekiq)
|
207
|
-
|
208
|
-
For example, in an action mailer.
|
209
|
-
|
210
|
-
```ruby
|
211
|
-
class MyMailer < ActionMailer::Base
|
212
|
-
include Reactor::EventMailer
|
213
|
-
|
214
|
-
on_event :pet_created do |event|
|
215
|
-
@user = event.actor
|
216
|
-
@pet = event.target
|
217
|
-
mail to: @user.email, subject: "Your pet is already hungry!", body: "feed it."
|
218
|
-
end
|
219
|
-
end
|
220
|
-
```
|
221
|
-
|
222
|
-
Or in a model, concern, or other business logic file.
|
223
|
-
|
224
|
-
```ruby
|
225
|
-
class MyClass
|
226
|
-
include Reactor::Subscribable
|
227
|
-
|
228
|
-
on_event :pet_updated do |event|
|
229
|
-
event.actor.recalculate_expensive_something_for(event.target)
|
230
|
-
end
|
231
|
-
end
|
232
|
-
```
|
104
|
+
It probably only makes sense if you have a real Magestic Monolith and an intentionally small team because coupling resource names to event streams makes refactoring harder.
|
105
|
+
(Though it may still be worth it for you, depending on your needs!)
|
233
106
|
|
234
107
|
### Testing
|
235
108
|
|
@@ -266,12 +139,30 @@ expect { some_thing }.to publish_events(:some_event, :another_event)
|
|
266
139
|
|
267
140
|
### Production Deployments
|
268
141
|
|
269
|
-
TLDR; Everything is a Sidekiq::
|
142
|
+
TLDR; Everything is a Sidekiq::Worker, so all the same gotchas apply with regard to removing & renaming jobs that may have a live reference sitting in the queue. (AKA, you'll start seeing 'const undefined' exceptions when the job gets picked up if you've already deleted/renamed the job code.)
|
270
143
|
|
271
144
|
#### Adding Events and Subscribers
|
272
145
|
|
273
146
|
This is as easy as write + deploy. Of course your events getting fired won't have a subscriber pick them up until the new subscriber code is deployed in your sidekiq instances, but that's not too surprising.
|
274
147
|
|
148
|
+
#### Validating Events On Publish
|
149
|
+
|
150
|
+
As of 1.0 you may inject your own validator lambda to handle the logic and flow-control of valid/invalid events.
|
151
|
+
|
152
|
+
This is entirely optional and the default behavior is to do nothing, to not validate any data being provided.
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
# in config/initializers/reactor.rb
|
156
|
+
Reactor.validator -> do |event|
|
157
|
+
Activity.build_from_event(event).validate! # you own the performance implications here
|
158
|
+
end
|
159
|
+
```
|
160
|
+
|
161
|
+
We at Hired use this to validate the event's schema as we found having stricter schema definitions
|
162
|
+
gave us more leverage as our team grew.
|
163
|
+
|
164
|
+
By injecting your own logic, you can be as permissive or strict as you want. (Throw exceptions if you want, even.)
|
165
|
+
|
275
166
|
#### Removing Events and Subscribers
|
276
167
|
|
277
168
|
Removing an event is as simple as deleting the line of code that `publish`es it.
|
data/lib/reactor.rb
CHANGED
@@ -9,14 +9,11 @@ require "reactor/workers/concerns/configuration"
|
|
9
9
|
require "reactor/workers"
|
10
10
|
require "reactor/subscription"
|
11
11
|
require "reactor/models"
|
12
|
-
require "reactor/controllers"
|
13
12
|
require "reactor/event"
|
14
13
|
|
15
|
-
# FIXME: should only be included in test environments
|
16
|
-
require "reactor/testing"
|
17
|
-
|
18
14
|
module Reactor
|
19
15
|
SUBSCRIBERS = {}.with_indifferent_access
|
16
|
+
BASE_VALIDATOR = -> (_event) { } # default behavior is to not actually validate anything
|
20
17
|
|
21
18
|
module_function
|
22
19
|
|
@@ -36,4 +33,17 @@ module Reactor
|
|
36
33
|
def subscriber_namespace
|
37
34
|
Reactor::StaticSubscribers
|
38
35
|
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# If you want, you can inject your own validator block in `config/initializers/reactor.rb`
|
39
|
+
#
|
40
|
+
# Reactor.validator -> (event) { Validator.new(event).validate! }
|
41
|
+
#
|
42
|
+
# If not, the default behavior is to do nothing. (see Reactor::BASE_VALIDATOR)
|
43
|
+
#
|
44
|
+
def validator(block = nil)
|
45
|
+
@validator = block if block.present?
|
46
|
+
|
47
|
+
@validator || BASE_VALIDATOR
|
48
|
+
end
|
39
49
|
end
|
data/lib/reactor/event.rb
CHANGED
@@ -63,8 +63,11 @@ class Reactor::Event
|
|
63
63
|
if defined?(Rails::Console) && ENV['RACK_ENV'] == 'production' && data[:srsly].blank?
|
64
64
|
raise ArgumentError.new(CONSOLE_CONFIRMATION_MESSAGE)
|
65
65
|
end
|
66
|
+
|
66
67
|
message = new(data.merge(event: name, uuid: SecureRandom.uuid))
|
67
68
|
|
69
|
+
Reactor.validator.call(message)
|
70
|
+
|
68
71
|
if message.at
|
69
72
|
perform_at message.at, name, message.__data__
|
70
73
|
else
|
@@ -124,6 +127,8 @@ class Reactor::Event
|
|
124
127
|
end
|
125
128
|
|
126
129
|
def fire_block_subscribers(data, name)
|
127
|
-
((Reactor::SUBSCRIBERS[name.to_s] || []) | (Reactor::SUBSCRIBERS['*'] || [])).each
|
130
|
+
((Reactor::SUBSCRIBERS[name.to_s] || []) | (Reactor::SUBSCRIBERS['*'] || [])).each do |s|
|
131
|
+
s.perform_where_needed(data)
|
132
|
+
end
|
128
133
|
end
|
129
134
|
end
|
data/lib/reactor/subscription.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Reactor
|
2
2
|
class Subscription
|
3
3
|
|
4
|
-
attr_reader :source, :event_name, :action, :handler_name, :delay, :
|
4
|
+
attr_reader :source, :event_name, :action, :handler_name, :delay, :worker_class,
|
5
5
|
:deprecated, :sidekiq_options
|
6
6
|
|
7
7
|
def self.build_handler_name(event_name, handler_name_option = nil)
|
@@ -24,7 +24,6 @@ module Reactor
|
|
24
24
|
@action = options[:action] || block
|
25
25
|
|
26
26
|
@delay = options[:delay].to_i
|
27
|
-
@async = determine_async(options)
|
28
27
|
@deprecated = !!options[:deprecated]
|
29
28
|
@sidekiq_options = options[:sidekiq_options] || {}
|
30
29
|
build_worker_class
|
@@ -41,13 +40,16 @@ module Reactor
|
|
41
40
|
|
42
41
|
def namespace
|
43
42
|
return @namespace if @namespace
|
43
|
+
@namespace = generate_namespace
|
44
|
+
end
|
44
45
|
|
45
|
-
|
46
|
-
|
47
|
-
|
46
|
+
def generate_namespace
|
47
|
+
module_chain.reduce(Reactor.subscriber_namespace) do |mod, name|
|
48
|
+
unless mod.const_defined?(name, false)
|
49
|
+
mod.const_set(name, Module.new)
|
50
|
+
end
|
51
|
+
mod.const_get(name)
|
48
52
|
end
|
49
|
-
|
50
|
-
@namespace = Reactor.subscriber_namespace.const_get(ns, false)
|
51
53
|
end
|
52
54
|
|
53
55
|
def mailer_subscriber?
|
@@ -56,24 +58,14 @@ module Reactor
|
|
56
58
|
|
57
59
|
private
|
58
60
|
|
59
|
-
|
60
|
-
|
61
|
-
def determine_async(options = {})
|
62
|
-
if options[:async].nil?
|
63
|
-
if options[:in_memory].nil?
|
64
|
-
true
|
65
|
-
else
|
66
|
-
!options[:in_memory]
|
67
|
-
end
|
68
|
-
else
|
69
|
-
!!options[:async]
|
70
|
-
end
|
61
|
+
def module_chain
|
62
|
+
source.name.split('::')
|
71
63
|
end
|
72
64
|
|
73
65
|
def build_worker_class
|
74
|
-
namespace.send(:remove_const, handler_name) if handler_defined?
|
75
66
|
|
76
67
|
worker_class = mailer_subscriber? ? build_mailer_worker : build_event_worker
|
68
|
+
namespace.send(:remove_const, handler_name) if handler_defined?
|
77
69
|
namespace.const_set(handler_name, worker_class)
|
78
70
|
@worker_class = namespace.const_get(handler_name)
|
79
71
|
end
|
@@ -83,7 +75,6 @@ module Reactor
|
|
83
75
|
Class.new(Reactor::Workers::EventWorker) do
|
84
76
|
self.source = subscription.source
|
85
77
|
self.action = subscription.action
|
86
|
-
self.async = subscription.async
|
87
78
|
self.delay = subscription.delay
|
88
79
|
self.deprecated = subscription.deprecated
|
89
80
|
self.sidekiq_options subscription.sidekiq_options
|
@@ -96,7 +87,6 @@ module Reactor
|
|
96
87
|
self.source = subscription.source
|
97
88
|
self.action = subscription.action
|
98
89
|
self.delay = subscription.delay
|
99
|
-
self.async = subscription.async
|
100
90
|
self.deprecated = subscription.deprecated
|
101
91
|
self.sidekiq_options subscription.sidekiq_options
|
102
92
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
#
|
2
|
+
# Run this before specs if you want to speed up tests by trading out code coverage into subscribers
|
3
|
+
#
|
4
|
+
def stub_reactor_subscribers
|
5
|
+
Reactor::SUBSCRIBERS.each do |_event, subscribers|
|
6
|
+
subscribers.each do |subscriber|
|
7
|
+
allow(subscriber).to receive(:perform_where_needed)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
#
|
13
|
+
# If stubbing out reactor in test, use this method to re-enable a specific subscriber
|
14
|
+
# to test its logic.
|
15
|
+
#
|
16
|
+
def allow_reactor_subscriber(subscribable_class)
|
17
|
+
worker_module_name = "Reactor::StaticSubscribers::#{subscribable_class}"
|
18
|
+
worker_module_name.safe_constantize.constants.each do |worker_class_name|
|
19
|
+
worker_class = "#{worker_module_name}::#{worker_class_name}".safe_constantize
|
20
|
+
allow(worker_class).to receive(:perform_where_needed).and_call_original
|
21
|
+
end
|
22
|
+
|
23
|
+
yield if block_given? # yes you can use block syntax if you want
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# If you publish events in ActiveRecord lifecycle hooks, you're gonna have a bad time.
|
28
|
+
#
|
29
|
+
# But inevitably it may make sense for you (yay software), in which case you may want to
|
30
|
+
# disable a subscriber if you're testing logic around it.
|
31
|
+
#
|
32
|
+
def disable_reactor_subscriber(subscribable_class)
|
33
|
+
worker_module_name = "Reactor::StaticSubscribers::#{subscribable_class}"
|
34
|
+
worker_module_name.safe_constantize.constants.each do |worker_class_name|
|
35
|
+
worker_class = "#{worker_module_name}::#{worker_class_name}".safe_constantize
|
36
|
+
allow(worker_class).to receive(:perform_where_needed).and_return(nil)
|
37
|
+
end
|
38
|
+
|
39
|
+
if block_given? # yes you can use block syntax if you want
|
40
|
+
begin
|
41
|
+
yield
|
42
|
+
ensure
|
43
|
+
allow_reactor_subscriber(subscribable_class) # and if you do, expect it to be re-enabled after
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/reactor/version.rb
CHANGED
@@ -3,7 +3,7 @@ module Reactor
|
|
3
3
|
module Configuration
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
|
-
CONFIG = [:source, :action, :
|
6
|
+
CONFIG = [:source, :action, :delay, :deprecated]
|
7
7
|
|
8
8
|
included do
|
9
9
|
include Sidekiq::Worker
|
@@ -21,10 +21,8 @@ module Reactor
|
|
21
21
|
return
|
22
22
|
elsif delay > 0
|
23
23
|
event_queue.perform_in(delay, data)
|
24
|
-
elsif async
|
25
|
-
event_queue.perform_async(data)
|
26
24
|
else
|
27
|
-
|
25
|
+
event_queue.perform_async(data)
|
28
26
|
end
|
29
27
|
source
|
30
28
|
end
|
@@ -51,11 +49,7 @@ module Reactor
|
|
51
49
|
end
|
52
50
|
|
53
51
|
def should_perform?
|
54
|
-
|
55
|
-
Reactor.test_mode_subscriber_enabled? source
|
56
|
-
else
|
57
|
-
true
|
58
|
-
end
|
52
|
+
true
|
59
53
|
end
|
60
54
|
|
61
55
|
private
|
data/spec/event_spec.rb
CHANGED
@@ -25,6 +25,7 @@ class OtherWorker
|
|
25
25
|
end
|
26
26
|
|
27
27
|
describe Reactor::Event do
|
28
|
+
before { stub_reactor_subscribers }
|
28
29
|
|
29
30
|
let(:model) { ArbitraryModel.create! }
|
30
31
|
let(:event_name) { :user_did_this }
|
@@ -77,24 +78,32 @@ describe Reactor::Event do
|
|
77
78
|
ENV['RACK_ENV'] = 'development'
|
78
79
|
end
|
79
80
|
end
|
81
|
+
|
82
|
+
describe 'using injected validator block' do
|
83
|
+
before { Reactor.validator -> (_event) {raise 'InvalidEvent'} }
|
84
|
+
after { Reactor.validator Reactor::BASE_VALIDATOR }
|
85
|
+
|
86
|
+
it 'gets called and yields flow control' do
|
87
|
+
expect {
|
88
|
+
Reactor::Event.publish(:something)
|
89
|
+
}.to raise_error(RuntimeError, 'InvalidEvent')
|
90
|
+
end
|
91
|
+
end
|
80
92
|
end
|
81
93
|
|
82
94
|
describe 'perform' do
|
83
|
-
# before do
|
84
|
-
# allow(Reactor::Event).
|
85
|
-
# end
|
86
95
|
let(:event_name) { :barfed }
|
87
96
|
|
88
97
|
it 'fires all subscribers' do
|
89
98
|
expect(Reactor::StaticSubscribers::ArbitraryModel::BarfedHandler).
|
90
|
-
to receive(:
|
99
|
+
to receive(:perform_where_needed).with(hash_including(actor_id: model.id.to_s))
|
91
100
|
|
92
101
|
Reactor::Event.perform(event_name, actor_id: model.id.to_s, actor_type: model.class.to_s)
|
93
102
|
end
|
94
103
|
|
95
104
|
it 'sets a fired_at key in event data' do
|
96
105
|
expect(Reactor::StaticSubscribers::ArbitraryModel::BarfedHandler).
|
97
|
-
to receive(:
|
106
|
+
to receive(:perform_where_needed).with(hash_including(fired_at: a_kind_of(Time)))
|
98
107
|
|
99
108
|
Reactor::Event.perform(event_name, actor_id: model.id.to_s, actor_type: model.class.to_s)
|
100
109
|
end
|
@@ -103,7 +112,7 @@ describe Reactor::Event do
|
|
103
112
|
it 'doesnt matter because it runs in a separate worker process' do
|
104
113
|
expect {
|
105
114
|
Reactor::Event.perform(
|
106
|
-
|
115
|
+
event_name,
|
107
116
|
somethin: 'up',
|
108
117
|
actor_id: model.id.to_s,
|
109
118
|
actor_type: model.class.to_s
|
@@ -81,7 +81,6 @@ describe Reactor::Publishable do
|
|
81
81
|
before do
|
82
82
|
Sidekiq::Testing.fake!
|
83
83
|
Sidekiq::Worker.clear_all
|
84
|
-
# TestSubscriber.create! event_name: :conditional_event_on_publish
|
85
84
|
publisher
|
86
85
|
job = Reactor::Event.jobs.detect do |job|
|
87
86
|
job['class'] == 'Reactor::Event' && job['args'].first == 'conditional_event_on_publish'
|
@@ -15,10 +15,6 @@ class Auction < ApplicationRecord
|
|
15
15
|
event.actor.more_puppies! if event.name == 'another_event'
|
16
16
|
end
|
17
17
|
|
18
|
-
on_event :cat_delivered, async: false do |event|
|
19
|
-
puppies!
|
20
|
-
end
|
21
|
-
|
22
18
|
on_event :a_high_frequency_event, deprecated: true do |event|
|
23
19
|
raise 'hell'
|
24
20
|
end
|
@@ -49,7 +45,7 @@ class KittenMailer < ActionMailer::Base
|
|
49
45
|
include Reactor::Subscribable
|
50
46
|
|
51
47
|
on_event :auction, handler_name: 'auction' do |event|
|
52
|
-
|
48
|
+
@@fired = true
|
53
49
|
end
|
54
50
|
|
55
51
|
on_event :kitten_streaming do |event|
|
@@ -67,20 +63,14 @@ class KittenMailer < ActionMailer::Base
|
|
67
63
|
end
|
68
64
|
end
|
69
65
|
|
70
|
-
|
71
|
-
|
72
|
-
on_event :test_puppy_delivered, -> (event) { "success" }
|
73
|
-
end
|
66
|
+
class TestModeAuction < ApplicationRecord
|
67
|
+
on_event :test_puppy_delivered, -> (event) { "success" }
|
74
68
|
end
|
75
69
|
|
76
70
|
describe Reactor::Subscribable do
|
77
71
|
let(:scheduled) { Sidekiq::ScheduledSet.new }
|
78
72
|
|
79
73
|
describe 'on_event' do
|
80
|
-
before do
|
81
|
-
Reactor.enable_test_mode_subscriber(Auction)
|
82
|
-
end
|
83
|
-
|
84
74
|
it 'binds block of code statically to event being fired' do
|
85
75
|
expect_any_instance_of(Auction).to receive(:update_column).with(:status, 'first_bid_made')
|
86
76
|
Reactor::Event.publish(:bid_made, target: Auction.create!(start_at: 10.minutes.from_now))
|
@@ -92,8 +82,8 @@ describe Reactor::Subscribable do
|
|
92
82
|
expect(Reactor::SUBSCRIBERS['puppy_delivered'][1]).to eq(Reactor::StaticSubscribers::Auction::DoNothingHandler)
|
93
83
|
end
|
94
84
|
|
95
|
-
it 'adds a static subscriber for namespaced classes' do
|
96
|
-
expect(Reactor::SUBSCRIBERS['rain'][0]).to eq(Reactor::StaticSubscribers::MyClass::RainHandler)
|
85
|
+
it 'adds a static subscriber for namespaced classes at the same module depth' do
|
86
|
+
expect(Reactor::SUBSCRIBERS['rain'][0]).to eq(Reactor::StaticSubscribers::MyNamespace::MyClass::RainHandler)
|
97
87
|
end
|
98
88
|
end
|
99
89
|
|
@@ -101,10 +91,16 @@ describe Reactor::Subscribable do
|
|
101
91
|
let(:pooped_handler) { Reactor::StaticSubscribers::Auction::PoopedHandler }
|
102
92
|
|
103
93
|
it 'fires on event' do
|
94
|
+
expect(Reactor::StaticSubscribers::Auction::WildcardHandler).
|
95
|
+
to receive(:perform_async).and_call_original
|
104
96
|
expect(Auction).to receive(:ring_bell)
|
105
97
|
Reactor::Event.publish(:puppy_delivered)
|
106
98
|
end
|
107
99
|
|
100
|
+
it 'fires perform_async when true / default' do
|
101
|
+
Reactor::Event.publish(:puppy_delivered)
|
102
|
+
end
|
103
|
+
|
108
104
|
it 'can be delayed' do
|
109
105
|
expect(Auction).to receive(:pick_up_poop)
|
110
106
|
expect(pooped_handler).to receive(:perform_in).with(5.minutes, anything).and_call_original
|
@@ -132,19 +128,6 @@ describe Reactor::Subscribable do
|
|
132
128
|
expect { Reactor::Event.publish :auction }.not_to raise_error
|
133
129
|
end
|
134
130
|
|
135
|
-
describe 'async flag' do
|
136
|
-
it 'doesnt fire perform_async when false' do
|
137
|
-
expect(Auction).to receive(:puppies!)
|
138
|
-
expect(Reactor::StaticSubscribers::Auction::CatDeliveredHandler).not_to receive(:perform_async)
|
139
|
-
Reactor::Event.publish(:cat_delivered)
|
140
|
-
end
|
141
|
-
|
142
|
-
it 'fires perform_async when true / default' do
|
143
|
-
expect(Reactor::StaticSubscribers::Auction::WildcardHandler).to receive(:perform_async)
|
144
|
-
Reactor::Event.publish(:puppy_delivered)
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
131
|
describe 'deprecate flag for high-frequency events in production deployments' do
|
149
132
|
it 'doesnt enqueue subscriber worker when true' do
|
150
133
|
# so subscriber can be safely deleted in next deploy
|
@@ -182,17 +165,8 @@ describe Reactor::Subscribable do
|
|
182
165
|
end
|
183
166
|
|
184
167
|
describe '#perform' do
|
185
|
-
around(:each) do |example|
|
186
|
-
Reactor.in_test_mode { example.run }
|
187
|
-
end
|
188
|
-
|
189
|
-
it 'returns :__perform_aborted__ when Reactor is in test mode' do
|
190
|
-
expect(Reactor::StaticSubscribers::TestModeAuction::TestPuppyDeliveredHandler.new.perform({})).to eq(:__perform_aborted__)
|
191
|
-
Reactor::Event.publish(:test_puppy_delivered)
|
192
|
-
end
|
193
|
-
|
194
168
|
it 'performs normally when specifically enabled' do
|
195
|
-
|
169
|
+
allow_reactor_subscriber(TestModeAuction) do
|
196
170
|
expect(Reactor::StaticSubscribers::TestModeAuction::TestPuppyDeliveredHandler.new.perform({})).not_to eq(:__perform_aborted__)
|
197
171
|
Reactor::Event.publish(:test_puppy_delivered)
|
198
172
|
end
|
@@ -201,8 +175,6 @@ describe Reactor::Subscribable do
|
|
201
175
|
end
|
202
176
|
|
203
177
|
describe 'mailers', type: :mailer do
|
204
|
-
before { Reactor.enable_test_mode_subscriber KittenMailer }
|
205
|
-
after { Reactor.disable_test_mode_subscriber KittenMailer }
|
206
178
|
|
207
179
|
def deliveries
|
208
180
|
ActionMailer::Base.deliveries
|
data/spec/spec_helper.rb
CHANGED
@@ -15,6 +15,7 @@ end
|
|
15
15
|
|
16
16
|
require 'reactor'
|
17
17
|
require 'reactor/testing/matchers'
|
18
|
+
require 'reactor/testing/stubs'
|
18
19
|
|
19
20
|
require 'rspec/its'
|
20
21
|
|
@@ -53,8 +54,6 @@ RSpec.configure do |config|
|
|
53
54
|
# some (optional) config here
|
54
55
|
|
55
56
|
config.before(:each) do
|
56
|
-
Reactor.test_mode!
|
57
|
-
Reactor.clear_test_subscribers!
|
58
57
|
ActionMailer::Base.deliveries.clear
|
59
58
|
end
|
60
59
|
|
data/spec/subscription_spec.rb
CHANGED
@@ -2,6 +2,31 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Reactor::Subscription do
|
4
4
|
|
5
|
+
describe '#initialize builds a worker class' do
|
6
|
+
subject! do
|
7
|
+
described_class.new(source: Pet, event_name: :pooped) do
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
specify do
|
12
|
+
expect(Reactor::StaticSubscribers::Pet::PoopedHandler < Reactor::Workers::EventWorker)
|
13
|
+
.to be true
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'when subscriber object has namespace of arbitrary length' do
|
17
|
+
subject! do
|
18
|
+
described_class.new(source: Reactor::Subscription, event_name: :pooped) do
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
specify do
|
23
|
+
expect(Reactor::StaticSubscribers::Reactor::Subscription::PoopedHandler <
|
24
|
+
Reactor::Workers::EventWorker)
|
25
|
+
.to be true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
5
30
|
describe '.build_handler_name' do
|
6
31
|
let(:event_name) { :kitten_sleeping }
|
7
32
|
|
@@ -24,7 +24,7 @@ shared_examples_for 'configurable subscriber worker' do
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
context 'for
|
27
|
+
context 'for not delayed workers' do
|
28
28
|
let(:klass) { MyEventWorker }
|
29
29
|
subject { klass.perform_where_needed(event_data) }
|
30
30
|
|
@@ -33,15 +33,5 @@ shared_examples_for 'configurable subscriber worker' do
|
|
33
33
|
subject
|
34
34
|
end
|
35
35
|
end
|
36
|
-
|
37
|
-
context 'for immediate workers' do
|
38
|
-
let(:klass) { MyImmediateWorker }
|
39
|
-
subject { klass.perform_where_needed(event_data) }
|
40
|
-
|
41
|
-
it 'creates and executes new instance' do
|
42
|
-
expect_any_instance_of(klass).to receive(:perform).with(event_data)
|
43
|
-
subject
|
44
|
-
end
|
45
|
-
end
|
46
36
|
end
|
47
37
|
end
|
@@ -14,7 +14,6 @@ end
|
|
14
14
|
class MyEventWorker < Reactor::Workers::EventWorker
|
15
15
|
self.source = SourceSubscriber
|
16
16
|
self.action = :fire_worker
|
17
|
-
self.async = true
|
18
17
|
self.delay = 0
|
19
18
|
self.deprecated = false
|
20
19
|
end
|
@@ -22,7 +21,6 @@ end
|
|
22
21
|
class MyBlockWorker < Reactor::Workers::EventWorker
|
23
22
|
self.source = SourceSubscriber
|
24
23
|
self.action = lambda { |event| :block_ran }
|
25
|
-
self.async = true
|
26
24
|
self.delay = 0
|
27
25
|
self.deprecated = false
|
28
26
|
end
|
@@ -30,19 +28,10 @@ end
|
|
30
28
|
class MyDelayedWorker < Reactor::Workers::EventWorker
|
31
29
|
self.source = SourceSubscriber
|
32
30
|
self.action = :fire_worker
|
33
|
-
self.async = true
|
34
31
|
self.delay = 1 # seconds
|
35
32
|
self.deprecated = false
|
36
33
|
end
|
37
34
|
|
38
|
-
class MyImmediateWorker < Reactor::Workers::EventWorker
|
39
|
-
self.source = SourceSubscriber
|
40
|
-
self.action = :fire_worker
|
41
|
-
self.async = false
|
42
|
-
self.delay = 0
|
43
|
-
self.deprecated = false
|
44
|
-
end
|
45
|
-
|
46
35
|
describe Reactor::Workers::EventWorker do
|
47
36
|
let(:event_name) { :fire_worker }
|
48
37
|
let(:event_data) { Hash[my_event_data: true] }
|
@@ -61,25 +50,15 @@ describe Reactor::Workers::EventWorker do
|
|
61
50
|
end
|
62
51
|
end
|
63
52
|
|
64
|
-
|
65
|
-
let(:klass) { MyEventWorker }
|
53
|
+
before { allow_any_instance_of(klass).to receive(:should_perform?).and_return(true) }
|
66
54
|
|
67
|
-
|
68
|
-
|
69
|
-
end
|
55
|
+
it 'calls class method by symbol' do
|
56
|
+
expect(subject).to eq(:method_called)
|
70
57
|
end
|
71
58
|
|
72
|
-
context '
|
73
|
-
|
74
|
-
|
75
|
-
it 'calls class method by symbol' do
|
76
|
-
expect(subject).to eq(:method_called)
|
77
|
-
end
|
78
|
-
|
79
|
-
context 'for block workers' do
|
80
|
-
let(:klass) { MyBlockWorker }
|
81
|
-
it { is_expected.to eq(:block_ran) }
|
82
|
-
end
|
59
|
+
context 'for block workers' do
|
60
|
+
let(:klass) { MyBlockWorker }
|
61
|
+
it { is_expected.to eq(:block_ran) }
|
83
62
|
end
|
84
63
|
end
|
85
64
|
|
@@ -14,14 +14,12 @@ end
|
|
14
14
|
class MyMailerWorker < Reactor::Workers::MailerWorker
|
15
15
|
self.source = MailerSubscriber
|
16
16
|
self.action = :fire_mailer
|
17
|
-
self.async = false
|
18
17
|
self.delay = 0
|
19
18
|
self.deprecated = false
|
20
19
|
end
|
21
20
|
|
22
21
|
class MyBlockMailerWorker < Reactor::Workers::MailerWorker
|
23
22
|
self.source = MailerSubscriber
|
24
|
-
self.async = false
|
25
23
|
self.delay = 0
|
26
24
|
self.action = lambda { |event| fire_mailer(event) }
|
27
25
|
self.deprecated = false
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: reactor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- winfred
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2018-
|
14
|
+
date: 2018-03-07 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: rails
|
@@ -205,16 +205,6 @@ files:
|
|
205
205
|
- gemfiles/sidekiq_4.gemfile
|
206
206
|
- gemfiles/sidekiq_5.gemfile
|
207
207
|
- lib/reactor.rb
|
208
|
-
- lib/reactor/controllers.rb
|
209
|
-
- lib/reactor/controllers/concerns/actions/action_event.rb
|
210
|
-
- lib/reactor/controllers/concerns/actions/create_event.rb
|
211
|
-
- lib/reactor/controllers/concerns/actions/destroy_event.rb
|
212
|
-
- lib/reactor/controllers/concerns/actions/edit_event.rb
|
213
|
-
- lib/reactor/controllers/concerns/actions/index_event.rb
|
214
|
-
- lib/reactor/controllers/concerns/actions/new_event.rb
|
215
|
-
- lib/reactor/controllers/concerns/actions/show_event.rb
|
216
|
-
- lib/reactor/controllers/concerns/actions/update_event.rb
|
217
|
-
- lib/reactor/controllers/concerns/resource_actionable.rb
|
218
208
|
- lib/reactor/errors.rb
|
219
209
|
- lib/reactor/event.rb
|
220
210
|
- lib/reactor/models.rb
|
@@ -222,19 +212,17 @@ files:
|
|
222
212
|
- lib/reactor/models/concerns/subscribable.rb
|
223
213
|
- lib/reactor/static_subscribers.rb
|
224
214
|
- lib/reactor/subscription.rb
|
225
|
-
- lib/reactor/testing.rb
|
226
215
|
- lib/reactor/testing/matchers.rb
|
216
|
+
- lib/reactor/testing/stubs.rb
|
227
217
|
- lib/reactor/version.rb
|
228
218
|
- lib/reactor/workers.rb
|
229
219
|
- lib/reactor/workers/concerns/configuration.rb
|
230
220
|
- lib/reactor/workers/event_worker.rb
|
231
221
|
- lib/reactor/workers/mailer_worker.rb
|
232
222
|
- reactor.gemspec
|
233
|
-
- spec/controllers/concerns/resource_actionable_spec.rb
|
234
223
|
- spec/event_spec.rb
|
235
224
|
- spec/models/concerns/publishable_spec.rb
|
236
225
|
- spec/models/concerns/subscribable_spec.rb
|
237
|
-
- spec/reactor_spec.rb
|
238
226
|
- spec/spec_helper.rb
|
239
227
|
- spec/subscription_spec.rb
|
240
228
|
- spec/support/active_record.rb
|
@@ -266,11 +254,9 @@ signing_key:
|
|
266
254
|
specification_version: 4
|
267
255
|
summary: Sidekiq/ActiveRecord pubsub lib
|
268
256
|
test_files:
|
269
|
-
- spec/controllers/concerns/resource_actionable_spec.rb
|
270
257
|
- spec/event_spec.rb
|
271
258
|
- spec/models/concerns/publishable_spec.rb
|
272
259
|
- spec/models/concerns/subscribable_spec.rb
|
273
|
-
- spec/reactor_spec.rb
|
274
260
|
- spec/spec_helper.rb
|
275
261
|
- spec/subscription_spec.rb
|
276
262
|
- spec/support/active_record.rb
|
data/lib/reactor/controllers.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
module Reactor
|
2
|
-
module ResourceActionable
|
3
|
-
class CreateEvent < ActionEvent
|
4
|
-
perform do
|
5
|
-
if actionable_resource.valid?
|
6
|
-
action_event "#{resource_name}_created",
|
7
|
-
target: actionable_resource,
|
8
|
-
attributes: params[resource_name]
|
9
|
-
else
|
10
|
-
action_event "#{resource_name}_create_failed",
|
11
|
-
errors: actionable_resource.errors.as_json,
|
12
|
-
attributes: params[resource_name],
|
13
|
-
target: nested_resource
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
@@ -1,18 +0,0 @@
|
|
1
|
-
module Reactor
|
2
|
-
module ResourceActionable
|
3
|
-
class UpdateEvent < ActionEvent
|
4
|
-
perform do
|
5
|
-
if actionable_resource.valid?
|
6
|
-
action_event "#{resource_name}_updated",
|
7
|
-
target: actionable_resource,
|
8
|
-
changes: actionable_resource.previous_changes.as_json
|
9
|
-
else
|
10
|
-
action_event "#{resource_name}_update_failed",
|
11
|
-
target: actionable_resource,
|
12
|
-
errors: actionable_resource.errors.as_json,
|
13
|
-
attributes: params[resource_name]
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
@@ -1,55 +0,0 @@
|
|
1
|
-
module Reactor
|
2
|
-
module ResourceActionable
|
3
|
-
extend ActiveSupport::Concern
|
4
|
-
|
5
|
-
included do
|
6
|
-
around_filter :infer_basic_action_event
|
7
|
-
end
|
8
|
-
|
9
|
-
def infer_basic_action_event
|
10
|
-
yield if block_given?
|
11
|
-
|
12
|
-
if (event_descriptor = "Reactor::ResourceActionable::#{action_name.camelize}Event".safe_constantize).present?
|
13
|
-
event_descriptor.perform_on self
|
14
|
-
else
|
15
|
-
action_event "#{resource_name}_#{action_name}"
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
module ClassMethods
|
20
|
-
def actionable_resource(ivar_name = nil)
|
21
|
-
@resource_ivar_name ||= ivar_name
|
22
|
-
end
|
23
|
-
|
24
|
-
def nested_resource(ivar_name = nil)
|
25
|
-
@nested_resource_ivar_name ||= ivar_name
|
26
|
-
end
|
27
|
-
|
28
|
-
# this is so our API controller subclasses can re-use the resource declarations
|
29
|
-
def inherited(subclass)
|
30
|
-
[:resource_ivar_name, :nested_resource_ivar_name].each do |inheritable_attribute|
|
31
|
-
instance_var = "@#{inheritable_attribute}"
|
32
|
-
subclass.instance_variable_set(instance_var, instance_variable_get(instance_var))
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def actionable_resource; instance_variable_get(self.class.actionable_resource); end
|
38
|
-
def nested_resource; self.class.nested_resource && instance_variable_get(self.class.nested_resource); end
|
39
|
-
|
40
|
-
private
|
41
|
-
|
42
|
-
def resource_name
|
43
|
-
self.class.actionable_resource.to_s.gsub('@','').underscore
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
require "reactor/controllers/concerns/actions/action_event"
|
49
|
-
require "reactor/controllers/concerns/actions/new_event"
|
50
|
-
require "reactor/controllers/concerns/actions/index_event"
|
51
|
-
require "reactor/controllers/concerns/actions/edit_event"
|
52
|
-
require "reactor/controllers/concerns/actions/create_event"
|
53
|
-
require "reactor/controllers/concerns/actions/update_event"
|
54
|
-
require "reactor/controllers/concerns/actions/destroy_event"
|
55
|
-
require "reactor/controllers/concerns/actions/show_event"
|
data/lib/reactor/testing.rb
DELETED
@@ -1,50 +0,0 @@
|
|
1
|
-
module Reactor
|
2
|
-
TEST_MODE_SUBSCRIBERS = Set.new
|
3
|
-
@@test_mode = false
|
4
|
-
|
5
|
-
module_function
|
6
|
-
|
7
|
-
def test_mode?
|
8
|
-
@@test_mode
|
9
|
-
end
|
10
|
-
|
11
|
-
def test_mode!
|
12
|
-
@@test_mode = true
|
13
|
-
end
|
14
|
-
|
15
|
-
def disable_test_mode!
|
16
|
-
@@test_mode = false
|
17
|
-
end
|
18
|
-
|
19
|
-
def in_test_mode
|
20
|
-
test_mode!
|
21
|
-
(yield if block_given?).tap { disable_test_mode! }
|
22
|
-
end
|
23
|
-
|
24
|
-
def test_mode_subscribers
|
25
|
-
TEST_MODE_SUBSCRIBERS
|
26
|
-
end
|
27
|
-
|
28
|
-
def enable_test_mode_subscriber(klass)
|
29
|
-
test_mode_subscribers << klass
|
30
|
-
end
|
31
|
-
|
32
|
-
def disable_test_mode_subscriber(klass)
|
33
|
-
test_mode_subscribers.delete klass
|
34
|
-
end
|
35
|
-
|
36
|
-
def with_subscriber_enabled(klass)
|
37
|
-
enable_test_mode_subscriber klass
|
38
|
-
yield if block_given?
|
39
|
-
ensure
|
40
|
-
disable_test_mode_subscriber klass
|
41
|
-
end
|
42
|
-
|
43
|
-
def clear_test_subscribers!
|
44
|
-
test_mode_subscribers.each {|klass| test_mode_subscribers.delete klass }
|
45
|
-
end
|
46
|
-
|
47
|
-
def test_mode_subscriber_enabled?(subscriber)
|
48
|
-
test_mode_subscribers.include?(subscriber)
|
49
|
-
end
|
50
|
-
end
|
@@ -1,140 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
class RandomActionController
|
4
|
-
|
5
|
-
def self.around_filter(method)
|
6
|
-
@around_filter ||= method
|
7
|
-
end
|
8
|
-
|
9
|
-
include Reactor::ResourceActionable
|
10
|
-
actionable_resource :@cat
|
11
|
-
nested_resource :@owner
|
12
|
-
|
13
|
-
attr_accessor :action_name
|
14
|
-
def initialize
|
15
|
-
self.action_name = 'create'
|
16
|
-
end
|
17
|
-
|
18
|
-
def create
|
19
|
-
#because I dont feel like re-implementing around_filter for this stub
|
20
|
-
infer_basic_action_event do
|
21
|
-
@owner = ArbitraryModel.create!
|
22
|
-
@cat = Pet.create!
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
end
|
27
|
-
|
28
|
-
describe Reactor::ResourceActionable do
|
29
|
-
let(:controller_stub) { RandomActionController.new }
|
30
|
-
|
31
|
-
describe "when action strategy class exists" do
|
32
|
-
it 'runs the strategy of the matching name' do
|
33
|
-
expect(Reactor::ResourceActionable::CreateEvent).to receive(:perform_on).with(controller_stub)
|
34
|
-
controller_stub.create
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
describe "when action is non-standard rails CRUD action" do
|
39
|
-
it 'fires a basic action_event' do
|
40
|
-
controller_stub.action_name = 'do_thing'
|
41
|
-
expect(controller_stub).to receive(:action_event).with("cat_do_thing")
|
42
|
-
controller_stub.create
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
describe "ActionEvents" do
|
48
|
-
let(:actionable_resource) { ArbitraryModel.create! }
|
49
|
-
let(:nested_resource) { Pet.create! }
|
50
|
-
let(:ctrl_stub) { double(resource_name: "cat", actionable_resource: actionable_resource, nested_resource: nested_resource, params: {'cat' => {name: "Sasha"}} ) }
|
51
|
-
|
52
|
-
describe "ShowEvent" do
|
53
|
-
after { Reactor::ResourceActionable::ShowEvent.perform_on(ctrl_stub) }
|
54
|
-
specify { expect(ctrl_stub).to receive(:action_event).with("cat_viewed", target: actionable_resource) }
|
55
|
-
end
|
56
|
-
|
57
|
-
describe "EditEvent" do
|
58
|
-
after { Reactor::ResourceActionable::EditEvent.perform_on(ctrl_stub) }
|
59
|
-
specify { expect(ctrl_stub).to receive(:action_event).with("edit_cat_form_viewed", target: actionable_resource) }
|
60
|
-
end
|
61
|
-
|
62
|
-
describe "NewEvent" do
|
63
|
-
after { Reactor::ResourceActionable::NewEvent.perform_on(ctrl_stub) }
|
64
|
-
specify { expect(ctrl_stub).to receive(:action_event).with("new_cat_form_viewed", target: nested_resource) }
|
65
|
-
end
|
66
|
-
|
67
|
-
describe "IndexEvent" do
|
68
|
-
after { Reactor::ResourceActionable::IndexEvent.perform_on(ctrl_stub) }
|
69
|
-
specify { expect(ctrl_stub).to receive(:action_event).with("cats_indexed", target: nested_resource) }
|
70
|
-
end
|
71
|
-
|
72
|
-
describe "DestroyEvent" do
|
73
|
-
after { Reactor::ResourceActionable::DestroyEvent.perform_on(ctrl_stub) }
|
74
|
-
specify { expect(ctrl_stub).to receive(:action_event).with("cat_destroyed", last_snapshot: actionable_resource.as_json) }
|
75
|
-
end
|
76
|
-
|
77
|
-
describe "CreateEvent" do
|
78
|
-
after { Reactor::ResourceActionable::CreateEvent.perform_on(ctrl_stub) }
|
79
|
-
|
80
|
-
describe "when resource is valid" do
|
81
|
-
before { expect(actionable_resource).to receive(:valid?).and_return(true) }
|
82
|
-
|
83
|
-
specify do
|
84
|
-
expect(ctrl_stub).to receive(:action_event)
|
85
|
-
.with("cat_created",
|
86
|
-
target: actionable_resource,
|
87
|
-
attributes: {name: "Sasha"})
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
describe "when resource is not valid" do
|
92
|
-
before do
|
93
|
-
expect(actionable_resource).to receive(:valid?).and_return(false)
|
94
|
-
expect(actionable_resource).to receive(:errors).and_return('awesomeness' => 'too awesome')
|
95
|
-
end
|
96
|
-
|
97
|
-
specify do
|
98
|
-
expect(ctrl_stub).to receive(:action_event)
|
99
|
-
.with("cat_create_failed",
|
100
|
-
errors: {'awesomeness' => 'too awesome'},
|
101
|
-
target: nested_resource,
|
102
|
-
attributes: {name: "Sasha"})
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
describe "UpdateEvent" do
|
108
|
-
after { Reactor::ResourceActionable::UpdateEvent.perform_on(ctrl_stub) }
|
109
|
-
|
110
|
-
describe "when resource is valid" do
|
111
|
-
before do
|
112
|
-
expect(actionable_resource).to receive(:valid?).and_return(true)
|
113
|
-
expect(actionable_resource).to receive(:previous_changes).and_return({'name' => [nil, "Sasha"]})
|
114
|
-
end
|
115
|
-
|
116
|
-
specify do
|
117
|
-
expect(ctrl_stub).to receive(:action_event)
|
118
|
-
.with("cat_updated",
|
119
|
-
target: actionable_resource,
|
120
|
-
changes: {'name' => [nil, "Sasha"]})
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
describe "when resource is not valid" do
|
125
|
-
before do
|
126
|
-
expect(actionable_resource).to receive(:valid?).and_return(false)
|
127
|
-
expect(actionable_resource).to receive(:errors).and_return('awesomeness' => 'too awesome')
|
128
|
-
end
|
129
|
-
|
130
|
-
specify do
|
131
|
-
expect(ctrl_stub).to receive(:action_event)
|
132
|
-
.with("cat_update_failed",
|
133
|
-
target: actionable_resource,
|
134
|
-
errors: {'awesomeness' => 'too awesome'},
|
135
|
-
attributes: {name: "Sasha"})
|
136
|
-
end
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
end
|
data/spec/reactor_spec.rb
DELETED
@@ -1,59 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
Reactor.in_test_mode do
|
4
|
-
class SomeClass
|
5
|
-
include Reactor::Subscribable
|
6
|
-
on_event :test_event, -> (event) { self.spy_on_me }
|
7
|
-
end
|
8
|
-
end
|
9
|
-
|
10
|
-
describe Reactor do
|
11
|
-
let(:subscriber) { SomeClass }
|
12
|
-
|
13
|
-
describe '.test_mode!' do
|
14
|
-
before { Reactor.disable_test_mode! }
|
15
|
-
|
16
|
-
it 'sets Reactor into test mode' do
|
17
|
-
expect(Reactor.test_mode?).to be_falsey
|
18
|
-
Reactor.test_mode!
|
19
|
-
expect(Reactor.test_mode?).to be_truthy
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
context 'in test mode' do
|
24
|
-
before { Reactor.test_mode! }
|
25
|
-
after { Reactor.disable_test_mode! }
|
26
|
-
|
27
|
-
it 'subscribers created in test mode are disabled' do
|
28
|
-
expect(subscriber).not_to receive :spy_on_me
|
29
|
-
Reactor::Event.publish :test_event
|
30
|
-
end
|
31
|
-
|
32
|
-
describe '.with_subscriber_enabled' do
|
33
|
-
it 'enables a subscriber during test mode' do
|
34
|
-
expect(subscriber).to receive :spy_on_me
|
35
|
-
Reactor.with_subscriber_enabled(subscriber) do
|
36
|
-
Reactor::Event.publish :test_event
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
it 'disables the subscriber outside the block' do
|
41
|
-
expect(Reactor::TEST_MODE_SUBSCRIBERS).to be_empty
|
42
|
-
Reactor.with_subscriber_enabled(subscriber) do
|
43
|
-
expect(Reactor::TEST_MODE_SUBSCRIBERS).to contain_exactly(subscriber)
|
44
|
-
end
|
45
|
-
expect(Reactor::TEST_MODE_SUBSCRIBERS).to be_empty
|
46
|
-
end
|
47
|
-
|
48
|
-
it 'correctly handles exceptions inside the block' do
|
49
|
-
expect(Reactor::TEST_MODE_SUBSCRIBERS).to be_empty
|
50
|
-
expect {
|
51
|
-
Reactor.with_subscriber_enabled(subscriber) do
|
52
|
-
raise RuntimeError
|
53
|
-
end
|
54
|
-
}.to raise_error(RuntimeError)
|
55
|
-
expect(Reactor::TEST_MODE_SUBSCRIBERS).to be_empty
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|