reactor icon indicating copy to clipboard operation
reactor copied to clipboard

publishes implementation firing erroneously

Open bradherman opened this issue 7 years ago • 1 comments

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 avatar Apr 05 '17 22:04 bradherman

@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

kevindjacobson avatar Jul 24 '17 18:07 kevindjacobson