ably-rest 0.7.1 → 0.7.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. checksums.yaml +13 -5
  2. data/.gitmodules +1 -1
  3. data/.rspec +1 -0
  4. data/.travis.yml +7 -3
  5. data/SPEC.md +495 -419
  6. data/ably-rest.gemspec +19 -5
  7. data/lib/ably-rest.rb +9 -1
  8. data/lib/submodules/ably-ruby/.gitignore +6 -0
  9. data/lib/submodules/ably-ruby/.rspec +1 -0
  10. data/lib/submodules/ably-ruby/.ruby-version.old +1 -0
  11. data/lib/submodules/ably-ruby/.travis.yml +10 -0
  12. data/lib/submodules/ably-ruby/Gemfile +4 -0
  13. data/lib/submodules/ably-ruby/LICENSE.txt +22 -0
  14. data/lib/submodules/ably-ruby/README.md +122 -0
  15. data/lib/submodules/ably-ruby/Rakefile +34 -0
  16. data/lib/submodules/ably-ruby/SPEC.md +1794 -0
  17. data/lib/submodules/ably-ruby/ably.gemspec +36 -0
  18. data/lib/submodules/ably-ruby/lib/ably.rb +12 -0
  19. data/lib/submodules/ably-ruby/lib/ably/auth.rb +438 -0
  20. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +69 -0
  21. data/lib/submodules/ably-ruby/lib/ably/logger.rb +102 -0
  22. data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +37 -0
  23. data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +223 -0
  24. data/lib/submodules/ably-ruby/lib/ably/models/message.rb +132 -0
  25. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base.rb +108 -0
  26. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base64.rb +40 -0
  27. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/cipher.rb +83 -0
  28. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/json.rb +34 -0
  29. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/utf8.rb +26 -0
  30. data/lib/submodules/ably-ruby/lib/ably/models/nil_logger.rb +20 -0
  31. data/lib/submodules/ably-ruby/lib/ably/models/paginated_resource.rb +173 -0
  32. data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +147 -0
  33. data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +210 -0
  34. data/lib/submodules/ably-ruby/lib/ably/models/stat.rb +161 -0
  35. data/lib/submodules/ably-ruby/lib/ably/models/token.rb +74 -0
  36. data/lib/submodules/ably-ruby/lib/ably/modules/ably.rb +15 -0
  37. data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +62 -0
  38. data/lib/submodules/ably-ruby/lib/ably/modules/channels_collection.rb +69 -0
  39. data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +100 -0
  40. data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +69 -0
  41. data/lib/submodules/ably-ruby/lib/ably/modules/enum.rb +202 -0
  42. data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +128 -0
  43. data/lib/submodules/ably-ruby/lib/ably/modules/event_machine_helpers.rb +26 -0
  44. data/lib/submodules/ably-ruby/lib/ably/modules/http_helpers.rb +41 -0
  45. data/lib/submodules/ably-ruby/lib/ably/modules/message_pack.rb +14 -0
  46. data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +41 -0
  47. data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +153 -0
  48. data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +57 -0
  49. data/lib/submodules/ably-ruby/lib/ably/modules/statesman_monkey_patch.rb +33 -0
  50. data/lib/submodules/ably-ruby/lib/ably/modules/uses_state_machine.rb +74 -0
  51. data/lib/submodules/ably-ruby/lib/ably/realtime.rb +64 -0
  52. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +298 -0
  53. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +92 -0
  54. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +69 -0
  55. data/lib/submodules/ably-ruby/lib/ably/realtime/channels.rb +50 -0
  56. data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +184 -0
  57. data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +184 -0
  58. data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +70 -0
  59. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +445 -0
  60. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +368 -0
  61. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +91 -0
  62. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +188 -0
  63. data/lib/submodules/ably-ruby/lib/ably/realtime/models/nil_channel.rb +30 -0
  64. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +564 -0
  65. data/lib/submodules/ably-ruby/lib/ably/rest.rb +43 -0
  66. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +104 -0
  67. data/lib/submodules/ably-ruby/lib/ably/rest/channels.rb +44 -0
  68. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +396 -0
  69. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/encoder.rb +49 -0
  70. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +41 -0
  71. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/external_exceptions.rb +24 -0
  72. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +17 -0
  73. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/logger.rb +58 -0
  74. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_json.rb +27 -0
  75. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +27 -0
  76. data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +92 -0
  77. data/lib/submodules/ably-ruby/lib/ably/util/crypto.rb +105 -0
  78. data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +43 -0
  79. data/lib/submodules/ably-ruby/lib/ably/version.rb +3 -0
  80. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +154 -0
  81. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +558 -0
  82. data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +119 -0
  83. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +575 -0
  84. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +785 -0
  85. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +457 -0
  86. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +55 -0
  87. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +1001 -0
  88. data/lib/submodules/ably-ruby/spec/acceptance/realtime/stats_spec.rb +23 -0
  89. data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +27 -0
  90. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +564 -0
  91. data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +165 -0
  92. data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +134 -0
  93. data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +41 -0
  94. data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +273 -0
  95. data/lib/submodules/ably-ruby/spec/acceptance/rest/encoders_spec.rb +185 -0
  96. data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +247 -0
  97. data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +292 -0
  98. data/lib/submodules/ably-ruby/spec/acceptance/rest/stats_spec.rb +172 -0
  99. data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +15 -0
  100. data/lib/submodules/ably-ruby/spec/resources/crypto-data-128.json +56 -0
  101. data/lib/submodules/ably-ruby/spec/resources/crypto-data-256.json +56 -0
  102. data/lib/submodules/ably-ruby/spec/rspec_config.rb +57 -0
  103. data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +212 -0
  104. data/lib/submodules/ably-ruby/spec/shared/model_behaviour.rb +86 -0
  105. data/lib/submodules/ably-ruby/spec/shared/protocol_msgbus_behaviour.rb +36 -0
  106. data/lib/submodules/ably-ruby/spec/spec_helper.rb +20 -0
  107. data/lib/submodules/ably-ruby/spec/support/api_helper.rb +60 -0
  108. data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +104 -0
  109. data/lib/submodules/ably-ruby/spec/support/markdown_spec_formatter.rb +118 -0
  110. data/lib/submodules/ably-ruby/spec/support/private_api_formatter.rb +36 -0
  111. data/lib/submodules/ably-ruby/spec/support/protocol_helper.rb +32 -0
  112. data/lib/submodules/ably-ruby/spec/support/random_helper.rb +15 -0
  113. data/lib/submodules/ably-ruby/spec/support/rest_testapp_before_retry.rb +15 -0
  114. data/lib/submodules/ably-ruby/spec/support/test_app.rb +113 -0
  115. data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +68 -0
  116. data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +146 -0
  117. data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +18 -0
  118. data/lib/submodules/ably-ruby/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +349 -0
  119. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/base64_spec.rb +181 -0
  120. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/cipher_spec.rb +260 -0
  121. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/json_spec.rb +135 -0
  122. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/utf8_spec.rb +56 -0
  123. data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +389 -0
  124. data/lib/submodules/ably-ruby/spec/unit/models/paginated_resource_spec.rb +288 -0
  125. data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +386 -0
  126. data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +315 -0
  127. data/lib/submodules/ably-ruby/spec/unit/models/stat_spec.rb +113 -0
  128. data/lib/submodules/ably-ruby/spec/unit/models/token_spec.rb +86 -0
  129. data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +124 -0
  130. data/lib/submodules/ably-ruby/spec/unit/modules/conversions_spec.rb +72 -0
  131. data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +272 -0
  132. data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +184 -0
  133. data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +283 -0
  134. data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +206 -0
  135. data/lib/submodules/ably-ruby/spec/unit/realtime/channels_spec.rb +81 -0
  136. data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +30 -0
  137. data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +33 -0
  138. data/lib/submodules/ably-ruby/spec/unit/realtime/incoming_message_dispatcher_spec.rb +36 -0
  139. data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +111 -0
  140. data/lib/submodules/ably-ruby/spec/unit/realtime/realtime_spec.rb +9 -0
  141. data/lib/submodules/ably-ruby/spec/unit/realtime/websocket_transport_spec.rb +25 -0
  142. data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +109 -0
  143. data/lib/submodules/ably-ruby/spec/unit/rest/channels_spec.rb +79 -0
  144. data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +53 -0
  145. data/lib/submodules/ably-ruby/spec/unit/rest/rest_spec.rb +10 -0
  146. data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +87 -0
  147. data/lib/submodules/ably-ruby/spec/unit/util/pub_sub_spec.rb +86 -0
  148. metadata +182 -27
@@ -0,0 +1,128 @@
1
+ module Ably
2
+ module Modules
3
+ # EventEmitter provides methods to attach to public events and trigger events on any class instance
4
+ #
5
+ # EventEmitter are typically used for public interfaces, and as such, may be overriden in
6
+ # the classes to enforce `event` names match expected values.
7
+ #
8
+ # @example
9
+ # class Example
10
+ # include Modules::EventEmitter
11
+ # end
12
+ #
13
+ # event_emitter = Example.new
14
+ # event_emitter.on(:signal) { |name| puts "Signal #{name} received" }
15
+ # event_emitter.trigger :signal, "Test"
16
+ # #=> "Signal Test received"
17
+ #
18
+ module EventEmitter
19
+ module ClassMethods
20
+ attr_reader :event_emitter_coerce_proc
21
+
22
+ # Configure included EventEmitter
23
+ #
24
+ # @param [Hash] options the options for the {EventEmitter}
25
+ # @option options [Proc] :coerce_into A lambda/Proc that is used to coerce the event names for all events. This is useful to ensure the event names conform to a naming or type convention.
26
+ #
27
+ # @example
28
+ # configure_event_emitter coerce_into: Proc.new { |event| event.to_sym }
29
+ #
30
+ def configure_event_emitter(options = {})
31
+ @event_emitter_coerce_proc = options[:coerce_into]
32
+ end
33
+
34
+ # Ensure @event_emitter_coerce_proc option is passed down to any classes that inherit the class with callbacks
35
+ def inherited(subclass)
36
+ subclass.instance_variable_set('@event_emitter_coerce_proc', @event_emitter_coerce_proc)
37
+ super
38
+ end
39
+ end
40
+
41
+ # On receiving an event matching the event_name, call the provided block
42
+ #
43
+ # @param [Array<String>] event_names event name
44
+ #
45
+ # @return [void]
46
+ def on(*event_names, &block)
47
+ event_names.each do |event_name|
48
+ callbacks[callbacks_event_coerced(event_name)] << proc_for_block(block)
49
+ end
50
+ end
51
+
52
+ # On receiving an event maching the event_name, call the provided block only once and remove the registered callback
53
+ #
54
+ # @param [Array<String>] event_names event name
55
+ #
56
+ # @return [void]
57
+ def once(*event_names, &block)
58
+ event_names.each do |event_name|
59
+ callbacks[callbacks_event_coerced(event_name)] << proc_for_block(block, delete_once_run: true)
60
+ end
61
+ end
62
+
63
+ # Trigger an event with event_name that will in turn call all matching callbacks setup with `on`
64
+ def trigger(event_name, *args)
65
+ callbacks[callbacks_event_coerced(event_name)].
66
+ clone.
67
+ select do |proc_hash|
68
+ proc_hash[:trigger_proc].call(*args)
69
+ end.each do |callback|
70
+ callbacks[callbacks_event_coerced(event_name)].delete callback
71
+ end
72
+ end
73
+
74
+ # Remove all callbacks for event_name.
75
+ #
76
+ # If a block is provided, only callbacks matching that block signature will be removed.
77
+ # If block is not provided, all callbacks matching the event_name will be removed.
78
+ #
79
+ # @param [Array<String>] event_names event name
80
+ #
81
+ # @return [void]
82
+ def off(*event_names, &block)
83
+ keys = if event_names.empty?
84
+ callbacks.keys
85
+ else
86
+ event_names
87
+ end
88
+
89
+ keys.each do |event_name|
90
+ if block_given?
91
+ callbacks[callbacks_event_coerced(event_name)].delete_if { |proc_hash| proc_hash[:block] == block }
92
+ else
93
+ callbacks[callbacks_event_coerced(event_name)].clear
94
+ end
95
+ end
96
+ end
97
+
98
+ private
99
+ def self.included(klass)
100
+ klass.extend ClassMethods
101
+ end
102
+
103
+ # Create a Hash with a proc that calls the provided block and returns true if option :delete_once_run is set to true.
104
+ # #trigger automatically deletes any blocks that return true thus allowing a block to be run once
105
+ def proc_for_block(block, options = {})
106
+ {
107
+ trigger_proc: Proc.new do |*args|
108
+ block.call *args
109
+ true if options[:delete_once_run]
110
+ end,
111
+ block: block
112
+ }
113
+ end
114
+
115
+ def callbacks
116
+ @callbacks ||= Hash.new { |hash, key| hash[key] = [] }
117
+ end
118
+
119
+ def callbacks_event_coerced(event_name)
120
+ if self.class.event_emitter_coerce_proc
121
+ self.class.event_emitter_coerce_proc.call(event_name)
122
+ else
123
+ event_name
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,26 @@
1
+ require 'eventmachine'
2
+
3
+ module Ably::Modules
4
+ # EventMachineHelpers module provides common private methods to classes simplifying interaction with EventMachine
5
+ module EventMachineHelpers
6
+ private
7
+
8
+ # This method allows looped blocks to be run at the next EventMachine tick
9
+ # @example
10
+ # x = 0
11
+ # less_than_3 = -> { x < 3 }
12
+ # non_blocking_loop_while(less_than_3) do
13
+ # x += 1
14
+ # end
15
+ def non_blocking_loop_while(lambda_condition, &execution_block)
16
+ if lambda_condition.call
17
+ EventMachine.next_tick do
18
+ if lambda_condition.call # ensure condition is still met following #next_tick
19
+ yield
20
+ non_blocking_loop_while(lambda_condition, &execution_block)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,41 @@
1
+ require 'base64'
2
+
3
+ require 'ably/version'
4
+
5
+ require 'ably/rest/middleware/encoder'
6
+ require 'ably/rest/middleware/external_exceptions'
7
+ require 'ably/rest/middleware/fail_if_unsupported_mime_type'
8
+ require 'ably/rest/middleware/logger'
9
+ require 'ably/rest/middleware/parse_json'
10
+ require 'ably/rest/middleware/parse_message_pack'
11
+
12
+ module Ably::Modules
13
+ # HttpHelpers provides common private methods to classes to simplify HTTP interactions with Ably
14
+ module HttpHelpers
15
+ protected
16
+ def encode64(text)
17
+ Base64.encode64(text).gsub("\n", '')
18
+ end
19
+
20
+ def user_agent
21
+ "Ably Ruby client #{Ably::VERSION} (https://ably.io)"
22
+ end
23
+
24
+ def setup_outgoing_middleware(builder)
25
+ # Convert request params to "www-form-urlencoded"
26
+ builder.use Ably::Rest::Middleware::Encoder
27
+ end
28
+
29
+ def setup_incoming_middleware(builder, logger, options = {})
30
+ builder.use Ably::Rest::Middleware::Logger, logger
31
+
32
+ # Parse JSON / MsgPack response bodies. ParseJson must be first (default) parsing middleware
33
+ if options[:fail_if_unsupported_mime_type] == true
34
+ builder.use Ably::Rest::Middleware::FailIfUnsupportedMimeType
35
+ end
36
+
37
+ builder.use Ably::Rest::Middleware::ParseJson
38
+ builder.use Ably::Rest::Middleware::ParseMessagePack
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,14 @@
1
+ require 'msgpack'
2
+
3
+ module Ably::Modules
4
+ # MessagePack module adds #to_msgpack to the class on the assumption that the class
5
+ # supports the method #as_json
6
+ #
7
+ module MessagePack
8
+ # Generate a packed MsgPack version of this object based on the JSON representation.
9
+ # Keys thus use mixedCase syntax as expected by the Realtime API
10
+ def to_msgpack(*args)
11
+ as_json(*args).to_msgpack
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,41 @@
1
+ require 'base64'
2
+ require 'ably/modules/conversions'
3
+ require 'ably/modules/message_pack'
4
+
5
+ module Ably::Modules
6
+ # Common model functionality shared across many {Ably::Models}
7
+ module ModelCommon
8
+ include Conversions
9
+ include MessagePack
10
+
11
+ # Provide a normal Hash accessor to the underlying raw message object
12
+ #
13
+ # @return [Object]
14
+ def [](key)
15
+ hash[key]
16
+ end
17
+
18
+ def ==(other)
19
+ other.kind_of?(self.class) &&
20
+ hash == other.hash
21
+ end
22
+
23
+ # Return a JSON ready object from the underlying #hash using Ably naming conventions for keys
24
+ def as_json
25
+ hash.as_json.dup
26
+ end
27
+
28
+ # Stringify the JSON representation of this object from the underlying #hash
29
+ def to_json(*args)
30
+ as_json.to_json(*args)
31
+ end
32
+
33
+ private
34
+ def ensure_utf8_string_for(attribute, value)
35
+ if value
36
+ raise ArgumentError, "#{attribute} must be a String" unless value.kind_of?(String)
37
+ raise ArgumentError, "#{attribute} cannot use ASCII_8BIT encoding, please use UTF_8 encoding" unless value.encoding == Encoding::UTF_8
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,153 @@
1
+ module Ably::Modules
2
+ # StateEmitter module adds a set of generic state related methods to a class on the assumption that
3
+ # the instance variable @state is used exclusively, the {Enum} STATE is defined prior to inclusion of this
4
+ # module, and the class is an {EventEmitter}. It then emits state changes.
5
+ #
6
+ # It also ensures the EventEmitter is configured to retrict permitted events to the
7
+ # the available STATEs and :error.
8
+ #
9
+ # @example
10
+ # class Connection
11
+ # include Ably::Modules::EventEmitter
12
+ # extend Ably::Modules::Enum
13
+ # STATE = ruby_enum('STATE',
14
+ # :initialized,
15
+ # :connecting,
16
+ # :connected
17
+ # )
18
+ # include Ably::Modules::StateEmitter
19
+ # end
20
+ #
21
+ # connection = Connection.new
22
+ # connection.state = :connecting # emits :connecting event via EventEmitter, returns STATE.Connecting
23
+ # connection.state?(:connected) # => false
24
+ # connection.connecting? # => true
25
+ # connection.state # => STATE.Connecting
26
+ # connection.state = :invalid # raises an Exception as only a valid state can be defined
27
+ # connection.trigger :invalid # raises an Exception as only a valid state can be used for EventEmitter
28
+ # connection.change_state :connected # emits :connected event via EventEmitter, returns STATE.Connected
29
+ # connection.once_or_if(:connected) { puts 'block called once when state is connected or becomes connected' }
30
+ #
31
+ module StateEmitter
32
+ # Current state {Ably::Modules::Enum}
33
+ #
34
+ # @return [Symbol] state
35
+ def state
36
+ STATE(@state)
37
+ end
38
+
39
+ # Evaluates if check_state matches current state
40
+ #
41
+ # @return [Boolean]
42
+ def state?(check_state)
43
+ state == check_state
44
+ end
45
+
46
+ # Set the current state {Ably::Modules::Enum}
47
+ #
48
+ # @return [Symbol] new state
49
+ # @api private
50
+ def state=(new_state, *args)
51
+ if state != new_state
52
+ logger.debug("#{self.class}: StateEmitter changed from #{state} => #{new_state}") if respond_to?(:logger, true)
53
+ @state = STATE(new_state)
54
+ trigger @state, *args
55
+ end
56
+ end
57
+ alias_method :change_state, :state=
58
+
59
+ # If the current state matches the target_state argument the block is called immediately.
60
+ # Else the block is called once when the target_state is reached.
61
+ #
62
+ # If the option block :else is provided then if any state other than target_state is reached, the :else block is called,
63
+ # however only one of the blocks will ever be called
64
+ #
65
+ # @param [Symbol,Ably::Modules::Enum,Array] target_states a single state or array of states that once met, will fire the success block only once
66
+ # @param [Hash] options
67
+ # @option options [Proc] :else block called once the state has changed to anything but target_state
68
+ #
69
+ # @yield block is called if the state is matched immediately or once when the state is reached
70
+ #
71
+ # @return [void]
72
+ def once_or_if(target_states, options = {})
73
+ raise ArgumentError, 'Block required' unless block_given?
74
+
75
+ if Array(target_states).any? { |target_state| state == target_state }
76
+ yield
77
+ else
78
+ failure_block = options.fetch(:else, nil)
79
+ failure_wrapper = nil
80
+
81
+ success_wrapper = Proc.new do
82
+ yield
83
+ off &success_wrapper
84
+ off &failure_wrapper if failure_wrapper
85
+ end
86
+
87
+ failure_wrapper = proc do |*args|
88
+ failure_block.call *args
89
+ off &success_wrapper
90
+ off &failure_wrapper
91
+ end if failure_block
92
+
93
+ Array(target_states).each do |target_state|
94
+ once target_state, &success_wrapper
95
+
96
+ once_state_changed do |*args|
97
+ failure_wrapper.call *args unless state == target_state
98
+ end if failure_block
99
+ end
100
+ end
101
+ end
102
+
103
+ # Calls the block once when the state changes
104
+ #
105
+ # @yield block is called once the state changes
106
+ # @return [void]
107
+ #
108
+ # @api private
109
+ def once_state_changed(&block)
110
+ raise ArgumentError, 'Block required' unless block_given?
111
+
112
+ once_block = proc do |*args|
113
+ off *self.class::STATE.map, &once_block
114
+ yield *args
115
+ end
116
+
117
+ once *self.class::STATE.map, &once_block
118
+ end
119
+
120
+ private
121
+
122
+ # Returns an {EventMachine::Deferrable} and once the target state is reached, the
123
+ # success block if provided and {EventMachine::Deferrable#callback} is called.
124
+ # If the state changes to any other state, the {EventMachine::Deferrable#errback} is called.
125
+ #
126
+ def deferrable_for_state_change_to(target_state)
127
+ EventMachine::DefaultDeferrable.new.tap do |deferrable|
128
+ once_or_if(target_state, else: proc { |*args| deferrable.fail self, *args }) do
129
+ yield self if block_given?
130
+ deferrable.succeed self
131
+ end
132
+ end
133
+ end
134
+
135
+ def self.included(klass)
136
+ klass.configure_event_emitter coerce_into: Proc.new { |event|
137
+ if event == :error
138
+ :error
139
+ else
140
+ klass::STATE(event)
141
+ end
142
+ }
143
+
144
+ klass::STATE.each do |state_predicate|
145
+ klass.instance_eval do
146
+ define_method("#{state_predicate.to_sym}?") do
147
+ state?(state_predicate)
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,57 @@
1
+ require 'statesman'
2
+ require 'ably/modules/statesman_monkey_patch'
3
+
4
+ module Ably::Modules
5
+ # Module providing Statesman StateMachine functionality
6
+ #
7
+ # Expects method #logger to be defined
8
+ #
9
+ # @api private
10
+ module StateMachine
11
+ def self.included(klass)
12
+ klass.class_eval do
13
+ include Statesman::Machine
14
+ end
15
+ klass.extend Ably::Modules::StatesmanMonkeyPatch
16
+ klass.extend ClassMethods
17
+ end
18
+
19
+ # Alternative to Statesman's #transition_to that:
20
+ # * log state change failures to {Logger}
21
+ # * raise an exception on the {Ably::Realtime::Channel}
22
+ #
23
+ # @return [void]
24
+ def transition_state(state, *args)
25
+ unless result = transition_to(state, *args)
26
+ exception = exception_for_state_change_to(state)
27
+ object.trigger :error, exception
28
+ logger.fatal "#{self.class}: #{exception.message}"
29
+ end
30
+ result
31
+ end
32
+
33
+ # @return [Statesman History Object]
34
+ def previous_transition
35
+ history[-2]
36
+ end
37
+
38
+ # @return [Symbol]
39
+ def previous_state
40
+ previous_transition.to_state if previous_transition
41
+ end
42
+
43
+ # @return [Ably::Exceptions::StateChangeError]
44
+ def exception_for_state_change_to(state)
45
+ error_message = "#{self.class}: Unable to transition from #{current_state} => #{state}"
46
+ Ably::Exceptions::StateChangeError.new(error_message, nil, 80020)
47
+ end
48
+
49
+ module ClassMethods
50
+ private
51
+
52
+ def is_error_type?(error)
53
+ error.kind_of?(Ably::Models::ErrorInfo) || error.kind_of?(StandardError)
54
+ end
55
+ end
56
+ end
57
+ end