doodle 0.1.6 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,21 @@
1
+ == 0.1.7 / 2008-05-10
2
+ - Features
3
+ - #has now accepts class constant in name position and generates name
4
+ and kind from that, e.g.
5
+ has Person --> has :person, :kind => Person
6
+ has AudioClip --> has :audio_clip, :kind => AudioClip
7
+ - moved datatypes code into main doodle.rb
8
+ - use doodle.parent instead of doodle_parent
9
+
10
+ - Bug fixes:
11
+ - fixed handling of :init blocks so they can refer to user supplied
12
+ attribute values which don't have defaults
13
+ - note: attributes referred to in :init blocks ~must~ be supplied as
14
+ keyword arguments - by the time evaluation gets to the
15
+ initialization block, the :init block has already been evaluated
16
+ so will raise an error if it cannot find the value it's looking for
17
+ - moved more methods into DoodleInfo from Core
18
+
1
19
  == 0.1.6 / 2008-05-08
2
20
  - Features:
3
21
  - short cut syntax for #must - can now specify constraints like
@@ -16,7 +16,7 @@ class Child < Doodle
16
16
  # - somewhat subtle difference (from programmer's point of
17
17
  # - view) between a proc and a block
18
18
  init do
19
- doodle_parent
19
+ doodle.parent
20
20
  end
21
21
  end
22
22
  end
@@ -3,6 +3,7 @@
3
3
  # 2007-11-24 first version
4
4
  # 2008-04-18 latest release 0.0.12
5
5
  # 2008-05-07 0.1.6
6
+ # 2008-05-12 0.1.7
6
7
  $:.unshift(File.dirname(__FILE__)) unless
7
8
  $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
8
9
 
@@ -57,6 +58,7 @@ class Doodle
57
58
  def snake_case(camel_cased_word)
58
59
  camel_cased_word.gsub(/([A-Z]+)([A-Z])/,'\1_\2').gsub(/([a-z])([A-Z])/,'\1_\2').downcase
59
60
  end
61
+ # resolve a constant of the form Some::Class::Or::Module
60
62
  def const_resolve(constant)
61
63
  constant.to_s.split(/::/).reject{|x| x.empty?}.inject(Object) { |prev, this| prev.const_get(this) }
62
64
  end
@@ -109,46 +111,6 @@ class Doodle
109
111
  end
110
112
  end
111
113
 
112
- # provide an alternative inheritance chain that works for singleton
113
- # classes as well as modules, classes and instances
114
- module Inherited
115
-
116
- # doodle_parents returns the set of parent classes of an object
117
- def doodle_parents
118
- anc = if respond_to?(:ancestors)
119
- if ancestors.include?(self)
120
- ancestors[1..-1]
121
- else
122
- # singletons have no doodle_parents (they're orphans)
123
- []
124
- end
125
- else
126
- self.class.ancestors
127
- end
128
- anc.select{|x| x.kind_of?(Class)}
129
- end
130
-
131
- # need concepts of
132
- # - attributes
133
- # - instance_attributes
134
- # - singleton_attributes
135
- # - class_attributes
136
-
137
- # send message to all doodle_parents and collect results
138
- def doodle_collect_inherited(message)
139
- result = []
140
- doodle_parents.each do |klass|
141
- if klass.respond_to?(message)
142
- result.unshift(*klass.__send__(message))
143
- else
144
- break
145
- end
146
- end
147
- result
148
- end
149
- private :doodle_collect_inherited
150
- end
151
-
152
114
  # = embrace
153
115
  # the intent of embrace is to provide a way to create directives
154
116
  # that affect all members of a class 'family' without having to
@@ -208,26 +170,134 @@ class Doodle
208
170
 
209
171
  # place to stash bookkeeping info
210
172
  class DoodleInfo
211
- attr_accessor :doodle_local_attributes
212
- attr_accessor :doodle_local_validations
213
- attr_accessor :doodle_local_conversions
173
+ attr_accessor :local_attributes
174
+ attr_accessor :local_validations
175
+ attr_accessor :local_conversions
214
176
  attr_accessor :validation_on
215
177
  attr_accessor :arg_order
216
178
  attr_accessor :errors
217
- attr_accessor :doodle_parent
179
+ attr_accessor :parent
218
180
 
219
181
  def initialize(object)
220
- @doodle_local_attributes = OrderedHash.new
221
- @doodle_local_validations = []
182
+ @this = object
183
+ @local_attributes = OrderedHash.new
184
+ @local_validations = []
222
185
  @validation_on = true
223
- @doodle_local_conversions = {}
186
+ @local_conversions = {}
224
187
  @arg_order = []
225
188
  @errors = []
226
- @doodle_parent = nil
189
+ @parent = nil
227
190
  end
191
+ # hide from inspect
228
192
  def inspect
229
193
  ''
230
194
  end
195
+
196
+ # handle errors either by collecting in :errors or raising an exception
197
+ def handle_error(name, *args)
198
+ # don't include duplicates (FIXME: hacky - shouldn't have duplicates in the first place)
199
+ if !errors.include?([name, *args])
200
+ errors << [name, *args]
201
+ end
202
+ if Doodle.raise_exception_on_error
203
+ raise(*args)
204
+ end
205
+ end
206
+
207
+ # provide an alternative inheritance chain that works for singleton
208
+ # classes as well as modules, classes and instances
209
+ def parents
210
+ anc = if @this.respond_to?(:ancestors)
211
+ if @this.ancestors.include?(@this)
212
+ @this.ancestors[1..-1]
213
+ else
214
+ # singletons have no doodle_parents (they're orphans)
215
+ []
216
+ end
217
+ else
218
+ @this.class.ancestors
219
+ end
220
+ anc.select{|x| x.kind_of?(Class)}
221
+ end
222
+
223
+ # send message to all doodle_parents and collect results
224
+ def collect_inherited(message)
225
+ result = []
226
+ parents.each do |klass|
227
+ if klass.respond_to?(message)
228
+ result.unshift(*klass.__send__(message))
229
+ else
230
+ break
231
+ end
232
+ end
233
+ result
234
+ end
235
+
236
+ def handle_inherited_hash(tf, method)
237
+ if tf
238
+ collect_inherited(method).inject(OrderedHash.new){ |hash, item|
239
+ hash.merge(OrderedHash[*item])
240
+ }.merge(@this.__send__(method))
241
+ else
242
+ @this.__send__(method)
243
+ end
244
+ end
245
+
246
+ # returns array of Attributes
247
+ # - if tf == true, returns all inherited attributes
248
+ # - if tf == false, returns only those attributes defined in the current object/class
249
+ def attributes(tf = true)
250
+ results = handle_inherited_hash(tf, :doodle_local_attributes)
251
+ # if an instance, include the singleton_class attributes
252
+ if !@this.kind_of?(Class) && @this.singleton_class.doodle.respond_to?(:attributes)
253
+ results = results.merge(@this.singleton_class.doodle_attributes)
254
+ end
255
+ results
256
+ end
257
+
258
+ def class_attributes
259
+ attrs = OrderedHash.new
260
+ if @this.kind_of?(Class)
261
+ attrs = collect_inherited(:class_attributes).inject(OrderedHash.new){ |hash, item|
262
+ hash.merge(OrderedHash[*item])
263
+ }.merge(@this.singleton_class.respond_to?(:doodle_attributes) ? @this.singleton_class.doodle_attributes : { })
264
+ attrs
265
+ else
266
+ @this.class.class_attributes
267
+ end
268
+ end
269
+
270
+ def validations(tf = true)
271
+ if tf
272
+ # note: validations are handled differently to attributes and
273
+ # conversions because ~all~ validations apply (so are stored
274
+ # as an array), whereas attributes and conversions are keyed
275
+ # by name and kind respectively, so only the most recent
276
+ # applies
277
+
278
+ local_validations + collect_inherited(:doodle_local_validations)
279
+ else
280
+ local_validations
281
+ end
282
+ end
283
+
284
+ def lookup_attribute(name)
285
+ # (look at singleton attributes first)
286
+ # fixme[this smells like a hack to me]
287
+ if @this.class == Class
288
+ class_attributes[name]
289
+ else
290
+ attributes[name]
291
+ end
292
+ end
293
+
294
+ # returns hash of conversions
295
+ # - if tf == true, returns all inherited conversions
296
+ # - if tf == false, returns only those conversions defined in the current object/class
297
+ def conversions(tf = true)
298
+ handle_inherited_hash(tf, :doodle_local_conversions)
299
+ end
300
+
231
301
  end
232
302
 
233
303
  # what it says on the tin :) various hacks to hide @__doodle__ variable
@@ -238,19 +308,36 @@ class Doodle
238
308
  define_method :instance_variables do
239
309
  meth.bind(self).call.reject{ |x| x.to_s =~ /@__doodle__/}
240
310
  end
241
-
242
311
  # hide @__doodle__ from inspect
243
312
  def inspect
244
313
  super.gsub(/\s*@__doodle__=,/,'').gsub(/,?\s*@__doodle__=/,'')
245
314
  end
315
+ # fix for pp
316
+ def pretty_print(q)
317
+ q.pp_object(self)
318
+ end
246
319
  end
247
320
 
321
+ class DataTypeHolder
322
+ attr_accessor :klass
323
+ def initialize(klass, &block)
324
+ @klass = klass
325
+ instance_eval(&block) if block_given?
326
+ end
327
+ def define(name, params, block, type_params, &type_block)
328
+ @klass.class_eval {
329
+ td = has(name, type_params.merge(params), &type_block)
330
+ td.instance_eval(&block) if block
331
+ td
332
+ }
333
+ end
334
+ end
335
+
248
336
  # the core module of Doodle - however, to get most facilities
249
337
  # provided by Doodle without inheriting from Doodle, include
250
338
  # Doodle::Core, not this module
251
339
  module BaseMethods
252
340
  include SelfClass
253
- include Inherited
254
341
  include SmokeAndMirrors
255
342
 
256
343
  # this is the only way to get at internal values. Note: this is
@@ -259,8 +346,30 @@ class Doodle
259
346
  def __doodle__
260
347
  @__doodle__ ||= DoodleInfo.new(self)
261
348
  end
262
- private :__doodle__
349
+ protected :__doodle__
263
350
 
351
+ # set up global datatypes
352
+ def datatypes(*mods)
353
+ mods.each do |mod|
354
+ DataTypeHolder.class_eval { include mod }
355
+ end
356
+ end
357
+
358
+ # vector through this method to get to doodle info or enable global
359
+ # datatypes and provide an interface that allows you to add your own
360
+ # datatypes to this declaration
361
+ def doodle(*mods, &block)
362
+ if mods.size == 0 && !block_given?
363
+ __doodle__
364
+ else
365
+ dh = Doodle::DataTypeHolder.new(self)
366
+ mods.each do |mod|
367
+ dh.extend(mod)
368
+ end
369
+ dh.instance_eval(&block)
370
+ end
371
+ end
372
+
264
373
  # helper for Marshal.dump
265
374
  def marshal_dump
266
375
  # note: perhaps should also dump singleton attribute definitions?
@@ -273,46 +382,17 @@ class Doodle
273
382
  end
274
383
  end
275
384
 
276
- #### ERRORS COLLECTION
277
-
278
- # where should I put this?
279
- def errors
280
- __doodle__.errors
281
- end
282
-
283
- # clear out the errors collection
284
- def clear_errors
285
- #pp [:clear_errors, self, caller]
286
- __doodle__.errors.clear
287
- end
288
-
289
- # handle errors either by collecting in :errors or raising an exception
290
- def handle_error(name, *args)
291
- # don't include duplicates (FIXME: hacky - shouldn't have duplicates in the first place)
292
- if !self.errors.include?([name, *args])
293
- self.errors << [name, *args]
294
- end
295
- if Doodle.raise_exception_on_error
296
- raise(*args)
297
- end
385
+ # deprecated methods
386
+ def doodle_collect_inherited(message)
387
+ __doodle__.collect_inherited(message)
298
388
  end
299
-
300
- #### ERRORS COLLECTION
301
-
302
- def _handle_inherited_hash(tf, method)
303
- if tf
304
- doodle_collect_inherited(method).inject(OrderedHash.new){ |hash, item|
305
- hash.merge(OrderedHash[*item])
306
- }.merge(__send__(method))
307
- else
308
- __send__(method)
309
- end
389
+ def doodle_parents
390
+ __doodle__.parents
310
391
  end
311
- private :_handle_inherited_hash
312
392
 
313
393
  # return attributes defined in instance
314
394
  def doodle_local_attributes
315
- __doodle__.doodle_local_attributes
395
+ __doodle__.local_attributes
316
396
  end
317
397
  protected :doodle_local_attributes
318
398
 
@@ -320,77 +400,47 @@ class Doodle
320
400
  # - if tf == true, returns all inherited attributes
321
401
  # - if tf == false, returns only those attributes defined in the current object/class
322
402
  def doodle_attributes(tf = true)
323
- results = _handle_inherited_hash(tf, :doodle_local_attributes)
324
- # if an instance, include the singleton_class attributes
325
- if !kind_of?(Class) && singleton_class.respond_to?(:doodle_attributes)
326
- results = results.merge(singleton_class.doodle_attributes)
327
- end
328
- results
403
+ __doodle__.attributes(tf)
329
404
  end
330
405
 
331
406
  # return attributes for class
332
- def class_attributes(tf = true)
333
- attrs = OrderedHash.new
334
- if self.kind_of?(Class)
335
- attrs = doodle_collect_inherited(:class_attributes).inject(OrderedHash.new){ |hash, item|
336
- hash.merge(OrderedHash[*item])
337
- }.merge(singleton_class.respond_to?(:doodle_attributes) ? singleton_class.doodle_attributes : { })
338
- attrs
339
- else
340
- self.class.class_attributes
341
- end
407
+ def class_attributes
408
+ __doodle__.class_attributes
342
409
  end
343
410
 
344
411
  # the set of conversions defined in the current class (i.e. without inheritance)
412
+ # deprecated
345
413
  def doodle_local_conversions
346
- __doodle__.doodle_local_conversions
414
+ __doodle__.local_conversions
347
415
  end
348
416
  protected :doodle_local_conversions
349
417
 
350
418
  # returns hash of conversions
351
419
  # - if tf == true, returns all inherited conversions
352
420
  # - if tf == false, returns only those conversions defined in the current object/class
421
+ # deprecated
353
422
  def doodle_conversions(tf = true)
354
- _handle_inherited_hash(tf, :doodle_local_conversions)
423
+ __doodle__.conversions(tf)
355
424
  end
356
425
 
357
426
  # the set of validations defined in the current class (i.e. without inheritance)
427
+ # deprecated
358
428
  def doodle_local_validations
359
- __doodle__.doodle_local_validations
429
+ __doodle__.local_validations
360
430
  end
361
431
  protected :doodle_local_validations
362
432
 
363
433
  # returns array of Validations
364
434
  # - if tf == true, returns all inherited validations
365
435
  # - if tf == false, returns only those validations defined in the current object/class
436
+ # deprecated
366
437
  def doodle_validations(tf = true)
367
- if tf
368
- # note: validations are handled differently to attributes and
369
- # conversions because ~all~ validations apply (so are stored
370
- # as an array), whereas attributes and conversions are keyed
371
- # by name and kind respectively, so only the most recent
372
- # applies
373
-
374
- doodle_local_validations + doodle_collect_inherited(:doodle_local_validations)
375
- else
376
- doodle_local_validations
377
- end
438
+ __doodle__.validations(tf)
378
439
  end
379
440
 
380
- # lookup a single attribute by name, searching the singleton class first
381
- def lookup_attribute(name)
382
- # (look at singleton attributes first)
383
- # fixme[this smells like a hack to me]
384
- if self.class == Class
385
- class_attributes[name]
386
- else
387
- doodle_attributes[name]
388
- end
389
- end
390
- private :lookup_attribute
391
-
392
441
  # either get an attribute value (if no args given) or set it
393
442
  # (using args and/or block)
443
+ # fixme: move
394
444
  def getter_setter(name, *args, &block)
395
445
  name = name.to_sym
396
446
  if block_given? || args.size > 0
@@ -404,52 +454,60 @@ class Doodle
404
454
  private :getter_setter
405
455
 
406
456
  # get an attribute by name - return default if not otherwise defined
457
+ # fixme: init deferred blocks are not getting resolved in all cases
407
458
  def _getter(name, &block)
459
+ #p [:_getter, name]
408
460
  ivar = "@#{name}"
409
461
  if instance_variable_defined?(ivar)
410
- #!p [:_getter, name, ivar, instance_variable_get(ivar)]
462
+ #p [:_getter, :instance_variable_defined, name, ivar, instance_variable_get(ivar)]
411
463
  instance_variable_get(ivar)
412
464
  else
413
465
  # handle default
414
466
  # Note: use :init => value to cover cases where defaults don't work
415
467
  # (e.g. arrays that disappear when you go out of scope)
416
- att = lookup_attribute(name)
468
+ att = __doodle__.lookup_attribute(name)
417
469
  # special case for class/singleton :init
418
- if att.init_defined?
419
- #!p [:_setter, att.init]
420
- _setter(name, att.init)
421
- elsif att.default_defined?
422
- case att.default
470
+ if att.optional?
471
+ optional_value = att.init_defined? ? att.init : att.default
472
+ #p [:optional_value, optional_value]
473
+ case optional_value
423
474
  when DeferredBlock
424
- instance_eval(&att.default.block)
475
+ #p [:deferred_block]
476
+ v = instance_eval(&optional_value.block)
425
477
  when Proc
426
- instance_eval(&att.default)
478
+ v = instance_eval(&optional_value)
427
479
  else
428
- att.default
480
+ v = optional_value
429
481
  end
482
+ if att.init_defined?
483
+ _setter(name, v)
484
+ end
485
+ v
430
486
  else
431
487
  # This is an internal error (i.e. shouldn't happen)
432
- handle_error name, NoDefaultError, "'#{name}' has no default defined", [caller[-1]]
488
+ __doodle__.handle_error name, NoDefaultError, "'#{name}' has no default defined", [caller[-1]]
433
489
  end
434
490
  end
435
491
  end
436
492
  private :_getter
437
493
 
438
494
  # set an attribute by name - apply validation if defined
495
+ # fixme: move
439
496
  def _setter(name, *args, &block)
440
497
  ##DBG: Doodle::Debug.d { [:_setter, name, args] }
498
+ #p [:_setter, name, *args]
441
499
  ivar = "@#{name}"
442
500
  if block_given?
443
501
  args.unshift(DeferredBlock.new(block))
444
502
  end
445
- if att = lookup_attribute(name)
503
+ if att = __doodle__.lookup_attribute(name)
446
504
  ##DBG: Doodle::Debug.d { [:_setter, name, args] }
447
- #!p [:_setter, :got_att, name, *args]
505
+ #p [:_setter, :got_att1, name, ivar, *args]
448
506
  v = instance_variable_set(ivar, att.validate(self, *args))
449
- #!p [:_setter, :got_att, name, :value, v]
507
+ #p [:_setter, :got_att2, name, ivar, :value, v]
450
508
  #v = instance_variable_set(ivar, *args)
451
509
  else
452
- #!p [:_setter, :no_att, name, *args]
510
+ #p [:_setter, :no_att, name, *args]
453
511
  ##DBG: Doodle::Debug.d { [:_setter, "no attribute"] }
454
512
  v = instance_variable_set(ivar, *args)
455
513
  end
@@ -464,7 +522,7 @@ class Doodle
464
522
  if block_given?
465
523
  # set the rule for each arg given
466
524
  args.each do |arg|
467
- doodle_local_conversions[arg] = block
525
+ __doodle__.local_conversions[arg] = block
468
526
  end
469
527
  else
470
528
  convert(self, *args)
@@ -475,9 +533,9 @@ class Doodle
475
533
  def must(constraint = 'be valid', &block)
476
534
  if block.nil?
477
535
  # is this really useful? do I really want it?
478
- doodle_local_validations << Validation.new(constraint, &proc { |v| v.instance_eval(constraint) })
536
+ __doodle__.local_validations << Validation.new(constraint, &proc { |v| v.instance_eval(constraint) })
479
537
  else
480
- doodle_local_validations << Validation.new(constraint, &block)
538
+ __doodle__.local_validations << Validation.new(constraint, &block)
481
539
  end
482
540
  end
483
541
 
@@ -486,19 +544,20 @@ class Doodle
486
544
  if args.size > 0
487
545
  # todo[figure out how to handle kind being specified twice?]
488
546
  @kind = args.first
489
- doodle_local_validations << (Validation.new("be #{@kind}") { |x| x.class <= @kind })
547
+ __doodle__.local_validations << (Validation.new("be #{@kind}") { |x| x.class <= @kind })
490
548
  else
491
549
  @kind
492
550
  end
493
551
  end
494
552
 
495
553
  # convert a value according to conversion rules
554
+ # fixme: move
496
555
  def convert(owner, *args)
497
556
  #!p [:convert, 1, owner, args]
498
557
  begin
499
558
  args = args.map do |value|
500
559
  #!p [:convert, 2, value]
501
- if (converter = doodle_conversions[value.class])
560
+ if (converter = __doodle__.conversions[value.class])
502
561
  #!p [:convert, 3, value]
503
562
  value = converter[*args]
504
563
  #!p [:convert, 4, value]
@@ -507,7 +566,7 @@ class Doodle
507
566
  # try to find nearest ancestor
508
567
  ancestors = value.class.ancestors
509
568
  #!p [:convert, 6, ancestors]
510
- matches = ancestors & doodle_conversions.keys
569
+ matches = ancestors & __doodle__.conversions.keys
511
570
  #!p [:convert, 7, matches]
512
571
  indexed_matches = matches.map{ |x| ancestors.index(x)}
513
572
  #!p [:convert, 8, indexed_matches]
@@ -515,7 +574,7 @@ class Doodle
515
574
  #!p [:convert, 9]
516
575
  converter_class = ancestors[indexed_matches.min]
517
576
  #!p [:convert, 10, converter_class]
518
- if converter = doodle_conversions[converter_class]
577
+ if converter = __doodle__.conversions[converter_class]
519
578
  #!p [:convert, 11, converter]
520
579
  value = converter[*args]
521
580
  #!p [:convert, 12, value]
@@ -525,7 +584,7 @@ class Doodle
525
584
  value
526
585
  end
527
586
  rescue Exception => e
528
- owner.handle_error name, ConversionError, "#{owner.kind_of?(Class) ? owner : owner.class} - #{e.to_s}", [caller[-1]]
587
+ owner.__doodle__.handle_error name, ConversionError, "#{owner.kind_of?(Class) ? owner : owner.class} - #{e.to_s}", [caller[-1]]
529
588
  end
530
589
  if args.size > 1
531
590
  args
@@ -535,21 +594,23 @@ class Doodle
535
594
  end
536
595
 
537
596
  # validate that args meet rules defined with +must+
597
+ # fixme: move
538
598
  def validate(owner, *args)
539
599
  ##DBG: Doodle::Debug.d { [:validate, self, :owner, owner, :args, args ] }
540
600
  #!p [:validate, :before_conversion, args]
541
601
  value = convert(owner, *args)
542
602
  #!p [:validate, :after_conversion, args, :becomes, value]
543
- doodle_validations.each do |v|
603
+ __doodle__.validations.each do |v|
544
604
  ##DBG: Doodle::Debug.d { [:validate, self, v, args, value] }
545
605
  if !v.block[value]
546
- owner.handle_error name, ValidationError, "#{owner.kind_of?(Class) ? owner : owner.class}.#{ name } must #{ v.message } - got #{ value.class }(#{ value.inspect })", [caller[-1]]
606
+ owner.__doodle__.handle_error name, ValidationError, "#{owner.kind_of?(Class) ? owner : owner.class}.#{ name } must #{ v.message } - got #{ value.class }(#{ value.inspect })", [caller[-1]]
547
607
  end
548
608
  end
549
609
  value
550
610
  end
551
611
 
552
612
  # define a getter_setter
613
+ # fixme: move
553
614
  def define_getter_setter(name, *args, &block)
554
615
  # need to use string eval because passing block
555
616
  sc_eval "def #{name}(*args, &block); getter_setter(:#{name}, *args, &block); end", __FILE__, __LINE__
@@ -591,70 +652,17 @@ class Doodle
591
652
  #
592
653
  def has(*args, &block)
593
654
  #DBG: Doodle::Debug.d { [:has, self, self.class, args] }
594
- # d { [:has2, name, args] }
595
-
596
- # fixme: this should be in generic Attribute - perhaps class method
597
- # how much of this can be handled by initialize_from_hash?
598
- # or at least in DoodleAttribute.from(params)
599
-
600
- key_values, positional_args = args.partition{ |x| x.kind_of?(Hash)}
601
- if positional_args.size > 0
602
- name = positional_args.shift.to_sym
603
- params = { :name => name }
604
- else
605
- params = { }
606
- end
607
- params = key_values.inject(params){ |acc, item| acc.merge(item)}
608
- #DBG: Doodle::Debug.d { [:has, self, self.class, params] }
609
- if !params.key?(:name)
610
- handle_error name, ArgumentError, "#{self.class} must have a name"
611
- else
612
- name = params[:name].to_sym
613
- end
614
- handle_error name, ArgumentError, "#{self.class} has too many arguments" if positional_args.size > 0
615
-
616
- if params.key?(:collect) && !params.key?(:using)
617
- if params.key?(:key)
618
- params[:using] = KeyedAttribute
619
- else
620
- params[:using] = AppendableAttribute
621
- end
622
- end
623
655
 
624
- if collector = params.delete(:collect)
625
- # this in generic CollectorAttribute class
626
- # collector from(Hash)
627
- if collector.kind_of?(Hash)
628
- collector_name, collector_class = collector.to_a[0]
629
- else
630
- # if Capitalized word given, treat as classname
631
- # and create collector for specific class
632
- collector_class = collector.to_s
633
- #p [:collector_klass, collector_klass]
634
- collector_name = Utils.snake_case(collector_class.split(/::/).last)
635
- #p [:collector_name, collector_name]
636
- if collector_class !~ /^[A-Z]/
637
- collector_class = nil
638
- end
639
- #!p [:collector_klass, collector_klass, params[:init]]
640
- end
641
- params[:collector_class] = collector_class
642
- params[:collector_name] = collector_name
643
- end
644
-
656
+ params = DoodleAttribute.params_from_args(self, *args)
645
657
  # get specialized attribute class or use default
646
658
  attribute_class = params.delete(:using) || DoodleAttribute
647
659
 
648
660
  # could this be handled in DoodleAttribute?
649
661
  # define getter setter before setting up attribute
650
- define_getter_setter name, *args, &block
651
- params[:doodle_owner] = self
662
+ define_getter_setter params[:name], *args, &block
652
663
  #p [:attribute, attribute_class, params]
653
- doodle_local_attributes[name] = attribute = attribute_class.new(params, &block)
654
-
655
- attribute
664
+ __doodle__.local_attributes[params[:name]] = attribute_class.new(params, &block)
656
665
  end
657
-
658
666
 
659
667
  # define order for positional arguments
660
668
  def arg_order(*args)
@@ -662,32 +670,44 @@ class Doodle
662
670
  begin
663
671
  args = args.uniq
664
672
  args.each do |x|
665
- handle_error :arg_order, ArgumentError, "#{x} not a Symbol" if !(x.class <= Symbol)
666
- handle_error :arg_order, NameError, "#{x} not an attribute name" if !doodle_attributes.keys.include?(x)
673
+ __doodle__.handle_error :arg_order, ArgumentError, "#{x} not a Symbol", [caller[-1]] if !(x.class <= Symbol)
674
+ __doodle__.handle_error :arg_order, NameError, "#{x} not an attribute name", [caller[-1]] if !doodle_attributes.keys.include?(x)
667
675
  end
668
676
  __doodle__.arg_order = args
669
677
  rescue Exception => e
670
- handle_error :arg_order, InvalidOrderError, e.to_s, [caller[-1]]
678
+ __doodle__.handle_error :arg_order, InvalidOrderError, e.to_s, [caller[-1]]
671
679
  end
672
680
  else
673
- __doodle__.arg_order + (doodle_attributes.keys - __doodle__.arg_order)
681
+ __doodle__.arg_order + (__doodle__.attributes.keys - __doodle__.arg_order)
674
682
  end
675
683
  end
676
684
 
685
+ # fixme: move
677
686
  def get_init_values(tf = true)
678
- doodle_attributes(tf).select{|n, a| a.init_defined? }.inject({}) {|hash, (n, a)|
679
- #!p [:get_init_values, a.init]
680
- hash[n] = begin
681
- case a.init
682
- when NilClass, TrueClass, FalseClass, Fixnum
683
- a.init
684
- when DeferredBlock
687
+ __doodle__.attributes(tf).select{|n, a| a.init_defined? }.inject({}) {|hash, (n, a)|
688
+ #p [:get_init_values, a.name]
689
+ hash[n] = case a.init
690
+ when NilClass, TrueClass, FalseClass, Fixnum, Float, Bignum
691
+ # uncloneable values
692
+ #p [:get_init_values, :special, a.name, a.init]
693
+ a.init
694
+ when DeferredBlock
695
+ #p [:get_init_values, self, DeferredBlock, a.name]
696
+ begin
685
697
  instance_eval(&a.init.block)
686
- else
687
- a.init.clone
698
+ rescue Object => e
699
+ #p [:exception_in_deferred_block, e]
700
+ raise
701
+ end
702
+ else
703
+ #p [:get_init_values, :clone, a.name]
704
+ begin
705
+ a.init.clone
706
+ rescue Exception => e
707
+ warn "tried to clone #{a.init.class} in :init option"
708
+ #p [:get_init_values, :exception, a.name, e]
709
+ a.init
688
710
  end
689
- rescue Exception => e
690
- a.init
691
711
  end
692
712
  hash
693
713
  }
@@ -695,6 +715,7 @@ class Doodle
695
715
  private :get_init_values
696
716
 
697
717
  # return true if instance variable +name+ defined
718
+ # fixme: move
698
719
  def ivar_defined?(name)
699
720
  instance_variable_defined?("@#{name}")
700
721
  end
@@ -705,14 +726,14 @@ class Doodle
705
726
  def validate!(all = true)
706
727
  ##DBG: Doodle::Debug.d { [:validate!, all, caller] }
707
728
  if all
708
- clear_errors
729
+ __doodle__.errors.clear
709
730
  end
710
731
  if __doodle__.validation_on
711
732
  if self.class == Class
712
- attribs = class_attributes
733
+ attribs = __doodle__.class_attributes
713
734
  ##DBG: Doodle::Debug.d { [:validate!, "using class_attributes", class_attributes] }
714
735
  else
715
- attribs = doodle_attributes
736
+ attribs = __doodle__.attributes
716
737
  ##DBG: Doodle::Debug.d { [:validate!, "using instance_attributes", doodle_attributes] }
717
738
  end
718
739
  attribs.each do |name, att|
@@ -729,21 +750,21 @@ class Doodle
729
750
  ##DBG: Doodle::Debug.d { [:validate!, :optional, name ]}
730
751
  break
731
752
  elsif self.class != Class
732
- handle_error name, Doodle::ValidationError, "#{self} missing required attribute '#{name}'", [caller[-1]]
753
+ __doodle__.handle_error name, Doodle::ValidationError, "#{self} missing required attribute '#{name}'", [caller[-1]]
733
754
  end
734
755
  end
735
756
 
736
757
  # now apply instance level validations
737
758
 
738
759
  ##DBG: Doodle::Debug.d { [:validate!, "validations", doodle_validations ]}
739
- doodle_validations.each do |v|
760
+ __doodle__.validations.each do |v|
740
761
  ##DBG: Doodle::Debug.d { [:validate!, self, v ] }
741
762
  begin
742
763
  if !instance_eval(&v.block)
743
- handle_error self, ValidationError, "#{ self.class } must #{ v.message }", [caller[-1]]
764
+ __doodle__.handle_error self, ValidationError, "#{ self.class } must #{ v.message }", [caller[-1]]
744
765
  end
745
766
  rescue Exception => e
746
- handle_error self, ValidationError, e.to_s, [caller[-1]]
767
+ __doodle__.handle_error self, ValidationError, e.to_s, [caller[-1]]
747
768
  end
748
769
  end
749
770
  end
@@ -753,6 +774,7 @@ class Doodle
753
774
 
754
775
  # turn off validation, execute block, then set validation to same
755
776
  # state as it was before +defer_validation+ was called - can be nested
777
+ # fixme: move
756
778
  def defer_validation(&block)
757
779
  old_validation = __doodle__.validation_on
758
780
  __doodle__.validation_on = false
@@ -769,6 +791,7 @@ class Doodle
769
791
  # helper function to initialize from hash - this is safe to use
770
792
  # after initialization (validate! is called if this method is
771
793
  # called after initialization)
794
+ # fixme?
772
795
  def doodle_initialize_from_hash(*args)
773
796
  #!p [:doodle_initialize_from_hash, :args, *args]
774
797
  defer_validation do
@@ -779,11 +802,12 @@ class Doodle
779
802
  #!p [self.class, :doodle_initialize_from_hash, :key_values, key_values, :args, args]
780
803
 
781
804
  # set up initial values with ~clones~ of specified values (so not shared between instances)
782
- init_values = get_init_values
805
+ #init_values = get_init_values
783
806
  #!p [:init_values, init_values]
784
807
 
785
808
  # match up positional args with attribute names (from arg_order) using idiom to create hash from array of assocs
786
- arg_keywords = init_values.merge(Hash[*(Utils.flatten_first_level(self.class.arg_order[0...args.size].zip(args)))])
809
+ #arg_keywords = init_values.merge(Hash[*(Utils.flatten_first_level(self.class.arg_order[0...args.size].zip(args)))])
810
+ arg_keywords = Hash[*(Utils.flatten_first_level(self.class.arg_order[0...args.size].zip(args)))]
787
811
  #!p [self.class, :doodle_initialize_from_hash, :arg_keywords, arg_keywords]
788
812
 
789
813
  # merge all hash args into one
@@ -805,12 +829,20 @@ class Doodle
805
829
  # create attributes
806
830
  key_values.keys.each do |key|
807
831
  #DBG: Doodle::Debug.d { [self.class, :doodle_initialize_from_hash, :setting, key, key_values[key]] }
808
- #!p [self.class, :doodle_initialize_from_hash, :setting, key, key_values[key]]
832
+ #p [self.class, :doodle_initialize_from_hash, :setting, key, key_values[key]]
809
833
  if respond_to?(key)
810
834
  __send__(key, key_values[key])
811
835
  else
812
836
  # raise error if not defined
813
- handle_error key, Doodle::UnknownAttributeError, "unknown attribute '#{key}' #{key_values[key].inspect}"
837
+ __doodle__.handle_error key, Doodle::UnknownAttributeError, "unknown attribute '#{key}' #{key_values[key].inspect}", [caller[-1]]
838
+ end
839
+ end
840
+ # do init_values after user supplied values so init blocks can depend on user supplied values
841
+ #p [:getting_init_values, instance_variables]
842
+ init_values = get_init_values
843
+ init_values.each do |key, value|
844
+ if !key_values.key?(key) && respond_to?(key)
845
+ __send__(key, value)
814
846
  end
815
847
  end
816
848
  end
@@ -819,8 +851,9 @@ class Doodle
819
851
 
820
852
  # return containing object (set during initialization)
821
853
  # (named doodle_parent to avoid clash with ActiveSupport)
854
+ # fixme: move
822
855
  def doodle_parent
823
- __doodle__.doodle_parent
856
+ __doodle__.parent
824
857
  end
825
858
 
826
859
  # object can be initialized from a mixture of positional arguments,
@@ -831,7 +864,7 @@ class Doodle
831
864
  super
832
865
  end
833
866
  __doodle__.validation_on = true
834
- __doodle__.doodle_parent = Doodle.context[-1]
867
+ __doodle__.parent = Doodle.context[-1]
835
868
  Doodle.context.push(self)
836
869
  defer_validation do
837
870
  doodle_initialize_from_hash(*args)
@@ -871,10 +904,10 @@ class Doodle
871
904
  method_defined = begin
872
905
  method(name)
873
906
  true
874
- rescue
907
+ rescue Object
875
908
  false
876
909
  end
877
-
910
+
878
911
  if name =~ Factory::RX_IDENTIFIER && !method_defined && !klass.respond_to?(name) && !eval("respond_to?(:#{name})", TOPLEVEL_BINDING)
879
912
  eval("def #{ name }(*args, &block); ::#{name}.new(*args, &block); end", ::TOPLEVEL_BINDING, __FILE__, __LINE__)
880
913
  end
@@ -926,7 +959,65 @@ class Doodle
926
959
  # It is used to provide a context for defining #must and #from rules
927
960
  #
928
961
  class DoodleAttribute < Doodle
929
- # todo[want to design Attribute so it's extensible, e.g. to specific datatypes & built-in validations]
962
+ # note: using extend with a module causes an infinite loop in 1.9
963
+ # hence the inline
964
+ class << self
965
+ # rewrite rules for the argument list to #has
966
+ def params_from_args(owner, *args)
967
+ key_values, positional_args = args.partition{ |x| x.kind_of?(Hash)}
968
+ params = { }
969
+ if positional_args.size > 0
970
+ name = positional_args.shift
971
+ case name
972
+ # has Person --> has :person, :kind => Person
973
+ when Class
974
+ params[:name] = Utils.snake_case(name.to_s.split(/::/).last)
975
+ params[:kind] = name
976
+ else
977
+ params[:name] = name.to_s.to_sym
978
+ end
979
+ end
980
+ params = key_values.inject(params){ |acc, item| acc.merge(item)}
981
+ #DBG: Doodle::Debug.d { [:has, self, self.class, params] }
982
+ if !params.key?(:name)
983
+ __doodle__.handle_error name, ArgumentError, "#{self.class} must have a name", [caller[-1]]
984
+ else
985
+ name = params[:name].to_sym
986
+ end
987
+ __doodle__.handle_error name, ArgumentError, "#{self.class} has too many arguments", [caller[-1]] if positional_args.size > 0
988
+
989
+ if collector = params.delete(:collect)
990
+ if !params.key?(:using)
991
+ if params.key?(:key)
992
+ params[:using] = KeyedAttribute
993
+ else
994
+ params[:using] = AppendableAttribute
995
+ end
996
+ end
997
+ # this in generic CollectorAttribute class
998
+ # collector from(Hash)
999
+ if collector.kind_of?(Hash)
1000
+ collector_name, collector_class = collector.to_a[0]
1001
+ else
1002
+ # if Capitalized word given, treat as classname
1003
+ # and create collector for specific class
1004
+ collector_class = collector.to_s
1005
+ #p [:collector_klass, collector_klass]
1006
+ collector_name = Utils.snake_case(collector_class.split(/::/).last)
1007
+ #p [:collector_name, collector_name]
1008
+ if collector_class !~ /^[A-Z]/
1009
+ collector_class = nil
1010
+ end
1011
+ #!p [:collector_klass, collector_klass, params[:init]]
1012
+ end
1013
+ params[:collector_class] = collector_class
1014
+ params[:collector_name] = collector_name
1015
+ end
1016
+ params[:doodle_owner] = owner
1017
+ params
1018
+ end
1019
+ end
1020
+
930
1021
  # must define these methods before using them in #has below
931
1022
 
932
1023
  # hack: bump off +validate!+ for Attributes - maybe better way of doing
@@ -995,7 +1086,7 @@ class Doodle
995
1086
  def resolve_value(value)
996
1087
  if value.kind_of?(collector_class)
997
1088
  value
998
- elsif collector_class.doodle_conversions.key?(value.class)
1089
+ elsif collector_class.__doodle__.conversions.key?(value.class)
999
1090
  collector_class.from(value)
1000
1091
  else
1001
1092
  collector_class.new(value)
@@ -3,40 +3,6 @@ $:.unshift(File.join(File.dirname(__FILE__), '.'))
3
3
 
4
4
  require 'doodle'
5
5
 
6
- class Doodle
7
- class DataTypeHolder
8
- attr_accessor :klass
9
- def initialize(klass, &block)
10
- @klass = klass
11
- instance_eval(&block) if block_given?
12
- end
13
- def define(name, params, block, type_params, &type_block)
14
- @klass.class_eval {
15
- td = has(name, type_params.merge(params), &type_block)
16
- td.instance_eval(&block) if block
17
- td
18
- }
19
- end
20
- end
21
-
22
- # set up global datatypes
23
- def self.datatypes(*mods)
24
- mods.each do |mod|
25
- DataTypeHolder.class_eval { include mod }
26
- end
27
- end
28
-
29
- # enable global datatypes and provide an interface that allows you
30
- # to add your own datatypes to this declaration
31
- def self.doodle(*mods, &block)
32
- dh = Doodle::DataTypeHolder.new(self)
33
- mods.each do |mod|
34
- dh.extend(mod)
35
- end
36
- dh.instance_eval(&block)
37
- end
38
- end
39
-
40
6
  ### user code
41
7
  require 'date'
42
8
  require 'uri'
@@ -2,7 +2,7 @@ class Doodle #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
4
  MINOR = 1
5
- TINY = 6
5
+ TINY = 7
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -0,0 +1,36 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ describe Doodle, 'has Class' do
4
+ temporary_constant :Foo, :Bar do
5
+ it "should convert 'has Bar' into 'has :bar, :kind => Bar'" do
6
+ class Bar
7
+ end
8
+ class Foo < Doodle
9
+ has Bar
10
+ end
11
+ att = Foo.doodle_attributes.values.first
12
+ att.name.should_be :bar
13
+ att.kind.should_be Bar
14
+ end
15
+ it "should allow overriding name of attribute when using 'has Bar'" do
16
+ class Bar
17
+ end
18
+ class Foo < Doodle
19
+ has Bar, :name => :baz
20
+ end
21
+ att = Foo.doodle_attributes.values.first
22
+ att.name.should_be :baz
23
+ att.kind.should_be Bar
24
+ end
25
+ it "should convert class name to snakecase when using CamelCase class constant" do
26
+ class AudioClip
27
+ end
28
+ class Foo < Doodle
29
+ has AudioClip
30
+ end
31
+ att = Foo.doodle_attributes.values.first
32
+ att.name.should_be :audio_clip
33
+ att.kind.should_be AudioClip
34
+ end
35
+ end
36
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: doodle
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean O'Halpin
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-05-08 00:00:00 +01:00
12
+ date: 2008-05-12 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -145,4 +145,5 @@ test_files:
145
145
  - spec/conversion_spec.rb
146
146
  - spec/class_validation_spec.rb
147
147
  - spec/doodle_spec.rb
148
+ - spec/has_spec.rb
148
149
  - spec/doodle_context_spec.rb