wonkavision 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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