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 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 three support methods
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 # => :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 # => :neutral
272
+ t.gear # => :neutral
240
273
  10.times { t.upshift }
241
- t.gear # => :over_drive
274
+ t.gear # => :over_drive
242
275
  10.times { t.downshift }
243
- t.gear # => :neutral
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.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.'
@@ -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! #remove the ?
92
+ middle = meth_name.chop #remove the ?
90
93
 
91
94
  attr = nil
92
95
  @@enumerated_attribute_names.each do |name|
93
- if meth_name.sub!(Regexp.new("^"+name.to_s), "")
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 meth_name.sub!(Regexp.new(v.to_s+"$"), "")
106
+ if middle.sub!(Regexp.new(v.to_s+"$"), "")
104
107
  value = v; break
105
108
  end
106
109
  end
107
110
  end
108
- return (nil) unless value
111
+ unless value #check for nil?
112
+ return (nil) unless middle.sub!(Regexp.new('nil$'), "")
113
+ value = nil
114
+ end
109
115
 
110
- [attr, value, meth_name]
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[2].downcase.match(/_not_/)
119
- self.class.define_attribute_state_method(methId, parts[0], parts[1].to_sym, negated)
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 define_attribute_state_method(symbol, attr_name, value, negated)
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
- if meth_name[-1,1] != '?'
223
- #not a state method - this must include either a proc or block - no short cuts
224
- if args.size > 0
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
- case arg
240
- when Symbol
241
- return create_method_from_symbol_or_string(meth_name, arg)
242
- when String
243
- return create_method_from_symbol_or_string(meth_name, arg)
244
- when Array
245
- return create_method_from_array(meth_name, arg)
246
- when Proc
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
- @class_obj.send(:define_method, methId, block)
252
- return
275
+ meth_def = MethodDefinition.new(meth_name, block)
276
+ else
277
+ raise_method_syntax_error(meth_name)
253
278
  end
254
- raise ArgumentError , "method '#{meth_name}' for :#{@attr_name} attribute must be followed by a symbol, array, proc or block", caller
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 create_method_from_symbol_or_string(meth_name, arg)
275
- if (!@attr_values.empty? && !@attr_values.include?(arg.to_sym))
276
- raise(NameError, "'#{arg}' in method '#{meth_name}' is not an enumeration value for :#{@attr_name} attribute", caller)
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 #{meth_name}; @#{@attr_name} == :#{arg}; end")
338
+ @class_obj.class_eval("def #{mdef.method_name}; @#{@attr_name} #{mdef.negated ? '!=' : '=='} :#{mdef.argument}; end")
279
339
  end
280
340
 
281
- def create_method_from_array(meth_name, arg)
341
+ def create_custom_method_for_array_of_enums(mdef)
282
342
  if !@attr_values.empty?
283
- arg.each do |m|
343
+ mdef.argument.each do |m|
284
344
  if !@attr_values.include?(m.to_sym)
285
- raise(NameError, "'#{m}' in method '#{meth_name}' is not an enumeration value for :#{@attr_name} attribute", caller)
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 #{meth_name}; [:#{arg.join(',:')}].include?(@#{@attr_name}); end")
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.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-09 00:00:00 -07:00
12
+ date: 2009-07-11 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15