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.
Files changed (38) hide show
  1. data/CHANGELOG.rdoc +3 -0
  2. data/LICENSE.txt +22 -0
  3. data/README.rdoc +0 -0
  4. data/Rakefile +48 -0
  5. data/lib/wonkavision.rb +68 -0
  6. data/lib/wonkavision/acts_as_oompa_loompa.rb +22 -0
  7. data/lib/wonkavision/event.rb +33 -0
  8. data/lib/wonkavision/event_binding.rb +21 -0
  9. data/lib/wonkavision/event_coordinator.rb +75 -0
  10. data/lib/wonkavision/event_handler.rb +14 -0
  11. data/lib/wonkavision/event_namespace.rb +72 -0
  12. data/lib/wonkavision/event_path_segment.rb +37 -0
  13. data/lib/wonkavision/message_mapper.rb +30 -0
  14. data/lib/wonkavision/message_mapper/indifferent_access.rb +26 -0
  15. data/lib/wonkavision/message_mapper/map.rb +126 -0
  16. data/lib/wonkavision/persistence/mongo_mapper_adapter.rb +33 -0
  17. data/lib/wonkavision/plugins.rb +30 -0
  18. data/lib/wonkavision/plugins/business_activity.rb +79 -0
  19. data/lib/wonkavision/plugins/event_handling.rb +78 -0
  20. data/lib/wonkavision/plugins/timeline.rb +80 -0
  21. data/lib/wonkavision/support.rb +0 -0
  22. data/lib/wonkavision/version.rb +3 -0
  23. data/test/business_activity_test.rb +24 -0
  24. data/test/config/database.yml +15 -0
  25. data/test/event_coordinator_test.rb +72 -0
  26. data/test/event_handler_test.rb +47 -0
  27. data/test/event_namespace_test.rb +109 -0
  28. data/test/event_path_segment_test.rb +56 -0
  29. data/test/event_test.rb +54 -0
  30. data/test/log/test.log +7853 -0
  31. data/test/map_test.rb +187 -0
  32. data/test/message_mapper_test.rb +21 -0
  33. data/test/test_activity_models.rb +56 -0
  34. data/test/test_helper.rb +63 -0
  35. data/test/timeline_test.rb +61 -0
  36. data/test/wonkavision_test.rb +10 -0
  37. data/wonkavision.gemspec +94 -0
  38. metadata +145 -0
@@ -0,0 +1,26 @@
1
+ module Wonkavision
2
+ module MessageMapper
3
+ module IndifferentAccess
4
+ def [](key)
5
+ key = key.to_s
6
+ super(key)
7
+ end
8
+
9
+ def []=(key,val)
10
+ super(key.to_s,val)
11
+ end
12
+
13
+ def include?(key)
14
+ super(key.to_s)
15
+ end
16
+
17
+ def method_missing(sym,*args,&block)
18
+ return self[sym.to_s[0..-2]] = args[0] if sym.to_s =~ /.*=$/
19
+ return self[sym] if self.keys.include?(sym.to_s)
20
+ nil
21
+ end
22
+ end
23
+ end
24
+
25
+ end
26
+
@@ -0,0 +1,126 @@
1
+ module Wonkavision
2
+
3
+ module MessageMapper
4
+ class Map < Hash
5
+ include IndifferentAccess
6
+
7
+ def initialize(context)
8
+ @context_stack = []
9
+ @context_stack.push(context)
10
+ end
11
+
12
+
13
+
14
+ def context
15
+ @context_stack[-1]
16
+ end
17
+
18
+ def from (context,&block)
19
+ raise "No block ws provided to 'from'" unless block
20
+ @context_stack.push(context)
21
+ instance_eval(&block)
22
+ @context_stack.pop
23
+ end
24
+
25
+ def exec(map_name)
26
+ mapped = MessageMapper.execute(map_name,context)
27
+ self.merge!(mapped) if mapped
28
+ end
29
+ alias import exec
30
+
31
+ def map(source,options={},&block)
32
+ raise "Neither a block nor a map_name were provided to 'map'" unless (block || options.keys.include?(:map_name))
33
+ if (source.is_a?(Hash))
34
+ field_name = source.keys[0]
35
+ ctx = source[field_name]
36
+ else
37
+ field_name = source
38
+ ctx = context.instance_eval("self.#{field_name}")
39
+ end
40
+ if ctx
41
+ if (map_name = options.delete(:map_name))
42
+ child = MessageMapper.execute(map_name,ctx)
43
+ else
44
+ child = Map.new(ctx)
45
+ child.instance_eval(&block)
46
+ end
47
+ else
48
+ child = {}
49
+ end
50
+ self[field_name] = child
51
+ end
52
+
53
+ def string(*args)
54
+ value(*args) {to_s}
55
+ end
56
+
57
+ def float(*args)
58
+ value(*args){respond_to?(:to_f) ? to_f : self}
59
+ end
60
+
61
+ def iso8601(*args)
62
+ value(*args) do
63
+ if respond_to?(:strftime)
64
+ strftime("%Y-%m-%dT%H:%M:%S")
65
+ elsif respond_to?(:ToString)
66
+ ToString("yyyy-MM-ddTHH:mm:ss")
67
+ else
68
+ self
69
+ end
70
+ end
71
+ end
72
+
73
+ def date(*args)
74
+ value(*args) do
75
+ if kind_of?(Time) || kind_of?(Date)
76
+ self
77
+ elsif respond_to?(:to_time)
78
+ to_time
79
+ elsif respond_to?(:to_date)
80
+ to_date
81
+ elsif (date_str=to_s) && date_str.length > 0
82
+ begin
83
+ Time.parse(date_str)
84
+ rescue
85
+ self
86
+ end
87
+ else
88
+ self
89
+ end
90
+ end
91
+ end
92
+
93
+ def boolean(*args)
94
+ value(*args) do
95
+ %w(true yes).include?(to_s.downcase) ? true : false
96
+ end
97
+ end
98
+
99
+ def int(*args)
100
+ value(*args){respond_to?(:to_i) ? to_i : self}
101
+ end
102
+
103
+ def value(*args,&block)
104
+ if (args.length == 1 && args[0].is_a?(Hash))
105
+ args[0].each do |key,value|
106
+ value = context.instance_eval(&value) if value.is_a?(Proc)
107
+ value = value.instance_eval(&block) if block
108
+ self[key] = value
109
+ end
110
+ else
111
+ args.each do |field_name|
112
+ if context.respond_to?(field_name.to_sym)
113
+ value = context.instance_eval("self.#{field_name}")
114
+ elsif context.respond_to?(:[])
115
+ value = context[field_name]
116
+ else
117
+ value = nil
118
+ end
119
+ value = value.instance_eval(&block) if block
120
+ self[field_name] = value
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,33 @@
1
+ module Wonkavision
2
+ module Persistence
3
+ module MongoMapperAdapter
4
+
5
+ def self.included(model)
6
+ model.plugin Wonkavision::Persistence::MongoMapperAdapter
7
+ end
8
+
9
+ module ClassMethods
10
+ include Wonkavision::ActsAsOompaLoompa
11
+
12
+ def define_document_key(key_name,key_type,options={})
13
+ key(key_name, key_type, options) unless keys[key_name]
14
+ end
15
+
16
+ def update_activity(activity,event_data)
17
+ activity.assign(event_data)
18
+ :updated
19
+ end
20
+
21
+ def find_activity_instance(correlation_field_name,correlation_id)
22
+ self.send("find_or_create_by_#{correlation_field_name}",correlation_id)
23
+ end
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+ end
30
+
31
+ if defined?(::MongoMapper::Document)
32
+ MongoMapper::Document.append_inclusions(Wonkavision::Persistence::MongoMapperAdapter)
33
+ end
@@ -0,0 +1,30 @@
1
+ #This concept (and code) shamelessly stolen from MongoMapper
2
+ #(http://railstips.org/blog/archives/2010/02/21/mongomapper-07-plugins/)
3
+ #as so much of my Ruby code tends to be. I added a few little things and changed some
4
+ #names to avoid conflicts.
5
+ module Wonkavision
6
+ module Plugins
7
+ def wonkavision_plugins
8
+ @wonkavision_plugins ||= []
9
+ end
10
+
11
+ def has_wonkavision_plugin?(plugin)
12
+ wonkavision_plugins.detect{|p|p==plugin}
13
+ end
14
+
15
+ def ensure_wonkavision_plugin(plugin,option={})
16
+ use(plugin,options) unless has_wonkavision_plugin?(plugin)
17
+ end
18
+
19
+ def plug(mod,options={})
20
+ extend mod::ClassMethods if mod.const_defined?(:ClassMethods)
21
+ include mod::InstanceMethods if mod.const_defined?(:InstanceMethods)
22
+ extend mod::Fields if mod.const_defined?(:Fields)
23
+ include mod::Fields if mod.const_defined?(:Fields)
24
+ mod.configure(self,options) if mod.respond_to?(:configure)
25
+ wonkavision_plugins << mod
26
+ end
27
+ alias use plug
28
+
29
+ end
30
+ end
@@ -0,0 +1,79 @@
1
+ module Wonkavision
2
+ module Plugins
3
+ module BusinessActivity
4
+
5
+ def self.all
6
+ @@all ||= []
7
+ end
8
+
9
+ def self.configure(activity,options={})
10
+ activity.write_inheritable_attribute :business_activity_options, {}
11
+ activity.class_inheritable_reader :business_activity_options
12
+
13
+ activity.write_inheritable_attribute :correlation_ids, []
14
+ activity.class_inheritable_reader :correlation_ids
15
+
16
+ BusinessActivity.all << activity
17
+ end
18
+
19
+ module ClassMethods
20
+
21
+ def event(name,*args,&block)
22
+ handle(name,args) do |data,path|
23
+ activity = find_activity(data)
24
+ result = :ok
25
+ if (block_given?)
26
+ result = case block.arity
27
+ when 3 then yield activity,data,path
28
+ when 2 then yield activity,data
29
+ when 1 then yield activity
30
+ else yield
31
+ end
32
+ end
33
+ unless result == :handled
34
+ result = update_activity(activity,data) unless result == :updated
35
+ activity.save!
36
+ end
37
+ result
38
+ end
39
+ end
40
+
41
+ def correlate_by(*args)
42
+ return {:model=>correlation_id_field, :event=>event_correlation_id_key} if args.blank?
43
+ model_field,event_field = if args.length == 1 then
44
+ case args[0]
45
+ when Hash then [args[0][:model], args[0][:event] || args[0][:model]]
46
+ else [args[0],args[0]]
47
+ end
48
+ else
49
+ [args[0],args[1] || args[0]]
50
+ end
51
+
52
+ business_activity_options[:correlation_id_field] = model_field
53
+ business_activity_options[:event_correlation_id_key] = event_field
54
+
55
+ define_document_key correlation_id_field, String, :index=>true
56
+ correlation_ids << {:model=>model_field.to_s, :event=>event_field.to_s}
57
+ end
58
+
59
+ def find_activity(event_data)
60
+ correlation_id = event_data[event_correlation_id_key.to_s]
61
+ find_activity_instance(correlation_id_field,correlation_id)
62
+ end
63
+
64
+ end
65
+
66
+ module Fields
67
+
68
+ def correlation_id_field
69
+ business_activity_options[:correlation_id_field] ||= "id"
70
+ end
71
+
72
+ def event_correlation_id_key
73
+ business_activity_options[:event_correlation_id_key] || correlation_id_field
74
+ end
75
+
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,78 @@
1
+ module Wonkavision
2
+ module Plugins
3
+ module EventHandling
4
+
5
+ def self.configure(handler,options)
6
+ handler.write_inheritable_attribute :event_handler_options, {}
7
+ handler.class_inheritable_reader :event_handler_options
8
+
9
+ handler.write_inheritable_attribute :bindings, []
10
+ handler.class_inheritable_reader :bindings
11
+
12
+ handler.write_inheritable_attribute :maps, []
13
+ handler.class_inheritable_reader :maps
14
+ end
15
+
16
+ module ClassMethods
17
+ def options
18
+ event_handler_options
19
+ end
20
+
21
+ def event_path(event_name)
22
+ return event_name.to_s if Wonkavision.is_absolute_path(event_name) #don't mess with an absolute path
23
+ Wonkavision.join(event_namespace,event_name)
24
+ end
25
+
26
+ def event_namespace(namespace=nil)
27
+ return event_handler_options[:event_namespace] unless namespace
28
+ event_handler_options[:event_namespace] = namespace
29
+ end
30
+ alias :namespace :event_namespace
31
+
32
+ def map(condition = nil,&block)
33
+ maps << [condition,block]
34
+ end
35
+
36
+ def handle(name,*args,&block)
37
+ binding = Wonkavision::EventBinding.new(name,self,*args)
38
+ binding.subscribe_to_events do |event_data,event_path|
39
+ event_data = map_data(event_data,event_path)
40
+ if block_given?
41
+ case block.arity
42
+ when 2 then yield event_data,event_path
43
+ when 1 then yield event_data
44
+ else yield
45
+ end
46
+ end
47
+ end
48
+ bindings << binding
49
+ binding
50
+ end
51
+
52
+ private
53
+
54
+ def map_data(data,path)
55
+ maps.each do |map_def|
56
+ condition = map_def[0]
57
+ map_block = map_def[1]
58
+ return Wonkavision::MessageMapper.execute(map_block,data) if map?(condition,data,path)
59
+ end
60
+ data.is_a?(Hash) ? data.dup : data
61
+ end
62
+
63
+ def map?(condition,data,path)
64
+ return true unless condition && condition.to_s != 'all'
65
+ return path =~ condition if condition.is_a?(Regexp)
66
+ if (condition.is_a?(Proc))
67
+ return condition.call if condition.arity <= 0
68
+ return condition.call(path) if condition.arity == 1
69
+ return condition.call(path,data)
70
+ end
71
+ #default behavior
72
+ header.properties[:routing_key] == filter.to_s
73
+ end
74
+ end
75
+ #Code here
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,80 @@
1
+ module Wonkavision
2
+ module Plugins
3
+ module Timeline
4
+ def self.all
5
+ @@all ||= []
6
+ end
7
+
8
+ def self.configure(activity,options={})
9
+ activity.ensure_wonkavision_plugin(Wonkavision::Plugins::BusinessActivity,options)
10
+ activity.write_inheritable_attribute :timeline_milestones, []
11
+ activity.class_inheritable_reader :timeline_milestones
12
+
13
+ options = {
14
+ :timeline_field => "timeline",
15
+ :latest_milestone_field => "latest_milestone",
16
+ :event_time_key => "event_time"
17
+ }.merge(options)
18
+
19
+ activity.business_activity_options.merge!(options)
20
+
21
+ activity.define_document_key(activity.timeline_field,Hash,:default=>{})
22
+ activity.define_document_key(activity.latest_milestone_field, String, :default=>"awaiting_first_event")
23
+
24
+ Timeline.all << activity
25
+ end
26
+
27
+ module ClassMethods
28
+
29
+ def milestone(name,*args)
30
+ timeline_milestones << event(name,*args) do |activity,event_data,event_path|
31
+ event_time = extract_event_time(event_data,event_path)
32
+ prev_event_time = activity[timeline_field][name]
33
+ unless prev_event_time
34
+ activity[timeline_field][name] = event_time
35
+ #If the event being processed happened earlier than a previously
36
+ #recorded event, we don't want to overwrite state of the activity, as
37
+ #it is already more up to date than the incoming event.
38
+ latest_ms = activity[latest_milestone_field]
39
+ unless latest_ms &&
40
+ (last_event = activity[timeline_field][latest_ms]) &&
41
+ last_event > event_time
42
+ update_activity(activity,event_data)
43
+ activity[latest_milestone_field] = name
44
+ end
45
+ :updated
46
+ else
47
+ :handled #If there was a previous event time for this milestone, we will just skip this event
48
+ end
49
+ end
50
+ end
51
+
52
+ def convert_time(time)
53
+ if (time.is_a?(Hash)) && (time.keys.include?(:date) || time.keys.include?(:time))
54
+ time = "#{time[:date]} #{time[:time]}".strip
55
+ end
56
+ time ? time.to_time : nil
57
+ end
58
+
59
+ private
60
+ def extract_event_time(event_data,event_path)
61
+ convert_time(event_data.delete(event_time_key.to_s)) || Time.now.utc
62
+ end
63
+ end
64
+
65
+ module Fields
66
+ def timeline_field
67
+ business_activity_options[:timeline_field]
68
+ end
69
+
70
+ def latest_milestone_field
71
+ business_activity_options[:latest_milestone_field]
72
+ end
73
+
74
+ def event_time_key
75
+ business_activity_options[:event_time_key]
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end