sumac 0.0.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.
- checksums.yaml +7 -0
- data/LICENSE +201 -0
- data/README.md +2 -0
- data/lib/core_extensions.rb +207 -0
- data/lib/sumac.rb +94 -0
- data/lib/sumac/adapter.rb +5 -0
- data/lib/sumac/adapter/closed.rb +7 -0
- data/lib/sumac/argument_error.rb +5 -0
- data/lib/sumac/call_dispatcher.rb +70 -0
- data/lib/sumac/call_processor.rb +93 -0
- data/lib/sumac/closed_error.rb +5 -0
- data/lib/sumac/closer.rb +53 -0
- data/lib/sumac/connection.rb +94 -0
- data/lib/sumac/exposed_object.rb +131 -0
- data/lib/sumac/exposed_object_child.rb +158 -0
- data/lib/sumac/forgoten.rb +5 -0
- data/lib/sumac/handshake.rb +48 -0
- data/lib/sumac/id_allocator.rb +99 -0
- data/lib/sumac/local_reference.rb +16 -0
- data/lib/sumac/local_references.rb +80 -0
- data/lib/sumac/message.rb +19 -0
- data/lib/sumac/message/exchange.rb +30 -0
- data/lib/sumac/message/exchange/base.rb +15 -0
- data/lib/sumac/message/exchange/call_request.rb +96 -0
- data/lib/sumac/message/exchange/call_response.rb +83 -0
- data/lib/sumac/message/exchange/compatibility_notification.rb +53 -0
- data/lib/sumac/message/exchange/forget_notification.rb +77 -0
- data/lib/sumac/message/exchange/id.rb +30 -0
- data/lib/sumac/message/exchange/initialization_notification.rb +52 -0
- data/lib/sumac/message/exchange/notification.rb +9 -0
- data/lib/sumac/message/exchange/shutdown_notification.rb +27 -0
- data/lib/sumac/message/object.rb +72 -0
- data/lib/sumac/message/object/array.rb +57 -0
- data/lib/sumac/message/object/base.rb +21 -0
- data/lib/sumac/message/object/boolean.rb +45 -0
- data/lib/sumac/message/object/exception.rb +66 -0
- data/lib/sumac/message/object/exposed.rb +79 -0
- data/lib/sumac/message/object/exposed_child.rb +86 -0
- data/lib/sumac/message/object/float.rb +45 -0
- data/lib/sumac/message/object/hash_table.rb +75 -0
- data/lib/sumac/message/object/integer.rb +45 -0
- data/lib/sumac/message/object/native_exception.rb +56 -0
- data/lib/sumac/message/object/null.rb +44 -0
- data/lib/sumac/message/object/string.rb +45 -0
- data/lib/sumac/message_error.rb +5 -0
- data/lib/sumac/messenger.rb +65 -0
- data/lib/sumac/native_error.rb +17 -0
- data/lib/sumac/no_method_error.rb +9 -0
- data/lib/sumac/reference.rb +68 -0
- data/lib/sumac/remote_entry.rb +42 -0
- data/lib/sumac/remote_object.rb +39 -0
- data/lib/sumac/remote_object_child.rb +38 -0
- data/lib/sumac/remote_reference.rb +16 -0
- data/lib/sumac/remote_references.rb +70 -0
- data/lib/sumac/scheduler.rb +56 -0
- data/lib/sumac/shutdown.rb +32 -0
- data/lib/sumac/stale_object_error.rb +5 -0
- data/lib/sumac/unexposable_object_error.rb +9 -0
- data/lib/sumac/worker_pool.rb +35 -0
- data/test/test_id_allocator.rb +25 -0
- metadata +145 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
class Sumac
|
|
2
|
+
class CallDispatcher
|
|
3
|
+
|
|
4
|
+
def initialize(connection)
|
|
5
|
+
raise "argument 'connection' must be a Connection" unless connection.is_a?(Connection)
|
|
6
|
+
@connection = connection
|
|
7
|
+
@pending_requests = {}
|
|
8
|
+
@id_allocator = IDAllocator.new
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def any_calls_pending?
|
|
12
|
+
@pending_requests.any?
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def kill_all
|
|
16
|
+
raise unless @connection.at?(:kill)
|
|
17
|
+
@pending_requests.each do |id, waiter|
|
|
18
|
+
@pending_requests.delete(id)
|
|
19
|
+
waiter.resume(nil)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def make_call(remote_object, method_name, arguments)
|
|
24
|
+
raise unless remote_object.is_a?(RemoteObject) || remote_object.is_a?(RemoteObjectChild)
|
|
25
|
+
raise ClosedError unless @connection.at?(:active)
|
|
26
|
+
id = @id_allocator.allocate
|
|
27
|
+
request = Message::Exchange::CallRequest.new(@connection)
|
|
28
|
+
request.id = id
|
|
29
|
+
@connection.local_references.start_transaction
|
|
30
|
+
@connection.remote_references.start_transaction
|
|
31
|
+
begin
|
|
32
|
+
request.exposed_object = remote_object
|
|
33
|
+
request.method_name = method_name
|
|
34
|
+
request.arguments = arguments
|
|
35
|
+
rescue StandardError => e # MessageError, StaleObjectError
|
|
36
|
+
@connection.local_references.rollback_transaction
|
|
37
|
+
@connection.remote_references.rollback_transaction
|
|
38
|
+
raise e
|
|
39
|
+
else
|
|
40
|
+
@connection.local_references.commit_transaction
|
|
41
|
+
@connection.remote_references.commit_transaction
|
|
42
|
+
end
|
|
43
|
+
@connection.messenger.send(request)
|
|
44
|
+
raise ClosedError if @connection.at?([:kill, :close])
|
|
45
|
+
waiter = QuackConcurrency::Waiter.new
|
|
46
|
+
@pending_requests[id] = waiter
|
|
47
|
+
@connection.mutex.unlock
|
|
48
|
+
response = waiter.wait
|
|
49
|
+
@connection.mutex.lock
|
|
50
|
+
@id_allocator.free(id)
|
|
51
|
+
@connection.closer.job_finished
|
|
52
|
+
raise ClosedError if response == nil
|
|
53
|
+
raise response.exception if response.exception
|
|
54
|
+
response.return_value
|
|
55
|
+
ensure
|
|
56
|
+
@id_allocator.free(id) if id && @id_allocator.allocated?(id)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def receive(exchange)
|
|
60
|
+
raise MessageError unless @connection.at?([:active, :initiate_shutdown, :shutdown])
|
|
61
|
+
raise MessageError unless exchange.is_a?(Message::Exchange::CallResponse)
|
|
62
|
+
waiter = @pending_requests[exchange.id]
|
|
63
|
+
@pending_requests.delete(exchange.id)
|
|
64
|
+
raise MessageError unless waiter
|
|
65
|
+
waiter.resume(exchange)
|
|
66
|
+
nil
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
class Sumac
|
|
2
|
+
class CallProcessor
|
|
3
|
+
include Emittable
|
|
4
|
+
|
|
5
|
+
def initialize(connection)
|
|
6
|
+
raise "argument 'connection' must be a Connection" unless connection.is_a?(Connection)
|
|
7
|
+
@connection = connection
|
|
8
|
+
@pending_calls = 0
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def any_calls_processing?
|
|
12
|
+
@pending_calls > 0
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def receive(exchange)
|
|
16
|
+
raise MessageError unless exchange.is_a?(Message::Exchange::CallRequest)
|
|
17
|
+
raise MessageError unless @connection.at?([:active, :initiate_shutdown])
|
|
18
|
+
raise MessageError unless exchange.exposed_object.is_a?(ExposedObject) || exchange.exposed_object.is_a?(ExposedObjectChild)
|
|
19
|
+
response = process(exchange)
|
|
20
|
+
@connection.messenger.send(response) if response
|
|
21
|
+
finished
|
|
22
|
+
nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def process(request)
|
|
28
|
+
@pending_calls += 1
|
|
29
|
+
response = Message::Exchange::CallResponse.new(@connection)
|
|
30
|
+
response.id = request.id
|
|
31
|
+
# validate_exposed_object_exist
|
|
32
|
+
begin
|
|
33
|
+
exposed_object = request.exposed_object
|
|
34
|
+
rescue MessageError
|
|
35
|
+
response.exception = MessageError.new
|
|
36
|
+
return response
|
|
37
|
+
end
|
|
38
|
+
# validate_method_exposed
|
|
39
|
+
unless request.exposed_object.__exposed_methods__.include?(request.method_name)
|
|
40
|
+
response.exception = NoMethodError.new
|
|
41
|
+
return response
|
|
42
|
+
end
|
|
43
|
+
# validate_arguments
|
|
44
|
+
@connection.local_references.start_transaction
|
|
45
|
+
@connection.remote_references.start_transaction
|
|
46
|
+
begin
|
|
47
|
+
arguments = request.arguments
|
|
48
|
+
rescue StandardError => e
|
|
49
|
+
response.exception = e
|
|
50
|
+
@connection.local_references.rollback_transaction
|
|
51
|
+
@connection.remote_references.rollback_transaction
|
|
52
|
+
return response
|
|
53
|
+
else
|
|
54
|
+
@connection.local_references.commit_transaction
|
|
55
|
+
@connection.remote_references.commit_transaction
|
|
56
|
+
end
|
|
57
|
+
# call method
|
|
58
|
+
@connection.mutex.unlock
|
|
59
|
+
begin
|
|
60
|
+
return_value = exposed_object.__send__(request.method_name, *arguments)
|
|
61
|
+
rescue StandardError => e
|
|
62
|
+
exception_raised = e
|
|
63
|
+
end
|
|
64
|
+
@connection.mutex.lock
|
|
65
|
+
return if @connection.at?(:kill)
|
|
66
|
+
if exception_raised
|
|
67
|
+
response.exception = exception_raised
|
|
68
|
+
return response
|
|
69
|
+
else
|
|
70
|
+
@connection.local_references.start_transaction
|
|
71
|
+
@connection.remote_references.start_transaction
|
|
72
|
+
begin
|
|
73
|
+
response.return_value = return_value
|
|
74
|
+
rescue StandardError => e # MessageError, StaleObjectError, UnexposableError
|
|
75
|
+
response.exception = e
|
|
76
|
+
@connection.local_references.rollback_transaction
|
|
77
|
+
@connection.remote_references.rollback_transaction
|
|
78
|
+
return response
|
|
79
|
+
else
|
|
80
|
+
@connection.local_references.commit_transaction
|
|
81
|
+
@connection.remote_references.commit_transaction
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
return response
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def finished
|
|
88
|
+
@pending_calls -= 1
|
|
89
|
+
@connection.closer.job_finished
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
end
|
|
93
|
+
end
|
data/lib/sumac/closer.rb
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
class Sumac
|
|
2
|
+
class Closer
|
|
3
|
+
|
|
4
|
+
def initialize(connection)
|
|
5
|
+
raise "argument 'connection' must be a Connection" unless connection.is_a?(Connection)
|
|
6
|
+
@connection = connection
|
|
7
|
+
@future = QuackConcurrency::Future.new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def job_finished
|
|
11
|
+
try_close if @connection.at?([:shutdown, :kill])
|
|
12
|
+
nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def try_close
|
|
16
|
+
@connection.to(:close) if can_close?
|
|
17
|
+
nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def close
|
|
21
|
+
@connection.mutex.synchronize do
|
|
22
|
+
case @connection.at.to_sym
|
|
23
|
+
when :initial, :compatibility_handshake, :initialization_handshake
|
|
24
|
+
@connection.to(:kill)
|
|
25
|
+
when :active
|
|
26
|
+
@connection.to(:initiate_shutdown)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
@future.get
|
|
30
|
+
@connection.scheduler.join
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def complete
|
|
35
|
+
@future.set
|
|
36
|
+
nil
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def join
|
|
40
|
+
@future.get
|
|
41
|
+
@connection.scheduler.join
|
|
42
|
+
nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def can_close?
|
|
48
|
+
!@connection.call_dispatcher.any_calls_pending? &&
|
|
49
|
+
!@connection.call_processor.any_calls_processing?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
class Sumac
|
|
2
|
+
class Connection
|
|
3
|
+
include QueuedStateMachine
|
|
4
|
+
|
|
5
|
+
state :initial, initial: true
|
|
6
|
+
state :compatibility_handshake
|
|
7
|
+
state :initialization_handshake
|
|
8
|
+
state :active
|
|
9
|
+
state :initiate_shutdown
|
|
10
|
+
state :shutdown
|
|
11
|
+
state :kill
|
|
12
|
+
state :close
|
|
13
|
+
|
|
14
|
+
transition from: :initial, to: :compatibility_handshake
|
|
15
|
+
transition from: :compatibility_handshake, to: [:initialization_handshake, :kill]
|
|
16
|
+
transition from: :initialization_handshake, to: [:active, :kill]
|
|
17
|
+
transition from: :active, to: [:initiate_shutdown, :shutdown, :kill]
|
|
18
|
+
transition from: :initiate_shutdown, to: [:shutdown, :kill]
|
|
19
|
+
transition from: :shutdown, to: [:close, :kill]
|
|
20
|
+
transition from: :kill, to: :close
|
|
21
|
+
|
|
22
|
+
on_transition(from: :initial, to: :compatibility_handshake) do
|
|
23
|
+
@handshake.send_compatibility_notification
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
on_transition(from: :compatibility_handshake, to: :initialization_handshake) do
|
|
27
|
+
@handshake.send_initialization_notification
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
on_transition(from: [:compatibility_handshake, :initialization_handshake], to: :kill) do
|
|
31
|
+
@local_references.detach
|
|
32
|
+
@remote_references.detach
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
on_transition(from: :active) do
|
|
36
|
+
@local_references.detach
|
|
37
|
+
@remote_references.detach
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
on_transition(from: :active, to: [:initiate_shutdown, :shutdown]) do
|
|
41
|
+
@shutdown.send_notification
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
on_transition(from: :active, to: [:initiate_shutdown, :shutdown, :kill]) do
|
|
45
|
+
@sumac.trigger(:shutdown)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
on_transition(to: [:shutdown, :kill]) do
|
|
49
|
+
@closer.try_close
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
on_transition(to: :kill) do
|
|
53
|
+
@call_dispatcher.kill_all
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
on_transition(to: [:kill, :close]) do
|
|
57
|
+
@remote_entry.cancel
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
on_transition(to: :close) do
|
|
61
|
+
@messenger.close
|
|
62
|
+
@local_references.destroy
|
|
63
|
+
@remote_references.destroy
|
|
64
|
+
@closer.complete
|
|
65
|
+
@sumac.trigger(:close)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
attr_reader :mutex, :sumac, :call_dispatcher, :call_processor,
|
|
69
|
+
:handshake, :shutdown, :local_references, :remote_references,
|
|
70
|
+
:local_entry, :messenger, :remote_entry, :scheduler,
|
|
71
|
+
:closer, :messenger_adapter
|
|
72
|
+
|
|
73
|
+
attr_accessor :remote_entry
|
|
74
|
+
|
|
75
|
+
def initialize(sumac, duck_types: , entry: , messenger: , workers: )
|
|
76
|
+
super()
|
|
77
|
+
@sumac = sumac
|
|
78
|
+
@local_entry = entry
|
|
79
|
+
@messenger_adapter = messenger
|
|
80
|
+
@remote_entry = RemoteEntry.new
|
|
81
|
+
@mutex = Mutex.new
|
|
82
|
+
@messenger = Messenger.new(self)
|
|
83
|
+
@call_dispatcher = CallDispatcher.new(self)
|
|
84
|
+
@call_processor = CallProcessor.new(self)
|
|
85
|
+
@handshake = Handshake.new(self)
|
|
86
|
+
@shutdown = Shutdown.new(self)
|
|
87
|
+
@local_references = LocalReferences.new(self)
|
|
88
|
+
@remote_references = RemoteReferences.new(self)
|
|
89
|
+
@scheduler = Scheduler.new(self, workers)
|
|
90
|
+
@closer = Closer.new(self)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
class Sumac
|
|
2
|
+
module ExposedObject
|
|
3
|
+
|
|
4
|
+
def self.included(base)
|
|
5
|
+
base.extend(IncludedClassMethods)
|
|
6
|
+
base.include(IncludedInstanceMethods)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.extended(base)
|
|
10
|
+
base.extend(ExtendedClassMethods)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
module IncludedClassMethods
|
|
15
|
+
|
|
16
|
+
def inherited(base)
|
|
17
|
+
base.instance_variable_set(:@__exposed_methods__, @__exposed_methods__.dup)
|
|
18
|
+
base.instance_variable_set(:@__child_accessor__, @__child_accessor__)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
attr_reader :__child_accessor__
|
|
22
|
+
|
|
23
|
+
def __exposed_methods__
|
|
24
|
+
@__exposed_methods__ ||= []
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def child_accessor(method_name = nil)
|
|
28
|
+
unless method_name.is_a?(Symbol) || method_name.is_a?(String)
|
|
29
|
+
raise ArgumentError, "'child_accessor' expects a method name as a string for symbol"
|
|
30
|
+
end
|
|
31
|
+
@__child_accessor__ = method_name.to_s
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def expose(*method_names)
|
|
35
|
+
raise ArgumentError, 'at least one argument expected' if method_names.empty?
|
|
36
|
+
unless method_names.each { |method_name| method_name.is_a?(Symbol) || method_name.is_a?(String) }
|
|
37
|
+
raise 'only symbols or strings expected'
|
|
38
|
+
end
|
|
39
|
+
@__exposed_methods__ ||= []
|
|
40
|
+
@__exposed_methods__.concat(method_names.map(&:to_s))
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
module IncludedInstanceMethods
|
|
47
|
+
|
|
48
|
+
def __child__(key)
|
|
49
|
+
raise 'child_accessor not defined' unless __child_accessor__
|
|
50
|
+
__send__(__child_accessor__, key)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def __child_accessor__
|
|
54
|
+
@__child_accessor__ || self.class.__child_accessor__
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def __exposed_methods__
|
|
58
|
+
@__exposed_methods__ ||= []
|
|
59
|
+
@__exposed_methods__ + self.class.__exposed_methods__
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def __native_id__
|
|
63
|
+
__id__
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def __sumac_exposed_object__
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def child_accessor(method_name = nil)
|
|
70
|
+
unless method_name.is_a?(Symbol) || method_name.is_a?(String)
|
|
71
|
+
raise ArgumentError, "'child_accessor' expects a method name as a string for symbol"
|
|
72
|
+
end
|
|
73
|
+
@__child_accessor__ = method_name.to_s
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def expose(*method_names)
|
|
77
|
+
raise ArgumentError, 'at least one argument expected' if method_names.empty?
|
|
78
|
+
unless method_names.each { |method_name| method_name.is_a?(Symbol) || method_name.is_a?(String) }
|
|
79
|
+
raise 'only symbols or strings expected'
|
|
80
|
+
end
|
|
81
|
+
@__exposed_methods__ ||= []
|
|
82
|
+
@__exposed_methods__.concat(method_names.map(&:to_s))
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
module ExtendedClassMethods
|
|
89
|
+
|
|
90
|
+
def inherited(base)
|
|
91
|
+
base.instance_variable_set(:@__exposed_methods__, @__exposed_methods__.dup)
|
|
92
|
+
base.instance_variable_set(:@__child_accessor__, @__child_accessor__)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def __child__(key)
|
|
96
|
+
raise 'child_accessor not defined' unless @__child_accessor__
|
|
97
|
+
__send__(@__child_accessor__, key)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def __exposed_methods__
|
|
101
|
+
@__exposed_methods__ ||= []
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def __native_id__
|
|
105
|
+
__id__
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def __sumac_exposed_object__
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def child_accessor(method_name = nil)
|
|
112
|
+
unless method_name.is_a?(Symbol) || method_name.is_a?(String)
|
|
113
|
+
raise ArgumentError, "'child_accessor' expects a method name as a string for symbol"
|
|
114
|
+
end
|
|
115
|
+
@__child_accessor__ = method_name.to_s
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def expose(*method_names)
|
|
119
|
+
raise ArgumentError, 'at least one argument expected' if method_names.empty?
|
|
120
|
+
unless method_names.each { |method_name| method_name.is_a?(Symbol) || method_name.is_a?(String) }
|
|
121
|
+
raise 'only symbols or strings expected'
|
|
122
|
+
end
|
|
123
|
+
@__exposed_methods__ ||= []
|
|
124
|
+
@__exposed_methods__.concat(method_names.map(&:to_s))
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
end
|
|
131
|
+
end
|