ably 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -1
  3. data/ably.gemspec +4 -3
  4. data/lib/ably.rb +6 -2
  5. data/lib/ably/auth.rb +24 -16
  6. data/lib/ably/exceptions.rb +16 -5
  7. data/lib/ably/{realtime/models → models}/error_info.rb +9 -11
  8. data/lib/ably/models/idiomatic_ruby_wrapper.rb +57 -26
  9. data/lib/ably/{realtime/models → models}/message.rb +45 -38
  10. data/lib/ably/{realtime/models → models}/nil_channel.rb +4 -4
  11. data/lib/ably/{rest/models/paged_resource.rb → models/paginated_resource.rb} +21 -10
  12. data/lib/ably/models/presence_message.rb +126 -0
  13. data/lib/ably/{realtime/models → models}/protocol_message.rb +76 -38
  14. data/lib/ably/models/token.rb +74 -0
  15. data/lib/ably/modules/channels_collection.rb +49 -0
  16. data/lib/ably/modules/conversions.rb +2 -0
  17. data/lib/ably/modules/event_emitter.rb +43 -8
  18. data/lib/ably/modules/event_machine_helpers.rb +1 -0
  19. data/lib/ably/modules/http_helpers.rb +9 -2
  20. data/lib/ably/modules/message_pack.rb +14 -0
  21. data/lib/ably/modules/model_common.rb +29 -0
  22. data/lib/ably/modules/{state.rb → state_emitter.rb} +8 -7
  23. data/lib/ably/realtime.rb +37 -7
  24. data/lib/ably/realtime/channel.rb +154 -31
  25. data/lib/ably/realtime/channels.rb +47 -0
  26. data/lib/ably/realtime/client.rb +39 -33
  27. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +50 -21
  28. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +9 -11
  29. data/lib/ably/realtime/connection.rb +148 -79
  30. data/lib/ably/realtime/connection/connection_state_machine.rb +111 -0
  31. data/lib/ably/realtime/connection/websocket_transport.rb +161 -0
  32. data/lib/ably/realtime/presence.rb +270 -0
  33. data/lib/ably/rest.rb +14 -3
  34. data/lib/ably/rest/channel.rb +3 -3
  35. data/lib/ably/rest/channels.rb +26 -12
  36. data/lib/ably/rest/client.rb +42 -25
  37. data/lib/ably/rest/middleware/exceptions.rb +21 -23
  38. data/lib/ably/rest/middleware/external_exceptions.rb +8 -10
  39. data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +17 -0
  40. data/lib/ably/rest/middleware/parse_json.rb +9 -2
  41. data/lib/ably/rest/middleware/parse_message_pack.rb +6 -2
  42. data/lib/ably/rest/presence.rb +4 -4
  43. data/lib/ably/version.rb +1 -1
  44. data/spec/acceptance/realtime/channel_history_spec.rb +125 -0
  45. data/spec/acceptance/realtime/channel_spec.rb +135 -63
  46. data/spec/acceptance/realtime/connection_spec.rb +86 -0
  47. data/spec/acceptance/realtime/message_spec.rb +116 -94
  48. data/spec/acceptance/realtime/presence_history_spec.rb +0 -0
  49. data/spec/acceptance/realtime/presence_spec.rb +277 -0
  50. data/spec/acceptance/rest/auth_spec.rb +351 -347
  51. data/spec/acceptance/rest/base_spec.rb +43 -26
  52. data/spec/acceptance/rest/channel_spec.rb +88 -83
  53. data/spec/acceptance/rest/channels_spec.rb +32 -28
  54. data/spec/acceptance/rest/presence_spec.rb +83 -63
  55. data/spec/acceptance/rest/stats_spec.rb +38 -37
  56. data/spec/acceptance/rest/time_spec.rb +10 -6
  57. data/spec/integration/modules/{state_spec.rb → state_emitter_spec.rb} +16 -2
  58. data/spec/spec_helper.rb +14 -0
  59. data/spec/support/api_helper.rb +4 -0
  60. data/spec/support/model_helper.rb +28 -9
  61. data/spec/support/protocol_msgbus_helper.rb +8 -1
  62. data/spec/support/test_app.rb +24 -14
  63. data/spec/unit/{realtime → models}/error_info_spec.rb +4 -4
  64. data/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +46 -9
  65. data/spec/unit/models/message_spec.rb +229 -0
  66. data/spec/unit/{rest/paged_resource_spec.rb → models/paginated_resource_spec.rb} +19 -11
  67. data/spec/unit/models/presence_message_spec.rb +230 -0
  68. data/spec/unit/models/protocol_message_spec.rb +280 -0
  69. data/spec/unit/{token_spec.rb → models/token_spec.rb} +18 -22
  70. data/spec/unit/modules/conversions_spec.rb +1 -1
  71. data/spec/unit/modules/event_emitter_spec.rb +36 -4
  72. data/spec/unit/realtime/channel_spec.rb +76 -2
  73. data/spec/unit/realtime/channels_spec.rb +50 -0
  74. data/spec/unit/realtime/client_spec.rb +31 -1
  75. data/spec/unit/realtime/connection_spec.rb +8 -15
  76. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +6 -6
  77. data/spec/unit/realtime/presence_spec.rb +100 -0
  78. data/spec/unit/rest/channels_spec.rb +48 -0
  79. metadata +72 -38
  80. data/lib/ably/realtime/models/shared.rb +0 -17
  81. data/lib/ably/rest/models/message.rb +0 -64
  82. data/lib/ably/rest/models/presence_message.rb +0 -21
  83. data/lib/ably/token.rb +0 -80
  84. data/spec/unit/realtime/message_spec.rb +0 -117
  85. data/spec/unit/realtime/protocol_message_spec.rb +0 -172
  86. data/spec/unit/rest/message_spec.rb +0 -75
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5b99a13d28dd5bd7e6b1d35e6ad0d718e759fbb3
4
- data.tar.gz: fa7e7aac5a7acf5cc74bfc1da6faf48913ef0f36
3
+ metadata.gz: bc321ac8e501652f72e08617651df2f90118a759
4
+ data.tar.gz: 9f9b3f5abedeba6a4ac79841a7e1d5d09bf2bc4c
5
5
  SHA512:
6
- metadata.gz: 8d2771237651cc84bc83f3de5c6e5438f6e04f57b36432d4394995f5ed7deae8e097d397d149a13e23993116d1f1f2266de046e7c1cca42e36ae776a92a6b5b6
7
- data.tar.gz: 9f12b4aef062743e6bbfd7b40388dd7582b42d9634efb062a66ea4a181f815d5c44473e97a48b6c9b3c6862bd07d1bcf12e2d366006da5925f20f9c7d1d8a35e
6
+ metadata.gz: cdd44c54e0015062c3ecd62bb39f2e03e2a5fe39118939bd7bd18d1bbddd3d774cd511634dac8273890d57b2fd2f2a2191df91016090a54e4e9ca8ffed3823c6
7
+ data.tar.gz: 4ef9786cc786a7d4b6b824bf5bc137b17d92180c0b3d201ba48cb3d6aa9cf85a56fb8b3639170a4d0ce4f41179e1b6780569e8727df761f5a9f384df20f4535c
data/README.md CHANGED
@@ -55,6 +55,16 @@ channel = client.channel("test")
55
55
  channel.publish("greeting", "Hello World!")
56
56
  ```
57
57
 
58
+ ### Presence on a channel
59
+
60
+ ```ruby
61
+ client = Ably::Realtime.new(api_key: "xxxxx")
62
+ channel = client.channel("test")
63
+ channel.presence.enter(client_data: 'john.doe') do |presence|
64
+ presence.get #=> [Array of members present]
65
+ end
66
+ ```
67
+
58
68
  ## Using the REST API
59
69
 
60
70
  ### Publishing a message to a channel
@@ -78,7 +88,7 @@ channel.history #=> [{:name=>"test", :data=>"payload"}]
78
88
  ```ruby
79
89
  client = Ably::Rest.new(api_key: "xxxxx")
80
90
  client.auth.authorise # creates a token and will use token authentication moving forwards
81
- client.auth.current_token #=> #<Ably::Token>
91
+ client.auth.current_token #=> #<Ably::Models::Token>
82
92
  channel.publish("myEvent", "Hello!") #=> true, sent using token authentication
83
93
  ```
84
94
 
data/ably.gemspec CHANGED
@@ -18,11 +18,12 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_runtime_dependency "eventmachine"
21
+ spec.add_runtime_dependency "eventmachine", "~> 1.0"
22
+ spec.add_runtime_dependency "statesman", "~> 1.0.0.beta2"
22
23
  spec.add_runtime_dependency "faraday", "~> 0.9"
23
24
  spec.add_runtime_dependency "json"
24
- spec.add_runtime_dependency "websocket-driver"
25
- spec.add_runtime_dependency "msgpack"
25
+ spec.add_runtime_dependency "websocket-driver", "~> 0.3"
26
+ spec.add_runtime_dependency "msgpack", "~> 0.5"
26
27
 
27
28
  spec.add_development_dependency "bundler", "~> 1.3"
28
29
  spec.add_development_dependency "rake"
data/lib/ably.rb CHANGED
@@ -1,4 +1,4 @@
1
- %w(modules models util).each do |namespace|
1
+ %w(modules util).each do |namespace|
2
2
  Dir.glob(File.expand_path("ably/#{namespace}/*.rb", File.dirname(__FILE__))).each do |file|
3
3
  require file
4
4
  end
@@ -8,8 +8,12 @@ require "ably/auth"
8
8
  require "ably/exceptions"
9
9
  require "ably/realtime"
10
10
  require "ably/rest"
11
- require "ably/token"
12
11
  require "ably/version"
13
12
 
13
+ # Ably is the base namespace for the Ably {Ably::Realtime Realtime} & {Ably::Rest Rest} client libraries.
14
+ #
15
+ # Please refer to the {file:README.md Readme} on getting started.
16
+ #
17
+ # @see file:README.md README
14
18
  module Ably
15
19
  end
data/lib/ably/auth.rb CHANGED
@@ -12,7 +12,7 @@ module Ably
12
12
  # @!attribute [r] client_id
13
13
  # @return [String] The provided client ID, used for identifying this client for presence purposes
14
14
  # @!attribute [r] current_token
15
- # @return [Ably::Token] Current {Ably::Token} issued by this library or one of the provided callbacks used to authenticate requests
15
+ # @return [Ably::Models::Token] Current {Ably::Models::Token} issued by this library or one of the provided callbacks used to authenticate requests
16
16
  # @!attribute [r] token_id
17
17
  # @return [String] Token ID provided to the {Ably::Client} constructor that is used to authenticate all requests
18
18
  # @!attribute [r] api_key
@@ -37,7 +37,7 @@ module Ably
37
37
  # @param [Hash] auth_options see {Ably::Rest::Client#initialize}
38
38
  # @yield [auth_options] see {Ably::Rest::Client#initialize}
39
39
  def initialize(client, auth_options, &auth_block)
40
- auth_options = auth_options.clone
40
+ auth_options = auth_options.dup
41
41
 
42
42
  @client = client
43
43
  @options = auth_options
@@ -81,7 +81,7 @@ module Ably
81
81
  # @option options [Hash] :auth_headers a set of application-specific headers to be added to any request made to the authUrl
82
82
  # @option options [Hash] :auth_params a set of application-specific query params to be added to any request made to the authUrl
83
83
  # @option options [Symbol] :auth_method HTTP method to use with auth_url, must be either `:get` or `:post` (defaults to :get)
84
- # @option options [Integer] :ttl validity time in seconds for the requested {Ably::Token}. Limits may apply, see {http://docs.ably.io/other/authentication/}
84
+ # @option options [Integer] :ttl validity time in seconds for the requested {Ably::Models::Token}. Limits may apply, see {http://docs.ably.io/other/authentication/}
85
85
  # @option options [Hash] :capability canonicalised representation of the resource paths and associated operations
86
86
  # @option options [Boolean] :query_time when true will query the {https://ably.io Ably} system for the current time instead of using the local time
87
87
  # @option options [Time] :timestamp the time of the of the request
@@ -90,9 +90,9 @@ module Ably
90
90
  #
91
91
  # @yield [options] (optional) if an auth block is passed to this method, then this block will be called to create a new token request object
92
92
  # @yieldparam [Hash] options options passed to request_token will be in turn sent to the block in this argument
93
- # @yieldreturn [Hash] valid token request object, see {#create_token_request}
93
+ # @yieldreturn [Hash] valid token request object, see {Auth#create_token_request}
94
94
  #
95
- # @return [Ably::Token]
95
+ # @return [Ably::Models::Token]
96
96
  #
97
97
  # @example
98
98
  # # will issue a simple token request using basic auth
@@ -113,7 +113,7 @@ module Ably
113
113
  @current_token = request_token(options, &block)
114
114
  end
115
115
 
116
- # Request a {Ably::Token} which can be used to make authenticated token based requests
116
+ # Request a {Ably::Models::Token} which can be used to make authenticated token based requests
117
117
  #
118
118
  # @param [Hash] options the options for the token request
119
119
  # @option options [String] :key_id key ID for the designated application (defaults to client key_id)
@@ -123,7 +123,7 @@ module Ably
123
123
  # @option options [Hash] :auth_headers a set of application-specific headers to be added to any request made to the authUrl
124
124
  # @option options [Hash] :auth_params a set of application-specific query params to be added to any request made to the authUrl
125
125
  # @option options [Symbol] :auth_method HTTP method to use with auth_url, must be either `:get` or `:post` (defaults to :get)
126
- # @option options [Integer] :ttl validity time in seconds for the requested {Ably::Token}. Limits may apply, see {http://docs.ably.io/other/authentication/}
126
+ # @option options [Integer] :ttl validity time in seconds for the requested {Ably::Models::Token}. Limits may apply, see {http://docs.ably.io/other/authentication/}
127
127
  # @option options [Hash] :capability canonicalised representation of the resource paths and associated operations
128
128
  # @option options [Boolean] :query_time when true will query the {https://ably.io Ably} system for the current time instead of using the local time
129
129
  # @option options [Time] :timestamp the time of the of the request
@@ -131,9 +131,9 @@ module Ably
131
131
  #
132
132
  # @yield [options] (optional) if an auth block is passed to this method, then this block will be called to create a new token request object
133
133
  # @yieldparam [Hash] options options passed to request_token will be in turn sent to the block in this argument
134
- # @yieldreturn [Hash] valid token request object, see {#create_token_request}
134
+ # @yieldreturn [Hash] valid token request object, see {Auth#create_token_request}
135
135
  #
136
- # @return [Ably::Token]
136
+ # @return [Ably::Models::Token]
137
137
  #
138
138
  # @example
139
139
  # # simple token request using basic auth
@@ -165,7 +165,7 @@ module Ably
165
165
  response = client.post("/keys/#{token_request.fetch(:id)}/requestToken", token_request, send_auth_header: false)
166
166
  body = IdiomaticRubyWrapper(response.body)
167
167
 
168
- Ably::Token.new(body.fetch(:access_token))
168
+ Ably::Models::Token.new(body.fetch(:access_token))
169
169
  end
170
170
 
171
171
  # Creates and signs a token request that can then subsequently be used by any client to request a token
@@ -174,7 +174,7 @@ module Ably
174
174
  # @option options [String] :key_id key ID for the designated application
175
175
  # @option options [String] :key_secret key secret for the designated application used to sign token requests (defaults to client key_secret)
176
176
  # @option options [String] :client_id client ID identifying this connection to other clients
177
- # @option options [Integer] :ttl validity time in seconds for the requested {Ably::Token}. Limits may apply, see {http://docs.ably.io/other/authentication/}
177
+ # @option options [Integer] :ttl validity time in seconds for the requested {Ably::Models::Token}. Limits may apply, see {http://docs.ably.io/other/authentication/}
178
178
  # @option options [Hash] :capability canonicalised representation of the resource paths and associated operations
179
179
  # @option options [Boolean] :query_time when true will query the {https://ably.io Ably} system for the current time instead of using the local time
180
180
  # @option options [Time] :timestamp the time of the of the request
@@ -210,9 +210,9 @@ module Ably
210
210
  token_request = {
211
211
  id: request_key_id,
212
212
  client_id: client_id,
213
- ttl: Token::DEFAULTS[:ttl],
213
+ ttl: Ably::Models::Token::DEFAULTS[:ttl],
214
214
  timestamp: timestamp,
215
- capability: Token::DEFAULTS[:capability],
215
+ capability: Ably::Models::Token::DEFAULTS[:capability],
216
216
  nonce: SecureRandom.hex
217
217
  }.merge(token_options.select { |key, val| token_attributes.include?(key.to_s) })
218
218
 
@@ -354,11 +354,18 @@ module Ably
354
354
  connection = Faraday.new("#{uri.scheme}://#{uri.host}", connection_options)
355
355
  method = options[:auth_method] || :get
356
356
 
357
- connection.send(method) do |request|
357
+ response = connection.send(method) do |request|
358
358
  request.url uri.path
359
359
  request.params = options[:auth_params] || {}
360
360
  request.headers = options[:auth_headers] || {}
361
- end.body
361
+ end
362
+
363
+ unless response.body.kind_of?(Hash)
364
+ raise Ably::Exceptions::InvalidResponseBody,
365
+ "Content Type #{response.headers['Content-Type']} is not supported by this client library"
366
+ end
367
+
368
+ response.body
362
369
  end
363
370
 
364
371
  # Return a Hash of connection options to initiate the Faraday::Connection with
@@ -383,11 +390,12 @@ module Ably
383
390
  # @see http://mislav.uniqpath.com/2011/07/faraday-advanced-http/
384
391
  def middleware
385
392
  @middleware ||= Faraday::RackBuilder.new do |builder|
386
- setup_middleware builder
393
+ setup_outgoing_middleware builder
387
394
 
388
395
  # Raise exceptions if response code is invalid
389
396
  builder.use Ably::Rest::Middleware::ExternalExceptions
390
397
 
398
+ setup_incoming_middleware builder
391
399
 
392
400
  # Log HTTP requests if log level is DEBUG option set
393
401
  builder.response :logger if client.log_level == Logger::DEBUG
@@ -1,6 +1,7 @@
1
1
  module Ably
2
2
  module Exceptions
3
- # An invalid request was received by Ably
3
+ # Base Ably exception class that contains status and code values used by Ably
4
+ # Refer to https://github.com/ably/ably-common/blob/master/protocol/errors.json
4
5
  #
5
6
  # @!attribute [r] message
6
7
  # @return [String] Error message from Ably
@@ -8,19 +9,22 @@ module Ably
8
9
  # @return [String] HTTP status code of error
9
10
  # @!attribute [r] code
10
11
  # @return [String] Ably specific error code
11
- class InvalidRequest < StandardError
12
+ class Base < StandardError
12
13
  attr_reader :status, :code
13
- def initialize(message, status: nil, code: nil)
14
+ def initialize(message, status = nil, code = nil)
14
15
  super message
15
16
  @status = status
16
17
  @code = code
17
18
  end
18
19
  end
19
20
 
21
+ # An invalid request was received by Ably
22
+ class InvalidRequest < Base; end
23
+
20
24
  # The HTTP request has returned a 500 error
21
25
  class ServerError < StandardError; end
22
26
 
23
- # PagedResource cannot retrieve the page
27
+ # PaginatedResource cannot retrieve the page
24
28
  class InvalidPageError < StandardError; end
25
29
 
26
30
  # The expected response from the server was invalid
@@ -33,9 +37,16 @@ module Ably
33
37
  class TokenRequestError < StandardError; end
34
38
 
35
39
  # The token is invalid
36
- class InvalidToken < InvalidRequest; end
40
+ class InvalidToken < Base; end
37
41
 
38
42
  # Encryption or decryption related failures
39
43
  class EncryptionError < StandardError; end
44
+
45
+ # Ably Protocol message received that is invalid
46
+ class ProtocolError < Base; end
47
+
48
+ # A generic Ably exception taht supports a status & code.
49
+ # See https://github.com/ably/ably-common/blob/master/protocol/errors.json for a list of Ably errors
50
+ class Standard < Base; end
40
51
  end
41
52
  end
@@ -1,4 +1,4 @@
1
- module Ably::Realtime::Models
1
+ module Ably::Models
2
2
  # An exception type encapsulating error information containing
3
3
  # an Ably-specific error code and generic status code.
4
4
  #
@@ -8,29 +8,27 @@ module Ably::Realtime::Models
8
8
  # @return [Integer] Ably error code (see ably-common/protocol/errors.json)
9
9
  # @!attribute [r] status
10
10
  # @return [Integer] HTTP Status Code corresponding to this error, where applicable
11
- # @!attribute [r] json
11
+ # @!attribute [r] hash
12
12
  # @return [Hash] Access the protocol message Hash object ruby'fied to use symbolized keys
13
13
  #
14
14
  class ErrorInfo
15
- include Shared
16
- include Ably::Modules::Conversions
15
+ include Ably::Modules::ModelCommon
17
16
 
18
- def initialize(json_object)
19
- @raw_json_object = json_object
20
- @json_object = IdiomaticRubyWrapper(@raw_json_object.clone.freeze)
17
+ def initialize(hash_object)
18
+ @raw_hash_object = hash_object
19
+ @hash_object = IdiomaticRubyWrapper(hash_object.clone.freeze)
21
20
  end
22
21
 
23
22
  %w( message code status_code ).each do |attribute|
24
23
  define_method attribute do
25
- json[attribute.to_sym]
24
+ hash[attribute.to_sym]
26
25
  end
27
26
  end
28
27
  alias_method :status, :status_code
29
28
 
30
- def json
31
- @json_object
29
+ def hash
30
+ @hash_object
32
31
  end
33
- alias_method :to_json, :json
34
32
 
35
33
  def to_s
36
34
  "Error: #{message} (code: #{code}, status_code: #{status_code})"
@@ -18,7 +18,7 @@ module Ably::Modules
18
18
  end
19
19
 
20
20
  module Ably::Models
21
- # Wraps JSON objects returned by Ably service to appear as Idiomatic Ruby Hashes with symbol keys
21
+ # Wraps Hash objects returned by Ably service to appear as Idiomatic Ruby Hashes with symbol keys
22
22
  # It recursively wraps containing Hashes, but will stop wrapping at arrays, any other non Hash object, or any key matching the `:stops_at` options
23
23
  # It also provides methods matching the symbolic keys for convenience
24
24
  #
@@ -36,32 +36,33 @@ module Ably::Models
36
36
  # ruby_hash.none # => nil
37
37
  #
38
38
  # @!attribute [r] stop_at
39
- # @return [Array<Symbol,String>] array of keys that this wrapper should stop wrapping at to preserve the underlying JSON hash as is
39
+ # @return [Array<Symbol,String>] array of keys that this wrapper should stop wrapping at to preserve the underlying Hash as is
40
40
  #
41
41
  class IdiomaticRubyWrapper
42
42
  include Enumerable
43
43
  include Ably::Modules::Conversions
44
+ include Ably::Modules::MessagePack
44
45
 
45
46
  attr_reader :stop_at
46
47
 
47
- # Creates an IdiomaticRubyWrapper around the mixed case JSON object
48
+ # Creates an IdiomaticRubyWrapper around the mixed case Hash object
48
49
  #
49
- # @attribute [Hash] mixedCaseJsonObject mixed case JSON object
50
- # @attribute [Array<Symbol,String>] stop_at array of keys that this wrapper should stop wrapping at to preserve the underlying JSON hash as is
50
+ # @attribute [Hash] mixedCaseHashObject mixed case Hash object
51
+ # @attribute [Array<Symbol,String>] stop_at array of keys that this wrapper should stop wrapping at to preserve the underlying Hash as is
51
52
  #
52
- def initialize(mixedCaseJsonObject, stop_at: [])
53
- if mixedCaseJsonObject.kind_of?(IdiomaticRubyWrapper)
53
+ def initialize(mixedCaseHashObject, stop_at: [])
54
+ if mixedCaseHashObject.kind_of?(IdiomaticRubyWrapper)
54
55
  $stderr.puts "<IdiomaticRubyWrapper#initialize> WARNING: Wrapping a IdiomaticRubyWrapper with another IdiomaticRubyWrapper"
55
56
  end
56
57
 
57
- @json = mixedCaseJsonObject
58
+ @hash = mixedCaseHashObject
58
59
  @stop_at = Array(stop_at).each_with_object({}) do |key, hash|
59
60
  hash[convert_to_snake_case_symbol(key)] = true
60
61
  end.freeze
61
62
  end
62
63
 
63
64
  def [](key)
64
- value = json[source_key_for(key)]
65
+ value = hash[source_key_for(key)]
65
66
  if stop_at?(key) || !value.kind_of?(Hash)
66
67
  value
67
68
  else
@@ -70,7 +71,7 @@ module Ably::Models
70
71
  end
71
72
 
72
73
  def []=(key, value)
73
- json[source_key_for(key)] = value
74
+ hash[source_key_for(key)] = value
74
75
  end
75
76
 
76
77
  def fetch(key, default = nil, &missing_block)
@@ -88,7 +89,7 @@ module Ably::Models
88
89
  end
89
90
 
90
91
  def size
91
- json.size
92
+ hash.size
92
93
  end
93
94
 
94
95
  def keys
@@ -100,12 +101,12 @@ module Ably::Models
100
101
  end
101
102
 
102
103
  def has_key?(key)
103
- json.has_key?(source_key_for(key))
104
+ hash.has_key?(source_key_for(key))
104
105
  end
105
106
 
106
107
  # Method ensuring this {IdiomaticRubyWrapper} is {http://ruby-doc.org/core-2.1.3/Enumerable.html Enumerable}
107
108
  def each(&block)
108
- json.each do |key, value|
109
+ hash.each do |key, value|
109
110
  key = convert_to_snake_case_symbol(key)
110
111
  value = self[key]
111
112
  if block_given?
@@ -137,29 +138,59 @@ module Ably::Models
137
138
  end
138
139
  end
139
140
 
140
- # Access to the raw JSON object provided to the constructer of this wrapper
141
- def json
142
- @json
141
+ # Access to the raw Hash object provided to the constructer of this wrapper
142
+ def hash
143
+ @hash
143
144
  end
144
145
 
145
- # Converts the current wrapped mixedCase object to a JSON string
146
- # using the provided mixedCase syntax
146
+ # Takes the underlying Hash object and returns it in as a JSON ready Hash object using snakeCase for compability with the Ably service.
147
+ # Note name clashes are ignored and will result in loss of one or more values
148
+ # @example
149
+ # wrapper = IdiomaticRubyWrapper({ 'mixedCase': true, mixed_case: false, 'snake_case': 1 })
150
+ # wrapper.as_json({ 'mixedCase': true, 'snakeCase': 1 })
151
+ def as_json(*args)
152
+ hash.each_with_object({}) do |key_val, new_hash|
153
+ key, val = key_val
154
+ mixed_case_key = convert_to_mixed_case(key)
155
+ wrapped_val = self[key]
156
+ wrapped_val = wrapped_val.as_json(args) if wrapped_val.kind_of?(IdiomaticRubyWrapper)
157
+
158
+ new_hash[mixed_case_key] = wrapped_val
159
+ end
160
+ end
161
+
162
+ # Converts the current wrapped mixedCase object to JSON
163
+ # using mixedCase syntax as expected by the Realtime API
147
164
  def to_json(*args)
148
- json.to_json
165
+ as_json(args).to_json
149
166
  end
150
167
 
151
- # Generate a symbolized Hash object representing the underlying JSON in a Ruby friendly format
152
- def to_hash
168
+ # Generate a symbolized Hash object representing the underlying Hash in a Ruby friendly format.
169
+ # Note name clashes are ignored and will result in loss of one or more values
170
+ # @example
171
+ # wrapper = IdiomaticRubyWrapper({ 'mixedCase': true, mixed_case: false, 'snake_case': 1 })
172
+ # wrapper.to_hash({ mixed_case: true, snake_case: 1 })
173
+ def to_hash(*args)
153
174
  each_with_object({}) do |key_val, hash|
154
- key, val = key_val
175
+ key, val = key_val
176
+ val = val.to_hash(args) if val.kind_of?(IdiomaticRubyWrapper)
155
177
  hash[key] = val
156
178
  end
157
179
  end
158
180
 
159
- # Method to create a duplicate of the underlying JSON object
160
- # Useful when underlying JSON is frozen
181
+ # Method to create a duplicate of the underlying Hash object
182
+ # Useful when underlying Hash is frozen
161
183
  def dup
162
- Ably::Models::IdiomaticRubyWrapper.new(json.dup)
184
+ Ably::Models::IdiomaticRubyWrapper.new(hash.dup)
185
+ end
186
+
187
+ # Freeze the underlying data
188
+ def freeze
189
+ hash.freeze
190
+ end
191
+
192
+ def to_s
193
+ hash.to_s
163
194
  end
164
195
 
165
196
  private
@@ -183,7 +214,7 @@ module Ably::Models
183
214
  ]
184
215
 
185
216
  preferred_format = format_preferences.detect do |format|
186
- json.has_key?(format.call(symbolized_key))
217
+ hash.has_key?(format.call(symbolized_key))
187
218
  end || format_preferences.first
188
219
 
189
220
  preferred_format.call(symbolized_key)