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 +1 -1
- data/lib/enumerated_attribute.rb +15 -530
- data/lib/enumerated_attribute/attribute.rb +208 -0
- data/lib/enumerated_attribute/integrations.rb +33 -0
- data/lib/enumerated_attribute/integrations/active_record.rb +100 -0
- data/lib/enumerated_attribute/integrations/datamapper.rb +6 -0
- data/lib/enumerated_attribute/integrations/default.rb +25 -0
- data/lib/enumerated_attribute/integrations/object.rb +44 -0
- data/lib/enumerated_attribute/method_definition_dsl.rb +132 -0
- metadata +10 -1
data/Rakefile
CHANGED
@@ -5,7 +5,7 @@ require 'rake/contrib/sshpublisher'
|
|
5
5
|
|
6
6
|
spec = Gem::Specification.new do |s|
|
7
7
|
s.name = 'enumerated_attribute'
|
8
|
-
s.version = '0.1.
|
8
|
+
s.version = '0.1.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 ...'
|
data/lib/enumerated_attribute.rb
CHANGED
@@ -1,544 +1,29 @@
|
|
1
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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,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.
|
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
|