aggro 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +8 -0
- data/.travis.yml +15 -0
- data/Gemfile +9 -0
- data/README.md +5 -1
- data/Rakefile +10 -0
- data/aggro.gemspec +8 -1
- data/lib/aggro.rb +191 -7
- data/lib/aggro/abstract_store.rb +12 -0
- data/lib/aggro/aggregate.rb +98 -0
- data/lib/aggro/aggregate_ref.rb +68 -6
- data/lib/aggro/attribute_dsl.rb +96 -0
- data/lib/aggro/binding_dsl.rb +45 -0
- data/lib/aggro/block_helper.rb +14 -0
- data/lib/aggro/channel.rb +37 -0
- data/lib/aggro/client.rb +12 -0
- data/lib/aggro/cluster_config.rb +57 -0
- data/lib/aggro/command.rb +16 -0
- data/lib/aggro/concurrent_actor.rb +26 -0
- data/lib/aggro/event_bus.rb +94 -0
- data/lib/aggro/event_dsl.rb +53 -0
- data/lib/aggro/event_proxy.rb +23 -0
- data/lib/aggro/event_serializer.rb +14 -0
- data/lib/aggro/file_store.rb +97 -0
- data/lib/aggro/file_store/reader.rb +21 -0
- data/lib/aggro/file_store/writer.rb +27 -0
- data/lib/aggro/handler/command.rb +60 -0
- data/lib/aggro/handler/create_aggregate.rb +42 -0
- data/lib/aggro/handler/get_events.rb +30 -0
- data/lib/aggro/handler/query.rb +60 -0
- data/lib/aggro/handler/start_saga.rb +56 -0
- data/lib/aggro/local_node.rb +28 -0
- data/lib/aggro/locator.rb +32 -0
- data/lib/aggro/message/ask.rb +16 -0
- data/lib/aggro/message/command.rb +36 -0
- data/lib/aggro/message/create_aggregate.rb +16 -0
- data/lib/aggro/message/endpoint.rb +16 -0
- data/lib/aggro/message/events.rb +24 -0
- data/lib/aggro/message/get_events.rb +16 -0
- data/lib/aggro/message/heartbeat.rb +16 -0
- data/lib/aggro/message/invalid_target.rb +20 -0
- data/lib/aggro/message/ok.rb +20 -0
- data/lib/aggro/message/publisher_endpoint_inquiry.rb +16 -0
- data/lib/aggro/message/query.rb +36 -0
- data/lib/aggro/message/result.rb +16 -0
- data/lib/aggro/message/start_saga.rb +28 -0
- data/lib/aggro/message/unhandled_operation.rb +20 -0
- data/lib/aggro/message/unknown_operation.rb +20 -0
- data/lib/aggro/message_parser.rb +10 -0
- data/lib/aggro/message_router.rb +26 -0
- data/lib/aggro/nanomsg_transport.rb +31 -0
- data/lib/aggro/nanomsg_transport/client.rb +35 -0
- data/lib/aggro/nanomsg_transport/connection.rb +98 -0
- data/lib/aggro/nanomsg_transport/publish.rb +17 -0
- data/lib/aggro/nanomsg_transport/publisher.rb +37 -0
- data/lib/aggro/nanomsg_transport/raw_reply.rb +18 -0
- data/lib/aggro/nanomsg_transport/raw_request.rb +18 -0
- data/lib/aggro/nanomsg_transport/reply.rb +17 -0
- data/lib/aggro/nanomsg_transport/request.rb +17 -0
- data/lib/aggro/nanomsg_transport/server.rb +84 -0
- data/lib/aggro/nanomsg_transport/socket_error.rb +20 -0
- data/lib/aggro/nanomsg_transport/subscribe.rb +27 -0
- data/lib/aggro/nanomsg_transport/subscriber.rb +82 -0
- data/lib/aggro/node.rb +29 -0
- data/lib/aggro/node_list.rb +39 -0
- data/lib/aggro/projection.rb +13 -0
- data/lib/aggro/query.rb +11 -0
- data/lib/aggro/saga.rb +94 -0
- data/lib/aggro/saga_runner.rb +87 -0
- data/lib/aggro/saga_runner/start_saga.rb +12 -0
- data/lib/aggro/saga_status.rb +29 -0
- data/lib/aggro/server.rb +88 -0
- data/lib/aggro/subscriber.rb +48 -0
- data/lib/aggro/subscription.rb +41 -0
- data/lib/aggro/transform/boolean.rb +16 -0
- data/lib/aggro/transform/email.rb +26 -0
- data/lib/aggro/transform/id.rb +34 -0
- data/lib/aggro/transform/integer.rb +22 -0
- data/lib/aggro/transform/money.rb +22 -0
- data/lib/aggro/transform/noop.rb +16 -0
- data/lib/aggro/transform/string.rb +16 -0
- data/lib/aggro/transform/time_interval.rb +24 -0
- data/lib/aggro/version.rb +1 -1
- data/spec/lib/aggro/abstract_store_spec.rb +15 -0
- data/spec/lib/aggro/aggregate_ref_spec.rb +63 -12
- data/spec/lib/aggro/aggregate_spec.rb +207 -0
- data/spec/lib/aggro/channel_spec.rb +87 -0
- data/spec/lib/aggro/client_spec.rb +26 -0
- data/spec/lib/aggro/cluster_config_spec.rb +33 -0
- data/spec/lib/aggro/command_spec.rb +52 -0
- data/spec/lib/aggro/concurrent_actor_spec.rb +44 -0
- data/spec/lib/aggro/event_bus_spec.rb +20 -0
- data/spec/lib/aggro/event_serializer_spec.rb +28 -0
- data/spec/lib/aggro/file_store/reader_spec.rb +32 -0
- data/spec/lib/aggro/file_store/writer_spec.rb +67 -0
- data/spec/lib/aggro/file_store_spec.rb +51 -0
- data/spec/lib/aggro/handler/command_spec.rb +78 -0
- data/spec/lib/aggro/handler/create_aggregate_spec.rb +64 -0
- data/spec/lib/aggro/handler/get_events_handler_spec.rb +45 -0
- data/spec/lib/aggro/handler/query_spec.rb +78 -0
- data/spec/lib/aggro/handler/start_saga_spec.rb +64 -0
- data/spec/lib/aggro/local_node_spec.rb +52 -0
- data/spec/lib/aggro/locator_spec.rb +61 -0
- data/spec/lib/aggro/message/ask_spec.rb +23 -0
- data/spec/lib/aggro/message/command_spec.rb +50 -0
- data/spec/lib/aggro/message/create_aggregate_spec.rb +28 -0
- data/spec/lib/aggro/message/endpoint_spec.rb +23 -0
- data/spec/lib/aggro/message/events_spec.rb +37 -0
- data/spec/lib/aggro/message/get_events_spec.rb +33 -0
- data/spec/lib/aggro/message/heartbeat_spec.rb +23 -0
- data/spec/lib/aggro/message/invalid_target_spec.rb +28 -0
- data/spec/lib/aggro/message/ok_spec.rb +27 -0
- data/spec/lib/aggro/message/publisher_endpoint_inquiry_spec.rb +23 -0
- data/spec/lib/aggro/message/query_spec.rb +50 -0
- data/spec/lib/aggro/message/start_saga_spec.rb +37 -0
- data/spec/lib/aggro/message/unhandled_operation_spec.rb +28 -0
- data/spec/lib/aggro/message/unknown_operation_spec.rb +28 -0
- data/spec/lib/aggro/message_parser_spec.rb +16 -0
- data/spec/lib/aggro/message_router_spec.rb +35 -0
- data/spec/lib/aggro/nanomsg_transport/socket_error_spec.rb +21 -0
- data/spec/lib/aggro/nanomsg_transport_spec.rb +37 -0
- data/spec/lib/aggro/node_list_spec.rb +38 -0
- data/spec/lib/aggro/node_spec.rb +44 -0
- data/spec/lib/aggro/projection_spec.rb +22 -0
- data/spec/lib/aggro/query_spec.rb +47 -0
- data/spec/lib/aggro/saga_runner_spec.rb +84 -0
- data/spec/lib/aggro/saga_spec.rb +126 -0
- data/spec/lib/aggro/saga_status_spec.rb +56 -0
- data/spec/lib/aggro/server_spec.rb +118 -0
- data/spec/lib/aggro/subscriber_spec.rb +59 -0
- data/spec/lib/aggro/subscription_spec.rb +50 -0
- data/spec/lib/aggro/transform/boolean_spec.rb +23 -0
- data/spec/lib/aggro/transform/email_spec.rb +13 -0
- data/spec/lib/aggro/transform/id_spec.rb +70 -0
- data/spec/lib/aggro/transform/integer_spec.rb +30 -0
- data/spec/lib/aggro/transform/money_spec.rb +34 -0
- data/spec/lib/aggro/transform/string_spec.rb +15 -0
- data/spec/lib/aggro/transform/time_interval_spec.rb +29 -0
- data/spec/lib/aggro_spec.rb +63 -19
- data/spec/spec_helper.rb +21 -2
- metadata +283 -3
@@ -0,0 +1,96 @@
|
|
1
|
+
module Aggro
|
2
|
+
# Public: Adds a DSL defining attributes and validations.
|
3
|
+
module AttributeDSL
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
include ActiveModel::Model
|
7
|
+
include ActiveModel::Validations::Callbacks
|
8
|
+
|
9
|
+
def initialize(attrs = {})
|
10
|
+
if Thread.current[:causation_id] && respond_to?(:causation_id=)
|
11
|
+
attrs.merge! causation_id: Thread.current[:causation_id]
|
12
|
+
end
|
13
|
+
|
14
|
+
if Thread.current[:correlation_id] && respond_to?(:correlation_id=)
|
15
|
+
attrs.merge! correlation_id: Thread.current[:correlation_id]
|
16
|
+
end
|
17
|
+
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
def attributes
|
22
|
+
self.class.attributes.keys.reduce({}) do |hash, name|
|
23
|
+
hash.merge name => send(name)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def serialized_attributes
|
28
|
+
self.class.attributes.reduce({}) do |hash, (name, transform)|
|
29
|
+
hash.merge name => transform.serialize(send(name))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class_methods do
|
34
|
+
def attributes
|
35
|
+
Aggro.class_attributes[name]
|
36
|
+
end
|
37
|
+
|
38
|
+
def attribute(name)
|
39
|
+
create_attrs name, Transform::NOOP
|
40
|
+
end
|
41
|
+
|
42
|
+
def boolean(name)
|
43
|
+
create_attrs name, Transform::Boolean
|
44
|
+
end
|
45
|
+
|
46
|
+
def email(name)
|
47
|
+
create_attrs name, Transform::Email
|
48
|
+
end
|
49
|
+
|
50
|
+
def generate_id(name)
|
51
|
+
create_attrs name, Transform::ID.new(generate: true)
|
52
|
+
end
|
53
|
+
|
54
|
+
def id(name)
|
55
|
+
create_attrs name, Transform::ID.new
|
56
|
+
end
|
57
|
+
|
58
|
+
def integer(name)
|
59
|
+
create_attrs name, Transform::Integer
|
60
|
+
end
|
61
|
+
|
62
|
+
def money(name)
|
63
|
+
require 'money'
|
64
|
+
require 'monetize'
|
65
|
+
|
66
|
+
create_attrs name, Transform::Money
|
67
|
+
rescue LoadError
|
68
|
+
puts '`money` and `monetize` gems must be present to use money type'
|
69
|
+
end
|
70
|
+
|
71
|
+
def string(name)
|
72
|
+
create_attrs name, Transform::String
|
73
|
+
end
|
74
|
+
|
75
|
+
def time_interval(name)
|
76
|
+
require 'time-interval'
|
77
|
+
|
78
|
+
create_attrs name, Transform::TimeInterval
|
79
|
+
rescue LoadError
|
80
|
+
puts '`time-interval` gem must be present to use time_interval type'
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def create_attrs(name, transformer)
|
86
|
+
attr_reader name
|
87
|
+
attributes[name] = transformer
|
88
|
+
|
89
|
+
define_method("#{name}=") do |value|
|
90
|
+
transformed = self.class.attributes[name].deserialize value
|
91
|
+
instance_variable_set "@#{name}", transformed
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Aggro
|
2
|
+
# Public: Adds a DSL creating domain event bindings.
|
3
|
+
module BindingDSL
|
4
|
+
def bind(ref, filters: default_filters, to: nil, &block)
|
5
|
+
if to
|
6
|
+
bindings << Aggro.event_bus.subscribe(ref.id, self, to, filters)
|
7
|
+
else
|
8
|
+
bind_block ref, filters, &block
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def bind_block(ref, filters, namespace = generate_namespace, &block)
|
15
|
+
new_methods = BlockHelper.method_definitions(&block)
|
16
|
+
event_methods[namespace] = Set.new(new_methods)
|
17
|
+
|
18
|
+
class_eval(&block)
|
19
|
+
move_methods_to_namespace(new_methods, namespace)
|
20
|
+
|
21
|
+
bindings << Aggro.event_bus.subscribe(ref.id, self, namespace, filters)
|
22
|
+
end
|
23
|
+
|
24
|
+
def bindings
|
25
|
+
@bindings ||= []
|
26
|
+
end
|
27
|
+
|
28
|
+
def default_filters
|
29
|
+
{}
|
30
|
+
end
|
31
|
+
|
32
|
+
def generate_namespace
|
33
|
+
[*('a'..'z')].sample(8).join
|
34
|
+
end
|
35
|
+
|
36
|
+
def move_methods_to_namespace(method_list, namespace)
|
37
|
+
class_eval do
|
38
|
+
method_list.each do |method|
|
39
|
+
alias_method "#{namespace}_#{method}", method
|
40
|
+
remove_method method
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Aggro
|
2
|
+
# Private: Helper fuction for common operations on blocks.
|
3
|
+
module BlockHelper
|
4
|
+
module_function
|
5
|
+
|
6
|
+
def method_definitions(&block)
|
7
|
+
test_class = Class.new(BasicObject)
|
8
|
+
starting_methods = test_class.instance_methods
|
9
|
+
test_class.class_eval(&block)
|
10
|
+
|
11
|
+
test_class.instance_methods - starting_methods
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Aggro
|
2
|
+
# Private: Provides an interface to communicate with an aggregate or saga.
|
3
|
+
# Only loads the target object when needed.
|
4
|
+
class Channel < Struct.new(:id, :type)
|
5
|
+
def forward_command(command)
|
6
|
+
target << command if handles_command?(command)
|
7
|
+
end
|
8
|
+
|
9
|
+
def handles_command?(command)
|
10
|
+
target_class.allows? command
|
11
|
+
end
|
12
|
+
|
13
|
+
def handles_query?(query)
|
14
|
+
target_class.responds_to? query
|
15
|
+
end
|
16
|
+
|
17
|
+
def run_query(query)
|
18
|
+
target.ask query if handles_query? query
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def target
|
24
|
+
@target ||= begin
|
25
|
+
ConcurrentActor.spawn!(
|
26
|
+
name: id,
|
27
|
+
args: [target_class.new(id)],
|
28
|
+
executor: Concurrent.configuration.global_task_pool
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def target_class
|
34
|
+
@target_class ||= ActiveSupport::Inflector.constantize type
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/aggro/client.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
module Aggro
|
2
|
+
# Public: Makes requests against a given endpoint returning parsed responses.
|
3
|
+
class Client
|
4
|
+
def initialize(endpoint)
|
5
|
+
@transport_client = Aggro.transport.client(endpoint)
|
6
|
+
end
|
7
|
+
|
8
|
+
def post(message)
|
9
|
+
MessageParser.parse @transport_client.post message
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Aggro
|
2
|
+
# Public: Stores the current cluster config. Persists to disk after changes.
|
3
|
+
class ClusterConfig
|
4
|
+
attr_reader :node_name
|
5
|
+
|
6
|
+
def initialize(path)
|
7
|
+
@path = path
|
8
|
+
|
9
|
+
if File.exist? path
|
10
|
+
load_config
|
11
|
+
else
|
12
|
+
initialize_config
|
13
|
+
persist_config
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_node(name, server)
|
18
|
+
@nodes = nodes.merge(name => server).freeze
|
19
|
+
persist_config
|
20
|
+
end
|
21
|
+
|
22
|
+
def nodes
|
23
|
+
@nodes ||= {}.freeze
|
24
|
+
end
|
25
|
+
|
26
|
+
def server_node?
|
27
|
+
@is_server_node == true
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def initialize_config
|
33
|
+
@node_name = SecureRandom.uuid
|
34
|
+
@is_server_node = false
|
35
|
+
end
|
36
|
+
|
37
|
+
def load_config
|
38
|
+
YAML.load_file(@path).each do |key, value|
|
39
|
+
instance_variable_set "@#{key}", value.freeze
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def persist_config
|
44
|
+
File.open @path, 'w' do |file|
|
45
|
+
file.write YAML.dump to_h
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_h
|
50
|
+
{
|
51
|
+
node_name: node_name,
|
52
|
+
nodes: nodes,
|
53
|
+
is_server_node: server_node?
|
54
|
+
}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Aggro
|
2
|
+
# Public: Mixin to turn a PORO into an Aggro command.
|
3
|
+
module Command
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
include AttributeDSL
|
6
|
+
|
7
|
+
included do
|
8
|
+
generate_id :causation_id
|
9
|
+
generate_id :correlation_id
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_details
|
13
|
+
{ name: model_name.name, args: serialized_attributes }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Aggro
|
2
|
+
# Private: Wraps a given target in an concurrent actor.
|
3
|
+
class ConcurrentActor < Concurrent::Actor::Context
|
4
|
+
def initialize(target)
|
5
|
+
@target = target
|
6
|
+
end
|
7
|
+
|
8
|
+
def on_message(message)
|
9
|
+
if command? message
|
10
|
+
@target.send :apply_command, message
|
11
|
+
elsif query? message
|
12
|
+
@target.send :run_query, message
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def command?(message)
|
19
|
+
message.class.included_modules.include? Command
|
20
|
+
end
|
21
|
+
|
22
|
+
def query?(message)
|
23
|
+
message.class.included_modules.include? Query
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Aggro
|
2
|
+
# Public: Publishes events to any subscribed listeners.
|
3
|
+
class EventBus
|
4
|
+
def initialize
|
5
|
+
@remote_publishers = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def publish(topic, event)
|
9
|
+
Aggro.server.publish Message::Events.new(topic, [event])
|
10
|
+
|
11
|
+
return unless subscriptions.key? topic
|
12
|
+
|
13
|
+
subscriptions[topic].each do |subscription|
|
14
|
+
subscription.handle_event event
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def subscribe(topic, subscriber, event_namespace = nil, filters = {})
|
19
|
+
subscription = Subscription.new(topic, subscriber, event_namespace,
|
20
|
+
filters, 0)
|
21
|
+
|
22
|
+
catchup_subscriber topic, subscription
|
23
|
+
|
24
|
+
subscriptions[topic] ||= []
|
25
|
+
subscriptions[topic] << subscription
|
26
|
+
|
27
|
+
subscribe_bus_to_publisher topic
|
28
|
+
|
29
|
+
subscription
|
30
|
+
end
|
31
|
+
|
32
|
+
def unsubscribe(topic, subscriber)
|
33
|
+
subscriptions[topic].delete subscriber
|
34
|
+
end
|
35
|
+
|
36
|
+
def shutdown
|
37
|
+
remote_publishers.values.each(&:stop)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
attr_reader :remote_publishers
|
43
|
+
|
44
|
+
def catchup_local(topic, subscription)
|
45
|
+
Aggro.store.read([topic]).first.events.each do |event|
|
46
|
+
subscription.handle_event event
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def catchup_remote(topic, subscription, node)
|
51
|
+
message = Message::GetEvents.new(Aggro.local_node.id, topic, 0)
|
52
|
+
response = node.client.post message
|
53
|
+
|
54
|
+
if response.is_a? Message::Events
|
55
|
+
response.events.each { |event| subscription.handle_event event }
|
56
|
+
else
|
57
|
+
fail 'Could not catchup subscriber'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def catchup_subscriber(topic, subscription)
|
62
|
+
node = Locator.new(topic).primary_node
|
63
|
+
|
64
|
+
if node.is_a? LocalNode
|
65
|
+
catchup_local(topic, subscription)
|
66
|
+
else
|
67
|
+
catchup_remote(topic, subscription, node)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def handle_events(topic, events)
|
72
|
+
subscriptions[topic].each do |subscription|
|
73
|
+
events.each { |event| subscription.handle_event event }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def subscribe_bus_to_publisher(topic)
|
78
|
+
node = Locator.new(topic).primary_node
|
79
|
+
|
80
|
+
return if node.is_a? LocalNode
|
81
|
+
|
82
|
+
publisher_endpoint = node.publisher_endpoint
|
83
|
+
remote_publishers[publisher_endpoint] ||= begin
|
84
|
+
Subscriber.new(publisher_endpoint, method(:handle_events)).tap(&:bind)
|
85
|
+
end
|
86
|
+
|
87
|
+
remote_publishers[publisher_endpoint].subscribe_to_topic topic
|
88
|
+
end
|
89
|
+
|
90
|
+
def subscriptions
|
91
|
+
@subscriptions ||= {}
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Aggro
|
2
|
+
# Public: Adds a DSL defining event handlers.
|
3
|
+
module EventDSL
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
def handles_event?(event_name, namespace = nil)
|
7
|
+
self.class.handles_event?(event_name, namespace) || \
|
8
|
+
namespace?(namespace) && event_methods[namespace].include?(event_name)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def event_methods
|
14
|
+
@event_methods ||= {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def namespace?(namespace)
|
18
|
+
!event_methods[namespace].nil?
|
19
|
+
end
|
20
|
+
|
21
|
+
class_methods do
|
22
|
+
def events(namespace = nil, &block)
|
23
|
+
new_methods = BlockHelper.method_definitions(&block)
|
24
|
+
|
25
|
+
event_methods[namespace] ||= Set.new
|
26
|
+
new_methods.each { |method| event_methods[namespace] << method }
|
27
|
+
|
28
|
+
class_eval(&block)
|
29
|
+
|
30
|
+
class_eval do
|
31
|
+
new_methods.each do |method|
|
32
|
+
alias_method "#{namespace}_#{method}", method
|
33
|
+
remove_method method
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def handles_event?(event_name, namespace = nil)
|
39
|
+
namespace?(namespace) && event_methods[namespace].include?(event_name)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def event_methods
|
45
|
+
@event_methods ||= {}
|
46
|
+
end
|
47
|
+
|
48
|
+
def namespace?(namespace)
|
49
|
+
!event_methods[namespace].nil?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|