isimud 0.5.2

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.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/.yardoc/checksums +15 -0
  6. data/.yardoc/object_types +0 -0
  7. data/.yardoc/objects/root.dat +0 -0
  8. data/.yardoc/proxy_types +0 -0
  9. data/Gemfile +23 -0
  10. data/Gemfile.lock +123 -0
  11. data/README.md +218 -0
  12. data/Rakefile +2 -0
  13. data/config.ru +7 -0
  14. data/config/tddium.yml +11 -0
  15. data/doc/Isimud.html +1696 -0
  16. data/doc/Isimud/BunnyClient.html +1004 -0
  17. data/doc/Isimud/Client.html +812 -0
  18. data/doc/Isimud/Event.html +1500 -0
  19. data/doc/Isimud/EventListener.html +1217 -0
  20. data/doc/Isimud/EventObserver.html +367 -0
  21. data/doc/Isimud/EventObserver/ClassMethods.html +292 -0
  22. data/doc/Isimud/Generators.html +117 -0
  23. data/doc/Isimud/Generators/ConfigGenerator.html +192 -0
  24. data/doc/Isimud/Generators/InitializerGenerator.html +192 -0
  25. data/doc/Isimud/Logging.html +230 -0
  26. data/doc/Isimud/ModelWatcher.html +312 -0
  27. data/doc/Isimud/ModelWatcher/ClassMethods.html +511 -0
  28. data/doc/Isimud/Railtie.html +123 -0
  29. data/doc/Isimud/TestClient.html +1003 -0
  30. data/doc/Isimud/TestClient/Queue.html +556 -0
  31. data/doc/_index.html +290 -0
  32. data/doc/class_list.html +58 -0
  33. data/doc/css/common.css +1 -0
  34. data/doc/css/full_list.css +57 -0
  35. data/doc/css/style.css +339 -0
  36. data/doc/file.README.html +338 -0
  37. data/doc/file_list.html +60 -0
  38. data/doc/frames.html +26 -0
  39. data/doc/index.html +338 -0
  40. data/doc/js/app.js +219 -0
  41. data/doc/js/full_list.js +181 -0
  42. data/doc/js/jquery.js +4 -0
  43. data/doc/method_list.html +711 -0
  44. data/doc/top-level-namespace.html +112 -0
  45. data/isimud.gemspec +25 -0
  46. data/lib/isimud.rb +91 -0
  47. data/lib/isimud/bunny_client.rb +95 -0
  48. data/lib/isimud/client.rb +48 -0
  49. data/lib/isimud/event.rb +112 -0
  50. data/lib/isimud/event_listener.rb +200 -0
  51. data/lib/isimud/event_observer.rb +81 -0
  52. data/lib/isimud/logging.rb +11 -0
  53. data/lib/isimud/model_watcher.rb +144 -0
  54. data/lib/isimud/railtie.rb +9 -0
  55. data/lib/isimud/tasks.rb +20 -0
  56. data/lib/isimud/test_client.rb +89 -0
  57. data/lib/isimud/version.rb +3 -0
  58. data/lib/rails/generators/isimud/config_generator.rb +12 -0
  59. data/lib/rails/generators/isimud/initializer_generator.rb +12 -0
  60. data/lib/rails/generators/isimud/templates/initializer.rb +17 -0
  61. data/lib/rails/generators/isimud/templates/isimud.yml +20 -0
  62. data/spec/internal/app/models/admin.rb +2 -0
  63. data/spec/internal/app/models/company.rb +34 -0
  64. data/spec/internal/app/models/user.rb +27 -0
  65. data/spec/internal/config/database.yml +3 -0
  66. data/spec/internal/config/routes.rb +3 -0
  67. data/spec/internal/db/schema.rb +22 -0
  68. data/spec/internal/log/.gitignore +1 -0
  69. data/spec/internal/public/favicon.ico +0 -0
  70. data/spec/isimud/bunny_client_spec.rb +125 -0
  71. data/spec/isimud/event_listener_spec.rb +86 -0
  72. data/spec/isimud/event_observer_spec.rb +32 -0
  73. data/spec/isimud/event_spec.rb +74 -0
  74. data/spec/isimud/model_watcher_spec.rb +189 -0
  75. data/spec/isimud/test_client_spec.rb +28 -0
  76. data/spec/isimud_spec.rb +49 -0
  77. data/spec/spec_helper.rb +55 -0
  78. metadata +195 -0
@@ -0,0 +1,112 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
4
+ <head>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
6
+ <title>
7
+ Top Level Namespace
8
+
9
+ &mdash; Documentation by YARD 0.8.7.6
10
+
11
+ </title>
12
+
13
+ <link rel="stylesheet" href="css/style.css" type="text/css" charset="utf-8" />
14
+
15
+ <link rel="stylesheet" href="css/common.css" type="text/css" charset="utf-8" />
16
+
17
+ <script type="text/javascript" charset="utf-8">
18
+ hasFrames = window.top.frames.main ? true : false;
19
+ relpath = '';
20
+ framesUrl = "frames.html#!top-level-namespace.html";
21
+ </script>
22
+
23
+
24
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
25
+
26
+ <script type="text/javascript" charset="utf-8" src="js/app.js"></script>
27
+
28
+
29
+ </head>
30
+ <body>
31
+ <div id="header">
32
+ <div id="menu">
33
+
34
+ <a href="_index.html">Index</a> &raquo;
35
+
36
+
37
+ <span class="title">Top Level Namespace</span>
38
+
39
+
40
+ <div class="noframes"><span class="title">(</span><a href="." target="_top">no frames</a><span class="title">)</span></div>
41
+ </div>
42
+
43
+ <div id="search">
44
+
45
+ <a class="full_list_link" id="class_list_link"
46
+ href="class_list.html">
47
+ Class List
48
+ </a>
49
+
50
+ <a class="full_list_link" id="method_list_link"
51
+ href="method_list.html">
52
+ Method List
53
+ </a>
54
+
55
+ <a class="full_list_link" id="file_list_link"
56
+ href="file_list.html">
57
+ File List
58
+ </a>
59
+
60
+ </div>
61
+ <div class="clear"></div>
62
+ </div>
63
+
64
+ <iframe id="search_frame"></iframe>
65
+
66
+ <div id="content"><h1>Top Level Namespace
67
+
68
+
69
+
70
+ </h1>
71
+
72
+ <dl class="box">
73
+
74
+
75
+
76
+
77
+
78
+
79
+
80
+
81
+ </dl>
82
+ <div class="clear"></div>
83
+
84
+ <h2>Defined Under Namespace</h2>
85
+ <p class="children">
86
+
87
+
88
+ <strong class="modules">Modules:</strong> <span class='object_link'><a href="Isimud.html" title="Isimud (module)">Isimud</a></span>
89
+
90
+
91
+
92
+
93
+ </p>
94
+
95
+
96
+
97
+
98
+
99
+
100
+
101
+
102
+
103
+ </div>
104
+
105
+ <div id="footer">
106
+ Generated on Fri Apr 3 09:28:08 2015 by
107
+ <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
108
+ 0.8.7.6 (ruby-2.2.1).
109
+ </div>
110
+
111
+ </body>
112
+ </html>
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'isimud/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'isimud'
8
+ spec.version = Isimud::VERSION
9
+ spec.authors = ['George Feil', 'Brian Jenkins']
10
+ spec.email = %w{george.feil@keas.com bonkydog@bonkydog.com}
11
+ spec.summary = %q{AMQP Messaging for Events and ActiveRecord changes}
12
+ spec.description = %q{}
13
+ spec.homepage = ''
14
+ spec.license = "Copyright (C) 2014 Keas -- All rights reserved"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'activerecord', '>= 3.2.17'
22
+ spec.add_dependency 'activesupport', '>= 3.2.17'
23
+ spec.add_dependency 'bunny', '>= 1.6.0'
24
+ spec.add_dependency 'chronic_duration', '>= 0.10.6'
25
+ end
@@ -0,0 +1,91 @@
1
+ require 'active_support'
2
+ require 'active_support/core_ext/module/attribute_accessors'
3
+
4
+ module Isimud
5
+ include ::ActiveSupport::Configurable
6
+ # @!attribute [r] client_type
7
+ # @return [Enumerable<'bunny', 'test'>] Type of client to use
8
+ # @!attribute [r] client_options
9
+ # @return [Hash] client specific options
10
+ # @!attribute [r] default_client
11
+ # @return [Isimud::Client] default client
12
+ # @!attribute [r] events_exchange
13
+ # @return [String] AMQP exchange used for publishing Event instances
14
+ # @!attribute [r] enable_model_watcher
15
+ # @return [Boolean] when set, send Isimud::ModelWatcher messages
16
+ # @!attribute [r] listener_error_limit
17
+ # @return [Integer] maximum number of exceptions allowed per hour before listener shuts down (100)
18
+ # @!attribute [r] logger
19
+ # @return [Logger] logger for tracing messages (Rails.logger)
20
+ # @!attribute [r] log_level
21
+ # @return [Symbol] log level (:debug)
22
+ # @!attribute [r] model_watcher_exchange
23
+ # @return [String] AMQP exchange used for publishing ModelWatcher messages
24
+ # @!attribute [r] model_watcher_schema
25
+ # @return [String] schema name (Rails.configuration.database_configuration[Rails.env]['database'])
26
+ # @!attribute [r] prefetch_count
27
+ # @return [Integer] number of messages to fetch -- only applies to BunnyClient
28
+ # @!attribute [r] retry_failures
29
+ # @return [Boolean] when set, if an exception occurs during message processing, requeue it
30
+ # @!attribute [r] server
31
+ # @return [<String, Hash>] server connection attributes
32
+ config_accessor :client_type do
33
+ :bunny
34
+ end
35
+ config_accessor :client_options, :default_client, :enable_model_watcher, :model_watcher_schema, :retry_failures
36
+ config_accessor :listener_error_limit do
37
+ 100
38
+ end
39
+ config_accessor :logger do
40
+ Rails.logger
41
+ end
42
+ config_accessor :log_level do
43
+ :debug
44
+ end
45
+ config_accessor :events_exchange do
46
+ 'events'
47
+ end
48
+ config_accessor :model_watcher_exchange do
49
+ 'models'
50
+ end
51
+ config_accessor :prefetch_count do
52
+ 100
53
+ end
54
+ config_accessor :server do
55
+ ENV['AMQP_URL']
56
+ end
57
+
58
+ def self.client_class
59
+ type = "#{client_type}_client".classify
60
+ "Isimud::#{type}".constantize
61
+ end
62
+
63
+ # Fetch or initialize the messaging client for this process.
64
+ # @return [Isimud::Client] messaging client
65
+ def self.client
66
+ self.default_client ||= client_class.new(server, client_options || {})
67
+ end
68
+
69
+ # Connect to the messaging server
70
+ def self.connect
71
+ client.connect
72
+ end
73
+
74
+ # Return status of model watching mode
75
+ def self.model_watcher_enabled?
76
+ enable_model_watcher.nil? || enable_model_watcher
77
+ end
78
+
79
+ # Reconnect the messaging client
80
+ def self.reconnect
81
+ client.reconnect
82
+ end
83
+ end
84
+
85
+ require 'isimud/logging'
86
+ require 'isimud/client'
87
+ require 'isimud/event'
88
+ require 'isimud/event_listener'
89
+ require 'isimud/event_observer'
90
+ require 'isimud/model_watcher'
91
+ require 'isimud/version'
@@ -0,0 +1,95 @@
1
+ require 'bunny'
2
+ require 'logger'
3
+
4
+ module Isimud
5
+ class BunnyClient < Isimud::Client
6
+ DEFAULT_URL = 'amqp://guest:guest@localhost'
7
+
8
+ attr_reader :url
9
+
10
+ def initialize(_url = nil, _bunny_options = {})
11
+ log "Isimud::BunnyClient.initialize: options = #{_bunny_options.inspect}"
12
+ @url = _url || DEFAULT_URL
13
+ @bunny_options = _bunny_options
14
+ end
15
+
16
+ def bind(queue_name, exchange_name, *routing_keys, &block)
17
+ create_queue(queue_name, exchange_name,
18
+ queue_options: {durable: true},
19
+ routing_keys: routing_keys,
20
+ subscribe_options: {manual_ack: true}, &block)
21
+ end
22
+
23
+ def create_queue(queue_name, exchange_name, options = {}, &block)
24
+ queue_options = options[:queue_options] || {}
25
+ routing_keys = options[:routing_keys] || []
26
+ subscribe_options = options[:subscribe_options] || {}
27
+ log "Isimud: create_queue #{queue_name}: queue_options=#{queue_options.inspect} routing_keys=#{routing_keys.join(',')} subscribe_options=#{subscribe_options.inspect}"
28
+ current_channel = channel
29
+ queue = current_channel.queue(queue_name, queue_options)
30
+ routing_keys.each { |key| queue.bind(exchange_name, routing_key: key, nowait: false) }
31
+ queue.subscribe(subscribe_options) do |delivery_info, properties, payload|
32
+ begin
33
+ log "Isimud: queue #{queue_name} received #{delivery_info.delivery_tag} routing_key: #{delivery_info.routing_key}"
34
+ Thread.current['isimud_queue_name'] = queue_name
35
+ Thread.current['isimud_delivery_info'] = delivery_info
36
+ Thread.current['isimud_properties'] = properties
37
+ block.call(payload)
38
+ log "Isimud: queue #{queue_name} finished with #{delivery_info.delivery_tag}, acknowledging"
39
+ current_channel.ack(delivery_info.delivery_tag)
40
+ rescue => e
41
+ log("Isimud: queue #{queue_name} error processing #{delivery_info.delivery_tag} payload #{payload.inspect}: #{e.class.name} #{e.message}\n #{e.backtrace.join("\n ")}", :warn)
42
+ current_channel.reject(delivery_info.delivery_tag, Isimud.retry_failures)
43
+ exception_handler.try(:call, e)
44
+ end
45
+ end
46
+ queue
47
+ end
48
+
49
+ def delete_queue(queue_name)
50
+ channel.queue(queue_name).delete
51
+ end
52
+
53
+ def connection
54
+ @connection ||= ::Bunny.new(url, @bunny_options).tap(&:start)
55
+ end
56
+
57
+ alias connect connection
58
+
59
+ CHANNEL_KEY = :'isimud.bunny_client.channel'
60
+
61
+ def channel
62
+ if (channel = Thread.current[CHANNEL_KEY]).try(:open?)
63
+ channel
64
+ else
65
+ new_channel = connection.channel
66
+ new_channel.confirm_select
67
+ new_channel.prefetch(Isimud.prefetch_count) if Isimud.prefetch_count
68
+ Thread.current[CHANNEL_KEY] = new_channel
69
+ end
70
+ end
71
+
72
+ def reset
73
+ connection.close_all_channels
74
+ end
75
+
76
+ def connected?
77
+ @connection && @connection.open?
78
+ end
79
+
80
+ def close
81
+ connection.close
82
+ ensure
83
+ @connection = nil
84
+ end
85
+
86
+ def publish(exchange, routing_key, payload)
87
+ channel.topic(exchange, durable: true).publish(payload, routing_key: routing_key, persistent: true)
88
+ end
89
+
90
+ def reconnect
91
+ close
92
+ connect
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,48 @@
1
+ module Isimud
2
+ # @abstract Messaging queue service client
3
+ class Client
4
+ include Isimud::Logging
5
+
6
+ attr_reader :exception_handler
7
+
8
+ def initialize(server = nil, options = nil)
9
+ end
10
+
11
+ def bind(queue_name, exchange_name, *keys, &method)
12
+ end
13
+
14
+ def channel
15
+ end
16
+
17
+ def close
18
+ end
19
+
20
+ def connect
21
+ end
22
+
23
+ def connected?
24
+ end
25
+
26
+ def create_queue(queue_name, exchange_name, options = {}, &method)
27
+ end
28
+
29
+ def delete_queue(queue_name)
30
+ end
31
+
32
+ def on_exception(&block)
33
+ @exception_handler = block
34
+ end
35
+
36
+ def publish(exchange, routing_key, payload)
37
+ end
38
+
39
+ def reconnect
40
+ end
41
+
42
+ def reset
43
+ end
44
+ end
45
+ end
46
+
47
+ require 'isimud/bunny_client'
48
+ require 'isimud/test_client'
@@ -0,0 +1,112 @@
1
+ require 'active_support'
2
+
3
+ module Isimud
4
+ class Event
5
+ include Isimud::Logging
6
+ attr_accessor :type, :action, :user_id, :occurred_at, :eventful_type, :eventful_id, :attributes, :parameters
7
+ attr_writer :exchange
8
+
9
+ DEFAULT_TYPE = :model
10
+
11
+ # Initialize a new Event
12
+ # @overload new(user, eventful, attributes)
13
+ # @param[#id] user user associated by the event
14
+ # @param[ActiveRecord::Base] eventful object associated with event
15
+ # @param[Hash] parameters optional additional attributes
16
+ # @overload new(attributes)
17
+ # @param[Hash] attributes event attributes
18
+ # @option attributes [Integer] :user_id ID of User associated with event
19
+ # @option attributes [String] :eventful_type class of object associated with event
20
+ # @option attributes [Integer] :eventful_id id of object associated with event
21
+ # @option attributes [String] :exchange (Isimud.events_exchange) exchange for publishing event
22
+ # @option attributes [ActiveRecord::Base] :eventful object associated with event. This sets :eventful_type and :eventful_id.
23
+ # @option attributes [String, Symbol] :type (:model) event type
24
+ # @option attributes [String] :action event action
25
+ # @option attributes [Time] :occurred_at (Time.now) date and time event occurred
26
+ # @option attributes [Hash] :attributes event attributes
27
+ # @option attributes [Hash] :parameters additional parameters (deprecated)
28
+ def initialize(*args)
29
+ options = args.extract_options!.with_indifferent_access
30
+
31
+ self.type = options.delete(:type).try(:to_sym) || DEFAULT_TYPE
32
+ self.exchange = options.delete(:exchange)
33
+ self.action = options.delete(:action).try(:to_sym)
34
+ self.user_id = options.delete(:user_id)
35
+ self.occurred_at = if (occurred = options.delete(:occurred_at))
36
+ occurred.kind_of?(String) ? Time.parse(occurred) : occurred
37
+ else
38
+ Time.now.utc
39
+ end
40
+
41
+ eventful_object = options.delete(:eventful)
42
+
43
+ if args.length > 0
44
+ self.parameters = options
45
+ if (user = args.shift)
46
+ self.user_id = user.id
47
+ end
48
+ eventful_object ||= args.shift
49
+ end
50
+
51
+ if eventful_object
52
+ self.eventful_type = eventful_object.class.base_class.name
53
+ self.eventful_id = eventful_object.id
54
+ else
55
+ self.eventful_type = options.delete(:eventful_type)
56
+ self.eventful_id = options.delete(:eventful_id)
57
+ end
58
+ self.attributes = options.delete(:attributes)
59
+ self.parameters = options.delete(:parameters) || options
60
+ end
61
+
62
+ def exchange
63
+ @exchange || Isimud.events_exchange
64
+ end
65
+
66
+ def routing_key
67
+ [type.to_s, eventful_type, eventful_id, action].compact.join('.')
68
+ end
69
+
70
+ # Return hash of data to be serialized to JSON
71
+ # @option options [Boolean] :omit_parameters when set, do not include attributes or parameters in data
72
+ # @return [Hash] data to serialize
73
+ def as_json(options = {})
74
+ session_id = parameters.delete(:session_id) || Thread.current[:keas_session_id]
75
+
76
+ data = {type: type,
77
+ action: action,
78
+ user_id: user_id,
79
+ occurred_at: occurred_at,
80
+ eventful_type: eventful_type,
81
+ eventful_id: eventful_id,
82
+ session_id: session_id}
83
+ unless options[:omit_parameters]
84
+ data[:parameters] = parameters
85
+ data[:attributes] = attributes
86
+ end
87
+ data
88
+ end
89
+
90
+ def serialize
91
+ self.to_json
92
+ end
93
+
94
+ class << self
95
+ def parse(data)
96
+ Event.new(JSON.parse(data))
97
+ end
98
+
99
+ def publish(*args)
100
+ Event.new(*args).publish
101
+ end
102
+
103
+ alias_method :dispatch, :publish
104
+ end
105
+
106
+ def publish
107
+ data = self.serialize
108
+ log "Event#publish: #{self.inspect}"
109
+ Isimud.client.publish(exchange, routing_key, data)
110
+ end
111
+ end
112
+ end