reactor 0.8.3 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 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