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