elemental 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.txt +364 -0
- data/Rakefile +29 -0
- data/elemental.gemspec +23 -0
- data/lib/element.rb +106 -0
- data/lib/elemental.rb +169 -0
- data/test/test_elemental.rb +399 -0
- metadata +63 -0
data/README.txt
ADDED
@@ -0,0 +1,364 @@
|
|
1
|
+
= ELEMENTAL
|
2
|
+
|
3
|
+
Elemental gives you enumerated sets. You code symbolically,
|
4
|
+
keeping literals away from your conditional logic.
|
5
|
+
|
6
|
+
== REQUIREMENTS:
|
7
|
+
|
8
|
+
* Ruby 1.8+
|
9
|
+
* Ruby Gems
|
10
|
+
|
11
|
+
== INSTALL:
|
12
|
+
|
13
|
+
Project Hosts:
|
14
|
+
* http://elemental.rubyforge.net
|
15
|
+
* http://github.com/mwlang/elemental/tree/master
|
16
|
+
|
17
|
+
From Gem:
|
18
|
+
sudo gem install elemental
|
19
|
+
|
20
|
+
From Source:
|
21
|
+
gem install bones, rake, test-unit
|
22
|
+
git clone git://github.com/mwlang/elemental.git
|
23
|
+
cd elemental
|
24
|
+
rake gem:package
|
25
|
+
cd pkg
|
26
|
+
sudo gem install elemental
|
27
|
+
|
28
|
+
Working Examples:
|
29
|
+
Check out the projects in the examples folder for working examples of
|
30
|
+
using Elemental. Examples may be limited presently, but more are planned.
|
31
|
+
If you have a unique/interesting application of Elemental, please feel free
|
32
|
+
to contact me and let me know about it for possible inclusion.
|
33
|
+
|
34
|
+
== FEATURES/PROBLEMS:
|
35
|
+
|
36
|
+
* Code with symbols, easily display what the user needs to see.
|
37
|
+
* Associate any number of values/attributes with a symbolic element.
|
38
|
+
* Change the display values without worrying about logical side-effects.
|
39
|
+
* Access elements in a variety of ways: symbol, constant, index.
|
40
|
+
* Store in database as either string (the default) or ordinal value.
|
41
|
+
* Elemental is also Enumerable. You can iterate all members and sort, too.
|
42
|
+
* A default flag can be set that makes it easier to render views.
|
43
|
+
|
44
|
+
== DESCRIPTION:
|
45
|
+
|
46
|
+
Elemental provides enumerated collection of elements that allow you to associate
|
47
|
+
ruby symbols to arbitrary "display" values, thus allowing your code to "think"
|
48
|
+
symbolically and unambiguously while giving you the means to easily display what
|
49
|
+
end-users need to see. Additionally, symbols are associated with ordinal values,
|
50
|
+
allowing easy storage/retrieval to persistent stores (databases, files,
|
51
|
+
marshalling, etc) by saving the Element#value as appropriate (String by default,
|
52
|
+
Fixnum if you "persist_ordinally").
|
53
|
+
|
54
|
+
The primary aim of Elemental is to collect and abstract literals away
|
55
|
+
from your code logic. There's an old programmer's wisdom that you should not
|
56
|
+
encode your logic using literal values, especially those the end-user is exposed to.
|
57
|
+
|
58
|
+
Yet, interpreted languages seem to be famous for sowing the practice of doing
|
59
|
+
boolean expressions replete with string literals. Which can be ok until one day
|
60
|
+
your client says, "I don't like 'Active' can we use 'enabled' instead?").
|
61
|
+
|
62
|
+
In your code, instead of:
|
63
|
+
|
64
|
+
if my_user.status == 'Active' ...
|
65
|
+
or worse
|
66
|
+
my_user.favorite_color = 1
|
67
|
+
|
68
|
+
(what color is 1?!)
|
69
|
+
|
70
|
+
The above suffers from the following:
|
71
|
+
- literals are subject to change or are cryptic.
|
72
|
+
- diff spellings mean diff things ('Active' != 'active' != 'Actve').
|
73
|
+
- relying on literals error prone and hard to test.
|
74
|
+
- code is often not readable as-is (what color is 1, again?).
|
75
|
+
|
76
|
+
With Elemental, do this:
|
77
|
+
|
78
|
+
if my_user.status == UserStatus::active.value
|
79
|
+
my_user.favorite_color = Color::blue
|
80
|
+
|
81
|
+
When you save a value to a persistent store (database, stream, file, etc.), you
|
82
|
+
assign the Element#value like so:
|
83
|
+
|
84
|
+
my_user.status = UserStatus::active.value
|
85
|
+
|
86
|
+
(more on what you get via "value" later)
|
87
|
+
|
88
|
+
If UserStatus::active isn't defined, you get an error immediately that you can
|
89
|
+
attribute to a typo in your code. This behavior is by design in Elemental.
|
90
|
+
That's a unit test you *don't* have to write! Simply define your class or module
|
91
|
+
and include the Ruby symbols you need:
|
92
|
+
|
93
|
+
class AccountStatus
|
94
|
+
extend Elemental
|
95
|
+
member :active, :display => 'Active'
|
96
|
+
member :delinquent, :display => "Account Past Due"
|
97
|
+
member :inactive :display => "Inactive"
|
98
|
+
end
|
99
|
+
|
100
|
+
Elemental can return the Element#value as either a Ruby symbol (Symbol) or an
|
101
|
+
ordinal (Fixnum) value. The default is a Ruby symbol. To override this and
|
102
|
+
return an ordinal value, use "persist_ordinally" as follows:
|
103
|
+
|
104
|
+
class AccountStatus
|
105
|
+
extend Elemental
|
106
|
+
persist_ordinally
|
107
|
+
|
108
|
+
member :active, :display => 'Active'
|
109
|
+
member :delinquent, :display => "Account Past Due"
|
110
|
+
member :inactive :display => "Inactive"
|
111
|
+
end
|
112
|
+
|
113
|
+
== SYNOPSIS:
|
114
|
+
|
115
|
+
Elementals are "containers" for your elements (a.k.a. Ruby symbols).
|
116
|
+
They are declared like this:
|
117
|
+
|
118
|
+
class AccountStatus
|
119
|
+
extend Elemental
|
120
|
+
member :active, :display => 'Active', :default => true
|
121
|
+
member :delinquent, :display => "Account Past Due"
|
122
|
+
member :inactive :display => "Inactive"
|
123
|
+
end
|
124
|
+
|
125
|
+
class Fruit
|
126
|
+
extend Elemental
|
127
|
+
member :orange, :position => 10
|
128
|
+
member :banana, :position => 5, :default => true
|
129
|
+
member :blueberry, :position => 15
|
130
|
+
end
|
131
|
+
|
132
|
+
Where:
|
133
|
+
* The symbol is main accessor for Elemental
|
134
|
+
* Display is what the end-user sees. Display defaults to the symbol's to_s.
|
135
|
+
* Position lets you define a display sort order (use WidgetType.sort...)
|
136
|
+
* Position defaults to Ordinal value if not given.
|
137
|
+
* Absent Position, the default sort order is the order in which
|
138
|
+
elements are declared (ordinal).
|
139
|
+
* Specifying Position does not change Ordinal value.
|
140
|
+
* An ElementNotFoundError is raised if you try an nonexistent Symbol.
|
141
|
+
|
142
|
+
Another example:
|
143
|
+
|
144
|
+
class Color
|
145
|
+
extend Elemental
|
146
|
+
member :red, :display "#FF0000"
|
147
|
+
member :green, :display "#00FF00"
|
148
|
+
member :blue, :display "#0000FF"
|
149
|
+
end
|
150
|
+
|
151
|
+
So you roll with this for a few months and decided you don't like primary colors?
|
152
|
+
Its simple to change the color constants (in display):
|
153
|
+
|
154
|
+
class Color
|
155
|
+
extend Elemental
|
156
|
+
member :red, :display "#AA5555"
|
157
|
+
member :green, :display "#55AA55"
|
158
|
+
member :blue, :display "#5555AA"
|
159
|
+
end
|
160
|
+
|
161
|
+
Your code logic remains the same because the symbols didn't change!
|
162
|
+
|
163
|
+
Once you have your Elemental classes defined, replace your conditionals
|
164
|
+
that use string literals.
|
165
|
+
|
166
|
+
So, instead of:
|
167
|
+
if widget.widget_type == "Foo bar"
|
168
|
+
|
169
|
+
(where widget is an Activerecord instance and widget_type is an attribute/column)
|
170
|
+
(mental note: was that "FOO BAR", "foobar", "Foo Bar", "foo bar", or "Foo bar"??)
|
171
|
+
|
172
|
+
Do this and be confident:
|
173
|
+
if WidgetType[widget.widget_type] == WidgetType::foo_bar
|
174
|
+
|
175
|
+
Or shorter:
|
176
|
+
if WidgetType[widget.widget_type].is?(WidgetType::foo_bar)
|
177
|
+
|
178
|
+
Or shorter, still:
|
179
|
+
if WidgetType[widget.widget_type].is?(:foo_bar)
|
180
|
+
|
181
|
+
Although Elemental wasn't specifically written for Rails, it is trivial to put to use. Instead
|
182
|
+
if creating a new model and migration script and all that, simply establish your Elemental
|
183
|
+
class definition and then iterate the Elemental to construct your views as appropriate.
|
184
|
+
|
185
|
+
With Elemental, simply populate select dropdowns like this:
|
186
|
+
|
187
|
+
in your $RAILS_ROOT/config/initializers/constants.rb:
|
188
|
+
|
189
|
+
class AccountStatus
|
190
|
+
extend Elemental
|
191
|
+
member :active, :display => 'Active', :default => true
|
192
|
+
member :delinquent, :display => "Account Past Due"
|
193
|
+
member :inactive :display => "Inactive"
|
194
|
+
end
|
195
|
+
|
196
|
+
Then in your view:
|
197
|
+
|
198
|
+
<p><label for="account_status">Account Status</label><br/>
|
199
|
+
<%= select "user", "status",
|
200
|
+
AccountStatus.sort.map{|a| [a.display, a.value]},
|
201
|
+
{ :include_blank => false,
|
202
|
+
:selected => AccountStatus.defaults.first.value }
|
203
|
+
%></select>
|
204
|
+
</p>
|
205
|
+
|
206
|
+
Or render checkboxes with:
|
207
|
+
|
208
|
+
<% Color.sort.each |c| do %>
|
209
|
+
<%= check_box_tag("user[favorite_colors][#{c.value}]",
|
210
|
+
"1", Color.defaults.detect{|color| color == c}) %>
|
211
|
+
<%= "#{c.display}"%><br />
|
212
|
+
<% end %>
|
213
|
+
|
214
|
+
Life is simplified because:
|
215
|
+
- No more migrate scripts for tiny tables
|
216
|
+
- No worries that the auto-incrementer is guaranteeing to assign
|
217
|
+
same ID accross deployments.
|
218
|
+
- No database penalties looking up rarely changing values
|
219
|
+
- No worries that an empty lookup table breaks your application logic/flow
|
220
|
+
- Values that mean something in your app are symbolic while what's displayed
|
221
|
+
and sorted on are free to change as the application grows and evolves.
|
222
|
+
|
223
|
+
More than one default is supported (dropdowns only use one, so first.value
|
224
|
+
gets you first one, radio buttons or checkboxes can use all defaults). Ordinal
|
225
|
+
position is preserved and traditionally shouldn't be changed during the life
|
226
|
+
of the project, although, Ruby being Ruby and developers saying "Ruby ain't C,"
|
227
|
+
an Elemental's elements almost definitely will get changed by some developer at
|
228
|
+
some time.
|
229
|
+
|
230
|
+
The chances of the name of the symbol changing is much lower than a developer's
|
231
|
+
tendency to keep an orderly house by sorting member elements alphabetically. As
|
232
|
+
such, the default behavior for "value" is to return the symbol rather than
|
233
|
+
ordinal value and storing as a string in DB, which seems the safest route to
|
234
|
+
take albeit not the optimal performance-wise (finding records by integral value
|
235
|
+
is faster than string searches, even with indexes in place). If you want to
|
236
|
+
override this, simply call "persist_ordinally" when you declare your Elemental
|
237
|
+
classes.
|
238
|
+
|
239
|
+
class Color
|
240
|
+
extend Elemental
|
241
|
+
persist_ordinally
|
242
|
+
member :red
|
243
|
+
member :green
|
244
|
+
member :blue
|
245
|
+
end
|
246
|
+
|
247
|
+
With that, Color::red.value returns Fixnum 0 instead of the Symbol :red
|
248
|
+
|
249
|
+
You can also associate many values with a particular element through custom accessors.
|
250
|
+
To extend the Color example further:
|
251
|
+
|
252
|
+
class Color
|
253
|
+
extend Elemental
|
254
|
+
persist_ordinally
|
255
|
+
member :red, :display => "Primary Red", :red => 255, :green => 0, :blue => 0, :hex => "#FF0000"
|
256
|
+
member :green, :display => "Primary Green", :red => 0, :green => 255, :blue => 0, :hex => "#00FF00"
|
257
|
+
member :blue, :display => "Primary Blue", :red => 0, :green => 0, :blue => 255, :hex => "#0000FF"
|
258
|
+
end
|
259
|
+
|
260
|
+
With that:
|
261
|
+
Color::red.red => 255
|
262
|
+
Color::red.green => 0
|
263
|
+
Color::red.blue => 0
|
264
|
+
Color::red.hex => "#FF0000"
|
265
|
+
|
266
|
+
Elements can be accessed multiple ways:
|
267
|
+
|
268
|
+
def test_different_retrievals_get_same_element
|
269
|
+
a1 = Car::honda
|
270
|
+
a2 = Car::Honda
|
271
|
+
a3 = Car[:honda]
|
272
|
+
a4 = Car[:Honda]
|
273
|
+
a5 = Car["Honda"]
|
274
|
+
a6 = Car["honda"]
|
275
|
+
a7 = Car.first
|
276
|
+
a8 = Car.last.succ
|
277
|
+
a9 = Car[0]
|
278
|
+
assert_element_sameness(a1, a2, :honda)
|
279
|
+
assert_element_sameness(a2, a3, :honda)
|
280
|
+
assert_element_sameness(a4, a5, :honda)
|
281
|
+
assert_element_sameness(a6, a7, :honda)
|
282
|
+
assert_element_sameness(a8, a1, :honda)
|
283
|
+
assert_element_sameness(a9, a2, :honda)
|
284
|
+
end
|
285
|
+
|
286
|
+
There are also several convenience aliases to pull ordinal, value, symbol and display:
|
287
|
+
|
288
|
+
def test_aliases
|
289
|
+
assert_equal(Car::toyota.index, Car::toyota.to_i)
|
290
|
+
assert_equal(Car::toyota.to_i, Car::toyota.to_i)
|
291
|
+
assert_equal(Car::toyota.to_int, Car::toyota.to_i)
|
292
|
+
assert_equal(Car::toyota.ord, Car::toyota.to_i)
|
293
|
+
assert_equal(Car::toyota.ordinal, Car::toyota.to_i)
|
294
|
+
assert_equal(Car::toyota.to_sym, Car::toyota.value)
|
295
|
+
assert_equal(:toyota, Car::toyota.to_sym)
|
296
|
+
assert_equal(:toyota, Car::toyota.value)
|
297
|
+
assert_equal("toyota", Car::toyota.display)
|
298
|
+
assert_equal("toyota", Car::toyota.humanize)
|
299
|
+
end
|
300
|
+
|
301
|
+
Full test coverage is provided.
|
302
|
+
|
303
|
+
One known limitation (which is probably a reflection of my not-so-great Ruby skills):
|
304
|
+
|
305
|
+
You cannot inherit and Elemental from another Elemental, esp. since none of the classes or
|
306
|
+
modules are ever actually instantiated. The following will NOT WORK:
|
307
|
+
|
308
|
+
class Fruit # OK
|
309
|
+
extend Elemental
|
310
|
+
member :orange
|
311
|
+
member :banana
|
312
|
+
member :blueberry
|
313
|
+
end
|
314
|
+
|
315
|
+
class Vegetable # OK
|
316
|
+
extend Elemental
|
317
|
+
member :potato
|
318
|
+
member :carrot
|
319
|
+
member :tomato
|
320
|
+
end
|
321
|
+
|
322
|
+
class Food # NOT OK
|
323
|
+
extend Fruit
|
324
|
+
extend Vegetable
|
325
|
+
end
|
326
|
+
|
327
|
+
Also, this doesn't work, either:
|
328
|
+
|
329
|
+
class Food < Fruit # NOT OK
|
330
|
+
extend Vegetable
|
331
|
+
end
|
332
|
+
|
333
|
+
Nor:
|
334
|
+
|
335
|
+
class ExoticFruit < Fruit # NOT OK
|
336
|
+
member :papaya
|
337
|
+
member :mango
|
338
|
+
end
|
339
|
+
|
340
|
+
|
341
|
+
== LICENSE:
|
342
|
+
|
343
|
+
(The MIT License)
|
344
|
+
|
345
|
+
Copyright (c) 2009 Michael Lang
|
346
|
+
|
347
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
348
|
+
a copy of this software and associated documentation files (the
|
349
|
+
'Software'), to deal in the Software without restriction, including
|
350
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
351
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
352
|
+
permit persons to whom the Software is furnished to do so, subject to
|
353
|
+
the following conditions:
|
354
|
+
|
355
|
+
The above copyright notice and this permission notice shall be
|
356
|
+
included in all copies or substantial portions of the Software.
|
357
|
+
|
358
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
359
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
360
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
361
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
362
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
363
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
364
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Look in the tasks/setup.rb file for the various options that can be
|
2
|
+
# configured in this Rakefile. The .rake files in the tasks directory
|
3
|
+
# are where the options are used.
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'bones'
|
7
|
+
Bones.setup
|
8
|
+
rescue LoadError
|
9
|
+
begin
|
10
|
+
load 'tasks/setup.rb'
|
11
|
+
rescue LoadError
|
12
|
+
raise RuntimeError, '### please install the "bones" gem ###'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
ensure_in_path 'lib'
|
17
|
+
require 'elemental'
|
18
|
+
|
19
|
+
task :default => 'test:run'
|
20
|
+
|
21
|
+
PROJ.name = 'elemental'
|
22
|
+
PROJ.authors = 'Michael Lang'
|
23
|
+
PROJ.email = 'mwlang@cybrains.net'
|
24
|
+
PROJ.url = 'http://github.com/mwlang/elemental/tree/master'
|
25
|
+
PROJ.version = Elemental::VERSION
|
26
|
+
PROJ.rubyforge.name = 'elemental'
|
27
|
+
PROJ.spec.opts << '--color'
|
28
|
+
|
29
|
+
# EOF
|
data/elemental.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "elemental"
|
3
|
+
s.version = "0.1.1"
|
4
|
+
s.date = "2009-06-03"
|
5
|
+
s.summary = "Implements a set of elements that are enumerable."
|
6
|
+
s.email = "mwlang@cybrains.net"
|
7
|
+
s.homepage = "http://github.com/mwlang/elemental"
|
8
|
+
s.description = "Elemental provides enumerated set of elements allowing your code to think symbolically and unambiguously while giving you the means to easily display what end-users need to see."
|
9
|
+
s.has_rdoc = true
|
10
|
+
s.authors = ["Michael Lang"]
|
11
|
+
s.platform = Gem::Platform::RUBY
|
12
|
+
s.files = [
|
13
|
+
"elemental.gemspec",
|
14
|
+
"README.txt",
|
15
|
+
"Rakefile",
|
16
|
+
"lib/element.rb",
|
17
|
+
"lib/elemental.rb",
|
18
|
+
"test/test_elemental.rb"
|
19
|
+
]
|
20
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Elemental", "--main", "README.txt"]
|
21
|
+
s.extra_rdoc_files = ["README.txt"]
|
22
|
+
end
|
23
|
+
|
data/lib/element.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (c) 2009 Michael Lang
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
a copy of this software and associated documentation files (the
|
6
|
+
'Software'), to deal in the Software without restriction, including
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
18
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
19
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
20
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
21
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
=end
|
23
|
+
class Element
|
24
|
+
|
25
|
+
attr_accessor :symbol, :ordinal, :display, :position, :default
|
26
|
+
|
27
|
+
# Initializes a new element
|
28
|
+
def initialize(elemental, symbol, ordinal, options={})
|
29
|
+
@elemental = elemental
|
30
|
+
@symbol = symbol
|
31
|
+
@ordinal = ordinal
|
32
|
+
|
33
|
+
# Sets all options and optionally creates an accessor method
|
34
|
+
options.each do |k, v|
|
35
|
+
eval("def #{k}; @#{k}; end") unless respond_to?(k)
|
36
|
+
v.is_a?(String) ? eval("@#{k} = '#{v}'") : eval("@#{k} = #{v}")
|
37
|
+
end
|
38
|
+
|
39
|
+
# Force the following values (overrides above looped assignments)
|
40
|
+
@default = options[:default] || false
|
41
|
+
@display = options[:display] || symbol.to_s
|
42
|
+
@position = options[:position] || ordinal
|
43
|
+
end
|
44
|
+
|
45
|
+
def is?(value)
|
46
|
+
self == (value.is_a?(Element) ? value : @elemental[value])
|
47
|
+
end
|
48
|
+
|
49
|
+
def value
|
50
|
+
@elemental.value_as_ordinal ? to_i : to_sym
|
51
|
+
end
|
52
|
+
|
53
|
+
# Will sort on position (which defaults to ordinal if not explicitly set)
|
54
|
+
def <=>(other)
|
55
|
+
position <=> other.position
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns the element that follows this element.
|
59
|
+
# If this is last element, returns first element
|
60
|
+
def succ
|
61
|
+
@elemental.succ(@symbol)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns the element that precedes this element.
|
65
|
+
# If this is the first element, returns last element
|
66
|
+
def pred
|
67
|
+
@elemental.pred(@symbol)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns the symbol for this element as a string
|
71
|
+
def to_s
|
72
|
+
@symbol.to_s
|
73
|
+
end
|
74
|
+
|
75
|
+
# Returns this element's symbol
|
76
|
+
def to_sym
|
77
|
+
@symbol
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns the ordinal value for this element
|
81
|
+
def index
|
82
|
+
@ordinal
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns true if is default. (useful if populating select lists)
|
86
|
+
def default?
|
87
|
+
@default
|
88
|
+
end
|
89
|
+
|
90
|
+
# Generates a reasonable inspect string
|
91
|
+
def inspect
|
92
|
+
"#<#{self.class}:#{self.object_id} {symbol => :#{@symbol}, " +
|
93
|
+
"ordinal => #{@ordinal}, display => \"#{@display}\", " +
|
94
|
+
"position => #{@position}, default => #{@default}}>"
|
95
|
+
end
|
96
|
+
|
97
|
+
# swiped from Rails...
|
98
|
+
def humanize
|
99
|
+
return @display if @display != to_s
|
100
|
+
return self.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize
|
101
|
+
end
|
102
|
+
|
103
|
+
alias :to_i :index
|
104
|
+
alias :to_int :index
|
105
|
+
alias :ord :index
|
106
|
+
end
|
data/lib/elemental.rb
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (c) 2009 Michael Lang
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
a copy of this software and associated documentation files (the
|
6
|
+
'Software'), to deal in the Software without restriction, including
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
18
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
19
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
20
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
21
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
=end
|
23
|
+
|
24
|
+
begin
|
25
|
+
require 'lib/element'
|
26
|
+
rescue LoadError
|
27
|
+
require 'element'
|
28
|
+
end
|
29
|
+
|
30
|
+
module Elemental
|
31
|
+
|
32
|
+
VERSION = '0.1.1'
|
33
|
+
|
34
|
+
include Enumerable
|
35
|
+
|
36
|
+
attr_reader :value_as_ordinal
|
37
|
+
|
38
|
+
# Causes the Element#value method to return ordinal value, which
|
39
|
+
# normally returns the ruby symbol
|
40
|
+
#
|
41
|
+
# Note: If you choose for Element#value to return ordinal and you are
|
42
|
+
# storing value to database, then you should always APPEND new elements
|
43
|
+
# to the declared Elemental class. Otherwise, you WILL CHANGE the
|
44
|
+
# ordinal values and thus invalidate any persistent stores. If you're
|
45
|
+
# not persistently storing ordinal values, then order of membership is moot.
|
46
|
+
def persist_ordinally
|
47
|
+
@value_as_ordinal = true
|
48
|
+
end
|
49
|
+
|
50
|
+
# Adds an element in the order given to the enumerable's class
|
51
|
+
# Order matters if you store ordinal to database or other persistent stores
|
52
|
+
def member(symbol, options={})
|
53
|
+
@unordered_elements ||= {}
|
54
|
+
@ordered_elements ||= []
|
55
|
+
symbol = conform_to_symbol(symbol)
|
56
|
+
element = Element.new(self, symbol, @ordered_elements.size, options)
|
57
|
+
|
58
|
+
@unordered_elements[symbol] = element
|
59
|
+
@ordered_elements << element
|
60
|
+
end
|
61
|
+
|
62
|
+
# allows you to define aliases for a given member with adding
|
63
|
+
# additional elements to the elemental class.
|
64
|
+
#
|
65
|
+
# class Fruit
|
66
|
+
# extend Elemental
|
67
|
+
# member :apple
|
68
|
+
# member :kiwi
|
69
|
+
# synonym :machintosh, :apple
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# will yield an Elemental with only two elements (apple and kiwi),
|
73
|
+
# but give the ability to retrieve Fruit::apple using Fruit::machintosh
|
74
|
+
def synonym(new_symbol, existing_symbol)
|
75
|
+
element = self[existing_symbol]
|
76
|
+
@unordered_elements[conform_to_symbol(new_symbol)] = element
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns the first element in this class
|
80
|
+
def first
|
81
|
+
@ordered_elements.first
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns the last element in this class
|
85
|
+
def last
|
86
|
+
@ordered_elements.last
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns the number of elements added
|
90
|
+
def size
|
91
|
+
@ordered_elements.size
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns the element that follows the given element. If given element is
|
95
|
+
# last element, then first element is returned.
|
96
|
+
def succ(value)
|
97
|
+
index = self[value].to_i + 1
|
98
|
+
(index >= size) ? first : @ordered_elements[index]
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns the element that precedes the given element. If given element is
|
102
|
+
# the first element, then last element is returned.
|
103
|
+
def pred(value)
|
104
|
+
index = self[value].to_i - 1
|
105
|
+
(index < 0) ? last : @ordered_elements[index]
|
106
|
+
end
|
107
|
+
|
108
|
+
# Shortcut to getting to a specific element
|
109
|
+
# Can get by symbol or constant (i.e. Color[:red] or Color[:Red])
|
110
|
+
def [](value)
|
111
|
+
value.is_a?(Fixnum) ? get_ordered_element(value) : get_unordered_element(value)
|
112
|
+
end
|
113
|
+
|
114
|
+
# iterates over the elements, passing each to the given block
|
115
|
+
def each(&block)
|
116
|
+
@ordered_elements.each(&block)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Allows Car::honda
|
120
|
+
def method_missing(value)
|
121
|
+
self[value]
|
122
|
+
end
|
123
|
+
|
124
|
+
# Allows Car::Honda
|
125
|
+
def const_missing(const)
|
126
|
+
get_unordered_element(const)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Returns all elements that are flagged as a default
|
130
|
+
def defaults
|
131
|
+
@ordered_elements.select{|s| s.default?}
|
132
|
+
end
|
133
|
+
|
134
|
+
# Outputs a sensible inspect string
|
135
|
+
def inspect
|
136
|
+
"#<#{self}:#{object_id} #{@ordered_elements.inspect}>"
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
# Takes a "CamelCased-String" and returns "camel_cased_string"
|
142
|
+
def underscore(camel_cased_word)
|
143
|
+
camel_cased_word.to_s.
|
144
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
145
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
146
|
+
tr("-", "_").
|
147
|
+
downcase
|
148
|
+
end
|
149
|
+
|
150
|
+
# Transforms given string or symbol
|
151
|
+
# into a :lowered_case_underscored_symbol
|
152
|
+
def conform_to_symbol(text_or_symbol)
|
153
|
+
underscore(text_or_symbol.to_s).downcase.to_sym
|
154
|
+
end
|
155
|
+
|
156
|
+
# returns the nth element, raising an exception if index out of bounds
|
157
|
+
def get_ordered_element(index)
|
158
|
+
result = @ordered_elements[index]
|
159
|
+
raise RuntimeError if result.nil?
|
160
|
+
result
|
161
|
+
end
|
162
|
+
|
163
|
+
# returns the requested element by name, raising exception if non-existent
|
164
|
+
def get_unordered_element(what)
|
165
|
+
result = @unordered_elements[conform_to_symbol(what)]
|
166
|
+
raise RuntimeError if result.nil?
|
167
|
+
result
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,399 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'lib/elemental'
|
5
|
+
|
6
|
+
module TestElemental
|
7
|
+
|
8
|
+
class TestElemental < Test::Unit::TestCase
|
9
|
+
|
10
|
+
class Fruit
|
11
|
+
extend Elemental
|
12
|
+
member :apple
|
13
|
+
member :pear, :default => true
|
14
|
+
member :banana, :default => true
|
15
|
+
member :kiwi
|
16
|
+
|
17
|
+
synonym :machintosh, :apple
|
18
|
+
end
|
19
|
+
|
20
|
+
FRUIT_SIZE = 4
|
21
|
+
|
22
|
+
class Color
|
23
|
+
extend Elemental
|
24
|
+
member :blue, :display => "Hazel Blue", :default => true
|
25
|
+
member :red, :display => "Fire Engine Red"
|
26
|
+
member :yellow
|
27
|
+
end
|
28
|
+
|
29
|
+
COLOR_SIZE = 3
|
30
|
+
|
31
|
+
class Car
|
32
|
+
extend Elemental
|
33
|
+
member :honda, :position => 100
|
34
|
+
member :toyota, :position => 30
|
35
|
+
member :ford, :position => 50
|
36
|
+
member :gm, :position => 25
|
37
|
+
member :mazda, :position => 1
|
38
|
+
end
|
39
|
+
|
40
|
+
CAR_SIZE = 5
|
41
|
+
|
42
|
+
class JumbledMess
|
43
|
+
extend Elemental
|
44
|
+
member :testing
|
45
|
+
member :Oh_one
|
46
|
+
member :two_three
|
47
|
+
member :FourFiveSix
|
48
|
+
member :what_an_id
|
49
|
+
member :your_idea
|
50
|
+
end
|
51
|
+
|
52
|
+
class TalkingNumbers
|
53
|
+
extend Elemental
|
54
|
+
persist_ordinally
|
55
|
+
member :zilch, :display => "zero", :alt => "zip", :zero => true, :custom => 0
|
56
|
+
member :uno,:display => "one", :alt => "1st", :zero => false, :custom => 1.0
|
57
|
+
member :dos, :display => "two", :alt => "2nd", :zero => false, :custom => 2
|
58
|
+
member :tres, :display => "three", :alt => "3rd", :zero => false, :custom => "3"
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_values_as_ordinal
|
62
|
+
one = TalkingNumbers[:uno]
|
63
|
+
two = TalkingNumbers["dos"]
|
64
|
+
three = TalkingNumbers[3]
|
65
|
+
|
66
|
+
assert_equal(one.to_i, one.value)
|
67
|
+
assert_equal(2, two.value)
|
68
|
+
assert_equal(3, three.value)
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_humanize
|
72
|
+
assert_equal("Testing", JumbledMess::testing.humanize)
|
73
|
+
assert_equal("Oh one", JumbledMess::oh_one.humanize)
|
74
|
+
assert_equal("Two three", JumbledMess::two_three.humanize)
|
75
|
+
assert_equal("Four five six", JumbledMess::FourFiveSix.humanize)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Test the order of breadth for each
|
79
|
+
def test_size_of_elemental
|
80
|
+
assert_equal(FRUIT_SIZE, Fruit.size)
|
81
|
+
assert_equal(COLOR_SIZE, Color.size)
|
82
|
+
assert_equal(CAR_SIZE, Car.size)
|
83
|
+
end
|
84
|
+
|
85
|
+
def assert_can_map_elements
|
86
|
+
assert_not_nil(Car.collect{|c| c.value})
|
87
|
+
end
|
88
|
+
|
89
|
+
def assert_element_sameness(a, b, symbol)
|
90
|
+
assert_not_nil(a)
|
91
|
+
assert_not_nil(b)
|
92
|
+
assert(a.is_a?(Element))
|
93
|
+
assert(b.is_a?(Element))
|
94
|
+
assert_equal(a, b)
|
95
|
+
assert_equal(symbol, a.to_sym)
|
96
|
+
assert_equal(symbol, b.to_sym)
|
97
|
+
assert_equal(a, b)
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_basic_retrieval
|
101
|
+
assert_not_nil(Fruit::apple)
|
102
|
+
assert_not_nil(Fruit::banana)
|
103
|
+
assert_not_nil(Fruit::pear)
|
104
|
+
assert_not_nil(Fruit::kiwi)
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_jumbled_retrival
|
108
|
+
assert_not_nil(JumbledMess::testing)
|
109
|
+
assert_not_nil(JumbledMess::two_three)
|
110
|
+
assert_not_nil(JumbledMess::what_an_id)
|
111
|
+
assert_not_nil(JumbledMess::your_idea)
|
112
|
+
assert_not_nil(JumbledMess::oh_one)
|
113
|
+
assert_not_nil(JumbledMess["oh_one"])
|
114
|
+
|
115
|
+
assert_not_nil(JumbledMess::Oh_one)
|
116
|
+
assert_not_nil(JumbledMess::Oh_One)
|
117
|
+
assert_not_nil(JumbledMess::FourFiveSix)
|
118
|
+
assert_not_nil(JumbledMess::Four_Five_six)
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_basic_nonretrieval
|
122
|
+
assert_raise(RuntimeError) { Fruit::tomato }
|
123
|
+
assert_raise(RuntimeError) { Fruit::Potato }
|
124
|
+
assert_raise(RuntimeError) { Fruit[:squash] }
|
125
|
+
assert_raise(RuntimeError) { Fruit["okra"] }
|
126
|
+
assert_raise(RuntimeError) { Fruit[10] }
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_retrieval_by_ordinal
|
130
|
+
apple1 = Fruit[0]
|
131
|
+
apple2 = Fruit.select{|s| s.to_sym == :apple}.first
|
132
|
+
assert_element_sameness(apple1, apple2, :apple)
|
133
|
+
|
134
|
+
pear1 = Fruit[1]
|
135
|
+
pear2 = Fruit[:pear]
|
136
|
+
assert_element_sameness(pear1, pear2, :pear)
|
137
|
+
|
138
|
+
kiwi1 = Fruit[-1]
|
139
|
+
kiwi2 = Fruit.last
|
140
|
+
assert_element_sameness(kiwi1, kiwi2, :kiwi)
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_retrieval_by_symbol
|
144
|
+
a1 = Fruit::apple
|
145
|
+
a2 = Fruit[:apple]
|
146
|
+
a3 = Fruit.select{|s| s.to_sym == :apple}.first
|
147
|
+
assert_element_sameness(a1, a2, :apple)
|
148
|
+
assert_element_sameness(a1, a3, :apple)
|
149
|
+
assert_element_sameness(a2, a3, :apple)
|
150
|
+
|
151
|
+
b1 = Fruit::banana
|
152
|
+
b2 = Fruit[:banana]
|
153
|
+
b3 = Fruit.select{|s| s.to_sym == :banana}.first
|
154
|
+
assert_element_sameness(b1, b2, :banana)
|
155
|
+
assert_element_sameness(b1, b3, :banana)
|
156
|
+
assert_element_sameness(b2, b3, :banana)
|
157
|
+
end
|
158
|
+
|
159
|
+
def test_retrieval_by_constant
|
160
|
+
a1 = Car::Toyota
|
161
|
+
a2 = Car[:Toyota]
|
162
|
+
a3 = Car.select{|s| s.to_sym == :toyota}.first
|
163
|
+
assert_element_sameness(a1, a2, :toyota)
|
164
|
+
assert_element_sameness(a1, a3, :toyota)
|
165
|
+
assert_element_sameness(a2, a3, :toyota)
|
166
|
+
end
|
167
|
+
|
168
|
+
def test_different_retrievals_get_same_element
|
169
|
+
a1 = Car::honda
|
170
|
+
a2 = Car::Honda
|
171
|
+
a3 = Car[:honda]
|
172
|
+
a4 = Car[:Honda]
|
173
|
+
a5 = Car["Honda"]
|
174
|
+
a6 = Car["honda"]
|
175
|
+
a7 = Car.first
|
176
|
+
a8 = Car.last.succ
|
177
|
+
a9 = Car[0]
|
178
|
+
assert_element_sameness(a1, a2, :honda)
|
179
|
+
assert_element_sameness(a2, a3, :honda)
|
180
|
+
assert_element_sameness(a4, a5, :honda)
|
181
|
+
assert_element_sameness(a6, a7, :honda)
|
182
|
+
assert_element_sameness(a8, a1, :honda)
|
183
|
+
assert_element_sameness(a9, a2, :honda)
|
184
|
+
end
|
185
|
+
|
186
|
+
def test_get_first_element
|
187
|
+
e1 = Color.first
|
188
|
+
e2 = Color::blue
|
189
|
+
assert_element_sameness(e1,e2,:blue)
|
190
|
+
assert_equal(e1.to_i, 0)
|
191
|
+
assert_equal(e1.to_i, e2.ord)
|
192
|
+
assert_equal(e2.ordinal, 0)
|
193
|
+
end
|
194
|
+
|
195
|
+
def test_walk_succ
|
196
|
+
e1 = Color.first
|
197
|
+
assert_element_sameness(e1, Color::blue, :blue)
|
198
|
+
assert_equal(0, e1.ordinal)
|
199
|
+
e1 = e1.succ
|
200
|
+
assert_element_sameness(e1, Color::red, :red)
|
201
|
+
assert_equal(1, e1.ordinal)
|
202
|
+
e1 = e1.succ
|
203
|
+
assert_element_sameness(e1, Color::yellow, :yellow)
|
204
|
+
assert_equal(2, e1.ordinal)
|
205
|
+
e1 = e1.succ
|
206
|
+
assert_element_sameness(e1, Color::blue, :blue)
|
207
|
+
assert_equal(0, e1.ordinal)
|
208
|
+
end
|
209
|
+
|
210
|
+
def test_get_last_element
|
211
|
+
e1 = Color.last
|
212
|
+
e2 = Color::yellow
|
213
|
+
assert_element_sameness(e1,e2,:yellow)
|
214
|
+
assert_equal(e1.to_i, COLOR_SIZE - 1)
|
215
|
+
assert_equal(e1.to_i, e2.ord)
|
216
|
+
assert_equal(e2.ordinal, COLOR_SIZE - 1)
|
217
|
+
end
|
218
|
+
|
219
|
+
def test_walk_pred
|
220
|
+
e1 = Color.first
|
221
|
+
assert_element_sameness(e1, Color::blue, :blue)
|
222
|
+
assert_equal(0, e1.ordinal)
|
223
|
+
|
224
|
+
e1 = e1.pred
|
225
|
+
assert_element_sameness(e1, Color::yellow, :yellow)
|
226
|
+
assert_equal(2, e1.ordinal)
|
227
|
+
|
228
|
+
e1 = e1.pred
|
229
|
+
assert_element_sameness(e1, Color::red, :red)
|
230
|
+
assert_equal(1, e1.ordinal)
|
231
|
+
|
232
|
+
e1 = e1.pred
|
233
|
+
assert_element_sameness(e1, Color::blue, :blue)
|
234
|
+
assert_equal(0, e1.ordinal)
|
235
|
+
end
|
236
|
+
|
237
|
+
def test_get_as_array
|
238
|
+
a = Color.to_a
|
239
|
+
assert(a.is_a?(Array))
|
240
|
+
assert_equal(COLOR_SIZE, a.size)
|
241
|
+
assert(a.first.is_a?(Element))
|
242
|
+
assert_element_sameness(Color.first, a.first, :blue)
|
243
|
+
assert_element_sameness(Color.first.succ, a[1], :red)
|
244
|
+
end
|
245
|
+
|
246
|
+
def test_aliases
|
247
|
+
assert_equal(Car::toyota.index, Car::toyota.to_i)
|
248
|
+
assert_equal(Car::toyota.to_i, Car::toyota.to_i)
|
249
|
+
assert_equal(Car::toyota.to_int, Car::toyota.to_i)
|
250
|
+
assert_equal(Car::toyota.ord, Car::toyota.to_i)
|
251
|
+
assert_equal(Car::toyota.ordinal, Car::toyota.to_i)
|
252
|
+
assert_equal(Car::toyota.to_sym, Car::toyota.value)
|
253
|
+
assert_equal(:toyota, Car::toyota.to_sym)
|
254
|
+
assert_equal(:toyota, Car::toyota.value)
|
255
|
+
assert_equal("toyota", Car::toyota.display)
|
256
|
+
assert_equal("Toyota", Car::toyota.humanize)
|
257
|
+
end
|
258
|
+
|
259
|
+
def test_to_a_gives_elements_in_ordinal_position
|
260
|
+
a = Color.to_a
|
261
|
+
assert(a.is_a?(Array))
|
262
|
+
assert_equal(COLOR_SIZE, a.size)
|
263
|
+
assert(a.first.is_a?(Element))
|
264
|
+
a.each_with_index do |element, index|
|
265
|
+
assert_equal(index, element.ordinal)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
def test_each_gives_elements_in_ordinal_position
|
270
|
+
Car.each_with_index do |element, index|
|
271
|
+
assert_equal(index, element.ordinal)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def test_sorted_by_position_is_equal_to_ordinal_when_position_not_specified
|
276
|
+
a = Color.to_a
|
277
|
+
b = Color.sort
|
278
|
+
assert(b.is_a?(Array))
|
279
|
+
assert_equal(COLOR_SIZE, b.size)
|
280
|
+
assert(b.first.is_a?(Element))
|
281
|
+
|
282
|
+
# Position (and thus order) should be same as ordinal order when position not specified at creation
|
283
|
+
a.each_with_index do |element, index|
|
284
|
+
assert_element_sameness(element, b[index], element.to_sym)
|
285
|
+
end
|
286
|
+
|
287
|
+
Color.sort.each_with_index do |element, index|
|
288
|
+
assert_element_sameness(element, b[index], element.to_sym)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
def test_can_use_map
|
293
|
+
Car.map{|m| assert(m.is_a?(Element))}
|
294
|
+
end
|
295
|
+
|
296
|
+
def test_equality
|
297
|
+
assert_equal(:mazda, Car::mazda.value)
|
298
|
+
assert_equal(:mazda, Car::mazda.to_sym)
|
299
|
+
end
|
300
|
+
|
301
|
+
def test_sorted_by_position_is_different_than_ordinal_order
|
302
|
+
a = Car.to_a
|
303
|
+
b = Car.sort
|
304
|
+
assert(b.is_a?(Array))
|
305
|
+
assert_equal(CAR_SIZE, b.size)
|
306
|
+
assert(b.first.is_a?(Element))
|
307
|
+
assert_element_sameness(Car::mazda, b.first, :mazda)
|
308
|
+
assert_element_sameness(Car::honda, b.last, :honda)
|
309
|
+
|
310
|
+
# Position (and thus order) should be same as ordinal order when position not specified at creation
|
311
|
+
a.each_with_index do |element, index|
|
312
|
+
assert_not_equal(b[index], element)
|
313
|
+
end
|
314
|
+
|
315
|
+
def test_sorted_by_position_is_ascending_by_position
|
316
|
+
b = Car.sort
|
317
|
+
last_ordinal = -1
|
318
|
+
b.each_with_index do |element, index|
|
319
|
+
assert(last_ordinal < element.ordinal)
|
320
|
+
last_ordinal = element.ordinal
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
def test_display_can_be_set
|
326
|
+
assert_equal("yellow", Color::yellow.to_s)
|
327
|
+
assert_equal("yellow", "#{Color::yellow}")
|
328
|
+
assert_equal("yellow", Color::yellow.display)
|
329
|
+
|
330
|
+
a = Color::blue
|
331
|
+
assert_equal("Hazel Blue", a.display)
|
332
|
+
assert_equal("blue", "#{a}")
|
333
|
+
|
334
|
+
assert_equal("Fire Engine Red", Color::red.display)
|
335
|
+
end
|
336
|
+
|
337
|
+
def test_setting_display_does_not_affect_symbol_to_string
|
338
|
+
a = Color::blue
|
339
|
+
assert_equal("Hazel Blue", a.display)
|
340
|
+
assert_equal("blue", "#{a}")
|
341
|
+
assert_not_equal("Hazel Blue", a.to_s)
|
342
|
+
end
|
343
|
+
|
344
|
+
def test_default_is_false_by_default
|
345
|
+
Car.each{|c| assert_equal(false, c.default?)}
|
346
|
+
end
|
347
|
+
|
348
|
+
def test_banana_is_default
|
349
|
+
assert_equal(true, Fruit::banana.default?)
|
350
|
+
end
|
351
|
+
|
352
|
+
def test_can_get_array_of_defaults
|
353
|
+
a = Fruit.defaults
|
354
|
+
b = [Fruit::banana, Fruit::pear]
|
355
|
+
assert_equal(2, a.size)
|
356
|
+
assert_equal(2, b.size)
|
357
|
+
b.each{|element| assert(a.include?(element), "a, #{a.inspect} does not contain #{element.inspect}")}
|
358
|
+
end
|
359
|
+
|
360
|
+
def test_defaults_is_only_defaults
|
361
|
+
a = [Fruit::banana, Fruit::pear]
|
362
|
+
c = Fruit.reject{|r| a.include?(r)}
|
363
|
+
assert_equal(2, a.size)
|
364
|
+
assert_equal(FRUIT_SIZE - 2, c.size)
|
365
|
+
c.each{|element| assert(!a.include?(element), "a, #{a.inspect} should not contain #{element.inspect}")}
|
366
|
+
end
|
367
|
+
|
368
|
+
def test_is_conditional
|
369
|
+
assert_equal(true, Fruit::banana.is?(:banana))
|
370
|
+
assert_equal(true, Fruit::banana.is?(Fruit::banana))
|
371
|
+
assert_equal(false, Fruit::banana.is?(:kiwi))
|
372
|
+
assert_equal(true, TalkingNumbers[1].is?(:uno))
|
373
|
+
assert_equal(true, TalkingNumbers[2].is?("dos"))
|
374
|
+
assert_equal(true, TalkingNumbers["tres"].is?(3))
|
375
|
+
end
|
376
|
+
|
377
|
+
def test_extended_attributes
|
378
|
+
assert_equal('1st', TalkingNumbers[1].alt)
|
379
|
+
assert_equal('2nd', TalkingNumbers[:dos].alt)
|
380
|
+
assert_equal('3rd', TalkingNumbers["tres"].alt)
|
381
|
+
|
382
|
+
assert_equal(true, TalkingNumbers[0].zero)
|
383
|
+
assert_equal(false, TalkingNumbers[1].zero)
|
384
|
+
assert_equal(false, TalkingNumbers[:dos].zero)
|
385
|
+
assert_equal(false, TalkingNumbers["tres"].zero)
|
386
|
+
|
387
|
+
assert_equal(0, TalkingNumbers[0].custom)
|
388
|
+
assert_equal(true, TalkingNumbers[1].custom.is_a?(Float))
|
389
|
+
assert_equal(true, TalkingNumbers[2].custom.is_a?(Fixnum))
|
390
|
+
assert_equal(true, TalkingNumbers[3].custom.is_a?(String))
|
391
|
+
end
|
392
|
+
|
393
|
+
def test_synonym_accessor
|
394
|
+
a = Fruit::apple
|
395
|
+
b = Fruit::machintosh
|
396
|
+
assert_equal(a, b)
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
metadata
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: elemental
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael Lang
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-06-03 00:00:00 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Elemental provides enumerated set of elements allowing your code to think symbolically and unambiguously while giving you the means to easily display what end-users need to see.
|
17
|
+
email: mwlang@cybrains.net
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.txt
|
24
|
+
files:
|
25
|
+
- elemental.gemspec
|
26
|
+
- README.txt
|
27
|
+
- Rakefile
|
28
|
+
- lib/element.rb
|
29
|
+
- lib/elemental.rb
|
30
|
+
- test/test_elemental.rb
|
31
|
+
has_rdoc: true
|
32
|
+
homepage: http://github.com/mwlang/elemental
|
33
|
+
post_install_message:
|
34
|
+
rdoc_options:
|
35
|
+
- --line-numbers
|
36
|
+
- --inline-source
|
37
|
+
- --title
|
38
|
+
- Elemental
|
39
|
+
- --main
|
40
|
+
- README.txt
|
41
|
+
require_paths:
|
42
|
+
- lib
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: "0"
|
48
|
+
version:
|
49
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0"
|
54
|
+
version:
|
55
|
+
requirements: []
|
56
|
+
|
57
|
+
rubyforge_project:
|
58
|
+
rubygems_version: 1.3.1
|
59
|
+
signing_key:
|
60
|
+
specification_version: 2
|
61
|
+
summary: Implements a set of elements that are enumerable.
|
62
|
+
test_files: []
|
63
|
+
|