super_map 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +91 -0
- data/Rakefile +51 -0
- data/lib/string_extensions.rb +18 -0
- data/lib/super_map.rb +240 -0
- data/lib/super_mapped_attr.rb +65 -0
- data/test/en.yml +9 -0
- data/test/super_map_test.rb +144 -0
- data/test/super_mapped_attr_test.rb +52 -0
- metadata +89 -0
data/README
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
Introduction
|
2
|
+
============
|
3
|
+
|
4
|
+
SuperMap is a two-way, multi-purpose ordered hash with some additional
|
5
|
+
capabilities (additional attributes, translations). It is meant to be used with
|
6
|
+
Rails, but it doesn't depend on them, so someone might find it useful in other
|
7
|
+
environments.
|
8
|
+
|
9
|
+
Ever had an enumerable field in a model that would map to a select box in a
|
10
|
+
form? Wanted some nice way to operate it as symbol in the code, integer in
|
11
|
+
database and label in views? That's where SuperMap comes into play.
|
12
|
+
|
13
|
+
Example
|
14
|
+
=======
|
15
|
+
|
16
|
+
Let's say there is a Payment model with two enumerable fields:
|
17
|
+
- payment_type: Cash In, Cash Out, Bonus, Commission
|
18
|
+
- payment_method: Paypal, Bank Transfer, Manual
|
19
|
+
|
20
|
+
We define 2 SuperMaps:
|
21
|
+
|
22
|
+
class Payment
|
23
|
+
|
24
|
+
PAYMENT_TYPES = SuperMap.new(
|
25
|
+
[:cash_in, 1, { :label => "Cash In", :tax => 0.01 }],
|
26
|
+
[:cash_out, 2, { :label => "Cash Out", :tax => 0.02 }],
|
27
|
+
[:bonus, 123, { :label => "Bonus Payment", :tax => 0.03 }],
|
28
|
+
[:commission, 384728, { :tax => 0.01 }]
|
29
|
+
)
|
30
|
+
|
31
|
+
PAYMENT_METHODS = SuperMap.new(
|
32
|
+
[:paypal, 1, { :label => "Paid via Paypal", :favorite => true }],
|
33
|
+
[:bank_transfer, 2],
|
34
|
+
[:manual, 3],
|
35
|
+
:translation_scope => "activerecord.models.payment.payment_methods"
|
36
|
+
)
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
PAYMENT_TYPES defines options for payment_type field. Some labels are set
|
41
|
+
explicitly, for other options key.titleize will be used implicitly. We also
|
42
|
+
defined a custom attribute :tax, which can be accessed using SuperMap#attribute
|
43
|
+
method.
|
44
|
+
|
45
|
+
PAYMENT_METHODS define translation_scope, which means labels will be present in
|
46
|
+
translations file (I18n). :paypal will have custom label, which overrides
|
47
|
+
translations.
|
48
|
+
|
49
|
+
We can than use those SuperMaps to handle attributes for Payment instances:
|
50
|
+
|
51
|
+
payment = Payment.new( :payment_method => 1 )
|
52
|
+
payment.payment_type = PAYMENT_TYPES[:cash_out] # Set to 2
|
53
|
+
label = PAYMENT_TYPES.label( payment.payment_type ) # "Cash Out"
|
54
|
+
label = PAYMENT_TYPES.label( :bonus ) # "Bonus Payment"
|
55
|
+
value = PAYMENT_METHODS[payment.payment_method] # 1
|
56
|
+
tax = PAYMENT_TYPES.attribute( payment.payment_type, :tax ) # 0.02
|
57
|
+
tax = PAYMENT_TYPES.attribute( :bonus, :tax ) # 0.03
|
58
|
+
|
59
|
+
We can also use
|
60
|
+
|
61
|
+
PAYMENT_TYPES.labeled_values
|
62
|
+
|
63
|
+
to get the form directly suitable to pass options to f.select helper method.
|
64
|
+
|
65
|
+
|
66
|
+
|
67
|
+
To make things even simpler, we can use super_mapped_attr:
|
68
|
+
|
69
|
+
class Payment
|
70
|
+
... # SuperMaps declarations
|
71
|
+
|
72
|
+
super_mapped_attr :payment_type, PAYMENT_TYPES
|
73
|
+
super_mapped_attr :payment_method, PAYMENT_METHODS
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
Now a whole lot of new methods comes into play:
|
78
|
+
|
79
|
+
p = Payment.new( :payment_method => 1 )
|
80
|
+
p.payment_method_label # "Paid via Paypal"
|
81
|
+
p.payment_method_key # :paypal
|
82
|
+
p.payment_method_attr( :favorite ) # true
|
83
|
+
p.payment_method_key = :manual
|
84
|
+
p.payment_method # 3
|
85
|
+
|
86
|
+
Thanks
|
87
|
+
======
|
88
|
+
|
89
|
+
Many thanks go to Stefan Nothegger and Sharewise project
|
90
|
+
(http://www.sharewise.com), where the original idea and large parts of the code
|
91
|
+
originate from.
|
data/Rakefile
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'rake/gempackagetask'
|
5
|
+
|
6
|
+
PKG_FILES = FileList[
|
7
|
+
'[a-zA-Z]*',
|
8
|
+
'generators/**/*',
|
9
|
+
'lib/**/*',
|
10
|
+
'rails/**/*',
|
11
|
+
'tasks/**/*',
|
12
|
+
'test/**/*'
|
13
|
+
]
|
14
|
+
|
15
|
+
spec = Gem::Specification.new do |s|
|
16
|
+
s.name = "super_map"
|
17
|
+
s.version = "1.0.0"
|
18
|
+
s.author = "Marek Janukowicz"
|
19
|
+
s.email = "marek@janukowicz.net"
|
20
|
+
s.homepage = ""
|
21
|
+
s.platform = Gem::Platform::RUBY
|
22
|
+
s.summary = "Swiss army knife of enum-based attributes"
|
23
|
+
s.files = PKG_FILES.to_a
|
24
|
+
s.require_path = "lib"
|
25
|
+
s.has_rdoc = false
|
26
|
+
s.extra_rdoc_files = ["README"]
|
27
|
+
s.add_dependency "i18n", ">= 0.4.0"
|
28
|
+
end
|
29
|
+
|
30
|
+
desc 'Create gem'
|
31
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
32
|
+
pkg.gem_spec = spec
|
33
|
+
end
|
34
|
+
|
35
|
+
desc 'Run tests'
|
36
|
+
Rake::TestTask.new(:test) do |t|
|
37
|
+
t.libs << 'lib'
|
38
|
+
t.libs << 'test'
|
39
|
+
t.pattern = 'test/**/*_test.rb'
|
40
|
+
t.verbose = true
|
41
|
+
end
|
42
|
+
|
43
|
+
desc 'Generate RDoc documentation'
|
44
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
45
|
+
rdoc.rdoc_dir = 'rdoc'
|
46
|
+
rdoc.title = 'SuperMap'
|
47
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
48
|
+
rdoc.rdoc_files.include('README')
|
49
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
50
|
+
end
|
51
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class String
|
2
|
+
|
3
|
+
# Some poor man's implementations of Rails String extensions in case we can't
|
4
|
+
# use those
|
5
|
+
|
6
|
+
unless method_defined?( :pluralize )
|
7
|
+
def pluralize
|
8
|
+
[-1,1] == "s" ? self : self + "s"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
unless method_defined?( :titleize )
|
13
|
+
def titleize
|
14
|
+
gsub( "_", " " ).capitalize
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/lib/super_map.rb
ADDED
@@ -0,0 +1,240 @@
|
|
1
|
+
begin
|
2
|
+
require 'i18n'
|
3
|
+
rescue LoadError
|
4
|
+
nil # We don't really need I18n, but if it's not present, :translation_scope can't be used
|
5
|
+
end
|
6
|
+
require 'string_extensions'
|
7
|
+
require 'super_mapped_attr'
|
8
|
+
|
9
|
+
# Two-way hash with additional custom data, autogenerated labels and human
|
10
|
+
# readable strings - swiss army knife of collections :) Especially suited for
|
11
|
+
# emulating enumerable fields in database.
|
12
|
+
#
|
13
|
+
# Immutable objects - most element accessing methods are cached, there is no
|
14
|
+
# designed way to get "raw" elements. If you need another (similar) SuperMap,
|
15
|
+
# use +/- methods to get a new one. The methods accessing Element objects
|
16
|
+
# directly (each, first, last) should be used with awareness of aforementioned
|
17
|
+
# limitations. Usually (when not enumerating over all elements) you would use
|
18
|
+
# key/value/attribute etc. accessing methods, eg. map.key( value ), map.value(
|
19
|
+
# key ), map.attribute( key, attr_name ).
|
20
|
+
class SuperMap
|
21
|
+
|
22
|
+
include Enumerable
|
23
|
+
|
24
|
+
class Error < ::StandardError; end
|
25
|
+
class UnknownKey < Error; end
|
26
|
+
class UnknownValue < Error; end
|
27
|
+
|
28
|
+
attr_reader :options
|
29
|
+
|
30
|
+
# Map element. Rarely used directly, usually the map object itself is queried
|
31
|
+
# for element properties.
|
32
|
+
class Element
|
33
|
+
|
34
|
+
# Key (symbol) - used for getting element by symbolic name
|
35
|
+
attr_reader :key
|
36
|
+
# Value - corresponds to key, usually an integer or string to be stored in
|
37
|
+
# database
|
38
|
+
attr_reader :value
|
39
|
+
# Additional information about element, eg. time frame, additional
|
40
|
+
# attributes. Also contains the label if not default or provided by I18n.
|
41
|
+
# Takes the form of a Hash.
|
42
|
+
attr_reader :attributes
|
43
|
+
|
44
|
+
# Creates new list element.
|
45
|
+
def initialize( list, key, value, attributes = {} )
|
46
|
+
raise "Key must be a Symbol" unless key.is_a? Symbol
|
47
|
+
raise "Value must NOT be a Symbol" if value.is_a? Symbol
|
48
|
+
@list, @key, @value, @attributes = list, key, value, attributes
|
49
|
+
end
|
50
|
+
|
51
|
+
def label
|
52
|
+
translated_attribute( :label )
|
53
|
+
end
|
54
|
+
|
55
|
+
def description
|
56
|
+
translated_attribute( :description )
|
57
|
+
end
|
58
|
+
|
59
|
+
def method_missing( name, *args )
|
60
|
+
return @attributes[name] || super( name, *args )
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns all initializing arguments as passed to SuperMap constructor - this
|
64
|
+
# method is used for cloning to another SuperMap.
|
65
|
+
def to_a
|
66
|
+
[key, value, attributes]
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
def translated_attribute( attr )
|
71
|
+
return @attributes[attr.to_sym] if @attributes[attr.to_sym]
|
72
|
+
if @list.options[:translation_scope]
|
73
|
+
return I18n.t( @key, :scope => [@list.options[:translation_scope], attr.to_s.pluralize], :default => @key.to_s.titleize )
|
74
|
+
else
|
75
|
+
return @key.to_s.titleize
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Creates new list.
|
81
|
+
# Elements - arrays with arguments for Element.new (key, value, attributes)
|
82
|
+
# Options (optional last argument):
|
83
|
+
# :translation_scope - where to find translated labels/descriptions
|
84
|
+
# :unknown_value_fatal - whether to raise an exception when accessing
|
85
|
+
# objects via unknown value (unknown key is always fatal)
|
86
|
+
#
|
87
|
+
# Example:
|
88
|
+
#
|
89
|
+
# class User
|
90
|
+
# KINDS = SuperMap.new(
|
91
|
+
# [:member, 1, "Casual member", { :min_age => 18.years, :welcome_string => "Welcome member!" }],
|
92
|
+
# [:admin, 2, "Administrator", { :min_age => nil, :welcome_string => "Hello admin!" }],
|
93
|
+
# :translation_scope => "user.kinds"
|
94
|
+
# )
|
95
|
+
# end
|
96
|
+
# TODO: enforce key/value uniqueness
|
97
|
+
def initialize( *elements )
|
98
|
+
if elements.last.is_a?( Hash )
|
99
|
+
@options = elements.pop
|
100
|
+
else
|
101
|
+
@options = {}
|
102
|
+
end
|
103
|
+
# TODO: handle old syntax here (elements passed in an Array and not as
|
104
|
+
# separate arguments)
|
105
|
+
@elements = elements.collect { |elem| Element.new( *([self] + elem) ) }
|
106
|
+
# We store elements in 2 Hashes, because we need to be able to lookup both
|
107
|
+
# on key (when in need of value) and on value (when in need of a key/label
|
108
|
+
# corresponding to the value fetched from database)
|
109
|
+
@elements_by_key = @elements.inject( {} ) { |hash, elem| hash[elem.key] = elem; hash }
|
110
|
+
@elements_by_value = @elements.inject( {} ) { |hash, elem| hash[elem.value] = elem; hash }
|
111
|
+
end
|
112
|
+
|
113
|
+
# Elements in the form of array (original form as passed as argument)
|
114
|
+
def element_array
|
115
|
+
@element_array ||= @elements.collect { |elem| elem.to_a }
|
116
|
+
end
|
117
|
+
|
118
|
+
# Iterates over the list return Element objects - required for Enumerable
|
119
|
+
# methods to work.
|
120
|
+
def each
|
121
|
+
@elements.each { |elem| yield elem }
|
122
|
+
end
|
123
|
+
|
124
|
+
def first
|
125
|
+
@elements.first
|
126
|
+
end
|
127
|
+
|
128
|
+
def last
|
129
|
+
@elements.last
|
130
|
+
end
|
131
|
+
|
132
|
+
# Removes element(s) corresponding to key(s) (returns new map)
|
133
|
+
# TODO: this should also handle another map as argument
|
134
|
+
def - (*keys)
|
135
|
+
keys = keys.flatten
|
136
|
+
new_elements = select { |el| !keys.include?( el.key ) }.collect { |elem| elem.to_a }
|
137
|
+
return self.class.new( *(new_elements + [@options]) )
|
138
|
+
end
|
139
|
+
|
140
|
+
# Adds elements from another map or array (returns new map)
|
141
|
+
def + (map_or_array)
|
142
|
+
if map_or_array.is_a?( self.class ) # Map
|
143
|
+
return self.class.new( *(element_array + map_or_array.element_array + [@options]) )
|
144
|
+
else # Array
|
145
|
+
return self.class.new( *(element_array + map_or_array + [@options]) )
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Returns array of label/value pairs, useful for SELECT tags - eg.
|
150
|
+
# f.select( :kind, User::KINDS.labeled_values )
|
151
|
+
def labeled_values
|
152
|
+
@labeled_values ||= @elements.collect { |elem| [elem.label, elem.value] }
|
153
|
+
end
|
154
|
+
|
155
|
+
# Array of values
|
156
|
+
def values
|
157
|
+
@values ||= @elements.collect { |elem| elem.value }
|
158
|
+
end
|
159
|
+
|
160
|
+
# Array of keys
|
161
|
+
def keys
|
162
|
+
@keys ||= @elements.collect { |elem| elem.key }
|
163
|
+
end
|
164
|
+
|
165
|
+
# Number of elements
|
166
|
+
def size
|
167
|
+
@size ||= @elements.size
|
168
|
+
end
|
169
|
+
|
170
|
+
# Key for given value - raises exception on unknown value depending on
|
171
|
+
# :unknown_value_fatal setting
|
172
|
+
def key(value)
|
173
|
+
elem = element_by_value_or_key( value )
|
174
|
+
# Exception handling in element_by_value_or_key
|
175
|
+
return elem ? elem.key : nil
|
176
|
+
end
|
177
|
+
|
178
|
+
# Value for given key - raises exception when key does not exist (to avoid bogus
|
179
|
+
# error messages when called with non-existant key)
|
180
|
+
def value(key)
|
181
|
+
# Exception handling in element_by_value_or_key
|
182
|
+
element_by_value_or_key( key ).value
|
183
|
+
end
|
184
|
+
|
185
|
+
# Label for given value or key (it is determined basing on argument type -
|
186
|
+
# keys must be Symbols while values can not)
|
187
|
+
def label( value_or_key )
|
188
|
+
elem = element_by_value_or_key( value_or_key )
|
189
|
+
return elem ? elem.label : nil
|
190
|
+
end
|
191
|
+
|
192
|
+
# Attribute field for given value or key
|
193
|
+
def attribute(value_or_key, attr_key)
|
194
|
+
elem = element_by_value_or_key( value_or_key )
|
195
|
+
if elem
|
196
|
+
return elem.attributes[attr_key]
|
197
|
+
else
|
198
|
+
return nil
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# Returns true if given key exists
|
203
|
+
def has_key?( key )
|
204
|
+
@elements_by_key.has_key?( key )
|
205
|
+
end
|
206
|
+
|
207
|
+
# Returns true if given value exists
|
208
|
+
def has_value?( value )
|
209
|
+
@elements_by_value.has_key?( value )
|
210
|
+
end
|
211
|
+
|
212
|
+
# Returns true if given key (Symbol) or value (non-Symbol) exists
|
213
|
+
def include?( value_or_key )
|
214
|
+
return has_key?( value_or_key ) || has_value?( value_or_key )
|
215
|
+
end
|
216
|
+
|
217
|
+
alias_method '[]', :value
|
218
|
+
|
219
|
+
def element_by_value_or_key( value_or_key )
|
220
|
+
|
221
|
+
if value_or_key.is_a?( Symbol ) # Key
|
222
|
+
elem = @elements_by_key[value_or_key]
|
223
|
+
if elem
|
224
|
+
return elem
|
225
|
+
else
|
226
|
+
raise UnknownKey, "Unknown key #{value_or_key}"
|
227
|
+
end
|
228
|
+
else
|
229
|
+
elem = @elements_by_value[value_or_key]
|
230
|
+
return elem if elem
|
231
|
+
if @options[:unknown_value_fatal]
|
232
|
+
raise UnknownValue, "Unknown value #{value_or_key}"
|
233
|
+
else
|
234
|
+
return nil
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
end
|
239
|
+
|
240
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module SuperMappedAttr
|
2
|
+
|
3
|
+
def self.included( klass )
|
4
|
+
klass.extend( ClassMethods )
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
|
9
|
+
# Makes the attribute mapped by a SuperMap, eg.
|
10
|
+
# class User
|
11
|
+
#
|
12
|
+
# super_mapped_attr :kind, SuperMap.new(
|
13
|
+
# [:member, 1, "Casual member", { :min_age => 18.years, :welcome_string => "Welcome member!" }],
|
14
|
+
# [:admin, 2, "Administrator", { :min_age => nil, :welcome_string => "Hello admin!" }]
|
15
|
+
# )
|
16
|
+
#
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# u = User.new( :kind => 1 )
|
20
|
+
#
|
21
|
+
# u.kind_label # "Casual member"
|
22
|
+
#
|
23
|
+
# u.kind_key # :member
|
24
|
+
#
|
25
|
+
# u.kind_attr( :member, :min_age ) # 18.years
|
26
|
+
#
|
27
|
+
# u.kind_key = :admin
|
28
|
+
#
|
29
|
+
# u.kind # 2
|
30
|
+
#
|
31
|
+
# User.kinds # #SuperMap:0x....
|
32
|
+
def super_mapped_attr( attr, map, options = {} )
|
33
|
+
|
34
|
+
if respond_to?( :validates_inclusion_of )
|
35
|
+
validates_inclusion_of attr, { :in => map.values }.merge( options )
|
36
|
+
end
|
37
|
+
|
38
|
+
define_method( "#{attr}_key" ) do
|
39
|
+
map.key(send(attr))
|
40
|
+
end
|
41
|
+
|
42
|
+
define_method( "#{attr}_key=" ) do |key|
|
43
|
+
send( "#{attr}=", map.value( key ))
|
44
|
+
end
|
45
|
+
|
46
|
+
define_method( "#{attr}_label" ) do
|
47
|
+
map.label(send(attr))
|
48
|
+
end
|
49
|
+
|
50
|
+
define_method( "#{attr}_attr" ) do |key|
|
51
|
+
map.attribute(send(attr), key)
|
52
|
+
end
|
53
|
+
|
54
|
+
(class << self; self; end).instance_eval do
|
55
|
+
define_method attr.to_s.pluralize, lambda { map }
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Include in ActiveRecord models by default
|
63
|
+
if defined?( ActiveRecord::Base )
|
64
|
+
ActiveRecord::Base.send( :include, SuperMappedAttr )
|
65
|
+
end
|
data/test/en.yml
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'super_map'
|
3
|
+
I18n.load_path << File.join( File.dirname( __FILE__ ), "en.yml" )
|
4
|
+
|
5
|
+
class SuperMapTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def setup
|
8
|
+
@color_map = SuperMap.new(
|
9
|
+
[:red, 5, { :rgb => "AA0000", :priority => 3 }],
|
10
|
+
[:green, 2, { :rgb => "00AA00", :priority => 12 }],
|
11
|
+
[:blue, 123, { :rgb => "0000AA", :preference => 8823 }],
|
12
|
+
:translation_scope => "color.map"
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_element_label_and_description
|
17
|
+
enum_map = SuperMap.new(
|
18
|
+
[:one, 1, { :label => "One label", :description => "One description" }],
|
19
|
+
[:two, 2],
|
20
|
+
:translation_scope => "enum.map"
|
21
|
+
)
|
22
|
+
assert_equal 'One label', enum_map.first.label
|
23
|
+
assert_equal 'One description', enum_map.first.description
|
24
|
+
assert_equal 'Two label', enum_map.last.label
|
25
|
+
assert_equal 'Two description', enum_map.last.description
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_element_attributes
|
29
|
+
red = @color_map.first
|
30
|
+
assert_equal "AA0000", red.rgb
|
31
|
+
assert_equal 3, red.priority
|
32
|
+
blue = @color_map.last
|
33
|
+
assert_equal "0000AA", blue.rgb
|
34
|
+
assert_equal 8823, blue.preference
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_enumeration
|
38
|
+
keys = []
|
39
|
+
@color_map.each do |elem|
|
40
|
+
keys << elem.key
|
41
|
+
end
|
42
|
+
assert_equal [:red, :green, :blue], keys
|
43
|
+
elems = @color_map.select { |elem| elem.value < 10 }.collect { |elem| elem.key }
|
44
|
+
assert_equal [:red, :green], elems
|
45
|
+
elems = @color_map.sort_by { |elem| elem.value }.collect { |elem| elem.key }
|
46
|
+
assert_equal [:green, :red, :blue], elems
|
47
|
+
assert_equal :red, @color_map.first.key
|
48
|
+
assert_equal :blue, @color_map.last.key
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_subtraction
|
52
|
+
# Existing key
|
53
|
+
map = @color_map - :red
|
54
|
+
assert_equal [:green, :blue], map.keys
|
55
|
+
# Non-existant key
|
56
|
+
map = @color_map - :yellow
|
57
|
+
assert_equal [:red, :green, :blue], map.keys
|
58
|
+
# multiple keys
|
59
|
+
map = @color_map - [:green, :blue]
|
60
|
+
assert_equal [:red], map.keys
|
61
|
+
assert_equal @color_map.options[:translation_scope], map.options[:translation_scope]
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_addition
|
65
|
+
# Single element
|
66
|
+
map = @color_map + [[:yellow, 3]]
|
67
|
+
assert_equal [:red, :green, :blue, :yellow], map.keys
|
68
|
+
# Multiple elements
|
69
|
+
map = @color_map + [[:yellow, 3], [:brown, 4]]
|
70
|
+
assert_equal [:red, :green, :blue, :yellow, :brown], map.keys
|
71
|
+
# Add another SuperMap
|
72
|
+
second_map = SuperMap.new( [:yellow, 3], [:brown, 4, {:label => "Blah"}], :translation_scope => "another.scope" )
|
73
|
+
map = @color_map + second_map
|
74
|
+
assert_equal [:red, :green, :blue, :yellow, :brown], map.keys
|
75
|
+
assert_equal @color_map.options[:translation_scope], map.options[:translation_scope]
|
76
|
+
# Original array is untouched
|
77
|
+
assert_equal [:red, :green, :blue], @color_map.keys
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_labeled_values
|
81
|
+
assert_equal [["Red", 5], ["Green", 2], ["Blue", 123]], @color_map.labeled_values
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_values
|
85
|
+
assert_equal [5,2,123], @color_map.values
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_keys
|
89
|
+
assert_equal [:red, :green, :blue], @color_map.keys
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_size
|
93
|
+
assert_equal 3, @color_map.size
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_key
|
97
|
+
assert_equal :red, @color_map.key( 5 )
|
98
|
+
assert_equal :green, @color_map.key( 2 )
|
99
|
+
assert_nil @color_map.key( 3 )
|
100
|
+
@color_map.options[:unknown_value_fatal] = true
|
101
|
+
assert_raise( SuperMap::UnknownValue ) { @color_map.key( 3 ) }
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_value
|
105
|
+
assert_equal 5, @color_map.value( :red )
|
106
|
+
assert_equal 123, @color_map.value( :blue )
|
107
|
+
assert_raise( SuperMap::UnknownKey ) { @color_map.value( :yellow ) }
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_label
|
111
|
+
assert_equal "Blue", @color_map.label( :blue )
|
112
|
+
assert_equal "Red", @color_map.label( 5 )
|
113
|
+
end
|
114
|
+
|
115
|
+
def test_attribute
|
116
|
+
assert_equal 8823, @color_map.attribute( :blue, :preference )
|
117
|
+
assert_equal nil, @color_map.attribute( :blue, :priority ) # This attribute doesn't exist for this element
|
118
|
+
assert_equal 3, @color_map.attribute( :red, :priority )
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_has_key
|
122
|
+
assert @color_map.has_key?( :green )
|
123
|
+
assert !@color_map.has_key?( :yellow )
|
124
|
+
end
|
125
|
+
|
126
|
+
def test_has_value
|
127
|
+
assert @color_map.has_value?( 2 )
|
128
|
+
assert !@color_map.has_value?( 55 )
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_include
|
132
|
+
assert @color_map.include?( :blue )
|
133
|
+
assert !@color_map.include?( :yellow )
|
134
|
+
assert @color_map.include?( 123 )
|
135
|
+
assert !@color_map.include?( 122 )
|
136
|
+
end
|
137
|
+
|
138
|
+
# [] is an alias to #value
|
139
|
+
def test_brackets
|
140
|
+
assert_equal 5, @color_map[ :red ]
|
141
|
+
assert_equal 123, @color_map[ :blue ]
|
142
|
+
assert_raise( SuperMap::UnknownKey ) { @color_map[ :yellow ] }
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'super_map'
|
3
|
+
I18n.load_path << File.join( File.dirname( __FILE__ ), "en.yml" )
|
4
|
+
|
5
|
+
class SuperMappedAttrTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
class User
|
8
|
+
|
9
|
+
include SuperMappedAttr
|
10
|
+
|
11
|
+
attr_accessor :color
|
12
|
+
super_mapped_attr :color, SuperMap.new(
|
13
|
+
[:red, 5, { :rgb => "AA0000", :priority => 3 }],
|
14
|
+
[:green, 2, { :rgb => "00AA00", :priority => 12 }],
|
15
|
+
[:blue, 123, { :rgb => "0000AA", :preference => 8823 }],
|
16
|
+
:translation_scope => "color.map"
|
17
|
+
)
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
def setup
|
22
|
+
@user = User.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_label
|
26
|
+
@user.color_key = :red
|
27
|
+
assert_equal "Red", @user.color_label
|
28
|
+
assert_raise( SuperMap::UnknownKey ) do
|
29
|
+
@user.color_key = :asdjfkajf
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_key
|
34
|
+
# This test DOES make sense - color_key getter and setter both pass through
|
35
|
+
# underlying SuperMap
|
36
|
+
@user.color_key = :red
|
37
|
+
assert_equal :red, @user.color_key
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_attr
|
41
|
+
@user.color_key = :blue
|
42
|
+
assert_nil @user.color_attr( :priority )
|
43
|
+
assert_equal "0000AA", @user.color_attr( :rgb )
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_map_accessor
|
47
|
+
assert_equal "Green", User.colors.label( :green )
|
48
|
+
assert_equal 123, User.colors[ :blue ]
|
49
|
+
assert_equal 12, User.colors.attribute( :green, :priority )
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: super_map
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Marek Janukowicz
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-09-08 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: i18n
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 15
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
- 4
|
33
|
+
- 0
|
34
|
+
version: 0.4.0
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
description:
|
38
|
+
email: marek@janukowicz.net
|
39
|
+
executables: []
|
40
|
+
|
41
|
+
extensions: []
|
42
|
+
|
43
|
+
extra_rdoc_files:
|
44
|
+
- README
|
45
|
+
files:
|
46
|
+
- Rakefile
|
47
|
+
- README
|
48
|
+
- lib/super_map.rb
|
49
|
+
- lib/super_mapped_attr.rb
|
50
|
+
- lib/string_extensions.rb
|
51
|
+
- test/super_mapped_attr_test.rb
|
52
|
+
- test/en.yml
|
53
|
+
- test/super_map_test.rb
|
54
|
+
has_rdoc: true
|
55
|
+
homepage: ""
|
56
|
+
licenses: []
|
57
|
+
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
hash: 3
|
69
|
+
segments:
|
70
|
+
- 0
|
71
|
+
version: "0"
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
hash: 3
|
78
|
+
segments:
|
79
|
+
- 0
|
80
|
+
version: "0"
|
81
|
+
requirements: []
|
82
|
+
|
83
|
+
rubyforge_project:
|
84
|
+
rubygems_version: 1.3.7
|
85
|
+
signing_key:
|
86
|
+
specification_version: 3
|
87
|
+
summary: Swiss army knife of enum-based attributes
|
88
|
+
test_files: []
|
89
|
+
|