Class: Amazon::SDB::Multimap

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/amazon_sdb/multimap.rb

Overview

A multimap is like a hash or set, but it only requires that key/value pair is unique (the same key may have multiple values). Multimaps may be created by the user to send into Amazon sdb or they may be read back from sdb as the attributes for an object.

For your convenience, multimap’s initializer can take several types of input parameters:

  • A hash of key/value pairs (for when you want keys to be unique)

  • An array of key/value pairs represented as 2-member arrays

  • Another multimap

  • Or nothing at all (an empty set)

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(init = nil) ⇒ Multimap

Returns a new instance of Multimap.



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/amazon_sdb/multimap.rb', line 24

def initialize(init=nil)
  @mset = {}

  clear_size!

  if init.nil?
    # do nothing
  elsif init.is_a? Hash
    init.each {|k, v| put(k, v) }
  elsif init.is_a? Array
    # load from array
    if init.any? {|v| ! v.is_a? Array || v.size != 2 }
      raise ArgumentError, "Array must be of key/value pairs only"
    end

    init.each {|v| self.put(v[0], v[1])}
  elsif init.is_a? Multimap
    @mset = init.mset.dup
  else
    raise ArgumentError, "Wrong type passed as initializer"
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_symbol, *args) ⇒ Object



261
262
263
264
265
266
267
268
269
270
271
272
# File 'lib/amazon_sdb/multimap.rb', line 261

def method_missing(method_symbol, *args)
  name = method_symbol.to_s
  if name =~ /^\w+$/
    if @mset.key? name
      get(name)
    else
      super
    end
  else
    super
  end
end

Class Method Details

.numeric(float, size, precision) ⇒ Object

To be honest, floats are difficult for sdb. In order to work with lexical comparisons, you need to save floats as strings padded to the same size. The problem is, automatic conversion can run afoul of rounding errors if it has a larger precision than the original float, so for the short term I’ve provided the numeric helper method for saving floats as strings into the multimap (when read back from sdb they will still be converted from floats). To use, specify the precision you want to represent as well as the total size (pick something large like 32 to be safe)



20
21
22
# File 'lib/amazon_sdb/multimap.rb', line 20

def self.numeric(float, size, precision)
  sprintf "%0#{size}.#{precision}f", float
end

Instance Method Details

#[](key) ⇒ Object

Shortcut for #get



115
116
117
# File 'lib/amazon_sdb/multimap.rb', line 115

def [](key)
  get(key)
end

#[]=(key, value) ⇒ Object

Shortcut for put(key, value, :replace => true)



121
122
123
# File 'lib/amazon_sdb/multimap.rb', line 121

def []=(key, value)
  put(key, value, :replace => true)
end

#clear_size!Object



47
48
49
# File 'lib/amazon_sdb/multimap.rb', line 47

def clear_size!
  @size = nil
end

#coerce(value) ⇒ Object



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/amazon_sdb/multimap.rb', line 209

def coerce(value)
  case value
  when 'true'
    true
  when 'false'
    false
  when /^0+\d+$/
    value.to_i
  when /^0+\d*.\d+$/
    value.to_f
  when /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}([+\-]\d{2}:\d{2})?)?$/
    Time.parse(value)
  else
    value
  end
end

#eachObject

Support for Enumerable. Yields each key/value pair as an array of 2 members.



127
128
129
130
131
132
133
# File 'lib/amazon_sdb/multimap.rb', line 127

def each
  @mset.each_pair do |key, group| 
    group.to_a.each do |value|
      yield [key, value]
    end
  end    
end

#each_pairObject

Yields each key/value pair as separate parameters to the block.



137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/amazon_sdb/multimap.rb', line 137

def each_pair
  @mset.each_pair do |key, group|
    case group
    when Array 
      group.each do |value|
        yield key, value
      end
    else
      yield key, group
    end
  end
end

#each_pair_with_indexObject

Yields each pair as separate key/value plus an index.



152
153
154
155
156
157
158
# File 'lib/amazon_sdb/multimap.rb', line 152

def each_pair_with_index
  index = 0
  self.each_pair do |key, value|
    yield key, value, index
    index += 1
  end
end

#from_sdb(values) ⇒ Object



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/amazon_sdb/multimap.rb', line 226

def from_sdb(values)
  @mset = {}
  clear_size!

  if values.nil?
    # do nothing
  elsif values.is_a? Array
    # load from array
    if values.any? {|v| ! v.is_a? Array || v.size != 2 }
      raise ArgumentError, "Array must be of key/value pairs only"
    end

    values.each do |v|
      self.put(v[0], v[1], :before_cast => true)
      self.put(v[0], coerce(v[1]))
    end
  else
    raise ArgumentError, "Wrong type passed as initializer"
  end
end

#get(key_arg, options = {}) ⇒ Object

Returns all the values that match a key. Normally, if there is only a single value entry returns just the value, with an array for multiple values, and nil for no match. If you want to always return an array, pass in :force_array => true in the options



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/amazon_sdb/multimap.rb', line 96

def get(key_arg, options = {})
  key = key_arg.to_s

  if options[:before_cast]
    key = "#{key}_before_cast"
  end

  k = @mset[key]

  if options[:force_array]
    return [] if k.nil?
    k.to_a
  else
    k
  end
end

#put(key_arg, value, options = {}) ⇒ Object

Save a key/value attribute into the multimap. Takes additional options

  • :replace => true remove any attributes with the same key before insert (otherwise, appends)



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/amazon_sdb/multimap.rb', line 71

def put(key_arg, value, options = {})
  key = key_arg.to_s

  if options[:before_cast]
    key = "#{key}_before_cast"
  end

  k = @mset[key]
  clear_size!

  if k.nil? || options[:replace]
    @mset[key] = value
  else
    if @mset[key].is_a? Array
      @mset[key] << value
    else
      @mset[key] = [@mset[key], value]
    end
  end
end

#sdb_key_escape(key) ⇒ Object



164
165
166
167
168
169
170
171
# File 'lib/amazon_sdb/multimap.rb', line 164

def sdb_key_escape(key)
  case key
  when String
    string_escape(key)
  else
    key.to_s
  end
end

#sdb_value_escape(value) ⇒ Object



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/amazon_sdb/multimap.rb', line 173

def sdb_value_escape(value)
  case value
  when TrueClass
    "true"
  when FalseClass
    "false"
  when Fixnum
    sprintf("%0#{Base.number_padding}d", value)
  when Float
    numeric(value, Base.number_padding, Base.float_precision)
  when String
    string_escape(value)
  when Time
    value.iso8601
  else
    string_escape(value.to_s)
  end
end

#sizeObject

Returns the size of the multimap. This is the total number of key/value pairs in it.



53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/amazon_sdb/multimap.rb', line 53

def size
  if @size.nil?
    @size = @mset.inject(0) do |total, pair|
      value = pair[1]
      if value.is_a? Array
        total + value.size
      else
        total + 1
      end
    end
  end

  @size
end

#string_escape(str) ⇒ Object



160
161
162
# File 'lib/amazon_sdb/multimap.rb', line 160

def string_escape(str)
  str.gsub('\\') {|c| "#{c}#{c}"}.gsub('\'') {|c| "\\#{c}"}
end

#to_aObject

Returns the multimap as an array of 2-item arrays, one for each key-value pair



249
250
251
252
253
# File 'lib/amazon_sdb/multimap.rb', line 249

def to_a
  out = []
  each_pair {|k, v| out << [k, v] }
  out
end

#to_hObject

Returns the multimap as a hash. In cases where there are multiple values for a key, it puts all the values into an array.



257
258
259
# File 'lib/amazon_sdb/multimap.rb', line 257

def to_h
  @mset.dup
end

#to_sdb(options = {}) ⇒ Object

Outputs a multimap to sdb using Amazon’s query-string notation (and doing auto-conversions of int and date values)



194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/amazon_sdb/multimap.rb', line 194

def to_sdb(options={})
  out = {}
  
  self.each_pair_with_index do |key, value, index|
    out["Attribute.#{index}.Name"] = sdb_key_escape(key)
    out["Attribute.#{index}.Value"] = sdb_value_escape(value)
    
    if options.key?(:replace) && (options[:replace] == :all || [*options[:replace]].find {|x| x.to_s == key })
      out["Attribute.#{index}.Replace"] = "true"
    end
  end

  out
end