jeffp-enumerated_attribute 0.1.5 → 0.1.6

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/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.5'
8
+ s.version = '0.1.6'
9
9
  s.platform = Gem::Platform::RUBY
10
10
  s.description = 'An enumerated attribute accessor'
11
11
  s.summary = 'Add enumerated attributes with initialization, dynamic predicate methods, more ...'
@@ -1,544 +1,29 @@
1
- module EnumeratedAttribute
2
-
3
- class EnumeratedAttributeError < StandardError; end
4
- class IntegrationError < EnumeratedAttributeError; end
5
- class InvalidEnumeration < EnumeratedAttributeError; end
6
- class InvalidDefinition < EnumeratedAttributeError; end
7
-
8
- def enumerated_attribute(*args, &block)
9
- return if args.empty?
10
- attr_name = args[0].to_s
11
- attr_sym = attr_name.to_sym
12
- enums = (args[1] && args[1].instance_of?(Array) ? args[1] : [])
13
- index = enums.empty? ? 1 : 2
14
- opts = (args[index] && args[index].instance_of?(Hash) ? args[index] : {})
15
-
16
- raise(InvalidDefinition, 'second argument of enumerated_attribute/enum_attr is not an array of symbols or strings representing the enum values', caller) if enums.empty?
17
-
18
- initial_value = nil
19
- plural_name = opts[:plural] || opts[:enums_accessor] || opts[:enums] || begin
20
- case
21
- when attr_name =~ /[aeiou]y$/
22
- "#{attr_name}s"
23
- when attr_name =~ /y$/
24
- attr_name.sub(/y$/, 'ies')
25
- when attr_name =~ /(sh|ch|x|s)$/
26
- "#{attr_name}es"
27
- else
28
- "#{attr_name}s"
29
- end
30
- end
31
- incrementor = opts[:incrementor] || opts[:inc] || "#{attr_name}_next"
32
- decrementor = opts[:decrementor] || opts[:dec] || "#{attr_name}_previous"
33
-
34
- enums = enums.map{|v| (v =~ /^\^/ ? (initial_value = v[1, v.length-1].to_sym) : v.to_sym )}
1
+ require 'enumerated_attribute/attribute'
35
2
 
36
- class_eval <<-ATTRIB
37
- @@enumerated_attribute_names ||= []
38
- @@enumerated_attribute_names << '#{attr_name}'
39
- @@enumerated_attribute_options ||={}
40
- @@enumerated_attribute_options[:#{attr_name}] = {#{opts.to_a.map{|v| ':'+v.first.to_s+'=>:'+v.last.to_s}.join(', ')}}
41
- @@enumerated_attribute_values ||= {}
42
- @@enumerated_attribute_values[:#{attr_name}] = [:#{enums.join(',:')}]
43
- ATTRIB
44
-
45
- #define_enumerated_attribute_[writer, reader] may be modified in a named Integrations module (see Integrations::ActiveRecord)
46
- class_eval <<-MAP
47
- unless @integration_map
48
- @integration_map = Integrations.find_integration_map(self)
49
- @integration_map[:aliasing].each do |p|
50
- alias_method(p.first, p.last)
51
- end
52
- include(EnumeratedAttribute::Integrations::Default)
53
- include(@integration_map[:module]) if @integration_map[:module]
54
-
55
- def self.has_enumerated_attribute?(name)
56
- @@enumerated_attribute_names.include?(name.to_s)
57
- end
58
- def self.enumerated_attribute_allows_nil?(name)
59
- return (false) unless @@enumerated_attribute_options[name.to_sym]
60
- @@enumerated_attribute_options[name.to_sym][:nil] || false
61
- end
62
- def self.enumerated_attribute_allows_value?(name, value)
63
- return (false) unless @@enumerated_attribute_values[name.to_sym]
64
- return enumerated_attribute_allows_nil?(name) if value == nil
65
- @@enumerated_attribute_values[name.to_sym].include?(value.to_sym)
66
- end
67
- end
68
- MAP
69
-
70
- #create accessors
71
- define_enumerated_attribute_reader_method(attr_sym) unless (opts.key?(:reader) && !opts[:reader])
72
- define_enumerated_attribute_writer_method(attr_sym) unless (opts.key?(:writer) && !opts[:writer])
73
-
74
- #define dynamic methods in method_missing
75
- class_eval <<-METHOD
76
- unless @enumerated_attribute_define_once_only
77
- if method_defined?(:method_missing)
78
- alias_method(:method_missing_without_enumerated_attribute, :method_missing)
79
- def method_missing(methId, *args, &block)
80
- return self.send(methId) if define_enumerated_attribute_dynamic_method(methId)
81
- method_missing_without_enumerated_attribute(methId, *args, &block)
82
- end
83
- else
84
- def method_missing(methId, *args, &block)
85
- return self.send(methId) if define_enumerated_attribute_dynamic_method(methId)
86
- super
87
- end
88
- end
89
- @enumerated_attribute_define_once_only = true
90
-
91
- alias_method :respond_to_without_enumerated_attribute?, :respond_to?
92
- def respond_to?(method)
93
- respond_to_without_enumerated_attribute?(method) || !!parse_dynamic_method_parts(method.to_s)
94
- end
95
-
96
- def initialize_enumerated_attributes(only_if_nil = false)
97
- self.class.enumerated_attribute_initial_value_list.each do |k,v|
98
- self.write_enumerated_attribute(k, v) unless (only_if_nil && read_enumerated_attribute(k) != nil)
99
- end
100
- end
101
-
102
- private
103
-
104
- def parse_dynamic_method_parts(meth_name)
105
- return(nil) unless meth_name[-1, 1] == '?'
106
-
107
- middle = meth_name.chop #remove the ?
108
-
109
- attr = nil
110
- @@enumerated_attribute_names.each do |name|
111
- if middle.sub!(Regexp.new("^"+name.to_s), "")
112
- attr = name; break
113
- end
114
- end
115
- return (nil) unless attr
116
- attr_sym = attr.to_sym
117
-
118
- value = nil
119
- if @@enumerated_attribute_values.key?(attr_sym)
120
- @@enumerated_attribute_values[attr_sym].each do |v|
121
- if middle.sub!(Regexp.new(v.to_s+"$"), "")
122
- value = v; break
123
- end
124
- end
125
- end
126
- unless value #check for nil?
127
- return (nil) unless middle.sub!(Regexp.new('nil$'), "")
128
- value = nil
129
- end
130
-
131
- [attr, middle, value]
132
- end
133
-
134
- def define_enumerated_attribute_dynamic_method(methId)
135
- meth_name = methId.id2name
136
- return(false) unless (parts = parse_dynamic_method_parts(meth_name))
137
-
138
- negated = !!parts[1].downcase.match(/_not_/)
139
- value = parts[2] ? parts[2].to_sym : nil
140
- self.class.define_enumerated_attribute_custom_method(methId, parts[0], value, negated)
141
-
142
- true
143
- end
144
-
145
- end
146
- METHOD
147
-
148
- #create state and action methods from block
149
- initial_value = opts[:init] || initial_value
150
- if block_given?
151
- m = EnumeratedAttribute::MethodDefinitionDSL.new(self, attr_name, enums)
152
- m.instance_eval(&block)
153
- initial_value = m.initial_value || initial_value
154
- plural_name = m.pluralized_name || plural_name
155
- decrementor = m.decrementor_name || decrementor
156
- incrementor = m.incrementor_name || incrementor
157
- end
3
+ module EnumeratedAttribute
158
4
 
159
- #define the enum values accessor
160
- class_eval <<-ENUM
161
- def #{plural_name}
162
- @@enumerated_attribute_values[:#{attr_name}]
163
- end
164
- def #{incrementor}
165
- z = @@enumerated_attribute_values[:#{attr_name}]
166
- index = z.index(@#{attr_name})
167
- write_enumerated_attribute(:#{attr_name}, z[index >= z.size-1 ? 0 : index+1])
168
- end
169
- def #{decrementor}
170
- z = @@enumerated_attribute_values[:#{attr_name}]
171
- index = z.index(@#{attr_name})
172
- write_enumerated_attribute(:#{attr_name}, z[index > 0 ? index-1 : z.size-1])
173
- end
174
- ENUM
5
+ module MacroMethods
175
6
 
176
- unless @enumerated_attribute_init
177
- define_enumerated_attribute_new_method
178
- end
179
- @enumerated_attribute_init ||= {}
180
- if (initial_value = opts[:init] || initial_value)
181
- @enumerated_attribute_init[attr_sym] = initial_value
182
- end
183
- class_eval do
7
+ def enumerated_attribute(*args, &block)
184
8
  class << self
185
- def enumerated_attribute_initial_value_list; @enumerated_attribute_init; end
186
- end
187
- end
188
-
189
- def self.define_enumerated_attribute_custom_method(symbol, attr_name, value, negated)
190
- define_method symbol do
191
- ival = read_enumerated_attribute(attr_name)
192
- negated ? ival != value : ival == value
9
+ include EnumeratedAttribute::Attribute
193
10
  end
11
+ create_enumerated_attribute(*args, &block)
194
12
  end
13
+ alias_method :enum_attr, :enumerated_attribute
195
14
 
15
+ #these implementations are for basic ruby objects - integrations (see Integrations::ActiveRecord and Integrations::Object) may alter them
16
+ #def define_enumerated_attribute_custom_method(symbol, attr_name, value, negated)
17
+ #private
18
+ #def define_enumerated_attribute_new_method
19
+ #def define_enumerated_attribute_writer_method name
20
+ #def define_enumerated_attribute_reader_method name
196
21
  end
197
- alias_method :enum_attr, :enumerated_attribute
198
-
199
- =begin
200
- def define_enumerated_attribute_custom_method(symbol, attr_name, value, negated)
201
- define_method symbol do
202
- ival = read_enumerated_attribute(attr_name)
203
- negated ? ival != value : ival == value
204
- end
205
- end
206
- =end
207
-
208
- #these implementations are for basic ruby objects - integrations (see Integrations::ActiveRecord and Integrations::Object) may alter them
209
- #def define_enumerated_attribute_new_method
210
- #def define_enumerated_attribute_writer_method name
211
- #def define_enumerated_attribute_reader_method name
212
22
 
213
- module Integrations
214
- module Default
215
- def self.included(klass); klass.extend(ClassMethods); end
216
-
217
- module ClassMethods
218
- def define_enumerated_attribute_writer_method name
219
- name = name.to_s
220
- class_eval <<-METHOD
221
- def #{name}=(val); write_enumerated_attribute(:#{name}, val); end
222
- METHOD
223
- end
224
-
225
- def define_enumerated_attribute_reader_method name
226
- name = name.to_s
227
- class_eval <<-METHOD
228
- def #{name}; read_enumerated_attribute(:#{name}); end
229
- METHOD
230
- end
231
- end
232
- end
233
-
234
- module Object
235
- def self.included(klass)
236
- klass.extend(ClassMethods)
237
- end
238
-
239
- def write_enumerated_attribute(name, val)
240
- name = name.to_s
241
- val = val.to_sym if val
242
- unless self.class.enumerated_attribute_allows_value?(name, val)
243
- raise(InvalidEnumeration, "nil is not allowed on '#{name}' attribute, set :nil=>true option", caller) unless val
244
- raise(InvalidEnumeration, ":#{val} is not a defined enumeration value for the '#{name}' attribute", caller)
245
- end
246
- instance_variable_set('@'+name, val)
247
- end
248
-
249
- def read_enumerated_attribute(name)
250
- return instance_variable_get('@'+name.to_s)
251
- end
252
-
253
- module ClassMethods
254
- private
255
-
256
- def define_enumerated_attribute_new_method
257
- class_eval <<-NEWMETH
258
- class << self
259
- alias_method :new_without_enumerated_attribute, :new
260
- def new(*args, &block)
261
- result = new_without_enumerated_attribute(*args)
262
- result.initialize_enumerated_attributes
263
- yield result if block_given?
264
- result
265
- end
266
- end
267
- NEWMETH
268
- end
269
-
270
- end
271
-
272
- end
273
-
274
- module ActiveRecord
275
- def self.included(klass)
276
- klass.extend(ClassMethods)
277
- end
278
-
279
- def write_enumerated_attribute(name, val)
280
- name = name.to_s
281
- return write_attribute(name, val) unless self.class.has_enumerated_attribute?(name)
282
- val_str = val.to_s if val
283
- val_sym = val.to_sym if val
284
- unless self.class.enumerated_attribute_allows_value?(name, val_sym)
285
- raise(InvalidEnumeration, "nil is not allowed on '#{name}' attribute, set :nil=>true option", caller) unless val
286
- raise(InvalidEnumeration, ":#{val_str} is not a defined enumeration value for the '#{name}' attribute", caller)
287
- end
288
- return instance_variable_set('@'+name, val_sym) unless self.has_attribute?(name)
289
- write_attribute(name, val_str)
290
- end
291
-
292
- def read_enumerated_attribute(name)
293
- name = name.to_s
294
- #if not enumerated - let active record handle it
295
- return read_attribute(name) unless self.class.has_enumerated_attribute?(name)
296
- #if enumerated, is it an active record attribute, if not, the value is stored in an instance variable
297
- return instance_variable_get('@'+name) unless self.has_attribute?(name)
298
- #this is an enumerated active record attribute
299
- val = read_attribute(name)
300
- val = val.to_sym if (!!val && self.class.has_enumerated_attribute?(name))
301
- val
302
- end
303
-
304
- def attributes=(attrs, guard_protected_attributes=true)
305
- return if attrs.nil?
306
- #check the attributes then turn them over
307
- attrs.each do |k, v|
308
- if self.class.has_enumerated_attribute?(k)
309
- unless self.class.enumerated_attribute_allows_value?(k, v)
310
- raise InvalidEnumeration, ":#{v.to_s} is not a defined enumeration value for the '#{k.to_s}' attribute", caller
311
- end
312
- attrs[k] = v.to_s
313
- end
314
- end
315
-
316
- super
317
- end
318
-
319
- def attributes
320
- super.map do |k,v|
321
- self.class.has_enumerated_attribute?(k) ? v.to_sym : v
322
- end
323
- end
324
-
325
- def [](attr_name); read_enumerated_attribute(attr_name); end
326
- def []=(attr_name, value); write_enumerated_attribute(attr_name, value); end
327
-
328
- private
329
-
330
- def attribute=(attr_name, value); write_enumerated_attribute(attr_name, value); end
331
-
332
- module ClassMethods
333
- private
334
-
335
- def construct_attributes_from_arguments(attribute_names, arguments)
336
- attributes = super
337
- attributes.each { |k,v| attributes[k] = v.to_s if has_enumerated_attribute?(k) }
338
- attributes
339
- end
340
-
341
- def instantiate(record)
342
- object = super(record)
343
- @enumerated_attribute_init.each do |k,v|
344
- unless object.has_attribute?(k)
345
- object.write_enumerated_attribute(k, v)
346
- end
347
- end
348
- object
349
- end
350
-
351
- def define_enumerated_attribute_new_method
352
- class_eval <<-INITVAL
353
- class << self
354
- alias_method :new_without_enumerated_attribute, :new
355
- def new(*args, &block)
356
- result = new_without_enumerated_attribute(*args, &block)
357
- params = (!args.empty? && args.first.instance_of?(Hash)) ? args.first : {}
358
- params.each { |k, v| result.write_enumerated_attribute(k, v) }
359
- result.initialize_enumerated_attributes(true)
360
- yield result if block_given?
361
- result
362
- end
363
- end
364
- INITVAL
365
- end
366
-
367
- end
368
- end
369
-
370
- module Datamapper
371
- end
372
-
373
- @@integration_map = {}
374
-
375
- def self.add_integration_map(base_klass_name, module_object, aliasing_array=[])
376
- @@integration_map[base_klass_name] = {:module=>module_object, :aliasing=>aliasing_array}
377
- end
378
- class << self
379
- alias_method(:add, :add_integration_map)
380
- end
381
-
382
- #included mappings
383
- add('Object', EnumeratedAttribute::Integrations::Object)
384
- add('ActiveRecord::Base', EnumeratedAttribute::Integrations::ActiveRecord)
385
-
386
- def self.find_integration_map(klass)
387
- path = "#{klass}"
388
- begin
389
- return @@integration_map[klass.to_s] if @@integration_map.key?(klass.to_s)
390
- klass = klass.superclass
391
- path << " < #{klass}"
392
- end while klass
393
- raise EnumeratedAttribute::IntegrationError, "Unable to find integration for class hierarchy '#{path}'", caller
394
- end
395
-
396
- end
397
-
398
- public
399
-
400
- class MethodDefinition
401
- attr_accessor :method_name, :negated, :argument
402
-
403
- def initialize(name, arg, negated=false)
404
- @method_name = name
405
- @negated = negated
406
- @argument = arg
407
- end
408
-
409
- def is_predicate_method?
410
- @method_name[-1, 1] == '?'
411
- end
412
- def has_method_name?
413
- !!@method_name
414
- end
415
- def has_argument?
416
- !!@argument
417
- end
418
-
419
- end
420
-
421
- class MethodDefinitionDSL
422
- attr_reader :initial_value, :pluralized_name, :decrementor_name, :incrementor_name
423
-
424
- def initialize(class_obj, attr_name, values=[])
425
- @class_obj = class_obj
426
- @attr_name = attr_name
427
- @attr_values = values
428
- end
429
-
430
- #we'll by pass this - they can use it if it helps make code more readable - not enforced - should it be??
431
- def define
432
- end
433
-
434
- def is_not(*args)
435
- arg = args.first if args.length > 0
436
- MethodDefinition.new(nil, arg, true)
437
- end
438
- alias :isnt :is_not
439
-
440
- def is(*args)
441
- arg = args.first if args.length > 0
442
- MethodDefinition.new(nil, arg)
443
- end
444
-
445
- def method_missing(methId, *args, &block)
446
- meth_name = methId.id2name
447
-
448
- meth_def = nil
449
- if args.size > 0
450
- arg = args.first
451
- if arg.instance_of?(EnumeratedAttribute::MethodDefinition)
452
- if arg.has_method_name?
453
- raise_method_syntax_error(meth_name, arg.method_name)
454
- end
455
- meth_def = arg
456
- meth_def.method_name = meth_name
457
- else
458
- meth_def = MethodDefinition.new(meth_name, arg)
459
- end
460
- elsif block_given?
461
- meth_def = MethodDefinition.new(meth_name, block)
462
- else
463
- raise_method_syntax_error(meth_name)
464
- end
465
- evaluate_method_definition(meth_def)
466
- end
467
-
468
-
469
- def init(value)
470
- if (!@attr_values.empty? && !@attr_values.include?(value.to_sym))
471
- raise(InvalidDefinition, "'#{value}' in method 'init' is not an enumeration value for :#{@attr_name} attribute", caller)
472
- end
473
- @initial_value = value
474
- end
475
-
476
- def decrementor(value); @decrementor_name = value; end
477
- def incrementor(value); @incrementor_name = value; end
478
- def enums_accessor(value); @pluralized_name = value; end
479
- alias :inc :incrementor
480
- alias :dec :decrementor
481
- alias :enums :enums_accessor
482
- alias :plural :enums_accessor
483
-
484
- private
485
-
486
- def raise_method_syntax_error(meth_name, offending_token=nil)
487
- suffix = offending_token ? "found '#{offending_token}'" : "found nothing"
488
- followed_by = (meth_name[-1,1] == '?' ? "is_not, an enumeration value, an array of enumeration values, " : "") + "a proc, lambda or code block"
489
- raise InvalidDefinition, "'#{meth_name}' should be followed by #{followed_by} -- but #{suffix}"
490
- end
491
-
492
- def evaluate_method_definition(mdef)
493
- unless mdef.has_argument?
494
- return raise_method_syntax_error(mdef.method_name)
495
- end
496
-
497
- if mdef.is_predicate_method?
498
- case mdef.argument
499
- when String
500
- return create_custom_method_for_symbol_or_string(mdef)
501
- when Symbol
502
- return create_custom_method_for_symbol_or_string(mdef)
503
- when Array
504
- return create_custom_method_for_array_of_enums(mdef)
505
- when Proc
506
- return create_custom_method_for_proc(mdef)
507
- end
508
- else #action method
509
- if mdef.argument.instance_of?(Proc)
510
- return create_custom_method_for_proc(mdef)
511
- end
512
- end
513
- raise_method_syntax_error(mdef.method_name, mdef.argument)
514
- end
515
-
516
- def create_custom_method_for_proc(mdef)
517
- @class_obj.send(:define_method, mdef.method_name, mdef.argument)
518
- end
519
-
520
- def create_custom_method_for_symbol_or_string(mdef)
521
- if (!@attr_values.empty? && !@attr_values.include?(mdef.argument.to_sym))
522
- raise(InvalidDefinition, "'#{mdef.argument}' in method '#{mdef.method_name}' is not an enumeration value for :#{@attr_name} attribute", caller)
523
- end
524
- @class_obj.class_eval("def #{mdef.method_name}; @#{@attr_name} #{mdef.negated ? '!=' : '=='} :#{mdef.argument}; end")
525
- end
526
-
527
- def create_custom_method_for_array_of_enums(mdef)
528
- if !@attr_values.empty?
529
- mdef.argument.each do |m|
530
- if !@attr_values.include?(m.to_sym)
531
- raise(InvalidDefinition, "'#{m}' in method '#{mdef.method_name}' is not an enumeration value for :#{@attr_name} attribute", caller)
532
- end
533
- end
534
- end
535
- @class_obj.class_eval("def #{mdef.method_name}; #{mdef.negated ? '!' : ''}[:#{mdef.argument.join(',:')}].include?(@#{@attr_name}); end")
536
- end
537
- end
538
23
  end
539
24
 
540
- class Class
541
- include EnumeratedAttribute
25
+ Class.class_eval do
26
+ include EnumeratedAttribute::MacroMethods
542
27
  end
543
28
 
544
29
 
@@ -0,0 +1,208 @@
1
+ require 'enumerated_attribute/method_definition_dsl'
2
+ require 'enumerated_attribute/integrations'
3
+
4
+ module EnumeratedAttribute
5
+
6
+ class EnumeratedAttributeError < StandardError; end
7
+ class IntegrationError < EnumeratedAttributeError; end
8
+ class InvalidEnumeration < EnumeratedAttributeError; end
9
+ class InvalidDefinition < EnumeratedAttributeError; end
10
+
11
+ module Attribute
12
+ # def included(klass); klass.extend(ClassMethods); end
13
+
14
+ # module ClassMethods
15
+ private
16
+ def create_enumerated_attribute(*args, &block)
17
+ return if args.empty?
18
+ attr_name = args[0].to_s
19
+ attr_sym = attr_name.to_sym
20
+ enums = (args[1] && args[1].instance_of?(Array) ? args[1] : [])
21
+ index = enums.empty? ? 1 : 2
22
+ opts = (args[index] && args[index].instance_of?(Hash) ? args[index] : {})
23
+
24
+ raise(InvalidDefinition, 'second argument of enumerated_attribute/enum_attr is not an array of symbols or strings representing the enum values', caller) if enums.empty?
25
+
26
+ initial_value = nil
27
+ plural_name = opts[:plural] || opts[:enums_accessor] || opts[:enums] || begin
28
+ case
29
+ when attr_name =~ /[aeiou]y$/
30
+ "#{attr_name}s"
31
+ when attr_name =~ /y$/
32
+ attr_name.sub(/y$/, 'ies')
33
+ when attr_name =~ /(sh|ch|x|s)$/
34
+ "#{attr_name}es"
35
+ else
36
+ "#{attr_name}s"
37
+ end
38
+ end
39
+ incrementor = opts[:incrementor] || opts[:inc] || "#{attr_name}_next"
40
+ decrementor = opts[:decrementor] || opts[:dec] || "#{attr_name}_previous"
41
+
42
+ enums = enums.map{|v| (v =~ /^\^/ ? (initial_value = v[1, v.length-1].to_sym) : v.to_sym )}
43
+
44
+ class_eval <<-ATTRIB
45
+ @@enumerated_attribute_names ||= []
46
+ @@enumerated_attribute_names << '#{attr_name}'
47
+ @@enumerated_attribute_options ||={}
48
+ @@enumerated_attribute_options[:#{attr_name}] = {#{opts.to_a.map{|v| ':'+v.first.to_s+'=>:'+v.last.to_s}.join(', ')}}
49
+ @@enumerated_attribute_values ||= {}
50
+ @@enumerated_attribute_values[:#{attr_name}] = [:#{enums.join(',:')}]
51
+ ATTRIB
52
+
53
+ #define_enumerated_attribute_[writer, reader] may be modified in a named Integrations module (see Integrations::ActiveRecord)
54
+ class_eval <<-MAP
55
+ unless @integration_map
56
+ @integration_map = Integrations.find_integration_map(self)
57
+ @integration_map[:aliasing].each do |p|
58
+ alias_method(p.first, p.last)
59
+ end
60
+ include(EnumeratedAttribute::Integrations::Default)
61
+ include(@integration_map[:module]) if @integration_map[:module]
62
+
63
+ def self.has_enumerated_attribute?(name)
64
+ @@enumerated_attribute_names.include?(name.to_s)
65
+ end
66
+ def self.enumerated_attribute_allows_nil?(name)
67
+ return (false) unless @@enumerated_attribute_options[name.to_sym]
68
+ @@enumerated_attribute_options[name.to_sym][:nil] || false
69
+ end
70
+ def self.enumerated_attribute_allows_value?(name, value)
71
+ return (false) unless @@enumerated_attribute_values[name.to_sym]
72
+ return enumerated_attribute_allows_nil?(name) if value == nil
73
+ @@enumerated_attribute_values[name.to_sym].include?(value.to_sym)
74
+ end
75
+ end
76
+ MAP
77
+
78
+ #create accessors
79
+ define_enumerated_attribute_reader_method(attr_sym) unless (opts.key?(:reader) && !opts[:reader])
80
+ define_enumerated_attribute_writer_method(attr_sym) unless (opts.key?(:writer) && !opts[:writer])
81
+
82
+ #define dynamic methods in method_missing
83
+ class_eval <<-METHOD
84
+ unless @enumerated_attribute_define_once_only
85
+ if method_defined?(:method_missing)
86
+ alias_method(:method_missing_without_enumerated_attribute, :method_missing)
87
+ def method_missing(methId, *args, &block)
88
+ return self.send(methId) if define_enumerated_attribute_dynamic_method(methId)
89
+ method_missing_without_enumerated_attribute(methId, *args, &block)
90
+ end
91
+ else
92
+ def method_missing(methId, *args, &block)
93
+ return self.send(methId) if define_enumerated_attribute_dynamic_method(methId)
94
+ super
95
+ end
96
+ end
97
+ @enumerated_attribute_define_once_only = true
98
+
99
+ alias_method :respond_to_without_enumerated_attribute?, :respond_to?
100
+ def respond_to?(method)
101
+ respond_to_without_enumerated_attribute?(method) || !!parse_dynamic_method_parts(method.to_s)
102
+ end
103
+
104
+ def initialize_enumerated_attributes(only_if_nil = false)
105
+ self.class.enumerated_attribute_initial_value_list.each do |k,v|
106
+ self.write_enumerated_attribute(k, v) unless (only_if_nil && read_enumerated_attribute(k) != nil)
107
+ end
108
+ end
109
+
110
+ private
111
+
112
+ def parse_dynamic_method_parts(meth_name)
113
+ return(nil) unless meth_name[-1, 1] == '?'
114
+
115
+ middle = meth_name.chop #remove the ?
116
+
117
+ attr = nil
118
+ @@enumerated_attribute_names.each do |name|
119
+ if middle.sub!(Regexp.new("^"+name.to_s), "")
120
+ attr = name; break
121
+ end
122
+ end
123
+ return (nil) unless attr
124
+ attr_sym = attr.to_sym
125
+
126
+ value = nil
127
+ if @@enumerated_attribute_values.key?(attr_sym)
128
+ @@enumerated_attribute_values[attr_sym].each do |v|
129
+ if middle.sub!(Regexp.new(v.to_s+"$"), "")
130
+ value = v; break
131
+ end
132
+ end
133
+ end
134
+ unless value #check for nil?
135
+ return (nil) unless middle.sub!(Regexp.new('nil$'), "")
136
+ value = nil
137
+ end
138
+
139
+ [attr, middle, value]
140
+ end
141
+
142
+ def define_enumerated_attribute_dynamic_method(methId)
143
+ meth_name = methId.id2name
144
+ return(false) unless (parts = parse_dynamic_method_parts(meth_name))
145
+
146
+ negated = !!parts[1].downcase.match(/_not_/)
147
+ value = parts[2] ? parts[2].to_sym : nil
148
+ self.class.define_enumerated_attribute_custom_method(methId, parts[0], value, negated)
149
+
150
+ true
151
+ end
152
+
153
+ end
154
+ METHOD
155
+
156
+ #create state and action methods from block
157
+ initial_value = opts[:init] || initial_value
158
+ if block_given?
159
+ m = EnumeratedAttribute::MethodDefinitionDSL.new(self, attr_name, enums)
160
+ m.instance_eval(&block)
161
+ initial_value = m.initial_value || initial_value
162
+ plural_name = m.pluralized_name || plural_name
163
+ decrementor = m.decrementor_name || decrementor
164
+ incrementor = m.incrementor_name || incrementor
165
+ end
166
+
167
+ #define the enum values accessor
168
+ class_eval <<-ENUM
169
+ def #{plural_name}
170
+ @@enumerated_attribute_values[:#{attr_name}]
171
+ end
172
+ def #{incrementor}
173
+ z = @@enumerated_attribute_values[:#{attr_name}]
174
+ index = z.index(@#{attr_name})
175
+ write_enumerated_attribute(:#{attr_name}, z[index >= z.size-1 ? 0 : index+1])
176
+ end
177
+ def #{decrementor}
178
+ z = @@enumerated_attribute_values[:#{attr_name}]
179
+ index = z.index(@#{attr_name})
180
+ write_enumerated_attribute(:#{attr_name}, z[index > 0 ? index-1 : z.size-1])
181
+ end
182
+ ENUM
183
+
184
+ unless @enumerated_attribute_init
185
+ define_enumerated_attribute_new_method
186
+ end
187
+ @enumerated_attribute_init ||= {}
188
+ if (initial_value = opts[:init] || initial_value)
189
+ @enumerated_attribute_init[attr_sym] = initial_value
190
+ end
191
+ class_eval do
192
+ class << self
193
+ def enumerated_attribute_initial_value_list; @enumerated_attribute_init; end
194
+ end
195
+ end
196
+
197
+ def self.define_enumerated_attribute_custom_method(symbol, attr_name, value, negated)
198
+ define_method symbol do
199
+ ival = read_enumerated_attribute(attr_name)
200
+ negated ? ival != value : ival == value
201
+ end
202
+ end
203
+
204
+ end
205
+ end
206
+ #end
207
+
208
+ end
@@ -0,0 +1,33 @@
1
+ require 'enumerated_attribute/integrations/active_record'
2
+ require 'enumerated_attribute/integrations/object'
3
+ require 'enumerated_attribute/integrations/datamapper'
4
+ require 'enumerated_attribute/integrations/default'
5
+
6
+ module EnumeratedAttribute
7
+ module Integrations
8
+
9
+ @@integration_map = {}
10
+
11
+ def self.add_integration_map(base_klass_name, module_object, aliasing_array=[])
12
+ @@integration_map[base_klass_name] = {:module=>module_object, :aliasing=>aliasing_array}
13
+ end
14
+ class << self
15
+ alias_method(:add, :add_integration_map)
16
+ end
17
+
18
+ #included mappings
19
+ add('Object', EnumeratedAttribute::Integrations::Object)
20
+ add('ActiveRecord::Base', EnumeratedAttribute::Integrations::ActiveRecord)
21
+
22
+ def self.find_integration_map(klass)
23
+ path = "#{klass}"
24
+ begin
25
+ return @@integration_map[klass.to_s] if @@integration_map.key?(klass.to_s)
26
+ klass = klass.superclass
27
+ path << " < #{klass}"
28
+ end while klass
29
+ raise EnumeratedAttribute::IntegrationError, "Unable to find integration for class hierarchy '#{path}'", caller
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,100 @@
1
+ module EnumeratedAttribute
2
+ module Integrations
3
+
4
+ module ActiveRecord
5
+ def self.included(klass)
6
+ klass.extend(ClassMethods)
7
+ end
8
+
9
+ def write_enumerated_attribute(name, val)
10
+ name = name.to_s
11
+ return write_attribute(name, val) unless self.class.has_enumerated_attribute?(name)
12
+ val_str = val.to_s if val
13
+ val_sym = val.to_sym if val
14
+ unless self.class.enumerated_attribute_allows_value?(name, val_sym)
15
+ raise(InvalidEnumeration, "nil is not allowed on '#{name}' attribute, set :nil=>true option", caller) unless val
16
+ raise(InvalidEnumeration, ":#{val_str} is not a defined enumeration value for the '#{name}' attribute", caller)
17
+ end
18
+ return instance_variable_set('@'+name, val_sym) unless self.has_attribute?(name)
19
+ write_attribute(name, val_str)
20
+ end
21
+
22
+ def read_enumerated_attribute(name)
23
+ name = name.to_s
24
+ #if not enumerated - let active record handle it
25
+ return read_attribute(name) unless self.class.has_enumerated_attribute?(name)
26
+ #if enumerated, is it an active record attribute, if not, the value is stored in an instance variable
27
+ return instance_variable_get('@'+name) unless self.has_attribute?(name)
28
+ #this is an enumerated active record attribute
29
+ val = read_attribute(name)
30
+ val = val.to_sym if (!!val && self.class.has_enumerated_attribute?(name))
31
+ val
32
+ end
33
+
34
+ def attributes=(attrs, guard_protected_attributes=true)
35
+ return if attrs.nil?
36
+ #check the attributes then turn them over
37
+ attrs.each do |k, v|
38
+ if self.class.has_enumerated_attribute?(k)
39
+ unless self.class.enumerated_attribute_allows_value?(k, v)
40
+ raise InvalidEnumeration, ":#{v.to_s} is not a defined enumeration value for the '#{k.to_s}' attribute", caller
41
+ end
42
+ attrs[k] = v.to_s
43
+ end
44
+ end
45
+
46
+ super
47
+ end
48
+
49
+ def attributes
50
+ super.map do |k,v|
51
+ self.class.has_enumerated_attribute?(k) ? v.to_sym : v
52
+ end
53
+ end
54
+
55
+ def [](attr_name); read_enumerated_attribute(attr_name); end
56
+ def []=(attr_name, value); write_enumerated_attribute(attr_name, value); end
57
+
58
+ private
59
+
60
+ def attribute=(attr_name, value); write_enumerated_attribute(attr_name, value); end
61
+
62
+ module ClassMethods
63
+ private
64
+
65
+ def construct_attributes_from_arguments(attribute_names, arguments)
66
+ attributes = super
67
+ attributes.each { |k,v| attributes[k] = v.to_s if has_enumerated_attribute?(k) }
68
+ attributes
69
+ end
70
+
71
+ def instantiate(record)
72
+ object = super(record)
73
+ @enumerated_attribute_init.each do |k,v|
74
+ unless object.has_attribute?(k)
75
+ object.write_enumerated_attribute(k, v)
76
+ end
77
+ end
78
+ object
79
+ end
80
+
81
+ def define_enumerated_attribute_new_method
82
+ class_eval <<-INITVAL
83
+ class << self
84
+ alias_method :new_without_enumerated_attribute, :new
85
+ def new(*args, &block)
86
+ result = new_without_enumerated_attribute(*args, &block)
87
+ params = (!args.empty? && args.first.instance_of?(Hash)) ? args.first : {}
88
+ params.each { |k, v| result.write_enumerated_attribute(k, v) }
89
+ result.initialize_enumerated_attributes(true)
90
+ yield result if block_given?
91
+ result
92
+ end
93
+ end
94
+ INITVAL
95
+ end
96
+
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,6 @@
1
+ module EnumerateAttribute
2
+ module Integrations
3
+ module Datamapper
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,25 @@
1
+ module EnumeratedAttribute
2
+ module Integrations
3
+
4
+ module Default
5
+ def self.included(klass); klass.extend(ClassMethods); end
6
+
7
+ module ClassMethods
8
+ def define_enumerated_attribute_writer_method name
9
+ name = name.to_s
10
+ class_eval <<-METHOD
11
+ def #{name}=(val); write_enumerated_attribute(:#{name}, val); end
12
+ METHOD
13
+ end
14
+
15
+ def define_enumerated_attribute_reader_method name
16
+ name = name.to_s
17
+ class_eval <<-METHOD
18
+ def #{name}; read_enumerated_attribute(:#{name}); end
19
+ METHOD
20
+ end
21
+ end
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,44 @@
1
+ module EnumeratedAttribute
2
+ module Integrations
3
+
4
+ module Object
5
+ def self.included(klass)
6
+ klass.extend(ClassMethods)
7
+ end
8
+
9
+ def write_enumerated_attribute(name, val)
10
+ name = name.to_s
11
+ val = val.to_sym if val
12
+ unless self.class.enumerated_attribute_allows_value?(name, val)
13
+ raise(InvalidEnumeration, "nil is not allowed on '#{name}' attribute, set :nil=>true option", caller) unless val
14
+ raise(InvalidEnumeration, ":#{val} is not a defined enumeration value for the '#{name}' attribute", caller)
15
+ end
16
+ instance_variable_set('@'+name, val)
17
+ end
18
+
19
+ def read_enumerated_attribute(name)
20
+ return instance_variable_get('@'+name.to_s)
21
+ end
22
+
23
+ module ClassMethods
24
+ private
25
+
26
+ def define_enumerated_attribute_new_method
27
+ class_eval <<-NEWMETH
28
+ class << self
29
+ alias_method :new_without_enumerated_attribute, :new
30
+ def new(*args, &block)
31
+ result = new_without_enumerated_attribute(*args)
32
+ result.initialize_enumerated_attributes
33
+ yield result if block_given?
34
+ result
35
+ end
36
+ end
37
+ NEWMETH
38
+ end
39
+
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,132 @@
1
+ module EnumeratedAttribute
2
+
3
+ class MethodDefinition
4
+ attr_accessor :method_name, :negated, :argument
5
+
6
+ def initialize(name, arg, negated=false)
7
+ @method_name, @negated, @argument = name, negated, arg
8
+ end
9
+
10
+ def is_predicate_method?; @method_name[-1, 1] == '?'; end
11
+ def has_method_name?; !!@method_name; end
12
+ def has_argument?; !!@argument; end
13
+ end
14
+
15
+ class MethodDefinitionDSL
16
+ attr_reader :initial_value, :pluralized_name, :decrementor_name, :incrementor_name
17
+
18
+ def initialize(class_obj, attr_name, values=[])
19
+ @class_obj = class_obj
20
+ @attr_name = attr_name
21
+ @attr_values = values
22
+ end
23
+
24
+ #we'll by pass this - they can use it if it helps make code more readable - not enforced - should it be??
25
+ def define
26
+ end
27
+
28
+ def is_not(*args)
29
+ arg = args.first if args.length > 0
30
+ MethodDefinition.new(nil, arg, true)
31
+ end
32
+ alias :isnt :is_not
33
+
34
+ def is(*args)
35
+ arg = args.first if args.length > 0
36
+ MethodDefinition.new(nil, arg)
37
+ end
38
+
39
+ def method_missing(methId, *args, &block)
40
+ meth_name = methId.id2name
41
+
42
+ meth_def = nil
43
+ if args.size > 0
44
+ arg = args.first
45
+ if arg.instance_of?(EnumeratedAttribute::MethodDefinition)
46
+ if arg.has_method_name?
47
+ raise_method_syntax_error(meth_name, arg.method_name)
48
+ end
49
+ meth_def = arg
50
+ meth_def.method_name = meth_name
51
+ else
52
+ meth_def = MethodDefinition.new(meth_name, arg)
53
+ end
54
+ elsif block_given?
55
+ meth_def = MethodDefinition.new(meth_name, block)
56
+ else
57
+ raise_method_syntax_error(meth_name)
58
+ end
59
+ evaluate_method_definition(meth_def)
60
+ end
61
+
62
+ def init(value)
63
+ if (!@attr_values.empty? && !@attr_values.include?(value.to_sym))
64
+ raise(InvalidDefinition, "'#{value}' in method 'init' is not an enumeration value for :#{@attr_name} attribute", caller)
65
+ end
66
+ @initial_value = value
67
+ end
68
+
69
+ def decrementor(value); @decrementor_name = value; end
70
+ def incrementor(value); @incrementor_name = value; end
71
+ def enums_accessor(value); @pluralized_name = value; end
72
+ alias :inc :incrementor
73
+ alias :dec :decrementor
74
+ alias :enums :enums_accessor
75
+ alias :plural :enums_accessor
76
+
77
+ private
78
+
79
+ def raise_method_syntax_error(meth_name, offending_token=nil)
80
+ suffix = offending_token ? "found '#{offending_token}'" : "found nothing"
81
+ followed_by = (meth_name[-1,1] == '?' ? "is_not, an enumeration value, an array of enumeration values, " : "") + "a proc, lambda or code block"
82
+ raise InvalidDefinition, "'#{meth_name}' should be followed by #{followed_by} -- but #{suffix}"
83
+ end
84
+
85
+ def evaluate_method_definition(mdef)
86
+ unless mdef.has_argument?
87
+ return raise_method_syntax_error(mdef.method_name)
88
+ end
89
+
90
+ if mdef.is_predicate_method?
91
+ case mdef.argument
92
+ when String
93
+ return create_custom_method_for_symbol_or_string(mdef)
94
+ when Symbol
95
+ return create_custom_method_for_symbol_or_string(mdef)
96
+ when Array
97
+ return create_custom_method_for_array_of_enums(mdef)
98
+ when Proc
99
+ return create_custom_method_for_proc(mdef)
100
+ end
101
+ else #action method
102
+ if mdef.argument.instance_of?(Proc)
103
+ return create_custom_method_for_proc(mdef)
104
+ end
105
+ end
106
+ raise_method_syntax_error(mdef.method_name, mdef.argument)
107
+ end
108
+
109
+ def create_custom_method_for_proc(mdef)
110
+ @class_obj.send(:define_method, mdef.method_name, mdef.argument)
111
+ end
112
+
113
+ def create_custom_method_for_symbol_or_string(mdef)
114
+ if (!@attr_values.empty? && !@attr_values.include?(mdef.argument.to_sym))
115
+ raise(InvalidDefinition, "'#{mdef.argument}' in method '#{mdef.method_name}' is not an enumeration value for :#{@attr_name} attribute", caller)
116
+ end
117
+ @class_obj.class_eval("def #{mdef.method_name}; @#{@attr_name} #{mdef.negated ? '!=' : '=='} :#{mdef.argument}; end")
118
+ end
119
+
120
+ def create_custom_method_for_array_of_enums(mdef)
121
+ if !@attr_values.empty?
122
+ mdef.argument.each do |m|
123
+ if !@attr_values.include?(m.to_sym)
124
+ raise(InvalidDefinition, "'#{m}' in method '#{mdef.method_name}' is not an enumeration value for :#{@attr_name} attribute", caller)
125
+ end
126
+ end
127
+ end
128
+ @class_obj.class_eval("def #{mdef.method_name}; #{mdef.negated ? '!' : ''}[:#{mdef.argument.join(',:')}].include?(@#{@attr_name}); end")
129
+ end
130
+ end
131
+
132
+ end
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.5
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeff Patmon
@@ -22,6 +22,15 @@ extensions: []
22
22
  extra_rdoc_files: []
23
23
 
24
24
  files:
25
+ - lib/enumerated_attribute
26
+ - lib/enumerated_attribute/attribute.rb
27
+ - lib/enumerated_attribute/integrations
28
+ - lib/enumerated_attribute/integrations/active_record.rb
29
+ - lib/enumerated_attribute/integrations/datamapper.rb
30
+ - lib/enumerated_attribute/integrations/default.rb
31
+ - lib/enumerated_attribute/integrations/object.rb
32
+ - lib/enumerated_attribute/integrations.rb
33
+ - lib/enumerated_attribute/method_definition_dsl.rb
25
34
  - lib/enumerated_attribute.rb
26
35
  - spec/active_record
27
36
  - spec/active_record/active_record_spec.rb