jeffp-enumerated_attribute 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/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
|
|