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