banter 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f98c954f1577641de0413fcdb61bd4de641159ee
4
+ data.tar.gz: cca0062d0018c28a2b6d3f1ff9951735cda37361
5
+ SHA512:
6
+ metadata.gz: c9df28a97f6ed9ba8764dd77002a5726092c8f60b5a20b3b3c89ed653f45699a4dc2def024b3f5471157dca3f39e65605850c3a73cafebf2db00c93ca675fbaf
7
+ data.tar.gz: 4d1abbfb632f94924f6e7cd69353032804cc2a3b2a6f72ee79099f6ce4528106773504b7987dd8bc49b84c57ba0fcae483e2982831321f0f9f9db25295529029
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .project
7
+ .ruby-gemset
8
+ Gemfile.lock
9
+ InstalledFiles
10
+ _yardoc
11
+ coverage
12
+ doc/
13
+ lib/bundler/man
14
+ pkg
15
+ rdoc
16
+ spec/reports
17
+ test/tmp
18
+ test/version_tmp
19
+ tmp
20
+ .rspec
21
+ .idea
22
+ test_pubsub.log
@@ -0,0 +1 @@
1
+ 2.0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in banter.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 The Honest Company
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,113 @@
1
+ # Banter
2
+
3
+ Simple Publishers and subscribers for Ruby using RabbitMQ
4
+
5
+ [![Gem Version](https://badge.fury.io/rb/banter.svg)](http://badge.fury.io/rb/banter)
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'banter'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install banter
20
+
21
+ You also need to install RabbitMQ
22
+
23
+ $ brew install rabbitmq
24
+
25
+ ## Usage
26
+
27
+ There are two sides to this gem, publishers and subscribers
28
+
29
+ ### Publishers
30
+
31
+ Publishing to a message is super simple
32
+
33
+ Banter.publish('user.created', { id: 3, name: 'Foo Bar', email: 'foobar@email.com')
34
+
35
+ Additionally you can setup a context that is passed down to the subscribers. This context can be set up in a `before_filter`
36
+
37
+ ```ruby
38
+ class ApplicationController < ActionController::Base
39
+ before_filter :setup_pubsub_context
40
+
41
+ private
42
+
43
+ def setup_pubsub_context
44
+ Banter::Context.setup_context
45
+ unique_id: request.uuid,
46
+ orig_ip_address: request.remote_ip,
47
+ application: "my_app/web:#{self.class.name.underscore}/#{action_name}",
48
+ user_id: current_user.try(:id)
49
+ end
50
+ end
51
+ ```
52
+
53
+ Context is automatically cleared up by the gem for a Rails Application. Otherwise, you can call
54
+ `Banter::Context.clear!`
55
+
56
+ ### Subscribers
57
+
58
+ You can declare a subscriber class as follows
59
+
60
+ ```ruby
61
+ class UserWelcomeSubscriber < Banter::Subscriber
62
+ subscribe_to "user_created" # The message prefix that you're subscribing to
63
+ # subscribe_to "user_created", on: 'welcome_emails_queue' # If you want to specify the queue name
64
+
65
+ def perform(payload)
66
+ # My awesome logic goes here...
67
+ end
68
+ end
69
+ ```
70
+
71
+ You also run subscribers in a separate process to consume the message using the provided executable
72
+
73
+ `bundle exec start_subscribers`
74
+
75
+ You can execute `bundle exec start_subscribers --help` to see all the various options that it providers
76
+ ```
77
+ Usage: bundle exec start_subscribers [options]
78
+ -P, --pidfile PATH path to pidfile
79
+ -o, --only [SUBSCRIBERS] comma separated name of subsriber classes that should be run
80
+ -r, --require [PATH|DIR] Location of Rails application with workers or file to require
81
+ -v, --version Print version and exit
82
+
83
+ ```
84
+
85
+ #### Validations
86
+
87
+ ```ruby
88
+ class UserWelcomeSubscriber < Banter::Subscriber
89
+ subscribe_to "user_created" # The message prefix that you're subscribing to
90
+ validates_payload_with :id_present
91
+ validates_payload_with do |payload|
92
+ payload[:email].present?
93
+ end
94
+
95
+ def perform(payload)
96
+ # My awesome logic goes here...
97
+ end
98
+
99
+ private
100
+
101
+ def id_present(payload)
102
+ payload[:id].present?
103
+ end
104
+ end
105
+ ```
106
+
107
+ ## Contributing
108
+
109
+ 1. Fork it
110
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
111
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
112
+ 4. Push to the branch (`git push origin my-new-feature`)
113
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,37 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ # require all files in the folder?
6
+ require 'banter/version'
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = "banter"
10
+ spec.version = Banter::VERSION
11
+ spec.authors = ["The Honest Company", "Thanh Lim", "Tushar Ranka", "Joel Jackson"]
12
+ spec.email = ["webadmin@honest.com", "tusharranka@gmail.com", "jackson.joel@gmail.com"]
13
+ spec.description = "Publish & subscribe to messages"
14
+ spec.summary = "Library for pub-sub (Message Bus)"
15
+ spec.homepage = ""
16
+ spec.license = "MIT"
17
+
18
+ spec.files = `git ls-files`.split($/)
19
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.3"
24
+ spec.add_development_dependency "rake", ">= 10.0"
25
+ spec.add_development_dependency "rspec", "~> 3.0.0"
26
+ spec.add_development_dependency "debugger"
27
+ spec.add_runtime_dependency "activesupport", ">= 3.2"
28
+ spec.add_runtime_dependency "awesome_print", "~> 1.2.0"
29
+ spec.add_runtime_dependency "bunny", ">= 1.2"
30
+ spec.add_runtime_dependency "airbrake", ">= 3.1"
31
+ spec.add_runtime_dependency "hashie", ">= 1.2"
32
+ spec.add_runtime_dependency "json", ">= 1.8"
33
+ spec.add_runtime_dependency "celluloid", ">= 0.15"
34
+ spec.add_runtime_dependency "celluloid-io", ">= 0.15"
35
+
36
+
37
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/banter/cli'
4
+
5
+ begin
6
+ cli = Banter::CLI.instance
7
+ cli.parse
8
+ cli.run
9
+ rescue => e
10
+ raise e if $DEBUG
11
+ STDERR.puts e.message
12
+ STDERR.puts e.backtrace.join("\n")
13
+ exit 1
14
+ end
@@ -0,0 +1,17 @@
1
+ development:
2
+ # Documentation for parameters for rabbit and bunny is located here: http://rubybunny.info/articles/connecting.html
3
+ connection:
4
+ host: localhost
5
+ port: 5672
6
+ # username: rabbit
7
+ # password: rabbit
8
+ heartbeat: 60 # in seconds
9
+ log_level: 0
10
+ log_file: rabbit.log
11
+ network_recovery_interval: 10 # in seconds
12
+ continuation_timeout: 4000 # in milliseconds
13
+
14
+ logger:
15
+ enabled: true
16
+ level: warn
17
+ file: pubsub.log
@@ -0,0 +1,47 @@
1
+ require "active_support/all"
2
+ require "bunny"
3
+ require "yaml"
4
+ require "airbrake"
5
+
6
+ require "banter/configuration"
7
+ require "banter/context"
8
+ require "banter/db_logger"
9
+ require "banter/logger"
10
+ require "banter/logging"
11
+ require "banter/message"
12
+ require "banter/publisher"
13
+ require "banter/server/rabbit_mq_subscriber"
14
+ require "banter/version"
15
+ require "banter/exceptions/payload_validation_error"
16
+ require 'banter/server'
17
+ require 'banter/subscriber'
18
+
19
+ if defined?(Rails::Railtie)
20
+ require "banter/middleware"
21
+ require "banter/railtie"
22
+ end
23
+
24
+ module Banter
25
+
26
+ def self.root
27
+ File.expand_path '../..', __FILE__
28
+ end
29
+
30
+ # This method publishes payload to rabbitmq. All listeners with appropriate
31
+ # routing keys will receive the payload.
32
+ # @param [String] routing_key Identifier of the message type
33
+ # @param [Hash] payload The data that will be passed to the subscriber
34
+
35
+ def self.publish(routing_key, payload)
36
+ Publisher.instance.publish(Banter::Context.instance, routing_key, payload)
37
+ end
38
+
39
+ def self.logger
40
+ Banter::Logging.logger
41
+ end
42
+
43
+ # @param [Logger] logger Logger used by Banter
44
+ def self.logger=(logger)
45
+ Banter::Logging.logger = logger
46
+ end
47
+ end
@@ -0,0 +1,94 @@
1
+ # This class handles parsing and running the command line interface for executing subscribers
2
+
3
+ $stdout.sync = true
4
+
5
+ require 'singleton'
6
+ require 'optparse'
7
+ require 'banter/server'
8
+
9
+ module Banter
10
+ class CLI
11
+ include Singleton
12
+
13
+ attr_accessor :pidfile, :subscribers
14
+
15
+ # Method to support parsing of arguments passed through the command line
16
+ def parse(args = ARGV)
17
+ optparse = OptionParser.new do |opts|
18
+ opts.banner = "Usage: bundle exec start_subscribers [options]"
19
+ opts.on '-P', '--pidfile PATH', "path to pidfile" do |arg|
20
+ @pidfile = arg
21
+ end
22
+
23
+ opts.on("-o", "--only [SUBSCRIBERS]", "comma separated name of subsriber classes that should be run") do |subscribers|
24
+ @subscribers = subscribers.split(/\,/)
25
+ end
26
+
27
+ opts.on '-r', '--require [PATH|DIR]', "Location of Rails application with workers or file to require" do |arg|
28
+ @require_path = arg
29
+ end
30
+
31
+ opts.on '-v', '--version', "Print version and exit" do |arg|
32
+ puts "Banter #{Banter::VERSION}"
33
+ abort
34
+ end
35
+ end
36
+
37
+ optparse.parse!(args)
38
+ end
39
+
40
+ def run
41
+ load_environment
42
+ write_pidfile
43
+ load_subscribers
44
+ end
45
+
46
+ def require_path
47
+ @require_path || "."
48
+ end
49
+
50
+
51
+ # @return [Array] returns array of subscriber classes that will be executed by the CLI
52
+ def subscriber_classes
53
+ if subscribers.present?
54
+ subscribers.map(&:constantize)
55
+ else
56
+ Banter::Subscriber.class_variable_get(:@@registered_subscribers)
57
+ end
58
+ end
59
+
60
+ def remove_pid
61
+ return unless pidfile
62
+ File.delete(pidfile) if File.exist?(pidfile)
63
+ end
64
+
65
+ private
66
+
67
+ def load_environment
68
+ if require_path
69
+ raise ArgumentError, "#{require_path} does not exist" unless File.exist?(require_path)
70
+ end
71
+
72
+ if File.directory?(require_path)
73
+ require 'rails'
74
+ require File.expand_path("#{require_path}/config/environment.rb")
75
+ ::Rails.application.eager_load!
76
+ else
77
+ require require_path
78
+ end
79
+ end
80
+
81
+ def write_pidfile
82
+ return unless pidfile
83
+ File.open(pidfile, 'w') do |f|
84
+ f.puts Process.pid
85
+ end
86
+ end
87
+
88
+ def load_subscribers
89
+ Banter::Server::SubscriberServer.new(subscriber_classes).start
90
+ end
91
+ end
92
+ end
93
+
94
+
@@ -0,0 +1,42 @@
1
+ module Banter
2
+ class Configuration
3
+ @@conf = nil
4
+
5
+ def self.configure_with(environment_name, yaml_file = nil)
6
+ @@yaml_file = yaml_file.nil? ? "config/pubsub.yml" : yaml_file
7
+ @@all_conf = Hashie::Mash.new(YAML.load_file(@@yaml_file) )
8
+ @@conf = @@all_conf[environment_name.to_sym]
9
+ self
10
+ end
11
+
12
+ def self.configuration
13
+ self.configure_with(self.environment) if @@conf.nil?
14
+ @@conf
15
+ end
16
+
17
+ def self.environment
18
+ val = ENV["RAILS_ENV"] || ENV["RACK_ENV"]
19
+ val = if val.present?
20
+ val.to_sym
21
+ else
22
+ if defined?(Rails)
23
+ Rails.env
24
+ else
25
+ raise "No environment can be found for configuration!"
26
+ end
27
+ end
28
+ end
29
+
30
+ def self.default_queue_ttl
31
+ 24.hours * 1000
32
+ end
33
+
34
+ def self.application_name
35
+ @application_name ||= if defined?(Rails)
36
+ Rails.application.class.name.to_s.gsub("::Application", '')
37
+ else
38
+ 'banter'
39
+ end.downcase
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,70 @@
1
+ module Banter
2
+ class Context
3
+ # acceptable values
4
+ # employee - employee id that the context could have
5
+ # user_id - ender user id of call
6
+ # unique_id - unique identifier (could be request.uuid most of the time)
7
+ # orig_ip_address - ip address of originating requester
8
+ def initialize(params_hash = {})
9
+ @data = Hashie::Mash.new(params_hash)
10
+ end
11
+
12
+ def user_id
13
+ @data.user_id
14
+ end
15
+
16
+ def employee_id
17
+ @data.employee_id
18
+ end
19
+
20
+ def as_json
21
+ @data.as_json
22
+ end
23
+
24
+ def unique_id
25
+ @data.unique_id
26
+ end
27
+
28
+ def originating_ip_address
29
+ @data.orig_ip_address
30
+ end
31
+
32
+ def serialize
33
+ @data.as_json
34
+ end
35
+
36
+ def application
37
+ @data.application
38
+ end
39
+
40
+ def self.instance
41
+ Thread.current['pubsub_context'] || {}
42
+ end
43
+
44
+ def self.setup_context(attrs = {})
45
+ Thread.current['pubsub_context'] = self.new(attrs)
46
+ end
47
+
48
+ def self.clear!
49
+ Thread.current['pubsub_context'] = nil
50
+ end
51
+
52
+ # USE AS INFREQUENTLY AS POSSIBLE. Only use it if you absolutely must
53
+ # as really, we want context in the object, and not the kitchen sink,
54
+ # which is what this will turn out to be if we use regularly use this
55
+ # mechanism.
56
+ def method_missing(method_name, *arguments, &block)
57
+ if @data.key?(method_name)
58
+ @data[method_name]
59
+ else
60
+ super
61
+ end
62
+
63
+ end
64
+
65
+ def self.from_json(data_hash)
66
+ ::Banter::Context.new(data_hash)
67
+ end
68
+
69
+ end
70
+ end