constants 0.1.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.
@@ -0,0 +1,133 @@
1
+ require 'constants/constant_library'
2
+
3
+ module Constants
4
+
5
+ # Library adds methods for convenient indexing and access of constants
6
+ # in a module. Usually Library is included after the constants have
7
+ # been defined.
8
+ #
9
+ # module Color
10
+ # RED = 'red'
11
+ # GREEN = 'green'
12
+ # BLUE = 'blue'
13
+ # GREY = 'grey'
14
+ #
15
+ # include Constants::Library
16
+ # library.index_by('name') {|c| c }
17
+ # end
18
+ #
19
+ # Now the Color constants can be accessed through the indicies hash
20
+ # or through [], which searches all indicies for the first match.
21
+ #
22
+ # Color.index('name')
23
+ # # => {
24
+ # # 'red' => Color::RED,
25
+ # # 'blue' => Color::BLUE,
26
+ # # 'green' => Color::GREEN,
27
+ # # 'grey' => Color::GREY}
28
+ #
29
+ # Color['red'] # => Color::RED
30
+ #
31
+ # Indexing is simplified for attributes. Notice that multiple
32
+ # values matching the same key are stashed into one group:
33
+ #
34
+ # Color.library.index_by_attribute 'length'
35
+ # Color.index('length')
36
+ # # => {
37
+ # # 3 => Color::RED,
38
+ # # 4 => [Color::BLUE, Color::GREY],
39
+ # # 5 => Color::GREEN}
40
+ #
41
+ # Color[4] # => [Color::BLUE, Color::GREY]
42
+ #
43
+ # Constants may also be assembled into ordered collections, which
44
+ # may or may not contain all the constants.
45
+ #
46
+ # Color.library.collect('gstar') {|c| c =~ /^g/ ? c : nil }
47
+ # Color.collection('gstar') # => [Color::GREEN, Color::GREY]
48
+ #
49
+ # Color.library.collect_attribute 'length'
50
+ # Color.collection('length') # => [3,5,4,4]
51
+ #
52
+ # New constants (even 'constants' that are not declared in the module) may be
53
+ # added manually, or by resetting the library. All indexes and collections
54
+ # are updated automatically.
55
+ #
56
+ # Color.library.add('yellow')
57
+ # Color.index('length')
58
+ # # => {
59
+ # # 3 => Color::RED,
60
+ # # 4 => [Color::BLUE, Color::GREY],
61
+ # # 5 => Color::GREEN,
62
+ # # 6 => 'yellow'}
63
+ #
64
+ # module Color
65
+ # ORANGE = 'orange'
66
+ # reset_library
67
+ # end
68
+ #
69
+ # Color.index('length')
70
+ # # => {
71
+ # # 3 => Color::RED,
72
+ # # 4 => [Color::BLUE, Color::GREY],
73
+ # # 5 => Color::GREEN,
74
+ # # 6 => Color::ORANGE}
75
+ #
76
+ # Notice 'yellow' was removed when the library was reset. The yellow example
77
+ # illustrates the fact that library only tracks the values of constants, not
78
+ # the constants themselves. If, for some reason, you dynamically reset a constant
79
+ # value then the change will not be reflected in the library until the library
80
+ # is reset.
81
+ #
82
+ # ==== Ruby 1.8
83
+ # Ruby doesn't track of the order of constant declaration until Ruby 1.9... this
84
+ # may cause collected values to be re-ordered in an unpredictable fashion. For
85
+ # instance in the Color example:
86
+ #
87
+ # Color[4] # may equal [Color::BLUE, Color::GREY] or [Color::GREY, Color::BLUE].
88
+ #
89
+ # In any case, the constants will be ordered as in Color.constants.
90
+ #
91
+ # == Performance Considerations
92
+ # ConstantsLibrary makes access of constants easier, but at the expense of
93
+ # performance. Naturally the constant itself is the highest-performing
94
+ # way of accessing the constant value.
95
+ #
96
+ module Library
97
+
98
+ # Accesses the module constant library.
99
+ attr_accessor :library
100
+
101
+ def self.included(mod)
102
+ mod.extend self
103
+ end
104
+
105
+ def self.extended(mod)
106
+ mod.library = ConstantLibrary.new
107
+ mod.reset_library
108
+ end
109
+
110
+ # Alias for library[identifier] (see Constants::ConstantLibrary#[])
111
+ def [](identifier)
112
+ library[identifier]
113
+ end
114
+
115
+ # Returns the index by the specified name.
116
+ def index(name)
117
+ library.indicies[name]
118
+ end
119
+
120
+ # Returns the collection by the specified name.
121
+ def collection(name)
122
+ library.collections[name]
123
+ end
124
+
125
+ # Resets the library using constants from self. A block
126
+ # can be provided to filter constants, see
127
+ # Constants::ConstantLibrary#add_constants_from
128
+ def reset_library(&block)
129
+ library.clear
130
+ library.add_constants_from(self, &block)
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,94 @@
1
+ module Constants
2
+
3
+ # Stash provides methods to store values by a non-unique key. The
4
+ # initial stored value is directly stored by the key, subsequent
5
+ # values are grouped into a StashArray.
6
+ #
7
+ # class StashingHash < Hash
8
+ # include Constants::Stash
9
+ #
10
+ # def initialize(*args)
11
+ # super(*args)
12
+ # @nil_value = nil
13
+ # end
14
+ # end
15
+ #
16
+ # s = StashingHash.new
17
+ #
18
+ # s.stash('key', 'one')
19
+ # s['key'] # => 'one'
20
+ #
21
+ # s.stash('key', 'two')
22
+ # s['key'] # => ['one' , 'two']
23
+ # s['key'].class # => Constants::Stash::StashArray
24
+ #
25
+ # The stash method requires some kind of flag to differentiate when a new
26
+ # value should be stored and when the existing value and new value
27
+ # should be converted into a StashArray. If the existing value as
28
+ # determined by [] is equal to the nil_value, then the new value is
29
+ # stored through []=.
30
+ #
31
+ # s = StashingHash.new
32
+ # s['key'] # => nil
33
+ # s.nil_value # => nil
34
+ #
35
+ # s.stash('key', 1)
36
+ # s['key'] # => 1
37
+ #
38
+ # s.stash('key', 2)
39
+ # s['key'] # => [1, 2]
40
+ #
41
+ # In the first case, the existing value for 'key' is equals the nil_value,
42
+ # so the new value is set. In the second case, the existing value for 'key'
43
+ # does not equal the nil_value; stash takes this as a signal that a
44
+ # non-unique key was specified and collects the values into a StashArray.
45
+ # As a consequence, neither the nil_value nor StashArrays may be stashed.
46
+ #
47
+ # s.stash('key', nil) # ! ArgumentError
48
+ #
49
+ module Stash
50
+
51
+ # A subclass of Array with no new functionality, necessary
52
+ # to differentiate between regular arrays and collections
53
+ # in a Stash.
54
+ class StashArray < Array
55
+ end
56
+
57
+ # The value considered to be nil in store (used to
58
+ # signal when a new value can be stashed for a given
59
+ # key; if store[key] == nil_value, then a new value
60
+ # can be stashed).
61
+ attr_accessor :nil_value
62
+
63
+ # Assigns the value to key in store. If the store already has a
64
+ # non-nil_value at key (as determined by []), then the existing
65
+ # and new value will be concatenated into a StashArray. All
66
+ # subsequent values are added to the StashArray. stash uses
67
+ # the []= method to set values.
68
+ #
69
+ # nil_value and StashArray values cannot be stashed; either raises
70
+ # an error.
71
+ def stash(key, value)
72
+ case value
73
+ when nil_value
74
+ raise ArgumentError.new("the nil_value for self cannot be stashed")
75
+ when StashArray
76
+ raise ArgumentError.new("StashArrays cannot be stashed")
77
+ end
78
+
79
+ current_value = self[key]
80
+
81
+ case current_value
82
+ when nil_value
83
+ self[key] = value
84
+ when StashArray
85
+ current_value << value
86
+ else
87
+ self[key] = StashArray.new([current_value, value])
88
+ end
89
+
90
+ self
91
+ end
92
+ end
93
+
94
+ end
@@ -0,0 +1,8 @@
1
+ module Constants
2
+
3
+ # Simply defines the values for exact and unknown uncertainty.
4
+ module Uncertainty
5
+ UNKNOWN = nil
6
+ EXACT = 0
7
+ end
8
+ end
@@ -0,0 +1,304 @@
1
+ require File.join(File.dirname(__FILE__), '../constants_test_helper.rb')
2
+ require 'constants/constant_library'
3
+
4
+ class ConstantLibraryTest < Test::Unit::TestCase
5
+ include Constants
6
+
7
+ attr_accessor :lib
8
+
9
+ def setup
10
+ @lib = ConstantLibrary.new 'one', 'two', :one
11
+ end
12
+
13
+ #
14
+ # documentation test
15
+ #
16
+
17
+ def test_documentation
18
+ lib = ConstantLibrary.new('one', 'two', :three)
19
+ lib.index_by('upcase') {|value| value.to_s.upcase }
20
+ assert_equal({'ONE' => 'one', 'TWO' => 'two', 'THREE' => :three}, lib.indicies['upcase'])
21
+
22
+ lib.collect("string") {|value| value.to_s }
23
+ assert_equal(['one', 'two', 'three'], lib.collections['string'])
24
+ end
25
+
26
+ #
27
+ # initialize test
28
+ #
29
+
30
+ def test_initialize
31
+ lib = ConstantLibrary.new
32
+
33
+ assert_equal([], lib.values)
34
+ assert_equal({}, lib.indicies)
35
+ assert_equal({}, lib.collections)
36
+ end
37
+
38
+ def test_initialize_with_values
39
+ assert_equal(['one', 'two', :one], lib.values)
40
+ end
41
+
42
+ def test_initialize_removes_duplicate_values
43
+ lib = ConstantLibrary.new 'one', 'two', 'one'
44
+ assert_equal(['one', 'two'], lib.values)
45
+ end
46
+
47
+ #
48
+ # index_by test
49
+ #
50
+
51
+ def test_index_by_documentation
52
+ lib = ConstantLibrary.new('one', 'two', :one)
53
+ lib.index_by("string") {|value| value.to_s }
54
+ assert_equal({
55
+ 'one' => ['one', :one],
56
+ 'two' => 'two'},
57
+ lib.indicies['string'])
58
+
59
+ lib = ConstantLibrary.new(1,2,nil)
60
+ assert_raise(ArgumentError) { lib.index_by("error", false, nil) {|value| value } }
61
+
62
+
63
+ obj = Object.new
64
+ index = lib.index_by("ok", false, obj) {|value| value }
65
+ assert_equal 1, index[1]
66
+ assert_equal nil, index[nil]
67
+
68
+ assert_equal obj, index['non-existant']
69
+ end
70
+
71
+ def test_index_by_creates_a_new_index_for_the_specified_inputs
72
+ block = lambda {|v| }
73
+ lib.index_by("name", "nil", "nil_value", &block)
74
+
75
+ assert lib.indicies.has_key?('name')
76
+ index = lib.indicies['name']
77
+
78
+ assert_equal ConstantLibrary::Index, index.class
79
+ assert_equal "nil", index.exclusion_value
80
+ assert_equal "nil_value", index.nil_value
81
+ assert_equal block, index.block
82
+ end
83
+
84
+ def test_index_by_uses_name_as_indicies_key
85
+ lib.index_by(:sym) {|v| v.to_s }
86
+ assert lib.indicies.has_key?(:sym)
87
+ assert !lib.indicies.has_key?('sym')
88
+
89
+ lib.index_by('str') {|v| v.to_s }
90
+ assert !lib.indicies.has_key?(:str)
91
+ assert lib.indicies.has_key?('str')
92
+ end
93
+
94
+ def test_index_stashes_values_by_block
95
+ lib.index_by("string") {|v| v.to_s }
96
+ assert_equal({'one' => ['one', :one], 'two' => 'two'}, lib.indicies["string"])
97
+ end
98
+
99
+ def test_index_stashes_key_value_pairs_if_returned
100
+ lib.index_by("pairs") {|v| [v, v.to_s.upcase] }
101
+ assert_equal({'one' => 'ONE', :one => 'ONE', 'two' => 'TWO'}, lib.indicies["pairs"])
102
+ end
103
+
104
+ def test_index_excludes_values_which_return_the_exclusion_value
105
+ lib.index_by("exclusion", nil) do |v|
106
+ v.kind_of?(String) ? nil : [v, v.to_s.upcase]
107
+ end
108
+
109
+ assert_equal({:one => 'ONE'}, lib.indicies["exclusion"])
110
+ end
111
+
112
+ def test_index_by_default_exclusion_value_is_nil
113
+ lib.index_by("name") {|v|}
114
+ assert_equal nil, lib.indicies['name'].exclusion_value
115
+ end
116
+
117
+ def test_index_by_raises_error_for_no_block_given
118
+ assert_raise(ArgumentError) { lib.index_by('name') }
119
+ end
120
+
121
+ def test_index_by_returns_index
122
+ result = lib.index_by("name") {|v|}
123
+ assert_equal(lib.indicies['name'], result)
124
+ end
125
+
126
+ #
127
+ # index_by_attribute test
128
+ #
129
+
130
+ def test_index_by_attribute_stashes_values_by_method_value
131
+ lib.index_by_attribute("to_s")
132
+ assert_equal({'one' => ['one', :one], 'two' => 'two'}, lib.indicies["to_s"])
133
+ end
134
+
135
+ def test_index_by_attribute_returns_index
136
+ result = lib.index_by_attribute("object_id")
137
+ assert_equal(lib.indicies['object_id'], result)
138
+ end
139
+
140
+ def test_index_by_attribute_raises_error_if_objects_dont_respond_to_attribute
141
+ assert_raise(NoMethodError) { lib.index_by_attribute("non_existant") }
142
+ end
143
+
144
+ #
145
+ # collect test
146
+ #
147
+
148
+ def test_collect_documentation
149
+ lib = ConstantLibrary.new('one', 'two', :three)
150
+ lib.collect("string") {|value| value.to_s }
151
+ assert_equal ['one', 'two', 'three'], lib.collections['string']
152
+
153
+ lib.collect("length") {|value| [value, value.to_s.length] }
154
+ assert_equal [nil, nil, nil, ['one', 'two'], nil, :three], lib.collections['length']
155
+ end
156
+
157
+ def test_collect_creates_a_new_collection_for_the_specified_inputs
158
+ block = lambda {|v| }
159
+ lib.collect("name", &block)
160
+
161
+ assert lib.collections.has_key?('name')
162
+ collection = lib.collections['name']
163
+
164
+ assert_equal ConstantLibrary::Collection, collection.class
165
+ assert_equal nil, collection.nil_value
166
+ assert_equal block, collection.block
167
+ end
168
+
169
+ def test_collect_uses_name_as_collections_key
170
+ lib.collect(:sym) {|v| v.to_s }
171
+ assert lib.collections.has_key?(:sym)
172
+ assert !lib.collections.has_key?('sym')
173
+
174
+ lib.collect('str') {|v| v.to_s }
175
+ assert !lib.collections.has_key?(:str)
176
+ assert lib.collections.has_key?('str')
177
+ end
178
+
179
+ def test_collection_stashes_block_value
180
+ lib.collect("string") {|v| v.to_s }
181
+ assert_equal(['one', 'two', 'one'], lib.collections["string"])
182
+ end
183
+
184
+ def test_collection_stashes_values_index_pairs_if_returned
185
+ map = {'one' => 2, 'two' => 0}
186
+
187
+ lib.collect("pairs") {|v| [v, map[v.to_s]] }
188
+ assert_equal(['two', nil, ['one', :one]], lib.collections["pairs"])
189
+ end
190
+
191
+ def test_collection_excludes_values_which_return_nil
192
+ lib.collect("exclusion") do |v|
193
+ v.kind_of?(String) ? nil : v
194
+ end
195
+ assert_equal([:one], lib.collections["exclusion"])
196
+ end
197
+
198
+ def test_collect_raises_error_for_no_block_given
199
+ assert_raise(ArgumentError) { lib.collect('name') }
200
+ end
201
+
202
+ def test_collect_returns_collection
203
+ result = lib.collect("name") {|v|}
204
+ assert_equal(lib.collections['name'], result)
205
+ end
206
+
207
+ #
208
+ # collect_attribute test
209
+ #
210
+
211
+ def test_collect_attribute_collects_attribute_values
212
+ lib.collect_attribute("to_s")
213
+ assert_equal(['one', 'two', 'one'], lib.collections["to_s"])
214
+ end
215
+
216
+ #
217
+ # [] test
218
+ #
219
+
220
+ def test_get_searches_all_indicies_match
221
+ lib.index_by("string") {|v| v.to_s }
222
+ lib.index_by("strlen") {|v| v.to_s.length }
223
+
224
+ assert_equal ['one', :one], lib['one']
225
+ assert_equal ['one', 'two', :one], lib[3]
226
+ end
227
+
228
+ def test_get_returns_nil_if_no_matches_are_found
229
+ assert_equal nil, lib['three']
230
+ end
231
+
232
+ def test_get_returns_first_match_only
233
+ lib.index_by("str1") {|v| v.to_s }
234
+ lib.index_by("str2") {|v| [v.to_s, v.to_s.upcase] }
235
+
236
+ assert lib.indicies['str1'].has_key?('one')
237
+ assert lib.indicies['str2'].has_key?('one')
238
+
239
+ assert_not_equal lib.indicies['str1']['one'], lib.indicies['str2']['one']
240
+ assert_equal lib.indicies['str1']['one'], lib['one']
241
+ end
242
+
243
+ #
244
+ # clear test
245
+ #
246
+
247
+ def test_clear_clears_all_values_from_lib_indicies_and_collections
248
+ lib.index_by("str") {|v| v.to_s }
249
+ lib.collect("str") {|v| v.to_s }
250
+
251
+ assert !lib.values.empty?
252
+ assert !lib.indicies['str'].empty?
253
+ assert !lib.collections['str'].empty?
254
+
255
+ lib.clear
256
+
257
+ assert lib.values.empty?
258
+ assert lib.indicies['str'].empty?
259
+ assert lib.collections['str'].empty?
260
+ end
261
+
262
+ def test_clear_only_removes_indicies_and_collections_if_specified
263
+ lib.index_by("str") {|v| v.to_s }
264
+ lib.collect("str") {|v| v.to_s }
265
+
266
+ lib.clear
267
+
268
+ assert !lib.indicies.empty?
269
+ assert !lib.collections.empty?
270
+
271
+ lib.clear(true)
272
+
273
+ assert lib.indicies.empty?
274
+ assert lib.collections.empty?
275
+ end
276
+
277
+ #
278
+ # add test
279
+ #
280
+
281
+ def test_add_adds_new_values
282
+ lib.add(:two, 'three')
283
+ assert_equal ['one', 'two', :one, :two, 'three'], lib.values
284
+ end
285
+
286
+ def test_add_does_not_add_duplicate_values
287
+ lib.add(:one, :two, 'two', 'three')
288
+ assert_equal ['one', 'two', :one, :two, 'three'], lib.values
289
+ end
290
+
291
+ def test_add_returns_newly_added_values
292
+ assert_equal [:two, 'three'], lib.add(:one, :two, 'two', 'three')
293
+ end
294
+
295
+ def test_add_incorporates_new_values_into_existing_indicies_and_collections
296
+ lib.index_by("str") {|v| v.to_s }
297
+ lib.collect("length") {|v| [v, v.to_s.length] }
298
+
299
+ lib.add(:one, :two, 'two', 'three')
300
+ assert_equal({'one' => ['one', :one], 'two' => ['two', :two], 'three' => 'three'}, lib.indicies['str'])
301
+ assert_equal([nil,nil,nil,['one', 'two', :one, :two], nil, 'three'], lib.collections['length'])
302
+ end
303
+
304
+ end