super_map 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.
- 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
|
+
|