jeffp-enumerated_attribute 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +7 -0
- data/README.rdoc +47 -14
- data/Rakefile +1 -1
- data/lib/enumerated_attribute.rb +112 -52
- data/spec/tractor.rb +4 -2
- data/spec/tractor_spec.rb +35 -0
- metadata +2 -2
data/CHANGELOG.rdoc
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
== master
|
2
2
|
|
3
|
+
== 0.1.2 / 2009-07-11
|
4
|
+
|
5
|
+
* Added 'is_not' for negated custom method definitions
|
6
|
+
* Added dynamic creation of 'nil' predicate methods
|
7
|
+
* Refactored MethodDefinitionDSL
|
8
|
+
* Added EnumeratedAttribute::MethodDefinition
|
9
|
+
|
3
10
|
== 0.1.1 / 2009-07-09
|
4
11
|
|
5
12
|
* Added enum_attr_reader and enum_attr_writer
|
data/README.rdoc
CHANGED
@@ -63,7 +63,7 @@ form of +enumerated_attribute+ looks like this:
|
|
63
63
|
|
64
64
|
Defining the attribute :gear has done a number things.
|
65
65
|
It has generated an instance variable '@gear', read/write accessors for the
|
66
|
-
attribute and
|
66
|
+
attribute and support methods
|
67
67
|
for the enumeration, including an incrementor and decrementor. These methods are
|
68
68
|
demonstrated below using the Tractor class defined above:
|
69
69
|
|
@@ -72,9 +72,9 @@ demonstrated below using the Tractor class defined above:
|
|
72
72
|
|
73
73
|
t = Tractor.new
|
74
74
|
t.gear # => :neutral
|
75
|
-
t.gear= :reverse
|
75
|
+
t.gear = :reverse # => :reverse
|
76
76
|
t.gear # => :reverse
|
77
|
-
t.gear= :third
|
77
|
+
t.gear = :third
|
78
78
|
# => ArgumentError: 'third' is not an enumerated value for gear attribute
|
79
79
|
|
80
80
|
t.gears # => [:reverse, :neutral, :first, :second, :over_drive]
|
@@ -144,7 +144,7 @@ the +initialize+ method. Two ways are demonstrated here:
|
|
144
144
|
enum_attr :front_light, %w(off low high), :init=>:off
|
145
145
|
end
|
146
146
|
|
147
|
-
t=Tractor.new
|
147
|
+
t = Tractor.new
|
148
148
|
t.gear # => :neutral
|
149
149
|
t.front_light # => :off
|
150
150
|
|
@@ -157,6 +157,26 @@ The plugin recognizes that the gear attribute is to be initialized to :neutral.
|
|
157
157
|
Alternatively, the :init option can be used to indicate the initial value of the attribute.
|
158
158
|
|
159
159
|
|
160
|
+
==== Setting Attributes to nil
|
161
|
+
|
162
|
+
By default, the attribute setter does not allow nils unless the :nil option is set to true
|
163
|
+
in the definition as demonstrated here:
|
164
|
+
|
165
|
+
class Tractor
|
166
|
+
enum_attr :plow %w(^up down), :nil=>true
|
167
|
+
end
|
168
|
+
|
169
|
+
t = Tractor.new
|
170
|
+
t.plow # => :up
|
171
|
+
t.plow_nil? # => :false
|
172
|
+
t.plow = nil # => nil
|
173
|
+
t.plow_is_nil? # => true
|
174
|
+
t.plow_is_not_nil? # => false
|
175
|
+
|
176
|
+
Regardless of the :nil option setting, the plugin can dynamically recognize and define
|
177
|
+
predicate methods for testing 'nil' values.
|
178
|
+
|
179
|
+
|
160
180
|
==== Changing Method Names
|
161
181
|
|
162
182
|
The plugin provides options for changing the method names of the enumeration accessor, incrementor
|
@@ -167,7 +187,7 @@ and decrementor (ie, +gears+, +gear_next+, +gear_previous+):
|
|
167
187
|
:inc=>'lights_inc', :dec=>'lights_dec'
|
168
188
|
end
|
169
189
|
|
170
|
-
t=Tractor.new
|
190
|
+
t = Tractor.new
|
171
191
|
t.lights_values # => [:off, :low, :high]
|
172
192
|
t.lights_inc # => :low
|
173
193
|
t.lights_dec # => :off
|
@@ -187,13 +207,13 @@ the plugin provides a short-hand for defining these methods in the
|
|
187
207
|
+enumerated_attribute+ block.
|
188
208
|
|
189
209
|
class Tractor
|
190
|
-
enum_attr :gear, %(reverse ^neutral first second over_drive) do
|
210
|
+
enum_attr :gear, %w(reverse ^neutral first second over_drive) do
|
191
211
|
parked? :neutral
|
192
212
|
driving? [:first, :second, :third]
|
193
213
|
end
|
194
214
|
end
|
195
215
|
|
196
|
-
t=Tractor.new
|
216
|
+
t = Tractor.new
|
197
217
|
t.parked? # => true
|
198
218
|
t.driving? # => false
|
199
219
|
|
@@ -203,16 +223,29 @@ The first method, parked?, defines a method which evaluates
|
|
203
223
|
the code {@gear == :neutral}. The second method, driving?, evaluates
|
204
224
|
to true if the attribute is set to one of the enumeration values defined in the array
|
205
225
|
[:first, :second, :third].
|
206
|
-
|
226
|
+
|
227
|
+
The same short-hand can be used to define methods where the attribute 'is not' equal to the
|
228
|
+
indicated value or 'is not' included in the array of values.
|
229
|
+
|
230
|
+
class Tractor
|
231
|
+
enum_attr :gear, %w(reverse ^neutral first second over_drive) do
|
232
|
+
not_parked? is_not :neutral
|
233
|
+
not_driving? is_not [:first, :second, :third]
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
|
238
|
+
==== Defining Other Methods With Blocks
|
239
|
+
|
207
240
|
For predicate methods requiring fancier logic,
|
208
241
|
a block can be used to define the method body.
|
209
242
|
|
210
243
|
class Tractor
|
211
|
-
enum_attr :gear, %(reverse ^neutral first second over_drive) do
|
244
|
+
enum_attr :gear, %w(reverse ^neutral first second over_drive) do
|
212
245
|
parked? :neutral
|
213
246
|
driving? [:first, :second, :third]
|
214
247
|
end
|
215
|
-
enum_attr :plow, %(^up down), :plural=>:plow_values do
|
248
|
+
enum_attr :plow, %w(^up down), :plural=>:plow_values do
|
216
249
|
plowing? { self.gear_is_in_first? && @plow == :down }
|
217
250
|
end
|
218
251
|
end
|
@@ -227,7 +260,7 @@ can be defined to manipulate the attributes. Here, two methods are defined acti
|
|
227
260
|
as bounded incrementor and decrementor of the gear attribute.
|
228
261
|
|
229
262
|
class Tractor
|
230
|
-
enum_attr :gear, %(reverse ^neutral first second over_drive) do
|
263
|
+
enum_attr :gear, %w(reverse ^neutral first second over_drive) do
|
231
264
|
parked? :neutral
|
232
265
|
driving? [:first, :second, :third]
|
233
266
|
upshift { self.gear_is_in_over_drive? ? self.gear : self.gear_next }
|
@@ -236,11 +269,11 @@ as bounded incrementor and decrementor of the gear attribute.
|
|
236
269
|
end
|
237
270
|
|
238
271
|
t = Tractor.new
|
239
|
-
t.gear
|
272
|
+
t.gear # => :neutral
|
240
273
|
10.times { t.upshift }
|
241
|
-
t.gear
|
274
|
+
t.gear # => :over_drive
|
242
275
|
10.times { t.downshift }
|
243
|
-
t.gear
|
276
|
+
t.gear # => :neutral
|
244
277
|
|
245
278
|
Methods +upshift+ and +downshift+ use the automatically generated
|
246
279
|
incrementor and decrementor as
|
data/Rakefile
CHANGED
@@ -5,7 +5,7 @@ require 'rake/contrib/sshpublisher'
|
|
5
5
|
|
6
6
|
spec = Gem::Specification.new do |s|
|
7
7
|
s.name = 'enumerated_attribute'
|
8
|
-
s.version = '0.1.
|
8
|
+
s.version = '0.1.2'
|
9
9
|
s.platform = Gem::Platform::RUBY
|
10
10
|
s.description = 'A enumerated attribute accessor'
|
11
11
|
s.summary = 'Defines enumerated attributes, initial state and dynamic state methods.'
|
data/lib/enumerated_attribute.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
1
|
module EnumeratedAttribute
|
2
2
|
|
3
|
+
#todo: is_not/is -- test raised errors contain useful info on invalid syntax
|
4
|
+
#todo: dynamic_enums
|
3
5
|
#todo: system wide constants
|
4
6
|
#todo: setter_callback
|
5
7
|
#todo: ArgumentError may need to use Errors for ActiveRecord
|
6
8
|
#todo: test new chaining plays nice
|
9
|
+
#todo: attribute methods gear.enums, gear.inc, gear.dec
|
7
10
|
|
8
11
|
def enum_attr_reader(*args, &block)
|
9
12
|
if args.length > 1
|
@@ -86,11 +89,11 @@ module EnumeratedAttribute
|
|
86
89
|
def parse_dynamic_method_parts(meth_name)
|
87
90
|
return(nil) unless meth_name[-1, 1] == '?'
|
88
91
|
|
89
|
-
meth_name.chop
|
92
|
+
middle = meth_name.chop #remove the ?
|
90
93
|
|
91
94
|
attr = nil
|
92
95
|
@@enumerated_attribute_names.each do |name|
|
93
|
-
if
|
96
|
+
if middle.sub!(Regexp.new("^"+name.to_s), "")
|
94
97
|
attr = name; break
|
95
98
|
end
|
96
99
|
end
|
@@ -100,23 +103,26 @@ module EnumeratedAttribute
|
|
100
103
|
value = nil
|
101
104
|
if @@enumerated_attribute_values.key?(attr_sym)
|
102
105
|
@@enumerated_attribute_values[attr_sym].each do |v|
|
103
|
-
if
|
106
|
+
if middle.sub!(Regexp.new(v.to_s+"$"), "")
|
104
107
|
value = v; break
|
105
108
|
end
|
106
109
|
end
|
107
110
|
end
|
108
|
-
|
111
|
+
unless value #check for nil?
|
112
|
+
return (nil) unless middle.sub!(Regexp.new('nil$'), "")
|
113
|
+
value = nil
|
114
|
+
end
|
109
115
|
|
110
|
-
[attr,
|
116
|
+
[attr, middle, value]
|
111
117
|
end
|
112
118
|
|
113
119
|
def define_enumerated_attribute_dynamic_method(methId)
|
114
120
|
meth_name = methId.id2name
|
115
121
|
return(false) unless (parts = parse_dynamic_method_parts(meth_name))
|
116
|
-
return(false) unless parts
|
117
122
|
|
118
|
-
negated = !!parts[
|
119
|
-
|
123
|
+
negated = !!parts[1].downcase.match(/_not_/)
|
124
|
+
value = parts[2] ? parts[2].to_sym : nil
|
125
|
+
self.class.define_enumerated_attribute_custom_method(methId, parts[0], value, negated)
|
120
126
|
|
121
127
|
true
|
122
128
|
end
|
@@ -124,7 +130,7 @@ module EnumeratedAttribute
|
|
124
130
|
METHOD
|
125
131
|
|
126
132
|
#create state and action methods from block
|
127
|
-
initial_value = opts[:init] || initial_value
|
133
|
+
initial_value = opts[:init] || initial_value
|
128
134
|
if block_given?
|
129
135
|
m = EnumeratedAttribute::MethodDefinitionDSL.new(self, attr_name, enums)
|
130
136
|
m.instance_eval(&block)
|
@@ -150,9 +156,6 @@ module EnumeratedAttribute
|
|
150
156
|
index = z.index(@#{attr_name})
|
151
157
|
@#{attr_name} = z[index > 0 ? index-1 : z.size-1]
|
152
158
|
end
|
153
|
-
def #{attr_name}_nil?
|
154
|
-
@#{attr_name} == nil
|
155
|
-
end
|
156
159
|
ENUM
|
157
160
|
end
|
158
161
|
|
@@ -182,7 +185,7 @@ module EnumeratedAttribute
|
|
182
185
|
#a short cut
|
183
186
|
alias :enum_attr :enumerated_attribute
|
184
187
|
|
185
|
-
def
|
188
|
+
def define_enumerated_attribute_custom_method(symbol, attr_name, value, negated)
|
186
189
|
define_method symbol do
|
187
190
|
ival = instance_variable_get('@'+attr_name)
|
188
191
|
negated ? ival != value : ival == value
|
@@ -208,51 +211,74 @@ module EnumeratedAttribute
|
|
208
211
|
|
209
212
|
public
|
210
213
|
|
214
|
+
class MethodDefinition
|
215
|
+
attr_accessor :method_name, :negated, :argument
|
216
|
+
|
217
|
+
def initialize(name, arg, negated=false)
|
218
|
+
@method_name = name
|
219
|
+
@negated = negated
|
220
|
+
@argument = arg
|
221
|
+
end
|
222
|
+
|
223
|
+
def is_predicate_method?
|
224
|
+
@method_name[-1, 1] == '?'
|
225
|
+
end
|
226
|
+
def has_method_name?
|
227
|
+
!!@method_name
|
228
|
+
end
|
229
|
+
def has_argument?
|
230
|
+
!!@argument
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
234
|
+
|
211
235
|
class MethodDefinitionDSL
|
212
236
|
attr_reader :initial_value, :pluralized_name, :decrementor_name, :incrementor_name
|
213
237
|
|
214
238
|
def initialize(class_obj, attr_name, values=[])
|
215
239
|
@class_obj = class_obj
|
216
240
|
@attr_name = attr_name
|
217
|
-
@attr_values = values
|
241
|
+
@attr_values = values
|
242
|
+
end
|
243
|
+
|
244
|
+
#we'll by pass this - they can use it if it helps make code more readable - not enforced - should it be??
|
245
|
+
def define
|
246
|
+
end
|
247
|
+
|
248
|
+
def is_not(*args)
|
249
|
+
arg = args[0] if args.length > 0
|
250
|
+
MethodDefinition.new(nil, arg, true)
|
251
|
+
end
|
252
|
+
alias :isnt :is_not
|
253
|
+
|
254
|
+
def is(*args)
|
255
|
+
arg = args[0] if args.length > 0
|
256
|
+
MethodDefinition.new(nil, arg)
|
218
257
|
end
|
219
258
|
|
220
259
|
def method_missing(methId, *args, &block)
|
221
260
|
meth_name = methId.id2name
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
arg = args[0]
|
226
|
-
if arg.instance_of?(Proc)
|
227
|
-
@class_obj.send(:define_method, methId, arg)
|
228
|
-
return
|
229
|
-
end
|
230
|
-
elsif block_given?
|
231
|
-
@class_obj.send(:define_method, methId, block)
|
232
|
-
return
|
233
|
-
end
|
234
|
-
raise ArgumentError, "method '#{meth_name}' must be followed by a proc or block", caller
|
235
|
-
end
|
236
|
-
|
237
|
-
if (args.size > 0)
|
261
|
+
|
262
|
+
meth_def = nil
|
263
|
+
if args.size > 0
|
238
264
|
arg = args[0]
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
@class_obj.send(:define_method, methId, arg)
|
248
|
-
return
|
265
|
+
if arg.instance_of?(EnumeratedAttribute::MethodDefinition)
|
266
|
+
if arg.has_method_name?
|
267
|
+
raise_method_syntax_error(meth_name, arg.method_name)
|
268
|
+
end
|
269
|
+
meth_def = arg
|
270
|
+
meth_def.method_name = meth_name
|
271
|
+
else
|
272
|
+
meth_def = MethodDefinition.new(meth_name, arg)
|
249
273
|
end
|
250
274
|
elsif block_given?
|
251
|
-
|
252
|
-
|
275
|
+
meth_def = MethodDefinition.new(meth_name, block)
|
276
|
+
else
|
277
|
+
raise_method_syntax_error(meth_name)
|
253
278
|
end
|
254
|
-
|
255
|
-
end
|
279
|
+
evaluate_method_definition(meth_def)
|
280
|
+
end
|
281
|
+
|
256
282
|
|
257
283
|
def init(value)
|
258
284
|
if (!@attr_values.empty? && !@attr_values.include?(value.to_sym))
|
@@ -271,22 +297,56 @@ module EnumeratedAttribute
|
|
271
297
|
|
272
298
|
private
|
273
299
|
|
274
|
-
def
|
275
|
-
|
276
|
-
|
300
|
+
def raise_method_syntax_error(meth_name, offending_token=nil)
|
301
|
+
suffix = offending_token ? "found '#{offending_token}'" : "found nothing"
|
302
|
+
followed_by = (meth_name[-1,1] == '?' ? "is_not, an enumeration value, an array of enumeration values, " : "") + "a proc, lambda or code block"
|
303
|
+
raise NameError, "'#{meth_name}' should be followed by #{followed_by} -- but #{suffix}"
|
304
|
+
end
|
305
|
+
|
306
|
+
def evaluate_method_definition(mdef)
|
307
|
+
unless mdef.has_argument?
|
308
|
+
return raise_method_syntax_error(mdef.method_name)
|
309
|
+
end
|
310
|
+
|
311
|
+
if mdef.is_predicate_method?
|
312
|
+
case mdef.argument
|
313
|
+
when String
|
314
|
+
return create_custom_method_for_symbol_or_string(mdef)
|
315
|
+
when Symbol
|
316
|
+
return create_custom_method_for_symbol_or_string(mdef)
|
317
|
+
when Array
|
318
|
+
return create_custom_method_for_array_of_enums(mdef)
|
319
|
+
when Proc
|
320
|
+
return create_custom_method_for_proc(mdef)
|
321
|
+
end
|
322
|
+
else #action method
|
323
|
+
if mdef.argument.instance_of?(Proc)
|
324
|
+
return create_custom_method_for_proc(mdef)
|
325
|
+
end
|
326
|
+
end
|
327
|
+
raise_method_syntax_error(mdef.method_name, mdef.argument)
|
328
|
+
end
|
329
|
+
|
330
|
+
def create_custom_method_for_proc(mdef)
|
331
|
+
@class_obj.send(:define_method, mdef.method_name, mdef.argument)
|
332
|
+
end
|
333
|
+
|
334
|
+
def create_custom_method_for_symbol_or_string(mdef)
|
335
|
+
if (!@attr_values.empty? && !@attr_values.include?(mdef.argument.to_sym))
|
336
|
+
raise(NameError, "'#{mdef.argument}' in method '#{mdef.method_name}' is not an enumeration value for :#{@attr_name} attribute", caller)
|
277
337
|
end
|
278
|
-
@class_obj.class_eval("def #{
|
338
|
+
@class_obj.class_eval("def #{mdef.method_name}; @#{@attr_name} #{mdef.negated ? '!=' : '=='} :#{mdef.argument}; end")
|
279
339
|
end
|
280
340
|
|
281
|
-
def
|
341
|
+
def create_custom_method_for_array_of_enums(mdef)
|
282
342
|
if !@attr_values.empty?
|
283
|
-
|
343
|
+
mdef.argument.each do |m|
|
284
344
|
if !@attr_values.include?(m.to_sym)
|
285
|
-
raise(NameError, "'#{m}' in method '#{
|
345
|
+
raise(NameError, "'#{m}' in method '#{mdef.method_name}' is not an enumeration value for :#{@attr_name} attribute", caller)
|
286
346
|
end
|
287
347
|
end
|
288
348
|
end
|
289
|
-
@class_obj.class_eval("def #{
|
349
|
+
@class_obj.class_eval("def #{mdef.method_name}; #{mdef.negated ? '!' : ''}[:#{mdef.argument.join(',:')}].include?(@#{@attr_name}); end")
|
290
350
|
end
|
291
351
|
end
|
292
352
|
end
|
data/spec/tractor.rb
CHANGED
@@ -13,8 +13,10 @@ class Tractor
|
|
13
13
|
end
|
14
14
|
|
15
15
|
enumerated_attribute :gear, %w(reverse ^neutral first second over_drive) do
|
16
|
-
parked? :neutral
|
17
|
-
driving? [:first, :second, :over_drive]
|
16
|
+
parked? is :neutral
|
17
|
+
driving? is [:first, :second, :over_drive]
|
18
|
+
not_parked? isnt :neutral
|
19
|
+
not_driving? is_not [:first, :second, :over_drive]
|
18
20
|
upshift { self.gear_is_in_over_drive? ? self.gear : self.gear_next }
|
19
21
|
downshift { self.driving? ? self.gear_previous : self.gear }
|
20
22
|
end
|
data/spec/tractor_spec.rb
CHANGED
@@ -1,6 +1,41 @@
|
|
1
1
|
require 'tractor'
|
2
2
|
|
3
3
|
describe "Tractor" do
|
4
|
+
|
5
|
+
it "should dynamically create :plow_nil? and :plow_not_nil?" do
|
6
|
+
t=Tractor.new
|
7
|
+
t.plow_nil?.should be_false
|
8
|
+
t.plow_not_nil?.should be_true
|
9
|
+
t.plow = nil
|
10
|
+
t.plow_not_nil?.should be_false
|
11
|
+
t.plow_nil?.should be_true
|
12
|
+
Tractor.instance_methods(false).should include('plow_nil?')
|
13
|
+
Tractor.instance_methods(false).should include('plow_not_nil?')
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should dynamically create :plow_is_nil? and :plow_is_not_nil?" do
|
17
|
+
t=Tractor.new
|
18
|
+
t.plow_is_nil?.should be_false
|
19
|
+
t.plow_is_not_nil?.should be_true
|
20
|
+
t.plow = nil
|
21
|
+
t.plow_is_not_nil?.should be_false
|
22
|
+
t.plow_is_nil?.should be_true
|
23
|
+
Tractor.instance_methods(false).should include('plow_is_nil?')
|
24
|
+
Tractor.instance_methods(false).should include('plow_is_not_nil?')
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should negate result for not_parked? defined with is_not" do
|
28
|
+
t=Tractor.new
|
29
|
+
t.gear = :neutral
|
30
|
+
t.not_parked?.should be_false
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should negate result for not_driving? defined with is_not" do
|
34
|
+
t=Tractor.new
|
35
|
+
t.gear = :neutral
|
36
|
+
t.not_driving?.should be_true
|
37
|
+
end
|
38
|
+
|
4
39
|
it "should have getter but no setter for :temperature" do
|
5
40
|
Tractor.instance_methods.should_not include('temperature=')
|
6
41
|
Tractor.instance_methods.should include('temperature')
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jeffp-enumerated_attribute
|
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
|
- Jeff Patmon
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-07-
|
12
|
+
date: 2009-07-11 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|