├── .gitignore ├── .rbenv-version ├── .rspec ├── .rvmrc ├── CHANGELOG.md ├── Gemfile ├── MIT-LICENSE ├── README.mdown ├── Rakefile ├── lib ├── resque-bus.rb └── resque_bus │ ├── adapter.rb │ ├── compatibility │ ├── deprecated.rb │ ├── driver.rb │ ├── heartbeat.rb │ ├── publisher.rb │ ├── rider.rb │ ├── subscriber.rb │ └── task_manager.rb │ ├── server.rb │ ├── server │ └── views │ │ └── bus.erb │ ├── tasks.rb │ └── version.rb ├── resque-bus.gemspec └── spec ├── adapter ├── compatibility_spec.rb ├── integration_spec.rb ├── publish_at_spec.rb ├── retry_spec.rb └── support.rb ├── adapter_spec.rb ├── application_spec.rb ├── config_spec.rb ├── dispatch_spec.rb ├── driver_spec.rb ├── heartbeat_spec.rb ├── integration_spec.rb ├── matcher_spec.rb ├── publish_spec.rb ├── publisher_spec.rb ├── rider_spec.rb ├── spec_helper.rb ├── subscriber_spec.rb ├── subscription_list_spec.rb ├── subscription_spec.rb └── worker_spec.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | Gemfile.lock 4 | pkg/* 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /.rbenv-version: -------------------------------------------------------------------------------- 1 | 1.9.3-p194 -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color -------------------------------------------------------------------------------- /.rvmrc: -------------------------------------------------------------------------------- 1 | rvm use 1.9.3-p194@resque-bus --install --create 2 | export PATH=./bin:$PATH 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.7.0] 2019-07-29 10 | 11 | ### Added 12 | - Adds `QueueBus.has_adapter?` to check whether the adapter is set before setting it to resque. This will allow multiple adapters to be loaded without error. 13 | 14 | ### Changed 15 | - Bump version dependency of queue-bus to at least 0.7 16 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | 5 | gem "rake" 6 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Brian Leonard 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.mdown: -------------------------------------------------------------------------------- 1 | ## Resque Bus 2 | 3 | This gem provides an adapter for Resque for use in the [queue-bus](https://github.com/queue-bus/queue-bus) system. 4 | It uses Redis and the Resque that you are already using to allow simple asynchronous communication between apps. 5 | 6 | ### Install 7 | 8 | To install, include the 'resque-bus' gem and add the following to your Rakefile: 9 | 10 | ```ruby 11 | require "resque_bus/tasks" 12 | ``` 13 | 14 | 15 | ### Example 16 | 17 | Application A can publish an event 18 | 19 | ```ruby 20 | # pick an adapter 21 | require 'resque-bus' # (or other adapter) 22 | 23 | # business logic 24 | QueueBus.publish("user_created", "id" => 42, "first_name" => "John", "last_name" => "Smith") 25 | 26 | # or do it later 27 | QueueBus.publish_at(1.hour.from_now, "user_created", "id" => 42, "first_name" => "John", "last_name" => "Smith") 28 | ``` 29 | 30 | Application B is subscribed to events 31 | 32 | ```ruby 33 | # pick an adapter 34 | require 'resque-bus' # (or other adapter) 35 | 36 | # initializer 37 | QueueBus.dispatch("app_b") do 38 | # processes event on app_b_default queue 39 | # subscribe is short-hand to subscribe to your 'default' queue and this block with process events with the name "user_created" 40 | subscribe "user_created" do |attributes| 41 | NameCount.find_or_create_by_name(attributes["last_name"]).increment! 42 | end 43 | 44 | # processes event on app_b_critical queue 45 | # critical is short-hand to subscribe to your 'critical' queue and this block with process events with the name "user_paid" 46 | critical "user_paid" do |attributes| 47 | CreditCard.charge!(attributes) 48 | end 49 | 50 | # you can pass any queue name you would like to process from as well IE: `banana "peeled" do |attributes|` 51 | 52 | # and regexes work as well. note that with the above configuration along with this regex, 53 | # the following as well as the corresponding block above would both be executed 54 | subscribe /^user_/ do |attributes| 55 | Metrics.record_user_action(attributes["bus_event_type"], attributes["id"]) 56 | end 57 | 58 | # the above all filter on just the event_type, but you can filter on anything 59 | # this would be _any_ event that has a user_id and the page value of homepage regardless of bus_event_type 60 | subscribe "my_key", { "user_id" => :present, "page" => "homepage"} do 61 | Mixpanel.homepage_action!(attributes["action"]) 62 | end 63 | end 64 | ``` 65 | 66 | Applications can also subscribe within classes using the provided `Subscriber` module. 67 | 68 | ```ruby 69 | class SimpleSubscriber 70 | include QueueBus::Subscriber 71 | subscribe :my_method 72 | 73 | def my_method(attributes) 74 | # heavy lifting 75 | end 76 | end 77 | ``` 78 | 79 | The following is equivalent to the original initializer and shows more options: 80 | 81 | ```ruby 82 | class OtherSubscriber 83 | include QueueBus::Subscriber 84 | application :app_b 85 | 86 | subscribe :user_created 87 | subscribe_queue :app_b_critical, :user_paid 88 | subscribe_queue :app_b_default, :user_action, :bus_event_type => /^user_/ 89 | subscribe :homepage_method, :user_id => :present, :page => "homepage" 90 | 91 | def user_created(attributes) 92 | NameCount.find_or_create_by_name(attributes["last_name"]).increment! 93 | end 94 | 95 | def user_paid(attributes) 96 | CreditCard.charge!(attributes) 97 | end 98 | 99 | def user_action(attributes) 100 | Metrics.record_user_action(attributes["bus_event_type"], attributes["id"]) 101 | end 102 | 103 | def homepage_method 104 | Mixpanel.homepage_action!(attributes["action"]) 105 | end 106 | end 107 | ``` 108 | 109 | Note: This subscribes when this class is loaded, so it needs to be in your load or otherwise referenced/required during app initialization to work properly. 110 | 111 | ### Commands 112 | 113 | Each app needs to tell Redis about its subscriptions: 114 | 115 | $ rake queuebus:subscribe 116 | 117 | The subscription block is run inside a Resque worker which needs to be started for each app. 118 | 119 | $ rake queuebus:setup resque:work 120 | 121 | The incoming queue also needs to be processed on a dedicated or all the app servers. 122 | 123 | $ rake queuebus:driver resque:work 124 | 125 | If you want retry to work for subscribing apps, you should run resque-scheduler 126 | 127 | $ rake resque:scheduler 128 | 129 | 130 | ### Heartbeat 131 | 132 | We've found it useful to have the bus act like `cron`, triggering timed jobs throughout the system. Resque Bus calls this a heartbeat. 133 | It uses resque-scheduler to trigger the events. You can enable it in your Rakefile. 134 | 135 | ```ruby 136 | # resque.rake 137 | namespace :resque do 138 | task :setup => [:environment] do 139 | QueueBus.heartbeat! 140 | end 141 | end 142 | ``` 143 | 144 | Or add it to your `schedule.yml` directly 145 | 146 | ```yaml 147 | resquebus_heartbeat: 148 | cron: "* * * * *" 149 | class: "::QueueBus::Heartbeat" 150 | queue: bus_incoming 151 | description: "I publish a heartbeat_minutes event every minute" 152 | ``` 153 | 154 | It is the equivalent of doing this every minute 155 | 156 | ```ruby 157 | seconds = minutes * (60) 158 | hours = minutes / (60) 159 | days = minutes / (60*24) 160 | 161 | now = Time.at(seconds) 162 | 163 | attributes = {} 164 | 165 | now = Time.now 166 | seconds = now.to_i 167 | QueueBus.publish("hearbeat_minutes", { 168 | "epoch_seconds" => seconds, 169 | "epoch_minutes" => seconds / 1.minute, 170 | "epoch_hours" => seconds / 1.hour, 171 | "epoch_days" => seconds / 1.day, 172 | "minute" => now.min 173 | "hour" => now.hour 174 | "day" => now.day 175 | "month" => now.month 176 | "year" => now.year 177 | "yday" => now.yday 178 | "wday" => now.wday 179 | }) 180 | ``` 181 | 182 | This allows you do something like this: 183 | 184 | ```ruby 185 | QueueBus.dispatch("app_c") do 186 | # runs at 10:20, 11:20, etc 187 | subscribe "once_an_hour", 'bus_event_type' => 'heartbeat_minutes', 'minute' => 20 do |attributes| 188 | Sitemap.generate! 189 | end 190 | 191 | # runs every five minutes 192 | subscribe "every_five_minutes", 'bus_event_type' => 'heartbeat_minutes' do |attributes| 193 | next unless attributes["epoch_minutes"] % 5 == 0 194 | HealthCheck.run! 195 | end 196 | 197 | # runs at 8am on the first of every month 198 | subscribe "new_month_morning", 'bus_event_type' => 'heartbeat_minutes', 'day' => 1, hour' => 8, 'minute' => 0, do |attributes| 199 | next unless attributes["epoch_minutes"] % 5 == 0 200 | Token.old.expire! 201 | end 202 | end 203 | ``` 204 | 205 | ### Local Mode 206 | 207 | For development, a local mode is provided and is specified in the configuration. 208 | 209 | ```ruby 210 | # config 211 | QueueBus.local_mode = :standalone 212 | or 213 | QueueBus.local_mode = :inline 214 | ``` 215 | 216 | Standalone mode does not require a separate queuebus:driver task to be running to process the 217 | incoming queue. Simply publishing to the bus will distribute the incoming events 218 | to the appropriate application specific queue. A separate queuebus:work task does 219 | still need to be run to process these events 220 | 221 | Inline mode skips queue processing entirely and directly dispatches the 222 | event to the appropriate code block. 223 | 224 | You can also say `QueueBus.local_mode = :suppress` to turn off publishing altogether. 225 | This can be helpful inside some sort of migration, for example. 226 | 227 | ### TODO 228 | 229 | * Replace local modes with adapters 230 | * Make this not freak out in development without Redis or when Redis is down 231 | * We might not actually need to publish in tests 232 | * Add some rspec helpers for the apps to use: should_ post an event_publish or something along those lines 233 | * Allow calling queuebus:setup and queuebus:driver together (append to ENV['QUEUES'], don't replace it) 234 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift 'lib' 2 | require 'resque_bus/tasks' 3 | -------------------------------------------------------------------------------- /lib/resque-bus.rb: -------------------------------------------------------------------------------- 1 | require "queue-bus" 2 | require "resque_bus/adapter" 3 | require "resque_bus/version" 4 | 5 | module ResqueBus 6 | # TODO: all of this will be removed 7 | 8 | autoload :Deprecated, 'resque_bus/compatibility/deprecated' 9 | autoload :Subscriber, 'resque_bus/compatibility/subscriber' 10 | autoload :TaskManager, 'resque_bus/compatibility/task_manager' 11 | autoload :Driver, 'resque_bus/compatibility/driver' 12 | autoload :Rider, 'resque_bus/compatibility/rider' 13 | autoload :Publisher, 'resque_bus/compatibility/publisher' 14 | autoload :Heartbeat, 'resque_bus/compatibility/heartbeat' 15 | 16 | extend ::ResqueBus::Deprecated 17 | end 18 | 19 | if QueueBus.has_adapter? 20 | warn '[ResqueBus] Not setting adapter on queue-bus because ' \ 21 | "#{QueueBus.adapter.class.name} is already the adapter" 22 | else 23 | QueueBus.adapter = QueueBus::Adapters::Resque.new 24 | end 25 | -------------------------------------------------------------------------------- /lib/resque_bus/adapter.rb: -------------------------------------------------------------------------------- 1 | module QueueBus 2 | module Adapters 3 | class Resque < QueueBus::Adapters::Base 4 | def enabled! 5 | # know we are using it 6 | require 'resque' 7 | require 'resque/scheduler' 8 | require 'resque-retry' 9 | 10 | QueueBus::Worker.extend(::Resque::Plugins::ExponentialBackoff) 11 | QueueBus::Worker.extend(::QueueBus::Adapters::Resque::RetryHandlers) 12 | end 13 | 14 | def redis(&block) 15 | block.call(::Resque.redis) 16 | end 17 | 18 | def enqueue(queue_name, klass, json) 19 | ::Resque.enqueue_to(queue_name, klass, json) 20 | end 21 | 22 | def enqueue_at(epoch_seconds, queue_name, klass, json) 23 | ::Resque.enqueue_at_with_queue(queue_name, epoch_seconds, klass, json) 24 | end 25 | 26 | def setup_heartbeat!(queue_name) 27 | # turn on the heartbeat 28 | # should be down after loading scheduler yml if you do that 29 | # otherwise, anytime 30 | name = 'resquebus_heartbeat' 31 | schedule = { 'class' => '::QueueBus::Worker', 32 | 'args'=>[::QueueBus::Util.encode({'bus_class_proxy' => '::QueueBus::Heartbeat'})], 33 | 'cron' => '* * * * *', # every minute 34 | 'queue' => queue_name, 35 | 'description' => 'I publish a heartbeat_minutes event every minute' 36 | } 37 | if ::Resque::Scheduler.dynamic 38 | ::Resque.set_schedule(name, schedule) 39 | end 40 | ::Resque.schedule[name] = schedule 41 | end 42 | 43 | private 44 | 45 | module RetryHandlers 46 | # @failure_hooks_already_ran on https://github.com/defunkt/resque/tree/1-x-stable 47 | # to prevent running twice 48 | def queue 49 | @my_queue 50 | end 51 | 52 | def on_failure_aaa(exception, *args) 53 | # note: sorted alphabetically 54 | # queue needs to be set for rety to work (know what queue in Requeue.class_to_queue) 55 | hash = ::QueueBus::Util.decode(args[0]) 56 | @my_queue = hash["bus_rider_queue"] 57 | end 58 | 59 | def on_failure_zzz(exception, *args) 60 | # note: sorted alphabetically 61 | @my_queue = nil 62 | end 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/resque_bus/compatibility/deprecated.rb: -------------------------------------------------------------------------------- 1 | module ResqueBus 2 | module Deprecated 3 | def show_deprecations=val 4 | @show_deprecations = val 5 | end 6 | 7 | def show_deprecations? 8 | return @show_deprecations if defined?(@show_deprecations) 9 | return true if !ENV['QUEUES'] && !ENV['QUEUE'] # not in background, probably test 10 | return true if ENV['VVERBOSE'] || ENV['LOGGING'] || ENV['VERBOSE'] 11 | false 12 | end 13 | 14 | def note_deprecation(message) 15 | @noted_deprecations ||= {} 16 | if @noted_deprecations[message] 17 | @noted_deprecations[message] += 1 18 | else 19 | warn(message) if show_deprecations? 20 | @noted_deprecations[message] = 1 21 | end 22 | end 23 | 24 | def redis 25 | ResqueBus.note_deprecation "[DEPRECATION] ResqueBus direct usage is deprecated. Use `QueueBus.redis` instead. Note that it also requires block usage now." 26 | ::Resque.redis 27 | end 28 | 29 | def redis=val 30 | ResqueBus.note_deprecation "[DEPRECATION] ResqueBus can no longer set redis directly. It will use Resque's instance of redis." 31 | end 32 | 33 | def method_missing(method_name, *args, &block) 34 | ResqueBus.note_deprecation "[DEPRECATION] ResqueBus direct usage is deprecated. Use `QueueBus.#{method_name}` instead." 35 | ::QueueBus.send(method_name, *args, &block) 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/resque_bus/compatibility/driver.rb: -------------------------------------------------------------------------------- 1 | module ResqueBus 2 | class Driver 3 | class << self 4 | def perform(attributes={}) 5 | ResqueBus.note_deprecation "[MIGRATION] Note: new events will be using QueueBus::Driver" 6 | ::QueueBus::Driver.perform(attributes) 7 | end 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/resque_bus/compatibility/heartbeat.rb: -------------------------------------------------------------------------------- 1 | module ResqueBus 2 | class Heartbeat 3 | class << self 4 | def perform(attributes={}) 5 | ResqueBus.note_deprecation "[MIGRATION] Note: new events will be using QueueBus::Heartbeat" 6 | ::QueueBus::Heartbeat.perform(attributes) 7 | end 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/resque_bus/compatibility/publisher.rb: -------------------------------------------------------------------------------- 1 | module ResqueBus 2 | # publishes on a delay 3 | class Publisher 4 | class << self 5 | def perform(event_type, attributes = {}) 6 | attributes["bus_event_type"] = event_type # now using one hash only 7 | ResqueBus.note_deprecation "[MIGRATION] Note: new events will be using QueueBus::Publisher" 8 | ::QueueBus::Publisher.perform(attributes) 9 | end 10 | end 11 | 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/resque_bus/compatibility/rider.rb: -------------------------------------------------------------------------------- 1 | require 'resque-retry' 2 | 3 | module ResqueBus 4 | class Rider 5 | extend Resque::Plugins::ExponentialBackoff 6 | 7 | class << self 8 | def perform(attributes = {}) 9 | ResqueBus.note_deprecation "[MIGRATION] Note: new events will be using QueueBus::Rider" 10 | ::QueueBus::Rider.perform(attributes) 11 | end 12 | 13 | # @failure_hooks_already_ran on https://github.com/defunkt/resque/tree/1-x-stable 14 | # to prevent running twice 15 | def queue 16 | @my_queue 17 | end 18 | 19 | def on_failure_aaa(exception, *args) 20 | # note: sorted alphabetically 21 | # queue needs to be set for rety to work (know what queue in Requeue.class_to_queue) 22 | @my_queue = args[0]["bus_rider_queue"] 23 | end 24 | 25 | def on_failure_zzz(exception, *args) 26 | # note: sorted alphabetically 27 | @my_queue = nil 28 | end 29 | 30 | end 31 | end 32 | end -------------------------------------------------------------------------------- /lib/resque_bus/compatibility/subscriber.rb: -------------------------------------------------------------------------------- 1 | module ResqueBus 2 | module Subscriber 3 | def self.included(base) 4 | ResqueBus.note_deprecation "[DEPRECATION] ResqueBus::Subscriber is deprecated. Use QueueBus::Subscriber instead." 5 | base.send(:include, ::QueueBus::Subscriber) 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/resque_bus/compatibility/task_manager.rb: -------------------------------------------------------------------------------- 1 | module ResqueBus 2 | class TaskManager < ::QueueBus::TaskManager 3 | def initialize(logging) 4 | ResqueBus.note_deprecation "[DEPRECATION] ResqueBus::TaskManager is deprecated. Use QueueBus::TaskManager instead." 5 | super(logging) 6 | end 7 | end 8 | end -------------------------------------------------------------------------------- /lib/resque_bus/server.rb: -------------------------------------------------------------------------------- 1 | require 'resque-bus' 2 | require 'resque/server' 3 | require 'erb' 4 | 5 | # MIGRATE TODO: move to resque gem 6 | # Extend ::Resque::Server to add tabs. 7 | module ResqueBus 8 | module Server 9 | 10 | def self.included(base) 11 | base.class_eval { 12 | 13 | get "/bus" do 14 | erb File.read(File.join(File.dirname(__FILE__), "server/views/bus.erb")) 15 | end 16 | 17 | 18 | post '/bus/unsubscribe' do 19 | app = ::QueueBus::Application.new(params[:name]).unsubscribe 20 | redirect u('bus') 21 | end 22 | 23 | } 24 | end 25 | end 26 | end 27 | 28 | ::Resque::Server.tabs << 'Bus' 29 | ::Resque::Server.class_eval do 30 | include ::ResqueBus::Server 31 | end -------------------------------------------------------------------------------- /lib/resque_bus/server/views/bus.erb: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | <% 16 | app_hash = {} 17 | class_hash = {} 18 | event_hash = {} 19 | 20 | # collect each differently 21 | ::QueueBus::Application.all.each do |app| 22 | app_key = app.app_key 23 | 24 | app_hash[app_key] ||= [] 25 | app.event_display_tuples.each do |tuple| 26 | class_name, queue, filters = tuple 27 | if filters["bus_event_type"] 28 | event = filters["bus_event_type"] 29 | else 30 | event = "see filter" 31 | end 32 | 33 | app_hash[app_key] << [event, class_name, queue, filters] 34 | 35 | class_hash[class_name] ||= [] 36 | class_hash[class_name] << [app_key, event, queue, filters] 37 | 38 | event_hash[event] ||= [] 39 | event_hash[event] << [app_key, class_name, queue, filters] 40 | end 41 | end 42 | 43 | # sort each list item by secondary label 44 | class_hash.each do |_, array| 45 | array.sort!{ |a,b| a.first <=> b.first } 46 | end 47 | event_hash.each do |_, array| 48 | array.sort!{ |a,b| a.first <=> b.first } 49 | end 50 | 51 | # helper to display either 52 | def display_row(name, val, button=nil, first=false) 53 | form = "" 54 | if button 55 | text, url = button 56 | form = "
" 57 | end 58 | 59 | if !val 60 | out = "  " 61 | else 62 | one, two, queue, filters = val 63 | out = "#{h(one)}#{h(two)}#{h(queue)}" 64 | out << "#{h(::QueueBus::Util.encode(filters).gsub(/\"bus_special_value_(\w+)\"/){ "(#{$1})" }).gsub(" ", " ").gsub('","', '", "')}" 65 | end 66 | 67 | if first 68 | "#{h(name)}#{form}#{out}\n" 69 | else 70 | " #{out}\n" 71 | end 72 | end 73 | 74 | def output_hash(hash, action=nil) 75 | out = "" 76 | hash.keys.sort.each do |item| 77 | display = hash[item] 78 | first = display.shift 79 | out << display_row(item, first, action, true) 80 | display.each do |val| 81 | out << display_row(item, val, action) 82 | end 83 | end 84 | out 85 | end 86 | %> 87 | 88 | 89 | 90 |

Applications

91 |

The apps below have registered the given classes and queues.

92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | <%= output_hash(app_hash, ["Unsubscribe", "bus/unsubscribe"]) %> 101 |
App KeyEvent TypeClass NameQueueFilters
102 | 103 |

 

104 | 105 |

Events

106 |

The events below have been registered by the given applications and queues.

107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | <%= output_hash(event_hash, false) %> 116 |
Event TypeApp KeyClass NameQueueFilters
117 | 118 | 119 | 120 |

 

121 | 122 |

Classes

123 |

The classes below have been registered by the given applications and queues.

124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | <%= output_hash(class_hash, false) %> 133 |
Class NameApp KeyEvent TypeQueueFilters
134 | -------------------------------------------------------------------------------- /lib/resque_bus/tasks.rb: -------------------------------------------------------------------------------- 1 | # require 'resque_bus/tasks' 2 | # will give you these tasks 3 | 4 | require "queue_bus/tasks" 5 | require "resque/tasks" 6 | 7 | namespace :resquebus do 8 | # deprecated 9 | task :setup => ["queuebus:setup"] do 10 | ResqueBus.note_deprecation "[DEPRECATION] rake resquebus:setup is deprecated. Use rake queuebus:setup instead." 11 | end 12 | 13 | task :driver => ["queuebus:driver"] do 14 | ResqueBus.note_deprecation "[DEPRECATION] rake resquebus:driver is deprecated. Use rake queuebus:driver instead." 15 | end 16 | 17 | task :subscribe => ["queuebus:subscribe"] do 18 | ResqueBus.note_deprecation "[DEPRECATION] rake resquebus:subscribe is deprecated. Use rake queuebus:subscribe instead." 19 | end 20 | 21 | task :unsubsribe => ["queuebus:unsubsribe"] do 22 | ResqueBus.note_deprecation "[DEPRECATION] rake resquebus:driver is deprecated. Use rake queuebus:unsubsribe instead." 23 | end 24 | end 25 | 26 | namespace :queuebus do 27 | 28 | desc "Setup will configure a resque task to run before resque:work" 29 | task :setup => [ :preload ] do 30 | 31 | if ENV['QUEUES'].nil? 32 | manager = ::QueueBus::TaskManager.new(true) 33 | queues = manager.queue_names 34 | ENV['QUEUES'] = queues.join(",") 35 | else 36 | queues = ENV['QUEUES'].split(",") 37 | end 38 | 39 | if queues.size == 1 40 | puts " >> Working Queue : #{queues.first}" 41 | else 42 | puts " >> Working Queues: #{queues.join(", ")}" 43 | end 44 | end 45 | 46 | desc "Sets the queue to work the driver Use: `rake queuebus:driver resque:work`" 47 | task :driver => [ :preload ] do 48 | ENV['QUEUES'] = ::QueueBus.incoming_queue 49 | end 50 | 51 | # Preload app files if this is Rails 52 | task :preload do 53 | require "resque" 54 | require "resque-bus" 55 | require "resque/failure/redis" 56 | require "resque/failure/multiple_with_retry_suppression" 57 | 58 | Resque::Failure::MultipleWithRetrySuppression.classes = [Resque::Failure::Redis] 59 | Resque::Failure.backend = Resque::Failure::MultipleWithRetrySuppression 60 | 61 | Rake::Task["resque:setup"].invoke # loads the environment and such if defined 62 | end 63 | 64 | 65 | # examples to test out the system 66 | namespace :example do 67 | desc "Publishes events to example applications" 68 | task :publish => [ "queuebus:preload", "queuebus:setup" ] do 69 | which = ["one", "two", "three", "other"][rand(4)] 70 | QueueBus.publish("event_#{which}", { "rand" => rand(99999)}) 71 | QueueBus.publish("event_all", { "rand" => rand(99999)}) 72 | QueueBus.publish("none_subscribed", { "rand" => rand(99999)}) 73 | puts "published event_#{which}, event_all, none_subscribed" 74 | end 75 | 76 | desc "Sets up an example config" 77 | task :register => [ "queuebus:preload"] do 78 | QueueBus.dispatch("example") do 79 | subscribe "event_one" do 80 | puts "event1 happened" 81 | end 82 | 83 | subscribe "event_two" do 84 | puts "event2 happened" 85 | end 86 | 87 | high "event_three" do 88 | puts "event3 happened (high)" 89 | end 90 | 91 | low "event_.*" do |attributes| 92 | puts "LOG ALL: #{attributes.inspect}" 93 | end 94 | end 95 | end 96 | 97 | desc "Subscribes this application to QueueBus example events" 98 | task :subscribe => [ :register, "queuebus:subscribe" ] 99 | 100 | desc "Start a QueueBus example worker" 101 | task :work => [ :register, "queuebus:setup", "resque:work" ] 102 | 103 | desc "Start a QueueBus example worker" 104 | task :driver => [ :register, "queuebus:driver", "resque:work" ] 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /lib/resque_bus/version.rb: -------------------------------------------------------------------------------- 1 | module ResqueBus 2 | VERSION = "0.7.0" 3 | end 4 | -------------------------------------------------------------------------------- /resque-bus.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "resque_bus/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "resque-bus" 7 | s.version = ResqueBus::VERSION 8 | s.authors = ["Brian Leonard"] 9 | s.email = ["brian@bleonard.com"] 10 | s.homepage = "https://github.com/queue-bus/resque-bus" 11 | s.summary = %q{A simple event bus on top of Resque} 12 | s.description = %q{A simple event bus on top of Resque. Publish and subscribe to events as they occur through a queue.} 13 | 14 | s.files = `git ls-files`.split("\n") 15 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 16 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 17 | s.require_paths = ["lib"] 18 | 19 | s.add_dependency('queue-bus', ['>= 0.7', '< 1']) 20 | s.add_dependency('resque', ['>= 1.10.0', '< 2.0']) 21 | s.add_dependency('resque-scheduler', '>= 2.0.1') 22 | s.add_dependency('resque-retry') 23 | 24 | s.add_development_dependency("rspec") 25 | s.add_development_dependency("timecop") 26 | s.add_development_dependency("json_pure") 27 | end 28 | -------------------------------------------------------------------------------- /spec/adapter/compatibility_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Compatibility with old resque-bus" do 4 | before(:each) do 5 | ResqueBus.show_deprecations = false # expected 6 | 7 | QueueBus.dispatch("r1") do 8 | subscribe "event_name" do |attributes| 9 | QueueBus::Runner1.run(attributes) 10 | end 11 | end 12 | 13 | QueueBus::TaskManager.new(false).subscribe! 14 | 15 | @incoming = Resque::Worker.new(:resquebus_incoming) 16 | @incoming.register_worker 17 | 18 | @new_incoming = Resque::Worker.new(:bus_incoming) 19 | @new_incoming.register_worker 20 | 21 | @rider = Resque::Worker.new(:r1_default) 22 | @rider.register_worker 23 | end 24 | 25 | describe "Publisher" do 26 | it "should still publish as expected" do 27 | val = QueueBus.redis { |redis| redis.lpop("queue:resquebus_incoming") } 28 | val.should == nil 29 | 30 | args = [ "event_name", {"bus_event_type"=>"event_name", "two"=>"here", "one"=>1, "id" => 12} ] 31 | item = {:class => "ResqueBus::Publisher", :args => args} 32 | 33 | QueueBus.redis { |redis| redis.sadd(:queues, "resquebus_incoming") } 34 | QueueBus.redis { |redis| redis.rpush "queue:resquebus_incoming", Resque.encode(item) } 35 | 36 | QueueBus::Runner1.value.should == 0 37 | 38 | perform_next_job @incoming # publish 39 | 40 | QueueBus::Runner1.value.should == 0 41 | 42 | perform_next_job @new_incoming # drive 43 | 44 | QueueBus::Runner1.value.should == 0 45 | 46 | perform_next_job @rider # ride 47 | 48 | QueueBus::Runner1.value.should == 1 49 | 50 | end 51 | end 52 | 53 | describe "Rider" do 54 | it "should still ride as expected" do 55 | val = QueueBus.redis { |redis| redis.lpop("queue:r1_default") } 56 | val.should == nil 57 | 58 | args = [ {"bus_rider_app_key"=>"r1", "x" => "y", "bus_event_type" => "event_name", 59 | "bus_rider_sub_key"=>"event_name", "bus_rider_queue" => "default", 60 | "bus_rider_class_name"=>"::ResqueBus::Rider"}] 61 | item = {:class => "ResqueBus::Rider", :args => args} 62 | 63 | QueueBus.redis { |redis| redis.sadd(:queues, "r1_default") } 64 | QueueBus.redis { |redis| redis.rpush "queue:r1_default", Resque.encode(item) } 65 | 66 | QueueBus::Runner1.value.should == 0 67 | 68 | perform_next_job @rider 69 | 70 | QueueBus::Runner1.value.should == 1 71 | end 72 | end 73 | 74 | describe "Driver" do 75 | it "should still drive as expected" do 76 | val = QueueBus.redis { |redis| redis.lpop("queue:resquebus_incoming") } 77 | val.should == nil 78 | 79 | args = [ {"bus_event_type" => "event_name", "two"=>"here", "one"=>1, "id" => 12} ] 80 | item = {:class => "ResqueBus::Driver", :args => args} 81 | 82 | QueueBus.redis { |redis| redis.sadd(:queues, "resquebus_incoming") } 83 | QueueBus.redis { |redis| redis.rpush "queue:resquebus_incoming", Resque.encode(item) } 84 | 85 | QueueBus::Runner1.value.should == 0 86 | 87 | perform_next_job @incoming 88 | 89 | QueueBus::Runner1.value.should == 0 90 | 91 | perform_next_job @rider 92 | 93 | QueueBus::Runner1.value.should == 1 94 | 95 | end 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /spec/adapter/integration_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Resque Integration" do 4 | describe "Happy Path" do 5 | before(:each) do 6 | QueueBus.dispatch("r1") do 7 | subscribe "event_name" do |attributes| 8 | QueueBus::Runner1.run(attributes) 9 | end 10 | end 11 | 12 | QueueBus::TaskManager.new(false).subscribe! 13 | 14 | @incoming = Resque::Worker.new(:bus_incoming) 15 | @incoming.register_worker 16 | 17 | @rider = Resque::Worker.new(:r1_default) 18 | @rider.register_worker 19 | end 20 | 21 | it "should publish and receive" do 22 | QueueBus::Runner1.value.should == 0 23 | 24 | QueueBus.publish("event_name", "ok" => true) 25 | QueueBus::Runner1.value.should == 0 26 | 27 | perform_next_job @incoming 28 | 29 | QueueBus::Runner1.value.should == 0 30 | 31 | perform_next_job @rider 32 | 33 | QueueBus::Runner1.value.should == 1 34 | end 35 | end 36 | 37 | describe "Delayed Publishing" do 38 | before(:each) do 39 | Timecop.freeze(now) 40 | QueueBus.stub(:generate_uuid).and_return("idfhlkj") 41 | end 42 | after(:each) do 43 | Timecop.return 44 | end 45 | let(:delayed_attrs) { {"bus_delayed_until" => future.to_i, 46 | "bus_id" => "#{now.to_i}-idfhlkj", 47 | "bus_app_hostname" => `hostname 2>&1`.strip.sub(/.local/,'')} } 48 | 49 | let(:bus_attrs) { delayed_attrs.merge({"bus_published_at" => worktime.to_i})} 50 | let(:now) { Time.parse("01/01/2013 5:00")} 51 | let(:future) { Time.at(now.to_i + 60) } 52 | let(:worktime) {Time.at(future.to_i + 1)} 53 | 54 | it "should add it to Redis" do 55 | hash = {:one => 1, "two" => "here", "id" => 12 } 56 | event_name = "event_name" 57 | QueueBus.publish_at(future, event_name, hash) 58 | 59 | schedule = QueueBus.redis { |redis| redis.zrange("delayed_queue_schedule", 0, 1) } 60 | schedule.should == [future.to_i.to_s] 61 | 62 | val = QueueBus.redis { |redis| redis.lpop("delayed:#{future.to_i}") } 63 | hash = JSON.parse(val) 64 | 65 | hash["class"].should == "QueueBus::Worker" 66 | hash["args"].size.should == 1 67 | JSON.parse(hash["args"].first).should == {"bus_class_proxy" => "QueueBus::Publisher", "bus_event_type"=>"event_name", "two"=>"here", "one"=>1, "id" => 12}.merge(delayed_attrs) 68 | hash["queue"].should == "bus_incoming" 69 | end 70 | 71 | it "should move it to the real queue when processing" do 72 | hash = {:one => 1, "two" => "here", "id" => 12 } 73 | event_name = "event_name" 74 | 75 | val = QueueBus.redis { |redis| redis.lpop("queue:bus_incoming") } 76 | val.should == nil 77 | 78 | QueueBus.publish_at(future, event_name, hash) 79 | 80 | val = QueueBus.redis { |redis| redis.lpop("queue:bus_incoming") } 81 | val.should == nil # nothing really added 82 | 83 | # process sceduler now 84 | Resque::Scheduler.handle_delayed_items 85 | 86 | val = QueueBus.redis { |redis| redis.lpop("queue:bus_incoming") } 87 | val.should == nil # nothing added yet 88 | 89 | # process scheduler in future 90 | Timecop.freeze(worktime) do 91 | Resque::Scheduler.handle_delayed_items 92 | 93 | # added 94 | val = QueueBus.redis { |redis| redis.lpop("queue:bus_incoming") } 95 | hash = JSON.parse(val) 96 | hash["class"].should == "QueueBus::Worker" 97 | hash["args"].size.should == 1 98 | JSON.parse(hash["args"].first).should == {"bus_class_proxy" => "QueueBus::Publisher", "bus_event_type"=>"event_name", "two"=>"here", "one"=>1, "id" => 12}.merge(delayed_attrs) 99 | 100 | QueueBus::Publisher.perform(JSON.parse(hash["args"].first)) 101 | 102 | val = QueueBus.redis { |redis| redis.lpop("queue:bus_incoming") } 103 | hash = JSON.parse(val) 104 | hash["class"].should == "QueueBus::Worker" 105 | hash["args"].size.should == 1 106 | JSON.parse(hash["args"].first).should == {"bus_class_proxy" => "QueueBus::Driver", "bus_event_type"=>"event_name", "two"=>"here", "one"=>1, "id" => 12}.merge(bus_attrs) 107 | end 108 | end 109 | 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /spec/adapter/publish_at_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Publishing an event in the future" do 4 | 5 | before(:each) do 6 | Timecop.freeze(now) 7 | QueueBus.stub(:generate_uuid).and_return("idfhlkj") 8 | end 9 | after(:each) do 10 | Timecop.return 11 | end 12 | let(:delayed_attrs) { {"bus_delayed_until" => future.to_i, 13 | "bus_id" => "#{now.to_i}-idfhlkj", 14 | "bus_app_hostname" => `hostname 2>&1`.strip.sub(/.local/,'')} } 15 | 16 | let(:bus_attrs) { delayed_attrs.merge({"bus_published_at" => worktime.to_i})} 17 | let(:now) { Time.parse("01/01/2013 5:00")} 18 | let(:future) { Time.at(now.to_i + 60) } 19 | let(:worktime) {Time.at(future.to_i + 1)} 20 | 21 | it "should add it to Redis then to the real queue" do 22 | hash = {:one => 1, "two" => "here", "id" => 12 } 23 | event_name = "event_name" 24 | QueueBus.publish_at(future, event_name, hash) 25 | 26 | schedule = QueueBus.redis { |redis| redis.zrange("delayed_queue_schedule", 0, 1) } 27 | schedule.should == [future.to_i.to_s] 28 | 29 | val = QueueBus.redis { |redis| redis.lpop("delayed:#{future.to_i}") } 30 | hash = JSON.parse(val) 31 | 32 | hash["class"].should == "QueueBus::Worker" 33 | hash["args"].size.should == 1 34 | JSON.parse(hash["args"].first).should == {"bus_class_proxy" => "QueueBus::Publisher", "bus_event_type"=>"event_name", "two"=>"here", "one"=>1, "id" => 12}.merge(delayed_attrs) 35 | hash["queue"].should == "bus_incoming" 36 | 37 | val = QueueBus.redis { |redis| redis.lpop("queue:bus_incoming") } 38 | val.should == nil # nothing really added 39 | 40 | Timecop.freeze(worktime) 41 | QueueBus::Publisher.perform(JSON.parse(hash["args"].first)) 42 | 43 | val = QueueBus.redis { |redis| redis.lpop("queue:bus_incoming") } 44 | hash = JSON.parse(val) 45 | hash["class"].should == "QueueBus::Worker" 46 | hash["args"].size.should == 1 47 | JSON.parse(hash["args"].first).should == {"bus_class_proxy" => "QueueBus::Driver", "bus_event_type"=>"event_name", "two"=>"here", "one"=>1, "id" => 12}.merge(bus_attrs) 48 | end 49 | 50 | end 51 | -------------------------------------------------------------------------------- /spec/adapter/retry_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Retry" do 4 | class RetryTest1 5 | include QueueBus::Subscriber 6 | application :my_thing 7 | subscribe :event_sub 8 | def event_sub(attributes) 9 | QueueBus::Runner1.run(attributes) 10 | end 11 | end 12 | 13 | it "should have the methods" do 14 | ::QueueBus::Worker.methods.should include(:on_failure_aaa) 15 | ::QueueBus::Worker.methods.should include(:on_failure_zzz) 16 | end 17 | 18 | # it "should retry failed riders" 19 | 20 | describe "Failed Jobs" do 21 | before(:each) do 22 | QueueBus.enqueue_to("testing", "QueueBus::Worker", { "bus_class_proxy" => "QueueBus::Rider", "bus_rider_app_key" => "r2", "bus_rider_sub_key" => "event_name", "bus_event_type" => "event_name", "ok" => true, "bus_rider_queue" => "testing" }) 23 | 24 | @worker = Resque::Worker.new(:testing) 25 | @worker.register_worker 26 | end 27 | 28 | it "should put it in the failed jobs" do 29 | 30 | QueueBus.dispatch("r2") do 31 | subscribe "event_name" do |attributes| 32 | raise "boo!" 33 | end 34 | end 35 | 36 | perform_next_job @worker 37 | Resque.info[:processed].should == 1 38 | Resque.info[:failed].should == 1 39 | Resque.info[:pending].should == 1 # requeued 40 | 41 | perform_next_job @worker 42 | Resque.info[:processed].should == 2 43 | Resque.info[:failed].should == 2 44 | Resque.info[:pending].should == 0 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/adapter/support.rb: -------------------------------------------------------------------------------- 1 | require 'resque-bus' 2 | require 'resque' 3 | require 'resque/scheduler' 4 | 5 | def reset_test_adapter 6 | QueueBus.send(:reset) 7 | QueueBus.adapter = QueueBus::Adapters::Resque.new 8 | end 9 | 10 | def adapter_under_test_class 11 | QueueBus::Adapters::Resque 12 | end 13 | 14 | def adapter_under_test_symbol 15 | :resque 16 | end 17 | 18 | def perform_next_job(worker, &block) 19 | return unless job = worker.reserve 20 | worker.perform(job, &block) 21 | worker.done_working 22 | end 23 | 24 | -------------------------------------------------------------------------------- /spec/adapter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "adapter is set" do 4 | it "should call it's enabled! method on init" do 5 | QueueBus.send(:reset) 6 | adapter_under_test_class.any_instance.should_receive(:enabled!) 7 | instance = adapter_under_test_class.new 8 | QueueBus.send(:reset) 9 | end 10 | 11 | it "should be defaulting to Data from spec_helper" do 12 | QueueBus.adapter.is_a?(adapter_under_test_class).should == true 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/application_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module QueueBus 4 | describe Application do 5 | describe ".all" do 6 | it "should return empty array when none" do 7 | Application.all.should == [] 8 | end 9 | it "should return registered applications when there are some" do 10 | Application.new("One").subscribe(test_list(test_sub("fdksjh"))) 11 | Application.new("Two").subscribe(test_list(test_sub("fdklhf"))) 12 | Application.new("Three").subscribe(test_list(test_sub("fkld"))) 13 | 14 | Application.all.collect(&:app_key).should =~ ["one", "two", "three"] 15 | 16 | Application.new("two").unsubscribe 17 | Application.all.collect(&:app_key).should =~ ["one", "three"] 18 | end 19 | end 20 | 21 | describe ".new" do 22 | it "should have a key" do 23 | Application.new("something").app_key.should == "something" 24 | 25 | Application.new("some thing").app_key.should == "some_thing" 26 | Application.new("some-thing").app_key.should == "some_thing" 27 | Application.new("some_thing").app_key.should == "some_thing" 28 | Application.new("Some Thing").app_key.should == "some_thing" 29 | end 30 | 31 | it "should raise an error if not valid" do 32 | lambda { 33 | Application.new("") 34 | }.should raise_error 35 | 36 | lambda { 37 | Application.new(nil) 38 | }.should raise_error 39 | 40 | lambda { 41 | Application.new("/") 42 | }.should raise_error 43 | end 44 | end 45 | 46 | describe "#read_redis_hash" do 47 | it "should handle old and new values" do 48 | 49 | QueueBus.redis { |redis| redis.hset("bus_app:myapp", "new_one", QueueBus::Util.encode("queue_name" => "x", "bus_event_type" => "event_name") ) } 50 | QueueBus.redis { |redis| redis.hset("bus_app:myapp", "old_one", "oldqueue_name") } 51 | app = Application.new("myapp") 52 | val = app.send(:read_redis_hash) 53 | val.should == {"new_one" => {"queue_name" => "x", "bus_event_type" => "event_name"}, "old_one" => "oldqueue_name"} 54 | end 55 | end 56 | 57 | describe "#subscribe" do 58 | let(:sub1) { test_sub("event_one", "default") } 59 | let(:sub2) { test_sub("event_two", "default") } 60 | let(:sub3) { test_sub("event_three", "other") } 61 | it "should add array to redis" do 62 | QueueBus.redis { |redis| redis.get("bus_app:myapp") }.should be_nil 63 | Application.new("myapp").subscribe(test_list(sub1, sub2)) 64 | 65 | QueueBus.redis { |redis| redis.hgetall("bus_app:myapp") }.should == {"event_two"=>"{\"queue_name\":\"default\",\"key\":\"event_two\",\"class\":\"::QueueBus::Rider\",\"matcher\":{\"bus_event_type\":\"event_two\"}}", 66 | "event_one"=>"{\"queue_name\":\"default\",\"key\":\"event_one\",\"class\":\"::QueueBus::Rider\",\"matcher\":{\"bus_event_type\":\"event_one\"}}"} 67 | QueueBus.redis { |redis| redis.hkeys("bus_app:myapp") }.should =~ ["event_one", "event_two"] 68 | QueueBus.redis { |redis| redis.smembers("bus_apps") }.should =~ ["myapp"] 69 | end 70 | it "should add string to redis" do 71 | QueueBus.redis { |redis| redis.get("bus_app:myapp") }.should be_nil 72 | Application.new("myapp").subscribe(test_list(sub1)) 73 | 74 | QueueBus.redis { |redis| redis.hgetall("bus_app:myapp") }.should == {"event_one"=>"{\"queue_name\":\"default\",\"key\":\"event_one\",\"class\":\"::QueueBus::Rider\",\"matcher\":{\"bus_event_type\":\"event_one\"}}"} 75 | QueueBus.redis { |redis| redis.hkeys("bus_app:myapp") }.should =~ ["event_one"] 76 | QueueBus.redis { |redis| redis.smembers("bus_apps") }.should =~ ["myapp"] 77 | end 78 | it "should multiple queues to redis" do 79 | QueueBus.redis { |redis| redis.get("bus_app:myapp") }.should be_nil 80 | Application.new("myapp").subscribe(test_list(sub1, sub2, sub3)) 81 | QueueBus.redis { |redis| redis.hgetall("bus_app:myapp") }.should == {"event_two"=>"{\"queue_name\":\"default\",\"key\":\"event_two\",\"class\":\"::QueueBus::Rider\",\"matcher\":{\"bus_event_type\":\"event_two\"}}", "event_one"=>"{\"queue_name\":\"default\",\"key\":\"event_one\",\"class\":\"::QueueBus::Rider\",\"matcher\":{\"bus_event_type\":\"event_one\"}}", 82 | "event_three"=>"{\"queue_name\":\"other\",\"key\":\"event_three\",\"class\":\"::QueueBus::Rider\",\"matcher\":{\"bus_event_type\":\"event_three\"}}"} 83 | QueueBus.redis { |redis| redis.hkeys("bus_app:myapp") }.should =~ ["event_three", "event_two", "event_one"] 84 | QueueBus.redis { |redis| redis.smembers("bus_apps") }.should =~ ["myapp"] 85 | end 86 | 87 | it "should do nothing if nil or empty" do 88 | 89 | QueueBus.redis { |redis| redis.get("bus_app:myapp") }.should be_nil 90 | 91 | Application.new("myapp").subscribe(nil) 92 | QueueBus.redis { |redis| redis.get("bus_app:myapp") }.should be_nil 93 | 94 | Application.new("myapp").subscribe([]) 95 | QueueBus.redis { |redis| redis.get("bus_app:myapp") }.should be_nil 96 | end 97 | 98 | it "should call unsubscribe" do 99 | app = Application.new("myapp") 100 | app.should_receive(:unsubscribe) 101 | app.subscribe([]) 102 | end 103 | end 104 | 105 | describe "#unsubscribe" do 106 | it "should remove items" do 107 | QueueBus.redis { |redis| redis.sadd("bus_apps", "myapp") } 108 | QueueBus.redis { |redis| redis.sadd("bus_apps", "other") } 109 | QueueBus.redis { |redis| redis.hset("bus_app:myapp", "event_one", "myapp_default") } 110 | 111 | Application.new("myapp").unsubscribe 112 | 113 | QueueBus.redis { |redis| redis.smembers("bus_apps") }.should == ["other"] 114 | QueueBus.redis { |redis| redis.get("bus_app:myapp") }.should be_nil 115 | end 116 | end 117 | 118 | describe "#subscription_matches" do 119 | it "should return if it is there" do 120 | Application.new("myapp").subscription_matches("bus_event_type"=>"three").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should == [] 121 | 122 | subs = test_list(test_sub("one_x"), test_sub("one_y"), test_sub("one"), test_sub("two")) 123 | Application.new("myapp").subscribe(subs) 124 | Application.new("myapp").subscription_matches("bus_event_type"=>"three").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should == [] 125 | 126 | Application.new("myapp").subscription_matches("bus_event_type"=>"two").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should =~ [["myapp", "two", "default", "::QueueBus::Rider"]] 127 | Application.new("myapp").subscription_matches("bus_event_type"=>"one").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should =~ [["myapp", "one", "default", "::QueueBus::Rider"]] 128 | end 129 | 130 | it "should handle * wildcards" do 131 | subs = test_list(test_sub("one.+"), test_sub("one"), test_sub("one_.*"), test_sub("two")) 132 | Application.new("myapp").subscribe(subs) 133 | Application.new("myapp").subscription_matches("bus_event_type"=>"three").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should == [] 134 | 135 | Application.new("myapp").subscription_matches("bus_event_type"=>"onex").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should =~ [["myapp", "one.+", "default", "::QueueBus::Rider"]] 136 | Application.new("myapp").subscription_matches("bus_event_type"=>"one").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should =~ [["myapp", "one", "default", "::QueueBus::Rider"]] 137 | Application.new("myapp").subscription_matches("bus_event_type"=>"one_x").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should =~ [["myapp", "one.+","default", "::QueueBus::Rider"], ["myapp", "one_.*", "default", "::QueueBus::Rider"]] 138 | end 139 | 140 | it "should handle actual regular expressions" do 141 | subs = test_list(test_sub(/one.+/), test_sub("one"), test_sub(/one_.*/), test_sub("two")) 142 | Application.new("myapp").subscribe(subs) 143 | Application.new("myapp").subscription_matches("bus_event_type"=>"three").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should == [] 144 | 145 | Application.new("myapp").subscription_matches("bus_event_type"=>"onex").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should =~ [["myapp", "(?-mix:one.+)", "default", "::QueueBus::Rider"]] 146 | Application.new("myapp").subscription_matches("bus_event_type"=>"donex").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should =~ [["myapp", "(?-mix:one.+)", "default", "::QueueBus::Rider"]] 147 | Application.new("myapp").subscription_matches("bus_event_type"=>"one").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should =~ [["myapp", "one" ,"default", "::QueueBus::Rider"]] 148 | Application.new("myapp").subscription_matches("bus_event_type"=>"one_x").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should =~ [["myapp", "(?-mix:one.+)", "default", "::QueueBus::Rider"], ["myapp", "(?-mix:one_.*)", "default", "::QueueBus::Rider"]] 149 | end 150 | end 151 | end 152 | end 153 | -------------------------------------------------------------------------------- /spec/config_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module QueueBus 4 | module Adapters 5 | class TestOne 6 | 7 | end 8 | end 9 | end 10 | 11 | describe "QueueBus config" do 12 | it "should set the default app key" do 13 | QueueBus.default_app_key.should == nil 14 | 15 | QueueBus.default_app_key = "my_app" 16 | QueueBus.default_app_key.should == "my_app" 17 | 18 | QueueBus.default_app_key = "something here" 19 | QueueBus.default_app_key.should == "something_here" 20 | end 21 | 22 | it "should set the default queue" do 23 | QueueBus.default_queue.should == nil 24 | 25 | QueueBus.default_queue = "my_queue" 26 | QueueBus.default_queue.should == "my_queue" 27 | end 28 | 29 | it "should set the local mode" do 30 | QueueBus.local_mode.should == nil 31 | QueueBus.local_mode = :standalone 32 | QueueBus.local_mode.should == :standalone 33 | end 34 | 35 | it "should set the hostname" do 36 | QueueBus.hostname.should_not == nil 37 | QueueBus.hostname = "whatever" 38 | QueueBus.hostname.should == "whatever" 39 | end 40 | 41 | it "should set before_publish callback" do 42 | QueueBus.before_publish = lambda {|attributes| 42 } 43 | QueueBus.before_publish_callback({}).should == 42 44 | end 45 | 46 | 47 | it "should use the default Redis connection" do 48 | QueueBus.redis { |redis| redis }.should_not eq(nil) 49 | end 50 | 51 | it "should default to given adapter" do 52 | QueueBus.adapter.is_a?(adapter_under_test_class).should == true 53 | 54 | # and should raise if already set 55 | lambda { 56 | QueueBus.adapter = :data 57 | }.should raise_error 58 | end 59 | 60 | context "with a fresh load" do 61 | before(:each) do 62 | QueueBus.send(:reset) 63 | end 64 | 65 | it "should be able to be set to resque" do 66 | QueueBus.adapter = adapter_under_test_symbol 67 | QueueBus.adapter.is_a?(adapter_under_test_class).should == true 68 | 69 | # and should raise if already set 70 | lambda { 71 | QueueBus.adapter = :data 72 | }.should raise_error 73 | end 74 | 75 | it "should be able to be set to something else" do 76 | 77 | QueueBus.adapter = :test_one 78 | QueueBus.adapter.is_a?(QueueBus::Adapters::TestOne).should == true 79 | end 80 | end 81 | 82 | 83 | end 84 | -------------------------------------------------------------------------------- /spec/dispatch_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module QueueBus 4 | describe Dispatch do 5 | it "should not start with any applications" do 6 | Dispatch.new("d").subscriptions.size.should == 0 7 | end 8 | 9 | it "should register code to run and execute it" do 10 | dispatch = Dispatch.new("d") 11 | dispatch.subscribe("my_event") do |attrs| 12 | Runner1.run(attrs) 13 | end 14 | sub = dispatch.subscriptions.key("my_event") 15 | sub.send(:executor).is_a?(Proc).should == true 16 | 17 | Runner.value.should == 0 18 | dispatch.execute("my_event", {"bus_event_type" => "my_event", "ok" => true}) 19 | Runner1.value.should == 1 20 | Runner1.attributes.should == {"bus_event_type" => "my_event", "ok" => true} 21 | 22 | end 23 | 24 | it "should not crash if not there" do 25 | lambda { 26 | Dispatch.new("d").execute("fdkjh", "bus_event_type" => "fdkjh") 27 | }.should_not raise_error 28 | end 29 | 30 | describe "Top Level" do 31 | before(:each) do 32 | QueueBus.dispatch("testit") do 33 | subscribe "event1" do |attributes| 34 | Runner2.run(attributes) 35 | end 36 | 37 | subscribe "event2" do 38 | Runner2.run({}) 39 | end 40 | 41 | high "event3" do 42 | Runner2.run({}) 43 | end 44 | 45 | low /^patt.+ern/ do 46 | Runner.run({}) 47 | end 48 | end 49 | end 50 | 51 | it "should register and run" do 52 | Runner2.value.should == 0 53 | QueueBus.dispatcher_execute("testit", "event2", "bus_event_type" => "event2") 54 | Runner2.value.should == 1 55 | QueueBus.dispatcher_execute("testit", "event1", "bus_event_type" => "event1") 56 | Runner2.value.should == 2 57 | QueueBus.dispatcher_execute("testit", "event1", "bus_event_type" => "event1") 58 | Runner2.value.should == 3 59 | end 60 | 61 | it "should return the subscriptions" do 62 | dispatcher = QueueBus.dispatcher_by_key("testit") 63 | subs = dispatcher.subscriptions.all 64 | tuples = subs.collect{ |sub| [sub.key, sub.queue_name]} 65 | tuples.should =~ [ ["event1", "testit_default"], 66 | ["event2", "testit_default"], 67 | ["event3", "testit_high"], 68 | [ "(?-mix:^patt.+ern)", "testit_low"] 69 | ] 70 | end 71 | 72 | end 73 | end 74 | 75 | end 76 | 77 | -------------------------------------------------------------------------------- /spec/driver_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module QueueBus 4 | describe Driver do 5 | before(:each) do 6 | Application.new("app1").subscribe(test_list(test_sub("event1"), test_sub("event2"), test_sub("event3"))) 7 | Application.new("app2").subscribe(test_list(test_sub("event2","other"), test_sub("event4", "more"))) 8 | Application.new("app3").subscribe(test_list(test_sub("event[45]"), test_sub("event5"), test_sub("event6"))) 9 | Timecop.freeze 10 | end 11 | after(:each) do 12 | Timecop.return 13 | end 14 | 15 | let(:bus_attrs) { {"bus_driven_at" => Time.now.to_i, "bus_rider_class_name"=>"::QueueBus::Rider", "bus_class_proxy" => "::QueueBus::Rider"} } 16 | 17 | describe ".subscription_matches" do 18 | it "return empty array when none" do 19 | Driver.subscription_matches("bus_event_type" => "else").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should == [] 20 | Driver.subscription_matches("bus_event_type" => "event").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should == [] 21 | end 22 | it "should return a match" do 23 | Driver.subscription_matches("bus_event_type" => "event1").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should =~ [["app1", "event1", "default", "::QueueBus::Rider"]] 24 | Driver.subscription_matches("bus_event_type" => "event6").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should =~ [["app3", "event6", "default", "::QueueBus::Rider"]] 25 | end 26 | it "should match multiple apps" do 27 | Driver.subscription_matches("bus_event_type" => "event2").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should =~ [["app1", "event2", "default", "::QueueBus::Rider"], ["app2", "event2", "other", "::QueueBus::Rider"]] 28 | end 29 | it "should match multiple apps with patterns" do 30 | Driver.subscription_matches("bus_event_type" => "event4").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should =~ [["app3", "event[45]", "default", "::QueueBus::Rider"], ["app2", "event4", "more", "::QueueBus::Rider"]] 31 | end 32 | it "should match multiple events in same app" do 33 | Driver.subscription_matches("bus_event_type" => "event5").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should =~ [["app3", "event[45]", "default", "::QueueBus::Rider"], ["app3", "event5", "default", "::QueueBus::Rider"]] 34 | end 35 | end 36 | 37 | describe ".perform" do 38 | let(:attributes) { {"x" => "y"} } 39 | 40 | before(:each) do 41 | QueueBus.redis { |redis| redis.smembers("queues") }.should == [] 42 | QueueBus.redis { |redis| redis.lpop("queue:app1_default") }.should be_nil 43 | QueueBus.redis { |redis| redis.lpop("queue:app2_default") }.should be_nil 44 | QueueBus.redis { |redis| redis.lpop("queue:app3_default") }.should be_nil 45 | end 46 | 47 | it "should do nothing when empty" do 48 | Driver.perform(attributes.merge("bus_event_type" => "else")) 49 | QueueBus.redis { |redis| redis.smembers("queues") }.should == [] 50 | end 51 | 52 | it "should queue up the riders in redis" do 53 | QueueBus.redis { |redis| redis.lpop("queue:app1_default") }.should be_nil 54 | Driver.perform(attributes.merge("bus_event_type" => "event1")) 55 | QueueBus.redis { |redis| redis.smembers("queues") }.should =~ ["default"] 56 | 57 | hash = JSON.parse(QueueBus.redis { |redis| redis.lpop("queue:default") }) 58 | hash["class"].should == "QueueBus::Worker" 59 | hash["args"].size.should == 1 60 | JSON.parse(hash["args"].first).should == {"bus_rider_app_key"=>"app1", "x" => "y", "bus_event_type" => "event1", "bus_rider_sub_key"=>"event1", "bus_rider_queue" => "default"}.merge(bus_attrs) 61 | end 62 | 63 | it "should queue up to multiple" do 64 | Driver.perform(attributes.merge("bus_event_type" => "event4")) 65 | QueueBus.redis { |redis| redis.smembers("queues") }.should =~ ["default", "more"] 66 | 67 | hash = JSON.parse(QueueBus.redis { |redis| redis.lpop("queue:more") }) 68 | hash["class"].should == "QueueBus::Worker" 69 | hash["args"].size.should == 1 70 | JSON.parse(hash["args"].first).should == {"bus_rider_app_key"=>"app2", "x" => "y", "bus_event_type" => "event4", "bus_rider_sub_key"=>"event4", "bus_rider_queue" => "more"}.merge(bus_attrs) 71 | 72 | hash = JSON.parse(QueueBus.redis { |redis| redis.lpop("queue:default") }) 73 | hash["class"].should == "QueueBus::Worker" 74 | hash["args"].size.should == 1 75 | JSON.parse(hash["args"].first).should == {"bus_rider_app_key"=>"app3", "x" => "y", "bus_event_type" => "event4", "bus_rider_sub_key"=>"event[45]", "bus_rider_queue" => "default"}.merge(bus_attrs) 76 | end 77 | 78 | it "should queue up to the same" do 79 | Driver.perform(attributes.merge("bus_event_type" => "event5")) 80 | QueueBus.redis { |redis| redis.smembers("queues") }.should =~ ["default"] 81 | 82 | QueueBus.redis { |redis| redis.llen("queue:default") }.should == 2 83 | 84 | pop1 = JSON.parse(QueueBus.redis { |redis| redis.lpop("queue:default") }) 85 | pop2 = JSON.parse(QueueBus.redis { |redis| redis.lpop("queue:default") }) 86 | 87 | pargs1 = JSON.parse(pop1["args"].first) 88 | pargs2 = JSON.parse(pop2["args"].first) 89 | if pargs1["bus_rider_sub_key"] == "event5" 90 | hash1 = pop1 91 | hash2 = pop2 92 | args1 = pargs1 93 | args2 = pargs2 94 | else 95 | hash1 = pop2 96 | hash2 = pop1 97 | args1 = pargs2 98 | args2 = pargs1 99 | end 100 | 101 | hash1["class"].should == "QueueBus::Worker" 102 | args1.should == {"bus_rider_app_key"=>"app3", "x" => "y", "bus_event_type" => "event5", "bus_rider_sub_key"=>"event5", "bus_rider_queue" => "default"}.merge(bus_attrs) 103 | 104 | hash2["class"].should == "QueueBus::Worker" 105 | args2.should == {"bus_rider_app_key"=>"app3", "x" => "y", "bus_event_type" => "event5", "bus_rider_sub_key"=>"event[45]", "bus_rider_queue" => "default"}.merge(bus_attrs) 106 | end 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /spec/heartbeat_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module QueueBus 4 | describe Heartbeat do 5 | def now_attributes 6 | { 7 | "epoch_seconds" => (Time.now.to_i / 60) * 60, # rounded 8 | "epoch_minutes" => Time.now.to_i / 60, 9 | "epoch_hours" => Time.now.to_i / (60*60), 10 | "epoch_days" => Time.now.to_i / (60*60*24), 11 | "minute" => Time.now.min, 12 | "hour" => Time.now.hour, 13 | "day" => Time.now.day, 14 | "month" => Time.now.month, 15 | "year" => Time.now.year, 16 | "yday" => Time.now.yday, 17 | "wday" => Time.now.wday 18 | } 19 | end 20 | 21 | it "should publish the current time once" do 22 | Timecop.freeze "12/12/2013 12:01:19" do 23 | QueueBus.should_receive(:publish).with("heartbeat_minutes", now_attributes) 24 | Heartbeat.perform 25 | end 26 | 27 | Timecop.freeze "12/12/2013 12:01:40" do 28 | Heartbeat.perform 29 | end 30 | end 31 | 32 | it "should publish a minute later" do 33 | Timecop.freeze "12/12/2013 12:01:19" do 34 | QueueBus.should_receive(:publish).with("heartbeat_minutes", now_attributes) 35 | Heartbeat.perform 36 | end 37 | 38 | Timecop.freeze "12/12/2013 12:02:01" do 39 | QueueBus.should_receive(:publish).with("heartbeat_minutes", now_attributes) 40 | Heartbeat.perform 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/integration_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module QueueBus 4 | describe "Integration" do 5 | it "should round trip attributes" do 6 | write1 = Subscription.new("default", "key1", "MyClass1", {"bus_event_type" => "event_one"}) 7 | write2 = Subscription.new("else_ok", "key2", "MyClass2", {"bus_event_type" => /^[ab]here/}) #regex 8 | 9 | write1.matches?("bus_event_type" => "event_one").should == true 10 | write1.matches?("bus_event_type" => "event_one1").should == false 11 | write1.matches?("bus_event_type" => "aevent_one").should == false 12 | 13 | write2.matches?("bus_event_type" => "ahere").should == true 14 | write2.matches?("bus_event_type" => "bhere").should == true 15 | write2.matches?("bus_event_type" => "qhere").should == false 16 | write2.matches?("bus_event_type" => "abhere").should == false 17 | write2.matches?("bus_event_type" => "[ab]here").should == false 18 | 19 | write = SubscriptionList.new 20 | write.add(write1) 21 | write.add(write2) 22 | 23 | app = Application.new("test") 24 | app.subscribe(write) 25 | 26 | reset_test_adapter # reset to make sure we read from redis 27 | app = Application.new("test") 28 | read = app.send(:subscriptions) 29 | 30 | read.size.should == 2 31 | read1 = read.key("key1") 32 | read2 = read.key("key2") 33 | read1.should_not be_nil 34 | read2.should_not be_nil 35 | 36 | read1.queue_name.should == "default" 37 | read1.class_name.should == "MyClass1" 38 | read2.queue_name.should == "else_ok" 39 | read2.class_name.should == "MyClass2" 40 | 41 | read1.matches?("bus_event_type" => "event_one").should == true 42 | read1.matches?("bus_event_type" => "event_one1").should == false 43 | read1.matches?("bus_event_type" => "aevent_one").should == false 44 | 45 | read2.matches?("bus_event_type" => "ahere").should == true 46 | read2.matches?("bus_event_type" => "bhere").should == true 47 | read2.matches?("bus_event_type" => "qhere").should == false 48 | read2.matches?("bus_event_type" => "abhere").should == false 49 | read2.matches?("bus_event_type" => "[ab]here").should == false 50 | 51 | end 52 | end 53 | end -------------------------------------------------------------------------------- /spec/matcher_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module QueueBus 4 | describe Matcher do 5 | it "should already return false on empty filters" do 6 | matcher = Matcher.new({}) 7 | matcher.matches?({}).should == false 8 | matcher.matches?(nil).should == false 9 | matcher.matches?("name" => "val").should == false 10 | end 11 | 12 | it "should not crash if nil inputs" do 13 | matcher = Matcher.new("name" => "val") 14 | matcher.matches?(nil).should == false 15 | end 16 | 17 | it "string filter to/from redis" do 18 | matcher = Matcher.new("name" => "val") 19 | matcher.matches?("name" => "val").should == true 20 | matcher.matches?("name" => " val").should == false 21 | matcher.matches?("name" => "zval").should == false 22 | end 23 | 24 | it "regex filter" do 25 | matcher = Matcher.new("name" => /^[cb]a+t/) 26 | matcher.matches?("name" => "cat").should == true 27 | matcher.matches?("name" => "bat").should == true 28 | matcher.matches?("name" => "caaaaat").should == true 29 | matcher.matches?("name" => "ct").should == false 30 | matcher.matches?("name" => "bcat").should == false 31 | end 32 | 33 | it "present filter" do 34 | matcher = Matcher.new("name" => :present) 35 | matcher.matches?("name" => "").should == false 36 | matcher.matches?("name" => "cat").should == true 37 | matcher.matches?("name" => "bear").should == true 38 | matcher.matches?("other" => "bear").should == false 39 | end 40 | 41 | it "blank filter" do 42 | matcher = Matcher.new("name" => :blank) 43 | matcher.matches?("name" => nil).should == true 44 | matcher.matches?("other" => "bear").should == true 45 | matcher.matches?("name" => "").should == true 46 | matcher.matches?("name" => " ").should == true 47 | matcher.matches?("name" => "bear").should == false 48 | matcher.matches?("name" => " s ").should == false 49 | end 50 | 51 | it "nil filter" do 52 | matcher = Matcher.new("name" => :nil) 53 | matcher.matches?("name" => nil).should == true 54 | matcher.matches?("other" => "bear").should == true 55 | matcher.matches?("name" => "").should == false 56 | matcher.matches?("name" => " ").should == false 57 | matcher.matches?("name" => "bear").should == false 58 | end 59 | 60 | it "key filter" do 61 | matcher = Matcher.new("name" => :key) 62 | matcher.matches?("name" => nil).should == true 63 | matcher.matches?("other" => "bear").should == false 64 | matcher.matches?("name" => "").should == true 65 | matcher.matches?("name" => " ").should == true 66 | matcher.matches?("name" => "bear").should == true 67 | end 68 | 69 | it "empty filter" do 70 | matcher = Matcher.new("name" => :empty) 71 | matcher.matches?("name" => nil).should == false 72 | matcher.matches?("other" => "bear").should == false 73 | matcher.matches?("name" => "").should == true 74 | matcher.matches?("name" => " ").should == false 75 | matcher.matches?("name" => "bear").should == false 76 | matcher.matches?("name" => " s ").should == false 77 | end 78 | 79 | it "value filter" do 80 | matcher = Matcher.new("name" => :value) 81 | matcher.matches?("name" => nil).should == false 82 | matcher.matches?("other" => "bear").should == false 83 | matcher.matches?("name" => "").should == true 84 | matcher.matches?("name" => " ").should == true 85 | matcher.matches?("name" => "bear").should == true 86 | matcher.matches?("name" => " s ").should == true 87 | end 88 | 89 | it "multiple filters" do 90 | matcher = Matcher.new("name" => /^[cb]a+t/, "state" => "sleeping") 91 | matcher.matches?("state" => "sleeping", "name" => "cat").should == true 92 | matcher.matches?("state" => "awake", "name" => "cat").should == false 93 | matcher.matches?("state" => "sleeping", "name" => "bat").should == true 94 | matcher.matches?("state" => "sleeping", "name" => "bear").should == false 95 | matcher.matches?("state" => "awake", "name" => "bear").should == false 96 | end 97 | 98 | it "regex should go back and forth into redis" do 99 | matcher = Matcher.new("name" => /^[cb]a+t/) 100 | matcher.matches?("name" => "cat").should == true 101 | matcher.matches?("name" => "bat").should == true 102 | matcher.matches?("name" => "caaaaat").should == true 103 | matcher.matches?("name" => "ct").should == false 104 | matcher.matches?("name" => "bcat").should == false 105 | 106 | QueueBus.redis { |redis| redis.set("temp1", QueueBus::Util.encode(matcher.to_redis) ) } 107 | redis = QueueBus.redis { |redis| redis.get("temp1") } 108 | matcher = Matcher.new(QueueBus::Util.decode(redis)) 109 | matcher.matches?("name" => "cat").should == true 110 | matcher.matches?("name" => "bat").should == true 111 | matcher.matches?("name" => "caaaaat").should == true 112 | matcher.matches?("name" => "ct").should == false 113 | matcher.matches?("name" => "bcat").should == false 114 | 115 | QueueBus.redis { |redis| redis.set("temp2", QueueBus::Util.encode(matcher.to_redis) ) } 116 | redis = QueueBus.redis { |redis| redis.get("temp2") } 117 | matcher = Matcher.new(QueueBus::Util.decode(redis)) 118 | matcher.matches?("name" => "cat").should == true 119 | matcher.matches?("name" => "bat").should == true 120 | matcher.matches?("name" => "caaaaat").should == true 121 | matcher.matches?("name" => "ct").should == false 122 | matcher.matches?("name" => "bcat").should == false 123 | end 124 | 125 | it "special value should go back and forth into redis" do 126 | matcher = Matcher.new("name" => :blank) 127 | matcher.matches?("name" => "cat").should == false 128 | matcher.matches?("name" => "").should == true 129 | 130 | QueueBus.redis { |redis| redis.set("temp1", QueueBus::Util.encode(matcher.to_redis) ) } 131 | redis= QueueBus.redis { |redis| redis.get("temp1") } 132 | matcher = Matcher.new(QueueBus::Util.decode(redis)) 133 | matcher.matches?("name" => "cat").should == false 134 | matcher.matches?("name" => "").should == true 135 | 136 | QueueBus.redis { |redis| redis.set("temp2", QueueBus::Util.encode(matcher.to_redis) ) } 137 | redis= QueueBus.redis { |redis| redis.get("temp2") } 138 | matcher = Matcher.new(QueueBus::Util.decode(redis)) 139 | matcher.matches?("name" => "cat").should == false 140 | matcher.matches?("name" => "").should == true 141 | end 142 | end 143 | end 144 | -------------------------------------------------------------------------------- /spec/publish_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Publishing an event" do 4 | 5 | before(:each) do 6 | Timecop.freeze 7 | QueueBus.stub(:generate_uuid).and_return("idfhlkj") 8 | end 9 | after(:each) do 10 | Timecop.return 11 | end 12 | let(:bus_attrs) { {"bus_class_proxy"=>"QueueBus::Driver", 13 | "bus_published_at" => Time.now.to_i, 14 | "bus_id"=>"#{Time.now.to_i}-idfhlkj", 15 | "bus_app_hostname" => `hostname 2>&1`.strip.sub(/.local/,'')} } 16 | 17 | it "should add it to Redis" do 18 | hash = {:one => 1, "two" => "here", "id" => 12 } 19 | event_name = "event_name" 20 | 21 | val = QueueBus.redis { |redis| redis.lpop("queue:bus_incoming") } 22 | val.should == nil 23 | 24 | QueueBus.publish(event_name, hash) 25 | 26 | val = QueueBus.redis { |redis| redis.lpop("queue:bus_incoming") } 27 | hash = JSON.parse(val) 28 | hash["class"].should == "QueueBus::Worker" 29 | hash["args"].size.should == 1 30 | JSON.parse(hash["args"].first).should == {"bus_event_type" => event_name, "two"=>"here", "one"=>1, "id" => 12}.merge(bus_attrs) 31 | 32 | end 33 | 34 | it "should use the id if given" do 35 | hash = {:one => 1, "two" => "here", "bus_id" => "app-given" } 36 | event_name = "event_name" 37 | 38 | val = QueueBus.redis { |redis| redis.lpop("queue:bus_incoming") } 39 | val.should == nil 40 | 41 | QueueBus.publish(event_name, hash) 42 | 43 | val = QueueBus.redis { |redis| redis.lpop("queue:bus_incoming") } 44 | hash = JSON.parse(val) 45 | hash["class"].should == "QueueBus::Worker" 46 | hash["args"].size.should == 1 47 | JSON.parse(hash["args"].first).should == {"bus_event_type" => event_name, "two"=>"here", "one"=>1}.merge(bus_attrs).merge("bus_id" => 'app-given') 48 | end 49 | 50 | it "should add metadata via callback" do 51 | myval = 0 52 | QueueBus.before_publish = lambda { |att| 53 | att["mine"] = 4 54 | myval += 1 55 | } 56 | 57 | hash = {:one => 1, "two" => "here", "bus_id" => "app-given" } 58 | event_name = "event_name" 59 | 60 | val = QueueBus.redis { |redis| redis.lpop("queue:bus_incoming") } 61 | val.should == nil 62 | 63 | QueueBus.publish(event_name, hash) 64 | 65 | 66 | val = QueueBus.redis { |redis| redis.lpop("queue:bus_incoming") } 67 | hash = JSON.parse(val) 68 | att = JSON.parse(hash["args"].first) 69 | att["mine"].should == 4 70 | myval.should == 1 71 | end 72 | 73 | it "should set the timezone and locale if available" do 74 | defined?(I18n).should be_nil 75 | Time.respond_to?(:zone).should eq(false) 76 | 77 | stub_const("I18n", Class.new) 78 | I18n.stub(:locale).and_return("jp") 79 | 80 | Time.stub(:zone).and_return(double('zone', :name => "EST")) 81 | 82 | hash = {:one => 1, "two" => "here", "bus_id" => "app-given" } 83 | event_name = "event_name" 84 | 85 | val = QueueBus.redis { |redis| redis.lpop("queue:bus_incoming") } 86 | val.should == nil 87 | 88 | QueueBus.publish(event_name, hash) 89 | 90 | val = QueueBus.redis { |redis| redis.lpop("queue:bus_incoming") } 91 | hash = JSON.parse(val) 92 | hash["class"].should == "QueueBus::Worker" 93 | att = JSON.parse(hash["args"].first) 94 | att["bus_locale"].should == "jp" 95 | att["bus_timezone"].should == "EST" 96 | end 97 | 98 | end 99 | -------------------------------------------------------------------------------- /spec/publisher_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module QueueBus 4 | describe Publisher do 5 | it "should call publish as expected" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/rider_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module QueueBus 4 | describe Rider do 5 | it "should call execute" do 6 | QueueBus.should_receive(:dispatcher_execute) 7 | Rider.perform("bus_rider_app_key" => "app", "bus_rider_sub_key" => "sub", "ok" => true, "bus_event_type" => "event_name") 8 | end 9 | 10 | it "should change the value" do 11 | QueueBus.dispatch("r1") do 12 | subscribe "event_name" do |attributes| 13 | Runner1.run(attributes) 14 | end 15 | end 16 | Runner1.value.should == 0 17 | Rider.perform("bus_locale" => "en", "bus_timezone" => "PST", "bus_rider_app_key" => "r1", "bus_rider_sub_key" => "event_name", "ok" => true, "bus_event_type" => "event_name") 18 | Rider.perform("bus_rider_app_key" => "other", "bus_rider_sub_key" => "event_name", "ok" => true, "bus_event_type" => "event_name") 19 | Runner1.value.should == 1 20 | end 21 | 22 | it "should set the timezone and locale if present" do 23 | QueueBus.dispatch("r1") do 24 | subscribe "event_name" do |attributes| 25 | Runner1.run(attributes) 26 | end 27 | end 28 | 29 | defined?(I18n).should be_nil 30 | Time.respond_to?(:zone).should eq(false) 31 | 32 | stub_const("I18n", Class.new) 33 | I18n.should_receive(:locale=).with("en") 34 | Time.should_receive(:zone=).with("PST") 35 | 36 | Rider.perform("bus_locale" => "en", "bus_timezone" => "PST", "bus_rider_app_key" => "r1", "bus_rider_sub_key" => "event_name", "ok" => true, "bus_event_type" => "event_name") 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'timecop' 2 | require 'queue-bus' 3 | require 'adapter/support' 4 | 5 | reset_test_adapter 6 | 7 | module QueueBus 8 | class Runner 9 | def self.value 10 | @value ||= 0 11 | end 12 | 13 | def self.attributes 14 | @attributes 15 | end 16 | 17 | def self.run(attrs) 18 | @value ||= 0 19 | @value += 1 20 | @attributes = attrs 21 | end 22 | 23 | def self.reset 24 | @value = nil 25 | @attributes = nil 26 | end 27 | end 28 | 29 | class Runner1 < Runner 30 | end 31 | 32 | class Runner2 < Runner 33 | end 34 | end 35 | 36 | def test_sub(event_name, queue="default") 37 | matcher = {"bus_event_type" => event_name} 38 | QueueBus::Subscription.new(queue, event_name, "::QueueBus::Rider", matcher, nil) 39 | end 40 | 41 | def test_list(*args) 42 | out = QueueBus::SubscriptionList.new 43 | args.each do |sub| 44 | out.add(sub) 45 | end 46 | out 47 | end 48 | 49 | RSpec.configure do |config| 50 | config.mock_with :rspec do |c| 51 | c.syntax = :should 52 | end 53 | config.expect_with :rspec do |c| 54 | c.syntax = :should 55 | end 56 | 57 | config.before(:each) do 58 | reset_test_adapter 59 | ResqueBus.show_deprecations = true 60 | end 61 | config.after(:each) do 62 | begin 63 | QueueBus.redis { |redis| redis.flushall } 64 | rescue 65 | end 66 | QueueBus.send(:reset) 67 | QueueBus::Runner1.reset 68 | QueueBus::Runner2.reset 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /spec/subscriber_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe QueueBus::Subscriber do 4 | let(:attributes) { {"x" => "y"} } 5 | let(:bus_attrs) { {"bus_driven_at" => Time.now.to_i} } 6 | 7 | before(:each) do 8 | class SubscriberTest1 9 | include QueueBus::Subscriber 10 | @queue = "myqueue" 11 | 12 | application :my_thing 13 | subscribe :thing_filter, :x => "y" 14 | subscribe :event_sub 15 | 16 | def event_sub(attributes) 17 | QueueBus::Runner1.run(attributes) 18 | end 19 | 20 | def thing_filter(attributes) 21 | QueueBus::Runner2.run(attributes) 22 | end 23 | end 24 | 25 | class SubscriberTest2 26 | include QueueBus::Subscriber 27 | application :test2 28 | subscribe :test2, "value" => :present 29 | transform :make_an_int 30 | 31 | def self.make_an_int(attributes) 32 | attributes["value"].to_s.length 33 | end 34 | 35 | def test2(int) 36 | QueueBus::Runner1.run("transformed"=>int) 37 | end 38 | end 39 | 40 | module SubModule 41 | class SubscriberTest3 42 | include QueueBus::Subscriber 43 | 44 | subscribe_queue :sub_queue1, :test3, :bus_event_type => "the_event" 45 | subscribe_queue :sub_queue2, :the_event 46 | subscribe :other, :bus_event_type => "other_event" 47 | 48 | def test3(attributes) 49 | QueueBus::Runner1.run(attributes) 50 | end 51 | 52 | def the_event(attributes) 53 | QueueBus::Runner2.run(attributes) 54 | end 55 | end 56 | 57 | class SubscriberTest4 58 | include QueueBus::Subscriber 59 | 60 | subscribe_queue :sub_queue1, :test4 61 | end 62 | end 63 | 64 | Timecop.freeze 65 | QueueBus::TaskManager.new(false).subscribe! 66 | end 67 | 68 | after(:each) do 69 | Timecop.return 70 | end 71 | 72 | it "should have the application" do 73 | SubscriberTest1.app_key.should == "my_thing" 74 | SubModule::SubscriberTest3.app_key.should == "sub_module" 75 | SubModule::SubscriberTest4.app_key.should == "sub_module" 76 | end 77 | 78 | it "should be able to transform the attributes" do 79 | dispatcher = QueueBus.dispatcher_by_key("test2") 80 | all = dispatcher.subscriptions.all 81 | all.size.should == 1 82 | 83 | sub = all.first 84 | sub.queue_name.should == "test2_default" 85 | sub.class_name.should == "SubscriberTest2" 86 | sub.key.should == "SubscriberTest2.test2" 87 | sub.matcher.filters.should == {"value"=>"bus_special_value_present"} 88 | 89 | QueueBus::Driver.perform(attributes.merge("bus_event_type" => "something2", "value"=>"nice")) 90 | 91 | hash = JSON.parse(QueueBus.redis { |redis| redis.lpop("queue:test2_default") }) 92 | hash["class"].should == "QueueBus::Worker" 93 | hash["args"].size.should == 1 94 | JSON.parse(hash["args"].first).should eq({"bus_class_proxy" => "SubscriberTest2", "bus_rider_app_key"=>"test2", "bus_rider_sub_key"=>"SubscriberTest2.test2", "bus_rider_queue" => "test2_default", "bus_rider_class_name"=>"SubscriberTest2", 95 | "bus_event_type" => "something2", "value"=>"nice", "x"=>"y"}.merge(bus_attrs)) 96 | 97 | QueueBus::Runner1.value.should == 0 98 | QueueBus::Runner2.value.should == 0 99 | QueueBus::Util.constantize(hash["class"]).perform(*hash["args"]) 100 | QueueBus::Runner1.value.should == 1 101 | QueueBus::Runner2.value.should == 0 102 | 103 | QueueBus::Runner1.attributes.should == {"transformed" => 4} 104 | 105 | 106 | QueueBus::Driver.perform(attributes.merge("bus_event_type" => "something2", "value"=>"12")) 107 | 108 | hash = JSON.parse(QueueBus.redis { |redis| redis.lpop("queue:test2_default") }) 109 | hash["class"].should == "QueueBus::Worker" 110 | hash["args"].size.should == 1 111 | JSON.parse(hash["args"].first).should == {"bus_class_proxy" => "SubscriberTest2", "bus_rider_app_key"=>"test2", "bus_rider_sub_key"=>"SubscriberTest2.test2", "bus_rider_queue" => "test2_default", "bus_rider_class_name"=>"SubscriberTest2", 112 | "bus_event_type" => "something2", "value"=>"12", "x"=>"y"}.merge(bus_attrs) 113 | 114 | QueueBus::Runner1.value.should == 1 115 | QueueBus::Runner2.value.should == 0 116 | QueueBus::Util.constantize(hash["class"]).perform(*hash["args"]) 117 | QueueBus::Runner1.value.should == 2 118 | QueueBus::Runner2.value.should == 0 119 | 120 | QueueBus::Runner1.attributes.should == {"transformed" => 2} 121 | end 122 | 123 | 124 | it "should put in a different queue" do 125 | dispatcher = QueueBus.dispatcher_by_key("sub_module") 126 | all = dispatcher.subscriptions.all 127 | all.size.should == 4 128 | 129 | sub = all.select{ |s| s.key == "SubModule::SubscriberTest3.test3"}.first 130 | sub.queue_name.should == "sub_queue1" 131 | sub.class_name.should == "SubModule::SubscriberTest3" 132 | sub.key.should == "SubModule::SubscriberTest3.test3" 133 | sub.matcher.filters.should == {"bus_event_type"=>"the_event"} 134 | 135 | sub = all.select{ |s| s.key == "SubModule::SubscriberTest3.the_event"}.first 136 | sub.queue_name.should == "sub_queue2" 137 | sub.class_name.should == "SubModule::SubscriberTest3" 138 | sub.key.should == "SubModule::SubscriberTest3.the_event" 139 | sub.matcher.filters.should == {"bus_event_type"=>"the_event"} 140 | 141 | sub = all.select{ |s| s.key == "SubModule::SubscriberTest3.other"}.first 142 | sub.queue_name.should == "sub_module_default" 143 | sub.class_name.should == "SubModule::SubscriberTest3" 144 | sub.key.should == "SubModule::SubscriberTest3.other" 145 | sub.matcher.filters.should == {"bus_event_type"=>"other_event"} 146 | 147 | sub = all.select{ |s| s.key == "SubModule::SubscriberTest4.test4"}.first 148 | sub.queue_name.should == "sub_queue1" 149 | sub.class_name.should == "SubModule::SubscriberTest4" 150 | sub.key.should == "SubModule::SubscriberTest4.test4" 151 | sub.matcher.filters.should == {"bus_event_type"=>"test4"} 152 | 153 | QueueBus::Driver.perform(attributes.merge("bus_event_type" => "the_event")) 154 | 155 | hash = JSON.parse(QueueBus.redis { |redis| redis.lpop("queue:sub_queue1") }) 156 | hash["class"].should == "QueueBus::Worker" 157 | hash["args"].size.should == 1 158 | JSON.parse(hash["args"].first).should == {"bus_class_proxy" => "SubModule::SubscriberTest3", "bus_rider_app_key"=>"sub_module", "bus_rider_sub_key"=>"SubModule::SubscriberTest3.test3", "bus_rider_queue" => "sub_queue1", "bus_rider_class_name"=>"SubModule::SubscriberTest3", 159 | "bus_event_type" => "the_event", "x" => "y"}.merge(bus_attrs) 160 | 161 | QueueBus::Runner1.value.should == 0 162 | QueueBus::Runner2.value.should == 0 163 | QueueBus::Util.constantize(hash["class"]).perform(*hash["args"]) 164 | QueueBus::Runner1.value.should == 1 165 | QueueBus::Runner2.value.should == 0 166 | 167 | hash = JSON.parse(QueueBus.redis { |redis| redis.lpop("queue:sub_queue2") }) 168 | hash["class"].should == "QueueBus::Worker" 169 | hash["args"].size.should == 1 170 | JSON.parse(hash["args"].first).should == {"bus_class_proxy" => "SubModule::SubscriberTest3", "bus_rider_app_key"=>"sub_module", "bus_rider_sub_key"=>"SubModule::SubscriberTest3.the_event", "bus_rider_queue" => "sub_queue2", "bus_rider_class_name"=>"SubModule::SubscriberTest3", 171 | "bus_event_type" => "the_event", "x" => "y"}.merge(bus_attrs) 172 | 173 | QueueBus::Runner1.value.should == 1 174 | QueueBus::Runner2.value.should == 0 175 | QueueBus::Util.constantize(hash["class"]).perform(*hash["args"]) 176 | QueueBus::Runner1.value.should == 1 177 | QueueBus::Runner2.value.should == 1 178 | end 179 | 180 | it "should subscribe to default and attributes" do 181 | dispatcher = QueueBus.dispatcher_by_key("my_thing") 182 | all = dispatcher.subscriptions.all 183 | 184 | sub = all.select{ |s| s.key == "SubscriberTest1.event_sub"}.first 185 | sub.queue_name.should == "myqueue" 186 | sub.class_name.should == "SubscriberTest1" 187 | sub.key.should == "SubscriberTest1.event_sub" 188 | sub.matcher.filters.should == {"bus_event_type"=>"event_sub"} 189 | 190 | sub = all.select{ |s| s.key == "SubscriberTest1.thing_filter"}.first 191 | sub.queue_name.should == "myqueue" 192 | sub.class_name.should == "SubscriberTest1" 193 | sub.key.should == "SubscriberTest1.thing_filter" 194 | sub.matcher.filters.should == {"x"=>"y"} 195 | 196 | QueueBus::Driver.perform(attributes.merge("bus_event_type" => "event_sub")) 197 | QueueBus.redis { |redis| redis.smembers("queues") }.should =~ ["myqueue"] 198 | 199 | pop1 = JSON.parse(QueueBus.redis { |redis| redis.lpop("queue:myqueue") }) 200 | pop2 = JSON.parse(QueueBus.redis { |redis| redis.lpop("queue:myqueue") }) 201 | 202 | if JSON.parse(pop1["args"].first)["bus_rider_sub_key"] == "SubscriberTest1.thing_filter" 203 | hash1 = pop1 204 | hash2 = pop2 205 | else 206 | hash1 = pop2 207 | hash2 = pop1 208 | end 209 | 210 | hash1["class"].should == "QueueBus::Worker" 211 | JSON.parse(hash1["args"].first).should eq({"bus_class_proxy" => "SubscriberTest1", "bus_rider_app_key"=>"my_thing", "bus_rider_sub_key"=>"SubscriberTest1.thing_filter", "bus_rider_queue" => "myqueue", "bus_rider_class_name"=>"SubscriberTest1", 212 | "bus_event_type" => "event_sub", "x" => "y"}.merge(bus_attrs)) 213 | 214 | QueueBus::Runner1.value.should == 0 215 | QueueBus::Runner2.value.should == 0 216 | QueueBus::Util.constantize(hash1["class"]).perform(*hash1["args"]) 217 | QueueBus::Runner1.value.should == 0 218 | QueueBus::Runner2.value.should == 1 219 | 220 | hash2["class"].should == "QueueBus::Worker" 221 | hash2["args"].size.should == 1 222 | JSON.parse(hash2["args"].first).should == {"bus_class_proxy" => "SubscriberTest1", "bus_rider_app_key"=>"my_thing", "bus_rider_sub_key"=>"SubscriberTest1.event_sub", "bus_rider_queue" => "myqueue", "bus_rider_class_name"=>"SubscriberTest1", 223 | "bus_event_type" => "event_sub", "x" => "y"}.merge(bus_attrs) 224 | 225 | QueueBus::Runner1.value.should == 0 226 | QueueBus::Runner2.value.should == 1 227 | QueueBus::Util.constantize(hash2["class"]).perform(*hash2["args"]) 228 | QueueBus::Runner1.value.should == 1 229 | QueueBus::Runner2.value.should == 1 230 | 231 | QueueBus::Driver.perform(attributes.merge("bus_event_type" => "event_sub_other")) 232 | QueueBus.redis { |redis| redis.smembers("queues") }.should =~ ["myqueue"] 233 | 234 | hash = JSON.parse(QueueBus.redis { |redis| redis.lpop("queue:myqueue") }) 235 | hash["class"].should == "QueueBus::Worker" 236 | hash["args"].size.should == 1 237 | JSON.parse(hash["args"].first).should == {"bus_class_proxy" => "SubscriberTest1", "bus_rider_app_key"=>"my_thing", "bus_rider_sub_key"=>"SubscriberTest1.thing_filter", "bus_rider_queue" => "myqueue", "bus_rider_class_name"=>"SubscriberTest1", 238 | "bus_event_type" => "event_sub_other", "x" => "y"}.merge(bus_attrs) 239 | 240 | QueueBus::Runner1.value.should == 1 241 | QueueBus::Runner2.value.should == 1 242 | QueueBus::Util.constantize(hash["class"]).perform(*hash["args"]) 243 | QueueBus::Runner1.value.should == 1 244 | QueueBus::Runner2.value.should == 2 245 | 246 | QueueBus::Driver.perform({"x"=>"z"}.merge("bus_event_type" => "event_sub_other")) 247 | QueueBus.redis { |redis| redis.smembers("queues") }.should =~ ["myqueue"] 248 | 249 | QueueBus.redis { |redis| redis.lpop("queue:myqueue") }.should be_nil 250 | end 251 | 252 | describe ".perform" do 253 | let(:attributes) { {"bus_rider_sub_key"=>"SubscriberTest1.event_sub", "bus_locale" => "en", "bus_timezone" => "PST"} } 254 | it "should call the method based on key" do 255 | SubscriberTest1.any_instance.should_receive(:event_sub) 256 | SubscriberTest1.perform(attributes) 257 | end 258 | it "should set the timezone and locale if present" do 259 | defined?(I18n).should be_nil 260 | Time.respond_to?(:zone).should eq(false) 261 | 262 | stub_const("I18n", Class.new) 263 | I18n.should_receive(:locale=).with("en") 264 | Time.should_receive(:zone=).with("PST") 265 | 266 | SubscriberTest1.any_instance.should_receive(:event_sub) 267 | SubscriberTest1.perform(attributes) 268 | end 269 | end 270 | end 271 | -------------------------------------------------------------------------------- /spec/subscription_list_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module QueueBus 4 | describe SubscriptionList do 5 | describe ".from_redis" do 6 | it "should return from attributes" do 7 | mult = {"event_one" => {"class" => "MyClass", "queue_name" => "default", "key" => "event_one", "matcher" => {"bus_event_type" => "event_one"}}, 8 | "event_two" => {"class" => "MyClass", "queue_name" => "else", "key" => "event_two", "matcher" => {"bus_event_type" => "event_two"}}} 9 | 10 | list = SubscriptionList.from_redis(mult) 11 | list.size.should == 2 12 | one = list.key("event_one") 13 | two = list.key("event_two") 14 | 15 | one.key.should == "event_one" 16 | one.key.should == "event_one" 17 | one.queue_name.should == "default" 18 | one.class_name.should == "MyClass" 19 | one.matcher.filters.should == {"bus_event_type" => "event_one"} 20 | 21 | two.key.should == "event_two" 22 | two.key.should == "event_two" 23 | two.queue_name.should == "else" 24 | two.class_name.should == "MyClass" 25 | two.matcher.filters.should == {"bus_event_type" => "event_two"} 26 | end 27 | end 28 | 29 | describe "#to_redis" do 30 | it "should generate what to store" do 31 | list = SubscriptionList.new 32 | list.add(Subscription.new("default", "key1", "MyClass", {"bus_event_type" => "event_one"})) 33 | list.add(Subscription.new("else_ok", "key2", "MyClass", {"bus_event_type" => "event_two"})) 34 | 35 | hash = list.to_redis 36 | hash.should == { "key1" => {"queue_name" => "default", "key" => "key1", "class" => "MyClass", "matcher" => {"bus_event_type" => "event_one"}}, 37 | "key2" => {"queue_name" => "else_ok", "key" => "key2", "class" => "MyClass", "matcher" => {"bus_event_type" => "event_two"}} 38 | } 39 | 40 | end 41 | end 42 | end 43 | end -------------------------------------------------------------------------------- /spec/subscription_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module QueueBus 4 | describe Subscription do 5 | it "should normalize the queue name" do 6 | Subscription.new("test", "my_event", "MyClass", {}, nil).queue_name.should == "test" 7 | Subscription.new("tes t", "my_event", "MyClass", {}, nil).queue_name.should == "tes_t" 8 | Subscription.new("t%s", "my_event", "MyClass", {}, nil).queue_name.should == "t_s" 9 | end 10 | 11 | describe ".register" do 12 | it "should take in args from dispatcher" do 13 | executor = Proc.new { |attributes| } 14 | sub = Subscription.register("queue_name", "mykey", "MyClass", {"bus_event_type" => "my_event"}, executor) 15 | sub.send(:executor).should == executor 16 | sub.matcher.filters.should == {"bus_event_type" => "my_event"} 17 | sub.queue_name.should == "queue_name" 18 | sub.key.should == "mykey" 19 | sub.class_name.should == "MyClass" 20 | end 21 | end 22 | 23 | describe "#execute!" do 24 | it "should call the executor with the attributes" do 25 | exec = Object.new 26 | exec.should_receive(:call) 27 | 28 | sub = Subscription.new("x", "y", "ClassName", {}, exec) 29 | sub.execute!({"ok" => true}) 30 | end 31 | end 32 | 33 | describe "#to_redis" do 34 | it "should return what to store for this subscription" do 35 | sub = Subscription.new("queue_one", "xyz", "ClassName", {"bus_event_type" => "my_event"}, nil) 36 | sub.to_redis.should == {"queue_name" => "queue_one", "key" => "xyz", "class" => "ClassName", "matcher" => {"bus_event_type" => "my_event"}} 37 | end 38 | end 39 | 40 | describe "#matches?" do 41 | it "should do pattern stuff" do 42 | Subscription.new("x", "id", "ClassName", {"bus_event_type" => "one"}).matches?("bus_event_type" => "one").should == true 43 | Subscription.new("x", "id", "ClassName", {"bus_event_type" => "one"}).matches?("bus_event_type" => "onex").should == false 44 | Subscription.new("x", "id", "ClassName", {"bus_event_type" => "^one.*$"}).matches?("bus_event_type" => "onex").should == true 45 | Subscription.new("x", "id", "ClassName", {"bus_event_type" => "one.*"}).matches?("bus_event_type" => "onex").should == true 46 | Subscription.new("x", "id", "ClassName", {"bus_event_type" => "one.?"}).matches?("bus_event_type" => "onex").should == true 47 | Subscription.new("x", "id", "ClassName", {"bus_event_type" => "one.?"}).matches?("bus_event_type" => "one").should == true 48 | Subscription.new("x", "id", "ClassName", {"bus_event_type" => "\\"}).matches?("bus_event_type" => "one").should == false 49 | end 50 | end 51 | 52 | end 53 | end -------------------------------------------------------------------------------- /spec/worker_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module QueueBus 4 | describe Worker do 5 | it "should proxy to given class" do 6 | hash = {"bus_class_proxy" => "QueueBus::Driver", "ok" => true} 7 | QueueBus::Driver.should_receive(:perform).with(hash) 8 | QueueBus::Worker.perform(JSON.generate(hash)) 9 | end 10 | 11 | it "should use instance" do 12 | hash = {"bus_class_proxy" => "QueueBus::Rider", "ok" => true} 13 | QueueBus::Rider.should_receive(:perform).with(hash) 14 | QueueBus::Worker.new.perform(JSON.generate(hash)) 15 | end 16 | 17 | it "should not freak out if class not there anymore" do 18 | hash = {"bus_class_proxy" => "QueueBus::BadClass", "ok" => true} 19 | lambda { 20 | QueueBus::Worker.perform(JSON.generate(hash)) 21 | }.should_not raise_error 22 | end 23 | 24 | it "should raise error if proxy raises error" do 25 | hash = {"bus_class_proxy" => "QueueBus::Rider", "ok" => true} 26 | QueueBus::Rider.should_receive(:perform).with(hash).and_raise("rider crash") 27 | lambda { 28 | QueueBus::Worker.perform(JSON.generate(hash)) 29 | }.should raise_error 30 | end 31 | end 32 | end 33 | --------------------------------------------------------------------------------