doodle 0.1.8 → 0.1.9
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/History.txt +52 -0
- data/Manifest.txt +1 -0
- data/examples/mail-datatypes.rb +1 -0
- data/examples/mail.rb +1 -1
- data/examples/profile-options.rb +1 -1
- data/lib/doodle.rb +332 -103
- data/lib/doodle/app.rb +445 -0
- data/lib/doodle/datatypes.rb +124 -15
- data/lib/doodle/version.rb +1 -1
- data/lib/molic_orderedhash.rb +99 -162
- data/log/debug.log +1 -0
- data/spec/block_init_spec.rb +52 -0
- data/spec/bugs_spec.rb +114 -18
- data/spec/class_var_spec.rb +28 -14
- data/spec/conversion_spec.rb +36 -38
- data/spec/doodle_spec.rb +23 -23
- data/spec/has_spec.rb +19 -1
- data/spec/init_spec.rb +22 -16
- data/spec/member_init_spec.rb +122 -0
- data/spec/readonly_spec.rb +32 -0
- data/spec/singleton_spec.rb +7 -7
- data/spec/spec_helper.rb +1 -1
- data/spec/symbolize_keys_spec.rb +40 -0
- data/spec/to_hash_spec.rb +35 -0
- metadata +39 -22
data/History.txt
CHANGED
@@ -1,3 +1,55 @@
|
|
1
|
+
== 0.1.9 / 2008-08-13
|
2
|
+
- Features:
|
3
|
+
- to_hash
|
4
|
+
- doodle do .. end blocks now support #has, #from, #must and
|
5
|
+
#arg_order
|
6
|
+
- will now initialize a setter from a block by calling kind.new if
|
7
|
+
kind is specified and kind is a Doodle or a Proc, e.g.
|
8
|
+
|
9
|
+
class Animal
|
10
|
+
has :species
|
11
|
+
end
|
12
|
+
|
13
|
+
class Barn
|
14
|
+
has :animals, :collect => Animal
|
15
|
+
end
|
16
|
+
|
17
|
+
class Farm
|
18
|
+
has Barn
|
19
|
+
end
|
20
|
+
|
21
|
+
farm = Farm do
|
22
|
+
# this is new - will call Barn.new(&block)
|
23
|
+
barn do
|
24
|
+
animal 'chicken'
|
25
|
+
animal 'pig'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
Will not try this for an attribute with :abstract => true
|
30
|
+
|
31
|
+
- attributes now have :doc option
|
32
|
+
- attributes now have :abstract option - will not try to
|
33
|
+
auto-instantiate an object from this class
|
34
|
+
- attributes now have a :readonly attribute - will not allow setting
|
35
|
+
outside initialization
|
36
|
+
- Doodle::Utils
|
37
|
+
- deep_copy(obj)
|
38
|
+
- normalize_keys!(hash, recursive = false, method = :to_sym),
|
39
|
+
optionally recurse into child hashes
|
40
|
+
- symbolize_keys!(hash, recursive = false)
|
41
|
+
- stringify_keys!(hash, recursive = false)
|
42
|
+
|
43
|
+
- Experimental:
|
44
|
+
- Doodle::App for handlng command line application options
|
45
|
+
- doodle/datatypes - added more datatypes
|
46
|
+
|
47
|
+
- Bug fixes:
|
48
|
+
- fixed reversion in 0.1.8 which enabled full backtrace from within
|
49
|
+
doodle.rb
|
50
|
+
- fixed bug where required attributes defined after attributes with
|
51
|
+
default values were not being validated (had 'break' instead of 'next')
|
52
|
+
|
1
53
|
== 0.1.8 / 2008-05-13
|
2
54
|
- Features:
|
3
55
|
- now applies instance level conversions (class level #from) to
|
data/Manifest.txt
CHANGED
data/examples/mail-datatypes.rb
CHANGED
data/examples/mail.rb
CHANGED
data/examples/profile-options.rb
CHANGED
data/lib/doodle.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# doodle
|
2
|
+
# -*- mode: ruby; ruby-indent-level: 2; tab-width: 2 -*- vim: sw=2 ts=2
|
2
3
|
# Copyright (C) 2007-2008 by Sean O'Halpin
|
3
4
|
# 2007-11-24 first version
|
4
5
|
# 2008-04-18 latest release 0.0.12
|
@@ -7,13 +8,56 @@
|
|
7
8
|
$:.unshift(File.dirname(__FILE__)) unless
|
8
9
|
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
9
10
|
|
10
|
-
|
11
|
+
if RUBY_VERSION < '1.9.0'
|
12
|
+
require 'molic_orderedhash' # todo[replace this with own (required functions only) version]
|
13
|
+
else
|
14
|
+
# 1.9+ hashes are ordered by default
|
15
|
+
class Doodle
|
16
|
+
OrderedHash = ::Hash
|
17
|
+
end
|
18
|
+
end
|
11
19
|
|
12
20
|
# require Ruby 1.8.6 or higher
|
13
21
|
if RUBY_VERSION < '1.8.6'
|
14
22
|
raise Exception, "Sorry - doodle does not work with versions of Ruby below 1.8.6"
|
15
23
|
end
|
16
24
|
|
25
|
+
#
|
26
|
+
# instance_exec for ruby 1.8 by Mauricio Fernandez
|
27
|
+
# http://eigenclass.org/hiki.rb?bounded+space+instance_exec
|
28
|
+
# thread-safe and handles frozen objects in bounded space
|
29
|
+
#
|
30
|
+
# (tag "ruby instance_exec")
|
31
|
+
#
|
32
|
+
if !Object.respond_to?(:instance_exec)
|
33
|
+
class Object
|
34
|
+
module InstanceExecHelper; end
|
35
|
+
include InstanceExecHelper
|
36
|
+
def instance_exec(*args, &block)
|
37
|
+
begin
|
38
|
+
old_critical, Thread.critical = Thread.critical, true
|
39
|
+
n = 0
|
40
|
+
methods = InstanceExecHelper.instance_methods
|
41
|
+
# this in order to make the lookup O(1), and name generation O(n) on the
|
42
|
+
# number of nested/concurrent instance_exec calls instead of O(n**2)
|
43
|
+
table = Hash[*methods.zip(methods).flatten]
|
44
|
+
n += 1 while table.has_key?(mname="__instance_exec#{n}")
|
45
|
+
ensure
|
46
|
+
Thread.critical = old_critical
|
47
|
+
end
|
48
|
+
InstanceExecHelper.module_eval{ define_method(mname, &block) }
|
49
|
+
begin
|
50
|
+
ret = send(mname, *args)
|
51
|
+
ensure
|
52
|
+
InstanceExecHelper.module_eval{ remove_method(mname) } rescue nil
|
53
|
+
end
|
54
|
+
ret
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
require 'yaml'
|
60
|
+
|
17
61
|
# *doodle* is my attempt at an eco-friendly metaprogramming framework that does not
|
18
62
|
# have pollute core Ruby objects such as Object, Class and Module.
|
19
63
|
#
|
@@ -30,6 +74,9 @@ class Doodle
|
|
30
74
|
def context
|
31
75
|
Thread.current[:doodle_context] ||= []
|
32
76
|
end
|
77
|
+
def parent
|
78
|
+
context[-1]
|
79
|
+
end
|
33
80
|
end
|
34
81
|
|
35
82
|
# debugging utilities
|
@@ -56,27 +103,90 @@ class Doodle
|
|
56
103
|
end
|
57
104
|
# from facets/string/case.rb, line 80
|
58
105
|
def snake_case(camel_cased_word)
|
59
|
-
camel_cased_word.gsub(/([A-Z]+)([A-Z])/,'\1_\2').gsub(/([a-z])([A-Z])/,'\1_\2').downcase
|
106
|
+
camel_cased_word.to_s.gsub(/([A-Z]+)([A-Z])/,'\1_\2').gsub(/([a-z])([A-Z])/,'\1_\2').downcase
|
60
107
|
end
|
61
108
|
# resolve a constant of the form Some::Class::Or::Module
|
62
109
|
def const_resolve(constant)
|
63
110
|
constant.to_s.split(/::/).reject{|x| x.empty?}.inject(Object) { |prev, this| prev.const_get(this) }
|
64
111
|
end
|
65
|
-
#
|
66
|
-
def
|
112
|
+
# deep copy of object (unlike shallow copy dup or clone)
|
113
|
+
def deep_copy(obj)
|
114
|
+
Marshal.load(Marshal.dump(obj))
|
115
|
+
end
|
116
|
+
# normalize hash keys using method (e.g. :to_sym, :to_s)
|
117
|
+
# - updates target hash
|
118
|
+
# - optionally recurse into child hashes
|
119
|
+
def normalize_keys!(hash, recursive = false, method = :to_sym)
|
67
120
|
hash.keys.each do |key|
|
68
|
-
|
69
|
-
|
121
|
+
normalized_key = key.respond_to?(method) ? key.send(method) : key
|
122
|
+
v = hash.delete(key)
|
123
|
+
if recursive
|
124
|
+
if v.kind_of?(Hash)
|
125
|
+
v = normalize_keys!(v, recursive, method)
|
126
|
+
elsif v.kind_of?(Array)
|
127
|
+
v = v.map{ |x| normalize_keys!(x, recursive, method) }
|
128
|
+
end
|
129
|
+
end
|
130
|
+
hash[normalized_key] = v
|
70
131
|
end
|
71
132
|
hash
|
72
133
|
end
|
134
|
+
# normalize hash keys using method (e.g. :to_sym, :to_s)
|
135
|
+
# - returns copy of hash
|
136
|
+
# - optionally recurse into child hashes
|
137
|
+
def normalize_keys(hash, recursive = false, method = :to_sym)
|
138
|
+
if recursive
|
139
|
+
h = deep_copy(hash)
|
140
|
+
else
|
141
|
+
h = hash.dup
|
142
|
+
end
|
143
|
+
normalize_keys!(h, recursive, method)
|
144
|
+
end
|
73
145
|
# convert keys to symbols
|
74
|
-
|
75
|
-
|
146
|
+
# - updates target hash in place
|
147
|
+
# - optionally recurse into child hashes
|
148
|
+
def symbolize_keys!(hash, recursive = false)
|
149
|
+
normalize_keys!(hash, recursive, :to_sym)
|
150
|
+
end
|
151
|
+
# convert keys to symbols
|
152
|
+
# - returns copy of hash
|
153
|
+
# - optionally recurse into child hashes
|
154
|
+
def symbolize_keys(hash, recursive = false)
|
155
|
+
normalize_keys(hash, recursive, :to_sym)
|
156
|
+
end
|
157
|
+
# convert keys to strings
|
158
|
+
# - updates target hash in place
|
159
|
+
# - optionally recurse into child hashes
|
160
|
+
def stringify_keys!(hash, recursive = false)
|
161
|
+
normalize_keys!(hash, recursive, :to_s)
|
162
|
+
end
|
163
|
+
# convert keys to strings
|
164
|
+
# - returns copy of hash
|
165
|
+
# - optionally recurse into child hashes
|
166
|
+
def stringify_keys(hash, recursive = false)
|
167
|
+
normalize_keys(hash, recursive, :to_s)
|
168
|
+
end
|
169
|
+
# simple (!) pluralization - if you want fancier, override this method
|
170
|
+
def pluralize(string)
|
171
|
+
s = string.to_s
|
172
|
+
if s =~ /s$/
|
173
|
+
s + 'es'
|
174
|
+
else
|
175
|
+
s + 's'
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# caller
|
180
|
+
def doodle_caller
|
181
|
+
if $DEBUG
|
182
|
+
caller
|
183
|
+
else
|
184
|
+
[caller[-1]]
|
185
|
+
end
|
76
186
|
end
|
77
187
|
end
|
78
188
|
end
|
79
|
-
|
189
|
+
|
80
190
|
# error handling
|
81
191
|
@@raise_exception_on_error = true
|
82
192
|
def self.raise_exception_on_error
|
@@ -101,6 +211,9 @@ class Doodle
|
|
101
211
|
# raised when arg_order called with incorrect arguments
|
102
212
|
class InvalidOrderError < Exception
|
103
213
|
end
|
214
|
+
# raised when try to set a readonly attribute after initialization
|
215
|
+
class ReadOnlyError < Exception
|
216
|
+
end
|
104
217
|
|
105
218
|
# provides more direct access to the singleton class and a way to
|
106
219
|
# treat singletons, Modules and Classes equally in a meta context
|
@@ -150,7 +263,7 @@ class Doodle
|
|
150
263
|
#p [:embrace, :inherited, klass]
|
151
264
|
klass.__send__(:embrace, other) # n.b. closure
|
152
265
|
klass.__send__(:include, Factory) # is there another way to do this? i.e. not in embrace
|
153
|
-
super(klass) if defined?(super)
|
266
|
+
#super(klass) if defined?(super)
|
154
267
|
end
|
155
268
|
}
|
156
269
|
sc.module_eval(&block) if block_given?
|
@@ -164,8 +277,11 @@ class Doodle
|
|
164
277
|
arg_block = block if block_given?
|
165
278
|
@block = arg_block
|
166
279
|
end
|
280
|
+
def call(*a, &b)
|
281
|
+
block.call(*a, &b)
|
282
|
+
end
|
167
283
|
end
|
168
|
-
|
284
|
+
|
169
285
|
# A Validation represents a validation rule applied to the instance
|
170
286
|
# after initialization. Generated using the Doodle::BaseMethods#must directive.
|
171
287
|
class Validation
|
@@ -182,6 +298,7 @@ class Doodle
|
|
182
298
|
|
183
299
|
# place to stash bookkeeping info
|
184
300
|
class DoodleInfo
|
301
|
+
attr_accessor :this
|
185
302
|
attr_accessor :local_attributes
|
186
303
|
attr_accessor :local_validations
|
187
304
|
attr_accessor :local_conversions
|
@@ -192,15 +309,20 @@ class Doodle
|
|
192
309
|
|
193
310
|
def initialize(object)
|
194
311
|
@this = object
|
195
|
-
@local_attributes = OrderedHash.new
|
312
|
+
@local_attributes = Doodle::OrderedHash.new
|
196
313
|
@local_validations = []
|
197
314
|
@validation_on = true
|
198
315
|
@local_conversions = {}
|
199
316
|
@arg_order = []
|
200
317
|
@errors = []
|
201
|
-
|
318
|
+
#@parent = nil
|
319
|
+
@parent = Doodle.parent
|
202
320
|
end
|
203
321
|
# hide from inspect
|
322
|
+
m = instance_method(:inspect)
|
323
|
+
define_method :__inspect__ do
|
324
|
+
m.bind(self).call
|
325
|
+
end
|
204
326
|
def inspect
|
205
327
|
''
|
206
328
|
end
|
@@ -215,7 +337,7 @@ class Doodle
|
|
215
337
|
raise(*args)
|
216
338
|
end
|
217
339
|
end
|
218
|
-
|
340
|
+
|
219
341
|
# provide an alternative inheritance chain that works for singleton
|
220
342
|
# classes as well as modules, classes and instances
|
221
343
|
def parents
|
@@ -232,7 +354,7 @@ class Doodle
|
|
232
354
|
anc.select{|x| x.kind_of?(Class)}
|
233
355
|
end
|
234
356
|
|
235
|
-
# send message to all doodle_parents and collect results
|
357
|
+
# send message to all doodle_parents and collect results
|
236
358
|
def collect_inherited(message)
|
237
359
|
result = []
|
238
360
|
parents.each do |klass|
|
@@ -247,14 +369,14 @@ class Doodle
|
|
247
369
|
|
248
370
|
def handle_inherited_hash(tf, method)
|
249
371
|
if tf
|
250
|
-
collect_inherited(method).inject(OrderedHash.new){ |hash, item|
|
251
|
-
hash.merge(OrderedHash[*item])
|
372
|
+
collect_inherited(method).inject(Doodle::OrderedHash.new){ |hash, item|
|
373
|
+
hash.merge(Doodle::OrderedHash[*item])
|
252
374
|
}.merge(@this.doodle.__send__(method))
|
253
375
|
else
|
254
376
|
@this.doodle.__send__(method)
|
255
377
|
end
|
256
378
|
end
|
257
|
-
|
379
|
+
|
258
380
|
# returns array of Attributes
|
259
381
|
# - if tf == true, returns all inherited attributes
|
260
382
|
# - if tf == false, returns only those attributes defined in the current object/class
|
@@ -269,10 +391,10 @@ class Doodle
|
|
269
391
|
|
270
392
|
# return class level attributes
|
271
393
|
def class_attributes
|
272
|
-
attrs = OrderedHash.new
|
394
|
+
attrs = Doodle::OrderedHash.new
|
273
395
|
if @this.kind_of?(Class)
|
274
|
-
attrs = collect_inherited(:class_attributes).inject(OrderedHash.new){ |hash, item|
|
275
|
-
hash.merge(OrderedHash[*item])
|
396
|
+
attrs = collect_inherited(:class_attributes).inject(Doodle::OrderedHash.new){ |hash, item|
|
397
|
+
hash.merge(Doodle::OrderedHash[*item])
|
276
398
|
}.merge(@this.singleton_class.doodle.respond_to?(:attributes) ? @this.singleton_class.doodle.attributes : { })
|
277
399
|
attrs
|
278
400
|
else
|
@@ -287,7 +409,7 @@ class Doodle
|
|
287
409
|
# as an array), whereas attributes and conversions are keyed
|
288
410
|
# by name and kind respectively, so only the most recent
|
289
411
|
# applies
|
290
|
-
|
412
|
+
|
291
413
|
local_validations + collect_inherited(:local_validations)
|
292
414
|
else
|
293
415
|
local_validations
|
@@ -311,12 +433,11 @@ class Doodle
|
|
311
433
|
handle_inherited_hash(tf, :local_conversions)
|
312
434
|
end
|
313
435
|
|
314
|
-
# fixme: move
|
315
436
|
def initial_values(tf = true)
|
316
437
|
attributes(tf).select{|n, a| a.init_defined? }.inject({}) {|hash, (n, a)|
|
317
438
|
#p [:initial_values, a.name]
|
318
439
|
hash[n] = case a.init
|
319
|
-
when NilClass, TrueClass, FalseClass, Fixnum, Float, Bignum
|
440
|
+
when NilClass, TrueClass, FalseClass, Fixnum, Float, Bignum, Symbol
|
320
441
|
# uncloneable values
|
321
442
|
#p [:initial_values, :special, a.name, a.init]
|
322
443
|
a.init
|
@@ -333,7 +454,7 @@ class Doodle
|
|
333
454
|
begin
|
334
455
|
a.init.clone
|
335
456
|
rescue Exception => e
|
336
|
-
warn "tried to clone #{a.init.class} in :init option"
|
457
|
+
warn "tried to clone #{a.init.class} in :init option (#{e})"
|
337
458
|
#p [:initial_values, :exception, a.name, e]
|
338
459
|
a.init
|
339
460
|
end
|
@@ -341,10 +462,9 @@ class Doodle
|
|
341
462
|
hash
|
342
463
|
}
|
343
464
|
end
|
344
|
-
|
465
|
+
|
345
466
|
# turn off validation, execute block, then set validation to same
|
346
467
|
# state as it was before +defer_validation+ was called - can be nested
|
347
|
-
# fixme: move
|
348
468
|
def defer_validation(&block)
|
349
469
|
old_validation = self.validation_on
|
350
470
|
self.validation_on = false
|
@@ -360,12 +480,12 @@ class Doodle
|
|
360
480
|
|
361
481
|
# helper function to initialize from hash - this is safe to use
|
362
482
|
# after initialization (validate! is called if this method is
|
363
|
-
# called after initialization)
|
483
|
+
# called after initialization)
|
364
484
|
def initialize_from_hash(*args)
|
365
|
-
|
485
|
+
# p [:doodle_initialize_from_hash, :args, *args]
|
366
486
|
defer_validation do
|
367
487
|
# hash initializer
|
368
|
-
# separate into array of hashes of form [{:k1 => v1}, {:k2 => v2}] and positional args
|
488
|
+
# separate into array of hashes of form [{:k1 => v1}, {:k2 => v2}] and positional args
|
369
489
|
key_values, args = args.partition{ |x| x.kind_of?(Hash)}
|
370
490
|
#DBG: Doodle::Debug.d { [self.class, :doodle_initialize_from_hash, :key_values, key_values, :args, args] }
|
371
491
|
#!p [self.class, :doodle_initialize_from_hash, :key_values, key_values, :args, args]
|
@@ -373,7 +493,7 @@ class Doodle
|
|
373
493
|
# set up initial values with ~clones~ of specified values (so not shared between instances)
|
374
494
|
#init_values = initial_values
|
375
495
|
#!p [:init_values, init_values]
|
376
|
-
|
496
|
+
|
377
497
|
# match up positional args with attribute names (from arg_order) using idiom to create hash from array of assocs
|
378
498
|
#arg_keywords = init_values.merge(Hash[*(Utils.flatten_first_level(self.class.arg_order[0...args.size].zip(args)))])
|
379
499
|
arg_keywords = Hash[*(Utils.flatten_first_level(self.class.arg_order[0...args.size].zip(args)))]
|
@@ -390,7 +510,7 @@ class Doodle
|
|
390
510
|
Doodle::Utils.symbolize_keys!(key_values)
|
391
511
|
#DBG: Doodle::Debug.d { [self.class, :doodle_initialize_from_hash, :key_values2, key_values, :args2, args] }
|
392
512
|
#!p [self.class, :doodle_initialize_from_hash, :key_values3, key_values]
|
393
|
-
|
513
|
+
|
394
514
|
# create attributes
|
395
515
|
key_values.keys.each do |key|
|
396
516
|
#DBG: Doodle::Debug.d { [self.class, :doodle_initialize_from_hash, :setting, key, key_values[key]] }
|
@@ -399,19 +519,20 @@ class Doodle
|
|
399
519
|
__send__(key, key_values[key])
|
400
520
|
else
|
401
521
|
# raise error if not defined
|
402
|
-
__doodle__.handle_error key, Doodle::UnknownAttributeError, "unknown attribute '#{key}' #{key_values[key].inspect}
|
522
|
+
__doodle__.handle_error key, Doodle::UnknownAttributeError, "unknown attribute '#{key}' => #{key_values[key].inspect} for #{self} #{doodle.attributes.map{ |k,v| k.inspect}.join(', ')}", Doodle::Utils.doodle_caller
|
403
523
|
end
|
404
524
|
end
|
405
525
|
# do init_values after user supplied values so init blocks can depend on user supplied values
|
406
526
|
#p [:getting_init_values, instance_variables]
|
407
527
|
__doodle__.initial_values.each do |key, value|
|
408
528
|
if !key_values.key?(key) && respond_to?(key)
|
529
|
+
#p [:initial_values, key, value]
|
409
530
|
__send__(key, value)
|
410
531
|
end
|
411
532
|
end
|
412
533
|
end
|
413
534
|
end
|
414
|
-
|
535
|
+
|
415
536
|
end
|
416
537
|
|
417
538
|
# what it says on the tin :) various hacks to hide @__doodle__ variable
|
@@ -432,6 +553,7 @@ class Doodle
|
|
432
553
|
end
|
433
554
|
end
|
434
555
|
|
556
|
+
# implements the #doodle directive
|
435
557
|
class DataTypeHolder
|
436
558
|
attr_accessor :klass
|
437
559
|
def initialize(klass, &block)
|
@@ -445,8 +567,23 @@ class Doodle
|
|
445
567
|
td
|
446
568
|
}
|
447
569
|
end
|
570
|
+
def has(*args, &block)
|
571
|
+
@klass.class_eval { has(*args, &block) }
|
572
|
+
end
|
573
|
+
def must(*args, &block)
|
574
|
+
@klass.class_eval { must(*args, &block) }
|
575
|
+
end
|
576
|
+
def from(*args, &block)
|
577
|
+
@klass.class_eval { from(*args, &block) }
|
578
|
+
end
|
579
|
+
def arg_order(*args, &block)
|
580
|
+
@klass.class_eval { arg_order(*args, &block) }
|
581
|
+
end
|
582
|
+
def doc(*args, &block)
|
583
|
+
@klass.class_eval { doc(*args, &block) }
|
584
|
+
end
|
448
585
|
end
|
449
|
-
|
586
|
+
|
450
587
|
# the core module of Doodle - however, to get most facilities
|
451
588
|
# provided by Doodle without inheriting from Doodle, include
|
452
589
|
# Doodle::Core, not this module
|
@@ -483,7 +620,7 @@ class Doodle
|
|
483
620
|
dh.instance_eval(&block)
|
484
621
|
end
|
485
622
|
end
|
486
|
-
|
623
|
+
|
487
624
|
# helper for Marshal.dump
|
488
625
|
def marshal_dump
|
489
626
|
# note: perhaps should also dump singleton attribute definitions?
|
@@ -500,6 +637,7 @@ class Doodle
|
|
500
637
|
# (using args and/or block)
|
501
638
|
# fixme: move
|
502
639
|
def getter_setter(name, *args, &block)
|
640
|
+
#p [:getter_setter, name]
|
503
641
|
name = name.to_sym
|
504
642
|
if block_given? || args.size > 0
|
505
643
|
#!p [:getter_setter, :setter, name, *args]
|
@@ -525,7 +663,7 @@ class Doodle
|
|
525
663
|
# (e.g. arrays that disappear when you go out of scope)
|
526
664
|
att = __doodle__.lookup_attribute(name)
|
527
665
|
# special case for class/singleton :init
|
528
|
-
if att.optional?
|
666
|
+
if att && att.optional?
|
529
667
|
optional_value = att.init_defined? ? att.init : att.default
|
530
668
|
#p [:optional_value, optional_value]
|
531
669
|
case optional_value
|
@@ -543,31 +681,81 @@ class Doodle
|
|
543
681
|
v
|
544
682
|
else
|
545
683
|
# This is an internal error (i.e. shouldn't happen)
|
546
|
-
__doodle__.handle_error name, NoDefaultError, "'#{name}' has no default defined",
|
684
|
+
__doodle__.handle_error name, NoDefaultError, "'#{name}' has no default defined", Doodle::Utils.doodle_caller
|
547
685
|
end
|
548
686
|
end
|
549
687
|
end
|
550
688
|
private :_getter
|
551
689
|
|
690
|
+
def after_update(params)
|
691
|
+
end
|
692
|
+
|
693
|
+
def ivar_set(name, *args)
|
694
|
+
ivar = "@#{name}"
|
695
|
+
if instance_variable_defined?(ivar)
|
696
|
+
old_value = instance_variable_get(ivar)
|
697
|
+
else
|
698
|
+
old_value = nil
|
699
|
+
end
|
700
|
+
instance_variable_set(ivar, *args)
|
701
|
+
new_value = instance_variable_get(ivar)
|
702
|
+
if new_value != old_value
|
703
|
+
#pp [Doodle, :after_update, { :instance => self, :name => name, :old_value => old_value, :new_value => new_value }]
|
704
|
+
after_update :instance => self, :name => name, :old_value => old_value, :new_value => new_value
|
705
|
+
end
|
706
|
+
end
|
707
|
+
private :ivar_set
|
708
|
+
|
552
709
|
# set an attribute by name - apply validation if defined
|
553
710
|
# fixme: move
|
554
711
|
def _setter(name, *args, &block)
|
555
712
|
##DBG: Doodle::Debug.d { [:_setter, name, args] }
|
556
713
|
#p [:_setter, name, *args]
|
557
|
-
ivar = "@#{name}"
|
714
|
+
#ivar = "@#{name}"
|
715
|
+
att = __doodle__.lookup_attribute(name)
|
716
|
+
if att && doodle.validation_on && att.readonly
|
717
|
+
raise Doodle::ReadOnlyError, "Trying to set a readonly attribute: #{att.name}", Doodle::Utils.doodle_caller
|
718
|
+
end
|
558
719
|
if block_given?
|
559
|
-
|
720
|
+
# if a class has been defined, let's assume it can take a
|
721
|
+
# block initializer (test that it's a Doodle or Proc)
|
722
|
+
if att.kind && !att.abstract && klass = att.kind.first
|
723
|
+
if [Doodle, Proc].any?{ |c| klass <= c }
|
724
|
+
# p [:_setter, '# 1 converting arg to value with kind ' + klass.to_s]
|
725
|
+
args = [klass.new(*args, &block)]
|
726
|
+
else
|
727
|
+
__doodle__.handle_error att.name, ArgumentError, "#{klass} #{att.name} does not take a block initializer", Doodle::Utils.doodle_caller
|
728
|
+
end
|
729
|
+
else
|
730
|
+
# this is used by init do ... block
|
731
|
+
args.unshift(DeferredBlock.new(block))
|
732
|
+
end
|
733
|
+
#elsif
|
560
734
|
end
|
561
|
-
if att = __doodle__.lookup_attribute(name)
|
562
|
-
|
735
|
+
if att # = __doodle__.lookup_attribute(name)
|
736
|
+
if att.kind && !att.abstract && klass = att.kind.first
|
737
|
+
if !args.first.kind_of?(klass) && [Doodle].any?{ |c| klass <= c }
|
738
|
+
#p [:_setter, "#2 converting arg #{att.name} to value with kind #{klass.to_s}"]
|
739
|
+
#p [:_setter, args]
|
740
|
+
begin
|
741
|
+
args = [klass.new(*args, &block)]
|
742
|
+
rescue Object => e
|
743
|
+
__doodle__.handle_error att.name, e.class, e.to_s, Doodle::Utils.doodle_caller
|
744
|
+
end
|
745
|
+
end
|
746
|
+
end
|
747
|
+
# args = [klass.new(*args, &block)] ##DBG: Doodle::Debug.d { [:_setter, name, args] }
|
563
748
|
#p [:_setter, :got_att1, name, ivar, *args]
|
564
|
-
v = instance_variable_set(ivar, att.validate(self, *args))
|
749
|
+
# v = instance_variable_set(ivar, att.validate(self, *args))
|
750
|
+
v = ivar_set(name, att.validate(self, *args))
|
751
|
+
|
565
752
|
#p [:_setter, :got_att2, name, ivar, :value, v]
|
566
753
|
#v = instance_variable_set(ivar, *args)
|
567
754
|
else
|
568
755
|
#p [:_setter, :no_att, name, *args]
|
569
756
|
##DBG: Doodle::Debug.d { [:_setter, "no attribute"] }
|
570
|
-
v = instance_variable_set(ivar, *args)
|
757
|
+
# v = instance_variable_set(ivar, *args)
|
758
|
+
v = ivar_set(name, *args)
|
571
759
|
end
|
572
760
|
validate!(false)
|
573
761
|
v
|
@@ -590,41 +778,36 @@ class Doodle
|
|
590
778
|
|
591
779
|
# add a validation
|
592
780
|
def must(constraint = 'be valid', &block)
|
593
|
-
|
594
|
-
# is this really useful? do I really want it?
|
595
|
-
__doodle__.local_validations << Validation.new(constraint, &proc { |v| v.instance_eval(constraint) })
|
596
|
-
else
|
597
|
-
__doodle__.local_validations << Validation.new(constraint, &block)
|
598
|
-
end
|
781
|
+
__doodle__.local_validations << Validation.new(constraint, &block)
|
599
782
|
end
|
600
783
|
|
601
784
|
# add a validation that attribute must be of class <= kind
|
602
785
|
def kind(*args, &block)
|
603
|
-
@kind ||= []
|
604
786
|
if args.size > 0
|
605
787
|
@kind = [args].flatten
|
606
788
|
# todo[figure out how to handle kind being specified twice?]
|
607
789
|
if @kind.size > 2
|
608
|
-
kind_text = "a kind of #{ @kind[0..-2].map{ |x| x.to_s }.join(', ') } or #{@kind[-1].to_s}" # =>
|
790
|
+
kind_text = "be a kind of #{ @kind[0..-2].map{ |x| x.to_s }.join(', ') } or #{@kind[-1].to_s}" # =>
|
609
791
|
else
|
610
|
-
kind_text = "a kind of #{@kind.to_s}"
|
792
|
+
kind_text = "be a kind of #{@kind.to_s}"
|
611
793
|
end
|
612
794
|
__doodle__.local_validations << (Validation.new(kind_text) { |x| @kind.any? { |klass| x.kind_of?(klass) } })
|
613
795
|
else
|
614
|
-
@kind
|
796
|
+
@kind ||= []
|
615
797
|
end
|
616
798
|
end
|
617
799
|
|
618
800
|
# convert a value according to conversion rules
|
619
801
|
# fixme: move
|
620
802
|
def convert(owner, *args)
|
621
|
-
|
803
|
+
#pp( { :convert => 1, :owner => owner, :args => args, :conversions => __doodle__.conversions } )
|
622
804
|
begin
|
623
805
|
args = args.map do |value|
|
624
806
|
#!p [:convert, 2, value]
|
625
807
|
if (converter = __doodle__.conversions[value.class])
|
626
|
-
|
627
|
-
value = converter[
|
808
|
+
#p [:convert, 3, value, self, caller]
|
809
|
+
value = converter[value]
|
810
|
+
#value = instance_exec(value, &converter)
|
628
811
|
#!p [:convert, 4, value]
|
629
812
|
else
|
630
813
|
#!p [:convert, 5, value]
|
@@ -641,22 +824,24 @@ class Doodle
|
|
641
824
|
#!p [:convert, 10, converter_class]
|
642
825
|
if converter = __doodle__.conversions[converter_class]
|
643
826
|
#!p [:convert, 11, converter]
|
644
|
-
value = converter[
|
827
|
+
value = converter[value]
|
828
|
+
#value = instance_exec(value, &converter)
|
645
829
|
#!p [:convert, 12, value]
|
646
830
|
end
|
647
831
|
else
|
648
|
-
#!p [:convert, 13, :kind, kind, name, value]
|
832
|
+
#!p [:convert, 13, :kind, kind, name, value]
|
649
833
|
mappable_kinds = kind.select{ |x| x <= Doodle::Core }
|
650
|
-
#!p [:convert, 13.1, :kind, kind, mappable_kinds]
|
834
|
+
#!p [:convert, 13.1, :kind, kind, mappable_kinds]
|
651
835
|
if mappable_kinds.size > 0
|
652
836
|
mappable_kinds.each do |mappable_kind|
|
653
|
-
#!p [:convert, 14, :kind_is_a_doodle, value.class, mappable_kind, mappable_kind.doodle.conversions, args]
|
837
|
+
#!p [:convert, 14, :kind_is_a_doodle, value.class, mappable_kind, mappable_kind.doodle.conversions, args]
|
654
838
|
if converter = mappable_kind.doodle.conversions[value.class]
|
655
|
-
#!p [:convert, 15, value, mappable_kind, args]
|
656
|
-
value = converter[
|
839
|
+
#!p [:convert, 15, value, mappable_kind, args]
|
840
|
+
value = converter[value]
|
841
|
+
#value = instance_exec(value, &converter)
|
657
842
|
break
|
658
843
|
else
|
659
|
-
#!p [:convert, 16, :no_conversion_for, value.class]
|
844
|
+
#!p [:convert, 16, :no_conversion_for, value.class]
|
660
845
|
end
|
661
846
|
end
|
662
847
|
else
|
@@ -668,7 +853,7 @@ class Doodle
|
|
668
853
|
value
|
669
854
|
end
|
670
855
|
rescue Exception => e
|
671
|
-
owner.__doodle__.handle_error name, ConversionError, "#{e.message}",
|
856
|
+
owner.__doodle__.handle_error name, ConversionError, "#{e.message}", Doodle::Utils.doodle_caller
|
672
857
|
end
|
673
858
|
if args.size > 1
|
674
859
|
args
|
@@ -681,26 +866,26 @@ class Doodle
|
|
681
866
|
# fixme: move
|
682
867
|
def validate(owner, *args)
|
683
868
|
##DBG: Doodle::Debug.d { [:validate, self, :owner, owner, :args, args ] }
|
684
|
-
|
869
|
+
#p [:validate, 1, args]
|
685
870
|
begin
|
686
871
|
value = convert(owner, *args)
|
687
872
|
rescue Exception => e
|
688
|
-
owner.__doodle__.handle_error name, ConversionError, "#{owner.kind_of?(Class) ? owner : owner.class}.#{ name } - #{e.message}",
|
873
|
+
owner.__doodle__.handle_error name, ConversionError, "#{owner.kind_of?(Class) ? owner : owner.class}.#{ name } - #{e.message}", Doodle::Utils.doodle_caller
|
689
874
|
end
|
690
|
-
|
875
|
+
#p [:validate, 2, args, :becomes, value]
|
691
876
|
__doodle__.validations.each do |v|
|
692
877
|
##DBG: Doodle::Debug.d { [:validate, self, v, args, value] }
|
693
878
|
if !v.block[value]
|
694
|
-
owner.__doodle__.handle_error name, ValidationError, "#{owner.kind_of?(Class) ? owner : owner.class}.#{ name } must #{ v.message } - got #{ value.class }(#{ value.inspect })",
|
879
|
+
owner.__doodle__.handle_error name, ValidationError, "#{owner.kind_of?(Class) ? owner : owner.class}.#{ name } must #{ v.message } - got #{ value.class }(#{ value.inspect })", Doodle::Utils.doodle_caller
|
695
880
|
end
|
696
881
|
end
|
697
|
-
|
882
|
+
#p [:validate, 3, value]
|
698
883
|
value
|
699
884
|
end
|
700
885
|
|
701
886
|
# define a getter_setter
|
702
887
|
# fixme: move
|
703
|
-
def define_getter_setter(name,
|
888
|
+
def define_getter_setter(name, params = { }, &block)
|
704
889
|
# need to use string eval because passing block
|
705
890
|
sc_eval "def #{name}(*args, &block); getter_setter(:#{name}, *args, &block); end", __FILE__, __LINE__
|
706
891
|
sc_eval "def #{name}=(*args, &block); _setter(:#{name}, *args); end", __FILE__, __LINE__
|
@@ -717,6 +902,16 @@ class Doodle
|
|
717
902
|
end
|
718
903
|
private :define_getter_setter
|
719
904
|
|
905
|
+
# +doc+ add docs to doodle class or attribute
|
906
|
+
def doc(*args, &block)
|
907
|
+
if args.size > 0
|
908
|
+
@doc = *args
|
909
|
+
else
|
910
|
+
@doc
|
911
|
+
end
|
912
|
+
end
|
913
|
+
alias :doc= :doc
|
914
|
+
|
720
915
|
# +has+ is an extended +attr_accessor+
|
721
916
|
#
|
722
917
|
# simple usage - just like +attr_accessor+:
|
@@ -738,33 +933,33 @@ class Doodle
|
|
738
933
|
# default { Date.today }
|
739
934
|
# end
|
740
935
|
# end
|
741
|
-
#
|
936
|
+
#
|
742
937
|
def has(*args, &block)
|
743
938
|
#DBG: Doodle::Debug.d { [:has, self, self.class, args] }
|
744
|
-
|
939
|
+
|
745
940
|
params = DoodleAttribute.params_from_args(self, *args)
|
746
941
|
# get specialized attribute class or use default
|
747
942
|
attribute_class = params.delete(:using) || DoodleAttribute
|
748
943
|
|
749
944
|
# could this be handled in DoodleAttribute?
|
750
945
|
# define getter setter before setting up attribute
|
751
|
-
define_getter_setter params[:name],
|
946
|
+
define_getter_setter params[:name], params, &block
|
752
947
|
#p [:attribute, attribute_class, params]
|
753
|
-
__doodle__.local_attributes[params[:name]] = attribute_class.new(params, &block)
|
948
|
+
attr = __doodle__.local_attributes[params[:name]] = attribute_class.new(params, &block)
|
754
949
|
end
|
755
|
-
|
950
|
+
|
756
951
|
# define order for positional arguments
|
757
952
|
def arg_order(*args)
|
758
953
|
if args.size > 0
|
759
954
|
begin
|
760
955
|
args = args.uniq
|
761
956
|
args.each do |x|
|
762
|
-
__doodle__.handle_error :arg_order, ArgumentError, "#{x} not a Symbol",
|
763
|
-
__doodle__.handle_error :arg_order, NameError, "#{x} not an attribute name",
|
957
|
+
__doodle__.handle_error :arg_order, ArgumentError, "#{x} not a Symbol", Doodle::Utils.doodle_caller if !(x.class <= Symbol)
|
958
|
+
__doodle__.handle_error :arg_order, NameError, "#{x} not an attribute name", Doodle::Utils.doodle_caller if !doodle.attributes.keys.include?(x)
|
764
959
|
end
|
765
960
|
__doodle__.arg_order = args
|
766
961
|
rescue Exception => e
|
767
|
-
__doodle__.handle_error :arg_order, InvalidOrderError, e.to_s,
|
962
|
+
__doodle__.handle_error :arg_order, InvalidOrderError, e.to_s, Doodle::Utils.doodle_caller
|
768
963
|
end
|
769
964
|
else
|
770
965
|
__doodle__.arg_order + (__doodle__.attributes.keys - __doodle__.arg_order)
|
@@ -799,29 +994,29 @@ class Doodle
|
|
799
994
|
# if all == true, reset values so conversions and
|
800
995
|
# validations are applied to raw instance variables
|
801
996
|
# e.g. when loaded from YAML
|
802
|
-
if all
|
997
|
+
if all && !att.readonly
|
803
998
|
##DBG: Doodle::Debug.d { [:validate!, :sending, att.name, instance_variable_get(ivar_name) ] }
|
804
999
|
__send__("#{att.name}=", instance_variable_get(ivar_name))
|
805
1000
|
end
|
806
1001
|
elsif att.optional? # treat default/init as special case
|
807
1002
|
##DBG: Doodle::Debug.d { [:validate!, :optional, name ]}
|
808
|
-
|
1003
|
+
next
|
809
1004
|
elsif self.class != Class
|
810
|
-
__doodle__.handle_error name, Doodle::ValidationError, "#{self} missing required attribute '#{name}'",
|
1005
|
+
__doodle__.handle_error name, Doodle::ValidationError, "#{self} missing required attribute '#{name}'", Doodle::Utils.doodle_caller
|
811
1006
|
end
|
812
1007
|
end
|
813
|
-
|
1008
|
+
|
814
1009
|
# now apply instance level validations
|
815
|
-
|
1010
|
+
|
816
1011
|
##DBG: Doodle::Debug.d { [:validate!, "validations", doodle_validations ]}
|
817
1012
|
__doodle__.validations.each do |v|
|
818
1013
|
##DBG: Doodle::Debug.d { [:validate!, self, v ] }
|
819
1014
|
begin
|
820
1015
|
if !instance_eval(&v.block)
|
821
|
-
__doodle__.handle_error self, ValidationError, "#{ self.class } must #{ v.message }",
|
1016
|
+
__doodle__.handle_error self, ValidationError, "#{ self.class } must #{ v.message }", Doodle::Utils.doodle_caller
|
822
1017
|
end
|
823
1018
|
rescue Exception => e
|
824
|
-
__doodle__.handle_error self, ValidationError, e.to_s,
|
1019
|
+
__doodle__.handle_error self, ValidationError, e.to_s, Doodle::Utils.doodle_caller
|
825
1020
|
end
|
826
1021
|
end
|
827
1022
|
end
|
@@ -837,13 +1032,29 @@ class Doodle
|
|
837
1032
|
super
|
838
1033
|
end
|
839
1034
|
__doodle__.validation_on = true
|
840
|
-
|
1035
|
+
#p [:doodle_parent, Doodle.parent, caller[-1]]
|
841
1036
|
Doodle.context.push(self)
|
842
1037
|
__doodle__.defer_validation do
|
843
1038
|
doodle.initialize_from_hash(*args)
|
844
1039
|
instance_eval(&block) if block_given?
|
845
1040
|
end
|
846
1041
|
Doodle.context.pop
|
1042
|
+
#p [:doodle, __doodle__.__inspect__]
|
1043
|
+
#p [:doodle, __doodle__.attributes]
|
1044
|
+
#p [:doodle_parent, __doodle__.parent]
|
1045
|
+
end
|
1046
|
+
|
1047
|
+
# create 'pure' hash of scalars only from attributes - hacky but works fine
|
1048
|
+
def to_hash
|
1049
|
+
Doodle::Utils.symbolize_keys!(YAML::load(to_yaml.gsub(/!ruby\/object:.*$/, '')) || { }, true)
|
1050
|
+
#begin
|
1051
|
+
# YAML::load(to_yaml.gsub(/!ruby\/object:.*$/, '')) || { }
|
1052
|
+
#rescue Object => e
|
1053
|
+
# doodle.attributes.inject({}) {|hash, (name, attribute)| hash[name] = send(name); hash}
|
1054
|
+
#end
|
1055
|
+
end
|
1056
|
+
def to_string_hash
|
1057
|
+
Doodle::Utils.stringify_keys!(YAML::load(to_yaml.gsub(/!ruby\/object:.*$/, '')) || { }, true)
|
847
1058
|
end
|
848
1059
|
|
849
1060
|
end
|
@@ -856,10 +1067,10 @@ class Doodle
|
|
856
1067
|
# As the notion of a factory function is somewhat contentious [xref
|
857
1068
|
# ruby-talk], you need to explicitly ask for them by including Factory
|
858
1069
|
# in your base class:
|
859
|
-
# class
|
1070
|
+
# class Animal < Doodle
|
860
1071
|
# include Factory
|
861
1072
|
# end
|
862
|
-
# class Dog <
|
1073
|
+
# class Dog < Animal
|
863
1074
|
# end
|
864
1075
|
# stimpy = Dog(:name => 'Stimpy')
|
865
1076
|
# etc.
|
@@ -880,8 +1091,8 @@ class Doodle
|
|
880
1091
|
rescue Object
|
881
1092
|
false
|
882
1093
|
end
|
883
|
-
|
884
|
-
if name =~ Factory::RX_IDENTIFIER && !method_defined && !klass.respond_to?(name) && !eval("respond_to?(:#{name})", TOPLEVEL_BINDING)
|
1094
|
+
|
1095
|
+
if name =~ Factory::RX_IDENTIFIER && !method_defined && !klass.respond_to?(name) && !eval("respond_to?(:#{name})", TOPLEVEL_BINDING)
|
885
1096
|
eval("def #{ name }(*args, &block); ::#{name}.new(*args, &block); end", ::TOPLEVEL_BINDING, __FILE__, __LINE__)
|
886
1097
|
end
|
887
1098
|
else
|
@@ -915,11 +1126,9 @@ class Doodle
|
|
915
1126
|
}
|
916
1127
|
end
|
917
1128
|
end
|
918
|
-
# deprecated
|
919
|
-
Helper = Core
|
920
1129
|
|
921
|
-
#
|
922
|
-
class
|
1130
|
+
# wierd 1.9 shit
|
1131
|
+
class IAmNotUsedBut1_9GoesIntoAnInfiniteRegressInInspectIfIAmNotDefined
|
923
1132
|
include Core
|
924
1133
|
end
|
925
1134
|
include Core
|
@@ -934,6 +1143,7 @@ class Doodle
|
|
934
1143
|
class DoodleAttribute < Doodle
|
935
1144
|
# note: using extend with a module causes an infinite loop in 1.9
|
936
1145
|
# hence the inline
|
1146
|
+
|
937
1147
|
class << self
|
938
1148
|
# rewrite rules for the argument list to #has
|
939
1149
|
def params_from_args(owner, *args)
|
@@ -953,15 +1163,15 @@ class Doodle
|
|
953
1163
|
params = key_values.inject(params){ |acc, item| acc.merge(item)}
|
954
1164
|
#DBG: Doodle::Debug.d { [:has, self, self.class, params] }
|
955
1165
|
if !params.key?(:name)
|
956
|
-
__doodle__.handle_error name, ArgumentError, "#{self.class} must have a name",
|
1166
|
+
__doodle__.handle_error name, ArgumentError, "#{self.class} must have a name", Doodle::Utils.doodle_caller
|
957
1167
|
params[:name] = :__ERROR_missing_name__
|
958
1168
|
else
|
959
1169
|
# ensure that :name is a symbol
|
960
1170
|
params[:name] = params[:name].to_sym
|
961
1171
|
end
|
962
1172
|
name = params[:name]
|
963
|
-
__doodle__.handle_error name, ArgumentError, "#{self.class} has too many arguments",
|
964
|
-
|
1173
|
+
__doodle__.handle_error name, ArgumentError, "#{self.class} has too many arguments", Doodle::Utils.doodle_caller if positional_args.size > 0
|
1174
|
+
|
965
1175
|
if collector = params.delete(:collect)
|
966
1176
|
if !params.key?(:using)
|
967
1177
|
if params.key?(:key)
|
@@ -1035,6 +1245,15 @@ class Doodle
|
|
1035
1245
|
# special case - not an attribute
|
1036
1246
|
define_getter_setter :doodle_owner
|
1037
1247
|
|
1248
|
+
# temporarily fake existence of abstract attribute - later has
|
1249
|
+
# :abstract overrides this
|
1250
|
+
def abstract
|
1251
|
+
@abstract = false
|
1252
|
+
end
|
1253
|
+
def readonly
|
1254
|
+
false
|
1255
|
+
end
|
1256
|
+
|
1038
1257
|
# name of attribute
|
1039
1258
|
has :name, :kind => Symbol do
|
1040
1259
|
from String do |s|
|
@@ -1048,13 +1267,21 @@ class Doodle
|
|
1048
1267
|
# initial value
|
1049
1268
|
has :init, :default => nil
|
1050
1269
|
|
1270
|
+
# documentation
|
1271
|
+
has :doc, :default => ""
|
1272
|
+
|
1273
|
+
# don't try to initialize from this class
|
1274
|
+
remove_method(:abstract) # because we faked it earlier - remove to avoid redefinition warning
|
1275
|
+
has :abstract, :default => false
|
1276
|
+
remove_method(:readonly) # because we faked it earlier - remove to avoid redefinition warning
|
1277
|
+
has :readonly, :default => false
|
1051
1278
|
end
|
1052
1279
|
|
1053
1280
|
# base class for attribute collector classes
|
1054
1281
|
class AttributeCollector < DoodleAttribute
|
1055
1282
|
has :collector_class
|
1056
1283
|
has :collector_name
|
1057
|
-
|
1284
|
+
|
1058
1285
|
def resolve_collector_class
|
1059
1286
|
if !collector_class.kind_of?(Class)
|
1060
1287
|
self.collector_class = Doodle::Utils.const_resolve(collector_class)
|
@@ -1074,7 +1301,7 @@ class Doodle
|
|
1074
1301
|
define_collection
|
1075
1302
|
from Hash do |hash|
|
1076
1303
|
resolve_collector_class
|
1077
|
-
hash.inject(
|
1304
|
+
hash.inject(self.init.clone) do |h, (key, value)|
|
1078
1305
|
h[key] = resolve_value(value)
|
1079
1306
|
h
|
1080
1307
|
end
|
@@ -1085,12 +1312,13 @@ class Doodle
|
|
1085
1312
|
end
|
1086
1313
|
end
|
1087
1314
|
def post_process(results)
|
1088
|
-
results
|
1315
|
+
self.init.clone.replace(results)
|
1089
1316
|
end
|
1090
1317
|
end
|
1091
1318
|
|
1092
1319
|
# define collector methods for array-like attribute collectors
|
1093
1320
|
class AppendableAttribute < AttributeCollector
|
1321
|
+
# has :init, :init => DoodleArray.new
|
1094
1322
|
has :init, :init => []
|
1095
1323
|
|
1096
1324
|
# define a collector for appendable collections
|
@@ -1113,16 +1341,17 @@ class Doodle
|
|
1113
1341
|
end", __FILE__, __LINE__)
|
1114
1342
|
end
|
1115
1343
|
end
|
1116
|
-
|
1344
|
+
|
1117
1345
|
end
|
1118
1346
|
|
1119
1347
|
# define collector methods for hash-like attribute collectors
|
1120
1348
|
class KeyedAttribute < AttributeCollector
|
1349
|
+
# has :init, :init => DoodleHash.new
|
1121
1350
|
has :init, :init => { }
|
1122
1351
|
has :key
|
1123
1352
|
|
1124
1353
|
def post_process(results)
|
1125
|
-
results.inject(
|
1354
|
+
results.inject(self.init.clone) do |h, result|
|
1126
1355
|
h[result.send(key)] = result
|
1127
1356
|
h
|
1128
1357
|
end
|