Class: Ably::Models::IdiomaticRubyWrapper

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/ably/models/idiomatic_ruby_wrapper.rb

Overview

Note:

It is recommended you include Ably::Modules::Conversions so that you can use the object creation syntax ‘IdiomaticRubyWrappers(hash_or_another_idiomatic_ruby_wrapper)`

ruby_hash # => ‘true’ ruby_hash.key_value # => ‘true’ ruby_hash = ‘new_value’ ruby_hash.key_value # => ‘new_value’

ruby_hash # => nil ruby_hash.none # => nil

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(mixedCaseJsonObject, stop_at: []) ⇒ IdiomaticRubyWrapper

Creates an IdiomaticRubyWrapper around the mixed case JSON object



36
37
38
39
40
41
42
43
44
45
# File 'lib/ably/models/idiomatic_ruby_wrapper.rb', line 36

def initialize(mixedCaseJsonObject, stop_at: [])
  if mixedCaseJsonObject.kind_of?(IdiomaticRubyWrapper)
    $stderr.puts "<IdiomaticRubyWrapper#initialize> WARNING: Wrapping a IdiomaticRubyWrapper with another IdiomaticRubyWrapper"
  end

  @json = mixedCaseJsonObject
  @stop_at = Array(stop_at).each_with_object({}) do |key, hash|
    hash[convert_to_snake_case_symbol(key)] = true
  end.freeze
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_sym, *arguments) ⇒ Object



111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/ably/models/idiomatic_ruby_wrapper.rb', line 111

def method_missing(method_sym, *arguments)
  key = method_sym.to_s.gsub(%r{=$}, '')
  return super if !has_key?(key)

  if method_sym.to_s.match(%r{=$})
    raise ArgumentError, "Cannot set #{method_sym} with more than one argument" unless arguments.length == 1
    self[key] = arguments.first
  else
    raise ArgumentError, "Cannot pass an argument to #{method_sym} when retrieving its value" unless arguments.empty?
    self[method_sym]
  end
end

Instance Attribute Details

#stop_atArray<Symbol,String> (readonly)

Returns array of keys that this wrapper should stop wrapping at to preserve the underlying JSON hash as is.

Returns:

  • (Array<Symbol,String>)

    array of keys that this wrapper should stop wrapping at to preserve the underlying JSON hash as is



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/ably/models/idiomatic_ruby_wrapper.rb', line 26

class IdiomaticRubyWrapper
  include Enumerable

  attr_reader :stop_at

  # Creates an IdiomaticRubyWrapper around the mixed case JSON object
  #
  # @attribute [Hash] mixedCaseJsonObject mixed case JSON object
  # @attribute [Array<Symbol,String>] stop_at array of keys that this wrapper should stop wrapping at to preserve the underlying JSON hash as is
  #
  def initialize(mixedCaseJsonObject, stop_at: [])
    if mixedCaseJsonObject.kind_of?(IdiomaticRubyWrapper)
      $stderr.puts "<IdiomaticRubyWrapper#initialize> WARNING: Wrapping a IdiomaticRubyWrapper with another IdiomaticRubyWrapper"
    end

    @json = mixedCaseJsonObject
    @stop_at = Array(stop_at).each_with_object({}) do |key, hash|
      hash[convert_to_snake_case_symbol(key)] = true
    end.freeze
  end

  def [](key)
    value = json[source_key_for(key)]
    if stop_at?(key) || !value.kind_of?(Hash)
      value
    else
      IdiomaticRubyWrapper.new(value, stop_at: stop_at)
    end
  end

  def []=(key, value)
    json[source_key_for(key)] = value
  end

  def fetch(key, default = nil, &missing_block)
    if has_key?(key)
      self[key]
    else
      if default
        default
      elsif block_given?
        yield key
      else
        raise KeyError, "key not found: #{key}"
      end
    end
  end

  def size
    json.size
  end

  def keys
    map { |key, value| key }
  end

  def values
    map { |key, value| value }
  end

  def has_key?(key)
    json.has_key?(source_key_for(key))
  end

  # Method ensuring this {IdiomaticRubyWrapper} is {http://ruby-doc.org/core-2.1.3/Enumerable.html Enumerable}
  def each(&block)
    json.each do |key, value|
      key = convert_to_snake_case_symbol(key)
      value = self[key]
      if block_given?
        block.call key, value
      else
        yield key, value
      end
    end
  end

  # Compare object based on Hash equivalent
  def ==(other)
    return false unless other.kind_of?(self.class) || other.kind_of?(Hash)

    other = other.to_hash if other.kind_of?(self.class)
    to_hash == other
  end

  def method_missing(method_sym, *arguments)
    key = method_sym.to_s.gsub(%r{=$}, '')
    return super if !has_key?(key)

    if method_sym.to_s.match(%r{=$})
      raise ArgumentError, "Cannot set #{method_sym} with more than one argument" unless arguments.length == 1
      self[key] = arguments.first
    else
      raise ArgumentError, "Cannot pass an argument to #{method_sym} when retrieving its value" unless arguments.empty?
      self[method_sym]
    end
  end

  # Access to the raw JSON object provided to the constructer of this wrapper
  def json
    @json
  end

  # Converts the current wrapped mixedCase object to a JSON string
  # using the provided mixedCase syntax
  def to_json(*args)
    json.to_json
  end

  # Generate a symbolized Hash object representing the underlying JSON in a Ruby friendly format
  def to_hash
    each_with_object({}) do |key_val, hash|
      key, val = key_val
      hash[key] = val
    end
  end

  # Method to create a duplicate of the underlying JSON object
  # Useful when underlying JSON is frozen
  def dup
    Ably::Models::IdiomaticRubyWrapper.new(json.dup)
  end

  private
  def stop_at?(key)
    @stop_at.has_key?(key)
  end

  # We assume by default all keys are interchangeable between :this_format and 'thisFormat'
  # However, this method will find other fallback formats such as CamelCase or :symbols if a matching
  # key is not found in mixedCase.
  def source_key_for(symbolized_key)
    format_preferences = [
      -> (key_sym) { convert_to_mixed_case(key_sym) },
      -> (key_sym) { key_sym.to_sym },
      -> (key_sym) { key_sym.to_s },
      -> (key_sym) { convert_to_mixed_case(key_sym).to_sym },
      -> (key_sym) { convert_to_lower_case(key_sym) },
      -> (key_sym) { convert_to_lower_case(key_sym).to_sym },
      -> (key_sym) { convert_to_mixed_case(key_sym, force_camel: true) },
      -> (key_sym) { convert_to_mixed_case(key_sym, force_camel: true).to_sym }
    ]

    preferred_format = format_preferences.detect do |format|
      json.has_key?(format.call(symbolized_key))
    end || format_preferences.first

    preferred_format.call(symbolized_key)
  end

  # Convert key to mixedCase from mixed_case
  def convert_to_mixed_case(key, force_camel: false)
    key.to_s.
      split('_').
      each_with_index.map do |str, index|
        if index > 0 || force_camel
          str.capitalize
        else
          str
        end
      end.
      join
  end

  # Convert key to :snake_case from snakeCase
  def convert_to_snake_case_symbol(key)
    key.to_s.gsub(/::/, '/').
      gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
      gsub(/([a-z\d])([A-Z])/,'\1_\2').
      tr("-", "_").
      downcase.
      to_sym
  end

  def convert_to_lower_case(key)
    key.to_s.gsub('_', '')
  end
end

#stop_at array of keys that this wrapper should stop wrapping at to preserve the underlying JSON hash as is(arrayofkeysthatthiswrappershouldstopwrappingattopreservetheunderlyingJSONhashasis) ⇒ Object (readonly)

Creates an IdiomaticRubyWrapper around the mixed case JSON object



36
37
38
39
40
41
42
43
44
45
# File 'lib/ably/models/idiomatic_ruby_wrapper.rb', line 36

def initialize(mixedCaseJsonObject, stop_at: [])
  if mixedCaseJsonObject.kind_of?(IdiomaticRubyWrapper)
    $stderr.puts "<IdiomaticRubyWrapper#initialize> WARNING: Wrapping a IdiomaticRubyWrapper with another IdiomaticRubyWrapper"
  end

  @json = mixedCaseJsonObject
  @stop_at = Array(stop_at).each_with_object({}) do |key, hash|
    hash[convert_to_snake_case_symbol(key)] = true
  end.freeze
end

Instance Method Details

#==(other) ⇒ Object

Compare object based on Hash equivalent



104
105
106
107
108
109
# File 'lib/ably/models/idiomatic_ruby_wrapper.rb', line 104

def ==(other)
  return false unless other.kind_of?(self.class) || other.kind_of?(Hash)

  other = other.to_hash if other.kind_of?(self.class)
  to_hash == other
end

#[](key) ⇒ Object



47
48
49
50
51
52
53
54
# File 'lib/ably/models/idiomatic_ruby_wrapper.rb', line 47

def [](key)
  value = json[source_key_for(key)]
  if stop_at?(key) || !value.kind_of?(Hash)
    value
  else
    IdiomaticRubyWrapper.new(value, stop_at: stop_at)
  end
end

#[]=(key, value) ⇒ Object



56
57
58
# File 'lib/ably/models/idiomatic_ruby_wrapper.rb', line 56

def []=(key, value)
  json[source_key_for(key)] = value
end

#dupObject

Method to create a duplicate of the underlying JSON object Useful when underlying JSON is frozen



145
146
147
# File 'lib/ably/models/idiomatic_ruby_wrapper.rb', line 145

def dup
  Ably::Models::IdiomaticRubyWrapper.new(json.dup)
end

#each(&block) ⇒ Object



91
92
93
94
95
96
97
98
99
100
101
# File 'lib/ably/models/idiomatic_ruby_wrapper.rb', line 91

def each(&block)
  json.each do |key, value|
    key = convert_to_snake_case_symbol(key)
    value = self[key]
    if block_given?
      block.call key, value
    else
      yield key, value
    end
  end
end

#fetch(key, default = nil, &missing_block) ⇒ Object



60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/ably/models/idiomatic_ruby_wrapper.rb', line 60

def fetch(key, default = nil, &missing_block)
  if has_key?(key)
    self[key]
  else
    if default
      default
    elsif block_given?
      yield key
    else
      raise KeyError, "key not found: #{key}"
    end
  end
end

#has_key?(key) ⇒ Boolean

Returns:

  • (Boolean)


86
87
88
# File 'lib/ably/models/idiomatic_ruby_wrapper.rb', line 86

def has_key?(key)
  json.has_key?(source_key_for(key))
end

#jsonObject

Access to the raw JSON object provided to the constructer of this wrapper



125
126
127
# File 'lib/ably/models/idiomatic_ruby_wrapper.rb', line 125

def json
  @json
end

#keysObject



78
79
80
# File 'lib/ably/models/idiomatic_ruby_wrapper.rb', line 78

def keys
  map { |key, value| key }
end

#mixedCaseJsonObject mixed case JSON object=(mixed) ⇒ Object

Creates an IdiomaticRubyWrapper around the mixed case JSON object



36
37
38
39
40
41
42
43
44
45
# File 'lib/ably/models/idiomatic_ruby_wrapper.rb', line 36

def initialize(mixedCaseJsonObject, stop_at: [])
  if mixedCaseJsonObject.kind_of?(IdiomaticRubyWrapper)
    $stderr.puts "<IdiomaticRubyWrapper#initialize> WARNING: Wrapping a IdiomaticRubyWrapper with another IdiomaticRubyWrapper"
  end

  @json = mixedCaseJsonObject
  @stop_at = Array(stop_at).each_with_object({}) do |key, hash|
    hash[convert_to_snake_case_symbol(key)] = true
  end.freeze
end

#sizeObject



74
75
76
# File 'lib/ably/models/idiomatic_ruby_wrapper.rb', line 74

def size
  json.size
end

#to_hashObject

Generate a symbolized Hash object representing the underlying JSON in a Ruby friendly format



136
137
138
139
140
141
# File 'lib/ably/models/idiomatic_ruby_wrapper.rb', line 136

def to_hash
  each_with_object({}) do |key_val, hash|
    key, val = key_val
    hash[key] = val
  end
end

#to_json(*args) ⇒ Object

Converts the current wrapped mixedCase object to a JSON string using the provided mixedCase syntax



131
132
133
# File 'lib/ably/models/idiomatic_ruby_wrapper.rb', line 131

def to_json(*args)
  json.to_json
end

#valuesObject



82
83
84
# File 'lib/ably/models/idiomatic_ruby_wrapper.rb', line 82

def values
  map { |key, value| value }
end