ably 0.2.0 → 0.6.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d814eac0ae04dc623743bbe5973e80a58662056d
4
- data.tar.gz: 95426e76baeaee6fbc297031b83cc128e5bed688
3
+ metadata.gz: ee3e7490507273d4da5f34286db1f6ee1e0bc968
4
+ data.tar.gz: a1558974a048679f9572bb607ce4ea00746364ba
5
5
  SHA512:
6
- metadata.gz: b660702de87dec06d7f982ea110d59310ba1f9a130be320bd2ec7a40f777b2e5452cfa4f97f6a08a0cf751646326f53196e7cf3a39c023ce55795583309ad4db
7
- data.tar.gz: 8088bb09de07b180385a4e817827ef2d867ccb2c6645f55c68888bfaf932df7f2a7c2ec7392b3aa1471a5045beda59d0f93fa5775171ca99b1a1083b3fca2037
6
+ metadata.gz: f8b0da46c04457a191111f3c58a004d8f12dbeee12ad9f47836a86c836132d99b7e4675220644050c8c7ab1342e71c84d1477f5dc1256d593f7e028bbbe94298
7
+ data.tar.gz: da2d88e2cba687467a1b5cd79259cf6ec78cb00de7f9ae22f26641b154197dc3dfded91be3adddceed1e0e48b79ac53055af3b23c6dec3444adf4b039c31e42a
@@ -1,10 +1,11 @@
1
1
  module Ably::Models
2
- # Wraps any Ably HTTP response that supports paging and automatically provides methdos to iterated through
2
+ # Wraps any Ably HTTP response that supports paging and automatically provides methods to iterate through
3
3
  # the array of resources using {#first_page}, {#next_page}, {#first_page?} and {#last_page?}
4
4
  #
5
5
  # Paging information is provided by Ably in the LINK HTTP headers
6
6
  class PaginatedResource
7
7
  include Enumerable
8
+ include Ably::Modules::AsyncWrapper
8
9
 
9
10
  # @param [Faraday::Response] http_response Initial HTTP response from an Ably request to a paged resource
10
11
  # @param [String] base_url Base URL for request that generated the http_response so that subsequent paged requests can be made
@@ -12,6 +13,8 @@ module Ably::Models
12
13
  # @param [Hash] options Options for this paged resource
13
14
  # @option options [Symbol,String] :coerce_into symbol or string representing class that should be used to create each item in the PaginatedResource
14
15
  #
16
+ # @yield [Object] block will be called for each resource object for the current page. This is a useful way to apply a transformation to any page resources after they are retrieved
17
+ #
15
18
  # @return [PaginatedResource]
16
19
  def initialize(http_response, base_url, client, options = {}, &each_block)
17
20
  @http_response = http_response
@@ -20,6 +23,7 @@ module Ably::Models
20
23
  @coerce_into = options[:coerce_into]
21
24
  @raw_body = http_response.body
22
25
  @each_block = each_block
26
+ @make_async = options.fetch(:async_blocking_operations, false)
23
27
 
24
28
  @body = if @coerce_into
25
29
  http_response.body.map do |item|
@@ -34,19 +38,27 @@ module Ably::Models
34
38
  end if block_given?
35
39
  end
36
40
 
37
- # Retrieve the first page of results
41
+ # Retrieve the first page of results.
42
+ # When used as part of the {Ably::Realtime} library, it will return a {EventMachine::Deferrable} object,
43
+ # and allows an optional success callback block to be provided.
38
44
  #
39
- # @return [PaginatedResource]
40
- def first_page
41
- PaginatedResource.new(client.get(pagination_url('first')), base_url, client, coerce_into: coerce_into, &each_block)
45
+ # @return [PaginatedResource,EventMachine::Deferrable]
46
+ def first_page(&success_callback)
47
+ async_wrap_if(make_async, success_callback) do
48
+ PaginatedResource.new(client.get(pagination_url('first')), base_url, client, pagination_options, &each_block)
49
+ end
42
50
  end
43
51
 
44
- # Retrieve the next page of results
52
+ # Retrieve the next page of results.
53
+ # When used as part of the {Ably::Realtime} library, it will return a {EventMachine::Deferrable} object,
54
+ # and allows an optional success callback block to be provided.
45
55
  #
46
- # @return [PaginatedResource]
47
- def next_page
48
- raise Ably::Exceptions::InvalidPageError, 'There are no more pages' if supports_pagination? && last_page?
49
- PaginatedResource.new(client.get(pagination_url('next')), base_url, client, coerce_into: coerce_into, &each_block)
56
+ # @return [PaginatedResource,EventMachine::Deferrable]
57
+ def next_page(&success_callback)
58
+ async_wrap_if(make_async, success_callback) do
59
+ raise Ably::Exceptions::InvalidPageError, 'There are no more pages' if supports_pagination? && last_page?
60
+ PaginatedResource.new(client.get(pagination_url('next')), base_url, client, pagination_options, &each_block)
61
+ end
50
62
  end
51
63
 
52
64
  # True if this is the last page in the paged resource set
@@ -84,7 +96,7 @@ module Ably::Models
84
96
  alias_method :count, :length
85
97
  alias_method :size, :length
86
98
 
87
- # Method ensuring this {PaginatedResource} is {http://ruby-doc.org/core-2.1.3/Enumerable.html Enumerable}
99
+ # Method to allow {PaginatedResource} to be {http://ruby-doc.org/core-2.1.3/Enumerable.html Enumerable}
88
100
  def each(&block)
89
101
  body.each do |item|
90
102
  if block_given?
@@ -95,7 +107,7 @@ module Ably::Models
95
107
  end
96
108
  end
97
109
 
98
- # Last item in this page
110
+ # First item in this page
99
111
  def first
100
112
  body.first
101
113
  end
@@ -118,7 +130,7 @@ module Ably::Models
118
130
  end
119
131
 
120
132
  private
121
- attr_reader :body, :http_response, :base_url, :client, :coerce_into, :raw_body, :each_block
133
+ attr_reader :body, :http_response, :base_url, :client, :coerce_into, :raw_body, :each_block, :make_async
122
134
 
123
135
  def pagination_headers
124
136
  link_regex = %r{<(?<url>[^>]+)>; rel="(?<rel>[^"]+)"}
@@ -146,5 +158,20 @@ module Ably::Models
146
158
  pagination_header[id]
147
159
  end
148
160
  end
161
+
162
+ def pagination_options
163
+ {
164
+ coerce_into: coerce_into,
165
+ async_blocking_operations: make_async
166
+ }
167
+ end
168
+
169
+ def async_wrap_if(is_realtime, success_callback, &operation)
170
+ if is_realtime
171
+ async_wrap success_callback, &operation
172
+ else
173
+ yield
174
+ end
175
+ end
149
176
  end
150
177
  end
@@ -0,0 +1,60 @@
1
+ module Ably::Modules
2
+ # Provides methods to convert synchronous operations into async operations through the use of
3
+ # {EventMachine#defer http://www.rubydoc.info/github/eventmachine/eventmachine/EventMachine#defer-class_method}.
4
+ # The async_wrap method can only be called from within an EventMachine reactor, and must be thread safe.
5
+ #
6
+ # @note using this AsyncWrapper should only be used for methods that are used less frequently and typically
7
+ # not run with levels of concurrency due to the limited number of threads available to EventMachine by default.
8
+ #
9
+ # @example
10
+ # class BlockingOperation
11
+ # include Aby::Modules::AsyncWrapper
12
+ #
13
+ # def operation(&success_callback)
14
+ # async_wrap(success_callback) do
15
+ # sleep 1
16
+ # 'slept'
17
+ # end
18
+ # end
19
+ # end
20
+ #
21
+ # blocking_object = BlockingOperation.new
22
+ # deferrable = blocking_object.operation do |result|
23
+ # puts "Done with result: #{result}"
24
+ # end
25
+ # puts "Starting"
26
+ #
27
+ # # => 'Starting'
28
+ # # => 'Done with result: slept'
29
+ #
30
+ module AsyncWrapper
31
+ private
32
+
33
+ # Will yield the provided block in a new thread and return an {EventMachine::Deferrable http://www.rubydoc.info/github/eventmachine/eventmachine/EventMachine/Deferrable}
34
+ #
35
+ # @yield [Object] operation block that is run in a thread
36
+ # @return [EventMachine::Deferrable]
37
+ #
38
+ def async_wrap(success_callback = nil, &operation)
39
+ raise ArgumentError, "Operation block is missing" unless block_given?
40
+
41
+ EventMachine::DefaultDeferrable.new.tap do |deferrable|
42
+ deferrable.callback &success_callback if success_callback
43
+
44
+ operation_with_exception_handling = proc do
45
+ begin
46
+ yield
47
+ rescue StandardError => e
48
+ deferrable.fail e
49
+ end
50
+ end
51
+
52
+ complete_callback = proc do |result|
53
+ deferrable.succeed result
54
+ end
55
+
56
+ EventMachine.defer operation_with_exception_handling, complete_callback
57
+ end
58
+ end
59
+ end
60
+ end
@@ -36,6 +36,7 @@ module Ably
36
36
  include Ably::Modules::Conversions
37
37
  include Ably::Modules::EventEmitter
38
38
  include Ably::Modules::EventMachineHelpers
39
+ include Ably::Modules::AsyncWrapper
39
40
  extend Ably::Modules::Enum
40
41
 
41
42
  STATE = ruby_enum('STATE',
@@ -102,6 +103,8 @@ module Ably
102
103
  # @param name [String] The event name of the message to subscribe to if provided. Defaults to all events.
103
104
  # @yield [Ably::Models::Message] For each message received, the block is called
104
105
  #
106
+ # @return [void]
107
+ #
105
108
  def subscribe(name = :all, &blk)
106
109
  attach unless attached? || attaching?
107
110
  subscriptions[message_name_key(name)] << blk
@@ -112,6 +115,8 @@ module Ably
112
115
  #
113
116
  # @param name [String] The event name of the message to subscribe to if provided. Defaults to all events.
114
117
  #
118
+ # @return [void]
119
+ #
115
120
  def unsubscribe(name = :all, &blk)
116
121
  if message_name_key(name) == :all
117
122
  subscriptions.keys
@@ -174,8 +179,14 @@ module Ably
174
179
  #
175
180
  # @param (see Ably::Rest::Channel#history)
176
181
  # @option options (see Ably::Rest::Channel#history)
177
- def history(options = {})
178
- rest_channel.history(options)
182
+ #
183
+ # @yield [Ably::Models::PaginatedResource<Ably::Models::Message>] An Array of {Ably::Models::Message} objects that supports paging (#next_page, #first_page)
184
+ #
185
+ # @return [EventMachine::Deferrable]
186
+ def history(options = {}, &callback)
187
+ async_wrap(callback) do
188
+ rest_channel.history(options.merge(async_blocking_operations: true))
189
+ end
179
190
  end
180
191
 
181
192
  # @!attribute [r] __incoming_msgbus__
@@ -23,6 +23,7 @@ module Ably
23
23
  # @!attribute [r] protocol_binary?
24
24
  # (see Ably::Rest::Client#protocol_binary?)
25
25
  class Client
26
+ include Ably::Modules::AsyncWrapper
26
27
  extend Forwardable
27
28
 
28
29
  DOMAIN = 'realtime.ably.io'
@@ -66,20 +67,32 @@ module Ably
66
67
  # Return a {Ably::Realtime::Channel Realtime Channel} for the given name
67
68
  #
68
69
  # @param (see Ably::Realtime::Channels#get)
69
- #
70
70
  # @return (see Ably::Realtime::Channels#get)
71
+ #
71
72
  def channel(name, channel_options = {})
72
73
  channels.get(name, channel_options)
73
74
  end
74
75
 
75
- # (see Ably::Rest::Client#time)
76
- def time
77
- rest_client.time
76
+ # Retrieve the Ably service time
77
+ #
78
+ # @yield [Time] The time as reported by the Ably service
79
+ # @return [EventMachine::Deferrable]
80
+ #
81
+ def time(&success_callback)
82
+ async_wrap(success_callback) do
83
+ rest_client.time
84
+ end
78
85
  end
79
86
 
80
- # (see Ably::Rest::Client#stats)
81
- def stats(params = {})
82
- rest_client.stats(params)
87
+ # Retrieve the stats for the application
88
+ #
89
+ # @yield [Array] An Array of hashes representing the stats
90
+ # @return [EventMachine::Deferrable]
91
+ #
92
+ def stats(params = {}, &success_callback)
93
+ async_wrap(success_callback) do
94
+ rest_client.stats(params)
95
+ end
83
96
  end
84
97
 
85
98
  # (see Ably::Realtime::Connection#close)
@@ -103,7 +116,7 @@ module Ably
103
116
  end
104
117
 
105
118
  # @!attribute [r] custom_socket_host
106
- # @return [String,nil] Returns the custom socket host that is being used if it was provided with the option :ws_host when the {Client} was created
119
+ # @return [String,NilClass] Returns the custom socket host that is being used if it was provided with the option :ws_host when the {Client} was created
107
120
  def custom_socket_host
108
121
  @custom_socket_host
109
122
  end
@@ -2,6 +2,7 @@ module Ably::Realtime
2
2
  # Presence provides access to presence operations and state for the associated Channel
3
3
  class Presence
4
4
  include Ably::Modules::EventEmitter
5
+ include Ably::Modules::AsyncWrapper
5
6
  extend Ably::Modules::Enum
6
7
 
7
8
  STATE = ruby_enum('STATE',
@@ -14,12 +15,15 @@ module Ably::Realtime
14
15
  )
15
16
  include Ably::Modules::StateEmitter
16
17
 
17
- # {Ably::Realtime::Channel} this Presence object is assoicated with
18
+ # {Ably::Realtime::Channel} this Presence object is associated with
19
+ # @return {Ably::Realtime::Channel}
18
20
  attr_reader :channel
19
21
 
20
22
  # A unique member identifier for this channel client, disambiguating situations where a given
21
23
  # client_id is present on multiple connections simultaneously.
22
- # TODO: This does not work at present as no ACK is sent from the server with a memberId
24
+ #
25
+ # @note TODO: This does not work at present as no ACK is sent from the server with a memberId
26
+ # @return {String}
23
27
  attr_reader :member_id
24
28
 
25
29
  def initialize(channel)
@@ -36,13 +40,16 @@ module Ably::Realtime
36
40
 
37
41
  # Enter this client into this channel. This client will be added to the presence set
38
42
  # and presence subscribers will see an enter message for this client.
43
+ #
39
44
  # @param [Hash,String] options an options Hash to specify client data and/or client ID, or a String with the client data
40
45
  # @option options [String] :data optional data (eg a status message) for this member
41
46
  # @option options [String] :client_id the optional id of the client.
42
47
  # This option is provided to support connections from server instances that act on behalf of
43
48
  # multiple client_ids. In order to be able to enter the channel with this method, the client
44
49
  # library must have been instanced either with a key, or with a token bound to the wildcard clientId.
50
+ #
45
51
  # @yield [Ably::Realtime::Presence] On success, will call the block with the {Ably::Realtime::Presence}
52
+ #
46
53
  # @return [Ably::Models::PresenceMessage] Deferrable {Ably::Models::PresenceMessage} that supports both success (callback) and failure (errback) callbacks
47
54
  #
48
55
  def enter(options = {}, &blk)
@@ -71,6 +78,7 @@ module Ably::Realtime
71
78
 
72
79
  # Leave this client from this channel. This client will be removed from the presence
73
80
  # set and presence subscribers will see a leave message for this client.
81
+ #
74
82
  # @param (see Presence#enter)
75
83
  # @yield (see Presence#enter)
76
84
  # @return (see Presence#enter)
@@ -101,6 +109,7 @@ module Ably::Realtime
101
109
  # Update the presence data for this client. If the client is not already a member of
102
110
  # the presence set it will be added, and presence subscribers will see an enter or
103
111
  # update message for this client.
112
+ #
104
113
  # @param (see Presence#enter)
105
114
  # @yield (see Presence#enter)
106
115
  # @return (see Presence#enter)
@@ -120,9 +129,15 @@ module Ably::Realtime
120
129
 
121
130
  # Get the presence state for this Channel.
122
131
  # Optionally get a member's {Ably::Models::PresenceMessage} state by member_id
132
+ #
123
133
  # @return [Array<Ably::Models::PresenceMessage>, Ably::Models::PresenceMessage] members on the channel
124
- def get()
125
- members.map { |key, presence| presence }
134
+ #
135
+ def get(member_id = nil)
136
+ if member_id
137
+ members.find { |key, presence| presence.member_id == member_id }
138
+ else
139
+ members.map { |key, presence| presence }
140
+ end
126
141
  end
127
142
 
128
143
  # Subscribe to presence events on the associated Channel.
@@ -131,6 +146,8 @@ module Ably::Realtime
131
146
  # @param action [Ably::Models::PresenceMessage::ACTION] Optional, the state change action to subscribe to. Defaults to all presence actions
132
147
  # @yield [Ably::Models::PresenceMessage] For each presence state change event, the block is called
133
148
  #
149
+ # @return [void]
150
+ #
134
151
  def subscribe(action = :all, &blk)
135
152
  ensure_channel_attached do
136
153
  subscriptions[message_action_key(action)] << blk
@@ -142,6 +159,8 @@ module Ably::Realtime
142
159
  #
143
160
  # @param action [Ably::Models::PresenceMessage::ACTION] Optional, the state change action to subscribe to. Defaults to all presence actions
144
161
  #
162
+ # @return [void]
163
+ #
145
164
  def unsubscribe(action = :all, &blk)
146
165
  if message_action_key(action) == :all
147
166
  subscriptions.keys
@@ -158,8 +177,15 @@ module Ably::Realtime
158
177
  #
159
178
  # @param (see Ably::Rest::Presence#history)
160
179
  # @option options (see Ably::Rest::Presence#history)
161
- def history(options = {})
162
- rest_presence.history(options)
180
+ #
181
+ # @yield [Ably::Models::PaginatedResource<Ably::Models::PresenceMessage>] An Array of {Ably::Models::PresenceMessage} objects that supports paging (#next_page, #first_page)
182
+ #
183
+ # @return [EventMachine::Deferrable]
184
+ #
185
+ def history(options = {}, &callback)
186
+ async_wrap(callback) do
187
+ rest_presence.history(options.merge(async_blocking_operations: true))
188
+ end
163
189
  end
164
190
 
165
191
  # @!attribute [r] __incoming_msgbus__
@@ -57,16 +57,22 @@ module Ably
57
57
  # @option options [Integer] :limit Maximum number of messages to retrieve up to 10,000
58
58
  # @option options [Symbol] :by `:message`, `:bundle` or `:hour`. Defaults to `:message`
59
59
  #
60
- # @return [Ably::Models::PaginatedResource<Ably::Models::Message>] An Array of hashes representing the message history that supports paging (next, first)
60
+ # @return [Ably::Models::PaginatedResource<Ably::Models::Message>] An Array of {Ably::Models::Message} objects that supports paging (#next_page, #first_page)
61
61
  def history(options = {})
62
62
  url = "#{base_path}/messages"
63
+ options = options.dup
63
64
 
64
65
  merge_options = { live: true } # TODO: Remove live param as all history should be live
65
66
  [:start, :end].each { |option| merge_options[option] = as_since_epoch(options[option]) if options.has_key?(option) }
66
67
 
68
+ paginated_options = {
69
+ coerce_into: 'Ably::Models::Message',
70
+ async_blocking_operations: options.delete(:async_blocking_operations),
71
+ }
72
+
67
73
  response = client.get(url, options.merge(merge_options))
68
74
 
69
- Ably::Models::PaginatedResource.new(response, url, client, coerce_into: 'Ably::Models::Message') do |message|
75
+ Ably::Models::PaginatedResource.new(response, url, client, paginated_options) do |message|
70
76
  message.tap do |message|
71
77
  message.decode self
72
78
  end
@@ -3,7 +3,13 @@ module Ably
3
3
  class Presence
4
4
  include Ably::Modules::Conversions
5
5
 
6
- attr_reader :client, :channel
6
+ # {Ably::Rest::Client} for this Presence object
7
+ # @return {Ably::Rest::Client}
8
+ attr_reader :client
9
+
10
+ # {Ably::Rest::Channel} this Presence object is associated with
11
+ # @return {Ably::Rest::Channel}
12
+ attr_reader :channel
7
13
 
8
14
  # Initialize a new Presence object
9
15
  #
@@ -16,11 +22,25 @@ module Ably
16
22
 
17
23
  # Obtain the set of members currently present for a channel
18
24
  #
19
- # @return [Ably::Models::PaginatedResource<Ably::Models::PresenceMessage>] An Array of presence-message Hash objects that supports paging (next, first)
25
+ # @param [Hash] options the options for the set of members present
26
+ # @option options [Integer,Time] :start Time or millisecond since epoch
27
+ # @option options [Integer,Time] :end Time or millisecond since epoch
28
+ # @option options [Symbol] :direction `:forwards` or `:backwards`
29
+ # @option options [Integer] :limit Maximum number of members to retrieve up to 10,000
30
+ #
31
+ # @return [Ably::Models::PaginatedResource<Ably::Models::PresenceMessage>] An Array of {Ably::Models::PresenceMessage} objects that supports paging (#next_page, #first_page)
20
32
  #
21
33
  def get(options = {})
34
+ options = options.dup
35
+
36
+ paginated_options = {
37
+ coerce_into: 'Ably::Models::PresenceMessage',
38
+ async_blocking_operations: options.delete(:async_blocking_operations),
39
+ }
40
+
22
41
  response = client.get(base_path, options)
23
- Ably::Models::PaginatedResource.new(response, base_path, client, coerce_into: 'Ably::Models::PresenceMessage') do |presence_message|
42
+
43
+ Ably::Models::PaginatedResource.new(response, base_path, client, paginated_options) do |presence_message|
24
44
  presence_message.tap do |message|
25
45
  message.decode self.channel
26
46
  end
@@ -35,17 +55,23 @@ module Ably
35
55
  # @option options [Symbol] :direction `:forwards` or `:backwards`
36
56
  # @option options [Integer] :limit Maximum number of presence messages to retrieve up to 10,000
37
57
  #
38
- # @return [Ably::Models::PaginatedResource<Ably::Models::PresenceMessage>] An Array of presence-message Hash objects that supports paging (next, first)
58
+ # @return [Ably::Models::PaginatedResource<Ably::Models::PresenceMessage>] An Array of {Ably::Models::PresenceMessage} objects that supports paging (#next_page, #first_page)
39
59
  #
40
60
  def history(options = {})
41
61
  url = "#{base_path}/history"
62
+ options = options.dup
42
63
 
43
64
  merge_options = { live: true } # TODO: Remove live param as all history should be live
44
65
  [:start, :end].each { |option| merge_options[option] = as_since_epoch(options[option]) if options.has_key?(option) }
45
66
 
67
+ paginated_options = {
68
+ coerce_into: 'Ably::Models::PresenceMessage',
69
+ async_blocking_operations: options.delete(:async_blocking_operations),
70
+ }
71
+
46
72
  response = client.get(url, options.merge(merge_options))
47
73
 
48
- Ably::Models::PaginatedResource.new(response, url, client, coerce_into: 'Ably::Models::PresenceMessage') do |presence_message|
74
+ Ably::Models::PaginatedResource.new(response, url, client, paginated_options) do |presence_message|
49
75
  presence_message.tap do |message|
50
76
  message.decode self.channel
51
77
  end
@@ -1,3 +1,3 @@
1
1
  module Ably
2
- VERSION = '0.2.0'
2
+ VERSION = '0.6.2'
3
3
  end
@@ -4,7 +4,7 @@ require 'securerandom'
4
4
  describe Ably::Realtime::Channel do
5
5
  include RSpec::EventMachine
6
6
 
7
- [:msgpack, :json].each do |protocol|
7
+ [:json].each do |protocol| # :msgpack,
8
8
  context "over #{protocol}" do
9
9
  let(:default_options) { options.merge(api_key: api_key, environment: environment, protocol: protocol) }
10
10
 
@@ -24,25 +24,36 @@ describe Ably::Realtime::Channel do
24
24
 
25
25
  let(:options) { { :protocol => :json } }
26
26
 
27
- it 'retrieves real-time history' do
27
+ it 'returns a Deferrable' do
28
28
  run_reactor do
29
29
  channel.publish('event', payload) do |message|
30
- history = channel.history
31
- expect(history.length).to eql(1)
32
- expect(history[0].data).to eql(payload)
30
+ expect(channel.history).to be_a(EventMachine::Deferrable)
33
31
  stop_reactor
34
32
  end
35
33
  end
36
34
  end
37
35
 
36
+ it 'retrieves real-time history' do
37
+ run_reactor do
38
+ channel.publish('event', payload) do |message|
39
+ channel.history do |history|
40
+ expect(history.length).to eql(1)
41
+ expect(history[0].data).to eql(payload)
42
+ stop_reactor
43
+ end
44
+ end
45
+ end
46
+ end
47
+
38
48
  it 'retrieves real-time history across two channels' do
39
49
  run_reactor do
40
50
  channel.publish('event', payload) do |message|
41
51
  channel2.publish('event', payload) do |message|
42
- history = channel2.history
43
- expect(history.length).to eql(2)
44
- expect(history.map(&:data).uniq).to eql([payload])
45
- stop_reactor
52
+ channel2.history do |history|
53
+ expect(history.length).to eql(2)
54
+ expect(history.map(&:data).uniq).to eql([payload])
55
+ stop_reactor
56
+ end
46
57
  end
47
58
  end
48
59
  end
@@ -53,21 +64,22 @@ describe Ably::Realtime::Channel do
53
64
  let(:limit) { 10 }
54
65
 
55
66
  def check_limited_history(direction)
56
- history = channel.history(direction: direction, limit: limit)
57
- expect(history.length).to eql(limit)
58
- limit.times do |index|
59
- expect(history[index].data).to eql("history#{index}")
60
- end
67
+ channel.history(direction: direction, limit: limit) do |history|
68
+ expect(history.length).to eql(limit)
69
+ limit.times do |index|
70
+ expect(history[index].data).to eql("history#{index}")
71
+ end
61
72
 
62
- history = history.next_page
73
+ history.next_page do |history|
74
+ expect(history.length).to eql(limit)
75
+ limit.times do |index|
76
+ expect(history[index].data).to eql("history#{index + limit}")
77
+ end
78
+ expect(history.last_page?).to eql(true)
63
79
 
64
- expect(history.length).to eql(limit)
65
- limit.times do |index|
66
- expect(history[index].data).to eql("history#{index + limit}")
80
+ stop_reactor
81
+ end
67
82
  end
68
- expect(history.last_page?).to eql(true)
69
-
70
- stop_reactor
71
83
  end
72
84
 
73
85
  context 'as one ProtocolMessage' do
@@ -24,17 +24,18 @@ describe 'Ably::Realtime::Presence Messages' do
24
24
  run_reactor do
25
25
  presence_client_one.enter(data: data) do
26
26
  presence_client_one.leave do
27
- history = presence_client_one.history
28
- expect(history.count).to eql(2)
27
+ presence_client_one.history do |history|
28
+ expect(history.count).to eql(2)
29
29
 
30
- expect(history[1].action).to eq(:enter)
31
- expect(history[1].client_id).to eq(client_one.client_id)
30
+ expect(history[1].action).to eq(:enter)
31
+ expect(history[1].client_id).to eq(client_one.client_id)
32
32
 
33
- expect(history[0].action).to eq(:leave)
34
- expect(history[0].client_id).to eq(client_one.client_id)
35
- expect(history[0].data).to eql(data)
33
+ expect(history[0].action).to eq(:leave)
34
+ expect(history[0].client_id).to eq(client_one.client_id)
35
+ expect(history[0].data).to eql(data)
36
36
 
37
- stop_reactor
37
+ stop_reactor
38
+ end
38
39
  end
39
40
  end
40
41
  end
@@ -43,11 +44,12 @@ describe 'Ably::Realtime::Presence Messages' do
43
44
  it 'ensures REST presence history message IDs match ProtocolMessage wrapped message IDs via Realtime' do
44
45
  run_reactor do
45
46
  presence_client_one.subscribe(:enter) do |message|
46
- history = presence_client_one.history
47
- expect(history.count).to eql(1)
47
+ presence_client_one.history do |history|
48
+ expect(history.count).to eql(1)
48
49
 
49
- expect(history[0].id).to eql(message.id)
50
- stop_reactor
50
+ expect(history[0].id).to eql(message.id)
51
+ stop_reactor
52
+ end
51
53
  end
52
54
 
53
55
  presence_client_one.enter(data: data)
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Ably::Realtime::Client stats' do
4
+ include RSpec::EventMachine
5
+
6
+ [:msgpack, :json].each do |protocol|
7
+ context "over #{protocol}" do
8
+ let(:client) do
9
+ Ably::Realtime::Client.new(api_key: api_key, environment: environment, protocol: protocol)
10
+ end
11
+
12
+ describe 'fetching stats' do
13
+ it 'should return a Hash' do
14
+ run_reactor do
15
+ client.stats do |stats|
16
+ expect(stats).to be_a(Array)
17
+ stop_reactor
18
+ end
19
+ end
20
+ end
21
+
22
+ it 'should return a deferrable object' do
23
+ run_reactor do
24
+ expect(client.stats).to be_a(EventMachine::Deferrable)
25
+ stop_reactor
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Ably::Realtime::Client time' do
4
+ include RSpec::EventMachine
5
+
6
+ [:msgpack, :json].each do |protocol|
7
+ context "over #{protocol}" do
8
+ let(:client) do
9
+ Ably::Realtime::Client.new(api_key: api_key, environment: environment, protocol: protocol)
10
+ end
11
+
12
+ describe 'fetching the service time' do
13
+ it 'should return the service time as a Time object' do
14
+ run_reactor do
15
+ client.time do |time|
16
+ expect(time).to be_within(2).of(Time.now)
17
+ stop_reactor
18
+ end
19
+ end
20
+ end
21
+
22
+ it 'should return a deferrable object' do
23
+ run_reactor do
24
+ expect(client.time).to be_a(EventMachine::Deferrable)
25
+ stop_reactor
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -28,6 +28,8 @@ describe Ably::Rest::Presence do
28
28
  expect(presence_message.data).to eq(fixture[:data])
29
29
  end
30
30
  end
31
+
32
+ skip 'with options'
31
33
  end
32
34
 
33
35
  describe 'presence #history' do
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
  require 'securerandom'
3
3
 
4
- describe 'Ably::Rest Stats' do
4
+ describe 'Ably::Rest::Client Stats' do
5
5
  [:json, :msgpack].each do |protocol|
6
6
  context "over #{protocol}" do
7
7
  describe 'fetching application stats' do
@@ -1,7 +1,6 @@
1
1
  require 'spec_helper'
2
- require 'securerandom'
3
2
 
4
- describe 'Ably::REST time' do
3
+ describe 'Ably::REST::Client time' do
5
4
  [:msgpack, :json].each do |protocol|
6
5
  context "over #{protocol}" do
7
6
  let(:client) do
@@ -9,7 +8,7 @@ describe 'Ably::REST time' do
9
8
  end
10
9
 
11
10
  describe 'fetching the service time' do
12
- it "should return the service time as a Time object" do
11
+ it 'should return the service time as a Time object' do
13
12
  expect(client.time).to be_within(2).of(Time.now)
14
13
  end
15
14
  end
@@ -67,7 +67,7 @@ describe Ably::Models::PaginatedResource do
67
67
  end
68
68
  end
69
69
 
70
- context 'with each block' do
70
+ context 'paged transformations' do
71
71
  let(:headers) do
72
72
  {
73
73
  'link' => [
@@ -93,23 +93,65 @@ describe Ably::Models::PaginatedResource do
93
93
  })
94
94
  end
95
95
 
96
- subject do
97
- paginated_resource_class.new(http_response, full_url, paged_client, paginated_resource_options) do |resource|
98
- resource[:added_attribute_from_block] = "id:#{resource[:id]}"
99
- resource
96
+ context 'with each block' do
97
+ subject do
98
+ paginated_resource_class.new(http_response, full_url, paged_client, paginated_resource_options) do |resource|
99
+ resource[:added_attribute_from_block] = "id:#{resource[:id]}"
100
+ resource
101
+ end
102
+ end
103
+
104
+ it 'calls the block for each resource after retrieving the resources' do
105
+ expect(subject[0][:added_attribute_from_block]).to eql("id:#{body[0][:id]}")
100
106
  end
101
- end
102
107
 
103
- it 'calls the block for each resource after retrieving the resources' do
104
- expect(subject[0][:added_attribute_from_block]).to eql("id:#{body[0][:id]}")
108
+ it 'calls the block for each resource on second page after retrieving the resources' do
109
+ page_1_first_id = subject[0][:id]
110
+ next_page = subject.next_page
111
+
112
+ expect(next_page[0][:added_attribute_from_block]).to eql("id:#{body_page2[0][:id]}")
113
+ expect(next_page[0][:id]).to_not eql(page_1_first_id)
114
+ end
105
115
  end
106
116
 
107
- it 'calls the block for each resource on second page after retrieving the resources' do
108
- page_1_first_id = subject[0][:id]
109
- next_page = subject.next_page
117
+ context 'with option async_blocking_operations: true' do
118
+ include RSpec::EventMachine
119
+
120
+ subject do
121
+ paginated_resource_class.new(http_response, full_url, paged_client, async_blocking_operations: true)
122
+ end
110
123
 
111
- expect(next_page[0][:added_attribute_from_block]).to eql("id:#{body_page2[0][:id]}")
112
- expect(next_page[0][:id]).to_not eql(page_1_first_id)
124
+ context '#next_page' do
125
+ it 'returns a deferrable object' do
126
+ run_reactor do
127
+ expect(subject.next_page).to be_a(EventMachine::Deferrable)
128
+ stop_reactor
129
+ end
130
+ end
131
+
132
+ it 'allows a success callback block to be added' do
133
+ run_reactor do
134
+ subject.next_page do |paginated_resource|
135
+ expect(paginated_resource).to be_a(Ably::Models::PaginatedResource)
136
+ stop_reactor
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+ context '#first_page' do
143
+ it 'calls the errback callback when first page headers are missing' do
144
+ run_reactor do
145
+ subject.next_page do |paginated_resource|
146
+ deferrable = subject.first_page
147
+ deferrable.errback do |error|
148
+ expect(error).to be_a(Ably::Exceptions::InvalidPageError)
149
+ stop_reactor
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
113
155
  end
114
156
  end
115
157
 
@@ -0,0 +1,125 @@
1
+ require 'spec_helper'
2
+ require 'securerandom'
3
+
4
+ describe Ably::Modules::AsyncWrapper do
5
+ include RSpec::EventMachine
6
+
7
+ let(:class_with_module) do
8
+ Class.new do
9
+ include Ably::Modules::AsyncWrapper
10
+
11
+ def operation(&success_callback)
12
+ async_wrap success_callback, &@block
13
+ end
14
+
15
+ def block=(block)
16
+ @block = block
17
+ end
18
+ end
19
+ end
20
+ let(:subject) { class_with_module.new }
21
+ let(:result) { SecureRandom.hex }
22
+ let(:sleep_time) { 0.1 }
23
+
24
+ before do
25
+ subject.block = block
26
+ end
27
+
28
+ context '#async_wrap blocking block' do
29
+ context 'returns result' do
30
+ let(:block) do
31
+ proc do
32
+ sleep sleep_time
33
+ result
34
+ end
35
+ end
36
+
37
+ it 'calls the success_callback block with result when provided' do
38
+ run_reactor do
39
+ subject.operation do |result|
40
+ expect(result).to eql(result)
41
+ stop_reactor
42
+ end
43
+ end
44
+ end
45
+
46
+ it 'returns a deferrable that succeeds with result' do
47
+ run_reactor do
48
+ deferrable = subject.operation
49
+ expect(deferrable).to be_a(EventMachine::Deferrable)
50
+ deferrable.callback do |result|
51
+ expect(result).to eql(result)
52
+ stop_reactor
53
+ end
54
+ end
55
+ end
56
+
57
+ it 'does not call the errback' do
58
+ run_reactor do
59
+ deferrable = subject.operation
60
+ expect(deferrable).to be_a(EventMachine::Deferrable)
61
+ deferrable.callback do |result|
62
+ expect(result).to eql(result)
63
+ EventMachine.add_timer(sleep_time * 2) { stop_reactor }
64
+ end
65
+ deferrable.errback do |result|
66
+ raise 'Errback should not have been called'
67
+ end
68
+ end
69
+ end
70
+
71
+ it 'does not block EventMachine' do
72
+ run_reactor do
73
+ timers_called = 0
74
+ EventMachine.add_periodic_timer(sleep_time / 5) { timers_called += 1 }
75
+
76
+ subject.operation do |result|
77
+ expect(timers_called).to be >= 4
78
+ stop_reactor
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ context 'raises an Exception' do
85
+ let(:block) do
86
+ proc do
87
+ sleep sleep_time
88
+ raise RuntimeError, 'Intentional'
89
+ end
90
+ end
91
+
92
+ it 'calls the errback block of the deferrable' do
93
+ run_reactor do
94
+ deferrable = subject.operation
95
+ expect(deferrable).to be_a(EventMachine::Deferrable)
96
+ deferrable.errback do |error|
97
+ expect(error).to be_a(RuntimeError)
98
+ expect(error.message).to match(/Intentional/)
99
+ stop_reactor
100
+ end
101
+ end
102
+ end
103
+
104
+ it 'does not call the success_callback block' do
105
+ run_reactor do
106
+ subject.operation do |result|
107
+ raise 'Callback should not have been called'
108
+ end
109
+ EventMachine.add_timer(sleep_time * 2) { stop_reactor }
110
+ end
111
+ end
112
+
113
+ it 'does not call the callback block of the deferrable' do
114
+ run_reactor do
115
+ deferrable = subject.operation
116
+ expect(deferrable).to be_a(EventMachine::Deferrable)
117
+ deferrable.callback do |result|
118
+ raise 'Callback should not have been called'
119
+ end
120
+ EventMachine.add_timer(sleep_time * 2) { stop_reactor }
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -16,16 +16,6 @@ describe Ably::Realtime::Client do
16
16
  subject
17
17
  end
18
18
 
19
- specify '#time' do
20
- expect(subject.rest_client).to receive(:time)
21
- subject.time
22
- end
23
-
24
- specify '#stats' do
25
- expect(subject.rest_client).to receive(:stats).with(options)
26
- subject.stats options
27
- end
28
-
29
19
  context 'for attribute' do
30
20
  [:environment, :use_tls?, :log_level].each do |attribute|
31
21
  specify "##{attribute}" do
@@ -47,7 +47,9 @@ describe Ably::Realtime::Presence do
47
47
  context 'subscriptions' do
48
48
  let(:message_history) { Hash.new { |hash, key| hash[key] = 0 } }
49
49
  let(:presence_action) { Ably::Models::PresenceMessage::ACTION.Enter }
50
- let(:message) { instance_double('Ably::Models::PresenceMessage', action: presence_action, member_id: SecureRandom.hex) }
50
+ let(:message) do
51
+ instance_double('Ably::Models::PresenceMessage', action: presence_action, member_id: SecureRandom.hex, decode: true)
52
+ end
51
53
 
52
54
  context '#subscribe' do
53
55
  specify 'to all presence state actions' do
@@ -3,11 +3,12 @@ require 'support/protocol_msgbus_helper'
3
3
 
4
4
  describe Ably::Realtime::Connection::WebsocketTransport do
5
5
  let(:client_ignored) { double('Ably::Realtime::Client').as_null_object }
6
- let(:connection) { instance_double('Ably::Realtime::Connection', client: client_ignored, id: nil) }
6
+ let(:connection) { instance_double('Ably::Realtime::Connection', client: client_ignored, id: nil) }
7
+ let(:url) { 'http://ably.io/' }
7
8
 
8
9
  let(:websocket_transport_without_eventmachine) do
9
10
  Ably::Realtime::Connection::WebsocketTransport.send(:allocate).tap do |websocket_transport|
10
- websocket_transport.send(:initialize, connection)
11
+ websocket_transport.send(:initialize, connection, url)
11
12
  end
12
13
  end
13
14
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ably
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lewis Marshall
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-12-09 00:00:00.000000000 Z
12
+ date: 2014-12-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: eventmachine
@@ -210,6 +210,7 @@ files:
210
210
  - lib/ably/models/presence_message.rb
211
211
  - lib/ably/models/protocol_message.rb
212
212
  - lib/ably/models/token.rb
213
+ - lib/ably/modules/async_wrapper.rb
213
214
  - lib/ably/modules/channels_collection.rb
214
215
  - lib/ably/modules/conversions.rb
215
216
  - lib/ably/modules/encodeable.rb
@@ -253,6 +254,8 @@ files:
253
254
  - spec/acceptance/realtime/message_spec.rb
254
255
  - spec/acceptance/realtime/presence_history_spec.rb
255
256
  - spec/acceptance/realtime/presence_spec.rb
257
+ - spec/acceptance/realtime/stats_spec.rb
258
+ - spec/acceptance/realtime/time_spec.rb
256
259
  - spec/acceptance/rest/auth_spec.rb
257
260
  - spec/acceptance/rest/base_spec.rb
258
261
  - spec/acceptance/rest/channel_spec.rb
@@ -284,6 +287,7 @@ files:
284
287
  - spec/unit/models/presence_message_spec.rb
285
288
  - spec/unit/models/protocol_message_spec.rb
286
289
  - spec/unit/models/token_spec.rb
290
+ - spec/unit/modules/async_wrapper_spec.rb
287
291
  - spec/unit/modules/conversions_spec.rb
288
292
  - spec/unit/modules/enum_spec.rb
289
293
  - spec/unit/modules/event_emitter_spec.rb
@@ -330,6 +334,8 @@ test_files:
330
334
  - spec/acceptance/realtime/message_spec.rb
331
335
  - spec/acceptance/realtime/presence_history_spec.rb
332
336
  - spec/acceptance/realtime/presence_spec.rb
337
+ - spec/acceptance/realtime/stats_spec.rb
338
+ - spec/acceptance/realtime/time_spec.rb
333
339
  - spec/acceptance/rest/auth_spec.rb
334
340
  - spec/acceptance/rest/base_spec.rb
335
341
  - spec/acceptance/rest/channel_spec.rb
@@ -361,6 +367,7 @@ test_files:
361
367
  - spec/unit/models/presence_message_spec.rb
362
368
  - spec/unit/models/protocol_message_spec.rb
363
369
  - spec/unit/models/token_spec.rb
370
+ - spec/unit/modules/async_wrapper_spec.rb
364
371
  - spec/unit/modules/conversions_spec.rb
365
372
  - spec/unit/modules/enum_spec.rb
366
373
  - spec/unit/modules/event_emitter_spec.rb