ably 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: da8f27b90ddc5d18cefb088124f42507675fba7e
4
- data.tar.gz: d4079770ea27bc17d35eb4791b1f05820a940877
3
+ metadata.gz: ea2c608bdda1c7910a086adf11b7d32854df487c
4
+ data.tar.gz: d490375a690b51efbe14ebc765ec7786b114a658
5
5
  SHA512:
6
- metadata.gz: 7a2413536347bdd4da0c993ddf3c9926f6639be60c84945e627e7a4127b8b6360d1ae77e55ed6132c71d8138b87323ff83087ba5cb57f5ef1a6ed2c9572ec322
7
- data.tar.gz: 4562af003c001e631344c160685b91f2e7f7018a11cb32e2c2572a8c949939a6b3ab16a8b25a0957077f2b96a7b07e71b7d8ef4b93420b10bf76859987bbfc28
6
+ metadata.gz: 7d3801488036052b450c6e81cc824e5c223432c697642e3aebe0d8f27c77c2eb1784d84693b5d232110dc9080bd285b6f828094bb383e88d436069922b264bc3
7
+ data.tar.gz: 272c272b9beb386d6ae7ca33fd8ee4868cd7ff4d4e0fdd8951a5df65df3af465d586fd5a79325e509b49b7e17f88ca1ce9a5e757f06d976449fd322ecda88912
@@ -1,10 +1,13 @@
1
- require "ably/modules/conversions"
2
- require "ably/modules/http_helpers"
1
+ %w(modules models).each do |namespace|
2
+ Dir.glob(File.expand_path("ably/#{namespace}/*.rb", File.dirname(__FILE__))).each do |file|
3
+ require file
4
+ end
5
+ end
3
6
 
4
7
  require "ably/auth"
5
8
  require "ably/exceptions"
6
- require "ably/rest"
7
9
  require "ably/realtime"
10
+ require "ably/rest"
8
11
  require "ably/token"
9
12
  require "ably/version"
10
13
 
@@ -26,6 +26,7 @@ module Ably
26
26
  # @return [Hash] {Ably::Auth} options configured for this client
27
27
 
28
28
  class Auth
29
+ include Ably::Modules::Conversions
29
30
  include Ably::Modules::HttpHelpers
30
31
 
31
32
  attr_reader :options, :current_token
@@ -37,7 +38,7 @@ module Ably
37
38
  # @param [Hash] auth_options see {Ably::Rest::Client#initialize}
38
39
  # @yield [auth_options] see {Ably::Rest::Client#initialize}
39
40
  def initialize(client, auth_options, &auth_block)
40
- auth_options = auth_options.dup
41
+ auth_options = auth_options.clone
41
42
 
42
43
  @client = client
43
44
  @options = auth_options
@@ -160,9 +161,12 @@ module Ably
160
161
  create_token_request(token_options)
161
162
  end
162
163
 
164
+ token_request = IdiomaticRubyWrapper(token_request)
165
+
163
166
  response = client.post("/keys/#{token_request.fetch(:id)}/requestToken", token_request, send_auth_header: false)
167
+ body = IdiomaticRubyWrapper(response.body)
164
168
 
165
- Ably::Token.new(response.body.fetch(:access_token))
169
+ Ably::Token.new(body.fetch(:access_token))
166
170
  end
167
171
 
168
172
  # Creates and signs a token request that can then subsequently be used by any client to request a token
@@ -192,7 +196,7 @@ module Ably
192
196
  def create_token_request(options = {})
193
197
  token_attributes = %w(id client_id ttl timestamp capability nonce)
194
198
 
195
- token_options = options.dup
199
+ token_options = options.clone
196
200
  request_key_id = token_options.delete(:key_id) || key_id
197
201
  request_key_secret = token_options.delete(:key_secret) || key_secret
198
202
 
@@ -0,0 +1,204 @@
1
+ require 'logger'
2
+
3
+ module Ably::Models
4
+ # Wraps JSON objects returned by Ably service to appear as Idiomatic Ruby Hashes with symbol keys
5
+ # It recursively wraps containing Hashes, but will stop wrapping at arrays, any other non Hash object, or any key matching the `:stops_at` options
6
+ # It also provides methods matching the symbolic keys for convenience
7
+ #
8
+ # @example
9
+ # ruby_hash = IdiomaticRubyWrapper.new({ 'keyValue' => 'true' })
10
+ # # or recommended to avoid wrapping wrapped objects
11
+ # ruby_hash = IdiomaticRubyWrapper({ 'keyValue' => 'true' })
12
+
13
+ # ruby_hash[:key_value] # => 'true'
14
+ # ruby_hash.key_value # => 'true'
15
+ # ruby_hash[:key_value] = 'new_value'
16
+ # ruby_hash.key_value # => 'new_value'
17
+ #
18
+ # ruby_hash[:none] # => nil
19
+ # ruby_hash.none # => nil
20
+ #
21
+ # @note It is recommended you include {Ably::Modules::Conversions Ably::Modules::Conversions} so that you can use the object creation syntax `IdiomaticRubyWrappers(hash_or_another_idiomatic_ruby_wrapper)`
22
+ #
23
+ # @!attribute [r] stop_at
24
+ # @return [Array<Symbol,String>] array of keys that this wrapper should stop wrapping at to preserve the underlying JSON hash as is
25
+ #
26
+ class IdiomaticRubyWrapper
27
+ include Enumerable
28
+
29
+ attr_reader :stop_at
30
+
31
+ # Creates an IdiomaticRubyWrapper around the mixed case JSON object
32
+ #
33
+ # @attribute [Hash] mixedCaseJsonObject mixed case JSON object
34
+ # @attribute [Array<Symbol,String>] stop_at array of keys that this wrapper should stop wrapping at to preserve the underlying JSON hash as is
35
+ #
36
+ def initialize(mixedCaseJsonObject, stop_at: [])
37
+ if mixedCaseJsonObject.kind_of?(IdiomaticRubyWrapper)
38
+ $stderr.puts "<IdiomaticRubyWrapper#initialize> WARNING: Wrapping a IdiomaticRubyWrapper with another IdiomaticRubyWrapper"
39
+ end
40
+
41
+ @json = mixedCaseJsonObject
42
+ @stop_at = Array(stop_at).each_with_object({}) do |key, hash|
43
+ hash[convert_to_snake_case_symbol(key)] = true
44
+ end.freeze
45
+ end
46
+
47
+ def [](key)
48
+ value = json[source_key_for(key)]
49
+ if stop_at?(key) || !value.kind_of?(Hash)
50
+ value
51
+ else
52
+ IdiomaticRubyWrapper.new(value, stop_at: stop_at)
53
+ end
54
+ end
55
+
56
+ def []=(key, value)
57
+ json[source_key_for(key)] = value
58
+ end
59
+
60
+ def fetch(key, default = nil, &missing_block)
61
+ if has_key?(key)
62
+ self[key]
63
+ else
64
+ if default
65
+ default
66
+ elsif block_given?
67
+ yield key
68
+ else
69
+ raise KeyError, "key not found: #{key}"
70
+ end
71
+ end
72
+ end
73
+
74
+ def size
75
+ json.size
76
+ end
77
+
78
+ def keys
79
+ map { |key, value| key }
80
+ end
81
+
82
+ def values
83
+ map { |key, value| value }
84
+ end
85
+
86
+ def has_key?(key)
87
+ json.has_key?(source_key_for(key))
88
+ end
89
+
90
+ # Method ensuring this {IdiomaticRubyWrapper} is {http://ruby-doc.org/core-2.1.3/Enumerable.html Enumerable}
91
+ def each(&block)
92
+ json.each do |key, value|
93
+ key = convert_to_snake_case_symbol(key)
94
+ value = self[key]
95
+ if block_given?
96
+ block.call key, value
97
+ else
98
+ yield key, value
99
+ end
100
+ end
101
+ end
102
+
103
+ # Compare object based on Hash equivalent
104
+ def ==(other)
105
+ return false unless other.kind_of?(self.class) || other.kind_of?(Hash)
106
+
107
+ other = other.to_hash if other.kind_of?(self.class)
108
+ to_hash == other
109
+ end
110
+
111
+ def method_missing(method_sym, *arguments)
112
+ key = method_sym.to_s.gsub(%r{=$}, '')
113
+ return super if !has_key?(key)
114
+
115
+ if method_sym.to_s.match(%r{=$})
116
+ raise ArgumentError, "Cannot set #{method_sym} with more than one argument" unless arguments.length == 1
117
+ self[key] = arguments.first
118
+ else
119
+ raise ArgumentError, "Cannot pass an argument to #{method_sym} when retrieving its value" unless arguments.empty?
120
+ self[method_sym]
121
+ end
122
+ end
123
+
124
+ # Access to the raw JSON object provided to the constructer of this wrapper
125
+ def json
126
+ @json
127
+ end
128
+
129
+ # Converts the current wrapped mixedCase object to a JSON string
130
+ # using the provided mixedCase syntax
131
+ def to_json(*args)
132
+ json.to_json
133
+ end
134
+
135
+ # Generate a symbolized Hash object representing the underlying JSON in a Ruby friendly format
136
+ def to_hash
137
+ each_with_object({}) do |key_val, hash|
138
+ key, val = key_val
139
+ hash[key] = val
140
+ end
141
+ end
142
+
143
+ # Method to create a duplicate of the underlying JSON object
144
+ # Useful when underlying JSON is frozen
145
+ def dup
146
+ Ably::Models::IdiomaticRubyWrapper.new(json.dup)
147
+ end
148
+
149
+ private
150
+ def stop_at?(key)
151
+ @stop_at.has_key?(key)
152
+ end
153
+
154
+ # We assume by default all keys are interchangeable between :this_format and 'thisFormat'
155
+ # However, this method will find other fallback formats such as CamelCase or :symbols if a matching
156
+ # key is not found in mixedCase.
157
+ def source_key_for(symbolized_key)
158
+ format_preferences = [
159
+ -> (key_sym) { convert_to_mixed_case(key_sym) },
160
+ -> (key_sym) { key_sym.to_sym },
161
+ -> (key_sym) { key_sym.to_s },
162
+ -> (key_sym) { convert_to_mixed_case(key_sym).to_sym },
163
+ -> (key_sym) { convert_to_lower_case(key_sym) },
164
+ -> (key_sym) { convert_to_lower_case(key_sym).to_sym },
165
+ -> (key_sym) { convert_to_mixed_case(key_sym, force_camel: true) },
166
+ -> (key_sym) { convert_to_mixed_case(key_sym, force_camel: true).to_sym }
167
+ ]
168
+
169
+ preferred_format = format_preferences.detect do |format|
170
+ json.has_key?(format.call(symbolized_key))
171
+ end || format_preferences.first
172
+
173
+ preferred_format.call(symbolized_key)
174
+ end
175
+
176
+ # Convert key to mixedCase from mixed_case
177
+ def convert_to_mixed_case(key, force_camel: false)
178
+ key.to_s.
179
+ split('_').
180
+ each_with_index.map do |str, index|
181
+ if index > 0 || force_camel
182
+ str.capitalize
183
+ else
184
+ str
185
+ end
186
+ end.
187
+ join
188
+ end
189
+
190
+ # Convert key to :snake_case from snakeCase
191
+ def convert_to_snake_case_symbol(key)
192
+ key.to_s.gsub(/::/, '/').
193
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
194
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
195
+ tr("-", "_").
196
+ downcase.
197
+ to_sym
198
+ end
199
+
200
+ def convert_to_lower_case(key)
201
+ key.to_s.gsub('_', '')
202
+ end
203
+ end
204
+ end
@@ -1,58 +1,47 @@
1
1
  module Ably::Modules
2
2
  module Conversions
3
3
  private
4
- # Take a Hash object and make it more Ruby like converting all keys
5
- # into symbols with snake_case notation
6
- def rubify(*args)
7
- convert_hash_recursively(*args) do |key|
8
- convert_to_snake_case(key).to_sym
4
+ # Returns object as {IdiomaticRubyWrapper}
5
+ def IdiomaticRubyWrapper(object, options = {})
6
+ case object
7
+ when Ably::Models::IdiomaticRubyWrapper
8
+ object
9
+ else
10
+ Ably::Models::IdiomaticRubyWrapper.new(object, options)
9
11
  end
10
12
  end
11
13
 
12
- # Take a Hash object and make it more Java like converting all keys
13
- # into strings with mixedCase notation
14
- def javify(*args)
15
- convert_hash_recursively(*args) do |key|
16
- convert_to_mixed_case(key).to_s
17
- end
18
- end
19
-
20
- def convert_hash_recursively(hash, ignore: [], &processing_block)
21
- raise ArgumentError, "Processing block is missing" unless block_given?
22
-
23
- return hash unless hash.kind_of?(Hash)
24
-
25
- Hash[hash.map do |key, val|
26
- key_sym = yield(key)
27
- converted_val = if ignore.include?(key_sym)
28
- val
29
- else
30
- convert_hash_recursively(val, ignore: ignore, &processing_block)
31
- end
32
-
33
- [key_sym, converted_val]
34
- end]
14
+ def as_since_epoch(time, granularity: :ms)
15
+ case time
16
+ when Time
17
+ time.to_f * multiplier_from_granularity(granularity)
18
+ when Numeric
19
+ time
20
+ else
21
+ raise ArgumentError, "time argument must be a Numeric or Time object"
22
+ end.to_i
35
23
  end
36
24
 
37
- def convert_to_mixed_case(string_like)
38
- string_like.to_s.
39
- split('_').
40
- each_with_index.map do |str, index|
41
- if index > 0
42
- str.capitalize
43
- else
44
- str
45
- end
46
- end.
47
- join
25
+ def as_time_from_epoch(time, granularity: :ms)
26
+ case time
27
+ when Numeric
28
+ Time.at(time / multiplier_from_granularity(granularity))
29
+ when Time
30
+ time
31
+ else
32
+ raise ArgumentError, "time argument must be a Numeric or Time object"
33
+ end
48
34
  end
49
35
 
50
- def convert_to_snake_case(string_like)
51
- string_like.to_s.gsub(/::/, '/').
52
- gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
53
- gsub(/([a-z\d])([A-Z])/,'\1_\2').
54
- tr("-", "_").
55
- downcase
36
+ def multiplier_from_granularity(granularity)
37
+ case granularity
38
+ when :ms # milliseconds
39
+ 1_000.0
40
+ when :s # seconds
41
+ 1.0
42
+ else
43
+ raise ArgumentError, "invalid granularity"
44
+ end
56
45
  end
57
46
  end
58
47
  end
@@ -1,6 +1,7 @@
1
1
  module Ably
2
2
  module Realtime
3
3
  class Channel
4
+ include Ably::Modules::Conversions
4
5
  include Callbacks
5
6
 
6
7
  STATES = {
@@ -52,7 +53,7 @@ module Ably
52
53
  end
53
54
 
54
55
  def publish(event, data)
55
- queue << { name: event, data: data, timestamp: Time.now.to_i * 1000 }
56
+ queue << { name: event, data: data, timestamp: as_since_epoch(Time.now) }
56
57
 
57
58
  if attached?
58
59
  process_queue
@@ -1,6 +1,7 @@
1
1
  module Ably
2
2
  module Realtime
3
3
  class Connection < EventMachine::Connection
4
+ include Ably::Modules::Conversions
4
5
  include Callbacks
5
6
 
6
7
  def initialize(client)
@@ -41,7 +42,7 @@ module Ably
41
42
  # WebSocket::Driver interface
42
43
  def url
43
44
  URI(client.endpoint).tap do |endpoint|
44
- endpoint.query = URI.encode_www_form(client.auth.auth_params.merge(timestamp: Time.now.to_i, binary: false))
45
+ endpoint.query = URI.encode_www_form(client.auth.auth_params.merge(timestamp: as_since_epoch(Time.now), binary: false))
45
46
  end.to_s
46
47
  end
47
48
 
@@ -52,17 +53,17 @@ module Ably
52
53
  private
53
54
  attr_reader :client, :driver, :message_serial
54
55
 
55
- def add_message_serial_if_ack_required_to(data)
56
- if Models::ProtocolMessage.ack_required?(data[:action])
57
- add_message_serial_to(data) { yield }
56
+ def add_message_serial_if_ack_required_to(protocol_message)
57
+ if Models::ProtocolMessage.ack_required?(protocol_message[:action])
58
+ add_message_serial_to(protocol_message) { yield }
58
59
  else
59
60
  yield
60
61
  end
61
62
  end
62
63
 
63
- def add_message_serial_to(data)
64
+ def add_message_serial_to(protocol_message)
64
65
  @message_serial += 1
65
- data.merge!(msg_serial: @message_serial)
66
+ protocol_message[:msgSerial] = @message_serial
66
67
  yield
67
68
  rescue StandardError => e
68
69
  @message_serial -= 1
@@ -17,7 +17,7 @@ module Ably::Realtime::Models
17
17
 
18
18
  def initialize(json_object)
19
19
  @raw_json_object = json_object
20
- @json_object = rubify(@raw_json_object).freeze
20
+ @json_object = IdiomaticRubyWrapper(@raw_json_object.clone.freeze)
21
21
  end
22
22
 
23
23
  %w( message code status ).each do |attribute|
@@ -24,7 +24,7 @@ module Ably::Realtime::Models
24
24
  def initialize(json_object, protocol_message)
25
25
  @protocol_message = protocol_message
26
26
  @raw_json_object = json_object
27
- @json_object = rubify(@raw_json_object, ignore: [:data]).freeze
27
+ @json_object = IdiomaticRubyWrapper(@raw_json_object.clone.freeze, stop_at: [:data])
28
28
  end
29
29
 
30
30
  %w( name client_id ).each do |attribute|
@@ -42,7 +42,7 @@ module Ably::Realtime::Models
42
42
  end
43
43
 
44
44
  def sender_timestamp
45
- Time.at(json[:timestamp] / 1000.0) if json[:timestamp]
45
+ as_time_from_epoch(json[:timestamp]) if json[:timestamp]
46
46
  end
47
47
 
48
48
  def ably_timestamp
@@ -56,14 +56,12 @@ module Ably::Realtime::Models
56
56
  def to_json_object
57
57
  raise RuntimeError, ":name is missing, cannot generate valid JSON for Message" unless name
58
58
 
59
- json_object = json.dup.tap do |json_object|
60
- json_object[:timestamp] = Time.now.to_i * 1000 unless sender_timestamp
59
+ json.dup.tap do |json_object|
60
+ json_object[:timestamp] = as_since_epoch(Time.now) unless sender_timestamp
61
61
  end
62
-
63
- javify(json_object)
64
62
  end
65
63
 
66
- def to_json
64
+ def to_json(*args)
67
65
  to_json_object.to_json
68
66
  end
69
67