require 'messaging/routing/message_matcher' require 'messaging/routing/route' require 'messaging/routing/enqueued_route' require 'messaging/routing/enqueue_message_handler' module Messaging module Routing def self.included(base) base.send :extend, ClassMethods end module ClassMethods def definitions @definitions ||= [] end def on(pattern, **options, &block) definitions << { pattern: pattern, options: options, block: block } end def new(*args, &block) instance = allocate # Pre-initialize definitions.each do |definition| instance.on(definition[:pattern], definition[:options], &definition[:block]) end instance.send(:initialize, *args, &block) instance end end # Public: Sets up routes for the events that matches the given pattern # # pattern - Which messages to route. Can be a string, a regexp, # a Message class, a module or anything that responds to call. # # call: - Any object that responds to call. # Will be called immediately for matching messages. # # enqueue: - A constant that responds to call. # Will be enqueued with Sidekiq for matching messages. # Needs to be a constant that Sidekiq can serialize to a string # and back again to a constant as you can't store procs in Redis. # # block - An optional block that will be called with each matching message. # # # Examples # # Messaging.routes.draw do # on 'Events::BidPlaced', call: NotifyOtherBidders # # on Events::BidPlaced, enqueue: NotifyOtherBidders # # on Events, do |event| # puts event.inspect # end # # on /.*Updated$/, enqueue: AuditChanges # # on ->(m) { m.topic == 'my-topic' }, call: DoSometing, enqueue: DoSomethingElseWithSidekiq # end # def on(pattern = /.*/, call: nil, enqueue: nil, &block) routes << Route.new(pattern, call) if call routes << Route.new(pattern, block) if block_given? routes << EnqueuedRoute.new(pattern, enqueue) if enqueue end # Internal: Handles the message with the matching subscribers def handle(message, context = self) routes.map { |route| route.call(message, context) } message end # Internal: Used by Rails reloading in development. def clear_routes! @routes = Set.new end private def routes @routes ||= Set.new end def topics routes.flat_map(&:topics).map(&:to_s).uniq end end end