reactor
reactor copied to clipboard
publishes implementation firing erroneously
With contract time cards, I used publishes
as seen below:
publishes :auto_approve_time_card, actor: :time_approver, target: :self,
approval_time: -> { auto_approval_time.to_s }, at: :auto_approval_time, watch: :submitted_at,
if: -> { !submitted_at.nil? && status == :pending_review_by_employer }
All time cards start out with submitted_at being nil and status being something different, however the event publishes multiple times before the conditions are met... I think this is because until the time card is submitted, auto_approval_time
returns nil, and this causes it to fire without checking the if:
condition... @wnadeau and I were looking into this at one point (it only happens in production for some reason) and weren't really getting anywhere, but it's clearly a bug
@bradherman @wnadeau I've recently encountered a similar scenario and I believe I've found the cause.
When using the publishes
declaration with an if-condition in a rails model, the event is published by the rails model regardless, and then the condition is evaluated by Sidekiq. Sidekiq evaluates the if conditionals later when the record is loaded as the "actor".
For example. The following
class Publisher < ActiveRecord::Base
publishes :conditional_event_on_save, at: :start_at, if: -> { we_want_it }
end
will create the a Reactor::Event
named :conditional_event_on_save
, regardless of the value of we_want_id
. The block -> { we_want_it }
will be serialized, and not evaluated until Sidekiq decides to take action on the event.
The following snippet (which is run from Sidekiq) may help clarify this:
# lib/reactor/event.rb:15
def perform(name, data)
data = data.with_indifferent_access
if data['actor_type']
actor = data["actor_type"].constantize.unscoped.find(data["actor_id"])
publishable_event = actor.class.events[name.to_sym]
ifarg = publishable_event[:if] if publishable_event
end
need_to_fire = case ifarg
when Proc
actor.instance_exec(&ifarg)
when Symbol
actor.send(ifarg)
when NilClass
true
end
if need_to_fire
data.merge!(fired_at: Time.current, name: name)
fire_database_driven_subscribers(data, name)
fire_block_subscribers(data, name)
end
end