ably 0.1.5 → 0.1.6

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.
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)