reactor 0.8.3 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7693ae6991c1cb79fa7257e131e844363db098fc
4
- data.tar.gz: df0eac5e0c734cace8b3d37fa2be485f896ffc31
3
+ metadata.gz: 6bbad3c2e6c18e53fd80a1c434aa616b24fca5bf
4
+ data.tar.gz: 990dc1373ec901fecedebd5f8f9878901a85bc7f
5
5
  SHA512:
6
- metadata.gz: 95f7c623e0569143793bc7ee1bd2870e5d71902ea794dd7f6aee6b0c95d67984f70b306ece0e1609f01f19ab0cb58b66f585dd557268a98e1c40d380b1113b36
7
- data.tar.gz: 4bf1fe49637b5a72cdd5e774c42c2ba2e24b5567b6be195b0c11f5f9a0b36708ddd54f303a1e554250107f1bcd1ff700686888c66a0ac279da7cea6057530e3b
6
+ metadata.gz: 9047b728853f35fe0fce8020942d5359a8d52ab69232d39e2011628b73607f9b96c0b902f7c94bcc99e91fff8a77f1c734352a9ac9de35aa441885c5a2f6ee8b
7
+ data.tar.gz: b46fc1ffc3de6e48e82998ba0134d6532b3a0c91ee448b554eb75a04fc15f91a8950ac9911c91cbc14a689a5e9e497b369189fda01daa3c908efabe4183e3096
data/README.md CHANGED
@@ -45,7 +45,29 @@ Reactor::Event.publish(:event_name, any: 'data', you: 'want')
45
45
 
46
46
  ```ruby
47
47
  publishes :my_model_created
48
- publishes :state_has_changed, if: -> { state_has_changed? }
48
+ ```
49
+
50
+ Schedule an event to get published at a specific time. Note: if timestamp is a property on an ActiveRecord::Model
51
+ then updating that property will re-schedule the firing of the event
52
+
53
+ ```ruby
54
+ publishes :something_happened, at: :timestamp
55
+ ```
56
+
57
+ Schedule an event to get published at a specific time using a method to generate the timestamp and following some other property. In this case the :something_happened event will be fired 72 hours after your model is created. The event will be re-scheduled if created_at is changed.
58
+
59
+ ```ruby
60
+ def reminder_email_time
61
+ created_at + 72.hours
62
+ end
63
+
64
+ publishes :reminder_sent, at: :reminder_email_time, watch: :created_at
65
+ ```
66
+
67
+ Scheduled events can check conditionally fire -- eg: in 2 days fire reminder_email if the user hasn't already responded.
68
+
69
+ ```ruby
70
+ publishes :reminder_sent, at: :reminder_email_time, if: -> { user.responded == false }
49
71
  ```
50
72
 
51
73
  #### Subscribable
data/lib/reactor/event.rb CHANGED
@@ -12,9 +12,28 @@ class Reactor::Event
12
12
  end
13
13
 
14
14
  def perform(name, data)
15
- data.merge!(fired_at: Time.current, name: name)
16
- fire_database_driven_subscribers(data, name)
17
- fire_block_subscribers(data, name)
15
+ data = data.with_indifferent_access
16
+
17
+ if data['actor_type']
18
+ actor = data["actor_type"].constantize.find(data["actor_id"])
19
+ publishable_event = actor.class.events[name.to_sym]
20
+ ifarg = publishable_event[:if] if publishable_event
21
+ end
22
+
23
+ need_to_fire = case ifarg
24
+ when Proc
25
+ actor.instance_exec(&ifarg)
26
+ when Symbol
27
+ actor.send(ifarg)
28
+ when NilClass
29
+ true
30
+ end
31
+
32
+ if need_to_fire
33
+ data.merge!(fired_at: Time.current, name: name)
34
+ fire_database_driven_subscribers(data, name)
35
+ fire_block_subscribers(data, name)
36
+ end
18
37
  end
19
38
 
20
39
  def method_missing(method, *args)
@@ -37,10 +56,10 @@ class Reactor::Event
37
56
  def publish(name, data = {})
38
57
  message = new(data.merge(event: name))
39
58
 
40
- if message.at.nil?
41
- perform_async name, message.data
42
- elsif message.at.future?
59
+ if message.at
43
60
  perform_at message.at, name, message.data
61
+ else
62
+ perform_async name, message.data
44
63
  end
45
64
  end
46
65
 
@@ -51,9 +70,10 @@ class Reactor::Event
51
70
  job['args'].first == name.to_s &&
52
71
  job.score.to_i == data[:was].to_i
53
72
  end
54
- return if job.nil?
55
- job.delete
56
- publish(name, data.except(:was)) if data[:at].future?
73
+
74
+ job.delete if job
75
+
76
+ publish(name, data.except([:was, :if])) if data[:at].future?
57
77
  end
58
78
  end
59
79
 
@@ -3,15 +3,19 @@ module Reactor::Publishable
3
3
 
4
4
  included do
5
5
  after_commit :schedule_events, if: :persisted?, on: :create
6
- after_commit :schedule_conditional_events_on_create, if: :persisted?, on: :create
7
- after_commit :schedule_conditional_events_on_update, if: :persisted?, on: :update
8
- after_commit :reschedule_events, if: :persisted?, on: :update
6
+ after_commit :reschedule_events_on_update, if: :persisted?, on: :update
9
7
  end
10
8
 
11
9
  def publish(name, data = {})
12
10
  Reactor::Event.publish(name, data.merge(actor: self) )
13
11
  end
14
12
 
13
+ def reschedule_events
14
+ self.class.events.each do |name, data|
15
+ reschedule(name, data)
16
+ end
17
+ end
18
+
15
19
  module ClassMethods
16
20
  def publishes(name, data = {})
17
21
  events[name] = data
@@ -24,41 +28,32 @@ module Reactor::Publishable
24
28
 
25
29
  private
26
30
 
27
- def schedule_events
31
+ def reschedule_events_on_update
28
32
  self.class.events.each do |name, data|
29
- event = event_data_for_signature(data)
30
- Reactor::Event.publish name, event
33
+ attr_changed_method = data[:watch] || data[:at]
34
+ if previous_changes[attr_changed_method]
35
+ reschedule(name, data)
36
+ end
31
37
  end
32
38
  end
33
39
 
34
- def reschedule_events
35
- self.class.events.each do |name, data|
36
- attr_changed_method = data[:watch] || data[:at]
37
- if data[:at] && previous_changes[attr_changed_method]
38
- Reactor::Event.reschedule name,
39
- data.merge(
40
+ def reschedule(name, data)
41
+ if data[:at]
42
+ Reactor::Event.reschedule name,
43
+ data.merge(
40
44
  at: send(data[:at]),
41
45
  actor: ( data[:actor] ? send(data[:actor]) : self ),
42
46
  target: ( data[:target] ? self : nil),
43
47
  was: previous_changes[data[:at]].try(:first) || send("#{data[:at]}_was"))
44
- end
45
48
  end
46
49
  end
47
50
 
48
- def schedule_conditional_events
49
- self.class.events.select { |k,v| v.has_key?(:if) }.each do |name, data|
51
+ def schedule_events
52
+ self.class.events.each do |name, data|
50
53
  event = event_data_for_signature(data)
51
- need_to_fire = case (ifarg = data[:if])
52
- when Proc
53
- instance_exec(&ifarg)
54
- when Symbol
55
- send(ifarg)
56
- end
57
- Reactor::Event.publish name, event if need_to_fire
54
+ Reactor::Event.publish name, event
58
55
  end
59
56
  end
60
- alias :schedule_conditional_events_on_create :schedule_conditional_events
61
- alias :schedule_conditional_events_on_update :schedule_conditional_events
62
57
 
63
58
  def event_data_for_signature(signature)
64
59
  signature.merge(
@@ -67,5 +62,4 @@ module Reactor::Publishable
67
62
  at: (signature[:at] ? send(signature[:at]) : nil)
68
63
  ).except(:watch, :if)
69
64
  end
70
-
71
65
  end
@@ -4,11 +4,11 @@ RSpec::Matchers.define :publish_event do |name, data = {}|
4
4
  match do |block|
5
5
  defaults = {:actor => anything}
6
6
 
7
- allow(Reactor::Event).to receive(:publish).with(name, a_hash_including(defaults.merge(data)))
7
+ allow(Reactor::Event).to receive(:publish)
8
8
 
9
9
  block.call
10
10
 
11
- expect(Reactor::Event).to have_received(:publish).with(name, a_hash_including(defaults.merge(data)))
11
+ expect(Reactor::Event).to have_received(:publish).with(name, a_hash_including(defaults.merge(data))).at_least(:once)
12
12
  end
13
13
  end
14
14
 
@@ -18,14 +18,12 @@ RSpec::Matchers.define :publish_events do |*names|
18
18
  match do |block|
19
19
  defaults = {:actor => anything}
20
20
 
21
- names.each do |name|
22
- allow(Reactor::Event).to receive(:publish).with(name, a_hash_including(defaults))
23
- end
21
+ allow(Reactor::Event).to receive(:publish)
24
22
 
25
23
  block.call
26
24
 
27
25
  names.each do |name|
28
- expect(Reactor::Event).to have_received(:publish).with(name, a_hash_including(defaults))
26
+ expect(Reactor::Event).to have_received(:publish).with(name, a_hash_including(defaults)).at_least(:once)
29
27
  end
30
28
  end
31
29
  end
@@ -1,3 +1,3 @@
1
1
  module Reactor
2
- VERSION = "0.8.3"
2
+ VERSION = "0.9.0"
3
3
  end
data/reactor.gemspec CHANGED
@@ -24,6 +24,7 @@ Gem::Specification.new do |spec|
24
24
  spec.add_development_dependency "bundler", "~> 1.3"
25
25
  spec.add_development_dependency "rake"
26
26
  spec.add_development_dependency "rspec", "~> 3.0.0"
27
+ spec.add_development_dependency "rspec-its"
27
28
  spec.add_development_dependency "pry"
28
29
  spec.add_development_dependency "pry-byebug"
29
30
  spec.add_development_dependency "sqlite3"
data/spec/event_spec.rb CHANGED
@@ -14,6 +14,7 @@ end
14
14
 
15
15
  describe Reactor::Event do
16
16
 
17
+ let(:model) { ArbitraryModel.create! }
17
18
  let(:event_name) { :user_did_this }
18
19
 
19
20
  describe 'publish' do
@@ -27,23 +28,23 @@ describe Reactor::Event do
27
28
  before { Reactor::Subscriber.create(event_name: :user_did_this) }
28
29
  after { Reactor::Subscriber.destroy_all }
29
30
  it 'fires all subscribers' do
30
- expect_any_instance_of(Reactor::Subscriber).to receive(:fire).with(hash_including(actor_id: '1'))
31
- Reactor::Event.perform(event_name, actor_id: '1')
31
+ expect_any_instance_of(Reactor::Subscriber).to receive(:fire).with(hash_including(actor_id: model.id.to_s))
32
+ Reactor::Event.perform(event_name, actor_id: model.id.to_s, actor_type: model.class.to_s)
32
33
  end
33
34
 
34
35
  it 'sets a fired_at key in event data' do
35
36
  expect_any_instance_of(Reactor::Subscriber).to receive(:fire).with(hash_including(fired_at: anything))
36
- Reactor::Event.perform(event_name, actor_id: '1')
37
+ Reactor::Event.perform(event_name, actor_id: model.id.to_s, actor_type: model.class.to_s)
37
38
  end
38
39
 
39
40
  it 'works with the legacy .process method, too' do
40
- expect_any_instance_of(Reactor::Subscriber).to receive(:fire).with(hash_including(actor_id: '1'))
41
- Reactor::Event.perform(event_name, actor_id: '1')
41
+ expect_any_instance_of(Reactor::Subscriber).to receive(:fire).with(hash_including(actor_id: model.id.to_s))
42
+ Reactor::Event.perform(event_name, actor_id: model.id.to_s, actor_type: model.class.to_s)
42
43
  end
43
44
 
44
45
  describe 'when subscriber throws exception', :sidekiq do
45
46
  let(:mock) { double(:thing, some_method: 3) }
46
- let(:barfing_event) { Reactor::Event.perform('barfed', somethin: 'up') }
47
+ let(:barfing_event) { Reactor::Event.perform('barfed', somethin: 'up', actor_id: model.id.to_s, actor_type: model.class.to_s) }
47
48
 
48
49
  before do
49
50
  Reactor::SUBSCRIBERS['barfed'] ||= []
@@ -65,6 +66,10 @@ describe Reactor::Event do
65
66
  let(:scheduled) { Sidekiq::ScheduledSet.new }
66
67
  let(:time) { 1.hour.from_now }
67
68
 
69
+ before do
70
+ Sidekiq::Worker.clear_all
71
+ end
72
+
68
73
  it 'can schedule and reschedule an event in the future' do
69
74
  expect {
70
75
  jid = Reactor::Event.publish :turtle_time, at: time
@@ -76,6 +81,13 @@ describe Reactor::Event do
76
81
  expect(scheduled.find_job(jid).score).to eq((time + 2.hours).to_f)
77
82
  }.to_not change { scheduled.size }
78
83
  end
84
+
85
+ it 'will schedule an event in the future even if that event was not previously scheduled in the past' do
86
+ expect {
87
+ jid = Reactor::Event.reschedule :no_old_turtule_time, at: (time + 2.hours), was: time
88
+ expect(scheduled.find_job(jid).score).to eq((time + 2.hours).to_f)
89
+ }.to change{ scheduled.size }.by(1)
90
+ end
79
91
  end
80
92
 
81
93
  describe 'event content' do
@@ -89,25 +101,12 @@ describe Reactor::Event do
89
101
 
90
102
  describe 'getters' do
91
103
  context 'basic key value' do
92
- describe '#random' do
93
- subject { super().random }
94
- it { is_expected.to eq('data') }
95
- end
104
+ its(:random) { is_expected.to eq('data') }
96
105
  end
97
106
 
98
107
  context 'foreign key and foreign type' do
99
- describe '#pet' do
100
- subject { super().pet }
101
- it { is_expected.to be_a MyModule::Cat }
102
- end
103
-
104
- describe '#pet' do
105
- subject { super().pet }
106
- describe '#id' do
107
- subject { super().id }
108
- it { is_expected.to eq(MyModule::Cat.last.id) }
109
- end
110
- end
108
+ its(:pet) { is_expected.to be_a MyModule::Cat }
109
+ its('pet.id') { is_expected.to eq(MyModule::Cat.last.id) }
111
110
  end
112
111
  end
113
112
 
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
+ require 'sidekiq/testing'
2
3
 
3
4
  class Auction < ActiveRecord::Base
4
- attr_accessor :we_want_it
5
5
  belongs_to :pet
6
6
 
7
7
  def ring_timeout
@@ -15,7 +15,7 @@ class Auction < ActiveRecord::Base
15
15
  publishes :bell
16
16
  publishes :ring, at: :ring_timeout, watch: :start_at
17
17
  publishes :begin, at: :start_at, additional_info: 'curtis was here'
18
- publishes :conditional_event_on_save, if: -> { we_want_it }
18
+ publishes :conditional_event_on_save, at: :start_at, if: -> { we_want_it }
19
19
  publishes :woof, actor: :pet, target: :self
20
20
  end
21
21
 
@@ -35,7 +35,7 @@ describe Reactor::Publishable do
35
35
 
36
36
  describe 'publish' do
37
37
  let(:pet) { Pet.create! }
38
- let(:auction) { Auction.create!(pet: pet, start_at: DateTime.new(2012,12,21)) }
38
+ let(:auction) { Auction.create!(pet: pet, start_at: Time.current + 1.day, we_want_it: false) }
39
39
 
40
40
  it 'publishes an event with actor_id and actor_type set as self' do
41
41
  auction
@@ -52,8 +52,13 @@ describe Reactor::Publishable do
52
52
  it 'reschedules an event when the :at time changes' do
53
53
  start_at = auction.start_at
54
54
  new_start_at = start_at + 1.week
55
- expect(Reactor::Event).to receive(:reschedule).with :ring, anything
56
- expect(Reactor::Event).to receive(:reschedule).with(:begin,
55
+
56
+ allow(Reactor::Event).to receive(:reschedule)
57
+
58
+ auction.start_at = new_start_at
59
+ auction.save!
60
+
61
+ expect(Reactor::Event).to have_received(:reschedule).with(:begin,
57
62
  a_hash_including(
58
63
  at: new_start_at,
59
64
  actor: auction,
@@ -61,24 +66,58 @@ describe Reactor::Publishable do
61
66
  additional_info: 'curtis was here'
62
67
  )
63
68
  )
64
- auction.start_at = new_start_at
65
- auction.save!
66
69
  end
67
70
 
68
71
  it 'reschedules an event when the :watch field changes' do
69
72
  ring_time = auction.ring_timeout
70
73
  new_start_at = auction.start_at + 1.week
71
74
  new_ring_time = new_start_at + 30.seconds
72
- expect(Reactor::Event).to receive(:reschedule).with :begin, anything
73
- expect(Reactor::Event).to receive(:reschedule).with(:ring,
75
+
76
+ allow(Reactor::Event).to receive(:reschedule)
77
+
78
+ auction.start_at = new_start_at
79
+ auction.save!
80
+
81
+ expect(Reactor::Event).to have_received(:reschedule).with(:ring,
74
82
  a_hash_including(
75
83
  at: new_ring_time,
76
84
  actor: auction,
77
85
  was: ring_time
78
86
  )
79
87
  )
80
- auction.start_at = new_start_at
81
- auction.save!
88
+ end
89
+
90
+ context 'conditional firing' do
91
+ before do
92
+ Sidekiq::Testing.fake!
93
+ Sidekiq::Worker.clear_all
94
+ TestSubscriber.create! event_name: :conditional_event_on_save
95
+ auction
96
+ job = Reactor::Event.jobs.detect do |job|
97
+ job['class'] == 'Reactor::Event' && job['args'].first == 'conditional_event_on_save'
98
+ end
99
+ @job_args = job['args']
100
+ end
101
+
102
+ after do
103
+ Sidekiq::Testing.inline!
104
+ end
105
+
106
+ it 'calls the subscriber when if is set to true' do
107
+ auction.we_want_it = true
108
+ auction.start_at = 3.day.from_now
109
+ auction.save!
110
+
111
+ expect{ Reactor::Event.perform(@job_args[0], @job_args[1]) }.to change{ Sidekiq::Extensions::DelayedClass.jobs.size }
112
+ end
113
+
114
+ it 'does not call the subscriber when if is set to false' do
115
+ auction.we_want_it = false
116
+ auction.start_at = 3.days.from_now
117
+ auction.save!
118
+
119
+ expect{ Reactor::Event.perform(@job_args[0], @job_args[1]) }.to_not change{ Sidekiq::Extensions::DelayedClass.jobs.size }
120
+ end
82
121
  end
83
122
 
84
123
  it 'supports immediate events (on create) that get fired once' do
@@ -91,30 +130,11 @@ describe Reactor::Publishable do
91
130
  expect(TestSubscriber.class_variable_get(:@@called)).to be_falsey
92
131
  end
93
132
 
94
- it 'does not publish an event scheduled for the past' do
95
- TestSubscriber.create! event_name: :begin
96
- auction
97
- expect(TestSubscriber.class_variable_get(:@@called)).to be_falsey
98
- end
99
-
100
133
  it 'does publish an event scheduled for the future' do
101
134
  TestSubscriber.create! event_name: :begin
102
135
  Auction.create!(pet: pet, start_at: Time.current + 1.week)
103
136
 
104
137
  expect(TestSubscriber.class_variable_get(:@@called)).to be_truthy
105
138
  end
106
-
107
- it 'can fire events onsave for any condition' do
108
- TestSubscriber.create! event_name: :conditional_event_on_save
109
- auction
110
- TestSubscriber.class_variable_set(:@@called, false)
111
- auction.start_at = 1.day.from_now
112
- auction.save
113
- expect(TestSubscriber.class_variable_get(:@@called)).to be_falsey
114
- auction.start_at = 2.days.from_now
115
- auction.we_want_it = true
116
- auction.save
117
- expect(TestSubscriber.class_variable_get(:@@called)).to be_truthy
118
- end
119
139
  end
120
140
  end
@@ -13,18 +13,8 @@ describe Reactor::Subscriber do
13
13
  describe 'fire' do
14
14
  subject { MySubscriber.create(event_name: :you_name_it).fire some: 'random', event: 'data' }
15
15
 
16
- describe '#event' do
17
- subject { super().event }
18
- it { is_expected.to be_a Reactor::Event }
19
- end
20
-
21
- describe '#event' do
22
- subject { super().event }
23
- describe '#some' do
24
- subject { super().some }
25
- it { is_expected.to eq('random') }
26
- end
27
- end
16
+ its(:event) { is_expected.to be_a Reactor::Event }
17
+ its('event.some') { is_expected.to eq('random') }
28
18
 
29
19
  it 'executes block given' do
30
20
  expect(subject.was_called).to be_truthy
data/spec/spec_helper.rb CHANGED
@@ -9,6 +9,8 @@ require 'sidekiq/api'
9
9
  require 'reactor'
10
10
  require 'reactor/testing/matchers'
11
11
 
12
+ require 'rspec/its'
13
+
12
14
  Sidekiq.configure_server do |config|
13
15
  config.redis = { url: ENV["REDISTOGO_URL"] }
14
16
 
@@ -8,6 +8,7 @@ ActiveRecord::Migration.create_table :auctions do |t|
8
8
  t.string :name
9
9
  t.datetime :start_at
10
10
  t.datetime :close_at
11
+ t.boolean :we_want_it
11
12
  t.integer :pet_id
12
13
 
13
14
  t.timestamps
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.8.3
4
+ version: 0.9.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: 2014-11-13 00:00:00.000000000 Z
14
+ date: 2014-12-13 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: sidekiq
@@ -83,6 +83,20 @@ dependencies:
83
83
  - - "~>"
84
84
  - !ruby/object:Gem::Version
85
85
  version: 3.0.0
86
+ - !ruby/object:Gem::Dependency
87
+ name: rspec-its
88
+ requirement: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ type: :development
94
+ prerelease: false
95
+ version_requirements: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
86
100
  - !ruby/object:Gem::Dependency
87
101
  name: pry
88
102
  requirement: !ruby/object:Gem::Requirement