wonkavision 0.5.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.
- data/CHANGELOG.rdoc +3 -0
- data/LICENSE.txt +22 -0
- data/README.rdoc +0 -0
- data/Rakefile +48 -0
- data/lib/wonkavision.rb +68 -0
- data/lib/wonkavision/acts_as_oompa_loompa.rb +22 -0
- data/lib/wonkavision/event.rb +33 -0
- data/lib/wonkavision/event_binding.rb +21 -0
- data/lib/wonkavision/event_coordinator.rb +75 -0
- data/lib/wonkavision/event_handler.rb +14 -0
- data/lib/wonkavision/event_namespace.rb +72 -0
- data/lib/wonkavision/event_path_segment.rb +37 -0
- data/lib/wonkavision/message_mapper.rb +30 -0
- data/lib/wonkavision/message_mapper/indifferent_access.rb +26 -0
- data/lib/wonkavision/message_mapper/map.rb +126 -0
- data/lib/wonkavision/persistence/mongo_mapper_adapter.rb +33 -0
- data/lib/wonkavision/plugins.rb +30 -0
- data/lib/wonkavision/plugins/business_activity.rb +79 -0
- data/lib/wonkavision/plugins/event_handling.rb +78 -0
- data/lib/wonkavision/plugins/timeline.rb +80 -0
- data/lib/wonkavision/support.rb +0 -0
- data/lib/wonkavision/version.rb +3 -0
- data/test/business_activity_test.rb +24 -0
- data/test/config/database.yml +15 -0
- data/test/event_coordinator_test.rb +72 -0
- data/test/event_handler_test.rb +47 -0
- data/test/event_namespace_test.rb +109 -0
- data/test/event_path_segment_test.rb +56 -0
- data/test/event_test.rb +54 -0
- data/test/log/test.log +7853 -0
- data/test/map_test.rb +187 -0
- data/test/message_mapper_test.rb +21 -0
- data/test/test_activity_models.rb +56 -0
- data/test/test_helper.rb +63 -0
- data/test/timeline_test.rb +61 -0
- data/test/wonkavision_test.rb +10 -0
- data/wonkavision.gemspec +94 -0
- metadata +145 -0
data/CHANGELOG.rdoc
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT LICENSE
|
2
|
+
|
3
|
+
Copyright (c) 2010 Nathan Stults
|
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.
|
data/README.rdoc
ADDED
File without changes
|
data/Rakefile
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'jeweler'
|
8
|
+
require File.dirname(__FILE__) + "/lib/wonkavision/version"
|
9
|
+
|
10
|
+
Jeweler::Tasks.new do |s|
|
11
|
+
s.name = "wonkavision"
|
12
|
+
s.version = Wonkavision::VERSION
|
13
|
+
s.summary = "Simple Business Activity Monitoring"
|
14
|
+
s.description = "Wonkavision is a small gem that allows you to publish"
|
15
|
+
s.email = "hereiam@sonic.net"
|
16
|
+
s.homepage = "http://github.com/PlasticLizard/wonkavision"
|
17
|
+
s.authors = ["Nathan Stults"]
|
18
|
+
s.has_rdoc = false #=>Should be true, someday
|
19
|
+
s.extra_rdoc_files = ["README.rdoc", "LICENSE.txt"]
|
20
|
+
s.files = FileList["[A-Z]*", "{bin,lib,test}/**/*"]
|
21
|
+
|
22
|
+
s.add_dependency('activesupport', '>= 2.3')
|
23
|
+
|
24
|
+
s.add_development_dependency('shoulda', '2.10.3')
|
25
|
+
end
|
26
|
+
|
27
|
+
Jeweler::GemcutterTasks.new
|
28
|
+
|
29
|
+
rescue LoadError => ex
|
30
|
+
puts "Jeweler not available. Install it for jeweler-related tasks with: sudo gem install jeweler"
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
Rake::TestTask.new do |t|
|
35
|
+
t.libs << 'libs' << 'test'
|
36
|
+
t.pattern = 'test/**/*_test.rb'
|
37
|
+
t.verbose = false
|
38
|
+
end
|
39
|
+
|
40
|
+
Rake::RDocTask.new do |rdoc|
|
41
|
+
rdoc.rdoc_dir = 'rdoc'
|
42
|
+
rdoc.title = 'test'
|
43
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
44
|
+
rdoc.rdoc_files.include('README*')
|
45
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
46
|
+
end
|
47
|
+
|
48
|
+
task :default => :test
|
data/lib/wonkavision.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "active_support"
|
3
|
+
require "active_support/hash_with_indifferent_access" unless defined?(HashWithIndifferentAccess)
|
4
|
+
|
5
|
+
dir = File.dirname(__FILE__)
|
6
|
+
["support",
|
7
|
+
"plugins",
|
8
|
+
"event_path_segment",
|
9
|
+
"event",
|
10
|
+
"event_namespace",
|
11
|
+
"event_coordinator",
|
12
|
+
"event_binding",
|
13
|
+
"event_handler",
|
14
|
+
"message_mapper/indifferent_access",
|
15
|
+
"message_mapper/map",
|
16
|
+
"message_mapper",
|
17
|
+
"plugins/event_handling",
|
18
|
+
"plugins/business_activity",
|
19
|
+
"plugins/timeline",
|
20
|
+
"acts_as_oompa_loompa",
|
21
|
+
"persistence/mongo_mapper_adapter"].each {|lib|require File.join(dir,'wonkavision',lib)}
|
22
|
+
|
23
|
+
#require File.join(dir,"cubicle","mongo_mapper","aggregate_plugin") if defined?(MongoMapper::Document)
|
24
|
+
|
25
|
+
module Wonkavision
|
26
|
+
|
27
|
+
# def self.register_cubicle_directory(directory_path, recursive=true)
|
28
|
+
# searcher = "#{recursive ? "*" : "**/*"}.rb"
|
29
|
+
# Dir[File.join(directory_path,searcher)].each {|cubicle| require cubicle}
|
30
|
+
# end
|
31
|
+
|
32
|
+
class << self
|
33
|
+
|
34
|
+
attr_accessor :event_path_separator
|
35
|
+
|
36
|
+
def event_coordinator
|
37
|
+
@event_coordinator ||= Wonkavision::EventCoordinator.new
|
38
|
+
end
|
39
|
+
|
40
|
+
def event_path_separator
|
41
|
+
@event_path_separator ||= '/'
|
42
|
+
end
|
43
|
+
|
44
|
+
def namespace_wildcard_character
|
45
|
+
@namespace_wildcard_character = "*"
|
46
|
+
end
|
47
|
+
|
48
|
+
def is_absolute_path(path)
|
49
|
+
path.to_s[0..0] == event_path_separator
|
50
|
+
end
|
51
|
+
|
52
|
+
def normalize_event_path(event_path)
|
53
|
+
event_path.to_s.split(event_path_separator).map{|s|s.underscore}.join(event_path_separator)
|
54
|
+
end
|
55
|
+
|
56
|
+
def join (*args)
|
57
|
+
args.map!{|segment|normalize_event_path(segment)}
|
58
|
+
args.reject{|segment|segment.blank?}.join(event_path_separator)
|
59
|
+
end
|
60
|
+
|
61
|
+
def split(event_path)
|
62
|
+
event_path.split(event_path_separator)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Wonkavision
|
2
|
+
module ActsAsOompaLoompa
|
3
|
+
|
4
|
+
def acts_as_event_handler
|
5
|
+
include Wonkavision::EventHandler
|
6
|
+
end
|
7
|
+
|
8
|
+
def acts_as_business_activity(opts={})
|
9
|
+
acts_as_event_handler
|
10
|
+
use Wonkavision::Plugins::BusinessActivity, opts
|
11
|
+
end
|
12
|
+
|
13
|
+
def acts_as_timeline(opts={})
|
14
|
+
acts_as_business_activity(opts)
|
15
|
+
use Wonkavision::Plugins::Timeline, opts
|
16
|
+
end
|
17
|
+
|
18
|
+
def acts_like_a_child(opts={})
|
19
|
+
raise "I don't want to include any plugins!" if (1 + rand(10)) % 3 == 0
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Wonkavision
|
2
|
+
|
3
|
+
class Event < Wonkavision::EventPathSegment
|
4
|
+
|
5
|
+
def initialize(name, namespace, opts={})
|
6
|
+
super name,namespace,opts
|
7
|
+
@source_events = []
|
8
|
+
source_events(*opts.delete(:source_events)) if opts.keys.include?(:source_events)
|
9
|
+
end
|
10
|
+
|
11
|
+
def matches(event_path)
|
12
|
+
event_path == path || @source_events.detect{|evt|evt.matches(event_path)} != nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def notify_subscribers(event_data,event_path)
|
16
|
+
super(event_data,self.path)
|
17
|
+
end
|
18
|
+
|
19
|
+
def source_events(*args)
|
20
|
+
return @source_events if args.blank?
|
21
|
+
@source_events = @source_events.concat(args.map do |source|
|
22
|
+
source_ns = namespace
|
23
|
+
if (source=~/^\/.*/)
|
24
|
+
source_ns = root_namespace
|
25
|
+
source = source[1..-1]
|
26
|
+
end
|
27
|
+
source_ns.find_or_create(source)
|
28
|
+
end)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Wonkavision
|
2
|
+
|
3
|
+
class EventBinding
|
4
|
+
attr_reader :name, :events, :options
|
5
|
+
|
6
|
+
def initialize(name,handler,*args)
|
7
|
+
@options = args.extract_options!
|
8
|
+
@handler = handler
|
9
|
+
@name = name
|
10
|
+
@events = []
|
11
|
+
args = [@name] if args.blank? || args.flatten.blank?
|
12
|
+
@events = args.flatten.map{|evt_name|@handler.event_path(evt_name)}
|
13
|
+
end
|
14
|
+
|
15
|
+
def subscribe_to_events(&block)
|
16
|
+
@events.each do |event|
|
17
|
+
Wonkavision.event_coordinator.subscribe(event,&block)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Wonkavision
|
2
|
+
class EventCoordinator
|
3
|
+
|
4
|
+
attr_reader :root_namespace
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@root_namespace = Wonkavision::EventNamespace.new
|
8
|
+
@lock = Mutex.new
|
9
|
+
#@event_cache = {}
|
10
|
+
@incoming_event_filters = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def before_receive_event(&block)
|
14
|
+
@incoming_event_filters << block
|
15
|
+
end
|
16
|
+
|
17
|
+
def clear_filters
|
18
|
+
@incoming_event_filters = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def configure(&block)
|
22
|
+
self.instance_eval(&block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def map ()
|
26
|
+
yield root_namespace if block_given?
|
27
|
+
end
|
28
|
+
|
29
|
+
def subscribe(event_path,final_segment_type=nil,&block)
|
30
|
+
event_path, final_segment_type = *detect_final_segment(event_path) unless final_segment_type
|
31
|
+
segment = (event_path.blank? ? root_namespace : root_namespace.find_or_create(event_path,final_segment_type))
|
32
|
+
segment.subscribe(&block)
|
33
|
+
end
|
34
|
+
|
35
|
+
def receive_event(event_path, event_data)
|
36
|
+
@lock.synchronize do
|
37
|
+
#If process_incoming_event returns nil or false, it means a filter chose to abort
|
38
|
+
#the event processing, in which case we'll break for lunch.
|
39
|
+
return unless event_data = process_incoming_event(event_path,event_data)
|
40
|
+
|
41
|
+
event_path = Wonkavision.normalize_event_path(event_path)
|
42
|
+
targets = root_namespace.find_matching_events(event_path)
|
43
|
+
#If the event wasn't matched, maybe someone is subscribing to '/*' ?
|
44
|
+
targets = [root_namespace] if targets.blank?
|
45
|
+
targets.each{|target|target.notify_subscribers(event_data,event_path)}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
def process_incoming_event(event_path,event_data)
|
51
|
+
return nil unless event_data
|
52
|
+
@incoming_event_filters.each do |filter_block|
|
53
|
+
if (filter_block.arity < 1)
|
54
|
+
event_data = event_data.instance_eval(&filter_block)
|
55
|
+
elsif filter_block.arity == 1
|
56
|
+
event_data = filter_block.call(event_data)
|
57
|
+
else
|
58
|
+
event_data = filter_block.call(event_data,event_path)
|
59
|
+
end
|
60
|
+
return nil unless event_data
|
61
|
+
end
|
62
|
+
event_data
|
63
|
+
end
|
64
|
+
|
65
|
+
def detect_final_segment(event_path)
|
66
|
+
parts = Wonkavision.split(event_path)
|
67
|
+
if parts[-1] == Wonkavision.namespace_wildcard_character
|
68
|
+
[Wonkavision.join(parts.slice(0..-2)),:namespace]
|
69
|
+
else
|
70
|
+
[event_path,:event]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Wonkavision
|
2
|
+
|
3
|
+
class EventNamespace < EventPathSegment
|
4
|
+
|
5
|
+
attr_reader :children
|
6
|
+
|
7
|
+
def initialize(name=nil,namespace = nil,opts={})
|
8
|
+
super name, namespace,opts
|
9
|
+
@children=HashWithIndifferentAccess.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def find_or_create (path, final_segment_type = :event)
|
13
|
+
if path.is_a?(Array)
|
14
|
+
child_name = path.shift
|
15
|
+
segment_type = path.blank? ? final_segment_type : :namespace
|
16
|
+
child = @children[child_name] ||= self.send(segment_type,child_name)
|
17
|
+
return child if path.blank?
|
18
|
+
raise "Events cannot have children. The path you requested is not valid" if child.is_a?(Wonkavision::Event)
|
19
|
+
child.find_or_create(path,final_segment_type)
|
20
|
+
else
|
21
|
+
path = Wonkavision.normalize_event_path(path)
|
22
|
+
source_ns = self
|
23
|
+
if (Wonkavision.is_absolute_path(path)) #indicates an absolute path, because it begins with a '/'
|
24
|
+
source_ns = root_namespace
|
25
|
+
path = path[1..-1]
|
26
|
+
end
|
27
|
+
source_ns.find_or_create(path.split(Wonkavision.event_path_separator), final_segment_type)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def find_matching_events (event_path)
|
32
|
+
events = []
|
33
|
+
@children.each_value do |child|
|
34
|
+
if (child.is_a?(Wonkavision::Event))
|
35
|
+
events << child if child.matches(event_path)
|
36
|
+
elsif (child.is_a?(Wonkavision::EventNamespace))
|
37
|
+
events.concat(child.find_matching_events(event_path))
|
38
|
+
else
|
39
|
+
raise "An unexpected child type was encountered in find_matching_events #{child.class}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
#If no child was found, and the event matches this namespace, we should add ourselves to the list.
|
43
|
+
#This is not necessary if any child event was located, because in that case event notifications
|
44
|
+
#would bubble up to us anyway. If no event was found, there's nobody to blow the bubble but us.
|
45
|
+
events << self if events.blank? && matches_event(event_path)
|
46
|
+
events
|
47
|
+
end
|
48
|
+
|
49
|
+
def matches_event(event_path)
|
50
|
+
Wonkavision.split(path) == Wonkavision.split(event_path).slice(0..-2)
|
51
|
+
end
|
52
|
+
|
53
|
+
#dsl
|
54
|
+
def namespace(*args)
|
55
|
+
return super if args.blank?
|
56
|
+
name, opts = args.shift, (args.shift || {})
|
57
|
+
ns = Wonkavision::EventNamespace.new(name,self,opts)
|
58
|
+
yield ns if block_given?
|
59
|
+
@children[ns.name] = ns
|
60
|
+
ns
|
61
|
+
end
|
62
|
+
|
63
|
+
def event(*args)
|
64
|
+
name, opts = args.shift, args.extract_options!
|
65
|
+
opts[:source_events] = (opts[:source_events] || []).concat(args) unless args.blank?
|
66
|
+
evt = Wonkavision::Event.new(name,self,opts)
|
67
|
+
yield evt if block_given?
|
68
|
+
@children[evt.name] = evt
|
69
|
+
evt
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Wonkavision
|
2
|
+
class EventPathSegment
|
3
|
+
attr_reader :name, :namespace, :options, :subscribers
|
4
|
+
|
5
|
+
def initialize(name = nil, namespace = nil, opts={})
|
6
|
+
@name = name
|
7
|
+
@namespace = namespace
|
8
|
+
@options = opts
|
9
|
+
@subscribers = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def path
|
13
|
+
Wonkavision.join(namespace.blank? ? nil : namespace.path,name)
|
14
|
+
end
|
15
|
+
|
16
|
+
def subscribe(&block)
|
17
|
+
@subscribers << block
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def notify_subscribers(event_data, event_path=self.path)
|
22
|
+
@subscribers.each do |sub|
|
23
|
+
sub.call(event_data,event_path)
|
24
|
+
end
|
25
|
+
namespace.notify_subscribers(event_data,event_path) if namespace
|
26
|
+
end
|
27
|
+
|
28
|
+
def root_namespace
|
29
|
+
cur_namespace = self
|
30
|
+
while (cur_namespace.namespace)
|
31
|
+
cur_namespace=cur_namespace.namespace
|
32
|
+
end
|
33
|
+
cur_namespace
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Wonkavision
|
2
|
+
|
3
|
+
module MessageMapper
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def maps
|
7
|
+
@maps ||={}
|
8
|
+
end
|
9
|
+
|
10
|
+
def register(map_name,&block)
|
11
|
+
MessageMapper.maps[map_name] = block
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute(map,data_source)
|
15
|
+
map_block = map.kind_of?(Proc) ? map : MessageMapper.maps[map]
|
16
|
+
|
17
|
+
raise "#{map} not found" unless map_block
|
18
|
+
message = MessageMapper::Map.new(data_source)
|
19
|
+
message.instance_eval(&map_block)
|
20
|
+
message
|
21
|
+
end
|
22
|
+
|
23
|
+
def register_map_directory(directory_path, recursive=true)
|
24
|
+
searcher = "#{recursive ? "*" : "**/*"}.rb"
|
25
|
+
Dir[File.join(directory_path,searcher)].each {|map| require map}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|