doodle 0.1.6 → 0.1.7
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 +18 -0
- data/examples/parent.rb +1 -1
- data/lib/doodle.rb +332 -241
- data/lib/doodle/datatypes.rb +0 -34
- data/lib/doodle/version.rb +1 -1
- data/spec/has_spec.rb +36 -0
- metadata +3 -2
data/History.txt
CHANGED
@@ -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
|
data/examples/parent.rb
CHANGED
data/lib/doodle.rb
CHANGED
@@ -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 :
|
212
|
-
attr_accessor :
|
213
|
-
attr_accessor :
|
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 :
|
179
|
+
attr_accessor :parent
|
218
180
|
|
219
181
|
def initialize(object)
|
220
|
-
@
|
221
|
-
@
|
182
|
+
@this = object
|
183
|
+
@local_attributes = OrderedHash.new
|
184
|
+
@local_validations = []
|
222
185
|
@validation_on = true
|
223
|
-
@
|
186
|
+
@local_conversions = {}
|
224
187
|
@arg_order = []
|
225
188
|
@errors = []
|
226
|
-
@
|
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
|
-
|
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
|
-
|
277
|
-
|
278
|
-
|
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
|
-
|
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__.
|
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
|
-
|
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
|
333
|
-
|
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__.
|
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
|
-
|
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__.
|
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
|
-
|
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
|
-
|
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.
|
419
|
-
|
420
|
-
|
421
|
-
|
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
|
-
|
475
|
+
#p [:deferred_block]
|
476
|
+
v = instance_eval(&optional_value.block)
|
425
477
|
when Proc
|
426
|
-
instance_eval(&
|
478
|
+
v = instance_eval(&optional_value)
|
427
479
|
else
|
428
|
-
|
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
|
-
|
505
|
+
#p [:_setter, :got_att1, name, ivar, *args]
|
448
506
|
v = instance_variable_set(ivar, att.validate(self, *args))
|
449
|
-
|
507
|
+
#p [:_setter, :got_att2, name, ivar, :value, v]
|
450
508
|
#v = instance_variable_set(ivar, *args)
|
451
509
|
else
|
452
|
-
|
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
|
-
|
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
|
-
|
536
|
+
__doodle__.local_validations << Validation.new(constraint, &proc { |v| v.instance_eval(constraint) })
|
479
537
|
else
|
480
|
-
|
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
|
-
|
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 =
|
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 &
|
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 =
|
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
|
-
|
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
|
-
|
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
|
-
|
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 + (
|
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
|
-
|
679
|
-
|
680
|
-
hash[n] =
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
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
|
-
|
687
|
-
|
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
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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__.
|
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__.
|
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
|
-
#
|
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.
|
1089
|
+
elsif collector_class.__doodle__.conversions.key?(value.class)
|
999
1090
|
collector_class.from(value)
|
1000
1091
|
else
|
1001
1092
|
collector_class.new(value)
|
data/lib/doodle/datatypes.rb
CHANGED
@@ -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'
|
data/lib/doodle/version.rb
CHANGED
data/spec/has_spec.rb
ADDED
@@ -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.
|
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-
|
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
|