elemental 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.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.
|