jeffp-enumerated_attribute 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
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