ably 0.8.15 → 1.0.0

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 (98) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +6 -4
  3. data/CHANGELOG.md +6 -2
  4. data/README.md +5 -1
  5. data/SPEC.md +1473 -852
  6. data/ably.gemspec +11 -8
  7. data/lib/ably/auth.rb +90 -53
  8. data/lib/ably/exceptions.rb +37 -8
  9. data/lib/ably/logger.rb +10 -1
  10. data/lib/ably/models/auth_details.rb +42 -0
  11. data/lib/ably/models/channel_state_change.rb +18 -4
  12. data/lib/ably/models/connection_details.rb +6 -3
  13. data/lib/ably/models/connection_state_change.rb +4 -3
  14. data/lib/ably/models/error_info.rb +1 -1
  15. data/lib/ably/models/message.rb +17 -1
  16. data/lib/ably/models/message_encoders/base.rb +103 -82
  17. data/lib/ably/models/message_encoders/base64.rb +1 -1
  18. data/lib/ably/models/presence_message.rb +16 -1
  19. data/lib/ably/models/protocol_message.rb +20 -3
  20. data/lib/ably/models/token_details.rb +11 -1
  21. data/lib/ably/models/token_request.rb +16 -6
  22. data/lib/ably/modules/async_wrapper.rb +7 -3
  23. data/lib/ably/modules/encodeable.rb +51 -12
  24. data/lib/ably/modules/enum.rb +17 -7
  25. data/lib/ably/modules/event_emitter.rb +29 -14
  26. data/lib/ably/modules/model_common.rb +13 -21
  27. data/lib/ably/modules/state_emitter.rb +7 -4
  28. data/lib/ably/modules/state_machine.rb +2 -4
  29. data/lib/ably/modules/uses_state_machine.rb +7 -3
  30. data/lib/ably/realtime.rb +2 -0
  31. data/lib/ably/realtime/auth.rb +102 -42
  32. data/lib/ably/realtime/channel.rb +68 -26
  33. data/lib/ably/realtime/channel/channel_manager.rb +154 -65
  34. data/lib/ably/realtime/channel/channel_state_machine.rb +14 -15
  35. data/lib/ably/realtime/client.rb +18 -3
  36. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +38 -29
  37. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +6 -1
  38. data/lib/ably/realtime/connection.rb +108 -49
  39. data/lib/ably/realtime/connection/connection_manager.rb +167 -61
  40. data/lib/ably/realtime/connection/connection_state_machine.rb +22 -3
  41. data/lib/ably/realtime/connection/websocket_transport.rb +19 -10
  42. data/lib/ably/realtime/presence.rb +70 -45
  43. data/lib/ably/realtime/presence/members_map.rb +201 -36
  44. data/lib/ably/realtime/presence/presence_manager.rb +30 -6
  45. data/lib/ably/realtime/presence/presence_state_machine.rb +5 -12
  46. data/lib/ably/rest.rb +2 -2
  47. data/lib/ably/rest/channel.rb +5 -5
  48. data/lib/ably/rest/client.rb +31 -27
  49. data/lib/ably/rest/middleware/exceptions.rb +1 -3
  50. data/lib/ably/rest/middleware/logger.rb +2 -2
  51. data/lib/ably/rest/presence.rb +2 -2
  52. data/lib/ably/util/pub_sub.rb +1 -1
  53. data/lib/ably/util/safe_deferrable.rb +26 -0
  54. data/lib/ably/version.rb +2 -2
  55. data/spec/acceptance/realtime/auth_spec.rb +470 -111
  56. data/spec/acceptance/realtime/channel_history_spec.rb +5 -3
  57. data/spec/acceptance/realtime/channel_spec.rb +1017 -168
  58. data/spec/acceptance/realtime/client_spec.rb +6 -6
  59. data/spec/acceptance/realtime/connection_failures_spec.rb +458 -27
  60. data/spec/acceptance/realtime/connection_spec.rb +424 -105
  61. data/spec/acceptance/realtime/message_spec.rb +52 -23
  62. data/spec/acceptance/realtime/presence_history_spec.rb +5 -3
  63. data/spec/acceptance/realtime/presence_spec.rb +1110 -96
  64. data/spec/acceptance/rest/auth_spec.rb +222 -59
  65. data/spec/acceptance/rest/base_spec.rb +1 -1
  66. data/spec/acceptance/rest/channel_spec.rb +1 -2
  67. data/spec/acceptance/rest/client_spec.rb +104 -48
  68. data/spec/acceptance/rest/message_spec.rb +42 -15
  69. data/spec/acceptance/rest/presence_spec.rb +4 -11
  70. data/spec/rspec_config.rb +2 -1
  71. data/spec/shared/client_initializer_behaviour.rb +2 -2
  72. data/spec/shared/safe_deferrable_behaviour.rb +6 -2
  73. data/spec/spec_helper.rb +4 -2
  74. data/spec/support/debug_failure_helper.rb +20 -4
  75. data/spec/support/event_machine_helper.rb +32 -1
  76. data/spec/unit/auth_spec.rb +4 -11
  77. data/spec/unit/logger_spec.rb +28 -2
  78. data/spec/unit/models/auth_details_spec.rb +49 -0
  79. data/spec/unit/models/channel_state_change_spec.rb +23 -3
  80. data/spec/unit/models/connection_details_spec.rb +12 -1
  81. data/spec/unit/models/connection_state_change_spec.rb +15 -4
  82. data/spec/unit/models/message_encoders/base64_spec.rb +2 -1
  83. data/spec/unit/models/message_spec.rb +153 -0
  84. data/spec/unit/models/presence_message_spec.rb +192 -0
  85. data/spec/unit/models/protocol_message_spec.rb +64 -6
  86. data/spec/unit/models/token_details_spec.rb +75 -0
  87. data/spec/unit/models/token_request_spec.rb +74 -0
  88. data/spec/unit/modules/async_wrapper_spec.rb +2 -1
  89. data/spec/unit/modules/enum_spec.rb +69 -0
  90. data/spec/unit/modules/event_emitter_spec.rb +149 -22
  91. data/spec/unit/modules/state_emitter_spec.rb +9 -3
  92. data/spec/unit/realtime/client_spec.rb +1 -1
  93. data/spec/unit/realtime/connection_spec.rb +8 -5
  94. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +1 -1
  95. data/spec/unit/realtime/presence_spec.rb +4 -3
  96. data/spec/unit/rest/client_spec.rb +1 -1
  97. data/spec/unit/util/crypto_spec.rb +3 -3
  98. metadata +22 -19
@@ -8,6 +8,8 @@ module Ably::Models
8
8
  #
9
9
  # @!attribute [r] action
10
10
  # @return [ACTION] Protocol Message action {Ably::Modules::Enum} from list of {ACTION}. Returns nil if action is unsupported by protocol
11
+ # @!attribute [r] auth
12
+ # @return [Ably::Models::AuthDetails] Authentication details used to perform authentication upgrades over an existing transport
11
13
  # @!attribute [r] count
12
14
  # @return [Integer] The count field is used for ACK and NACK actions. See {http://docs.ably.io/client-lib-development-guide/protocol/#message-acknowledgement message acknowledgement protocol}
13
15
  # @!attribute [r] error
@@ -15,7 +17,7 @@ module Ably::Models
15
17
  # @!attribute [r] channel
16
18
  # @return [String] Channel name for messages
17
19
  # @!attribute [r] channel_serial
18
- # @return [String] Contains a serial number for a message on the current channelƒ
20
+ # @return [String] Contains a serial number for a message on the current channel
19
21
  # @!attribute [r] connection_id
20
22
  # @return [String] Contains a string public identifier for the connection
21
23
  # @!attribute [r] connection_key
@@ -62,13 +64,14 @@ module Ably::Models
62
64
  detached: 13,
63
65
  presence: 14,
64
66
  message: 15,
65
- sync: 16
67
+ sync: 16,
68
+ auth: 17
66
69
  )
67
70
 
68
71
  # Indicates this protocol message action will generate an ACK response such as :message or :presence
69
72
  # @api private
70
73
  def self.ack_required?(for_action)
71
- [ACTION.Presence, ACTION.Message].include?(ACTION(for_action))
74
+ ACTION(for_action).match_any?(ACTION.Presence, ACTION.Message)
72
75
  end
73
76
 
74
77
  # {ProtocolMessage} initializer
@@ -193,10 +196,24 @@ module Ably::Models
193
196
  flags & 1 == 1
194
197
  end
195
198
 
199
+ # @api private
200
+ def has_backlog_flag?
201
+ flags & 2 == 2
202
+ end
203
+
204
+ # @api private
205
+ def has_channel_resumed_flag?
206
+ flags & 4 == 4
207
+ end
208
+
196
209
  def connection_details
197
210
  @connection_details ||= Ably::Models::ConnectionDetails(attributes[:connection_details])
198
211
  end
199
212
 
213
+ def auth
214
+ @auth ||= Ably::Models::AuthDetails(attributes[:auth])
215
+ end
216
+
200
217
  # Indicates this protocol message will generate an ACK response when sent
201
218
  # Examples of protocol messages required ACK include :message and :presence
202
219
  # @api private
@@ -73,7 +73,17 @@ module Ably::Models
73
73
  # @!attribute [r] capability
74
74
  # @return [Hash] Capabilities assigned to this token
75
75
  def capability
76
- JSON.parse(attributes.fetch(:capability)) if attributes.has_key?(:capability)
76
+ if attributes.has_key?(:capability)
77
+ capability_val = attributes.fetch(:capability)
78
+ case capability_val
79
+ when Hash
80
+ capability_val
81
+ when Ably::Models::IdiomaticRubyWrapper
82
+ capability_val.as_json
83
+ else
84
+ JSON.parse(attributes.fetch(:capability))
85
+ end
86
+ end
77
87
  end
78
88
 
79
89
  # @!attribute [r] client_id
@@ -42,7 +42,7 @@ module Ably::Models
42
42
  # @!attribute [r] key_name
43
43
  # @return [String] API key name of the key against which this request is made. An API key is made up of an API key name and secret delimited by a +:+
44
44
  def key_name
45
- attributes.fetch(:key_name)
45
+ attributes.fetch(:key_name) { raise Ably::Exceptions::InvalidTokenRequest, 'Key name is missing' }
46
46
  end
47
47
 
48
48
  # @!attribute [r] ttl
@@ -59,7 +59,16 @@ module Ably::Models
59
59
  # the capability of the returned token will be the intersection of
60
60
  # this capability with the capability of the issuing key.
61
61
  def capability
62
- JSON.parse(attributes.fetch(:capability))
62
+ capability_val = attributes.fetch(:capability) { raise Ably::Exceptions::InvalidTokenRequest, 'Capability is missing' }
63
+
64
+ case capability_val
65
+ when Hash
66
+ capability_val
67
+ when Ably::Models::IdiomaticRubyWrapper
68
+ capability_val.as_json
69
+ else
70
+ JSON.parse(attributes.fetch(:capability))
71
+ end
63
72
  end
64
73
 
65
74
  # @!attribute [r] client_id
@@ -75,7 +84,8 @@ module Ably::Models
75
84
  # token requests from being replayed.
76
85
  # Timestamp when sent to Ably is in milliseconds.
77
86
  def timestamp
78
- as_time_from_epoch(attributes.fetch(:timestamp), granularity: :ms)
87
+ timestamp_val = attributes.fetch(:timestamp) { raise Ably::Exceptions::InvalidTokenRequest, 'Timestamp is missing' }
88
+ as_time_from_epoch(timestamp_val, granularity: :ms)
79
89
  end
80
90
 
81
91
  # @!attribute [r] nonce
@@ -83,21 +93,21 @@ module Ably::Models
83
93
  # uniqueness of this request. Any subsequent request using the
84
94
  # same nonce will be rejected.
85
95
  def nonce
86
- attributes.fetch(:nonce)
96
+ attributes.fetch(:nonce) { raise Ably::Exceptions::InvalidTokenRequest, 'Nonce is missing' }
87
97
  end
88
98
 
89
99
  # @!attribute [r] mac
90
100
  # @return [String] the Message Authentication Code for this request. See the
91
101
  # {https://www.ably.io/documentation Ably Authentication documentation} for more details.
92
102
  def mac
93
- attributes.fetch(:mac)
103
+ attributes.fetch(:mac) { raise Ably::Exceptions::InvalidTokenRequest, 'MAC is missing' }
94
104
  end
95
105
 
96
106
  # Requests that the token is always persisted
97
107
  # @api private
98
108
  #
99
109
  def persisted
100
- attributes.fetch(:persisted)
110
+ attributes[:persisted]
101
111
  end
102
112
 
103
113
  # @!attribute [r] attributes
@@ -38,7 +38,7 @@ module Ably::Modules
38
38
  # @yield [Object] operation block that is run in a thread
39
39
  # @return [Ably::Util::SafeDeferrable]
40
40
  #
41
- def async_wrap(success_callback = nil)
41
+ def async_wrap(success_callback = nil, custom_error_handling = nil)
42
42
  raise ArgumentError, 'Block required' unless block_given?
43
43
 
44
44
  Ably::Util::SafeDeferrable.new(logger).tap do |deferrable|
@@ -48,8 +48,12 @@ module Ably::Modules
48
48
  begin
49
49
  yield
50
50
  rescue StandardError => err
51
- logger.error "An exception in an AsyncWrapper block was caught. #{err.class}: #{err.message}\n#{err.backtrace.join("\n")}"
52
- deferrable.fail err
51
+ if custom_error_handling
52
+ custom_error_handling.call err, deferrable
53
+ else
54
+ logger.error { "An exception in an AsyncWrapper block was caught. #{err.class}: #{err.message}\n#{err.backtrace.join("\n")}" }
55
+ deferrable.fail err
56
+ end
53
57
  end
54
58
  end
55
59
 
@@ -10,20 +10,60 @@ module Ably::Modules
10
10
  # - A #raw_hash_object attribute that returns the original hash object used to create this object
11
11
  #
12
12
  module Encodeable
13
+ def self.included(base)
14
+ base.extend(ClassMethods)
15
+ end
16
+
17
+ module ClassMethods
18
+ # Return a Message or Presence object from the encoded JSON-like object, using the optional channel options
19
+ # @param message_object [Hash] JSON-like object representation of an encoded message
20
+ # @param channel_options [Hash] Channel options, currently reserved for Encryption options
21
+ # @yield [Ably::Exceptions::BaseAblyException] yields an Ably exception if decoding fails
22
+ # @return [Message,Presence]
23
+ def from_encoded(message_object, channel_options = {}, &error_block)
24
+ new(message_object).tap do |message|
25
+ message.decode(encoders, channel_options, &error_block)
26
+ end
27
+ end
28
+
29
+ # Return an Array of Message or Presence objects from the encoded Array of JSON-like objects, using the optional channel options
30
+ # @param message_objects [Array<Hash>] Array of JSON-like objects with encoded messages
31
+ # @param channel_options [Hash] Channel options, currently reserved for Encryption options
32
+ # @return [Array<Message,Presence>]
33
+ def from_encoded_array(message_object_array, channel_options = {})
34
+ Array(message_object_array).map do |message_object|
35
+ from_encoded(message_object, channel_options)
36
+ end
37
+ end
38
+
39
+ # Register an encoder for this object
40
+ # @api private
41
+ def register_encoder(encoder, options = {})
42
+ encoders << Ably::Models::MessageEncoders.encoder_from(encoder, options)
43
+ end
44
+
45
+ private
46
+ def encoders
47
+ @encoders ||= []
48
+ end
49
+ end
50
+
13
51
  # Encode a message using the channel options and register encoders for the client
14
- # @param channel [Ably::Realtime::Channel]
52
+ # @param encoders [Array<Ably::Models::MessageEncoders::Base>] List of encoders to apply to the message
53
+ # @param channel_options [Hash] Channel options, currently reserved for Encryption options
15
54
  # @return [void]
16
55
  # @api private
17
- def encode(channel)
18
- apply_encoders :encode, channel
56
+ def encode(encoders, channel_options, &error_block)
57
+ apply_encoders :encode, encoders, channel_options, &error_block
19
58
  end
20
59
 
21
60
  # Decode a message using the channel options and registered encoders for the client
22
- # @param channel [Ably::Realtime::Channel]
61
+ # @param encoders [Array<Ably::Models::MessageEncoders::Base>] List of encoders to apply to the message
62
+ # @param channel_options [Hash] Channel options, currently reserved for Encryption options
23
63
  # @return [void]
24
64
  # @api private
25
- def decode(channel)
26
- apply_encoders :decode, channel
65
+ def decode(encoders, channel_options, &error_block)
66
+ apply_encoders :decode, encoders, channel_options, &error_block
27
67
  end
28
68
 
29
69
  # The original encoding of this message when it was received as a raw message from the Ably service
@@ -44,7 +84,7 @@ module Ably::Modules
44
84
  end
45
85
  end
46
86
 
47
- def apply_encoders(method, channel)
87
+ def apply_encoders(method, encoders, channel_options, &error_callback)
48
88
  max_encoding_length = 512
49
89
  message_attributes = attributes.dup
50
90
 
@@ -54,16 +94,15 @@ module Ably::Modules
54
94
  end
55
95
 
56
96
  previous_encoding = message_attributes[:encoding]
57
- channel.client.encoders.each do |encoder|
58
- encoder.send method, message_attributes, channel.options
97
+ encoders.each do |encoder|
98
+ encoder.send method, message_attributes, channel_options
59
99
  end
60
100
  end until previous_encoding == message_attributes[:encoding]
61
101
 
62
102
  set_attributes_object message_attributes
63
103
  rescue Ably::Exceptions::CipherError => cipher_error
64
- if channel.respond_to?(:emit)
65
- channel.client.logger.error "Encoder error #{cipher_error.code} trying to #{method} message: #{cipher_error.message}"
66
- channel.emit :error, cipher_error
104
+ if block_given?
105
+ yield cipher_error, "Encoder error #{cipher_error.code} trying to #{method} message: #{cipher_error.message}"
67
106
  else
68
107
  raise cipher_error
69
108
  end
@@ -49,11 +49,11 @@ module Ably::Modules
49
49
  def get(identifier)
50
50
  case identifier
51
51
  when Symbol
52
- by_symbol.fetch(identifier)
52
+ by_symbol.fetch(identifier) { raise KeyError, "#{name} key not found: :#{identifier}" }
53
53
  when String
54
- by_symbol.fetch(convert_to_snake_case_symbol(identifier))
54
+ by_symbol.fetch(convert_to_snake_case_symbol(identifier)) { raise KeyError, "#{name} key not found: '#{identifier}'" }
55
55
  when Numeric
56
- by_index.fetch(identifier)
56
+ by_index.fetch(identifier) { raise KeyError, "#{name} key not found: #{identifier}" }
57
57
  when ancestors.first
58
58
  identifier
59
59
  else
@@ -89,6 +89,12 @@ module Ably::Modules
89
89
  @enum_name
90
90
  end
91
91
 
92
+ # Array of Enum values as symbols
93
+ # @return [Array<Symbol>]
94
+ def to_sym_arr
95
+ @by_symbol.keys
96
+ end
97
+
92
98
  private
93
99
  def by_index
94
100
  @by_index
@@ -158,7 +164,7 @@ module Ably::Modules
158
164
 
159
165
  # Allow comparison of Enum objects based on:
160
166
  #
161
- # * Other equivalent Enum objects
167
+ # * Other equivalent Enum objects compared by Symbol (not Integer value)
162
168
  # * Symbol
163
169
  # * String
164
170
  # * Integer index of Enum
@@ -171,13 +177,17 @@ module Ably::Modules
171
177
  self.to_sym == convert_to_snake_case_symbol(other)
172
178
  when Numeric
173
179
  self.to_i == other.to_i
174
- when self.class
175
- self.to_i == other.to_i
176
180
  else
177
- false
181
+ if other.kind_of?(Ably::Modules::Enum::Base)
182
+ self.to_sym == other.to_sym
183
+ end
178
184
  end
179
185
  end
180
186
 
187
+ def match_any?(*enums)
188
+ enums.any? { |enum| self.==(enum) }
189
+ end
190
+
181
191
  private
182
192
  def name
183
193
  @name
@@ -54,7 +54,7 @@ module Ably
54
54
  end
55
55
 
56
56
  # Equivalent of {#on} but any exception raised in a block will bubble up and cause this client library to fail.
57
- # This method should only be used internally by the client library.
57
+ # This method is designed to be used internally by the client library.
58
58
  # @api private
59
59
  def unsafe_on(*event_names, &block)
60
60
  add_callback event_names, proc_for_block(block, unsafe: true)
@@ -70,7 +70,7 @@ module Ably
70
70
  end
71
71
 
72
72
  # Equivalent of {#once} but any exception raised in a block will bubble up and cause this client library to fail.
73
- # This method should only be used internally by the client library.
73
+ # This method is designed to be used internally by the client library.
74
74
  # @api private
75
75
  def unsafe_once(*event_names, &block)
76
76
  add_callback event_names, proc_for_block(block, delete_once_run: true, unsafe: true)
@@ -101,30 +101,45 @@ module Ably
101
101
  #
102
102
  # @return [void]
103
103
  def off(*event_names, &block)
104
+ off_internal(false, *event_names, &block)
105
+ end
106
+
107
+ # Equivalent of {#off} but only unsafe listeners are removed.
108
+ # This method is designed to be used internally by the client library.
109
+ # @api private
110
+ def unsafe_off(*event_names, &block)
111
+ off_internal(true, *event_names, &block)
112
+ end
113
+
114
+ private
115
+ def off_internal(unsafe, *event_names, &block)
104
116
  keys = if event_names.empty?
105
117
  callbacks.keys
106
118
  else
107
119
  event_names
108
120
  end
109
121
 
110
- keys.each do |event_name|
111
- if block_given?
112
- callbacks[callbacks_event_coerced(event_name)].delete_if { |proc_hash| proc_hash[:block] == block }
113
- else
114
- callbacks[callbacks_event_coerced(event_name)].clear
122
+ if event_names.empty?
123
+ callbacks_any.delete_if do |proc_hash|
124
+ if block_given?
125
+ (proc_hash[:unsafe] == unsafe) && (proc_hash[:block] == block)
126
+ else
127
+ proc_hash[:unsafe] == unsafe
128
+ end
115
129
  end
116
130
  end
117
131
 
118
- if event_names.empty?
119
- if block_given?
120
- callbacks_any.delete_if { |proc_hash| proc_hash[:block] == block }
121
- else
122
- callbacks_any.clear
132
+ keys.each do |event_name|
133
+ callbacks[callbacks_event_coerced(event_name)].delete_if do |proc_hash|
134
+ if block_given?
135
+ (proc_hash[:unsafe] == unsafe) && (proc_hash[:block] == block)
136
+ else
137
+ proc_hash[:unsafe] == unsafe
138
+ end
123
139
  end
124
140
  end
125
141
  end
126
142
 
127
- private
128
143
  def self.included(klass)
129
144
  klass.extend ClassMethods
130
145
  end
@@ -148,7 +163,7 @@ module Ably
148
163
  true if options[:delete_once_run]
149
164
  end,
150
165
  block: block,
151
- unsafe: options[:unsafe]
166
+ unsafe: options[:unsafe] || false
152
167
  }
153
168
  end
154
169
 
@@ -8,6 +8,10 @@ module Ably::Modules
8
8
  include Conversions
9
9
  include MessagePack
10
10
 
11
+ def self.included(base)
12
+ base.extend(ClassMethods)
13
+ end
14
+
11
15
  # Provide a normal Hash accessor to the underlying raw message object
12
16
  #
13
17
  # @return [Object]
@@ -32,38 +36,26 @@ module Ably::Modules
32
36
  as_json.to_json(*args)
33
37
  end
34
38
 
35
- # Like to_json but encodes all binary fields to hex
36
- def to_safe_json(*args)
37
- as_json.
38
- each_with_object({}) do |(key, val), obj|
39
- obj[key] = to_safe_jsonable_val(val)
40
- end.to_json(*args)
41
- end
42
-
43
39
  # @!attribute [r] hash
44
40
  # @return [Integer] Compute a hash-code for this hash. Two hashes with the same content will have the same hash code
45
41
  def hash
46
42
  attributes.hash
47
43
  end
48
44
 
49
- private
50
- def to_safe_jsonable_val(val)
51
- case val
52
- when Array
53
- val.map { |array_val| to_safe_jsonable_val(array_val) }
54
- when Hash
55
- val.each_with_object({}) { |(key, hash_val), obj| obj[key] = to_safe_jsonable_val(hash_val) }
56
- when String
57
- if val.encoding == Encoding::ASCII_8BIT
58
- val.unpack("H*").first
45
+ module ClassMethods
46
+ # Return a new instance of this object using the provided JSON-like object or JSON string
47
+ # @param [Hash, String] JSON-like object or JSON string
48
+ # @return a new instance o this object
49
+ def from_json(json_like_object)
50
+ if json_like_object.kind_of?(String)
51
+ new(JSON.parse(json_like_object))
59
52
  else
60
- val
53
+ new(json_like_object)
61
54
  end
62
- else
63
- val
64
55
  end
65
56
  end
66
57
 
58
+ private
67
59
  def ensure_utf8_string_for(attribute, value)
68
60
  if value
69
61
  raise ArgumentError, "#{attribute} must be a String" unless value.kind_of?(String)