Class: SuperMap

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

Overview

Two-way hash with additional custom data, autogenerated labels and human readable strings - swiss army knife of collections :) Especially suited for emulating enumerable fields in database.

Immutable objects - most element accessing methods are cached, there is no designed way to get “raw” elements. If you need another (similar) SuperMap, use +/- methods to get a new one. The methods accessing Element objects directly (each, first, last) should be used with awareness of aforementioned limitations. Usually (when not enumerating over all elements) you would use key/value/attribute etc. accessing methods, eg. map.key( value ), map.value( key ), map.attribute( key, attr_name ).

Defined Under Namespace

Classes: Element, Error, UnknownKey, UnknownValue

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*elements) ⇒ SuperMap

Creates new list. Elements - arrays with arguments for Element.new (key, value, attributes) Options (optional last argument):

:translation_scope - where to find translated labels/descriptions
:unknown_value_fatal - whether to raise an exception when accessing
  objects via unknown value (unknown key is always fatal)

Example:

class User

KINDS = SuperMap.new(
  [:member, 1, "Casual member", { :min_age => 18.years, :welcome_string => "Welcome member!" }],
  [:admin, 2, "Administrator", { :min_age => nil, :welcome_string => "Hello admin!" }],
  :translation_scope => "user.kinds"
)

end TODO: enforce key/value uniqueness



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

def initialize( *elements )
  if elements.last.is_a?( Hash )
    @options = elements.pop
  else
    @options = {}
  end
  # TODO: handle old syntax here (elements passed in an Array and not as
  # separate arguments)
  @elements = elements.collect { |elem| Element.new( *([self] + elem) ) }
  # We store elements in 2 Hashes, because we need to be able to lookup both
  # on key (when in need of value) and on value (when in need of a key/label
  # corresponding to the value fetched from database)
  @elements_by_key = @elements.inject( {} ) { |hash, elem| hash[elem.key] = elem; hash }
  @elements_by_value = @elements.inject( {} ) { |hash, elem| hash[elem.value] = elem; hash }
end

Instance Attribute Details

#optionsObject (readonly)

Returns the value of attribute options.



28
29
30
# File 'lib/super_map.rb', line 28

def options
  @options
end

Instance Method Details

#+(map_or_array) ⇒ Object

Adds elements from another map or array (returns new map)



141
142
143
144
145
146
147
# File 'lib/super_map.rb', line 141

def + (map_or_array)
  if map_or_array.is_a?( self.class ) # Map
    return self.class.new( *(element_array + map_or_array.element_array + [@options]) )
  else # Array
    return self.class.new( *(element_array + map_or_array + [@options]) )
  end
end

#-(*keys) ⇒ Object

Removes element(s) corresponding to key(s) (returns new map) TODO: this should also handle another map as argument



134
135
136
137
138
# File 'lib/super_map.rb', line 134

def - (*keys)
  keys = keys.flatten
  new_elements = select { |el| !keys.include?( el.key ) }.collect { |elem| elem.to_a }
  return self.class.new( *(new_elements + [@options]) )
end

#attribute(value_or_key, attr_key) ⇒ Object

Attribute field for given value or key



193
194
195
196
197
198
199
200
# File 'lib/super_map.rb', line 193

def attribute(value_or_key, attr_key)
  elem = element_by_value_or_key( value_or_key )
  if elem
    return elem.attributes[attr_key]
  else
    return nil
  end
end

#eachObject

Iterates over the list return Element objects - required for Enumerable methods to work.



120
121
122
# File 'lib/super_map.rb', line 120

def each
  @elements.each { |elem| yield elem }
end

#element_arrayObject

Elements in the form of array (original form as passed as argument)



114
115
116
# File 'lib/super_map.rb', line 114

def element_array
  @element_array ||= @elements.collect { |elem| elem.to_a }
end

#element_by_value_or_key(value_or_key) ⇒ Object



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/super_map.rb', line 219

def element_by_value_or_key( value_or_key )
 
  if value_or_key.is_a?( Symbol ) # Key
    elem = @elements_by_key[value_or_key]
    if elem
      return elem
    else
      raise UnknownKey, "Unknown key #{value_or_key}"
    end
  else
    elem = @elements_by_value[value_or_key]
    return elem if elem
    if @options[:unknown_value_fatal]
      raise UnknownValue, "Unknown value #{value_or_key}"
    else
      return nil
    end
  end
    
end

#firstObject



124
125
126
# File 'lib/super_map.rb', line 124

def first
  @elements.first
end

#has_key?(key) ⇒ Boolean

Returns true if given key exists

Returns:

  • (Boolean)


203
204
205
# File 'lib/super_map.rb', line 203

def has_key?( key )
  @elements_by_key.has_key?( key )
end

#has_value?(value) ⇒ Boolean

Returns true if given value exists

Returns:

  • (Boolean)


208
209
210
# File 'lib/super_map.rb', line 208

def has_value?( value )
  @elements_by_value.has_key?( value )
end

#include?(value_or_key) ⇒ Boolean

Returns true if given key (Symbol) or value (non-Symbol) exists

Returns:

  • (Boolean)


213
214
215
# File 'lib/super_map.rb', line 213

def include?( value_or_key )
  return has_key?( value_or_key ) || has_value?( value_or_key )
end

#key(value) ⇒ Object

Key for given value - raises exception on unknown value depending on :unknown_value_fatal setting



172
173
174
175
176
# File 'lib/super_map.rb', line 172

def key(value)
  elem = element_by_value_or_key( value )
  # Exception handling in element_by_value_or_key
  return elem ? elem.key : nil
end

#keysObject

Array of keys



161
162
163
# File 'lib/super_map.rb', line 161

def keys
  @keys ||= @elements.collect { |elem| elem.key }
end

#label(value_or_key) ⇒ Object

Label for given value or key (it is determined basing on argument type - keys must be Symbols while values can not)



187
188
189
190
# File 'lib/super_map.rb', line 187

def label( value_or_key )
  elem = element_by_value_or_key( value_or_key )
  return elem ? elem.label : nil
end

#labeled_valuesObject

Returns array of label/value pairs, useful for SELECT tags - eg. f.select( :kind, User::KINDS.labeled_values )



151
152
153
# File 'lib/super_map.rb', line 151

def labeled_values
  @labeled_values ||= @elements.collect { |elem| [elem.label, elem.value] }
end

#lastObject



128
129
130
# File 'lib/super_map.rb', line 128

def last
  @elements.last
end

#sizeObject

Number of elements



166
167
168
# File 'lib/super_map.rb', line 166

def size
  @size ||= @elements.size
end

#value(key) ⇒ Object Also known as: []

Value for given key - raises exception when key does not exist (to avoid bogus error messages when called with non-existant key)



180
181
182
183
# File 'lib/super_map.rb', line 180

def value(key)
  # Exception handling in element_by_value_or_key
  element_by_value_or_key( key ).value
end

#valuesObject

Array of values



156
157
158
# File 'lib/super_map.rb', line 156

def values
  @values ||= @elements.collect { |elem| elem.value }
end